state.currentMode, shallow);
+ const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
const setModalOpenOnCanvas = useStore((state) => state.setModalOpenOnCanvas);
/**** Start - Logic to reset the zIndex of modal control box ****/
diff --git a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx
index bfe1431ad0..01c07cf49f 100644
--- a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx
+++ b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx
@@ -16,6 +16,7 @@ import { createModalStyles } from '@/AppBuilder/Widgets/ModalV2/helpers/stylesFa
import { onShowSideEffects, onHideSideEffects } from '@/AppBuilder/Widgets/ModalV2/helpers/sideEffects';
import '@/AppBuilder/Widgets/ModalV2/style.scss';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
export const ModalV2 = function Modal({
id,
@@ -29,6 +30,7 @@ export const ModalV2 = function Modal({
dataCy,
height,
}) {
+ const { moduleId } = useModuleContext();
const [showModal, setShowModal] = useState(false);
const {
closeOnClickingOutside = false,
@@ -56,7 +58,7 @@ export const ModalV2 = function Modal({
const titleAlignment = properties.titleAlignment ?? 'left';
const size = properties.size ?? 'lg';
const setSelectedComponentAsModal = useStore((state) => state.setSelectedComponentAsModal, shallow);
- const mode = useStore((state) => state.currentMode, shallow);
+ const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
const computedModalBodyHeight = getModalBodyHeight(modalHeight, showHeader, showFooter, headerHeight, footerHeight);
const headerHeightPx = getModalHeaderHeight(showHeader, headerHeight);
diff --git a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx
index c5f1328aee..7615f9f585 100644
--- a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx
+++ b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx
@@ -9,9 +9,11 @@ import useTableStore from './_stores/tableStore';
import TableContainer from './_components/TableContainer';
import { transformTableData } from './_utils/transformTableData';
import { usePrevious } from '@dnd-kit/utilities';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
export const Table = memo(
({ id, componentName, width, height, properties, styles, darkMode, fireEvent, setExposedVariables }) => {
+ const { moduleId } = useModuleContext();
// get table store functions
const initializeComponent = useTableStore((state) => state.initializeComponent, shallow);
const removeComponent = useTableStore((state) => state.removeComponent, shallow);
@@ -83,7 +85,8 @@ export const Table = memo(
firstRowOfTable,
autogenerateColumns,
columnDeletionHistory,
- shouldAutogenerateColumns.current
+ shouldAutogenerateColumns.current,
+ moduleId
);
shouldAutogenerateColumns.current = false;
}, [
@@ -94,6 +97,7 @@ export const Table = memo(
setColumnDetails,
firstRowOfTable,
autogenerateColumns,
+ moduleId,
columnDeletionHistory,
]);
diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx
index 6ec4f66994..93f0721366 100644
--- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx
+++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx
@@ -7,6 +7,7 @@ import { filterFunctions } from '../Header/_components/Filter/filterUtils';
import { isArray, debounce } from 'lodash';
import { useMounted } from '@/_hooks/use-mount';
import { usePrevious } from '@dnd-kit/utilities';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
// Component to expose variables & fire events from the table
// It might miss some variables which are tightly coupled with the component state
export const TableExposedVariables = ({
@@ -19,6 +20,7 @@ export const TableExposedVariables = ({
pageIndex = 1,
lastClickedRow,
}) => {
+ const { moduleId } = useModuleContext();
const editedRows = useTableStore((state) => state.getAllEditedRows(id), shallow);
const editedFields = useTableStore((state) => state.getAllEditedFields(id), shallow);
const addNewRowDetails = useTableStore((state) => state.getAllAddNewRowDetails(id), shallow);
@@ -314,7 +316,7 @@ export const TableExposedVariables = ({
// Create debounced function using useRef to persist between renders
const debouncedSetProperty = useRef(
debounce((sizing) => {
- setComponentProperty(id, 'columnSizes', sizing, 'properties');
+ setComponentProperty(id, 'columnSizes', sizing, 'properties', 'value', false, moduleId);
}, 300)
).current;
diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js b/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js
index 6663f4f2f9..2ea3a69bc5 100644
--- a/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js
+++ b/frontend/src/AppBuilder/Widgets/NewTable/_stores/slices/columnSlice.js
@@ -14,7 +14,8 @@ export const createColumnSlice = (set, get) => ({
firstRowOfTable,
autogenerateColumnsFlag,
columnDeletionHistory,
- shouldAutogenerateColumns
+ shouldAutogenerateColumns,
+ moduleId = 'canvas'
) => {
set(
(state) => {
@@ -33,7 +34,8 @@ export const createColumnSlice = (set, get) => ({
isDynamicColumnSelected,
autogenerateColumnsFlag,
columnDeletionHistory,
- columnData
+ columnData,
+ moduleId
);
state.components[id].columnDetails.columnProperties = columnProperties;
state.components[id].columnDetails.transformations = get().generateColumnTransformations(
@@ -60,7 +62,8 @@ export const createColumnSlice = (set, get) => ({
isDynamicColumnSelected,
autogenerateColumnsFlag,
columnDeletionHistory,
- columnData
+ columnData,
+ moduleId
) => {
if (autogenerateColumnsFlag) {
const setComponentProperty = useStore.getState().setComponentProperty;
@@ -75,7 +78,7 @@ export const createColumnSlice = (set, get) => ({
);
if (!isDynamicColumnSelected && !isEqual(existingGeneratedColumn, generatedColumns)) {
- setComponentProperty(id, 'columns', generatedColumns, 'properties', 'value', false, 'canvas', {
+ setComponentProperty(id, 'columns', generatedColumns, 'properties', 'value', false, moduleId, {
skipUndoRedo: true,
saveAfterAction: true,
});
diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx
index 7d378e7d70..5921fd0e34 100644
--- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx
+++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx
@@ -49,6 +49,7 @@ import { EmptyState } from './Components/EmptyState';
import { LoadingState } from './Components/LoadingState';
// eslint-disable-next-line import/no-unresolved
import { useVirtualizer } from '@tanstack/react-virtual';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
// utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row
const utilityForNestedNewRow = (row) => {
@@ -87,11 +88,12 @@ export const Table = React.memo(
// events,
// setProperty,
}) => {
- const component = useStore((state) => state.getComponentDefinition(id), shallow);
+ const { moduleId } = useModuleContext();
+ const component = useStore((state) => state.getComponentDefinition(id, moduleId), shallow);
const exposedNewRows = useStore((state) => state.getExposedValueOfComponent(id)?.newRows || [], shallow);
const validateWidget = useStore((state) => state.validateWidget, shallow);
const validateDates = useStore((state) => state.validateDates, shallow);
- const mode = useStore((state) => state.currentMode);
+ const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode);
const {
color,
diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx
index 1c8e64a843..d0f2b96c31 100644
--- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx
+++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx
@@ -2,16 +2,51 @@ import React, { createContext, useContext } from 'react';
export const ModuleContext = createContext();
-export const ModuleProvider = ({ moduleId, children }) => {
- return
{children} ;
+export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor, children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useModuleContext = () => {
+ const context = useContext(ModuleContext);
+ if (!context) {
+ throw new Error('useModuleContext must be used within a ModuleProvider');
+ }
+ return context;
};
export const useModuleId = () => {
const context = useContext(ModuleContext);
-
if (!context) {
throw new Error('useModuleId must be used within a ModuleProvider');
}
- return context;
+ return context.moduleId;
+};
+
+export const useIsModuleMode = () => {
+ const context = useContext(ModuleContext);
+ if (!context) {
+ throw new Error('useIsModuleMode must be used within a ModuleProvider');
+ }
+ return context.isModuleMode;
+};
+
+export const useAppType = () => {
+ const context = useContext(ModuleContext);
+ if (!context) {
+ throw new Error('useAppType must be used within a ModuleProvider');
+ }
+ return context.appType;
+};
+
+export const useIsModuleEditor = () => {
+ const context = useContext(ModuleContext);
+ if (!context) {
+ throw new Error('useIsModuleEditor must be used within a ModuleProvider');
+ }
+ return context.isModuleEditor;
};
diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js
index 183c5c1cec..f6c00e60a0 100644
--- a/frontend/src/AppBuilder/_helpers/editorHelpers.js
+++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js
@@ -71,6 +71,9 @@ import { Form } from '@/AppBuilder/Widgets/Form/Form';
import { Modal } from '@/AppBuilder/Widgets/Modal';
import { ModalV2 } from '@/AppBuilder/Widgets/ModalV2/ModalV2';
import { Calendar } from '@/AppBuilder/Widgets/Calendar/Calendar';
+
+import { ModuleContainer, ModuleViewer } from '@/modules/Modules/components';
+
// import './requestIdleCallbackPolyfill';
export function memoizeFunction(func) {
@@ -152,6 +155,8 @@ export const AllComponents = {
Form,
BoundedBox,
ToggleSwitchV2,
+ ModuleContainer,
+ ModuleViewer,
};
if (isPDFSupported()) {
AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF);
diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js
index 433a677dc7..f1331e5768 100644
--- a/frontend/src/AppBuilder/_hooks/useAppData.js
+++ b/frontend/src/AppBuilder/_hooks/useAppData.js
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import {
appEnvironmentService,
appService,
+ appsService,
appVersionService,
dataqueryService,
datasourceService,
@@ -27,6 +28,7 @@ import { distinctUntilChanged } from 'rxjs';
import { baseTheme, convertAllKeysToSnakeCase } from '../_stores/utils';
import { getPreviewQueryParams } from '@/_helpers/routes';
import { useLocation, useMatch, useParams } from 'react-router-dom';
+import { useMounted } from '@/_hooks/use-mount';
import useThemeAccess from './useThemeAccess';
import { handleError } from '@/_helpers/handleAppAccess';
import toast from 'react-hot-toast';
@@ -56,12 +58,22 @@ const normalizeQueryTransformationOptions = (query) => {
return query;
};
-const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, versionId } = {}) => {
+const useAppData = (
+ appId,
+ moduleId,
+ darkMode,
+ mode = 'edit',
+ { environmentId, versionId } = {},
+ moduleMode = false
+) => {
+ const mounted = useMounted();
+ const initModules = useStore((state) => state.initModules, shallow);
+ moduleMode && !mounted && initModules(moduleId);
const { state } = useLocation();
const [currentSession, setCurrentSession] = useState();
const setEditorLoading = useStore((state) => state.setEditorLoading);
const setApp = useStore((state) => state.setApp);
- const app = useStore((state) => state.app);
+ const app = useStore((state) => state.appStore.modules[moduleId].app);
const user = useStore((state) => state.user);
const setCurrentVersionId = useStore((state) => state.setCurrentVersionId);
const currentVersionId = useStore((state) => state.currentVersionId);
@@ -79,9 +91,9 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
// const fetchDataSources = useStore((state) => state.fetchDataSources);
const fetchGlobalDataSources = useStore((state) => state.fetchGlobalDataSources);
const previousVersion = usePrevious(currentVersionId);
- const events = useStore((state) => state.eventsSlice.module[moduleId].events);
- const pages = useStore((state) => state.modules[moduleId].pages);
- const currentPageId = useStore((state) => state.currentPageId);
+ const events = useStore((state) => state.eventsSlice.module[moduleId]?.events || []);
+ const pages = useStore((state) => state.modules[moduleId]?.pages || []);
+ const currentPageId = useStore((state) => state.modules[moduleId].currentPageId);
const setResolvedConstants = useStore((state) => state.setResolvedConstants);
const setSecrets = useStore((state) => state.setSecrets);
const setQueryMapping = useStore((state) => state.setQueryMapping);
@@ -106,11 +118,18 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
const appMode = useStore((state) => state.globalSettings.appMode);
const selectedTheme = useStore((state) => state.globalSettings.theme);
const previousEnvironmentId = usePrevious(selectedEnvironment?.id);
- const isComponentLayoutReady = useStore((state) => state.isComponentLayoutReady, shallow);
+ const isComponentLayoutReady = useStore((state) => state.appStore.modules[moduleId].isComponentLayoutReady, shallow);
const pageSwitchInProgress = useStore((state) => state.pageSwitchInProgress);
const setPageSwitchInProgress = useStore((state) => state.setPageSwitchInProgress);
const selectedVersion = useStore((state) => state.selectedVersion);
const setIsPublicAccess = useStore((state) => state.setIsPublicAccess);
+
+ const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow);
+ const setModulesList = useStore((state) => state.setModulesList, shallow);
+ const setModuleDefinition = useStore((state) => state.setModuleDefinition);
+ const getModuleDefinition = useStore((state) => state.getModuleDefinition);
+ const deleteModuleDefinition = useStore((state) => state.deleteModuleDefinition);
+
const themeAccess = useThemeAccess();
const setConversation = useStore((state) => state.ai?.setConversation);
@@ -121,7 +140,8 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
const setSelectedSidebarItem = useStore((state) => state.setSelectedSidebarItem);
const toggleLeftSidebar = useStore((state) => state.toggleLeftSidebar);
const pathParams = useParams();
- const slug = pathParams?.slug;
+ const slug = moduleMode ? '' : pathParams?.slug;
+ const licenseStatus = useStore((state) => state.isLicenseValid());
const match = useMatch('/applications/:slug/:pageHandle');
@@ -129,6 +149,14 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
const initialLoadRef = useRef(true);
+ const appTypeRef = useRef(null);
+ const { isReleasedVersionId } = useStore(
+ (state) => ({
+ isReleasedVersionId: state?.releasedVersionId == state.currentVersionId || state.isVersionReleased,
+ }),
+ shallow
+ );
+
const fetchAndInjectCustomStyles = async (isPublicAccess = false) => {
try {
const head = document.head || document.getElementsByTagName('head')[0];
@@ -153,14 +181,14 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
};
useEffect(() => {
- if (pageSwitchInProgress) {
+ if (pageSwitchInProgress && !moduleMode) {
const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId);
setPageSwitchInProgress(false);
setTimeout(() => {
handleEvent('onPageLoad', currentPageEvents, {});
}, 0);
}
- }, [pageSwitchInProgress, currentPageId]);
+ }, [pageSwitchInProgress, currentPageId, moduleMode]);
useEffect(() => {
const subscription = authenticationService.currentSession
@@ -190,268 +218,317 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
return () => {
subscription && subscription.unsubscribe();
};
- }, []);
+ }, [moduleMode]);
useEffect(() => {
const exposedTheme =
appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light';
- setResolvedGlobals('theme', { name: exposedTheme });
- }, [appMode, darkMode]);
+ setResolvedGlobals('theme', { name: exposedTheme }, moduleId);
+ }, [appMode, darkMode, moduleId]);
useEffect(() => {
if (!currentSession) {
return;
}
- const queryParams = getPreviewQueryParams();
- const isPublicAccess =
- (currentSession?.load_app && currentSession?.authentication_failed) || (!queryParams.version && mode !== 'edit');
- const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess;
let appDataPromise;
- if (isPublicAccess) {
- appDataPromise = appService.fetchAppBySlug(slug);
+ const queryParams = moduleMode ? {} : getPreviewQueryParams();
+ const isPublicAccess = moduleMode
+ ? false
+ : (currentSession?.load_app && currentSession?.authentication_failed) ||
+ (!queryParams.version && mode !== 'edit');
+ const isPreviewForVersion = (mode !== 'edit' && queryParams.version) || isPublicAccess;
+
+ if (moduleMode) {
+ const moduleDefinition = getModuleDefinition(appId);
+ if (moduleDefinition) {
+ // clean up the module definition from the store
+ deleteModuleDefinition(appId);
+ appDataPromise = Promise.resolve(moduleDefinition);
+ } else {
+ appDataPromise = appService.fetchApp(appId);
+ }
} else {
- appDataPromise = isPreviewForVersion
- ? appVersionService.getAppVersionData(appId, versionId)
- : appService.fetchApp(appId);
+ if (isPublicAccess) {
+ appDataPromise = appService.fetchAppBySlug(slug);
+ } else {
+ appDataPromise = isPreviewForVersion
+ ? appVersionService.getAppVersionData(appId, versionId)
+ : appService.fetchApp(appId);
+ }
}
// const appDataPromise = appService.fetchApp(appId);
- appDataPromise.then(async (result) => {
- let appData = { ...result };
- let editorEnvironment = result.editorEnvironment;
- if (isPreviewForVersion) {
- const rawDataQueries = appData?.data_queries;
- const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
- appData = convertAllKeysToSnakeCase(appData);
+ appDataPromise
+ .then(async (result) => {
+ let appData = { ...result };
+ let editorEnvironment = result.editorEnvironment;
+ if (isPreviewForVersion) {
+ const rawDataQueries = appData?.data_queries;
+ const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
+ appData = convertAllKeysToSnakeCase(appData);
- appData.data_queries = rawDataQueries;
- if (appData.editing_version && rawEditingVersionDataQueries) {
- appData.editing_version.data_queries = rawEditingVersionDataQueries;
- }
+ appData.data_queries = rawDataQueries;
+ if (appData.editing_version && rawEditingVersionDataQueries) {
+ appData.editing_version.data_queries = rawEditingVersionDataQueries;
+ }
- editorEnvironment = {
- id: environmentId,
- name: queryParams.env,
- };
- }
-
- let constantsResp;
- if (mode !== 'edit') {
- try {
- const queryParams = { slug: slug };
- const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams);
editorEnvironment = {
- id: viewerEnvironment?.environment?.id,
- name: viewerEnvironment?.environment?.name,
+ id: environmentId,
+ name: queryParams.env,
};
- constantsResp =
- isPublicAccess && appData.is_public
- ? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug, viewerEnvironment?.environment?.id)
- : await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
- } catch (error) {
- console.error('Error fetching viewer environment:', error);
}
- }
- if (mode === 'edit') {
- constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
- }
- // get the constants for specific environment
- constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
- constantsResp?.constants,
- editorEnvironment?.name
- );
-
- setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public);
-
- fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public);
-
- const pages = appData.pages.map((page) => {
- return page;
- });
- const conversation = appData.ai_conversation;
- const docsConversation = appData.ai_conversation_learn;
- if (setConversation && setDocsConversation) {
- setConversation(conversation);
- setDocsConversation(docsConversation);
- // important to control ai inputs
- getCreditBalance();
- }
-
- let showWalkthrough = true;
- // if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
-
- // handles the getappdataby slug api call. Gets the homePageId from the appData.
- const homePageId =
- appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
-
- setApp({
- appName: appData.name,
- appId: appData.id,
- slug: appData.slug,
- currentAppEnvironmentId: editorEnvironment.id,
- isMaintenanceOn:
- 'is_maintenance_on' in result
- ? result.is_maintenance_on
- : 'isMaintenanceOn' in result
- ? result.isMaintenanceOn
- : false,
- organizationId: appData.organizationId || appData.organization_id,
- homePageId: homePageId,
- isPublic: appData.is_public,
- creationMode: appData.creation_mode,
- });
- setIsEditorFreezed(appData.should_freeze_editor);
- const global_settings = mapKeys(
- appData.editing_version?.global_settings || appData.global_settings,
- (value, key) => camelCase(key)
- );
- if (!global_settings?.theme) {
- global_settings.theme = baseTheme;
- }
- setGlobalSettings(global_settings);
- setPages(pages, moduleId);
- setPageSettings(
- computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
- );
-
- // set starting page as homepage initially
- let startingPage = appData.pages.find((page) => page.id === homePageId);
-
- //no access to homepage, set to the next available page
- if (startingPage?.restricted) {
- startingPage = appData.pages.find((page) => !page?.restricted);
- }
-
- if (initialLoadRef.current) {
- // if initial load, check if the path has a page handle and set that as the starting page
- const initialLoadPath = location.pathname.split('/').pop();
-
- const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
- if (page) {
- // if page is disabled, and not editing redirect to home page
- const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
-
- if (shouldRedirect) {
- const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
- window.history.replaceState(null, null, newUrl);
-
- if (page?.restricted) {
- toast.error('Access to this page is restricted. Contact admin to know more.', {
- className: 'text-nowrap w-auto mw-100',
- });
- }
- } else {
- startingPage = page;
+ let constantsResp;
+ if (mode !== 'edit') {
+ try {
+ const queryParams = { slug: slug };
+ const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams);
+ editorEnvironment = {
+ id: viewerEnvironment?.environment?.id,
+ name: viewerEnvironment?.environment?.name,
+ };
+ constantsResp =
+ isPublicAccess && appData.is_public
+ ? await orgEnvironmentConstantService.getConstantsFromPublicApp(
+ slug,
+ viewerEnvironment?.environment?.id
+ )
+ : await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
+ } catch (error) {
+ console.error('Error fetching viewer environment:', error);
}
}
- // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
- }
-
- // Add page id and handle to the state on initial load
- const currentState = window.history.state || {};
- const pageInfo = {
- id: startingPage.id,
- handle: startingPage.handle,
- };
- const newState = { ...currentState, ...pageInfo };
- window.history.replaceState(newState, '', window.location.href);
-
- setCurrentPageHandle(startingPage.handle);
- updateFeatureAccess();
- setCurrentPageId(startingPage.id, moduleId);
- setResolvedPageConstants({
- id: startingPage?.id,
- handle: startingPage?.handle,
- name: startingPage?.name,
- });
- setComponentNameIdMapping(moduleId);
- updateEventsField('events', appData.events);
- setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
- setAppHomePageId(homePageId);
-
- const queryData =
- isPublicAccess || (mode !== 'edit' && appData.is_public)
- ? appData
- : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
- const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
- dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
- setQueries(dataQueries);
- if (dataQueries?.length > 0) {
- setSelectedQuery(dataQueries[0]?.id);
- initialiseResolvedQuery(dataQueries.map((query) => query.id));
- }
- const constants = constantsResp?.constants;
-
- if (constants) {
- const orgConstants = {};
- const orgSecrets = {};
- constants.map((constant) => {
- if (constant.type !== 'Secret') {
- orgConstants[constant.name] = constant.value;
- } else {
- orgSecrets[constant.name] = constant.value;
- }
- });
- setResolvedConstants(orgConstants);
- setSecrets(orgSecrets);
- }
- setQueryMapping(moduleId);
-
- setResolvedGlobals('environment', editorEnvironment);
- setResolvedGlobals('mode', { value: mode });
- setResolvedGlobals('currentUser', {
- ...user,
- groups: currentSession?.groups,
- role: currentSession?.role?.name,
- ssoUserInfo: currentSession?.ssoUserInfo,
- ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
- ? { metadata: currentSession?.currentUser?.metadata }
- : {}),
- });
- setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))));
- initDependencyGraph(moduleId);
- setCurrentMode(mode); // TODO: set mode based on the slug/appDef
- if (
- state.ai &&
- state?.prompt &&
- initialLoadRef.current &&
- (conversation?.aiConversationMessages || []).length === 0
- ) {
- setSelectedSidebarItem('tooljetai');
- toggleLeftSidebar('true');
- sendMessage(state.prompt);
- setConversationZeroState(true);
- showWalkthrough = false;
- }
- // fetchDataSources(appData.editing_version.id, editorEnvironment.id);
- if (!isPublicAccess) {
- const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
- useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
- fetchGlobalDataSources(
- appData.organization_id,
- appData.editing_version?.id || appData.current_version_id,
- editorEnvironment.id
+ if (mode === 'edit') {
+ constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
+ }
+ // get the constants for specific environment
+ constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
+ constantsResp?.constants,
+ editorEnvironment?.name
);
- }
- useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
- updateReleasedVersionId(appData.current_version_id);
- setEditorLoading(false);
- initialLoadRef.current = false;
- // only show if app is not created from prompt
- if (showWalkthrough) initEditorWalkThrough();
- checkAndSetTrueBuildSuggestionsFlag();
- return () => {
- document.title = retrieveWhiteLabelText();
- };
- });
+ !moduleMode && setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public);
+
+ fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public);
+
+ const pages = appData.pages.map((page) => {
+ return page;
+ });
+ const conversation = appData.ai_conversation;
+ const docsConversation = appData.ai_conversation_learn;
+ if (setConversation && setDocsConversation) {
+ setConversation(conversation);
+ setDocsConversation(docsConversation);
+ // important to control ai inputs
+ getCreditBalance();
+ }
+
+ let showWalkthrough = true;
+ // if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
+
+ // handles the getappdataby slug api call. Gets the homePageId from the appData.
+ const homePageId =
+ appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
+
+ appTypeRef.current = appData.type;
+
+ setApp(
+ {
+ appName: appData.name,
+ appId: appData.id,
+ slug: appData.slug,
+ currentAppEnvironmentId: editorEnvironment.id,
+ isMaintenanceOn:
+ 'is_maintenance_on' in result
+ ? result.is_maintenance_on
+ : 'isMaintenanceOn' in result
+ ? result.isMaintenanceOn
+ : false,
+ organizationId: appData.organizationId || appData.organization_id,
+ homePageId: homePageId,
+ isPublic: appData.is_public,
+ creationMode: appData.creation_mode,
+ },
+ moduleId
+ );
+ if (!moduleMode) {
+ setIsEditorFreezed(appData.should_freeze_editor);
+ const global_settings = mapKeys(
+ appData.editing_version?.global_settings || appData.global_settings,
+ (value, key) => camelCase(key)
+ );
+ if (!global_settings?.theme) {
+ global_settings.theme = baseTheme;
+ }
+ setGlobalSettings(global_settings);
+ }
+ setPages(pages, moduleId);
+ setPageSettings(
+ computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
+ );
+
+ // set starting page as homepage initially
+ let startingPage = appData.pages.find((page) => page.id === homePageId);
+
+ //no access to homepage, set to the next available page
+ if (startingPage?.restricted) {
+ startingPage = appData.pages.find((page) => !page?.restricted);
+ }
+
+ if (initialLoadRef.current && !moduleMode) {
+ // if initial load, check if the path has a page handle and set that as the starting page
+ const initialLoadPath = location.pathname.split('/').pop();
+
+ const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
+ if (page) {
+ // if page is disabled, and not editing redirect to home page
+ const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
+
+ if (shouldRedirect) {
+ const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
+ window.history.replaceState(null, null, newUrl);
+
+ if (page?.restricted) {
+ toast.error('Access to this page is restricted. Contact admin to know more.', {
+ className: 'text-nowrap w-auto mw-100',
+ });
+ }
+ } else {
+ startingPage = page;
+ }
+ }
+
+ // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
+ }
+
+ // Add page id and handle to the state on initial load
+ const currentState = window.history.state || {};
+ const pageInfo = {
+ id: startingPage.id,
+ handle: startingPage.handle,
+ };
+ const newState = { ...currentState, ...pageInfo };
+ window.history.replaceState(newState, '', window.location.href);
+
+ setCurrentPageHandle(startingPage.handle, moduleId);
+ setCurrentPageId(startingPage.id, moduleId);
+ setResolvedPageConstants(
+ {
+ id: startingPage?.id,
+ handle: startingPage?.handle,
+ name: startingPage?.name,
+ },
+ moduleId
+ );
+ setComponentNameIdMapping(moduleId);
+ updateEventsField('events', appData.events, moduleId);
+ if (!moduleMode) {
+ updateFeatureAccess();
+ setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
+ }
+ setAppHomePageId(homePageId, moduleId);
+ if (!moduleMode && appData.modules) {
+ setModuleDefinition(appData.modules);
+ }
+
+ const queryData =
+ isPublicAccess || (mode !== 'edit' && appData.is_public)
+ ? appData
+ : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
+ const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
+ dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
+ setQueries(dataQueries, moduleId);
+ if (dataQueries?.length > 0) {
+ !moduleMode && setSelectedQuery(dataQueries[0]?.id);
+ initialiseResolvedQuery(
+ dataQueries.map((query) => query.id),
+ moduleId
+ );
+ }
+ const constants = constantsResp?.constants;
+
+ if (constants) {
+ const orgConstants = {};
+ const orgSecrets = {};
+ constants.map((constant) => {
+ if (constant.type !== 'Secret') {
+ orgConstants[constant.name] = constant.value;
+ } else {
+ orgSecrets[constant.name] = constant.value;
+ }
+ });
+ setResolvedConstants(orgConstants, moduleId);
+ setSecrets(orgSecrets, moduleId);
+ }
+ setQueryMapping(moduleId);
+
+ setResolvedGlobals('environment', editorEnvironment, moduleId);
+ setResolvedGlobals('mode', { value: mode }, moduleId);
+ setResolvedGlobals(
+ 'currentUser',
+ {
+ ...user,
+ groups: currentSession?.groups,
+ role: currentSession?.role?.name,
+ ssoUserInfo: currentSession?.ssoUserInfo,
+ ...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
+ ? { metadata: currentSession?.currentUser?.metadata }
+ : {}),
+ },
+ moduleId
+ );
+ setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))), moduleId);
+ initDependencyGraph(moduleId);
+ setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef
+ if (
+ !moduleMode &&
+ state.ai &&
+ state?.prompt &&
+ initialLoadRef.current &&
+ (conversation?.aiConversationMessages || []).length === 0
+ ) {
+ setSelectedSidebarItem('tooljetai');
+ toggleLeftSidebar('true');
+ sendMessage(state.prompt);
+ setConversationZeroState(true);
+ showWalkthrough = false;
+ }
+ // fetchDataSources(appData.editing_version.id, editorEnvironment.id);
+ if (!isPublicAccess && !moduleMode) {
+ const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
+ useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
+ fetchGlobalDataSources(
+ appData.organization_id,
+ appData.editing_version?.id || appData.current_version_id,
+ editorEnvironment.id
+ );
+ }
+ if (!moduleMode) {
+ useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
+ updateReleasedVersionId(appData.current_version_id);
+ }
+
+ setEditorLoading(false, moduleId);
+ initialLoadRef.current = false;
+ // only show if app is not created from prompt
+ if (showWalkthrough && !moduleMode) initEditorWalkThrough();
+ !moduleMode && checkAndSetTrueBuildSuggestionsFlag();
+ return () => {
+ document.title = retrieveWhiteLabelText();
+ };
+ })
+ .catch((error) => {
+ if (moduleMode) {
+ setEditorLoading(false, moduleId);
+ toast.error('Error fetching module data');
+ }
+ });
}, [setApp, setEditorLoading, currentSession]);
useEffect(() => {
if (isComponentLayoutReady) {
- runOnLoadQueries().then(() => {
+ runOnLoadQueries(moduleId).then(() => {
let startingPage = pages.find((page) => page.id === currentPageId);
const currentPageEvents = events.filter(
(event) => event.target === 'page' && event.sourceId === startingPage.id
@@ -459,11 +536,18 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
handleEvent('onPageLoad', currentPageEvents, {});
});
}
- }, [isComponentLayoutReady]);
+ }, [isComponentLayoutReady, moduleId]);
useEffect(() => {
- fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: app.appName });
- }, [app.appName]);
+ if (moduleId) return;
+ fetchAndSetWindowTitle({
+ page: pageTitles.EDITOR,
+ appName: app.appName,
+ mode: mode,
+ isReleased: isReleasedVersionId,
+ licenseStatus: licenseStatus,
+ });
+ }, [app.appName, isReleasedVersionId, licenseStatus, mode, moduleId]);
useEffect(() => {
if (!themeAccess) return;
@@ -476,6 +560,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
}, [darkMode, selectedTheme, themeAccess]);
useEffect(() => {
+ if (moduleMode) return;
const exposedTheme =
appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light';
const isEnvChanged =
@@ -483,13 +568,13 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
const isVersionChanged = currentVersionId && previousVersion && currentVersionId != previousVersion;
if (isEnvChanged || isVersionChanged) {
- setEditorLoading(true);
+ setEditorLoading(true, moduleId);
clearSelectedComponents();
if (isEnvChanged) {
setEnvironmentLoadingState('loading');
}
appVersionService.getAppVersionData(appId, selectedVersion?.id).then(async (appData) => {
- cleanUpStore();
+ cleanUpStore(false);
const { should_freeze_editor } = appData;
setIsEditorFreezed(should_freeze_editor);
@@ -524,7 +609,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
);
setCurrentPageId(startingPage.id, moduleId);
setComponentNameIdMapping(moduleId);
- updateEventsField('events', appData.events);
+ updateEventsField('events', appData.events, moduleId);
// const queryData = await dataqueryService.getAll(currentVersionId);
if (isEnvChanged) {
@@ -550,7 +635,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
const queryData = await dataqueryService.getAll(currentVersionId);
const dataQueries = queryData.data_queries;
dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
- setQueries(dataQueries);
+ setQueries(dataQueries, moduleId);
if (dataQueries?.length > 0) {
setSelectedQuery(dataQueries[0]?.id);
initialiseResolvedQuery(dataQueries.map((query) => query.id));
@@ -579,10 +664,27 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
setQueryMapping(moduleId);
initDependencyGraph(moduleId);
- setEditorLoading(false);
+ setEditorLoading(false, false);
});
}
- }, [selectedEnvironment?.id, currentVersionId]);
+ }, [selectedEnvironment?.id, currentVersionId, moduleMode, moduleId]);
+
+ useEffect(() => {
+ if (moduleMode) return;
+ if (mode === 'edit') {
+ requestIdleCallback(
+ () => {
+ appsService.getAll(0, '', '', 'module').then((data) => {
+ setModulesIsLoading(false);
+ setModulesList(data.apps);
+ });
+ },
+ { timeout: 2000 }
+ ); // Adding a timeout of 2 seconds as fallback
+ }
+ }, [setModulesIsLoading, setModulesList, mode, moduleMode]);
+
+ return appTypeRef.current;
};
export default useAppData;
diff --git a/frontend/src/AppBuilder/_stores/ast.js b/frontend/src/AppBuilder/_stores/ast.js
index 106298588f..8412c3b7b2 100644
--- a/frontend/src/AppBuilder/_stores/ast.js
+++ b/frontend/src/AppBuilder/_stores/ast.js
@@ -32,7 +32,7 @@ function findExpression(input) {
export function extractAndReplaceReferencesFromString(input, componentIdNameMapping = {}, queryIdNameMapping = {}) {
// Quick check for relevant keywords
const regexForQuickCheck =
- /\b(components|queries|globals|variables|page|parameters|secrets|constants)(?:\[\S*|\.\S*|\?\.\S*)/;
+ /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)(?:\[\S*|\.\S*|\?\.\S*)/;
if (!regexForQuickCheck.test(input)) {
return {
allRefs: [],
@@ -41,7 +41,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp
};
}
- const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants)\b/;
+ const relevantKeywords = /\b(components|queries|globals|variables|page|parameters|secrets|constants|input)\b/;
const expressionRegex = /{{(.*?)}}/gs;
const results = [];
let lastIndex = 0;
@@ -312,6 +312,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping,
variables: true,
globals: true,
page: true,
+ input: true,
};
walk.simple(ast, {
@@ -359,7 +360,7 @@ function parseExpression(expression, componentIdNameMapping, queryIdNameMapping,
if (
(rootObject && (rootObject === 'queries' || rootObject === 'components') && path.length >= 3) ||
- ((rootObject === 'variables' || rootObject === 'globals') && path.length === 2) ||
+ ((rootObject === 'variables' || rootObject === 'globals' || rootObject === 'input') && path.length === 2) ||
(rootObject === 'page' && path.length === 3)
) {
return createReferenceObject(rootObject, path, uuidMappings, componentIdNameMapping, queryIdNameMapping);
@@ -386,7 +387,7 @@ function createReferenceObject(entityType, path, uuidMappings, componentIdNameMa
const mapping = entityType === 'components' ? componentIdNameMapping : queryIdNameMapping;
entityNameOrId = mapping[entityNameOrId] || entityNameOrId;
}
- } else if (entityType === 'variables' || entityType === 'globals') {
+ } else if (entityType === 'variables' || entityType === 'globals' || entityType === 'input') {
entityKey = path[1];
} else if (entityType === 'page') {
entityNameOrId = path[1];
diff --git a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js
index 730cb41e12..e263532310 100644
--- a/frontend/src/AppBuilder/_stores/slices/DependencyClass.js
+++ b/frontend/src/AppBuilder/_stores/slices/DependencyClass.js
@@ -14,7 +14,7 @@ class DependencyGraph {
}
addDependency(fromPath, toPath, nodeData = '') {
- if (fromPath.startsWith('variables.')) {
+ if (fromPath.startsWith('variables.') || fromPath.startsWith('input.')) {
if (!this.hasNode(fromPath)) {
const parts = fromPath.split('.');
fromPath = parts.slice(0, 2).join('.');
diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js
index 4b0ded7023..6962c6752d 100644
--- a/frontend/src/AppBuilder/_stores/slices/appSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js
@@ -1,4 +1,3 @@
-import { updateCanvasBackground } from '@/_helpers/editorHelpers';
import { appsService, appVersionService } from '@/_services';
import { decimalToHex } from '@/Editor/editorConstants';
import toast from 'react-hot-toast';
@@ -10,8 +9,6 @@ import { replaceEntityReferencesWithIds, baseTheme } from '../utils';
import _ from 'lodash';
const initialState = {
- app: {},
- canvasHeight: null,
isSaving: false,
globalSettings: {
theme: baseTheme,
@@ -19,20 +16,80 @@ const initialState = {
pageSwitchInProgress: false,
isTJDarkMode: localStorage.getItem('darkMode') === 'true',
isViewer: false,
- isComponentLayoutReady: false,
+ appStore: {
+ modules: {
+ canvas: {
+ canvasHeight: null,
+ app: {},
+ isViewer: false,
+ isComponentLayoutReady: false,
+ },
+ },
+ },
};
export const createAppSlice = (set, get) => ({
...initialState,
- setIsViewer: (isViewer) => set(() => ({ isViewer }), false, 'setIsViewer'),
- setApp: (app) => set(() => ({ app }), false, 'setApp'),
- setAppName: (name) => set((state) => ({ app: { ...state.app, appName: name } }), false, 'setAppName'),
- setAppHomePageId: (homePageId) => set((state) => ({ app: { ...state.app, homePageId } }), false, 'setAppHomePageId'),
- setIsComponentLayoutReady: (isReady) =>
- set(() => ({ isComponentLayoutReady: isReady }), false, 'setIsComponentLayoutReady'),
- setCanvasHeight: (canvasHeight) => set({ canvasHeight }, false, 'setCanvasHeight'),
- updateCanvasBottomHeight: (components) => {
- const { currentLayout, currentMode, setCanvasHeight } = get();
+ initializeAppSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.appStore.modules[moduleId] = { ...initialState.appStore.modules.canvas };
+ },
+ false,
+ 'initializeAppSlice'
+ );
+ },
+ setIsViewer: (isViewer, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].isViewer = isViewer;
+ },
+ false,
+ 'setIsViewer'
+ ),
+ setApp: (app, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].app = app;
+ },
+ false,
+ 'setApp'
+ ),
+ setAppName: (name, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].app.appName = name;
+ },
+ false,
+ 'setAppName'
+ ),
+ setAppHomePageId: (homePageId, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].app.homePageId = homePageId;
+ },
+ false,
+ 'setAppHomePageId'
+ ),
+ setIsComponentLayoutReady: (isReady, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].isComponentLayoutReady = isReady;
+ },
+ false,
+ 'setIsComponentLayoutReady'
+ ),
+ setCanvasHeight: (canvasHeight, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.appStore.modules[moduleId].canvasHeight = canvasHeight;
+ },
+ false,
+ 'setCanvasHeight'
+ ),
+ updateCanvasBottomHeight: (components, moduleId = 'canvas') => {
+ const { currentLayout, getCurrentMode, setCanvasHeight } = get();
+ const currentMode = getCurrentMode(moduleId);
const maxHeight = Object.values(components).reduce((max, component) => {
const layout = component?.layouts?.[currentLayout];
if (!layout) {
@@ -43,23 +100,25 @@ export const createAppSlice = (set, get) => ({
}, 0);
const bottomPadding = currentMode === 'view' ? 100 : 300;
const frameHeight = currentMode === 'view' ? 45 : 85;
- setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`);
+ setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`, moduleId);
},
- setIsAppSaving: (isSaving) => {
+ setIsAppSaving: (isSaving, moduleId = 'canvas') => {
set(
(state) => {
- state.app.isSaving = isSaving;
+ state.appStore.modules[moduleId].app.isSaving = isSaving;
},
false,
'setIsAppSaving'
);
},
setGlobalSettings: (globalSettings) => set(() => ({ globalSettings }), false, 'setGlobalSettings'),
- toggleAppMaintenance: () => {
- const { isMaintenanceOn, appId } = get().app;
+ toggleAppMaintenance: (moduleId = 'canvas') => {
+ const { isMaintenanceOn, appId } = get().appStore.modules[moduleId].app;
appsService.setMaintenance(appId, !isMaintenanceOn).then(() => {
- set((state) => ({ app: { ...state.app, isMaintenanceOn: !isMaintenanceOn } }));
+ set((state) => {
+ state.appStore.modules[moduleId].app.isMaintenanceOn = !isMaintenanceOn;
+ });
if (isMaintenanceOn) {
toast.success('Application is on maintenance.');
} else {
@@ -67,9 +126,9 @@ export const createAppSlice = (set, get) => ({
}
});
},
- globalSettingsChanged: async (options) => {
- const componentNameIdMapping = get().modules.canvas.componentNameIdMapping;
- const queryNameIdMapping = get().modules.canvas.queryNameIdMapping;
+ globalSettingsChanged: async (options, moduleId = 'canvas') => {
+ const componentNameIdMapping = get().modules[moduleId].componentNameIdMapping;
+ const queryNameIdMapping = get().modules[moduleId].queryNameIdMapping;
for (const [key, value] of Object.entries(options)) {
if (value?.[1]?.a == undefined) {
options[key] = value;
@@ -80,10 +139,10 @@ export const createAppSlice = (set, get) => ({
}
// Replace entity references with ids if present
const newOptions = replaceEntityReferencesWithIds(options, componentNameIdMapping, queryNameIdMapping);
- const { app, currentVersionId, currentPageId } = get();
+ const { appStore, currentVersionId, currentPageId } = get();
try {
const res = await appVersionService.autoSaveApp(
- app.appId,
+ appStore.modules[moduleId].app.appId,
currentVersionId,
{ globalSettings: newOptions },
'global_settings',
@@ -96,7 +155,7 @@ export const createAppSlice = (set, get) => ({
console.error('Error updating page:', error);
}
},
- switchPage: (pageId, handle, queryParams = [], isBackOrForward = false) => {
+ switchPage: (pageId, handle, queryParams = [], moduleId = 'canvas', isBackOrForward = false) => {
get().debugger.resetUnreadErrorCount();
// reset stores
if (get().pageSwitchInProgress) {
@@ -114,24 +173,24 @@ export const createAppSlice = (set, get) => ({
setResolvedGlobals,
setResolvedPageConstants,
setPageSwitchInProgress,
- currentMode,
license,
modules: {
canvas: { pages },
},
+ getCurrentMode,
} = get();
- const isPreview = currentMode !== 'edit';
+ const isPreview = getCurrentMode(moduleId) !== 'edit';
//!TODO clear all queued tasks
cleanUpStore(true);
- setCurrentPageId(pageId, 'canvas');
- setComponentNameIdMapping('canvas');
- setQueryMapping('canvas');
+ setCurrentPageId(pageId, moduleId);
+ setComponentNameIdMapping(moduleId);
+ setQueryMapping(moduleId);
const isLicenseValid =
!_.get(license, 'featureAccess.licenseStatus.isExpired', true) &&
_.get(license, 'featureAccess.licenseStatus.isLicenseValid', false);
- const appId = get().app.appId;
+ const appId = get().appStore.modules[moduleId].app.appId;
const filteredQueryParams = queryParams.filter(([key, value]) => {
if (!value) return false;
if (key === 'env' && isLicenseValid) return false;
@@ -139,7 +198,7 @@ export const createAppSlice = (set, get) => ({
});
const queryParamsString = filteredQueryParams.map(([key, value]) => `${key}=${value}`).join('&');
- const slug = get().app.slug;
+ const slug = get().appStore.modules[moduleId].app.slug;
if (!isBackOrForward) {
navigate(
@@ -155,11 +214,14 @@ export const createAppSlice = (set, get) => ({
}
const newPage = pages.find((p) => p.id === pageId);
- setResolvedPageConstants({
- id: newPage?.id,
- handle: newPage?.handle,
- name: newPage?.name,
- });
+ setResolvedPageConstants(
+ {
+ id: newPage?.id,
+ handle: newPage?.handle,
+ name: newPage?.name,
+ },
+ moduleId
+ );
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(queryParamsString))));
initDependencyGraph('canvas');
setPageSwitchInProgress(true);
@@ -167,8 +229,9 @@ export const createAppSlice = (set, get) => ({
setPageSwitchInProgress: (isInProgress) =>
set(() => ({ pageSwitchInProgress: isInProgress }), false, 'setPageSwitchInProgress'),
- cleanUpStore: (isPageSwitch = false) => {
- get().resetUndoRedoStack();
+ cleanUpStore: (isPageSwitch = false, moduleId) => {
+ const { resetUndoRedoStack, initModules } = get();
+ resetUndoRedoStack();
set((state) => {
state.modules.canvas.componentNameIdMapping = {};
state.selectedComponents = [];
@@ -184,26 +247,33 @@ export const createAppSlice = (set, get) => ({
state.resolvedStore.modules.canvas.customResolvables = {};
state.resolvedStore.modules.canvas.exposedValues.components = {};
state.resolvedStore.modules.canvas.exposedValues.page.variables = {};
+ // initModules(moduleId);
});
},
- setSlug: (slug) => {
+ setSlug: (slug, moduleId = 'canvas') => {
set(
(state) => {
- state.app.slug = slug;
+ state.appStore.modules[moduleId].app.slug = slug;
},
false,
'setSlug'
);
},
- setIsPublic: (isPublic) => {
+ setIsPublic: (isPublic, moduleId = 'canvas') => {
set(
(state) => {
- state.app.isPublic = isPublic;
+ state.appStore.modules[moduleId].app.isPublic = isPublic;
},
false,
'setIsPublic'
);
},
+ getAppId: (moduleId = 'canvas') => {
+ return get().appStore.modules[moduleId].app.appId;
+ },
+ getHomePageId: (moduleId = 'canvas') => {
+ return get().appStore.modules[moduleId].app.homePageId;
+ },
updateIsTJDarkMode: (newMode) => set({ isTJDarkMode: newMode }, false, 'updateIsTJDarkMode'),
});
diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
index b500a4d912..7e74a6a006 100644
--- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
@@ -6,6 +6,7 @@ import {
checkSubstringRegex,
hasArrayNotation,
parsePropertyPath,
+ resolveCode,
} from '@/AppBuilder/_stores/utils';
import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
@@ -29,19 +30,19 @@ import { findHighestLevelofSelection } from '@/AppBuilder/AppCanvas/Grid/gridUti
const initialState = {
modules: {
canvas: {
+ currentPageId: null,
+ currentPageIndex: 0,
pages: [],
componentNameIdMapping: {},
queryNameIdMapping: {},
queryIdNameMapping: {},
+ currentPageHandle: null,
},
},
- currentPageId: null,
- currentPageIndex: 0,
containerChildrenMapping: {
canvas: [],
},
selectedComponents: [],
- currentPageHandle: null,
showWidgetDeleteConfirmation: false,
focusedParentId: null,
modalsOpenOnCanvas: [],
@@ -50,6 +51,17 @@ const initialState = {
export const createComponentsSlice = (set, get) => ({
...initialState,
+ initializeComponentsSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.modules[moduleId] = { ...initialState.modules.canvas };
+ state.containerChildrenMapping[moduleId] = [];
+ },
+ false,
+ 'initializeComponentsSlice'
+ );
+ },
+
setPages: (pages = [], moduleId = 'canvas') => {
set(
(state) => {
@@ -70,16 +82,16 @@ export const createComponentsSlice = (set, get) => ({
);
},
- setCurrentPageId: (id, moduleId) =>
+ setCurrentPageId: (id, moduleId = 'canvas') =>
set(
(state) => {
- const currentPageIndex = state.modules.canvas.pages.findIndex((page) => page.id === id);
+ const currentPageIndex = state.modules[moduleId].pages.findIndex((page) => page.id === id);
const currentPageComponents = state.modules[moduleId].pages[currentPageIndex]?.components || {};
- state.currentPageIndex = currentPageIndex;
- state.currentPageId = id;
- state.containerChildrenMapping = { canvas: [] };
+ state.modules[moduleId].currentPageIndex = currentPageIndex;
+ state.modules[moduleId].currentPageId = id;
+ state.containerChildrenMapping[moduleId] = [];
Object.entries(currentPageComponents).forEach(([componentId, component]) => {
- const parentId = component.component.parent || 'canvas';
+ const parentId = component.component.parent || moduleId;
if (!state.containerChildrenMapping[parentId]) {
state.containerChildrenMapping[parentId] = [];
}
@@ -89,10 +101,10 @@ export const createComponentsSlice = (set, get) => ({
false,
'setCurrentPageId'
),
- setCurrentPageHandle: (handle) => {
+ setCurrentPageHandle: (handle, moduleId = 'canvas') => {
set(
(state) => {
- state.currentPageHandle = handle;
+ state.modules[moduleId].currentPageHandle = handle;
},
false,
'setCurrentPageHandle'
@@ -149,7 +161,7 @@ export const createComponentsSlice = (set, get) => ({
},
setComponentNameIdMapping: (moduleId = 'canvas') => {
- const components = get().getCurrentPageComponents();
+ const components = get().getCurrentPageComponents(moduleId);
set(
(state) => {
Object.entries(components).forEach(([componentId, component]) => {
@@ -162,12 +174,13 @@ export const createComponentsSlice = (set, get) => ({
},
setComponentName: (componentId, newName, moduleId = 'canvas') => {
- const { renameComponentNameIdMapping, saveComponentChanges } = get();
+ const { renameComponentNameIdMapping, saveComponentChanges, getCurrentPageIndex } = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
let oldName = '';
set(
(state) => {
- oldName = state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name;
- state.modules[moduleId].pages[state.currentPageIndex].components[componentId].component.name = newName;
+ oldName = state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name;
+ state.modules[moduleId].pages[currentPageIndex].components[componentId].component.name = newName;
},
false,
'setComponentName'
@@ -177,7 +190,7 @@ export const createComponentsSlice = (set, get) => ({
[componentId]: { component: { name: newName } },
};
- saveComponentChanges(diff, 'components', 'update');
+ saveComponentChanges(diff, 'components', 'update', moduleId);
renameComponentNameIdMapping(oldName, newName, moduleId);
},
@@ -227,7 +240,15 @@ export const createComponentsSlice = (set, get) => ({
get().checkAndSetTrueBuildSuggestionsFlag();
},
- generateDependencyGraphForRefs: (allRefs, key, paramType, property, unResolvedValue, isUpdate = false) => {
+ generateDependencyGraphForRefs: (
+ allRefs,
+ key,
+ paramType,
+ property,
+ unResolvedValue,
+ isUpdate = false,
+ moduleId = 'canvas'
+ ) => {
const { addDependency, updateDependency } = get();
if (allRefs.length !== 0) {
allRefs.forEach(({ entityType, entityNameOrId, entityKey }, index) => {
@@ -236,9 +257,9 @@ export const createComponentsSlice = (set, get) => ({
: `${entityType}.${entityKey}`;
const propertyPath = paramType === undefined ? `others.${key}` : `components.${key}.${paramType}.${property}`;
if (isUpdate && index === 0) {
- updateDependency(propertyValue, propertyPath, unResolvedValue);
+ updateDependency(propertyValue, propertyPath, unResolvedValue, moduleId);
} else {
- addDependency(propertyValue, propertyPath, unResolvedValue);
+ addDependency(propertyValue, propertyPath, unResolvedValue, moduleId);
}
});
}
@@ -330,7 +351,7 @@ export const createComponentsSlice = (set, get) => ({
const length = Object.keys(customResolvables).length;
if (length === 0) {
const resolvedValue = shouldResolve
- ? resolveDynamicValues(value, getAllExposedValues(), customResolvables, false, [])
+ ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables, false, [])
: value;
if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) {
componentResolvedValues[componentId] = index === null ? deepClone(DEFAULT_COMPONENT_STRUCTURE) : [];
@@ -372,7 +393,7 @@ export const createComponentsSlice = (set, get) => ({
// Loop all the index and set the resolved value
for (let i = 0; i < length; i++) {
const resolvedValue = shouldResolve
- ? resolveDynamicValues(value, getAllExposedValues(), customResolvables[i], false, [])
+ ? resolveDynamicValues(value, getAllExposedValues(moduleId), customResolvables[i], false, [])
: value;
if (!componentResolvedValues[componentId] || Object.keys(componentResolvedValues[componentId]).length === 0) {
componentResolvedValues[componentId] = [];
@@ -425,14 +446,14 @@ export const createComponentsSlice = (set, get) => ({
const length = Object.keys(customResolvables).length;
if (length === 0) {
const resolvedValue = shouldResolve
- ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables, false, [])
+ ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables, false, [])
: value;
setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, index, moduleId);
} else {
// Loop all the index and set the resolved value
for (let i = 0; i < length; i++) {
const resolvedValue = shouldResolve
- ? resolveDynamicValues(unResolvedValue, getAllExposedValues(), customResolvables[i], false, [])
+ ? resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), customResolvables[i], false, [])
: value;
setResolvedComponentByProperty(componentId, paramType, property, resolvedValue, i, moduleId);
}
@@ -638,7 +659,15 @@ export const createComponentsSlice = (set, get) => ({
);
lodashSet(updatedPropertyValue, [index, ...keys], updatedValue);
if (allRefs.length) {
- generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue);
+ generateDependencyGraphForRefs(
+ allRefs,
+ componentId,
+ paramType,
+ propertyWithArrayValue,
+ unResolvedValue,
+ false,
+ moduleId
+ );
}
});
} else {
@@ -654,9 +683,16 @@ export const createComponentsSlice = (set, get) => ({
moduleId
);
updatedPropertyValue[index] = updatedValue;
- console.log('updatedPropertyValue', updatedPropertyValue);
if (allRefs.length) {
- generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue);
+ generateDependencyGraphForRefs(
+ allRefs,
+ componentId,
+ paramType,
+ propertyWithArrayValue,
+ unResolvedValue,
+ false,
+ moduleId
+ );
}
}
});
@@ -673,7 +709,7 @@ export const createComponentsSlice = (set, get) => ({
moduleId
);
if (allRefs.length) {
- generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue);
+ generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, false, moduleId);
}
return { allRefs, unResolvedValue, updatedValue };
}
@@ -711,7 +747,7 @@ export const createComponentsSlice = (set, get) => ({
addToDependencyGraph: (moduleId = 'canvas', componentId, component) => {
const { updateDependencyGraphAndResolvedValues, getResolvedComponent } = get();
//TODO: Replace with object of component types
- let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) };
+ let resolvedComponentValues = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) };
const componentType = componentTypes.find((comp) => component.component === comp.component);
['properties', 'general', 'generalStyles', 'others', 'styles', 'validation'].forEach((key) => {
updateDependencyGraphAndResolvedValues(
@@ -728,7 +764,7 @@ export const createComponentsSlice = (set, get) => ({
initDependencyGraph: (moduleId) => {
const { getCurrentPageComponents, addToDependencyGraph, setResolvedComponents, resolveOthers } = get();
- const components = getCurrentPageComponents();
+ const components = getCurrentPageComponents(moduleId);
//TODO: Replace with object of component types
let resolvedComponentValues = {};
@@ -764,9 +800,9 @@ export const createComponentsSlice = (set, get) => ({
get().modules[moduleId].componentNameIdMapping,
get().modules[moduleId].queryNameIdMapping
);
- const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(), {}, false, []);
+ const resolvedValue = resolveDynamicValues(valueWithBrackets, getAllExposedValues(moduleId), {}, false, []);
resolvedValues[key] = resolvedValue;
- generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate);
+ generateDependencyGraphForRefs(allRefs, key, undefined, undefined, valueWithBrackets, isUpdate, moduleId);
} else {
resolvedValues[key] = item;
}
@@ -797,7 +833,9 @@ export const createComponentsSlice = (set, get) => ({
canAddToParent,
getComponentNameFromId,
deleteComponentNameIdMapping,
+ getCurrentPageId,
} = get();
+ const currentPageId = getCurrentPageId(moduleId);
// This is made into a promise to wait for the saveComponentChanges to complete so that the caller can await it
return new Promise((resolve) => {
if (
@@ -811,7 +849,7 @@ export const createComponentsSlice = (set, get) => ({
}
const newComponents = componentDefinitions.reduce((acc, componentDefinition) => {
const currentComponents = {
- ...getCurrentPageComponents(),
+ ...getCurrentPageComponents(moduleId),
...Object.fromEntries(acc.map((component) => [component.id, component])),
};
const componentName =
@@ -865,7 +903,7 @@ export const createComponentsSlice = (set, get) => ({
if (!state.containerChildrenMapping[parentId].includes(newComponent.id)) {
state.containerChildrenMapping[parentId].push(newComponent.id);
}
- const page = state.modules[moduleId].pages.find((page) => page.id === state.currentPageId);
+ const page = state.modules[moduleId].pages.find((page) => page.id === currentPageId);
page.components[newComponent.id] = newComponent;
}, skipUndoRedo),
false,
@@ -876,7 +914,7 @@ export const createComponentsSlice = (set, get) => ({
get().setSelectedComponents(selectedComponents.map((component) => component.id));
if (saveAfterAction) {
- saveComponentChanges(diff, 'components', 'create')
+ saveComponentChanges(diff, 'components', 'create', moduleId)
.then(() => {
resolve(); // Resolve the promise after all operations are complete
})
@@ -901,7 +939,11 @@ export const createComponentsSlice = (set, get) => ({
selectedComponents,
deleteComponentNameIdMapping,
removeNode,
+ getCurrentPageId,
+ checkIfComponentIsModule,
+ clearModuleFromStore,
} = get();
+ const currentPageId = getCurrentPageId(moduleId);
const appEvents = get().eventsSlice.getModuleEvents(moduleId);
const componentNames = [];
const _selectedComponents = selected?.length ? selected : selectedComponents;
@@ -910,7 +952,7 @@ export const createComponentsSlice = (set, get) => ({
withUndoRedo((state) => {
const toDeleteComponents = [];
const toDeleteEvents = [];
- const allComponents = getCurrentPageComponents();
+ const allComponents = getCurrentPageComponents(moduleId);
const findAllChildComponents = (componentId) => {
if (!toDeleteComponents.includes(componentId)) {
@@ -931,7 +973,7 @@ export const createComponentsSlice = (set, get) => ({
findAllChildComponents(componentId);
});
- const page = state.modules?.canvas?.pages.find((page) => page.id === state.currentPageId);
+ const page = state.modules?.[moduleId]?.pages.find((page) => page.id === currentPageId);
const resolvedComponents = state.resolvedStore.modules?.[moduleId]?.components;
const componentsExposedValues = state.resolvedStore.modules?.[moduleId]?.exposedValues.components;
@@ -943,12 +985,18 @@ export const createComponentsSlice = (set, get) => ({
);
});
+ if (checkIfComponentIsModule(id, moduleId)) {
+ clearModuleFromStore(id);
+ }
+
// Remove the container itself if it's a container
if (state.containerChildrenMapping[id]) {
delete state.containerChildrenMapping[id];
}
if (state.containerChildrenMapping?.canvas?.includes(id)) {
- state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter((wid) => wid !== id);
+ state.containerChildrenMapping[moduleId].canvas = state.containerChildrenMapping[moduleId].filter(
+ (wid) => wid !== id
+ );
}
componentNames.push(page.components[id]?.component?.name);
const eventsToRemove = appEvents.filter((event) => event.sourceId === id).map((event) => event.id);
@@ -957,7 +1005,7 @@ export const createComponentsSlice = (set, get) => ({
delete resolvedComponents[id]; // Remove the component from the resolved store
delete componentsExposedValues[id]; // Remove the component from the exposed values
state.selectedComponents = []; // Empty the selected components
- removeNode(`components.${id}`);
+ removeNode(`components.${id}`, moduleId);
state.showWidgetDeleteConfirmation = false; // Set it to false always
});
@@ -965,7 +1013,7 @@ export const createComponentsSlice = (set, get) => ({
state.eventsSlice.module[moduleId].events = filteredEvents;
if (saveAfterAction) {
- saveComponentChanges(toDeleteComponents, 'components', 'delete')
+ saveComponentChanges(toDeleteComponents, 'components', 'delete', moduleId)
.then(() => {
get().multiplayer.broadcastUpdates({ selectedComponents: _selectedComponents }, 'components', 'delete');
// Show delete toast message
@@ -991,7 +1039,7 @@ export const createComponentsSlice = (set, get) => ({
'deleteComponents'
);
componentNames.forEach((componentName) => {
- deleteComponentNameIdMapping(componentName);
+ deleteComponentNameIdMapping(componentName, moduleId);
});
},
@@ -1040,12 +1088,14 @@ export const createComponentsSlice = (set, get) => ({
getComponentDefinition,
currentLayout,
checkValueAndResolve,
+ getCurrentPageIndex,
} = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
let hasParentChanged = false;
let oldParentId;
set(
withUndoRedo((state) => {
- const page = state.modules[moduleId].pages[state.currentPageIndex];
+ const page = state.modules[moduleId].pages[currentPageIndex];
if (page) {
// ============ Component layout update logic ============
Object.entries(componentLayouts).forEach(([componentId, layout]) => {
@@ -1069,8 +1119,8 @@ export const createComponentsSlice = (set, get) => ({
state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter(
(id) => id !== componentId
);
- } else if (state.containerChildrenMapping.canvas.includes(componentId)) {
- state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter(
+ } else if (state.containerChildrenMapping[moduleId].includes(componentId)) {
+ state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter(
(id) => id !== componentId
);
}
@@ -1082,7 +1132,7 @@ export const createComponentsSlice = (set, get) => ({
}
state.containerChildrenMapping[newParentId].push(componentId);
} else {
- state.containerChildrenMapping.canvas.push(componentId);
+ state.containerChildrenMapping[moduleId].push(componentId);
}
}
// ============ Parent update logic ends ============
@@ -1149,7 +1199,7 @@ export const createComponentsSlice = (set, get) => ({
}, {});
if (saveAfterAction) {
- saveComponentChanges(diff, 'components/layout', 'update');
+ saveComponentChanges(diff, 'components/layout', 'update', moduleId);
get().multiplayer.broadcastUpdates(diff, 'components/layout', 'update');
}
},
@@ -1165,7 +1215,7 @@ export const createComponentsSlice = (set, get) => ({
{ skipUndoRedo = false, saveAfterAction = true } = {}
) => {
const {
- currentPageIndex,
+ getCurrentPageIndex,
saveComponentChanges,
withUndoRedo,
updateResolvedValues,
@@ -1176,12 +1226,14 @@ export const createComponentsSlice = (set, get) => ({
checkValueAndResolve,
getResolvedComponent,
setResolvedComponent,
+ getCurrentMode,
} = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
const { component } = getComponentDefinition(componentId, moduleId);
const oldValue = component.definition[paramType][property];
if (Array.isArray(oldValue?.value)) {
- const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId) ?? {}) };
+ const resolvedComponent = { [componentId]: deepClone(getResolvedComponent(componentId, null, moduleId) ?? {}) };
resolvedComponent[componentId][paramType][property] = [];
const { updatedValue } = checkValueAndResolve(
@@ -1221,8 +1273,8 @@ export const createComponentsSlice = (set, get) => ({
};
if (saveAfterAction) {
- const currentMode = get().currentMode;
- if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update');
+ const currentMode = getCurrentMode(moduleId);
+ if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId);
get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update');
}
@@ -1273,18 +1325,18 @@ export const createComponentsSlice = (set, get) => ({
};
if (saveAfterAction) {
- const currentMode = get().currentMode;
- if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update');
+ const currentMode = getCurrentMode(moduleId);
+ if (currentMode !== 'view') saveComponentChanges(diff, 'components', 'update', moduleId);
get().multiplayer.broadcastUpdates({ componentId, property, value, paramType, attr }, 'components', 'update');
}
if (attr !== 'value' || skipResolve) return;
if (allRefs.length) {
- generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true);
+ generateDependencyGraphForRefs(allRefs, componentId, paramType, property, unResolvedValue, true, moduleId);
} else {
const propertyPath = `components.${componentId}.${paramType}.${property}`;
- removeDependency(propertyPath, true);
+ removeDependency(propertyPath, true, moduleId);
}
},
@@ -1317,8 +1369,8 @@ export const createComponentsSlice = (set, get) => ({
state.containerChildrenMapping[oldParentId] = state.containerChildrenMapping[oldParentId].filter(
(id) => id !== componentId
);
- } else if (state.containerChildrenMapping.canvas.includes(componentId)) {
- state.containerChildrenMapping.canvas = state.containerChildrenMapping.canvas.filter(
+ } else if (state.containerChildrenMapping[moduleId].includes(componentId)) {
+ state.containerChildrenMapping[moduleId] = state.containerChildrenMapping[moduleId].filter(
(id) => id !== componentId
);
}
@@ -1330,7 +1382,7 @@ export const createComponentsSlice = (set, get) => ({
}
state.containerChildrenMapping[newParentId].push(componentId);
} else {
- state.containerChildrenMapping.canvas.push(componentId);
+ state.containerChildrenMapping[moduleId].push(componentId);
}
}, skipUndoRedo),
false,
@@ -1381,7 +1433,7 @@ export const createComponentsSlice = (set, get) => ({
};
if (saveAfterAction) {
- saveComponentChanges(diff, 'components', 'update');
+ saveComponentChanges(diff, 'components', 'update', moduleId);
get().multiplayer.broadcastUpdates({ componentId, newParentId }, 'components', 'parent');
}
},
@@ -1411,21 +1463,21 @@ export const createComponentsSlice = (set, get) => ({
setFocusedParentId: (parentId) => {
set((state) => {
state.focusedParentId = parentId;
- });
+ }),
+ false,
+ { type: 'setFocusedParentId', payload: { parentId } };
},
- saveComponentChanges: (diff, type, operation) => {
+ saveComponentChanges: (diff, type, operation, moduleId = 'canvas') => {
set(
(state) => {
- state.app.isSaving = true;
+ state.appStore.modules[moduleId].app.isSaving = true;
},
false,
'setAppSavingChanges'
);
- const {
- app: { appId },
- currentVersionId,
- currentPageId,
- } = get();
+ const { getAppId, currentVersionId, getCurrentPageId } = get();
+ const appId = getAppId(moduleId);
+ const currentPageId = getCurrentPageId(moduleId);
return new Promise((resolve) => {
appVersionService
@@ -1449,7 +1501,7 @@ export const createComponentsSlice = (set, get) => ({
.finally(() => {
set(
(state) => {
- state.app.isSaving = false;
+ state.appStore.modules[moduleId].app.isSaving = false;
},
false,
'setAppSavingChanges'
@@ -1475,7 +1527,8 @@ export const createComponentsSlice = (set, get) => ({
},
turnOffAutoComputeLayout: async (moduleId = 'canvas') => {
- const { app, currentPageId, currentVersionId } = get();
+ const { app, getCurrentPageId, currentVersionId } = get();
+ const currentPageId = getCurrentPageId(moduleId);
set(
(state) => {
state.modules[moduleId].pages[state.currentPageIndex].autoComputeLayout = false;
@@ -1492,38 +1545,44 @@ export const createComponentsSlice = (set, get) => ({
});
},
- getCurrentPageId: () => get().currentPageId,
+ getCurrentPageId: (moduleId = 'canvas') => get().modules[moduleId].currentPageId,
+ getCurrentPageIndex: (moduleId = 'canvas') => get().modules[moduleId].currentPageIndex,
- getComponentsFromAllPages: () => {
+ getComponentsFromAllPages: (moduleId = 'canvas') => {
const { modules } = get();
return Object.fromEntries(
- modules.canvas.pages.flatMap((page) =>
+ modules[moduleId].pages.flatMap((page) =>
Object.entries(page.components).map(([id, { component }]) => [id, component.name])
)
);
},
- getCurrentPageComponents: () => {
- const { modules, currentPageId } = get();
- const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId);
- return modules.canvas.pages[currentPageIndex]?.components || [];
+ getCurrentPageComponents: (moduleId = 'canvas') => {
+ const { modules, getCurrentPageId } = get();
+ const currentPageId = getCurrentPageId(moduleId);
+ const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId);
+ return modules[moduleId].pages[currentPageIndex]?.components || [];
},
- getCurrentPageComponentIds: () => {
- const { pages, currentPageId, modules } = get();
- const currentPageIndex = modules.canvas.pages.findIndex((page) => page.id === currentPageId);
+ getCurrentPageComponentIds: (moduleId = 'canvas') => {
+ const { pages, getCurrentPageId, modules } = get();
+ const currentPageId = getCurrentPageId(moduleId);
+ const currentPageIndex = modules[moduleId].pages.findIndex((page) => page.id === currentPageId);
return Object.keys(pages[currentPageIndex]?.components || {});
},
getCurrentPage: (moduleId = 'canvas') => {
- const { modules, currentPageId } = get();
+ const { modules, getCurrentPageId } = get();
+ const currentPageId = getCurrentPageId(moduleId);
const currentPage = modules[moduleId].pages.find((page) => page.id === currentPageId);
return currentPage;
},
// Get the component definition from the component id
getComponentDefinition: (componentId, moduleId = 'canvas') => {
- const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().currentPageId);
+ const currentPage = get().modules[moduleId].pages.find((page) => page.id === get().getCurrentPageId(moduleId));
+ // if (componentId === 'd78554b8-2af0-4add-9d7d-0032bb4c90ce')
+ // console.trace('here--- getComponentDefinition--- ', componentId, moduleId, currentPage?.components[componentId]);
return currentPage?.components[componentId];
},
@@ -1533,24 +1592,26 @@ export const createComponentsSlice = (set, get) => ({
},
// Get the component name from the component id
getComponentNameFromId: (componentId, moduleId = 'canvas') => {
- const { modules, currentPageIndex } = get();
+ const { modules, getCurrentPageIndex } = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.name;
},
getComponentTypeFromId: (componentId, moduleId = 'canvas') => {
- const { modules, currentPageIndex } = get();
+ const { modules, getCurrentPageIndex } = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
return modules[moduleId].pages[currentPageIndex]?.components[componentId]?.component.component;
},
getComponentNameIdMapping: (moduleId = 'canvas') => {
const { modules } = get();
return modules[moduleId].componentNameIdMapping;
},
- getComponentIdNameMapping: () => {
+ getComponentIdNameMapping: (moduleId = 'canvas') => {
const { getComponentNameIdMapping } = get();
- return Object.fromEntries(Object.entries(getComponentNameIdMapping()).map(([name, id]) => [id, name]));
+ return Object.fromEntries(Object.entries(getComponentNameIdMapping(moduleId)).map(([name, id]) => [id, name]));
},
- getSelectedComponentsDefinition: () => {
+ getSelectedComponentsDefinition: (moduleId = 'canvas') => {
const { selectedComponents, getCurrentPageComponents } = get();
- const allComponents = getCurrentPageComponents();
+ const allComponents = getCurrentPageComponents(moduleId);
const _selected = [];
for (let componentId of selectedComponents) {
const component = {
@@ -1574,13 +1635,17 @@ export const createComponentsSlice = (set, get) => ({
const { modules } = get();
return modules[moduleId].queryIdNameMapping;
},
+ getQueryIdFromName: (queryName, moduleId = 'canvas') => {
+ const { modules } = get();
+ return modules[moduleId].queryNameIdMapping[queryName];
+ },
getContainerChildrenMapping: (id) => {
const { containerChildrenMapping } = get();
return containerChildrenMapping[id] || [];
},
getChildComponents: (parentId, moduleId = 'canvas') => {
const { getCurrentPageComponents } = get();
- const allComponents = getCurrentPageComponents();
+ const allComponents = getCurrentPageComponents(moduleId);
const childComponents = Object.entries(allComponents)
.filter(([_, component]) => component.component.parent === parentId)
.reduce((acc, [id, component]) => {
@@ -1609,8 +1674,8 @@ export const createComponentsSlice = (set, get) => ({
} else {
const [entityType, entityId, type, ...keys] = dependency.split('.');
const key = keys.join('.');
- const unResolvedValue = getNodeData(dependency);
- const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(), {}, false, []);
+ const unResolvedValue = getNodeData(dependency, moduleId);
+ const resolvedValue = resolveDynamicValues(unResolvedValue, getAllExposedValues(moduleId), {}, false, []);
if (type === undefined) {
set(
@@ -1624,7 +1689,7 @@ export const createComponentsSlice = (set, get) => ({
} else {
const shouldValidate = entityType === 'components' && entityId;
const validatedValue = shouldValidate
- ? get().debugger.validateProperty(entityId, type, key, resolvedValue)
+ ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId)
: resolvedValue;
// logic to handle the key like options[0].visible. It will resolve the visible directly and update the resolved store
@@ -1639,7 +1704,7 @@ export const createComponentsSlice = (set, get) => ({
lodashSet(
state.resolvedStore.modules[moduleId][entityType][entityId],
['properties', 'shouldRender'],
- (getResolvedComponent(entityId)?.['properties']?.['shouldRender'] ?? 0) + 1
+ (getResolvedComponent(entityId, null, moduleId)?.['properties']?.['shouldRender'] ?? 0) + 1
);
},
false,
@@ -1688,24 +1753,24 @@ export const createComponentsSlice = (set, get) => ({
}
},
- getParentIdFromDependency: (dependency) => {
+ getParentIdFromDependency: (dependency, moduleId = 'canvas') => {
const { getComponentDefinition } = get();
const componentId = dependency.split('.')[1];
- const component = getComponentDefinition(componentId);
+ const component = getComponentDefinition(componentId, moduleId);
return component?.component?.parent;
},
updateChildComponentResolvedValues: (dependency, path, length, moduleId = 'canvas') => {
const { getCustomResolvables, getNodeData, getAllExposedValues, getParentIdFromDependency } = get();
const [entityType, entityId, type, key] = dependency.split('.');
- const parentId = getParentIdFromDependency(dependency);
- const unResolvedValue = getNodeData(dependency);
+ const parentId = getParentIdFromDependency(dependency, moduleId);
+ const unResolvedValue = getNodeData(dependency, moduleId);
// Loop through the customResolvables and update the resolved value
for (let i = 0; i < length; i++) {
const resolvedValue = resolveDynamicValues(
unResolvedValue,
- getAllExposedValues(),
+ getAllExposedValues(moduleId),
getCustomResolvables(parentId, i, moduleId), // passing the parent ID and index to get the custom resolvables of the child
false,
[]
@@ -1713,7 +1778,7 @@ export const createComponentsSlice = (set, get) => ({
// If the index is not in the resolved store then add it with first index data
const shouldValidate = entityType === 'components' && entityId;
const validatedValue = shouldValidate
- ? get().debugger.validateProperty(entityId, type, key, resolvedValue)
+ ? get().debugger.validateProperty(entityId, type, key, resolvedValue, moduleId)
: resolvedValue;
set(
@@ -1736,7 +1801,8 @@ export const createComponentsSlice = (set, get) => ({
getParentComponentType: (parentId, moduleId) => {
if (!parentId) return null;
- const { modules, currentPageIndex } = get();
+ const { modules, getCurrentPageIndex } = get();
+ const currentPageIndex = getCurrentPageIndex(moduleId);
// Remove the tab id or any other details from the parent id (ie, -modal, -calendar, -0 from parentId)
const parentUUID = parentId.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] || parentId;
const component = modules[moduleId].pages[currentPageIndex].components[parentUUID];
@@ -1833,8 +1899,8 @@ export const createComponentsSlice = (set, get) => ({
return match; // Return the original match if no mapping is found
});
},
- calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition) => {
- const componentDefinition = get().getComponentDefinition(componentId);
+ calculateMoveableBoxHeightWithId: (componentId, currentLayout, stylesDefinition, moduleId = 'canvas') => {
+ const componentDefinition = get().getComponentDefinition(componentId, moduleId);
const layoutData = componentDefinition?.layouts?.[currentLayout];
const componentType = componentDefinition?.component?.component;
const label = componentDefinition?.component?.definition?.properties?.label;
@@ -1862,8 +1928,8 @@ export const createComponentsSlice = (set, get) => ({
}
const { alignment = { value: null }, width = { value: null }, auto = { value: null } } = stylesDefinition ?? {};
const resolvedLabel = label?.value?.length ?? 0;
- const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues()) ?? 0;
- const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues()) ?? false;
+ const resolvedWidth = resolveDynamicValues(width?.value + '', getAllExposedValues(moduleId)) ?? 0;
+ const resolvedAuto = resolveDynamicValues(auto?.value + '', getAllExposedValues(moduleId)) ?? false;
const resolvedAlignment =
alignment.value === 'top' || alignment.value === 'side'
@@ -1896,6 +1962,8 @@ export const createComponentsSlice = (set, get) => ({
state.modalsOpenOnCanvas = newModalOpenOnCanvas;
});
},
+ checkIfComponentIsModule: (componentId, moduleId = 'canvas') =>
+ get().getComponentDefinition(componentId, moduleId)?.component?.component === 'ModuleViewer',
updateContainerAutoHeight: (componentId) => {
if (
!componentId ||
diff --git a/frontend/src/AppBuilder/_stores/slices/createSelectors.js b/frontend/src/AppBuilder/_stores/slices/createSelectors.js
deleted file mode 100644
index 50fd299f4e..0000000000
--- a/frontend/src/AppBuilder/_stores/slices/createSelectors.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// import { useStore } from 'zustand';
-
-// const createSelectors = (_store) => {
-// const store = _store;
-// store.use = {};
-// for (const k of Object.keys(store.getState())) {
-// store.use[k] = () => useStore(_store, (s) => s[k]);
-// }
-
-// return store;
-// };
-
-// export { createSelectors };
diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js
index 79acc2c461..99d1ee660b 100644
--- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js
@@ -22,10 +22,20 @@ const initialState = {
};
export const createDataQuerySlice = (set, get) => ({
+ initializeDataQuerySlice: (moduleId = 'canvas') => {
+ set(
+ (state) => {
+ state.dataQuery.queries.modules[moduleId] = [];
+ },
+ false,
+ 'initializeDataQuerySlice'
+ );
+ },
dataQuery: {
...initialState,
- checkExistingQueryName: (newName) => get().dataQuery.queries.modules.canvas.some((query) => query.name === newName),
- getCurrentModuleQueries: (moduleId) => get().dataQuery.queries.modules[moduleId],
+ checkExistingQueryName: (newName, moduleId = 'canvas') =>
+ get().dataQuery.queries.modules[moduleId].some((query) => query.name === newName),
+ getCurrentModuleQueries: (moduleId = 'canvas') => get().dataQuery.queries.modules[moduleId],
setQueries: (queries, moduleId = 'canvas') => {
set(
(state) => {
@@ -49,7 +59,7 @@ export const createDataQuerySlice = (set, get) => ({
},
createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas') => {
const appVersionId = get().currentVersionId;
- const appId = get().app.appId;
+ const appId = get().appStore.modules[moduleId].app.appId;
const { options: defaultOptions, name } = getDefaultOptions(selectedDataSource);
const options = { ...defaultOptions, ...customOptions };
const kind = selectedDataSource.kind;
@@ -101,7 +111,7 @@ export const createDataQuerySlice = (set, get) => ({
return query;
});
});
- setSelectedQuery(data.id, data);
+ setSelectedQuery(data.id, moduleId);
if (shouldRunQuery) setQueryToBeRun(data);
/** Checks if there is an API call cached. If yes execute it */
@@ -121,12 +131,16 @@ export const createDataQuerySlice = (set, get) => ({
get().addNewQueryMapping(data.id, data.name, moduleId);
//! we need default value in store so that query can be resolved if referenced from other entity
- get().setResolvedQuery(data.id, {
- isLoading: false,
- data: [],
- rawData: [],
- id: data.id,
- });
+ get().setResolvedQuery(
+ data.id,
+ {
+ isLoading: false,
+ data: [],
+ rawData: [],
+ id: data.id,
+ },
+ moduleId
+ );
})
.catch((error) => {
set((state) => {
@@ -220,8 +234,8 @@ export const createDataQuerySlice = (set, get) => ({
})
.finally(() => setIsAppSaving(false));
- get().removeNode(`queries.${queryId}`);
- get().updateDependencyValues(`queries.${queryId}`);
+ get().removeNode(`queries.${queryId}`, moduleId);
+ get().updateDependencyValues(`queries.${queryId}`, moduleId);
},
duplicateQuery: (id, appId, moduleId = 'canvas') => {
set((state) => {
@@ -266,16 +280,20 @@ export const createDataQuerySlice = (set, get) => ({
...state.dataQuery.queries.modules[moduleId],
];
});
- setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id });
+ setSelectedQuery(data.id, moduleId);
get().addNewQueryMapping(data.id, data.name, moduleId);
//! we need default value in store so that query can be resolved if referenced from other entity
- get().setResolvedQuery(data.id, {
- isLoading: false,
- data: [],
- rawData: [],
- id: data.id,
- });
+ get().setResolvedQuery(
+ data.id,
+ {
+ isLoading: false,
+ data: [],
+ rawData: [],
+ id: data.id,
+ },
+ moduleId
+ );
const events = getEventsByComponentsId(queryToClone.id);
@@ -416,12 +434,23 @@ export const createDataQuerySlice = (set, get) => ({
});
});
}, 500),
- runOnLoadQueries: async () => {
- const queries = get().dataQuery.queries.modules.canvas;
+ runOnLoadQueries: async (moduleId = 'canvas') => {
+ const queries = get().dataQuery.queries.modules[moduleId];
try {
for (const query of queries) {
if ((query.options.runOnPageLoad || query.options.run_on_page_load) && isQueryRunnable(query)) {
- await get().queryPanel.runQuery(query.id, query.name, undefined, undefined, {}, false, true, 'canvas');
+ await get().queryPanel.runQuery(
+ query.id,
+ query.name,
+ undefined,
+ undefined,
+ {},
+ undefined,
+ undefined,
+ false,
+ true,
+ moduleId
+ );
}
}
return Promise.resolve();
diff --git a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
index f4bab3d4ee..912ee596ae 100644
--- a/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/debuggerSlice.js
@@ -32,7 +32,7 @@ export const createDebuggerSlice = (set, get) => ({
log: (log) => {
set(
(state) => {
- log.page = get().currentPageId;
+ log.page = get().getCurrentPageId('canvas');
state.debugger.logs.unshift(log);
if (log.logLevel === 'error') state.debugger.unreadErrorCount++;
},
@@ -44,7 +44,7 @@ export const createDebuggerSlice = (set, get) => ({
logMultiple: (logs) => {
set(
(state) => {
- state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().currentPageId })));
+ state.debugger.logs.push(...logs.map((log) => ({ ...log, page: get().getCurrentPageId('canvas') })));
state.debugger.unreadErrorCount += logs.length;
},
false,
@@ -84,20 +84,20 @@ export const createDebuggerSlice = (set, get) => ({
return transformedStyles;
},
- validateComponents: (components) => {
+ validateComponents: (components, moduleId = 'canvas') => {
const validateComponent = get().debugger.validateComponent;
const entries = Object.entries(components).map(([id, component]) => {
// If component is an array, validate each component in the array and return the array
if (Array.isArray(component)) {
- return [id, component.map((c) => validateComponent(id, c))];
+ return [id, component.map((c) => validateComponent(id, c, moduleId))];
}
- return [id, validateComponent(id, component)];
+ return [id, validateComponent(id, component, moduleId)];
});
return Object.fromEntries(entries);
},
- validateComponent: (id, component) => {
- const componentDefinition = get().getComponentDefinition(id);
+ validateComponent: (id, component, moduleId = 'canvas') => {
+ const componentDefinition = get().getComponentDefinition(id, moduleId);
const componentName = componentDefinition.component.name;
const componentType = componentDefinition.component.component;
const componentMeta = componentTypeDefinitionMap[componentType];
@@ -135,7 +135,7 @@ export const createDebuggerSlice = (set, get) => ({
};
const logs = allErrors.map((error) => ({
- page: get().currentPageId,
+ page: get().getCurrentPageId('canvas'),
type: 'component',
kind: 'component',
key: `${componentName} - ${error.property}`,
@@ -158,10 +158,10 @@ export const createDebuggerSlice = (set, get) => ({
return newComponent;
},
- validateProperty: (componentId, type, property, value) => {
+ validateProperty: (componentId, type, property, value, moduleId = 'canvas') => {
const log = get().debugger.log;
- const componentDefinition = get().getComponentDefinition(componentId);
+ const componentDefinition = get().getComponentDefinition(componentId, moduleId);
const componentName = componentDefinition.component.name;
const componentType = componentDefinition.component.component;
const componentMeta = componentTypeDefinitionMap[componentType];
@@ -179,7 +179,7 @@ export const createDebuggerSlice = (set, get) => ({
if (valid === false) {
log({
- page: get().currentPageId,
+ page: get().getCurrentPageId('canvas'),
type: 'component',
kind: 'component',
key: `${componentName} - ${componentMeta[type][property]?.displayName}`,
diff --git a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js
index 8bea44819d..66fc0d1b16 100644
--- a/frontend/src/AppBuilder/_stores/slices/dependencySlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/dependencySlice.js
@@ -12,48 +12,61 @@ const initialState = {
export const createDependencySlice = (set, get) => ({
...initialState,
+ initializeDependencySlice: (moduleId) => {
+ set(
+ (state) => {
+ state.dependencyGraph.modules[moduleId] = {
+ graph: new DependencyGraph(),
+ };
+ },
+ false,
+ 'initializeDependencySlice'
+ );
+ },
- addDependency: (fromPath, toPath, nodeData) => {
- if (!get().checkIfDependencyExists(fromPath, toPath)) {
+ addDependency: (fromPath, toPath, nodeData, moduleId = 'canvas') => {
+ if (!get().checkIfDependencyExists(fromPath, toPath, moduleId)) {
set((state) => {
- state.dependencyGraph.modules.canvas.graph.addDependency(fromPath, toPath, nodeData);
+ state.dependencyGraph.modules[moduleId].graph.addDependency(fromPath, toPath, nodeData);
return { ...state };
});
}
},
- updateDependency: (newFromPath, toPath, nodeData) =>
+ updateDependency: (newFromPath, toPath, nodeData, moduleId = 'canvas') =>
set((state) => {
- state.dependencyGraph.modules.canvas.graph.updateDependency(newFromPath, toPath, nodeData);
+ state.dependencyGraph.modules[moduleId].graph.updateDependency(newFromPath, toPath, nodeData);
return { ...state };
}),
- removeDependency: (toPath, clearToPath = false) =>
+ removeDependency: (toPath, clearToPath = false, moduleId = 'canvas') =>
set((state) => {
- state.dependencyGraph.modules.canvas.graph.removeDependency(toPath, clearToPath);
+ state.dependencyGraph.modules[moduleId].graph.removeDependency(toPath, clearToPath);
return { ...state };
}),
- removeNode: (path) =>
+ removeNode: (path, moduleId = 'canvas') =>
set((state) => {
- state.dependencyGraph.modules.canvas.graph.removeNode(path);
+ state.dependencyGraph.modules[moduleId].graph.removeNode(path);
return { ...state };
}),
- getNodeData: (path) => get().dependencyGraph.modules.canvas.graph.getNodeData(path),
+ getNodeData: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getNodeData(path),
- getDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDependencies(path),
+ getDependencies: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependencies(path),
- getDirectDependencies: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependencies(path),
+ getDirectDependencies: (path, moduleId = 'canvas') =>
+ get().dependencyGraph.modules[moduleId].graph.getDirectDependencies(path),
- getDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDependents(path),
+ getDependents: (path, moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getDependents(path),
- getDirectDependents: (path) => get().dependencyGraph.modules.canvas.graph.getDirectDependents(path),
+ getDirectDependents: (path, moduleId = 'canvas') =>
+ get().dependencyGraph.modules[moduleId].graph.getDirectDependents(path),
- getOverallOrder: () => get().dependencyGraph.modules.canvas.graph.getOverallOrder(),
+ getOverallOrder: (moduleId = 'canvas') => get().dependencyGraph.modules[moduleId].graph.getOverallOrder(),
- checkIfDependencyExists: (fromPath, toPath) => {
- const dependencies = get().getDependencies(fromPath);
+ checkIfDependencyExists: (fromPath, toPath, moduleId = 'canvas') => {
+ const dependencies = get().getDependencies(fromPath, moduleId);
return dependencies.includes(toPath);
},
});
diff --git a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
index 1edd3994c5..1077a22608 100644
--- a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
@@ -243,7 +243,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({
const versionIsAvailableInEnvironment = environment?.priority <= get().currentAppVersionEnvironment?.priority;
if (!versionIsAvailableInEnvironment) {
- const appId = useStore.getState().app.appId;
+ const { appId } = useStore.getState().appStore.modules.canvas.app;
const response = await appEnvironmentService.postEnvironmentChangedAction({
appId,
editorEnvironmentId: environmentId,
@@ -285,7 +285,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({
promoteAppVersionAction: async (versionId, onSuccess, onFailure) => {
try {
- const appId = useStore.getState().app.appId; // Correct way to access appId
+ const { appId } = useStore.getState().appStore.modules.canvas.app;
const response = await appVersionService.promoteEnvironment(appId, versionId, get().selectedEnvironment.id);
set((state) => ({
diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
index f93f64b1c5..5991f093f8 100644
--- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js
@@ -6,7 +6,6 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { dfs } from '@/_stores/handleReferenceTransactions';
import { isQueryRunnable, isValidUUID, serializeNestedObjectToQueryParams } from '@/_helpers/utils';
import useStore from '@/AppBuilder/_stores/store';
-import { handleLowPriorityWork } from '@/AppBuilder/_helpers/editorHelpers';
import _ from 'lodash';
import { logoutAction } from '@/AppBuilder/_utils/auth';
import { copyToClipboard } from '@/_helpers/appUtils';
@@ -59,8 +58,8 @@ export const useEventActions = (moduleId = 'canvas') => {
);
const memoizedUpdateEventsField = useCallback(
- (field, value) => updateEventsField(field, value, moduleId),
- [updateEventsField, moduleId]
+ (field, value, moduleId) => updateEventsField(field, value, moduleId),
+ [updateEventsField]
);
return {
@@ -72,6 +71,17 @@ export const useEventActions = (moduleId = 'canvas') => {
};
export const createEventsSlice = (set, get) => ({
+ initializeEventsSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.eventsSlice.module[moduleId] = {
+ ...initialState.module.canvas,
+ };
+ },
+ false,
+ 'initializeEventsSlice'
+ );
+ },
eventsSlice: {
...initialState,
setEvents: (events, moduleId = 'canvas') => {
@@ -98,25 +108,17 @@ export const createEventsSlice = (set, get) => ({
);
},
fireEvent: (eventName, id, moduleId, customResolvables, options) => {
- const { eventsSlice } = get();
- const {
- handleEvent,
- isEditorLoading,
- module: {
- [moduleId]: { events },
- },
- } = eventsSlice;
+ const { eventsSlice, getCurrentMode, getEditorLoading } = get();
+ const { handleEvent } = eventsSlice;
+ const events = get().eventsSlice.module[moduleId].events;
const componentEvents = events.filter((event) => event.sourceId === id);
- const mode = get().currentMode;
- if (isEditorLoading) return;
- // if (mode === 'edit' && eventName === 'onClick') {
- // onComponentClick(id, component);
- // }
+ const mode = getCurrentMode(moduleId);
+ if (getEditorLoading(moduleId)) return;
handleEvent(
eventName,
componentEvents,
{ ...options, customVariables: { ...customResolvables } },
- 'canvas',
+ moduleId,
mode
);
},
@@ -129,7 +131,7 @@ export const createEventsSlice = (set, get) => ({
},
} = eventsSlice;
const componentEvents = events.filter((event) => event.sourceId === id);
- executeActionsForEventId('onClick', componentEvents, mode);
+ executeActionsForEventId('onClick', componentEvents, mode, moduleId);
},
addEvent: (event, moduleId = 'canvas') =>
set((state) => {
@@ -164,46 +166,46 @@ export const createEventsSlice = (set, get) => ({
createAppVersionEventHandlers: async (event, moduleId) => {
// get().actions.setIsSaving(true);
// set({ eventsCreatedLoader: true });
- get().eventsSlice.updateEventsField('eventsCreatedLoader', true);
- const appId = get().app.appId;
+ get().eventsSlice.updateEventsField('eventsCreatedLoader', true, moduleId);
+ const appId = get().appStore.modules[moduleId].app.appId;
const versionId = get().currentVersionId;
appVersionService
.createAppVersionEventHandler(appId, versionId, event)
.then((response) => {
- get().eventsSlice.updateEventsField('eventsCreatedLoader', false);
- get().eventsSlice.addEvent(response);
+ get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId);
+ get().eventsSlice.addEvent(response, moduleId);
})
.catch((err) => {
- get().eventsSlice.updateEventsField('eventsCreatedLoader', false);
+ get().eventsSlice.updateEventsField('eventsCreatedLoader', false, moduleId);
toast.error(err?.error || 'An error occurred while creating the event handler');
});
},
deleteAppVersionEventHandler: async (eventId, index, moduleId = 'canvas') => {
- const appId = get().app.appId;
+ const appId = get().appStore.modules[moduleId].app.appId;
const versionId = get().currentVersionId;
- get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index);
+ get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', index, moduleId);
const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId);
- get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null);
+ get().eventsSlice.updateEventsField('eventToDeleteLoaderIndex', null, moduleId);
if (response?.affected === 1) {
- get().eventsSlice.removeEvent(eventId);
+ get().eventsSlice.removeEvent(eventId, moduleId);
}
},
updateAppVersionEventHandlers: async (events, updateType = 'update', param, moduleId = 'canvas') => {
if (param === 'actionId') {
- get().eventsSlice.updateEventsField('actionsUpdatedLoader', true);
+ get().eventsSlice.updateEventsField('actionsUpdatedLoader', true, moduleId);
}
if (param === 'eventId') {
- get().eventsSlice.updateEventsField('eventsUpdatedLoader', true);
+ get().eventsSlice.updateEventsField('eventsUpdatedLoader', true, moduleId);
}
const componentNameIdMapping = get().modules['canvas'].componentNameIdMapping;
const queryNameIdMapping = get().modules['canvas'].queryNameIdMapping;
//! Revisit this
- const appId = get().app.appId;
+ const appId = get().appStore.modules[moduleId].app.appId;
const versionId = get().currentVersionId;
const newEvents = replaceEntityReferencesWithIds(events, componentNameIdMapping, queryNameIdMapping);
const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, newEvents, updateType);
- get().eventsSlice.updateEventsField('actionsUpdatedLoader', false);
- get().eventsSlice.updateEventsField('eventsUpdatedLoader', false);
+ get().eventsSlice.updateEventsField('actionsUpdatedLoader', false, moduleId);
+ get().eventsSlice.updateEventsField('eventsUpdatedLoader', false, moduleId);
set((state) => {
const eventsInState = state.eventsSlice.getModuleEvents('canvas');
const newEvents = eventsInState.map((event) => {
@@ -266,19 +268,19 @@ export const createEventsSlice = (set, get) => ({
return foundEvent && foundEvent.name === eventName;
});
try {
- return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode);
+ return get().eventsSlice.onEvent(eventName, filteredEvents, options, mode, moduleId);
} catch (error) {
console.error(error);
}
},
- onEvent: async (eventName, events, options = {}, mode = 'edit') => {
+ onEvent: async (eventName, events, options = {}, mode = 'edit', moduleId = 'canvas') => {
const executeActionsForEventId = get().eventsSlice.executeActionsForEventId;
const customVariables = options?.customVariables ?? {};
const { setExposedValue } = get();
if (eventName === 'onPageLoad') {
// for onPageLoad events, we need to execute the actions after the page is loaded
- executeActionsForEventId('onPageLoad', events, mode, customVariables);
+ executeActionsForEventId('onPageLoad', events, mode, customVariables, moduleId);
}
if (eventName === 'onTrigger') {
const { queryPanel, dataQuery } = get();
@@ -287,7 +289,7 @@ export const createEventsSlice = (set, get) => ({
const { queryName, parameters } = options;
const queryId = queries.filter((query) => query.name === queryName && isQueryRunnable(query))?.[0]?.id;
if (!queryId) return;
- runQuery(queryId, queryName, true, mode, parameters);
+ runQuery(queryId, queryName, true, mode, parameters, undefined, undefined, false, false, moduleId);
}
if (eventName === 'onTableActionButtonClicked') {
const { action, tableActionEvents } = options;
@@ -296,7 +298,7 @@ export const createEventsSlice = (set, get) => ({
if (action && executeableActions) {
for (const event of executeableActions) {
if (event?.event?.actionId) {
- await get().eventsSlice.executeAction(event.event, mode, customVariables);
+ await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId);
}
}
} else {
@@ -310,7 +312,7 @@ export const createEventsSlice = (set, get) => ({
if (column && tableColumnEvents) {
for (const event of tableColumnEvents) {
if (event?.event?.actionId) {
- await get().eventsSlice.executeAction(event.event, mode, customVariables);
+ await get().eventsSlice.executeAction(event.event, mode, customVariables, moduleId);
}
}
} else {
@@ -321,13 +323,13 @@ export const createEventsSlice = (set, get) => ({
if (eventName === 'onCalendarEventSelect') {
const { id, calendarEvent } = options;
setExposedValue(id, 'selectedEvent', calendarEvent);
- executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables);
+ executeActionsForEventId('onCalendarEventSelect', events, mode, customVariables, moduleId);
}
if (eventName === 'onCalendarSlotSelect') {
const { id, selectedSlots } = options;
setExposedValue(id, 'selectedSlots', selectedSlots);
- executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables);
+ executeActionsForEventId('onCalendarSlotSelect', events, mode, customVariables, moduleId);
}
if (
@@ -385,31 +387,31 @@ export const createEventsSlice = (set, get) => ({
'onTableDataDownload',
].includes(eventName)
) {
- executeActionsForEventId(eventName, events, mode, customVariables);
+ executeActionsForEventId(eventName, events, mode, customVariables, moduleId);
}
if (eventName === 'onBulkUpdate') {
- await executeActionsForEventId(eventName, events, mode, customVariables);
+ await executeActionsForEventId(eventName, events, mode, customVariables, moduleId);
}
if (['onDataQuerySuccess', 'onDataQueryFailure'].includes(eventName)) {
if (!events || !Array.isArray(events) || events.length === 0) return;
- await executeActionsForEventId(eventName, events, mode, customVariables);
+ await executeActionsForEventId(eventName, events, mode, customVariables, moduleId);
}
},
- executeActionsForEventId: async (eventId, events = [], mode, customVariables) => {
+ executeActionsForEventId: async (eventId, events = [], mode, customVariables, moduleId = 'canvas') => {
if (!events || !Array.isArray(events) || events.length === 0) return;
const filteredEvents = events
?.filter((event) => event?.event.eventId === eventId)
?.sort((a, b) => a.index - b.index);
for (const event of filteredEvents) {
- await get().eventsSlice.executeAction(event, mode, customVariables);
+ await get().eventsSlice.executeAction(event, mode, customVariables, moduleId);
}
},
logError(errorType, errorKind, error, eventObj = '', options = {}, logLevel = 'error') {
const { event = eventObj } = eventObj;
const pages = get().modules.canvas.pages;
- const currentPageId = get().currentPageId;
+ const currentPageId = get().getCurrentPageId('canvas');
const currentPage = pages.find((page) => page.id === currentPageId);
const componentIdMapping = get().modules['canvas'].componentNameIdMapping;
const componentName = Object.keys(componentIdMapping).find(
@@ -479,12 +481,12 @@ export const createEventsSlice = (set, get) => ({
timestamp: moment().toISOString(),
});
},
- executeAction: debounce(async (eventObj, mode, customVariables = {}) => {
+ executeAction: debounce(async (eventObj, mode, customVariables = {}, moduleId = 'canvas') => {
const { event = eventObj } = eventObj;
const { getExposedValueOfComponent, getResolvedValue } = get();
if (event?.runOnlyIf) {
- const shouldRun = getResolvedValue(event.runOnlyIf, customVariables);
+ const shouldRun = getResolvedValue(event.runOnlyIf, customVariables, moduleId);
if (!shouldRun) {
return false;
}
@@ -494,7 +496,8 @@ export const createEventsSlice = (set, get) => ({
//! TODO run only if conditions
switch (event.actionId) {
case 'show-alert': {
- let message = getResolvedValue(event.message, customVariables);
+ let message = getResolvedValue(event.message, customVariables, moduleId);
+
if (typeof message === 'object') message = JSON.stringify(message);
switch (event.alertType) {
@@ -552,16 +555,32 @@ export const createEventsSlice = (set, get) => ({
if (!queryId && !queryName) {
throw new Error('No query selected');
}
+ // Check and replace the module input dummy queries with the linked query id
+ /* Logic starts here */
+ const moduleInputDummyQueries = get()?.getModuleInputDummyQueries?.() || {};
+ let updatedQueryId = queryId,
+ updatedQueryName = queryName,
+ updatedModuleId = moduleId;
+ if (moduleInputDummyQueries[queryId]) {
+ updatedQueryId =
+ get().resolvedStore.modules[moduleId].exposedValues.input[moduleInputDummyQueries[queryId]]?.id;
+ updatedModuleId = 'canvas'; // Updating the moduleId to canvas as the query is a module input query which will be present on canvas
+ }
+ /* Logic ends here */
+
+ if (!updatedQueryId) {
+ throw new Error('No query selected');
+ }
const resolvedParams = {};
if (params) {
Object.keys(params).map(
- (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined))
+ (param) => (resolvedParams[param] = getResolvedValue(params[param], undefined, moduleId))
);
}
// !Todo tackle confirm query part once done
return get().queryPanel.runQuery(
- queryId,
- queryName,
+ updatedQueryId,
+ updatedQueryName,
undefined,
undefined,
resolvedParams,
@@ -569,7 +588,7 @@ export const createEventsSlice = (set, get) => ({
eventId,
false,
false,
- 'canvas'
+ updatedModuleId
);
} catch (error) {
get().eventsSlice.logError('run_query', 'run-query', error, eventObj, {
@@ -583,7 +602,7 @@ export const createEventsSlice = (set, get) => ({
}
case 'open-webpage': {
//! if resolvecode default value should be the value itself not empty string ... Ask KAVIN
- const resolvedValue = getResolvedValue(event.url, customVariables);
+ const resolvedValue = getResolvedValue(event.url, customVariables, moduleId);
// const url = resolveReferences(event.url, undefined, customVariables);
window.open(resolvedValue, event?.windowTarget === 'newTab' ? '_blank' : '_self');
return Promise.resolve();
@@ -593,7 +612,7 @@ export const createEventsSlice = (set, get) => ({
if (!event.slug) {
throw new Error('No application slug provided');
}
- const resolvedValue = getResolvedValue(event.slug, customVariables);
+ const resolvedValue = getResolvedValue(event.slug, customVariables, moduleId);
const slug = resolvedValue;
const queryParams = event.queryParams?.reduce(
(result, queryParam) => ({
@@ -631,23 +650,23 @@ export const createEventsSlice = (set, get) => ({
case 'close-modal':
return get().eventsSlice.showModal(event.modal, false, eventObj);
case 'copy-to-clipboard': {
- const contentToCopy = getResolvedValue(event.contentToCopy, customVariables);
+ const contentToCopy = getResolvedValue(event.contentToCopy, customVariables, moduleId);
copyToClipboard(contentToCopy);
return Promise.resolve();
}
case 'set-localstorage-value': {
- const key = getResolvedValue(event.key, customVariables);
- const value = getResolvedValue(event.value, customVariables);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ const value = getResolvedValue(event.value, customVariables, moduleId);
localStorage.setItem(key, value);
return Promise.resolve();
}
case 'generate-file': {
// const fileType = event.fileType;
- const data = getResolvedValue(event.data, customVariables) || [];
- const fileName = getResolvedValue(event.fileName, customVariables) || 'data.txt';
- const fileType = getResolvedValue(event.fileType, customVariables) || 'csv';
+ const data = getResolvedValue(event.data, customVariables, moduleId) || [];
+ const fileName = getResolvedValue(event.fileName, customVariables, moduleId) || 'data.txt';
+ const fileType = getResolvedValue(event.fileType, customVariables, moduleId) || 'csv';
const fileData = {
csv: generateCSV,
plaintext: (plaintext) => plaintext,
@@ -658,15 +677,22 @@ export const createEventsSlice = (set, get) => ({
}
case 'set-table-page': {
- get().eventsSlice.setTablePageIndex(event.table, getResolvedValue(event.pageIndex), eventObj);
+ get().eventsSlice.setTablePageIndex(
+ event.table,
+ getResolvedValue(event.pageIndex, undefined, moduleId),
+ eventObj
+ );
break;
}
case 'set-custom-variable': {
const { setVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- const value = getResolvedValue(event.value, customVariables);
- setVariable(key, value);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ const value = getResolvedValue(event.value, customVariables, moduleId);
+
+ console.log('here--- set-custom-variable', key, value, moduleId);
+
+ setVariable(key, value, moduleId);
return Promise.resolve();
// customAppVariables[key] = value;
// const resp = useCurrentStateStore.getState().actions.setCurrentState({
@@ -687,20 +713,20 @@ export const createEventsSlice = (set, get) => ({
case 'get-custom-variable': {
const { getVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- return getVariable(key);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ return getVariable(key, moduleId);
}
case 'unset-all-custom-variables': {
const { unsetAllVariables } = get();
- unsetAllVariables();
+ unsetAllVariables(moduleId);
return Promise.resolve();
}
case 'unset-custom-variable': {
const { unsetVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- unsetVariable(key);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ unsetVariable(key, moduleId);
return Promise.resolve();
// const customAppVariables = { ...getCurrentState().variables };
// delete customAppVariables[key];
@@ -717,9 +743,9 @@ export const createEventsSlice = (set, get) => ({
case 'set-page-variable': {
const { setPageVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- const value = getResolvedValue(event.value, customVariables);
- setPageVariable(key, value);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ const value = getResolvedValue(event.value, customVariables, moduleId);
+ setPageVariable(key, value, moduleId);
return Promise.resolve();
// const customPageVariables = {
// ...getCurrentState().page.variables,
@@ -749,20 +775,20 @@ export const createEventsSlice = (set, get) => ({
case 'get-page-variable': {
const { getPageVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- return getPageVariable(key);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ return getPageVariable(key, moduleId);
}
case 'unset-all-page-variables': {
const { unsetAllPageVariables } = get();
- unsetAllPageVariables();
+ unsetAllPageVariables(moduleId);
return Promise.resolve();
}
case 'unset-page-variable': {
const { unsetPageVariable } = get();
- const key = getResolvedValue(event.key, customVariables);
- unsetPageVariable(key);
+ const key = getResolvedValue(event.key, customVariables, moduleId);
+ unsetPageVariable(key, moduleId);
return Promise.resolve();
// useStore.getState().unsetPageVariable(key);
@@ -829,17 +855,12 @@ export const createEventsSlice = (set, get) => ({
// }));
// console.log('actionArguments', event.componentSpecificActionParams);
const actionArguments = event.componentSpecificActionParams.map((param) => {
- const value = getResolvedValue(param.value, customVariables);
+ const value = getResolvedValue(param.value, customVariables, moduleId);
return {
...param,
value: value,
- // value: resolveCode(re.valueWithBrackets, getAllExposedValues()),
};
});
- // const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
- // ...param,
- // value: resolveReferences(param.value, getAllExposedValues(), customVariables),
- // }));
const actionPromise = action && action(...actionArguments.map((argument) => argument.value));
return actionPromise ?? Promise.resolve();
@@ -858,7 +879,7 @@ export const createEventsSlice = (set, get) => ({
throw new Error('No page ID provided');
}
const { switchPage } = get();
- const page = get().modules.canvas.pages.find((page) => page.id === event.pageId);
+ const page = get().modules[moduleId].pages.find((page) => page.id === event.pageId);
const queryParams = event.queryParams || [];
if (page.restricted && mode !== 'edit') {
toast.error('Access to this page is restricted. Contact admin to know more.');
@@ -866,8 +887,8 @@ export const createEventsSlice = (set, get) => ({
const resolvedQueryParams = [];
queryParams.forEach((param) => {
resolvedQueryParams.push([
- getResolvedValue(param[0], customVariables),
- getResolvedValue(param[1], customVariables),
+ getResolvedValue(param[0], customVariables, moduleId),
+ getResolvedValue(param[1], customVariables, moduleId),
]);
});
const currentUrlParams = new URLSearchParams(window.location.search);
@@ -880,7 +901,7 @@ export const createEventsSlice = (set, get) => ({
}
}
});
- switchPage(page.id, page.handle, resolvedQueryParams);
+ switchPage(page.id, page.handle, resolvedQueryParams, moduleId);
} else {
toast.error('Page is disabled');
//!TODO push to debugger
@@ -904,14 +925,14 @@ export const createEventsSlice = (set, get) => ({
}
}),
- generateAppActions: (queryId, mode, isPreview = false) => {
+ generateAppActions: (queryId, mode, isPreview = false, moduleId = 'canvas') => {
const { getCurrentPageComponents, dataQuery, eventsSlice, queryPanel, modules } = get();
const { previewQuery } = queryPanel;
const { executeAction } = eventsSlice;
- const currentComponents = Object.entries(getCurrentPageComponents());
+ const currentComponents = Object.entries(getCurrentPageComponents(moduleId));
- const runQuery = (queryName = '', parameters) => {
- const query = dataQuery.queries.modules['canvas'].find((query) => {
+ const runQuery = (queryName = '', parameters, moduleId = 'canvas') => {
+ const query = dataQuery.queries.modules[moduleId].find((query) => {
const isFound = query.name === queryName;
if (isPreview) {
return isFound;
@@ -944,7 +965,7 @@ export const createEventsSlice = (set, get) => ({
parameters: processedParams,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const setVariable = (key = '', value = '') => {
@@ -954,7 +975,7 @@ export const createEventsSlice = (set, get) => ({
key,
value,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
}
};
@@ -964,7 +985,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'get-custom-variable',
key,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
}
};
@@ -981,7 +1002,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'unset-custom-variable',
key,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
}
};
@@ -991,14 +1012,14 @@ export const createEventsSlice = (set, get) => ({
alertType,
message,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const logout = () => {
const event = {
actionId: 'logout',
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const showModal = (modalName = '') => {
@@ -1013,7 +1034,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'show-modal',
modal,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const closeModal = (modalName = '') => {
@@ -1028,7 +1049,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'close-modal',
modal,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const setLocalStorage = (key = '', value = '') => {
@@ -1037,7 +1058,7 @@ export const createEventsSlice = (set, get) => ({
key,
value,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const copyToClipboard = (contentToCopy = '') => {
@@ -1045,7 +1066,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'copy-to-clipboard',
contentToCopy,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const goToApp = (slug = '', queryParams = []) => {
@@ -1054,7 +1075,7 @@ export const createEventsSlice = (set, get) => ({
slug,
queryParams,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const generateFile = (fileName, fileType, data) => {
@@ -1068,7 +1089,7 @@ export const createEventsSlice = (set, get) => ({
data,
fileType,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const setPageVariable = (key = '', value = '') => {
@@ -1077,7 +1098,7 @@ export const createEventsSlice = (set, get) => ({
key,
value,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const getPageVariable = (key = '') => {
@@ -1085,7 +1106,7 @@ export const createEventsSlice = (set, get) => ({
actionId: 'get-page-variable',
key,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const unsetAllPageVariables = () => {
@@ -1100,10 +1121,10 @@ export const createEventsSlice = (set, get) => ({
actionId: 'unset-page-variable',
key,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
- const switchPage = (pageHandle, queryParams = []) => {
+ const switchPage = (pageHandle, queryParams = [], moduleId = 'canvas') => {
if (isPreview) {
mode != 'view' &&
toast('Page will not be switched for query preview', {
@@ -1111,7 +1132,7 @@ export const createEventsSlice = (set, get) => ({
});
return Promise.resolve();
}
- const pages = modules.canvas.pages;
+ const pages = modules[moduleId].pages;
const transformedPageHandle = pageHandle?.toLowerCase();
const pageId = pages.find((page) => page.handle === transformedPageHandle)?.id;
@@ -1128,7 +1149,7 @@ export const createEventsSlice = (set, get) => ({
pageId,
queryParams,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const logInfo = (log, isFromTransformation) => {
@@ -1144,7 +1165,7 @@ export const createEventsSlice = (set, get) => ({
eventType: 'customLog',
query,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const logError = (log, isFromTransformation = false) => {
@@ -1160,7 +1181,7 @@ export const createEventsSlice = (set, get) => ({
eventType: 'customLog',
query,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
const log = (log, isFromTransformation = false) => {
@@ -1176,7 +1197,7 @@ export const createEventsSlice = (set, get) => ({
eventType: 'customLog',
query,
};
- return executeAction(event, mode, {});
+ return executeAction(event, mode, {}, moduleId);
};
return {
diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js
new file mode 100644
index 0000000000..c20768a257
--- /dev/null
+++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js
@@ -0,0 +1,139 @@
+const initialState = {
+ selectedNodes: new Set(),
+ searchedNodes: new Set(),
+ inspectorSearchValue: '',
+ inspectorSearchResults: new Set(),
+ selectedNodePath: null,
+};
+
+export const createInspectorSlice = (set, get) => ({
+ ...initialState,
+ getSelectedNodes: () => {
+ const selectedNodes = get().selectedNodes;
+ return Array.from(selectedNodes);
+ },
+ setSelectedNodes: (node) => {
+ const selectedNodes = get().selectedNodes;
+ const newSelectedNodes = new Set(selectedNodes);
+ if (newSelectedNodes.has(node)) {
+ newSelectedNodes.delete(node);
+ } else {
+ newSelectedNodes.add(node);
+ }
+ set({ selectedNodes: newSelectedNodes });
+ },
+ getInspectorSearchResults: () => {
+ const inspectorSearchResults = get().inspectorSearchResults;
+ return Array.from(inspectorSearchResults);
+ },
+ setInspectorSearchValue: (value) => {
+ set({ inspectorSearchValue: value });
+ },
+ setInspectorSearchResults: (results) => {
+ set({ inspectorSearchResults: results });
+ },
+ setSelectedNodePath: (path) => {
+ set({ selectedNodePath: path });
+ },
+ getAllComponentChildrenById: (id) => {
+ const { getComponentDefinition, getResolvedComponent } = get();
+ const component = getComponentDefinition(id);
+ const componentType = component?.component?.component;
+ switch (componentType) {
+ case 'Container':
+ case 'Form':
+ case 'ModalV2':
+ return [
+ ...get().getContainerChildrenMapping(id),
+ ...get().getContainerChildrenMapping(`${id}-header`),
+ ...get().getContainerChildrenMapping(`${id}-footer`),
+ ];
+ case 'Tabs': {
+ const tabs = getResolvedComponent(id)?.properties?.tabs;
+ const children = Array.isArray(tabs) ? tabs : [];
+ const res = children
+ ?.map((tab) => {
+ const tabId = `${id}-${tab.id}`;
+ return get().getContainerChildrenMapping(tabId);
+ })
+ .reduce((acc, curr) => {
+ return [...acc, ...curr];
+ }, []);
+ return res;
+ }
+ default:
+ return get().getContainerChildrenMapping(id);
+ }
+ },
+
+ formatInspectorComponentData: (
+ componentIdNameMapping,
+ exposedComponentsVariables,
+ searchablePaths = new Set(),
+ moduleId = 'canvas'
+ ) => {
+ const { getComponentDefinition, getAllComponentChildrenById } = get();
+ const data = Object.entries(componentIdNameMapping)
+ .filter(([key]) => {
+ const component = getComponentDefinition(key, moduleId);
+ return !component?.component?.parent;
+ })
+ .map(([key, name]) => {
+ const component = getComponentDefinition(key, moduleId);
+ let parentComponentType = null;
+ if (component?.component?.parent) {
+ const parentComponent = getComponentDefinition(component.component.parent, moduleId);
+ parentComponentType = parentComponent?.component?.component;
+ }
+ return {
+ key,
+ name: name || key,
+ parentType: parentComponentType,
+ };
+ })
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
+
+ const reduceData = (obj, path = 'components', level = 1) => {
+ let data = obj;
+ if (!obj || typeof obj !== 'object') return [];
+
+ return data
+ .filter((item) => item.name)
+ .reduce((acc, { key, name, parentType }) => {
+ const currentPath = `components.${name}`;
+ const actualPath = `${path}.${name}`;
+ searchablePaths.add(actualPath);
+ const children = getAllComponentChildrenById(key).map((childKey) => {
+ const childComponent = getComponentDefinition(childKey);
+ let parentComponentType = null;
+ if (childComponent?.component?.parent) {
+ const parentComponent = getComponentDefinition(childComponent.component.parent);
+ parentComponentType = parentComponent?.component?.component;
+ }
+ return {
+ key: childKey,
+ name: childComponent?.component?.name,
+ parentType: parentComponentType,
+ };
+ });
+
+ return [
+ ...acc,
+ {
+ id: actualPath,
+ name,
+ children: reduceData(children, actualPath, level + 1),
+ metadata: {
+ type: 'components',
+ path: currentPath,
+ parentType: parentType,
+ actualPath,
+ },
+ },
+ ];
+ }, []);
+ };
+
+ return reduceData(data);
+ },
+});
diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js
index 367ca4cf0c..8a3989a05b 100644
--- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js
@@ -30,16 +30,23 @@ export const createLeftSideBarSlice = (set, get) => ({
),
setPathToBeInspected: (pathToBeInspected) => set(() => ({ pathToBeInspected }), false, 'setPathToBeInspected'),
setComponentToInspect: (componentToInspect) => {
- const { setPathToBeInspected, setSelectedSidebarItem, toggleLeftSidebar, selectedSidebarItem } = get();
- setPathToBeInspected(['components', componentToInspect]);
+ const {
+ setPathToBeInspected,
+ setSelectedSidebarItem,
+ toggleLeftSidebar,
+ selectedSidebarItem,
+ setSelectedNodePath,
+ } = get();
+ // setPathToBeInspected(['components', componentToInspect]);
+ setSelectedNodePath(`components.${componentToInspect}`);
if (selectedSidebarItem !== 'inspect') {
setSelectedSidebarItem('inspect');
toggleLeftSidebar(true);
}
},
- getComponentIdToAutoScroll: (componentId) => {
+ getComponentIdToAutoScroll: (componentId, moduleId = 'canvas') => {
const { getCurrentPageComponents, getAllExposedValues, modalsOpenOnCanvas } = get();
- const currentPageComponents = getCurrentPageComponents();
+ const currentPageComponents = getCurrentPageComponents(moduleId);
let targetComponentId = componentId;
let current = componentId;
@@ -66,7 +73,7 @@ export const createLeftSideBarSlice = (set, get) => ({
const tabId = parentId.replace(regForTabs, ''); // Extract tab id from parent id
- const { currentTab } = getAllExposedValues().components?.[tabId] || {};
+ const { currentTab } = getAllExposedValues(moduleId).components?.[tabId] || {};
const activeTabIndex = Number(currentTab);
nextPossibleCandidate = tabId;
diff --git a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js
index 6b1da506e1..6a0ef71271 100644
--- a/frontend/src/AppBuilder/_stores/slices/loaderSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/loaderSlice.js
@@ -1,10 +1,43 @@
const initialState = {
- isEditorLoading: true,
- isCanvasLoading: false,
+ loaderStore: {
+ modules: {
+ canvas: {
+ isEditorLoading: true,
+ },
+ },
+ },
};
-export const createLoaderSlice = (set) => ({
+export const createLoaderSlice = (set, get) => ({
...initialState,
- setEditorLoading: (status) => set(() => ({ isEditorLoading: status }), false, 'setEditorLoading'),
- setCanvasLoading: (status) => set(() => ({ isCanvasLoading: status }), false, 'setCanvasLoading'),
+ initializeLoaderSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.loaderStore.modules[moduleId] = {
+ ...initialState.loaderStore.modules.canvas,
+ };
+ },
+ false,
+ 'initializeLoaderSlice'
+ );
+ },
+ setEditorLoading: (status, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.loaderStore.modules[moduleId].isEditorLoading = status;
+ },
+ false,
+ 'setEditorLoading'
+ ),
+ setIsLoaderLoading: (status, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.loaderStore.modules[moduleId] = {
+ isLoaderLoading: status,
+ };
+ },
+ false,
+ 'setIsLoaderLoading'
+ ),
+ getEditorLoading: (moduleId) => get().loaderStore.modules[moduleId].isEditorLoading,
});
diff --git a/frontend/src/AppBuilder/_stores/slices/modeSlice.js b/frontend/src/AppBuilder/_stores/slices/modeSlice.js
index a24d5c93d6..ce2672a91a 100644
--- a/frontend/src/AppBuilder/_stores/slices/modeSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/modeSlice.js
@@ -1,8 +1,33 @@
const initialState = {
- currentMode: 'view',
+ modeStore: {
+ modules: {
+ canvas: {
+ currentMode: 'view',
+ },
+ },
+ },
};
-export const createModeSlice = (set) => ({
+export const createModeSlice = (set, get) => ({
...initialState,
- setCurrentMode: (currentMode) => set(() => ({ currentMode }), false, 'setCurrentMode'),
+ initializeModeSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.modeStore.modules[moduleId] = {
+ ...initialState.modeStore.modules.canvas,
+ };
+ },
+ false,
+ 'initializeModeSlice'
+ );
+ },
+ setCurrentMode: (currentMode, moduleId = 'canvas') =>
+ set(
+ (state) => {
+ state.modeStore.modules[moduleId].currentMode = currentMode;
+ },
+ false,
+ 'setCurrentMode'
+ ),
+ getCurrentMode: (moduleId) => get().modeStore.modules[moduleId].currentMode,
});
diff --git a/frontend/src/AppBuilder/_stores/slices/moduleSlice.js b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js
new file mode 100644
index 0000000000..5c97f74cf1
--- /dev/null
+++ b/frontend/src/AppBuilder/_stores/slices/moduleSlice.js
@@ -0,0 +1,5 @@
+import { getEditionSpecificSlice } from '../../../modules/common/helpers/getEditionSpecificSlice';
+
+const createModuleSlice = getEditionSpecificSlice('createModuleSlice');
+
+export { createModuleSlice };
diff --git a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js
index f025563f66..8043a0bd18 100644
--- a/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/multiplayerSlice.js
@@ -21,14 +21,14 @@ export const createMultiplayerSlice = (set, get) => ({
diff,
type,
operation,
- pageId: get().currentPageId,
+ pageId: get().getCurrentPageId('canvas'),
versionId: get().selectedVersion?.id,
});
}
},
processUpdate: ({ diff, type, operation, pageId, versionId }) => {
- const currentPageId = get().currentPageId;
+ const currentPageId = get().getCurrentPageId('canvas');
const currentVersionId = get().selectedVersion?.id;
if (currentPageId === pageId && currentVersionId === versionId)
diff --git a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js
index 45a5b86428..5ece338ed8 100644
--- a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js
@@ -55,7 +55,8 @@ const createPageUpdateCommand =
}
});
- const { app, currentVersionId } = get();
+ const { appStore, currentVersionId } = get();
+ const app = appStore.modules.canvas.app;
const diff = _.zipObject(updatePaths, values);
if (enableSave) savePageChanges(app.appId, currentVersionId, pageId, diff);
};
@@ -199,10 +200,8 @@ export const createPageMenuSlice = (set, get) => {
updatePageWithPermissions: (pageId, value) => updatePageWithPermissions(pageId, [value])(set, get),
// unsure about this one
clonePage: async (pageId) => {
- const {
- app: { appId },
- currentVersionId,
- } = get();
+ const { getAppId, currentVersionId } = get();
+ const appId = getAppId('canvas');
const pages = get().modules.canvas.pages;
const data = await appVersionService.clonePage(appId, currentVersionId, pageId);
const newPages = data?.pages;
@@ -220,19 +219,21 @@ export const createPageMenuSlice = (set, get) => {
}
},
deletePage: async (pageId) => {
- const { app, currentVersionId } = get();
+ const { getAppId, getHomePageId, currentVersionId } = get();
+ const appId = getAppId('canvas');
+ const homePageId = getHomePageId('canvas');
const diff = {
pageId: pageId,
};
const pages = get().modules.canvas.pages;
- const currentPageId = get().currentPageId;
+ const currentPageId = get().getCurrentPageId('canvas');
const switchPage = get().switchPage;
if (pages.length === 1) {
toast.error('You cannot delete the only page in your app.');
return;
}
if (currentPageId === pageId) {
- const homePage = pages.find((p) => p.id === app.homePageId);
+ const homePage = pages.find((p) => p.id === homePageId);
switchPage(homePage.id, homePage.handle);
}
set((state) => {
@@ -241,7 +242,7 @@ export const createPageMenuSlice = (set, get) => {
state.showEditingPopover = false;
state.editingPage = null;
});
- await savePageChanges(app.appId, currentVersionId, pageId, diff, 'delete');
+ await savePageChanges(appId, currentVersionId, pageId, diff, 'delete');
toast.success('Page deleted successfully');
},
/*
@@ -250,11 +251,11 @@ export const createPageMenuSlice = (set, get) => {
* If home page is in the group, the group cannot be deleted
* If current page is in the group, the page will be switched to home page
*/
- deletePageGroup: async (pageGroupId, deleteAssociatedPages = false) => {
- const { app, currentVersionId } = get();
+ deletePageGroup: async (pageGroupId, deleteAssociatedPages = false, moduleId = 'canvas') => {
+ const { getAppId, getHomePageId, currentVersionId } = get();
+ const appId = getAppId(moduleId);
+ const homePageId = getHomePageId(moduleId);
const pages = get().modules.canvas.pages;
-
- const homePageId = get().app.homePageId;
const diff = {
pageId: pageGroupId,
deleteAssociatedPages,
@@ -267,7 +268,7 @@ export const createPageMenuSlice = (set, get) => {
if (pages[i].id === homePageId && pages[i].pageGroupId === pageGroupId) {
isHomePageInGroup = true;
}
- if (pages[i].id === get().currentPageId && pages[i].pageGroupId === pageGroupId) {
+ if (pages[i].id === get().getCurrentPageId('canvas') && pages[i].pageGroupId === pageGroupId) {
isCurrentPageInGroup = true;
}
}
@@ -284,10 +285,10 @@ export const createPageMenuSlice = (set, get) => {
});
// switch page to home page if current page is in the group
if (isCurrentPageInGroup) {
- const homePage = pages.find((p) => p.id === app.homePageId);
+ const homePage = pages.find((p) => p.id === homePageId);
get().switchPage(homePage.id, homePage.handle);
}
- await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete');
+ await savePageChanges(appId, currentVersionId, pageGroupId, diff, 'delete');
} else {
set((state) => {
const pages = get().modules.canvas.pages;
@@ -306,25 +307,26 @@ export const createPageMenuSlice = (set, get) => {
state.modules.canvas.pages = newPages;
state.showDeleteConfirmationModal = false;
});
- await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete');
+ await savePageChanges(appId, currentVersionId, pageGroupId, diff, 'delete');
}
},
- markAsHomePage: async (pageId) => {
- const { app, currentVersionId, editingPage } = get();
+ markAsHomePage: async (pageId, moduleId = 'canvas') => {
+ const { getAppId, currentVersionId, editingPage } = get();
+ const appId = getAppId(moduleId);
const diff = {
homePageId: pageId,
};
set((state) => {
- state.app.homePageId = pageId;
+ state.appStore.modules[moduleId].app.homePageId = pageId;
state.showEditingPopover = false;
state.editingPage = null;
});
- await savePageChanges(app.appId, currentVersionId, editingPage.id, diff, 'update', null);
+ await savePageChanges(appId, currentVersionId, editingPage.id, diff, 'update', null);
},
reorderPages: async (reorderdPages) => {
const diff = {};
- const currentPageId = get().currentPageId;
+ const currentPageId = get().getCurrentPageId('canvas');
// update index of everything to avoid inconsistencies
reorderdPages.forEach((page, index) => {
diff[page.id] = {
@@ -336,8 +338,9 @@ export const createPageMenuSlice = (set, get) => {
set((state) => {
state.modules.canvas.pages = reorderdPages;
});
- const { app, currentVersionId } = get();
- await savePageChanges(app.appId, currentVersionId, currentPageId, diff, 'update', 'pages/reorder');
+ const { getAppId, currentVersionId } = get();
+ const appId = getAppId('canvas');
+ await savePageChanges(appId, currentVersionId, currentPageId, diff, 'update', 'pages/reorder');
},
addNewPage: async (name, handle, isPageGroup = false) => {
@@ -379,8 +382,9 @@ export const createPageMenuSlice = (set, get) => {
set((state) => {
state.modules.canvas.pages.push(pageObject);
});
- const { app, currentVersionId } = get();
- await savePageChanges(app.appId, currentVersionId, '', pageObject, 'create', 'pages');
+ const { getAppId, currentVersionId } = get();
+ const appId = getAppId('canvas');
+ await savePageChanges(appId, currentVersionId, '', pageObject, 'create', 'pages');
if (!isPageGroup) get().switchPage(newPageId, newHandle);
},
@@ -408,10 +412,11 @@ export const createPageMenuSlice = (set, get) => {
newOptions[key] = hexCode;
}
}
- const { app, currentVersionId, currentPageId } = get();
+ const { getAppId, currentVersionId, currentPageId } = get();
+ const appId = getAppId('canvas');
try {
const res = await appVersionService.autoSaveApp(
- app.appId,
+ appId,
currentVersionId,
{ pageSettings: { [type]: newOptions } },
'page_settings',
diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
index ddb67a5445..7344f54aec 100644
--- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js
@@ -66,13 +66,13 @@ export const createQueryPanelSlice = (set, get) => ({
'setQueryPanelHeight'
);
}, // updateQueryPanelHeight
- setSelectedQuery: (queryId) => {
+ setSelectedQuery: (queryId, moduleId = 'canvas') => {
set((state) => {
if (queryId === null) {
state.queryPanel.selectedQuery = null;
return;
}
- const query = get().dataQuery.queries.modules.canvas.find((query) => query.id === queryId);
+ const query = get().dataQuery.queries.modules[moduleId].find((query) => query.id === queryId);
state.queryPanel.selectedQuery = query;
return;
});
@@ -162,7 +162,7 @@ export const createQueryPanelSlice = (set, get) => ({
'setLoadingDataQueries'
),
- onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit') => {
+ onQueryConfirmOrCancel: (queryConfirmationData, isConfirm = false, mode = 'edit', moduleId = 'canvas') => {
const { queryPanel, dataQuery, setResolvedQuery } = get();
const { runQuery } = queryPanel;
const { queryConfirmationList } = dataQuery;
@@ -185,13 +185,21 @@ export const createQueryPanelSlice = (set, get) => ({
true,
mode,
queryConfirmationData.parameters,
- queryConfirmationData.shouldSetPreviewData
+ undefined,
+ undefined,
+ queryConfirmationData.shouldSetPreviewData,
+ false,
+ moduleId
);
!isConfirm &&
- setResolvedQuery(queryConfirmationData.queryId, {
- isLoading: false,
- });
+ setResolvedQuery(
+ queryConfirmationData.queryId,
+ {
+ isLoading: false,
+ },
+ moduleId
+ );
},
runQuery: (
@@ -211,7 +219,7 @@ export const createQueryPanelSlice = (set, get) => ({
dataQuery: dataQuerySlice,
queryPanel,
setResolvedQuery,
- app,
+ appStore,
selectedEnvironment,
isPublicAccess,
currentVersionId,
@@ -265,14 +273,18 @@ export const createQueryPanelSlice = (set, get) => ({
let dataQuery = {};
//for viewer we will only get the environment id from the url
- const { currentAppEnvironmentId, environmentId } = app;
+ const { currentAppEnvironmentId, environmentId } = appStore.modules[moduleId].app;
if (shouldSetPreviewData) {
setPreviewPanelExpanded(true);
setPreviewLoading(true);
- setResolvedQuery(queryId, {
- isLoading: true,
- });
+ setResolvedQuery(
+ queryId,
+ {
+ isLoading: true,
+ },
+ moduleId
+ );
queryPreviewData && setPreviewData('');
}
@@ -293,10 +305,11 @@ export const createQueryPanelSlice = (set, get) => ({
}
// const queryState = { ...getCurrentState(), parameters };
- const queryState = { ...get().getAllExposedValues('canvas'), parameters };
+ const queryState = { ...get().getAllExposedValues(moduleId), parameters };
+
const options = getQueryVariables(dataQuery.options, queryState, {
- components: get().getComponentNameIdMapping(),
- queries: get().getQueryNameIdMapping(),
+ components: get().getComponentNameIdMapping(moduleId),
+ queries: get().getQueryNameIdMapping(moduleId),
});
if (dataQuery.options?.requestConfirmation) {
const queryConfirmation = {
@@ -326,18 +339,22 @@ export const createQueryPanelSlice = (set, get) => ({
queryPreviewData && setPreviewData('');
}
- setResolvedQuery(queryId, {
- isLoading: true,
- data: [],
- rawData: [],
- id: queryId,
- });
+ setResolvedQuery(
+ queryId,
+ {
+ isLoading: true,
+ data: [],
+ rawData: [],
+ id: queryId,
+ },
+ moduleId
+ );
let queryExecutionPromise = null;
if (query.kind === 'runjs') {
- queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters);
+ queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters, moduleId);
} else if (query.kind === 'runpy') {
- queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState);
+ queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState, moduleId);
} else if (query.kind === 'workflows') {
queryExecutionPromise = executeWorkflow(
moduleId,
@@ -347,11 +364,16 @@ export const createQueryPanelSlice = (set, get) => ({
(currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check
);
} else {
+ let versionId = currentVersionId;
+ // IMPORTANT: This logic needs to be changed when we implement the module versioning
+ if (moduleId !== 'canvas') {
+ versionId = get().resolvedStore.modules.canvas.components[moduleId].properties.moduleVersionId;
+ }
queryExecutionPromise = dataqueryService.run(
queryId,
options,
query?.options,
- currentVersionId,
+ versionId,
!isPublicAccess ? (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id : undefined //TODO: currentAppEnvironmentId may no longer required. Need to check
);
}
@@ -421,17 +443,21 @@ export const createQueryPanelSlice = (set, get) => ({
isQuerySuccessLog: false,
});
- setResolvedQuery(queryId, {
- isLoading: false,
- ...(query.kind === 'restapi'
- ? {
- metadata: data.metadata,
- request: data.data.requestObject,
- response: data.data.responseObject,
- responseHeaders: data.data.responseHeaders,
- }
- : {}),
- });
+ setResolvedQuery(
+ queryId,
+ {
+ isLoading: false,
+ ...(query.kind === 'restapi'
+ ? {
+ metadata: data.metadata,
+ request: data.data.requestObject,
+ response: data.data.responseObject,
+ responseHeaders: data.data.responseHeaders,
+ }
+ : {}),
+ },
+ moduleId
+ );
resolve(data);
onEvent('onDataQueryFailure', queryEvents);
@@ -445,12 +471,17 @@ export const createQueryPanelSlice = (set, get) => ({
query.options.transformation,
query.options.transformationLanguage,
query,
- 'edit'
+ 'edit',
+ moduleId
);
- if (finalData?.status === 'failed') {
- setResolvedQuery(queryId, {
- isLoading: false,
- });
+ if (finalData.status === 'failed') {
+ setResolvedQuery(
+ queryId,
+ {
+ isLoading: false,
+ },
+ moduleId
+ );
resolve(finalData);
onEvent('onDataQueryFailure', queryEvents);
@@ -482,14 +513,18 @@ export const createQueryPanelSlice = (set, get) => ({
errorTarget: 'Queries',
});
- setResolvedQuery(queryId, {
- isLoading: false,
- data: finalData,
- rawData,
- metadata: data?.metadata,
- request: data?.metadata?.request,
- response: data?.metadata?.response,
- });
+ setResolvedQuery(
+ queryId,
+ {
+ isLoading: false,
+ data: finalData,
+ rawData,
+ metadata: data?.metadata,
+ request: data?.metadata?.request,
+ response: data?.metadata?.response,
+ },
+ moduleId
+ );
resolve({ status: 'ok', data: finalData });
onEvent('onDataQuerySuccess', queryEvents, mode);
@@ -557,7 +592,7 @@ export const createQueryPanelSlice = (set, get) => ({
}
// const queryState = { ...getCurrentState(), parameters };
- const queryState = { ...get().getAllExposedValues(), parameters };
+ const queryState = { ...get().getAllExposedValues(moduleId), parameters };
const options = getQueryVariables(query.options, queryState, {
components: get().getComponentNameIdMapping(),
queries: get().getQueryNameIdMapping(),
@@ -649,7 +684,8 @@ export const createQueryPanelSlice = (set, get) => ({
query.options.transformation,
query.options.transformationLanguage,
query,
- 'edit'
+ 'edit',
+ moduleId
);
if (finalData?.status === 'failed') {
onEvent('onDataQueryFailure', queryEvents);
@@ -709,32 +745,32 @@ export const createQueryPanelSlice = (set, get) => ({
let result = {};
try {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
const queriesInCurentState = deepClone(resolvedState.queries);
const appStateVars = deepClone(resolvedState.variables) ?? {};
if (!isEmpty(query)) {
- const actions = generateAppActions(query.id, mode, isPreview);
+ const actions = generateAppActions(query.id, mode, isPreview, moduleId);
for (const key of Object.keys(queriesInCurentState)) {
queriesInCurentState[key] = {
...queriesInCurentState[key],
run: () => {
const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key);
- return actions.runQuery(query.name);
+ return actions.runQuery(query.name, undefined, moduleId);
},
getData: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].data;
},
getRawData: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].rawData;
},
getloadingState: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].isLoading;
},
};
@@ -776,14 +812,21 @@ export const createQueryPanelSlice = (set, get) => ({
return pyodide.isPyProxy(result) ? convertMapSet(result.toJs()) : result;
},
- runTransformation: async (rawData, transformation, transformationLanguage = 'javascript', query, mode = 'edit') => {
+ runTransformation: async (
+ rawData,
+ transformation,
+ transformationLanguage = 'javascript',
+ query,
+ mode = 'edit',
+ moduleId = 'canvas'
+ ) => {
const data = rawData;
const {
queryPanel: { runPythonTransformation, createProxy },
getResolvedState,
} = get();
let result = {};
- const currentState = getResolvedState();
+ const currentState = getResolvedState(moduleId);
if (transformationLanguage === 'python') {
result = await runPythonTransformation(currentState, data, transformation, query, mode);
@@ -903,12 +946,10 @@ export const createQueryPanelSlice = (set, get) => ({
// queries: updatedQueries,
// });
},
- executeWorkflow: async (moduleId, workflowId, _blocking = false, params = {}, appEnvId) => {
- const {
- app: { appId },
- getAllExposedValues,
- } = get();
- const currentState = getAllExposedValues();
+ executeWorkflow: async (moduleId = 'canvas', workflowId, _blocking = false, params = {}, appEnvId) => {
+ const { getAppId, getAllExposedValues } = get();
+ const appId = getAppId('canvas');
+ const currentState = getAllExposedValues(moduleId);
const resolvedParams = get().resolveReferences(moduleId, params, currentState, {}, {});
try {
@@ -947,7 +988,7 @@ export const createQueryPanelSlice = (set, get) => ({
return isValidCode;
}
- const currentState = getAllExposedValues();
+ // const currentState = getAllExposedValues();
let result = {},
error = null;
@@ -957,7 +998,7 @@ export const createQueryPanelSlice = (set, get) => ({
parameters = {};
}
- const actions = generateAppActions(queryId, mode, isPreview);
+ const actions = generateAppActions(queryId, mode, isPreview, moduleId);
const queryDetails = dataQuery.queries.modules?.[moduleId].find((q) => q.id === queryId);
@@ -980,7 +1021,7 @@ export const createQueryPanelSlice = (set, get) => ({
//this will handle the preview case where you cannot find the queryDetails in state.
formattedParams = { ...parameters };
}
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
const queriesInResolvedState = deepClone(resolvedState.queries);
for (const key of Object.keys(resolvedState.queries)) {
queriesInResolvedState[key] = {
@@ -992,21 +1033,21 @@ export const createQueryPanelSlice = (set, get) => ({
const processedParams = {};
const query = dataQuery.queries.modules?.[moduleId].find((q) => q.name === key);
query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name]));
- return actions.runQuery(query.name, processedParams);
+ return actions.runQuery(query.name, processedParams, moduleId);
},
getData: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].data;
},
getRawData: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].rawData;
},
getloadingState: () => {
- const resolvedState = get().getResolvedState();
+ const resolvedState = get().getResolvedState(moduleId);
return resolvedState.queries[key].isLoading;
},
};
diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js
index a253346eec..3b6877b726 100644
--- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js
@@ -3,15 +3,6 @@ import { resolveDynamicValues } from '../utils';
import { extractAndReplaceReferencesFromString } from '@/AppBuilder/_stores/ast';
import { componentTypeDefinitionMap } from '@/AppBuilder/WidgetManager';
import _ from 'lodash';
-import {
- reservedKeyword,
- resolveString,
- removeNestedDoubleCurlyBraces,
- getDynamicVariables,
- resolveCode,
-} from '@/_helpers/utils';
-
-import { validateMultilineCode } from '@/_helpers/utility';
const initialState = {
resolvedStore: {
@@ -25,7 +16,7 @@ const initialState = {
secrets: {},
customResolvables: {},
exposedValues: {
- queries: {},
+ queries: {} /* IMPORTANT: Query is subscribed by the moduleContainer component */,
components: {},
variables: {},
constants: {},
@@ -50,6 +41,17 @@ export const DEFAULT_COMPONENT_STRUCTURE = {
export const createResolvedSlice = (set, get) => ({
...initialState,
+ initializeResolvedSlice: (moduleId) => {
+ set(
+ (state) => {
+ state.resolvedStore.modules[moduleId] = {
+ ...initialState.resolvedStore.modules.canvas,
+ };
+ },
+ false,
+ 'initializeResolvedSlice'
+ );
+ },
setResolvedGlobals: (objKey, values, moduleId = 'canvas') => {
set(
(state) => {
@@ -71,7 +73,7 @@ export const createResolvedSlice = (set, get) => ({
'setResolvedGlobals'
);
Object.entries(values).forEach(() => {
- get().updateDependencyValues(`globals.${objKey}`);
+ get().updateDependencyValues(`globals.${objKey}`, moduleId);
});
},
setResolvedConstants: (constants = {}, moduleId = 'canvas') => {
@@ -85,7 +87,7 @@ export const createResolvedSlice = (set, get) => ({
'setResolvedConstants'
);
Object.entries(constants).forEach(([key, value]) => {
- get().updateDependencyValues(`constants.${key}`);
+ get().updateDependencyValues(`constants.${key}`, moduleId);
});
},
@@ -108,7 +110,7 @@ export const createResolvedSlice = (set, get) => ({
'setResolvedPageConstants'
);
Object.entries(constants).forEach(([key, value]) => {
- get().updateDependencyValues(`page.${key}`);
+ get().updateDependencyValues(`page.${key}`, moduleId);
});
},
@@ -121,7 +123,7 @@ export const createResolvedSlice = (set, get) => ({
false,
'setVariables'
);
- get().updateDependencyValues(`variables.${key}`);
+ get().updateDependencyValues(`variables.${key}`, moduleId);
get().checkAndSetTrueBuildSuggestionsFlag();
},
@@ -137,8 +139,8 @@ export const createResolvedSlice = (set, get) => ({
false,
'unsetVariable'
);
- get().removeNode(`variables.${key}`);
- get().updateDependencyValues(`variables.${key}`);
+ get().removeNode(`variables.${key}`, moduleId);
+ get().updateDependencyValues(`variables.${key}`, moduleId);
},
unsetAllVariables: (moduleId = 'canvas') => {
@@ -165,7 +167,7 @@ export const createResolvedSlice = (set, get) => ({
false,
'setPageVariable'
);
- get().updateDependencyValues(`page.variables.${key}`);
+ get().updateDependencyValues(`page.variables.${key}`, moduleId);
get().checkAndSetTrueBuildSuggestionsFlag();
},
@@ -180,8 +182,8 @@ export const createResolvedSlice = (set, get) => ({
false,
'unsetPageVariable'
);
- get().removeNode(`page.variables.${key}`);
- get().updateDependencyValues(`page.variables.${key}`);
+ get().removeNode(`page.variables.${key}`, moduleId);
+ get().updateDependencyValues(`page.variables.${key}`, moduleId);
},
unsetAllPageVariables: (moduleId = 'canvas') => {
@@ -213,13 +215,13 @@ export const createResolvedSlice = (set, get) => ({
Object.entries(details).forEach(([key, value]) => {
if (['isLoading', 'data', 'rawData', 'request', 'response', 'responseHeaders', 'metadata'].includes(key)) {
- if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`);
+ if (typeof value !== 'function') get().updateDependencyValues(`queries.${queryId}.${key}`, moduleId);
}
});
// Flag to update the codehinter suggestions
get().checkAndSetTrueBuildSuggestionsFlag();
},
- initialiseResolvedQuery(querIds, moduleId = 'canvas') {
+ initialiseResolvedQuery: (querIds, moduleId = 'canvas') => {
const defaultObject = {};
querIds.forEach((queryId) => {
defaultObject[queryId] = {
@@ -248,7 +250,7 @@ export const createResolvedSlice = (set, get) => ({
setResolvedComponents: (components, moduleId = 'canvas') => {
const validateComponents = get().debugger.validateComponents;
- const validatedComponents = validateComponents(components);
+ const validatedComponents = validateComponents(components, moduleId);
set(
(state) => {
@@ -277,7 +279,7 @@ export const createResolvedSlice = (set, get) => ({
}
*/
setResolvedComponentByProperty: (componentId, type, property, value, index = null, moduleId = 'canvas') => {
- value = get().debugger.validateProperty(componentId, type, property, value);
+ value = get().debugger.validateProperty(componentId, type, property, value, moduleId);
set(
(state) => {
@@ -338,7 +340,7 @@ export const createResolvedSlice = (set, get) => ({
payload: { componentId, property, value, moduleId },
}
);
- get().updateDependencyValues(`components.${componentId}.${property}`);
+ get().updateDependencyValues(`components.${componentId}.${property}`, moduleId);
},
setExposedValues: (id, type, values, moduleId = 'canvas') => {
@@ -359,7 +361,7 @@ export const createResolvedSlice = (set, get) => ({
}
);
Object.entries(values).forEach(([key, value]) => {
- if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`);
+ if (typeof value !== 'function') get().updateDependencyValues(`components.${id}.${key}`, moduleId);
});
},
@@ -368,7 +370,7 @@ export const createResolvedSlice = (set, get) => ({
if (val && Object.keys(val).length > 0) return;
const component = componentTypeDefinitionMap[componentType];
if (!component) return;
- const parentComponentType = get().getComponentDefinition(parentId)?.component?.component;
+ const parentComponentType = get().getComponentDefinition(parentId, moduleId)?.component?.component;
if (['Form', 'Listview'].includes(parentComponentType)) return;
const exposedVariables = component.exposedVariables || {};
get().setExposedValues(id, 'components', exposedVariables, moduleId);
@@ -424,7 +426,7 @@ export const createResolvedSlice = (set, get) => ({
},
getExposedValueOfComponent: (componentId, moduleId = 'canvas') => {
try {
- const components = get().getCurrentPageComponents();
+ const components = get().getCurrentPageComponents(moduleId);
const {
component: { parent: parentId, name: componentName },
} = components[componentId];
@@ -501,6 +503,9 @@ export const createResolvedSlice = (set, get) => ({
state.resolvedStore.modules[moduleId].exposedValues.components = {};
state.resolvedStore.modules[moduleId].exposedValues.variables = {};
state.resolvedStore.modules[moduleId].exposedValues.globals = {};
+ if (state.resolvedStore.modules[moduleId].exposedValues.input) {
+ state.resolvedStore.modules[moduleId].exposedValues.input = {};
+ }
if (state.resolvedStore.modules[moduleId].exposedValues.page?.variables) {
state.resolvedStore.modules[moduleId].exposedValues.page.variables = {};
}
@@ -530,7 +535,7 @@ export const createResolvedSlice = (set, get) => ({
},
// this function simply replaces the id with name for queries and components inside resolvedStore
- getResolvedState: (key, moduleId = 'canvas') => {
+ getResolvedState: (moduleId = 'canvas', key) => {
const state = {
components: {},
queries: {},
@@ -574,7 +579,7 @@ export const createResolvedSlice = (set, get) => ({
const objectType = typeof object;
let error;
- const state = _state ?? get().getAllExposedValues();
+ const state = _state ?? get().getAllExposedValues(moduleId);
if (_state?.parameters) {
state.parameters = { ..._state.parameters };
@@ -613,4 +618,41 @@ export const createResolvedSlice = (set, get) => ({
}
}
},
+
+ setModuleInputs: (key, value, moduleId = 'canvas') => {
+ set(
+ (state) => {
+ if (!state.resolvedStore.modules[moduleId].exposedValues.input) {
+ state.resolvedStore.modules[moduleId].exposedValues.input = {};
+ }
+ state.resolvedStore.modules[moduleId].exposedValues.input[key] = value;
+ },
+ false,
+ 'setModuleInputs'
+ );
+ get().updateDependencyValues(`input.${key}`, moduleId);
+ },
+ setModuleOutputs: (key, value, moduleId = 'canvas') => {
+ set(
+ (state) => {
+ if (!state.resolvedStore.modules[moduleId].exposedValues.output) {
+ state.resolvedStore.modules[moduleId].exposedValues.output = {};
+ }
+ state.resolvedStore.modules[moduleId].exposedValues.output[key] = value;
+ },
+ false,
+ 'setModuleOutputs'
+ );
+ get().updateDependencyValues(`output.${key}`, moduleId);
+ },
+ clearModuleInputs: (moduleId = 'canvas') => {
+ set((state) => {
+ state.resolvedStore.modules[moduleId].exposedValues.input = {};
+ });
+ },
+ clearModuleOutputs: (moduleId = 'canvas') => {
+ set((state) => {
+ state.resolvedStore.modules[moduleId].exposedValues.output = {};
+ });
+ },
});
diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js
index 4d1392fc7c..f7b83e9704 100644
--- a/frontend/src/AppBuilder/_stores/store.js
+++ b/frontend/src/AppBuilder/_stores/store.js
@@ -28,6 +28,8 @@ import { createDebuggerSlice } from './slices/debuggerSlice';
import { createGitSyncSlice } from './slices/gitSyncSlice';
import { createAiSlice } from './slices/aiSlice';
import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice';
+import { createInspectorSlice } from './slices/inspectorSlice';
+import { createModuleSlice } from './slices/moduleSlice';
export default create(
zustandDevTools(
@@ -60,6 +62,8 @@ export default create(
...createGitSyncSlice(...state),
...createAiSlice(...state),
...createWhiteLabellingSlice(...state),
+ ...createInspectorSlice(...state),
+ ...createModuleSlice(...state),
})),
{ name: 'App Builder Store', anonymousActionType: 'unknown' }
)
diff --git a/frontend/src/AppBuilder/_stores/utils.js b/frontend/src/AppBuilder/_stores/utils.js
index 012d59aaf5..f333654a16 100644
--- a/frontend/src/AppBuilder/_stores/utils.js
+++ b/frontend/src/AppBuilder/_stores/utils.js
@@ -6,7 +6,7 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { dfs } from '@/_stores/handleReferenceTransactions';
import { extractAndReplaceReferencesFromString as extractAndReplaceReferencesFromStringAst } from '@/AppBuilder/_stores/ast';
-import _ from 'lodash';
+var _ = require('lodash');
const resetters = [];
@@ -148,6 +148,7 @@ export const resolveCode = (
'queries',
'globals',
'page',
+ 'input',
'client',
'server',
'constants',
@@ -165,6 +166,7 @@ export const resolveCode = (
isJsCode ? state?.queries : undefined,
isJsCode ? state?.globals : undefined,
isJsCode ? state?.page : undefined,
+ isJsCode ? state?.input : undefined,
isJsCode ? undefined : state?.client,
isJsCode ? undefined : state?.server,
state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly.
diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx
index dba9883d14..e276360843 100644
--- a/frontend/src/AppLoader/AppLoader.jsx
+++ b/frontend/src/AppLoader/AppLoader.jsx
@@ -12,8 +12,14 @@ const AppLoader = (props) => {
resetAllStores();
}, []);
- if (appType === 'front-end') return
;
- else if (appType === 'workflow') return
;
+ switch (appType) {
+ case 'front-end':
+ return
;
+ case 'workflow':
+ return
;
+ case 'module':
+ return
;
+ }
};
export default withTranslation()(AppLoader);
diff --git a/frontend/src/Editor/ControlledComponentToRender.jsx b/frontend/src/Editor/ControlledComponentToRender.jsx
index 54a451188b..120b47f864 100644
--- a/frontend/src/Editor/ControlledComponentToRender.jsx
+++ b/frontend/src/Editor/ControlledComponentToRender.jsx
@@ -1,5 +1,5 @@
import React, { useState, useCallback } from 'react';
-import { getComponentToRender } from '@/_helpers/editorHelpers';
+// import { getComponentToRender } from '@/_helpers/editorHelpers';
import _ from 'lodash';
import { getComponentsToRenders, flushComponentsToRender } from '@/_stores/editorStore';
@@ -58,7 +58,7 @@ const ComponentWrapper = React.memo(({ componentName, ...props }) => {
setKey(Math.random());
}, []);
- const ComponentToRender = getComponentToRender(componentName);
+ const ComponentToRender = <>>; // getComponentToRender(componentName);
if (ComponentToRender === null) return;
if (componentName === 'Form') {
diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx
index 3103be3e16..a4b20b5e61 100644
--- a/frontend/src/Editor/Viewer/PreviewSettings.jsx
+++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx
@@ -15,6 +15,7 @@ import { useEditorStore } from '@/_stores/editorStore';
import Cross from '@/_ui/Icon/solidIcons/Cross';
import { checkIfLicenseNotValid } from '@/_helpers/appUtils';
import EnvironmentManager from '@/Editor/Header/EnvironmentManager';
+import { useAppType } from '@/AppBuilder/_contexts/ModuleContext';
const PreviewSettings = ({
isMobileLayout,
@@ -23,6 +24,7 @@ const PreviewSettings = ({
showHeader,
darkMode,
}) => {
+ const { appType } = useAppType();
const { featureAccess, currentAppEnvironment, setCurrentAppEnvironmentId } = useEditorStore(
(state) => ({
featureAccess: state?.featureAccess,
@@ -84,9 +86,9 @@ const PreviewSettings = ({
Preview settings
-
{editingVersion && _renderAppVersionsManager()}
+
{editingVersion && appType !== 'module' && _renderAppVersionsManager()}
-
{editingVersion && _renderEnvironmentManager()}
+
{editingVersion && appType !== 'module' && _renderEnvironmentManager()}
diff --git a/frontend/src/HomePage/AppMenu.jsx b/frontend/src/HomePage/AppMenu.jsx
index 493f5d66b4..05281de413 100644
--- a/frontend/src/HomePage/AppMenu.jsx
+++ b/frontend/src/HomePage/AppMenu.jsx
@@ -19,6 +19,7 @@ export const AppMenu = function AppMenu({
appCreationMode,
}) {
const { t } = useTranslation();
+ const isModuleApp = appType === 'module';
const Field = ({ text, onClick, customClass }) => {
const closeMenu = () => {
document.body.click();
@@ -81,7 +82,7 @@ export const AppMenu = function AppMenu({
)}
>
)}
- {canUpdateApp && canCreateApp && appType !== 'workflow' && (
+ {canUpdateApp && canCreateApp && appType !== 'workflow' && !isModuleApp && (
<>
{
if (_.isEmpty(currentFolder)) {
- updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : 'apps'}`);
+ updateSidebarNAV(`All ${appType === 'workflow' ? 'workflows' : appType === 'module' ? 'modules' : 'apps'}`);
setActiveFolder({});
} else {
updateSidebarNAV(currentFolder.name);
@@ -104,7 +104,9 @@ export const Folders = function Folders({
setActiveFolder(folder);
}
folderChanged(folder);
- updateSidebarNAV(folder?.name ?? 'All apps');
+ updateSidebarNAV(
+ folder?.name ?? `All ${appType === 'front-end' ? 'apps' : appType === 'module' ? 'modules' : 'workflows'}`
+ );
//update the url query parameter with folder name
updateFolderQuery(folder?.name);
}
@@ -112,7 +114,12 @@ export const Folders = function Folders({
function updateFolderQuery(name) {
const search = `${name ? `?folder=${name}` : ''}`;
navigate(
- { pathname: `/${getWorkspaceId()}${appType === 'workflow' ? '/workflows' : ''}`, search },
+ {
+ pathname: `/${getWorkspaceId()}${
+ appType === 'workflow' ? '/workflows' : appType === 'module' ? '/modules' : ''
+ }`,
+ search,
+ },
{ replace: true }
);
}
@@ -286,10 +293,12 @@ export const Folders = function Folders({
onClick={() => handleFolderChange({})}
data-cy="all-applications-link"
>
- {t(
- `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`,
- 'All apps'
- )}
+ {appType === 'module'
+ ? 'All modules'
+ : t(
+ `${appType === 'workflow' ? 'workflowsDashboard' : 'homePage'}.foldersSection.allApplications`,
+ 'All apps'
+ )}
)}
diff --git a/frontend/src/HomePage/Footer.jsx b/frontend/src/HomePage/Footer.jsx
index d5520aea77..a95f450e1b 100644
--- a/frontend/src/HomePage/Footer.jsx
+++ b/frontend/src/HomePage/Footer.jsx
@@ -2,7 +2,7 @@ import React, { useState, useMemo } from 'react';
import Pagination from '@/_ui/Pagination';
import Skeleton from 'react-loading-skeleton';
-const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 }) => {
+const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9, appType }) => {
const [pageCount, setPageCount] = useState(1);
const totalPages = useMemo(() => {
return Math.floor((count - 1) / itemsPerPage) + 1;
@@ -60,7 +60,7 @@ const Footer = ({ darkMode, count, pageChanged, dataLoading, itemsPerPage = 9 })
) : (
- {pageRange} of {count} apps
+ {pageRange} of {count} {appType === 'module' ? 'modules' : 'apps'}
)}
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx
index 0e04b4b2a3..dba8b21962 100644
--- a/frontend/src/HomePage/HomePage.jsx
+++ b/frontend/src/HomePage/HomePage.jsx
@@ -45,8 +45,10 @@ import {
OrganizationList,
UserGroupMigrationBanner,
ConsultationBanner,
+ AppTypeTab,
} from '@/modules/dashboard/components';
import CreateAppWithPrompt from '@/modules/AiBuilder/components/CreateAppWithPrompt';
+import SolidIcon from '@/_ui/Icon/SolidIcons';
const { iconList, defaultIcon } = configs;
@@ -100,7 +102,6 @@ class HomePageComponent extends React.Component {
importingGitAppOperations: {},
featuresLoaded: false,
showCreateAppModal: false,
- showCreateModuleModal: false,
showCreateAppFromTemplateModal: false,
showImportAppModal: false,
showCloneAppModal: false,
@@ -116,6 +117,9 @@ class HomePageComponent extends React.Component {
shouldAutoImportPlugin: false,
dependentPlugins: [],
dependentPluginsDetail: {},
+ showMissingGroupsModal: false,
+ missingGroups: [],
+ missingGroupsExpanded: false,
};
}
@@ -233,14 +237,23 @@ class HomePageComponent extends React.Component {
this.fetchFolders();
};
- createApp = async (appName, type) => {
+ getAppType = () => {
+ return this.props.appType === 'module' ? 'Module' : this.props.appType === 'workflow' ? 'Workflow' : 'App';
+ };
+
+ createApp = async (appName) => {
let _self = this;
_self.setState({ creatingApp: true });
+
try {
- const data = await appsService.createApp({ icon: sample(iconList), name: appName, type: this.props.appType });
+ const data = await appsService.createApp({
+ icon: sample(iconList),
+ name: appName,
+ type: this.props.appType,
+ });
const workspaceId = getWorkspaceId();
_self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } });
- toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} created successfully!`);
+ toast.success(`${this.getAppType()} created successfully!`);
_self.setState({ creatingApp: false });
return true;
} catch (errorResponse) {
@@ -259,7 +272,7 @@ class HomePageComponent extends React.Component {
try {
await appsService.saveApp(appId, { name: newAppName });
await this.fetchApps(this.state.currentPage, this.state.currentFolder.id);
- toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} name has been updated!`);
+ toast.success(`${this.getAppType()} name has been updated!`);
_self.setState({ renamingApp: false });
return true;
} catch (errorResponse) {
@@ -356,17 +369,24 @@ class HomePageComponent extends React.Component {
}
};
- importFile = async (importJSON, appName) => {
+ importFile = async (importJSON, appName, skipPagePermissionsGroupCheck = false) => {
this.setState({ isImportingApp: true });
// For backward compatibility with legacy app import
const organization_id = this.state.currentUser?.organization_id;
const isLegacyImport = isEmpty(importJSON.tooljet_version);
if (isLegacyImport) {
- importJSON = { app: [{ definition: importJSON, appName: appName }], tooljet_version: importJSON.tooljetVersion };
+ importJSON = {
+ app: [{ definition: importJSON, appName: appName }],
+ tooljet_version: importJSON.tooljetVersion,
+ };
} else {
importJSON.app[0].appName = appName;
}
- const requestBody = { organization_id, ...importJSON };
+ const requestBody = {
+ organization_id,
+ ...importJSON,
+ skip_page_permissions_group_check: skipPagePermissionsGroupCheck,
+ };
let installedPluginsInfo = [];
try {
if (this.state.dependentPlugins.length) {
@@ -388,6 +408,10 @@ class HomePageComponent extends React.Component {
this.props.navigate(`/${getWorkspaceId()}/database`);
}
} catch (error) {
+ if (error?.error?.type === 'permission-check') {
+ this.setState({ showMissingGroupsModal: true, missingGroups: error?.error?.data });
+ return;
+ }
if (installedPluginsInfo.length) {
const pluginsId = installedPluginsInfo.map((pluginInfo) => pluginInfo.id);
await pluginsService.uninstallPlugins(pluginsId);
@@ -501,7 +525,7 @@ class HomePageComponent extends React.Component {
.deleteApp(this.state.appToBeDeleted.id)
// eslint-disable-next-line no-unused-vars
.then((data) => {
- toast.success(`${this.props.appType === 'workflow' ? 'Workflow' : 'App'} deleted successfully.`);
+ toast.success(`${this.getAppType()} deleted successfully.`);
this.fetchApps(
this.state.currentPage
? this.state.apps?.length === 1
@@ -793,11 +817,11 @@ class HomePageComponent extends React.Component {
};
openCreateAppModal = () => {
- this.setState({ showCreateAppModal: true, showCreateModuleModal: true });
+ this.setState({ showCreateAppModal: true });
};
closeCreateAppModal = () => {
- this.setState({ showCreateAppModal: false, showCreateModuleModal: false });
+ this.setState({ showCreateAppModal: false });
};
openImportAppModal = async () => {
@@ -876,7 +900,6 @@ class HomePageComponent extends React.Component {
importingGitAppOperations,
featuresLoaded,
showCreateAppModal,
- showCreateModuleModal,
showImportAppModal,
fileContent,
fileName,
@@ -888,15 +911,22 @@ class HomePageComponent extends React.Component {
showGroupMigrationBanner,
dependentPlugins,
dependentPluginsDetail,
+ showMissingGroupsModal,
+ missingGroups,
+ missingGroupsExpanded,
} = this.state;
+
+ const invalidLicense = featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid;
+ // const invalidLicense = false;
+
const modalConfigs = {
create: {
modalType: 'create',
closeModal: this.closeCreateAppModal,
- processApp: (name) => this.createApp(name, showCreateAppModal ? 'front-end' : 'module'),
+ processApp: (name) => this.createApp(name),
show: this.openCreateAppModal,
- title: this.props.appType === 'workflow' ? 'Create workflow' : 'Create app',
- actionButton: this.props.appType === 'workflow' ? '+ Create workflow' : '+ Create app',
+ title: `Create ${this.getAppType().toLocaleLowerCase()}`,
+ actionButton: `+ Create ${this.getAppType().toLocaleLowerCase()}`,
actionLoadingButton: 'Creating',
appType: this.props.appType,
},
@@ -939,13 +969,18 @@ class HomePageComponent extends React.Component {
};
const isAdmin = authenticationService?.currentSessionValue?.admin;
const isBuilder = authenticationService?.currentSessionValue?.is_builder;
+
+ //import app missing groups modal config
+ const threshold = 3;
+ const isLong = missingGroups.length > threshold;
+ const displayedGroups = missingGroupsExpanded ? missingGroups : missingGroups.slice(0, threshold);
+
return (
+
this.importFile(fileContent, fileName, true)}
+ show={showMissingGroupsModal}
+ isLoading={importingApp}
+ handleClose={() => this.setState({ showMissingGroupsModal: false })}
+ confirmBtnProps={{
+ title: 'Import',
+ tooltipMessage: '',
+ }}
+ className="missing-groups-modal"
+ darkMode={this.props.darkMode}
+ >
+
+
+
+
+
Warning: Missing user groups for permissions
+
+ Permissions for the following user group(s) won’t be applied since they do not exist in this
+ workspace.
+
+
+
+
+
+
+
+ User groups
+
+
+ {displayedGroups.map((group, idx) => (
+
+ {group}
+ {idx < displayedGroups.length - 1 ? ', ' : ''}
+
+ ))}
+ {!missingGroupsExpanded && isLong && '...'}
+
+
+
+ {isLong && (
+
this.setState({ missingGroupsExpanded: !missingGroupsExpanded })}
+ >
+
+
+
+ {missingGroupsExpanded ? 'See less' : 'See more'}
+
+ )}
+
+
+
+ Restricted pages, queries, or components will become accessible to all users or to existing groups with
+ permissions. To avoid this, create the missing groups before importing, or reconfigure permissions after
+ import.
+
+
+
+ this.setState({ showMissingGroupsModal: false, isImportingApp: false })}
+ >
+ Cancel import
+
+ this.importFile(fileContent, fileName, true)}
+ className="primary-action"
+ >
+ Import with limited permissions
+
+
+
+
{showRenameAppModal && (
this.setState({ showRenameAppModal: true })}
@@ -960,8 +1082,8 @@ class HomePageComponent extends React.Component {
processApp={this.renameApp}
selectedAppId={appOperations.selectedApp.id}
selectedAppName={appOperations.selectedApp.name}
- title={`Rename ${this.props.appType === 'workflow' ? 'workflow' : 'app'}`}
- actionButton={`Rename ${this.props.appType === 'workflow' ? 'workflow' : 'app'}`}
+ title={`Rename ${this.getAppType().toLocaleLowerCase()}`}
+ actionButton={`Rename ${this.getAppType().toLocaleLowerCase()}`}
actionLoadingButton={'Renaming'}
appType={this.props.appType}
/>
@@ -1190,6 +1312,7 @@ class HomePageComponent extends React.Component {
)}
+
{this.canCreateApp() && (
= 100}
disabled={
- this.props.appType === 'front-end'
- ? appsLimit?.percentage >= 100
+ this.props.appType === 'front-end' || this.props.appType === 'module'
+ ? appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense)
: workflowInstanceLevelLimit.percentage >= 100 ||
workflowWorkspaceLevelLimit.percentage >= 100
}
className={`create-new-app-button col-11 ${creatingApp ? 'btn-loading' : ''}`}
- onClick={() => this.setState({ showCreateAppModal: true })}
+ onClick={() =>
+ this.setState({
+ showCreateAppModal: true,
+ })
+ }
data-cy="create-new-app-button"
>
{isImportingApp && (
)}
- {this.props.t(
- `${
- this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
- }.header.createNewApplication`,
- 'Create new app'
- )}
+ {this.props.appType === 'module'
+ ? 'Create new module'
+ : this.props.t(
+ `${
+ this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage'
+ }.header.createNewApplication`,
+ 'Create new app'
+ )}
- {this.props.appType !== 'workflow' && (
+ {this.props.appType !== 'workflow' && this.props.appType !== 'module' && (
= 100}
+ disabled={
+ appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense)
+ }
split
className="d-inline"
data-cy="import-dropdown-menu"
@@ -1315,7 +1446,7 @@ class HomePageComponent extends React.Component {
!appSearchKey &&
)}
- {this.props.appType !== 'workflow' && this.canCreateApp() && (
+ {this.props.appType !== 'workflow' && this.props.appType !== 'module' && this.canCreateApp() && (
)}
@@ -1327,6 +1458,7 @@ class HomePageComponent extends React.Component {
onSearchSubmit={this.onSearchSubmit}
darkMode={this.props.darkMode}
appType={this.props.appType}
+ disabled={this.props.appType === 'module' && invalidLicense}
/>
>
@@ -1354,31 +1486,44 @@ class HomePageComponent extends React.Component {
>
)}
- {!isLoading && featuresLoaded && meta?.total_count === 0 && !currentFolder.id && !appSearchKey && (
-
= workflowInstanceLevelLimit.total ||
- 100 > workflowInstanceLevelLimit.percentage >= 90 ||
- workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
- ? workflowInstanceLevelLimit
- : workflowWorkspaceLevelLimit
- }
- />
- )}
+ {!isLoading &&
+ featuresLoaded &&
+ meta?.total_count === 0 &&
+ !currentFolder.id &&
+ !appSearchKey &&
+ (['front-end', 'workflow'].includes(this.props.appType) ? (
+ = workflowInstanceLevelLimit.total ||
+ 100 > workflowInstanceLevelLimit.percentage >= 90 ||
+ workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1
+ ? workflowInstanceLevelLimit
+ : workflowWorkspaceLevelLimit
+ }
+ />
+ ) : (
+
+ You have not created any modules.
+
+ Create a module
+
+ to start using it within your apps.
+
+ ))}
{!isLoading && apps?.length === 0 && appSearchKey && (
@@ -1402,7 +1547,7 @@ class HomePageComponent extends React.Component {
appActionModal={this.appActionModal}
removeAppFromFolder={this.removeAppFromFolder}
appType={this.props.appType}
- basicPlan={featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid}
+ basicPlan={invalidLicense}
appSearchKey={this.state.appSearchKey}
/>
)}
@@ -1416,6 +1561,7 @@ class HomePageComponent extends React.Component {
pageChanged={this.pageChanged}
darkMode={this.props.darkMode}
dataLoading={isLoading}
+ appType={this.props.appType}
/>
)}
{/* need to review the mobile view */}
diff --git a/frontend/src/_components/AppModal.jsx b/frontend/src/_components/AppModal.jsx
index 54c623dbed..72fb7cdcb5 100644
--- a/frontend/src/_components/AppModal.jsx
+++ b/frontend/src/_components/AppModal.jsx
@@ -10,6 +10,7 @@ import { PluginsListForAppModal } from './PluginsListForAppModal';
const APP_TYPE = {
WORKFLOW: 'workflow',
APP: 'app',
+ MODULE: 'module',
};
export function AppModal({
@@ -52,6 +53,8 @@ export function AppModal({
const [isNameChanged, setIsNameChanged] = useState(false);
const inputRef = useRef(null);
+ const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : APP_TYPE.MODULE == appType ? 'Module' : 'App';
+
useEffect(() => {
setIsNameChanged(newAppName?.trim() !== selectedAppName);
}, [newAppName, selectedAppName]);
@@ -85,7 +88,7 @@ export function AppModal({
success = await processApp(trimmedAppName);
}
if (success === false) {
- setErrorText(`${appType == APP_TYPE.WORKFLOW ? 'Workflow' : 'App'} name already exists`);
+ setErrorText(`${appTypeName} name already exists`);
setInfoText('');
} else {
setErrorText('');
@@ -127,8 +130,6 @@ export function AppModal({
(actionButton === 'Rename app' && (!isNameChanged || newAppName.trim().length === 0 || newAppName.length > 50)) || // For rename case
(actionButton !== 'Rename app' && (newAppName.length > 50 || newAppName.trim().length === 0));
- const appTypeName = APP_TYPE.WORKFLOW == appType ? 'Workflow' : 'App';
-
return (
)}
- {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && (
+ {orgGit?.is_enabled && appType != APP_TYPE.WORKFLOW && appType != APP_TYPE.MODULE && (
maxLetters
+ ? `${children.substring(0, maxLetters)}...`
+ : children;
+
return (
module.PDF);
-}
+// export const AllComponents = {
+// Button,
+// Image,
+// Text,
+// TextInput,
+// NumberInput,
+// Table,
+// TextArea,
+// Container,
+// Tabs,
+// RichTextEditor,
+// DropDown,
+// DropdownV2,
+// Checkbox,
+// Datepicker,
+// DaterangePicker,
+// Multiselect,
+// MultiselectV2,
+// Modal,
+// Chart,
+// Map: MapComponent,
+// QrScanner,
+// ToggleSwitch,
+// RadioButton,
+// StarRating,
+// Divider,
+// FilePicker,
+// PasswordInput,
+// Calendar,
+// IFrame,
+// CodeEditor,
+// Listview,
+// Timer,
+// Statistics,
+// Pagination,
+// Tags,
+// Spinner,
+// CircularProgressBar,
+// RangeSlider,
+// Timeline,
+// SvgImage,
+// Html,
+// ButtonGroup,
+// CustomComponent,
+// VerticalDivider,
+// ColorPicker,
+// KanbanBoard,
+// Kanban,
+// Steps,
+// TreeSelect,
+// Link,
+// Icon,
+// Form,
+// BoundedBox,
+// ToggleSwitchV2,
+// };
+// if (isPDFSupported()) {
+// AllComponents.PDF = await import('@/Editor/Components/PDF').then((module) => module.PDF);
+// }
-export const getComponentToRender = (componentName) => {
- const shouldHideWidget = componentName === 'PDF' && !isPDFSupported();
- if (shouldHideWidget) return null;
- return AllComponents[componentName];
-};
+// export const getComponentToRender = (componentName) => {
+// const shouldHideWidget = componentName === 'PDF' && !isPDFSupported();
+// if (shouldHideWidget) return null;
+// return AllComponents[componentName];
+// };
export function isOnlyLayoutUpdate(diffState) {
const componentDiff = Object.keys(diffState).filter((key) => diffState[key]?.layouts && !diffState[key]?.component);
diff --git a/frontend/src/_helpers/routes.js b/frontend/src/_helpers/routes.js
index 0f552cb955..495ca04e18 100644
--- a/frontend/src/_helpers/routes.js
+++ b/frontend/src/_helpers/routes.js
@@ -21,6 +21,7 @@ export const getPrivateRoute = (page, params = {}) => {
workflows: '/workflows',
workspace_constants: '/workspace-constants',
profile_settings: '/profile-settings',
+ modules: '/modules',
};
let url = routes[page];
diff --git a/frontend/src/_helpers/white-label/whiteLabelling.js b/frontend/src/_helpers/white-label/whiteLabelling.js
index cfbbcbfc0c..c32573a96f 100644
--- a/frontend/src/_helpers/white-label/whiteLabelling.js
+++ b/frontend/src/_helpers/white-label/whiteLabelling.js
@@ -54,7 +54,8 @@ export async function setFaviconAndTitle(location) {
'data-sources': 'Data sources',
'audit-logs': 'Audit logs',
'account-settings': 'Profile settings',
- settings: 'Profile settings',
+ settings: 'Settings',
+ 'profile-settings': 'Profile settings',
login: '',
signUp: '',
error: '',
@@ -65,9 +66,12 @@ export async function setFaviconAndTitle(location) {
'reset-password': '',
'workspace-constants': 'Workspace constants',
setup: '',
+ '/': 'Dashboard',
};
- const pageTitleKey = Object.keys(pageTitles).find((path) => location?.pathname.includes(path));
+ const pageTitleKey = Object.keys(pageTitles)
+ .sort((a, b) => b.length - a.length) // Sort by length descending
+ .find((path) => location?.pathname.includes(path));
const pageTitle = pageTitles[pageTitleKey] || '';
document.title = pageTitle ? `${decodeEntities(pageTitle)} | ${whiteLabelText}` : `${decodeEntities(whiteLabelText)}`;
@@ -77,6 +81,9 @@ export async function fetchAndSetWindowTitle(pageDetails) {
const whiteLabelText = retrieveWhiteLabelText();
let pageTitleKey = pageDetails?.page || '';
let pageTitle = '';
+ let mode = pageDetails?.mode || '';
+ let isPreview = !pageDetails?.isReleased || false;
+ const license = pageDetails?.licenseStatus;
switch (pageTitleKey) {
case pageTitles.VIEWER: {
const titlePrefix = pageDetails?.preview ? 'Preview - ' : '';
@@ -85,7 +92,11 @@ export async function fetchAndSetWindowTitle(pageDetails) {
}
case pageTitles.EDITOR:
case pageTitles.WORKFLOW_EDITOR: {
- pageTitle = pageDetails?.appName || 'My App';
+ if (mode == 'edit') {
+ pageTitle = `${pageDetails?.appName}`;
+ } else {
+ pageTitle = `Preview - ${pageDetails?.appName}` || 'My App';
+ }
break;
}
default: {
@@ -93,6 +104,10 @@ export async function fetchAndSetWindowTitle(pageDetails) {
break;
}
}
+ if (!isPreview && mode === 'view') {
+ document.title = `${pageDetails?.appName} ${license ? '' : '| ToolJet'}`;
+ return;
+ }
document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`;
}
diff --git a/frontend/src/_services/apps.service.js b/frontend/src/_services/apps.service.js
index 82cf4312e9..c29dea481a 100644
--- a/frontend/src/_services/apps.service.js
+++ b/frontend/src/_services/apps.service.js
@@ -67,18 +67,30 @@ function getAll(page, folder, searchKey, type = 'front-end') {
}
function createApp(body = {}) {
- if (body.type === 'workflow') {
- return createWorkflow(body);
+ const requestOptions = {
+ method: 'POST',
+ headers: authHeader(),
+ credentials: 'include',
+ body: JSON.stringify(body),
+ };
+ switch (body.type) {
+ case 'workflow':
+ return createWorkflow(requestOptions);
+ case 'module':
+ return createModule(requestOptions);
+ default:
+ return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse);
}
- const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
- return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse);
}
-function createWorkflow(body = {}) {
- const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
+function createWorkflow(requestOptions) {
return fetch(`${config.apiUrl}/workflows`, requestOptions).then(handleResponse);
}
+function createModule(requestOptions) {
+ return fetch(`${config.apiUrl}/modules`, requestOptions).then(handleResponse);
+}
+
function cloneApp(id, name) {
const requestOptions = {
method: 'POST',
diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss
index 074338602e..b823e7623c 100644
--- a/frontend/src/_styles/components.scss
+++ b/frontend/src/_styles/components.scss
@@ -95,10 +95,7 @@ $btn-dark-color: #FFFFFF;
}
.leftsidebar-panel-header {
- background-color: var(--slate3);
- padding: 12px 16px;
- min-height: 52px;
- border-bottom: 1px solid var(--slate5);
+ padding: 12px 16px 0px 16px;
.panel-header-container {
@@ -125,6 +122,8 @@ $btn-dark-color: #FFFFFF;
.page-selector-panel-body {
padding: 4px;
border-right: 1px solid #DFE3E6;
+ padding-left:16px;
+ padding-right:16px;
&.dark-theme {
border-right: 1px solid var(--slate7);
@@ -242,10 +241,16 @@ $btn-dark-color: #FFFFFF;
display: flex;
align-items: baseline;
gap: 5px;
+ cursor: pointer !important;
+ pointer-events: unset !important;
&.disabled {
opacity: 1 !important;
}
+
+ svg {
+ margin-left: 5px;
+ }
}
}
diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss
index e2fe92105a..6a0311b0c4 100644
--- a/frontend/src/_styles/left-sidebar.scss
+++ b/frontend/src/_styles/left-sidebar.scss
@@ -182,9 +182,17 @@
}
}
+.page {
+ .leftsidebar-panel-header {
+ margin-bottom: 8px;
+ }
+
+}
+
.debugger {
.leftsidebar-panel-header {
border-bottom: none;
+ margin-bottom: 8px;
}
.text-slate-12 {
@@ -207,7 +215,7 @@
}
.nav-item .nav-link {
- background-color: var(--slate3) !important;
+ background-color: var(--base) !important;
color: var(--slate11) !important;
}
@@ -859,4 +867,20 @@
height: calc(100% - 48px);
min-height: 300px;
position: relative;
-}
\ No newline at end of file
+}
+
+.left-sidebar-scrollbar {
+ &::-webkit-scrollbar {
+ width: 6px;
+ margin-right: 3px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--interactive-default) !important;
+ border-radius: 3px;
+ }
+}
diff --git a/frontend/src/_styles/modules.scss b/frontend/src/_styles/modules.scss
new file mode 100644
index 0000000000..dd9891df6d
--- /dev/null
+++ b/frontend/src/_styles/modules.scss
@@ -0,0 +1,76 @@
+.apps-modules-tabs {
+ .nav-link {
+ background-color: var(--page-default);
+ }
+
+ .nav-link.active {
+ border-bottom-width: medium !important;
+ }
+
+ li.nav-item {
+ flex: 1;
+
+ button {
+ width: 100%;
+ }
+ }
+
+}
+
+.apps-modules-navigation {
+ margin-bottom: 10px;
+
+ .tab-content {
+ display: none;
+ }
+}
+
+.apps-modules-tabs.dark-mode {
+ .nav-link {
+ background-color: inherit;
+ }
+
+ border-bottom-color: #2B394A;
+}
+
+#homePage-tab-front-end,
+#homePage-tab-module {
+ border-radius: 0;
+}
+
+.show-module-border {
+ outline: dotted 2px #CCD1D5 !important;
+}
+
+.module-container-canvas {
+ >div:first-child {
+ height: 100%;
+ overflow: hidden auto;
+
+ /* Hide scrollbar by default */
+ &::-webkit-scrollbar {
+ width: 6px;
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: transparent;
+ }
+
+ /* Show scrollbar only on hover */
+ &:hover {
+ &::-webkit-scrollbar-thumb {
+ background-color: #6a727c4d;
+ border-radius: 3px;
+ }
+ }
+
+ /* Firefox scrollbar support */
+ scrollbar-width: thin;
+ scrollbar-color: transparent transparent;
+
+ &:hover {
+ scrollbar-color: #6a727c4d transparent;
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index 136bc37f11..c97d63bdf3 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -19,6 +19,7 @@
@import 'tailwindcss/utilities';
@import "./componentdesign.scss";
@import './pages-sidebar.scss';
+@import './modules.scss';
/* ibm-plex-sans-100 - latin */
@font-face {
@@ -806,6 +807,21 @@ button {
}
}
+ .viewer .main {
+ height: auto !important;
+
+ .canvas-container {
+ top: 0;
+ right: 0;
+ scrollbar-width: thin;
+ scrollbar-color: #6a727c4d transparent;
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #6a727c4d !important;
+ }
+ }
+ }
+
@media screen and (max-height: 450px) {
.sidebar {
padding-top: 15px;
@@ -1552,6 +1568,13 @@ button {
border-top: 1px solid var(--slate5) !important;
}
+ &.module-editor-inspector {
+ .tab-content {
+ border-top: none !important;
+ }
+ }
+
+ /* Hide scrollbar for Chrome, Safari and Opera */
/* Hide scrollbar for Chrome, Safari and Opera */
.tab-content::-webkit-scrollbar {
display: none;
@@ -2707,6 +2730,14 @@ hr {
max-height: 10px;
z-index: 100;
min-width: 108px;
+
+ &.module-container {
+ .handle-content {
+ cursor: move;
+ color: #fff;
+ background: #c6cad0 !important;
+ }
+ }
}
@@ -4119,6 +4150,7 @@ input[type="text"] {
.rbc-event-label {
display: none;
}
+
background-color: var(--primary-brand) !important;
border: transparent
}
@@ -7574,6 +7606,13 @@ tbody {
.apploader {
height: 100vh;
+ &.module-mode {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
.app-container {
height: 100%;
display: flex;
@@ -7935,6 +7974,11 @@ tbody {
display: grid;
grid-template-rows: auto 1fr auto;
+ // Added to work with AppTypeTab component
+ &:has(:nth-child(4):last-child) {
+ grid-template-rows: auto auto 1fr auto;
+ }
+
@media only screen and (max-width: 767px) {
display: none;
}
@@ -9137,7 +9181,8 @@ tbody {
}
.global-settings-app-wrapper {
- max-width: 190px;
+ max-width: 350px;
+ margin-right: 10px;
}
.version-manager-container {
@@ -10894,7 +10939,7 @@ tbody {
background: var(--indigo3);
border-radius: 6px;
padding: 5px 10px;
-
+
p {
font-weight: 500 !important;
line-height: 18px !important;
@@ -12304,11 +12349,11 @@ tbody {
}
.design-component-inputs textarea {
-
+
&.valid-textarea {
border: 1.5px solid #519b62 !important;
}
-
+
&.invalid-textarea {
border: 1.5px solid #e26367 !important;
}
@@ -15750,7 +15795,7 @@ tbody {
/* Set the desired width */
}
-textarea.tj-text-input-widget{
+textarea.tj-text-input-widget {
resize: none !important;
overflow-y: auto !important;
}
@@ -18659,8 +18704,8 @@ section.ai-message-prompt-input-wrapper {
}
.codebuilder-color-swatches-options {
- width:100%;
- height:30px;
+ width: 100%;
+ height: 30px;
padding: 6px 8px;
border-radius: 6px;
@@ -18693,8 +18738,8 @@ section.ai-message-prompt-input-wrapper {
.theme-create-btn {
width: 100%;
margin-bottom: 8px;
- height:32px;
- color:#000;
+ height: 32px;
+ color: #000;
border: 1px solid var(--Border-brand-weak, #97AEFC);
}
@@ -18717,14 +18762,14 @@ section.ai-message-prompt-input-wrapper {
border-color: var(--primary-brand);
}
-.multiselct-widget-option{
+.multiselct-widget-option {
input:checked {
background-color: var(--primary-brand);
}
}
.multiselect-box {
- .options{
+ .options {
input:checked {
background-color: var(--primary-brand);
}
@@ -18733,6 +18778,7 @@ section.ai-message-prompt-input-wrapper {
.timer-btn {
background-color: var(--primary-brand);
+
&:hover {
background-color: var(--primary-brand);
}
@@ -18771,6 +18817,7 @@ section.ai-message-prompt-input-wrapper {
color: #ffffff;
border: 1px solid transparent;
}
+
.canvas-styles-header {
background-color: #212325;
color: #ffffff;
@@ -18816,8 +18863,9 @@ section.ai-message-prompt-input-wrapper {
}
#inspector-tabpane-properties .accordion-header {
- height:32px;
+ height: 32px;
}
+
.cm-tooltip {
z-index: 9999 !important;
}
@@ -18887,4 +18935,70 @@ section.ai-message-prompt-input-wrapper {
.cm-editor {
max-height: 100px !important;
}
+}
+
+.missing-groups-modal {
+ .modal-body {
+ padding: 16px;
+
+ .header {
+ padding-top: 12px;
+ font-weight: 500;
+ font-size: 14px;
+ }
+
+ .sub-header {
+ margin-bottom: 0px;
+ font-size: 12px;
+ }
+
+ .groups-list {
+ padding-top: 16px;
+ padding-bottom: 16px;
+
+ .container {
+ padding: 12px;
+ }
+ }
+
+ .info {
+ margin-bottom: 0px;
+ font-size: 12px;
+ padding-bottom: 24px;
+ }
+
+ .action-btns {
+ justify-content: space-between;
+ }
+
+ .primary-action,
+ .secondary-action {
+ padding: 8px !important;
+ font-size: 12px;
+ }
+
+ .toggle-button {
+ display: inline-flex;
+ align-items: center;
+ font-size: 14px;
+ color: var(--icon-brand);
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ font-family: inherit;
+ }
+
+ .toggle-button:hover {
+ text-decoration: underline;
+ }
+
+ .toggle-button .chevron {
+ transition: transform 0.2s ease;
+ }
+
+ .toggle-button.expanded .chevron {
+ transform: rotate(180deg);
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/src/_ui/Icon/solidIcons/Corners.jsx b/frontend/src/_ui/Icon/solidIcons/Corners.jsx
new file mode 100644
index 0000000000..72ca376981
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/Corners.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+const Corners = ({ style, fill = '#C1C8CD', width = '12', height = '13', className = '', viewBox = '0 0 12 13' }) => (
+
+
+
+
+
+);
+
+export default Corners;
diff --git a/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx
new file mode 100644
index 0000000000..963c4b7bef
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+const EmptyStateModules = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24' }) => (
+
+
+
+
+
+);
+
+export default EmptyStateModules;
diff --git a/frontend/src/_ui/Icon/solidIcons/EnterrpiseCrown.jsx b/frontend/src/_ui/Icon/solidIcons/EnterrpiseCrown.jsx
new file mode 100644
index 0000000000..eed8dd5e8c
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/EnterrpiseCrown.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+const EnterpriseCrown = ({ fill = '#FCA23F', width = '12', className = '', viewBox = '0 0 16 16' }) => (
+
+
+
+);
+
+export default EnterpriseCrown;
diff --git a/frontend/src/_ui/Icon/solidIcons/FileCode.jsx b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx
new file mode 100644
index 0000000000..ed1f3268c9
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/FileCode.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+const FileCode = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 12 12' }) => (
+
+
+
+
+
+);
+
+export default FileCode;
diff --git a/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx
new file mode 100644
index 0000000000..dd97454175
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/RemoveFolder.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+const RemoveFolder = ({ width = '14', fill = '#6A727C', className = '', viewBox = '0 0 14 14' }) => (
+
+
+
+
+
+
+
+
+
+
+);
+
+export default RemoveFolder;
diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js
index 2a7b801570..0e7192edaf 100644
--- a/frontend/src/_ui/Icon/solidIcons/index.js
+++ b/frontend/src/_ui/Icon/solidIcons/index.js
@@ -231,10 +231,15 @@ import CalendarSmall from './CalendarSmall.jsx';
import UserGroupsGrey from './UserGroupsGrey.jsx';
import AppLimitSvg from './AppLimitSvg.jsx';
import NewTabSmall from './NewTabSmall.jsx';
+import EmptyStateModules from './EmptyStateModules.jsx';
import Code from './Code.jsx';
import WorkflowV3 from './WorkflowV3.jsx';
import WorkspaceV3 from './WorkspaceV3.jsx';
+import EnterpriseCrown from './EnterrpiseCrown.jsx';
+import FileCode from './FileCode.jsx';
+import Corners from './Corners.jsx';
import Moon from './Moon.jsx';
+import RemoveFolder from './RemoveFolder.jsx';
const Icon = (props) => {
switch (props.name) {
@@ -356,6 +361,8 @@ const Icon = (props) => {
return ;
case 'enterprisev3':
return ;
+ case 'enterprisecrown':
+ return ;
case 'lockGradient':
return ;
case 'datasourceGradient':
@@ -370,6 +377,8 @@ const Icon = (props) => {
return ;
case 'expand':
return ;
+ case 'file-code':
+ return ;
case 'file01':
return ;
case 'filedownload':
@@ -496,6 +505,8 @@ const Icon = (props) => {
return ;
case 'remove01':
return ;
+ case 'removefolder':
+ return ;
case 'removerectangle':
return ;
case 'rightarrrow':
@@ -532,6 +543,8 @@ const Icon = (props) => {
return ;
case 'comments':
return ;
+ case 'corners':
+ return ;
case 'share':
return ;
case 'shield':
@@ -706,6 +719,8 @@ const Icon = (props) => {
return ;
case 'ai-crown':
return ;
+ case 'empty-state-modules':
+ return ;
case 'play01':
return ;
case 'moon':
diff --git a/frontend/src/_ui/Modal/index.jsx b/frontend/src/_ui/Modal/index.jsx
index b9d48f0d88..6ed7fa04db 100644
--- a/frontend/src/_ui/Modal/index.jsx
+++ b/frontend/src/_ui/Modal/index.jsx
@@ -18,6 +18,8 @@ export default function ModalBase({
className = '',
size = 'sm',
headerAction,
+ showHeader = true,
+ showFooter = true,
}) {
return (
-
-
- {title}
-
-
-
+ {showHeader && (
+
+
+ {title}
+
+
+
+ )}
{children ? (
children
@@ -45,28 +49,30 @@ export default function ModalBase({
)}
-
-
- Cancel
-
-
-
-
- {confirmBtnProps?.title || 'Continue'}
-
-
-
-
+ {showFooter && (
+
+
+ Cancel
+
+
+
+
+ {confirmBtnProps?.title || 'Continue'}
+
+
+
+
+ )}
);
}
diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx
index 978e69798f..27903c783f 100644
--- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx
+++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx
@@ -47,6 +47,7 @@ const TextInput = ({
diff --git a/frontend/src/components/ui/Input/Index.jsx b/frontend/src/components/ui/Input/Index.jsx
index 139019a198..dbb6806d6b 100644
--- a/frontend/src/components/ui/Input/Index.jsx
+++ b/frontend/src/components/ui/Input/Index.jsx
@@ -13,6 +13,7 @@ InputComponent.propTypes = {
type: PropTypes.oneOf(['text', 'number', 'editable title', 'password', 'email']),
value: PropTypes.string,
onChange: PropTypes.func,
+ onClear: PropTypes.func,
placeholder: PropTypes.string,
name: PropTypes.string,
id: PropTypes.string,
@@ -32,6 +33,7 @@ InputComponent.propTypes = {
InputComponent.defaultProps = {
type: 'text',
onChange: (e, validateObj) => {},
+ onClear: () => {},
placeholder: '',
name: '',
id: '',
diff --git a/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx
new file mode 100644
index 0000000000..8c18f534f6
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleContainer = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleContainer, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleContainer/index.js b/frontend/src/modules/Modules/components/ModuleContainer/index.js
new file mode 100644
index 0000000000..f5495b7be9
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainer/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleContainer';
diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx
new file mode 100644
index 0000000000..2ddb28f0d2
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleContainerBlank = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleContainerBlank, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js
new file mode 100644
index 0000000000..b8954c974c
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainerBlank/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleContainerBlank';
diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx
new file mode 100644
index 0000000000..8782ae9ac7
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleContainerInspector = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleContainerInspector, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js
new file mode 100644
index 0000000000..6417a2160e
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleContainerInspector/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleContainerInspector';
diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx
new file mode 100644
index 0000000000..3bf0d5b4e8
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleEditorBanner = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleEditorBanner, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js
new file mode 100644
index 0000000000..c157c1e0b9
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleEditorBanner/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleEditorBanner';
diff --git a/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx
new file mode 100644
index 0000000000..ee4d9a6a47
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleManager = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleManager, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleManager/index.js b/frontend/src/modules/Modules/components/ModuleManager/index.js
new file mode 100644
index 0000000000..5a89ccc37e
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleManager/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleManager';
diff --git a/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx
new file mode 100644
index 0000000000..2297735672
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleViewer = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleViewer, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleViewer/index.js b/frontend/src/modules/Modules/components/ModuleViewer/index.js
new file mode 100644
index 0000000000..df8725725a
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleViewer/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleViewer';
diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx
new file mode 100644
index 0000000000..b5043e170a
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleViewerInspector = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleViewerInspector, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js
new file mode 100644
index 0000000000..c95c5a7a46
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleViewerInspector/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleViewerInspector';
diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx
new file mode 100644
index 0000000000..7a43aa67e3
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const ModuleWidgetBox = () => {
+ return <>>;
+};
+
+export default withEditionSpecificComponent(ModuleWidgetBox, 'Modules');
diff --git a/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js
new file mode 100644
index 0000000000..1a903dadf7
--- /dev/null
+++ b/frontend/src/modules/Modules/components/ModuleWidgetBox/index.js
@@ -0,0 +1 @@
+export { default } from './ModuleWidgetBox';
diff --git a/frontend/src/modules/Modules/components/index.js b/frontend/src/modules/Modules/components/index.js
new file mode 100644
index 0000000000..0cd5f2301e
--- /dev/null
+++ b/frontend/src/modules/Modules/components/index.js
@@ -0,0 +1,19 @@
+import ModuleContainer from './ModuleContainer';
+import ModuleViewer from './ModuleViewer';
+import ModuleContainerInspector from './ModuleContainerInspector';
+import ModuleViewerInspector from './ModuleViewerInspector';
+import ModuleWidgetBox from './ModuleWidgetBox';
+import ModuleManager from './ModuleManager';
+import ModuleEditorBanner from './ModuleEditorBanner';
+import ModuleContainerBlank from './ModuleContainerBlank';
+
+export {
+ ModuleContainer,
+ ModuleViewer,
+ ModuleContainerInspector,
+ ModuleViewerInspector,
+ ModuleWidgetBox,
+ ModuleManager,
+ ModuleEditorBanner,
+ ModuleContainerBlank,
+};
diff --git a/frontend/src/modules/Modules/index.js b/frontend/src/modules/Modules/index.js
new file mode 100644
index 0000000000..04dab890ed
--- /dev/null
+++ b/frontend/src/modules/Modules/index.js
@@ -0,0 +1,2 @@
+const Modules = (props) => [];
+export default Modules;
diff --git a/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx b/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx
index 76b647f545..3d21041205 100644
--- a/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx
+++ b/frontend/src/modules/common/components/BaseAppActionModal/BaseAppActionModal.jsx
@@ -4,7 +4,7 @@ import { AppModal } from '@/_components';
const BaseAppActionModal = ({ configs, modalStates, ...props }) => {
const getActiveConfig = () => {
switch (true) {
- case modalStates.showCreateAppModal || modalStates.showCreateModuleModal:
+ case modalStates.showCreateAppModal:
return configs.create;
case modalStates.showCloneAppModal:
return configs.clone;
diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx
index a0e14f7bb2..f30c40b82f 100644
--- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx
+++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx
@@ -4,12 +4,14 @@ import { shallow } from 'zustand/shallow';
import { ToolTip } from '@/_components/ToolTip';
import { PromoteConfirmationModal } from './components';
import useStore from '@/AppBuilder/_stores/store';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
const PromoteVersionButton = () => {
+ const { moduleId } = useModuleContext();
const [promoteModalData, setPromoteModalData] = useState(null);
const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment, currentEnvIndex } = useStore(
(state) => ({
- isSaving: state.app.isSaving,
+ isSaving: state.appStore.modules[moduleId].app.isSaving,
editingVersion: state.currentVersionId,
selectedEnvironment: state.selectedEnvironment,
environments: state.environments,
diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx
index cae938d130..cbc1284c06 100644
--- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx
+++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/components/PromoteConfirmationModal/PromoteConfirmationModal.jsx
@@ -9,8 +9,10 @@ import ArrowRightIcon from '@assets/images/icons/arrow-right.svg';
import '@/_styles/versions.scss';
import { shallow } from 'zustand/shallow';
import useStore from '@/AppBuilder/_stores/store';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
const PromoteConfirmationModal = React.memo(({ data, onClose }) => {
+ const { moduleId } = useModuleContext();
const [promotingEnvironment, setPromotingEnvironment] = useState(false);
const darkMode = localStorage.getItem('darkMode') === 'true' || false;
const currentVersionId = useStore((state) => state.currentVersionId);
@@ -22,7 +24,7 @@ const PromoteConfirmationModal = React.memo(({ data, onClose }) => {
(state) => ({
promoteAppVersionAction: state.promoteAppVersionAction,
selectedVersion: state.selectedVersion,
- creationMode: state.app.creationMode,
+ creationMode: state.appStore.modules[moduleId].app.creationMode,
}),
shallow
);
diff --git a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx
index 494eaa52e9..48b77cc238 100644
--- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx
+++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/ReleaseVersionButton/ReleaseVersionButton.jsx
@@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow';
import '@/_styles/versions.scss';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import useStore from '@/AppBuilder/_stores/store';
+import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
const ReleaseVersionButton = function DeployVersionButton() {
+ const { moduleId } = useModuleContext();
const [isReleasing, setIsReleasing] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false);
const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore(
@@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() {
editingVersion: state.editingVersion,
isEditorFreezed: state.isEditorFreezed,
updateReleasedVersionId: state.updateReleasedVersionId,
- appId: state.app.appId,
+ appId: state.appStore.modules[moduleId].app.appId,
versionToBeReleased: state.currentVersionId,
// selectedVersionId: state.selectedVersion.id,
}),
diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx
new file mode 100644
index 0000000000..293d325c59
--- /dev/null
+++ b/frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx
@@ -0,0 +1,7 @@
+import React from 'react';
+import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
+
+const AppTypeTab = () => {
+ return <>>;
+};
+export default withEditionSpecificComponent(AppTypeTab, 'Dashboard');
diff --git a/frontend/src/modules/dashboard/components/AppTypeTab/index.js b/frontend/src/modules/dashboard/components/AppTypeTab/index.js
new file mode 100644
index 0000000000..ae18e0c4c3
--- /dev/null
+++ b/frontend/src/modules/dashboard/components/AppTypeTab/index.js
@@ -0,0 +1 @@
+export { default } from './AppTypeTab';
diff --git a/frontend/src/modules/dashboard/components/index.js b/frontend/src/modules/dashboard/components/index.js
index ab540b5feb..d4ffff00c4 100644
--- a/frontend/src/modules/dashboard/components/index.js
+++ b/frontend/src/modules/dashboard/components/index.js
@@ -6,6 +6,7 @@ import SettingsMenu from './SettingsMenu';
import WorkspaceActions from './WorkspaceActions';
import ConsultationBanner from './ConsultationBanner';
import UserGroupMigrationBanner from './UserGroupMigrationBanner';
+import AppTypeTab from './AppTypeTab';
export {
ImportAppMenu,
@@ -16,4 +17,5 @@ export {
WorkspaceActions,
ConsultationBanner,
UserGroupMigrationBanner,
+ AppTypeTab,
};
diff --git a/frontend/src/modules/index.js b/frontend/src/modules/index.js
index 69786dee07..f6feaf45c0 100644
--- a/frontend/src/modules/index.js
+++ b/frontend/src/modules/index.js
@@ -13,6 +13,7 @@ import Settings from './Settings';
import Workflows from './workflows';
import WorkspaceSettings from './WorkspaceSettings';
import RenderWorkflow from './RenderWorkflow';
+import Modules from './Modules';
export {
onboarding,
@@ -27,4 +28,5 @@ export {
getAuditLogsRoutes,
RenderWorkflow,
AiBuilder,
+ Modules,
};
diff --git a/server/ee b/server/ee
index f70ac83c38..b58301bdd8 160000
--- a/server/ee
+++ b/server/ee
@@ -1 +1 @@
-Subproject commit f70ac83c38e0a8b44aeb2a0fb2059690eb5e2f46
+Subproject commit b58301bdd81d035f491debec0a55c7fc342c4f12
diff --git a/server/migrations/1744610362161-CreatePagePermissions.ts b/server/migrations/1744610362161-CreatePagePermissions.ts
index ca4afbac66..ebf622da8b 100644
--- a/server/migrations/1744610362161-CreatePagePermissions.ts
+++ b/server/migrations/1744610362161-CreatePagePermissions.ts
@@ -1,13 +1,7 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-import { TOOLJET_EDITIONS } from '@modules/app/constants';
-import { getTooljetEdition } from '@helpers/utils.helper';
export class CreatePagePermissions1744610362161 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise
{
- if (getTooljetEdition() === TOOLJET_EDITIONS.CE) {
- return;
- }
-
await queryRunner.createTable(
new Table({
name: 'page_permissions',
diff --git a/server/migrations/1744611380594-CreatePageUsers.ts b/server/migrations/1744611380594-CreatePageUsers.ts
index 5fe4d126c7..f1c6c89beb 100644
--- a/server/migrations/1744611380594-CreatePageUsers.ts
+++ b/server/migrations/1744611380594-CreatePageUsers.ts
@@ -1,13 +1,7 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-import { TOOLJET_EDITIONS } from '@modules/app/constants';
-import { getTooljetEdition } from '@helpers/utils.helper';
export class CreatePageUsers1744611380594 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
- if (getTooljetEdition() === TOOLJET_EDITIONS.CE) {
- return;
- }
-
await queryRunner.createTable(
new Table({
name: 'page_users',
diff --git a/server/src/dto/import-resources.dto.ts b/server/src/dto/import-resources.dto.ts
index f00992213e..89b3ee182c 100644
--- a/server/src/dto/import-resources.dto.ts
+++ b/server/src/dto/import-resources.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsOptional, IsString, IsDefined, ValidateNested } from 'class-validator';
+import { IsUUID, IsOptional, IsString, IsDefined, ValidateNested, IsBoolean } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { ValidateTooljetDatabaseSchema } from './validators/tooljet-database.validator';
import { TjdbSchemaToLatestVersion } from './transformers/resource-transformer';
@@ -28,6 +28,10 @@ export class ImportResourcesDto {
// and instantiated data
@ValidateTooljetDatabaseSchema({ each: true })
tooljet_database: ImportTooljetDatabaseDto[];
+
+ @IsOptional()
+ @IsBoolean()
+ skip_page_permissions_group_check?: boolean;
}
export class ImportAppDto {
diff --git a/server/src/entities/group_permissions.entity.ts b/server/src/entities/group_permissions.entity.ts
index 92868d7510..693f4f930c 100644
--- a/server/src/entities/group_permissions.entity.ts
+++ b/server/src/entities/group_permissions.entity.ts
@@ -3,7 +3,6 @@ import {
Column,
CreateDateColumn,
Entity,
- Index,
JoinColumn,
ManyToOne,
OneToMany,
@@ -21,7 +20,6 @@ export class GroupPermissions extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
- @Index()
@Column({ name: 'organization_id', nullable: false })
organizationId: string;
diff --git a/server/src/entities/group_users.entity.ts b/server/src/entities/group_users.entity.ts
index 29771a5557..03ac55386b 100644
--- a/server/src/entities/group_users.entity.ts
+++ b/server/src/entities/group_users.entity.ts
@@ -3,7 +3,6 @@ import {
Column,
CreateDateColumn,
Entity,
- Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
@@ -17,11 +16,9 @@ export class GroupUsers extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
- @Index()
@Column({ name: 'user_id', nullable: false })
userId: string;
- @Index()
@Column({ name: 'group_id', nullable: false })
groupId: string;
diff --git a/server/src/entities/page_users.entity.ts b/server/src/entities/page_users.entity.ts
index ca3ef77c65..960be5b32f 100644
--- a/server/src/entities/page_users.entity.ts
+++ b/server/src/entities/page_users.entity.ts
@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, Index } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
import { User } from './user.entity';
import { PagePermission } from './page_permissions.entity';
import { GroupPermissions } from './group_permissions.entity';
@@ -8,15 +8,12 @@ export class PageUser {
@PrimaryGeneratedColumn('uuid')
id: string;
- @Index()
@Column({ name: 'page_permissions_id', type: 'uuid' })
pagePermissionsId: string;
- @Index()
@Column({ name: 'user_id', type: 'uuid', nullable: true })
userId: string | null;
- @Index()
@Column({ name: 'permission_groups_id', type: 'uuid', nullable: true })
permissionGroupsId: string | null;
diff --git a/server/src/helpers/error_type.constant.ts b/server/src/helpers/error_type.constant.ts
index 4b968f002e..cb483e8896 100644
--- a/server/src/helpers/error_type.constant.ts
+++ b/server/src/helpers/error_type.constant.ts
@@ -1,5 +1,7 @@
export const APP_ERROR_TYPE = {
IMPORT_EXPORT_SERVICE: {
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
+ PAGE_PERMISSION_GROUP_ERROR: 'Following groups are missing from the workspace',
+ PERMISSION_CHECK: 'permission-check',
},
};
diff --git a/server/src/modules/app/constants/modules.ts b/server/src/modules/app/constants/modules.ts
index ab87064a84..1522332a09 100644
--- a/server/src/modules/app/constants/modules.ts
+++ b/server/src/modules/app/constants/modules.ts
@@ -39,4 +39,5 @@ export enum MODULES {
APP_PERMISSIONS = 'AppPermissions',
AUDIT_LOGS = 'auditLogs',
EXTERNAL_APIS = 'externalApis',
+ MODULES = 'Modules',
}
diff --git a/server/src/modules/app/module.ts b/server/src/modules/app/module.ts
index 435122b0cb..7f95f46b46 100644
--- a/server/src/modules/app/module.ts
+++ b/server/src/modules/app/module.ts
@@ -39,6 +39,7 @@ import { TemplatesModule } from '@modules/templates/module';
import { ImportExportResourcesModule } from '@modules/import-export-resources/module';
import { TooljetDbModule } from '@modules/tooljet-db/module';
import { WorkflowsModule } from '@modules/workflows/module';
+import { ModulesModule } from '@modules/modules/module';
import { AiModule } from '@modules/ai/module';
import { CustomStylesModule } from '@modules/custom-styles/module';
import { AppPermissionsModule } from '@modules/app-permissions/module';
@@ -95,6 +96,7 @@ export class AppModule implements OnModuleInit {
await TemplatesModule.register(configs),
await TooljetDbModule.register(configs),
await WorkflowsModule.register(configs),
+ await ModulesModule.register(configs),
await AiModule.register(configs),
await CustomStylesModule.register(configs),
await AppPermissionsModule.register(configs),
diff --git a/server/src/modules/apps/constants/index.ts b/server/src/modules/apps/constants/index.ts
index 234950ca4d..81b7b8cae4 100644
--- a/server/src/modules/apps/constants/index.ts
+++ b/server/src/modules/apps/constants/index.ts
@@ -15,6 +15,7 @@ export enum FEATURE_KEY {
export enum APP_TYPES {
FRONT_END = 'front-end',
WORKFLOW = 'workflow',
+ MODULE = 'module',
}
export enum LayoutDimensionUnits {
diff --git a/server/src/modules/apps/module.ts b/server/src/modules/apps/module.ts
index 6565c17ed1..2a54c29326 100644
--- a/server/src/modules/apps/module.ts
+++ b/server/src/modules/apps/module.ts
@@ -39,15 +39,7 @@ export class AppsModule {
return {
module: AppsModule,
imports: [
- TypeOrmModule.forFeature([
- App,
- Page,
- EventHandler,
- Organization,
- Component,
- VersionRepository,
- RolesRepository,
- ]),
+ TypeOrmModule.forFeature([App, Page, EventHandler, Organization, Component, VersionRepository]),
await FolderAppsModule.register(configs),
await ThemesModule.register(configs),
await FoldersModule.register(configs),
diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts
index 27fd03c034..7d3b865b60 100644
--- a/server/src/modules/apps/service.ts
+++ b/server/src/modules/apps/service.ts
@@ -31,6 +31,7 @@ import { FoldersUtilService } from '@modules/folders/util.service';
import { FolderAppsUtilService } from '@modules/folder-apps/util.service';
import { PageService } from './services/page.service';
import { EventsService } from './services/event.service';
+import { ComponentsService } from './services/component.service';
import { LICENSE_FIELD } from '@modules/licensing/constants';
import { AppEnvironment } from '@entities/app_environments.entity';
import { OrganizationThemesUtilService } from '@modules/organization-themes/util.service';
@@ -38,6 +39,7 @@ import { IAppsService } from './interfaces/IService';
import { AiUtilService } from '@modules/ai/util.service';
import { RequestContext } from '@modules/request-context/service';
import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants';
+import { MODULES } from '@modules/app/constants/modules';
@Injectable()
export class AppsService implements IAppsService {
@@ -52,8 +54,9 @@ export class AppsService implements IAppsService {
protected readonly pageService: PageService,
protected readonly eventService: EventsService,
protected readonly organizationThemeUtilService: OrganizationThemesUtilService,
- protected readonly aiUtilService: AiUtilService
- ) { }
+ protected readonly aiUtilService: AiUtilService,
+ protected readonly componentsService: ComponentsService
+ ) {}
async create(user: User, appCreateDto: AppCreateDto) {
const { name, icon, type } = appCreateDto;
return await dbTransactionWrap(async (manager: EntityManager) => {
@@ -98,8 +101,8 @@ export class AppsService implements IAppsService {
const version = versionId
? await this.versionRepository.findById(versionId, app.id)
: versionName
- ? await this.versionRepository.findByName(versionName, app.id)
- : // Handle version retrieval based on env
+ ? await this.versionRepository.findByName(versionName, app.id)
+ : // Handle version retrieval based on env
await this.versionRepository.findLatestVersionForEnvironment(
app.id,
envId,
@@ -200,6 +203,13 @@ export class AppsService implements IAppsService {
apps = await this.appsUtilService.all(user, parseInt(page || '1'), searchKey, type);
}
+ if (type === 'module') {
+ for (const app of apps) {
+ const appVersionId = app?.appVersions[0]?.id;
+ app.moduleContainer = await this.pageService.findModuleContainer(appVersionId);
+ }
+ }
+
const totalCount = await this.appsUtilService.count(user, searchKey, type);
const totalPageCount = folderId ? totalFolderCount : totalCount;
@@ -296,42 +306,53 @@ export class AppsService implements IAppsService {
}
async getBySlug(app: App, user: User): Promise {
- const versionToLoad = app.currentVersionId
- ? await this.versionRepository.findVersion(app.currentVersionId)
- : await this.versionRepository.findVersion(app.editingVersion?.id);
+ const prepareResponse = async (app) => {
+ const versionToLoad = app.currentVersionId
+ ? await this.versionRepository.findVersion(app.currentVersionId)
+ : await this.versionRepository.findVersion(app.editingVersion?.id);
- const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : [];
- const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : [];
- const appTheme = await this.organizationThemeUtilService.getTheme(
- app.organizationId,
- versionToLoad?.globalSettings?.theme?.id
- );
+ const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : [];
+ const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : [];
+ const appTheme = await this.organizationThemeUtilService.getTheme(
+ app.organizationId,
+ versionToLoad?.globalSettings?.theme?.id
+ );
- if (app?.isPublic && user) {
- RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
- userId: user.id,
- organizationId: user.organizationId,
- resourceId: app.id,
- resourceName: app.name,
- });
- }
+ if (app?.isPublic && user) {
+ RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, {
+ userId: user.id,
+ organizationId: user.organizationId,
+ resourceId: app.id,
+ resourceName: app.name,
+ resourceType: MODULES.APP,
+ });
+ }
- // serialize
- return {
- current_version_id: app['currentVersionId'],
- data_queries: versionToLoad?.dataQueries,
- definition: versionToLoad?.definition,
- is_public: app.isPublic,
- is_maintenance_on: app.isMaintenanceOn,
- name: app.name,
- slug: app.slug,
- events: eventsForVersion,
- pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion),
- homePageId: versionToLoad.homePageId,
- globalSettings: { ...versionToLoad.globalSettings, theme: appTheme },
- showViewerNavigation: versionToLoad.showViewerNavigation,
- pageSettings: versionToLoad?.pageSettings,
+ // serialize
+ return {
+ current_version_id: app['currentVersionId'],
+ data_queries: versionToLoad?.dataQueries,
+ definition: versionToLoad?.definition,
+ is_public: app.isPublic,
+ is_maintenance_on: app.isMaintenanceOn,
+ name: app.name,
+ slug: app.slug,
+ events: eventsForVersion,
+ pages: this.appsUtilService.mergeDefaultComponentData(pagesForVersion),
+ homePageId: versionToLoad.homePageId,
+ globalSettings: { ...versionToLoad.globalSettings, theme: appTheme },
+ showViewerNavigation: versionToLoad.showViewerNavigation,
+ pageSettings: versionToLoad?.pageSettings,
+ };
};
+
+ const response = await prepareResponse(app);
+
+ const modules = await this.appsUtilService.fetchModules(app, false, undefined);
+
+ response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module)));
+
+ return response;
}
async release(app: App, user: User, versionReleaseDto: VersionReleaseDto) {
diff --git a/server/src/modules/apps/services/app-import-export.service.ts b/server/src/modules/apps/services/app-import-export.service.ts
index 16fa8289f6..5d6da2ea7d 100644
--- a/server/src/modules/apps/services/app-import-export.service.ts
+++ b/server/src/modules/apps/services/app-import-export.service.ts
@@ -1,4 +1,4 @@
-import { BadRequestException, Injectable } from '@nestjs/common';
+import { BadRequestException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { isEmpty, set } from 'lodash';
import { App } from 'src/entities/app.entity';
import { AppEnvironment } from 'src/entities/app_environments.entity';
@@ -33,6 +33,11 @@ import { DataSourcesUtilService } from '@modules/data-sources/util.service';
import { DataSourcesRepository } from '@modules/data-sources/repository';
import { AppEnvironmentUtilService } from '@modules/app-environments/util.service';
import { ComponentsService } from './component.service';
+import { GroupPermissions } from '@entities/group_permissions.entity';
+import { APP_ERROR_TYPE } from '@helpers/error_type.constant';
+import { PAGE_PERMISSION_TYPE } from '@modules/app-permissions/constants';
+import { PagePermission } from '@entities/page_permissions.entity';
+import { PageUser } from '@entities/page_users.entity';
import { UsersUtilService } from '@modules/users/util.service';
interface AppResourceMappings {
defaultDataSourceIdMapping: Record;
@@ -186,13 +191,31 @@ export class AppImportExportService {
}
const pages = await manager
- .createQueryBuilder(Page, 'pages')
- .where('pages.appVersionId IN(:...versionId)', {
+ .createQueryBuilder(Page, 'page')
+ .leftJoinAndSelect('page.permissions', 'permission')
+ .leftJoinAndSelect('permission.users', 'pageUser')
+ .leftJoinAndSelect('pageUser.permissionGroup', 'permissionGroup')
+ .where('page.appVersionId IN(:...versionId)', {
versionId: appVersions.map((v) => v.id),
})
- .orderBy('pages.created_at', 'ASC')
+ .orderBy('page.created_at', 'ASC')
.getMany();
+ const pagesWithPermissionGroups = pages.map((page) => {
+ const groupPermission = page.permissions.find((perm) => perm.type === 'GROUP');
+
+ return {
+ ...page,
+ permissions: groupPermission
+ ? {
+ permissionGroup: groupPermission.users
+ .map((user) => user.permissionGroup?.name)
+ .filter((name): name is string => Boolean(name)),
+ }
+ : undefined,
+ };
+ });
+
const components =
pages.length > 0
? await manager
@@ -214,7 +237,7 @@ export class AppImportExportService {
.getMany();
appToExport['components'] = components;
- appToExport['pages'] = pages;
+ appToExport['pages'] = pagesWithPermissionGroups;
appToExport['events'] = events;
appToExport['dataQueries'] = dataQueries;
appToExport['dataSources'] = dataSources;
@@ -812,6 +835,10 @@ export class AppImportExportService {
});
}
+ if (page.permissions) {
+ pageCreated.permissions = page.permissions;
+ }
+
appResourceMappings.pagesMapping[page.id] = pageCreated.id;
isHomePage = importingAppVersion.homePageId === page.id;
@@ -820,6 +847,9 @@ export class AppImportExportService {
updateHomepageId = pageCreated.id;
}
+ //create page permissions of page if flag enabled in dto
+ await this.createPagePermissionsForGroups(pageCreated, user.organizationId, manager);
+
const pageComponents = importingComponents.filter((component) => component.pageId === page.id);
const newComponentIdsMap = {};
@@ -936,6 +966,7 @@ export class AppImportExportService {
});
}
}
+
// relink page groups
const updateArr = [];
for (const { pageId, groupId } of pageGroupIdArr) {
@@ -1327,6 +1358,76 @@ export class AppImportExportService {
return pageSettings;
}
+ async checkIfGroupPermissionsExist(pages, organizationId) {
+ const allGroupNames = new Set();
+
+ for (const page of pages) {
+ const groupNames = page.permissions?.permissionGroup || [];
+ for (const name of groupNames) {
+ allGroupNames.add(name);
+ }
+ }
+
+ if (!allGroupNames.size) return;
+
+ return await dbTransactionWrap(async (manager: EntityManager) => {
+ const existingGroups = await manager
+ .createQueryBuilder(GroupPermissions, 'gp')
+ .where('gp.name IN (:...names)', { names: Array.from(allGroupNames) })
+ .andWhere('gp.organizationId = :organizationId', { organizationId })
+ .select(['gp.name'])
+ .getMany();
+
+ const existingGroupNames = new Set(existingGroups.map((g) => g.name));
+
+ const missingGroups = Array.from(allGroupNames).filter((name) => !existingGroupNames.has(name));
+
+ if (missingGroups.length > 0) {
+ throw new HttpException(
+ {
+ message: { type: APP_ERROR_TYPE.IMPORT_EXPORT_SERVICE.PERMISSION_CHECK, data: missingGroups },
+ },
+ HttpStatus.BAD_REQUEST
+ );
+ }
+ });
+ }
+
+ async createPagePermissionsForGroups(page, organizationId: string, manager: EntityManager) {
+ const groupNames = page.permissions?.permissionGroup || [];
+ if (!groupNames.length) return;
+
+ const existingGroups = await manager
+ .createQueryBuilder(GroupPermissions, 'gp')
+ .where('gp.name IN (:...names)', { names: groupNames })
+ .andWhere('gp.organizationId = :organizationId', { organizationId })
+ .getMany();
+
+ const groupMap = new Map(existingGroups.map((g) => [g.name, g]));
+
+ // Filter to only existing group names
+ const validGroupNames = groupNames.filter((name) => groupMap.has(name));
+
+ // If no valid group names exist, do not create permissions
+ if (!validGroupNames.length) return;
+
+ const permission = manager.create(PagePermission, {
+ pageId: page.id,
+ type: PAGE_PERMISSION_TYPE.GROUP,
+ });
+
+ const savedPermission = await manager.save(permission);
+
+ const pageUsers = validGroupNames.map((name) =>
+ manager.create(PageUser, {
+ pagePermissionsId: savedPermission.id,
+ permissionGroupsId: groupMap.get(name).id,
+ })
+ );
+
+ await manager.save(pageUsers);
+ }
+
async createAppVersionsForImportedApp(
manager: EntityManager,
user: User,
diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts
index fcc01e52f0..c6ef31f5b8 100644
--- a/server/src/modules/apps/services/component.service.ts
+++ b/server/src/modules/apps/services/component.service.ts
@@ -12,7 +12,7 @@ const _ = require('lodash');
@Injectable()
export class ComponentsService implements IComponentsService {
- constructor(protected eventHandlerService: EventsService) {}
+ constructor(protected eventHandlerService: EventsService) { }
findOne(id: string): Promise {
return dbTransactionWrap((manager: EntityManager) => {
@@ -97,6 +97,7 @@ export class ComponentsService implements IComponentsService {
} else if (
(componentData.type === 'DropdownV2' ||
componentData.type === 'MultiselectV2' ||
+ componentData.type === 'ModuleContainer' ||
componentData.type === 'Steps') &&
_.isArray(objValue)
) {
diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts
index fa0b2864e0..02bea48dab 100644
--- a/server/src/modules/apps/services/page.service.ts
+++ b/server/src/modules/apps/services/page.service.ts
@@ -304,4 +304,8 @@ export class PageService implements IPageService {
return await this.pageHelperService.rearrangePagesOrderPostDeletion(pageExists, manager);
}, appVersionId);
}
+
+ async findModuleContainer(appVersionId: string): Promise {
+ return this.pageHelperService.findModuleContainer(appVersionId);
+ }
}
diff --git a/server/src/modules/apps/services/page.util.service.ts b/server/src/modules/apps/services/page.util.service.ts
index cb10863972..aface2cc16 100644
--- a/server/src/modules/apps/services/page.util.service.ts
+++ b/server/src/modules/apps/services/page.util.service.ts
@@ -79,4 +79,8 @@ export class PageHelperService implements IPageHelperService {
page.index = dto.index;
return page;
}
+
+ public async findModuleContainer(appVersionId: string): Promise {
+ return null;
+ }
}
diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js
index fdb4cf26df..c1ac0d205b 100644
--- a/server/src/modules/apps/services/widget-config/index.js
+++ b/server/src/modules/apps/services/widget-config/index.js
@@ -58,6 +58,8 @@ import { kanbanBoardConfig } from './kanbanBoard';
import { datetimePickerV2Config } from './datetimepickerV2';
import { datePickerV2Config } from './datepickerV2';
import { timePickerConfig } from './timepicker';
+import { moduleContainerConfig } from './moduleContainer';
+import { moduleViewerConfig } from './moduleViewer';
import { emailinputConfig } from './emailinput';
import { phoneinputConfig } from './phoneinput';
import {currencyinputConfig} from './currencyinput';
@@ -126,6 +128,8 @@ const widgets = {
linkConfig,
iconConfig,
boundedBoxConfig,
+ moduleContainerConfig,
+ moduleViewerConfig
};
const universalProps = {
diff --git a/server/src/modules/apps/services/widget-config/moduleContainer.js b/server/src/modules/apps/services/widget-config/moduleContainer.js
new file mode 100644
index 0000000000..af0f77c823
--- /dev/null
+++ b/server/src/modules/apps/services/widget-config/moduleContainer.js
@@ -0,0 +1,36 @@
+export const moduleContainerConfig = {
+ name: 'ModuleContainer',
+ displayName: 'Module Container',
+ description: 'Module Container',
+ component: 'ModuleContainer',
+ defaultSize: {
+ width: 10,
+ height: 400,
+ },
+ others: {
+ showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
+ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
+ },
+ properties: {
+ inputItems: { type: 'array', displayName: 'Input' },
+ outputItems: { type: 'array', displayName: 'Output' },
+ },
+ events: {},
+ styles: {},
+ exposedVariables: {},
+ actions: [],
+ definition: {
+ others: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{false}}' },
+ },
+ properties: {
+ inputItems: { value: [] },
+ outputItems: { value: [] },
+ },
+ events: [],
+ styles: {
+ backgroundColor: { value: '#fff' },
+ },
+ },
+};
diff --git a/server/src/modules/apps/services/widget-config/moduleViewer.js b/server/src/modules/apps/services/widget-config/moduleViewer.js
new file mode 100644
index 0000000000..b0f5342787
--- /dev/null
+++ b/server/src/modules/apps/services/widget-config/moduleViewer.js
@@ -0,0 +1,31 @@
+export const moduleViewerConfig = {
+ name: 'ModuleViewer',
+ displayName: 'Module',
+ description: 'Module',
+ component: 'ModuleViewer',
+ defaultSize: {
+ width: 10,
+ height: 400,
+ },
+ others: {
+ showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
+ showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
+ },
+ properties: {},
+ events: {},
+ styles: {},
+ exposedVariables: {},
+ actions: [],
+ definition: {
+ others: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{false}}' },
+ },
+ properties: {},
+ events: [],
+ styles: {
+ backgroundColor: { value: '#fff' },
+ },
+ },
+ };
+
\ No newline at end of file
diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts
index 3db7df4a99..ff8aaeda48 100644
--- a/server/src/modules/apps/util.service.ts
+++ b/server/src/modules/apps/util.service.ts
@@ -37,6 +37,8 @@ import { DataSourcesRepository } from '@modules/data-sources/repository';
import { IAppsUtilService } from './interfaces/IUtilService';
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
+import { Component } from 'src/entities/component.entity';
+import { Layout } from 'src/entities/layout.entity';
import { WorkspaceAppsResponseDto } from '@modules/external-apis/dto';
import { DataQuery } from '@entities/data_query.entity';
import { DataSource } from '@entities/data_source.entity';
@@ -52,7 +54,7 @@ export class AppsUtilService implements IAppsUtilService {
protected readonly abilityService: AbilityService,
protected readonly dataSourceRepository: DataSourcesRepository,
protected readonly dataSourceUtilService: DataSourcesUtilService
- ) {}
+ ) { }
async create(name: string, user: User, type: string, manager: EntityManager): Promise {
return await dbTransactionWrap(async (manager: EntityManager) => {
const app = await catchDbException(() => {
@@ -92,8 +94,52 @@ export class AppsUtilService implements IAppsUtilService {
})
);
+ if (type === 'module') {
+ const moduleContainer = await manager.save(
+ manager.create(Component, {
+ name: 'ModuleContainer',
+ type: 'ModuleContainer',
+ pageId: defaultHomePage.id,
+ properties: {
+ inputItems: { value: [] },
+ outputItems: { value: [] },
+ visibility: { value: '{{true}}' },
+ },
+ styles: {
+ backgroundColor: { value: '#fff' },
+ },
+ displayPreferences: {
+ showOnDesktop: { value: '{{true}}' },
+ showOnMobile: { value: '{{true}}' },
+ },
+ })
+ );
+
+ await manager.save(
+ manager.create(Layout, {
+ component: moduleContainer,
+ type: 'desktop',
+ top: 50,
+ left: 6,
+ height: 400,
+ width: 38,
+ })
+ );
+
+ await manager.save(
+ manager.create(Layout, {
+ component: moduleContainer,
+ type: 'mobile',
+ top: 50,
+ left: 6,
+ height: 400,
+ width: 38,
+ })
+ );
+ }
+
// Set default values for app version
- appVersion.showViewerNavigation = true;
+ appVersion.showViewerNavigation = type === 'module' ? false : true;
appVersion.homePageId = defaultHomePage.id;
appVersion.globalSettings = {
hideHeader: false,
@@ -179,8 +225,8 @@ export class AppsUtilService implements IAppsUtilService {
const processEnvironmentName = environmentName
? environmentName
: !isMultiEnvironmentEnabled
- ? 'development'
- : null;
+ ? 'development'
+ : null;
const environment: AppEnvironment = environmentId
? await this.appEnvironmentUtilService.get(organizationId, environmentId)
@@ -396,20 +442,24 @@ export class AppsUtilService implements IAppsUtilService {
const viewableApps = userAppPermissions.hideAll
? [null, ...userAppPermissions.editableAppsId]
: [
- null,
- ...Array.from(
- new Set([
- ...userAppPermissions.editableAppsId,
- ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
- ])
- ),
- ];
+ null,
+ ...Array.from(
+ new Set([
+ ...userAppPermissions.editableAppsId,
+ ...userAppPermissions.viewableAppsId.filter((id) => !userAppPermissions.hiddenAppsId.includes(id)),
+ ])
+ ),
+ ];
const viewableAppsQb = manager
.createQueryBuilder(AppBase, 'viewable_apps')
.innerJoin('viewable_apps.user', 'user')
.addSelect(['user.firstName', 'user.lastName'])
.where('viewable_apps.organizationId = :organizationId', { organizationId: user.organizationId });
+ if (type === 'module') {
+ viewableAppsQb.leftJoinAndSelect('viewable_apps.appVersions', 'versions');
+ }
+
if (type) viewableAppsQb.andWhere('viewable_apps.type = :type', { type: type });
if (searchKey) {
@@ -526,6 +576,43 @@ export class AppsUtilService implements IAppsUtilService {
return components;
}
+ async fetchModules(app: App, allVersions: boolean = false, versionId: string): Promise {
+ const versionToLoad = versionId
+ ? await this.versionRepository.findVersion(versionId)
+ : app.currentVersionId
+ ? await this.versionRepository.findVersion(app.currentVersionId)
+ : await this.versionRepository.findVersion(app.editingVersion?.id);
+
+ const modules = await dbTransactionWrap(async (manager) => {
+ const moduleComponents = await manager
+ .createQueryBuilder(Component, 'component')
+ .leftJoinAndSelect(Page, 'page', 'page.id = component.page_id')
+ .leftJoinAndSelect(AppVersion, 'app_version', 'app_version.id = page.app_version_id')
+ .leftJoinAndSelect(App, 'app', 'app.id = app_version.app_id')
+ .andWhere(
+ `component.type = :module ${allVersions ? '' : 'AND app_version.id = :appVersionId'} AND app.id = :appId`,
+ {
+ module: 'ModuleViewer',
+ appVersionId: versionToLoad.id,
+ appId: app.id,
+ }
+ )
+ .getMany();
+
+ const moduleAppIds = moduleComponents.map((moduleComponent) => moduleComponent.properties.moduleAppId.value);
+
+ const modules =
+ moduleAppIds.length > 0
+ ? await manager
+ .createQueryBuilder(App, 'app')
+ .where('app.id IN (:...moduleAppIds)', { moduleAppIds })
+ .distinct(true)
+ .getMany()
+ : [];
+ return modules;
+ });
+ return modules;
+ }
async findAllOrganizationApps(organizationId: string): Promise {
return await this.appRepository.findAllOrganizationApps(organizationId);
}
diff --git a/server/src/modules/import-export-resources/service.ts b/server/src/modules/import-export-resources/service.ts
index e8292c8fd7..e47205258e 100644
--- a/server/src/modules/import-export-resources/service.ts
+++ b/server/src/modules/import-export-resources/service.ts
@@ -69,6 +69,18 @@ export class ImportExportResourcesService {
let tableNameMapping = {};
const imports = { app: [], tooljet_database: [], tableNameMapping: {} };
const importingVersion = importResourcesDto.tooljet_version;
+ const skipPagePermissionsGroupCheck = importResourcesDto.skip_page_permissions_group_check;
+
+ if (!isEmpty(importResourcesDto.app) && !skipPagePermissionsGroupCheck) {
+ for (const appImportDto of importResourcesDto.app) {
+ let appParams = appImportDto.definition;
+ if (appParams?.appV2) {
+ appParams = { ...appParams.appV2 };
+ const pages = appParams?.pages;
+ pages?.length && (await this.appImportExportService.checkIfGroupPermissionsExist(pages, user.organizationId));
+ }
+ }
+ }
if (!isEmpty(importResourcesDto.tooljet_database)) {
const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning);
diff --git a/server/src/modules/modules/IModulesController.ts b/server/src/modules/modules/IModulesController.ts
new file mode 100644
index 0000000000..cd84b9c534
--- /dev/null
+++ b/server/src/modules/modules/IModulesController.ts
@@ -0,0 +1,6 @@
+import { User } from '@entities/user.entity';
+import { AppCreateDto } from '@modules/apps/dto';
+
+export interface IModulesController {
+ create(user: User, appCreateDto: AppCreateDto): Promise;
+}
diff --git a/server/src/modules/modules/constants/index.ts b/server/src/modules/modules/constants/index.ts
new file mode 100644
index 0000000000..4adfa4a519
--- /dev/null
+++ b/server/src/modules/modules/constants/index.ts
@@ -0,0 +1,4 @@
+export enum FEATURE_KEY {
+ CREATE_MODULE = 'create_module',
+ GET_MODULES = 'get_modules',
+}
diff --git a/server/src/modules/modules/module.ts b/server/src/modules/modules/module.ts
new file mode 100644
index 0000000000..a7af218b81
--- /dev/null
+++ b/server/src/modules/modules/module.ts
@@ -0,0 +1,54 @@
+import { DynamicModule, Module } from '@nestjs/common';
+import { getImportPath } from '@modules/app/constants';
+import { ThemesModule } from '@modules/organization-themes/module';
+import { FoldersModule } from '@modules/folders/module';
+import { FolderAppsModule } from '@modules/folder-apps/module';
+import { OrganizationsModule } from '@modules/organizations/module';
+import { AppEnvironmentsModule } from '@modules/app-environments/module';
+import { OrganizationRepository } from '@modules/organizations/repository';
+import { DataSourcesRepository } from '@modules/data-sources/repository';
+import { VersionRepository } from '@modules/versions/repository';
+import { DataSourcesModule } from '@modules/data-sources/module';
+import { AiModule } from '@modules/ai/module';
+import { AppsRepository } from '@modules/apps/repository';
+import { AppPermissionsModule } from '@modules/app-permissions/module';
+@Module({})
+export class ModulesModule {
+ static async register(configs: { IS_GET_CONTEXT: boolean }): Promise {
+ const importPath = await getImportPath(configs.IS_GET_CONTEXT);
+ const { ModulesController } = await import(`${importPath}/modules/modules.controller`);
+ const { AppsService } = await import(`${importPath}/apps/service`);
+ const { AppsUtilService } = await import(`${importPath}/apps/util.service`);
+ const { PageService } = await import(`${importPath}/apps/services/page.service`);
+ const { EventsService } = await import(`${importPath}/apps/services/event.service`);
+ const { ComponentsService } = await import(`${importPath}/apps/services/component.service`);
+ const { PageHelperService } = await import(`${importPath}/apps/services/page.util.service`);
+
+ return {
+ module: ModulesModule,
+ imports: [
+ await FolderAppsModule.register(configs),
+ await ThemesModule.register(configs),
+ await FoldersModule.register(configs),
+ await OrganizationsModule.register(configs),
+ await AppEnvironmentsModule.register(configs),
+ await DataSourcesModule.register(configs),
+ await AiModule.register(configs),
+ await AppPermissionsModule.register(configs),
+ ],
+ controllers: [ModulesController],
+ providers: [
+ AppsService,
+ VersionRepository,
+ AppsRepository,
+ PageService,
+ EventsService,
+ AppsUtilService,
+ ComponentsService,
+ PageHelperService,
+ OrganizationRepository,
+ DataSourcesRepository,
+ ],
+ };
+ }
+}
diff --git a/server/src/modules/modules/modules.controller.ts b/server/src/modules/modules/modules.controller.ts
new file mode 100644
index 0000000000..705d81dece
--- /dev/null
+++ b/server/src/modules/modules/modules.controller.ts
@@ -0,0 +1,20 @@
+import { Controller, Post, UseGuards, Body } from '@nestjs/common';
+import { User } from '@modules/app/decorators/user.decorator';
+import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard';
+import { AppCreateDto } from '@modules/apps/dto';
+import { IModulesController } from '@modules/modules/IModulesController';
+import { InitModule } from '@modules/app/decorators/init-module';
+import { MODULES } from '@modules/app/constants/modules';
+import { InitFeature } from '@modules/app/decorators/init-feature.decorator';
+import { FEATURE_KEY } from '@modules/modules/constants';
+
+@InitModule(MODULES.MODULES)
+@Controller('modules')
+export class ModulesController implements IModulesController {
+ @InitFeature(FEATURE_KEY.CREATE_MODULE)
+ @UseGuards(JwtAuthGuard)
+ @Post()
+ async create(@User() user, @Body() appCreateDto: AppCreateDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+}
diff --git a/server/src/modules/versions/module.ts b/server/src/modules/versions/module.ts
index 261401a18d..7fac84f033 100644
--- a/server/src/modules/versions/module.ts
+++ b/server/src/modules/versions/module.ts
@@ -9,6 +9,7 @@ import { DataSourcesModule } from '@modules/data-sources/module';
import { AppsRepository } from '@modules/apps/repository';
import { FeatureAbilityFactory } from './ability';
import { getImportPath } from '@modules/app/constants';
+import { AppPermissionsModule } from '@modules/app-permissions/module';
export class VersionModule {
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise {
@@ -33,6 +34,7 @@ export class VersionModule {
await DataSourcesModule.register(configs),
await AppEnvironmentsModule.register(configs),
await ThemesModule.register(configs),
+ await AppPermissionsModule.register(configs),
],
controllers: [ComponentsController, EventsController, PagesController, VersionController, VersionControllerV2],
providers: [
diff --git a/server/src/modules/versions/repository.ts b/server/src/modules/versions/repository.ts
index d67b56def1..110f41f2df 100644
--- a/server/src/modules/versions/repository.ts
+++ b/server/src/modules/versions/repository.ts
@@ -164,4 +164,20 @@ export class VersionRepository extends Repository {
return appVersion.app;
}, manager || this.manager);
}
+
+ async findVersionsFromApp(app: App, manager?: EntityManager): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const appVersions = await manager.find(AppVersion, {
+ where: { appId: app.id },
+ relations: [
+ 'app',
+ 'dataQueries',
+ 'dataQueries.dataSource',
+ 'dataQueries.plugins',
+ 'dataQueries.plugins.manifestFile',
+ ],
+ });
+ return appVersions;
+ }, manager || this.manager);
+ }
}
diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts
index 5ab17efd5c..84620d446c 100644
--- a/server/src/modules/versions/service.ts
+++ b/server/src/modules/versions/service.ts
@@ -108,62 +108,78 @@ export class VersionService implements IVersionService {
}
async getVersion(app: App, user: User): Promise {
- const versionId = app.appVersions[0].id;
- const appVersion = await this.versionRepository.findVersion(versionId);
-
- const pagesForVersion = await this.pageService.findPagesForVersion(versionId);
- const eventsForVersion = await this.eventsService.findEventsForVersion(versionId);
-
- const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion));
-
- if (
- appCurrentEditingVersion &&
- !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT))
- ) {
- const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId);
- appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id;
- }
-
- let shouldFreezeEditor = false;
- if (appCurrentEditingVersion) {
- const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT);
- if (hasMultiEnvLicense) {
- const currentEnvironment = await this.appEnvironmentUtilService.get(
- user.organizationId,
- appCurrentEditingVersion['currentEnvironmentId']
- );
- shouldFreezeEditor = currentEnvironment.priority > 1;
+ const prepareResponse = async (app: App, versionId: string) => {
+ let appVersion,
+ updatedVersionId = versionId;
+ if (updatedVersionId) {
+ appVersion = await this.versionRepository.findVersion(updatedVersionId);
} else {
+ appVersion = await this.versionRepository.findVersionsFromApp(app);
+ appVersion = appVersion[0];
+ updatedVersionId = appVersion.id;
+ }
+
+ const pagesForVersion = await this.pageService.findPagesForVersion(updatedVersionId);
+ const eventsForVersion = await this.eventsService.findEventsForVersion(updatedVersionId);
+
+ const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion));
+
+ if (
+ appCurrentEditingVersion &&
+ !(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT))
+ ) {
const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId);
appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id;
}
- }
- delete appCurrentEditingVersion['app'];
+ let shouldFreezeEditor = false;
+ if (appCurrentEditingVersion) {
+ const hasMultiEnvLicense = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT);
+ if (hasMultiEnvLicense) {
+ const currentEnvironment = await this.appEnvironmentUtilService.get(
+ user.organizationId,
+ appCurrentEditingVersion['currentEnvironmentId']
+ );
+ shouldFreezeEditor = currentEnvironment.priority > 1;
+ } else {
+ const developmentEnv = await this.appEnvironmentUtilService.getByPriority(user.organizationId);
+ appCurrentEditingVersion['currentEnvironmentId'] = developmentEnv.id;
+ }
+ }
- const appData = {
- ...app,
+ delete appCurrentEditingVersion['app'];
+
+ const appData = {
+ ...app,
+ };
+
+ delete appData['editingVersion'];
+
+ const editingVersion = camelizeKeys(appCurrentEditingVersion);
+
+ // Inject app theme
+ const appTheme = await this.organizationThemesUtilService.getTheme(
+ user.organizationId,
+ editingVersion?.globalSettings?.theme?.id
+ );
+
+ editingVersion['globalSettings']['theme'] = appTheme;
+
+ return {
+ ...appData,
+ editing_version: editingVersion,
+ pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion),
+ events: eventsForVersion,
+ should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor,
+ };
};
- delete appData['editingVersion'];
+ const response = await prepareResponse(app, app.appVersions?.[0]?.id);
+ const modules = await this.appUtilService.fetchModules(app, false, undefined);
- const editingVersion = camelizeKeys(appCurrentEditingVersion);
+ response['modules'] = await Promise.all(modules.map((module) => prepareResponse(module, undefined)));
- // Inject app theme
- const appTheme = await this.organizationThemesUtilService.getTheme(
- user.organizationId,
- editingVersion?.globalSettings?.theme?.id
- );
-
- editingVersion['globalSettings']['theme'] = appTheme;
-
- return {
- ...appData,
- editing_version: editingVersion,
- pages: this.appUtilService.mergeDefaultComponentData(pagesForVersion),
- events: eventsForVersion,
- should_freeze_editor: app.creationMode === 'GIT' || shouldFreezeEditor,
- };
+ return response;
}
async update(app: App, user: User, appVersionUpdateDto: AppVersionUpdateDto) {
diff --git a/server/src/modules/workflows/module.ts b/server/src/modules/workflows/module.ts
index 77dfbd0af3..389a754701 100644
--- a/server/src/modules/workflows/module.ts
+++ b/server/src/modules/workflows/module.ts
@@ -71,7 +71,6 @@ export class WorkflowsModule {
WorkflowExecutionNode,
WorkflowExecutionNode,
WorkflowExecutionEdge,
- RolesRepository,
]),
ThrottlerModule.forRootAsync({
imports: [ConfigModule],