From b2a6490e6b13c330be25b500a7b42955f0efbb33 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 3 Apr 2025 18:10:49 +0530 Subject: [PATCH 01/84] init: modules and added a tab on homepage --- frontend/src/App/App.jsx | 9 + frontend/src/HomePage/Folders.jsx | 23 +- frontend/src/HomePage/Footer.jsx | 4 +- frontend/src/HomePage/HomePage.jsx | 99 ++-- frontend/src/_components/AppModal.jsx | 9 +- frontend/src/_styles/modules.scss | 39 ++ frontend/src/_styles/theme.scss | 494 ++++++++++-------- .../components/AppTypeTab/AppTypeTab.jsx | 7 + .../dashboard/components/AppTypeTab/index.js | 1 + .../src/modules/dashboard/components/index.js | 2 + 10 files changed, 423 insertions(+), 264 deletions(-) create mode 100644 frontend/src/_styles/modules.scss create mode 100644 frontend/src/modules/dashboard/components/AppTypeTab/AppTypeTab.jsx create mode 100644 frontend/src/modules/dashboard/components/AppTypeTab/index.js diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 0c06b4c513..06cc061e0c 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -254,6 +254,15 @@ class AppComponent extends React.Component { }> }> }> + + + + } + /> {getAuditLogsRoutes(this.props)} { 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 ab50cbb05d..a94b322252 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -44,6 +44,7 @@ import { OrganizationList, UserGroupMigrationBanner, ConsultationBanner, + AppTypeTab, } from '@/modules/dashboard/components'; import CreateAppWithPrompt from '@/modules/AiBuilder/components/CreateAppWithPrompt'; @@ -236,10 +237,18 @@ class HomePageComponent extends React.Component { 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: 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.props.appType === 'workflow' ? 'Workflow' : this.props.appType === 'module' ? 'Module' : 'App' + } created successfully!` + ); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { @@ -839,6 +848,10 @@ class HomePageComponent extends React.Component { dependentPluginsForTemplate, dependentPluginsDetail, } = this.state; + + const invalidLicense = featureAccess?.licenseStatus?.isExpired || !featureAccess?.licenseStatus?.isLicenseValid; + // const invalidLicense = false; + const modalConfigs = { create: { modalType: 'create', @@ -1136,6 +1149,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({ + [this.props.appType === 'module' ? 'showCreateModuleModal' : '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' && ( = 100} + disabled={ + appsLimit?.percentage >= 100 || (this.props.appType === 'module' && invalidLicense) + } split className="d-inline" data-cy="import-dropdown-menu" @@ -1273,6 +1295,7 @@ class HomePageComponent extends React.Component { onSearchSubmit={this.onSearchSubmit} darkMode={this.props.darkMode} appType={this.props.appType} + disabled={this.props.appType === 'module' && invalidLicense} />
@@ -1300,24 +1323,37 @@ class HomePageComponent extends React.Component {
)} - {!isLoading && featuresLoaded && meta?.total_count === 0 && !currentFolder.id && !appSearchKey && ( - - )} + {!isLoading && + featuresLoaded && + meta?.total_count === 0 && + !currentFolder.id && + !appSearchKey && + (['front-end', 'modules'].includes(this.props.appType) ? ( + + ) : ( +

+ You have not created any modules.  + + Create a module  + + to start using it within your apps. +

+ ))} {!isLoading && apps?.length === 0 && appSearchKey && (
@@ -1341,7 +1377,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} /> )} @@ -1355,6 +1391,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 7d45e4c36e..318d7f5280 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 && (
'); - /* Add a custom arrow (you can use your own SVG) */ - background-repeat: no-repeat; - background-position: right center; - border: none; - /* Remove the default border */ - padding: 8px; - /* Adjust padding as needed */ - cursor: pointer; - /* Add pointer cursor for better usability */ - background: none; - padding: 0px; - height: 24px; - text-align: center; - color: var(--text-primary); - font-weight: 500; - width:auto; +.tj-daterangepicker-widget-month-selector, +.tj-daterangepicker-widget-year-selector { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + padding-right: 4px; + /* Add some padding on the right to create space for custom arrow */ + background-image: url('data:image/svg+xml;utf8,'); + /* Add a custom arrow (you can use your own SVG) */ + background-repeat: no-repeat; + background-position: right center; + border: none; + /* Remove the default border */ + padding: 8px; + /* Adjust padding as needed */ + cursor: pointer; + /* Add pointer cursor for better usability */ + background: none; + padding: 0px; + height: 24px; + text-align: center; + color: var(--text-primary); + font-weight: 500; + width: auto; } .datepicker-widget { - .react-datepicker-wrapper{ - width:100% !important; + .react-datepicker-wrapper { + width: 100% !important; } } @@ -17185,26 +17223,29 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-month-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__month-container { box-shadow: none !important; } - + .react-datepicker__month-text { - height:26px !important; + height: 26px !important; margin: 0px; - width:100% !important; + width: 100% !important; } - .react-datepicker__month-text--in-selecting-range, .react-datepicker__month-text--in-range { - border-radius:0px; + .react-datepicker__month-text--in-selecting-range, + .react-datepicker__month-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17217,45 +17258,49 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__month-text--selecting-range-start, .react-datepicker__month-text--selected, .react-datepicker__month-text--range-end { - border-radius:8px !important; + .react-datepicker__month-text--selecting-range-start, + .react-datepicker__month-text--selected, + .react-datepicker__month-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range, .react-datepicker__month-text--selecting-range-start + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--selecting-range-start+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range { + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - .react-datepicker__month-wrapper{ - gap:0px !important; - .react-datepicker__month-text--in-range:first-of-type, - .react-datepicker__month-text--in-selecting-range:first-of-type, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-range, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-wrapper { + gap: 0px !important; + + .react-datepicker__month-text--in-range:first-of-type, + .react-datepicker__month-text--in-selecting-range:first-of-type, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--in-range:last-of-type, - .react-datepicker__month-text--in-selecting-range:last-of-type, - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), - .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text){ + .react-datepicker__month-text--in-range:last-of-type, + .react-datepicker__month-text--in-selecting-range:last-of-type, + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17275,44 +17320,47 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-year-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__year-container { box-shadow: none !important; } - .react-datepicker__year-wrapper{ - gap:0px !important; + .react-datepicker__year-wrapper { + gap: 0px !important; - .react-datepicker__year-text--in-range:first-of-type, - .react-datepicker__year-text--in-selecting-range:first-of-type{ + .react-datepicker__year-text--in-range:first-of-type, + .react-datepicker__year-text--in-selecting-range:first-of-type { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--in-range:last-of-type, - .react-datepicker__year-text--in-selecting-range:last-of-type{ + .react-datepicker__year-text--in-range:last-of-type, + .react-datepicker__year-text--in-selecting-range:last-of-type { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } } - + .react-datepicker__year-text { - height:26px !important; + height: 26px !important; margin-top: 5px !important; - margin-bottom:5px !important; + margin-bottom: 5px !important; margin: 0px; - width:62px !important; + width: 62px !important; } - .react-datepicker__year-text--in-selecting-range, .react-datepicker__year-text--in-range { - border-radius:0px; + .react-datepicker__year-text--in-selecting-range, + .react-datepicker__year-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17325,31 +17373,35 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__year-text--selecting-range-start, .react-datepicker__year-text--selected, .react-datepicker__year-text--range-end { - border-radius:8px !important; + .react-datepicker__year-text--selecting-range-start, + .react-datepicker__year-text--selected, + .react-datepicker__year-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), + .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range, .react-datepicker__year-text--selecting-range-start + .react-datepicker__year-text--in-selecting-range{ + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range, + .react-datepicker__year-text--selecting-range-start+.react-datepicker__year-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range { + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - + + } .dark-theme { @@ -18610,6 +18662,7 @@ section.ai-message-prompt-input-wrapper { flex-grow: 1; } } + .workspace-constant-value { position: relative; @@ -18653,8 +18706,9 @@ section.ai-message-prompt-input-wrapper { font-style: normal; font-weight: 400; line-height: 18px; + &.dark { background: #FFFAEB !important; } } -} +} \ No newline at end of file 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, }; From 61dfb9299c81004152c90f80bed3c5b02e78427f Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Fri, 4 Apr 2025 17:40:21 +0530 Subject: [PATCH 02/84] Fixed bug on creating a app with correct type --- frontend/src/HomePage/HomePage.jsx | 44 +++++++++---------- .../BaseAppActionModal/BaseAppActionModal.jsx | 2 +- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index a94b322252..0b9a69aa71 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -100,7 +100,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations: {}, featuresLoaded: false, showCreateAppModal: false, - showCreateModuleModal: false, showCreateAppFromTemplateModal: false, showImportAppModal: false, showCloneAppModal: false, @@ -233,22 +232,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: type ?? this.props.appType, + 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' : this.props.appType === 'module' ? 'Module' : 'App' - } created successfully!` - ); + toast.success(`${this.getAppType()} created successfully!`); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { @@ -267,7 +267,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) { @@ -483,7 +483,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 @@ -766,11 +766,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 }); }; isWithinSevenDaysOfSignUp = (date) => { const currentDate = new Date().toISOString(); @@ -835,7 +835,6 @@ class HomePageComponent extends React.Component { importingGitAppOperations, featuresLoaded, showCreateAppModal, - showCreateModuleModal, showImportAppModal, fileContent, fileName, @@ -856,10 +855,10 @@ class HomePageComponent extends React.Component { 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, }, @@ -904,7 +903,6 @@ class HomePageComponent extends React.Component { @@ -1171,7 +1169,7 @@ class HomePageComponent extends React.Component { className={`create-new-app-button col-11 ${creatingApp ? 'btn-loading' : ''}`} onClick={() => this.setState({ - [this.props.appType === 'module' ? 'showCreateModuleModal' : 'showCreateAppModal']: true, + showCreateAppModal: true, }) } data-cy="create-new-app-button" @@ -1189,7 +1187,7 @@ class HomePageComponent extends React.Component { )} - {this.props.appType !== 'workflow' && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && ( = 100 || (this.props.appType === 'module' && invalidLicense) @@ -1283,7 +1281,7 @@ class HomePageComponent extends React.Component { !appSearchKey && )} - {this.props.appType !== 'workflow' && this.canCreateApp() && ( + {this.props.appType !== 'workflow' && this.props.appType !== 'module' && this.canCreateApp() && ( )} @@ -1328,7 +1326,7 @@ class HomePageComponent extends React.Component { meta?.total_count === 0 && !currentFolder.id && !appSearchKey && - (['front-end', 'modules'].includes(this.props.appType) ? ( + (['front-end', 'workflow'].includes(this.props.appType) ? ( { const getActiveConfig = () => { switch (true) { - case modalStates.showCreateAppModal || modalStates.showCreateModuleModal: + case modalStates.showCreateAppModal: return configs.create; case modalStates.showCloneAppModal: return configs.clone; From 3ba4affe6d7ef630a429eff25c0e9ac9d3f286c5 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Wed, 16 Apr 2025 17:37:24 +0530 Subject: [PATCH 03/84] Left sidebar inspector base --- frontend/package-lock.json | 12 +++ frontend/package.json | 3 +- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 80 +++++++++++++++ .../LeftSidebarInspector.jsx | 99 +++++++++---------- .../LeftSidebar/LeftSidebarInspector/utils.js | 78 +++++++++++++++ frontend/src/_styles/theme.scss | 55 +++++++++++ 6 files changed, 276 insertions(+), 51 deletions(-) create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2db11e6493..56c9e6c5c1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -89,6 +89,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -41074,6 +41075,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-accessible-treeview": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/react-accessible-treeview/-/react-accessible-treeview-2.11.1.tgz", + "integrity": "sha512-lFegHjFJp2OvtoHMtbIqjby7N3MGDRASlbJsMLqElxQHwZ97xIYho2S4QvXKK7l3/nII0IKDQFJXZNBj6ecG3g==", + "peerDependencies": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-base16-styling": { "version": "0.9.1", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index 45d94532f5..789fdae8df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -84,6 +84,7 @@ "query-string": "^8.1.0", "rc-slider": "^10.1.1", "react": "^18.2.0", + "react-accessible-treeview": "^2.11.1", "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.6.5", "react-bootstrap": "^2.7.2", @@ -263,4 +264,4 @@ "jsx" ] } -} \ No newline at end of file +} diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx new file mode 100644 index 0000000000..87194f3ca2 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import TreeView, { flattenTree } from 'react-accessible-treeview'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ToolTip } from '@/_components/ToolTip'; +import { extractComponentName } from './utils'; + +const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, callbackActions = [] }) => { + const flattendedData = flattenTree(data); + + const renderNodeIcons = (node) => { + const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; + if (icon && icon?.iconPath) { + return ( + + ); + } + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx()}
+
+ ); + } + return icon.jsx(); + } + }; + + return ( + { + const { element, getNodeProps, level, handleSelect, handleExpand, isExpanded, isDisabled, isBranch } = props; + const nodeIcon = renderNodeIcons(element.name); + const metadata = element.metadata || {}; + const { type } = metadata; + + const actions = callbackActions.filter((action) => [type, 'all'].includes(action.for)); + + return ( +
+ {(isBranch || level === 1) && ( +
+ {isExpanded ? ( + + ) : ( + + )} +
+ )} + {nodeIcon &&
{nodeIcon}
} +
+ {element.name} +
+
+ ); + }} + /> + ); +}; + +export default JSONTreeViewerV2; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 3adca4be98..6cbd53ed8f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -3,19 +3,12 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { HeaderSection } from '@/_ui/LeftSidebar'; import JSONTreeViewer from '@/_ui/JSONTreeViewer'; +import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; import useCallbackActions from './useCallbackActions'; - -const sortAndReduce = (obj) => { - return Object.entries(obj) - .sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })) - .reduce((acc, [name, value]) => { - acc[name] = value; - return acc; - }, {}); -}; +import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); @@ -33,59 +26,59 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { exposedQueries, }); const callbackActions = useCallbackActions(); + console.log('callbackActions', callbackActions); const sortedComponents = useMemo(() => { - return Object.entries(componentIdNameMapping) - .map(([key, name]) => ({ - key, - name: name || key, - value: exposedComponentsVariables[key] ?? { id: key }, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { key, name, value }) => { - acc[name] = { ...value, id: key }; - return acc; - }, {}); + return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables); }, [exposedComponentsVariables, componentIdNameMapping]); const sortedQueries = useMemo(() => { - // Create a reverse mapping for faster lookups - const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); - - const _sortedQueries = Object.entries(exposedQueries) - .map(([key, value]) => ({ - key, - name: reverseMapping[key] || key, - value, - })) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })) - .reduce((acc, { name, value }) => { - acc[name] = value; - return acc; - }, {}); - return _sortedQueries; + return formatInspectorQueryData(queryNameIdMapping, exposedQueries); }, [exposedQueries, queryNameIdMapping]); - const sortedVariables = useMemo(() => sortAndReduce(exposedVariables), [exposedVariables]); + const sortedVariables = useMemo(() => formatInspectorDataMisc(exposedVariables), [exposedVariables]); - const sortedConstants = useMemo(() => sortAndReduce(exposedConstants), [exposedConstants]); + const sortedConstants = useMemo(() => formatInspectorDataMisc(exposedConstants), [exposedConstants]); - const sortedPageVariables = useMemo(() => sortAndReduce(exposedPageVariables), [exposedPageVariables]); + const sortedPageVariables = useMemo(() => formatInspectorDataMisc(exposedPageVariables), [exposedPageVariables]); - const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]); + const sortedGlobalVariables = useMemo( + () => formatInspectorDataMisc(exposedGlobalVariables), + [exposedGlobalVariables] + ); const memoizedJSONData = React.useMemo(() => { - const jsontreeData = {}; - - jsontreeData['queries'] = sortedQueries; - jsontreeData['components'] = sortedComponents; - jsontreeData['globals'] = sortedGlobalVariables; - jsontreeData['variables'] = sortedVariables; - jsontreeData['page'] = sortedPageVariables; - jsontreeData['constants'] = sortedConstants; + const jsontreeData = { + name: '', + children: [ + { + name: 'Queries', + children: sortedQueries, + }, + { + name: 'Components', + children: sortedComponents, + }, + { + name: 'Globals', + children: sortedGlobalVariables, + }, + { + name: 'Variables', + children: sortedVariables, + }, + { + name: 'Page', + children: sortedPageVariables, + }, + { + name: 'Constants', + children: sortedConstants, + }, + ], + }; return jsontreeData; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); const handleNodeExpansion = (path, data, currentNode) => { @@ -128,7 +121,13 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
- + {/* { // selectedComponent={selectedComponent} treeType="inspector" darkMode={darkMode} - /> + /> */}
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js new file mode 100644 index 0000000000..5fc6176bf4 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -0,0 +1,78 @@ +export const formatInspectorDataMisc = (obj) => { + if (typeof obj !== 'object' || obj === null) return []; + const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data.reduce((acc, [name, value]) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'misc' } }]; + }, []); + }; + + return reduceData(data); +}; + +export const formatInspectorComponentData = (componentIdNameMapping, exposedComponentsVariables) => { + const data = Object.entries(componentIdNameMapping) + .map(([key, name]) => ({ + key, + name: name || key, + value: exposedComponentsVariables[key] ?? { id: key }, + })) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data + .filter((item) => item.name) + .reduce((acc, { key, name, value }) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'components' } }]; + }, []); + }; + + return reduceData(data); +}; + +export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => { + const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); + const _sortedQueries = Object.entries(exposedQueries) + .map(([key, value]) => ({ + key, + name: reverseMapping[key] || key, + value, + })) + .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + + const reduceData = (obj) => { + let data = obj; + if (!obj || typeof obj !== 'object') return []; + else if (!Array.isArray(obj)) { + data = Object.entries(obj); + } + return data + .filter((item) => item.name) + .reduce((acc, { id, name, value }) => { + return [...acc, { name, children: reduceData(value), metadata: { type: 'queries' } }]; + }, []); + }; + + return reduceData(_sortedQueries); +}; + +export const extractComponentName = (path) => { + // Match the last part of the URL before ".svg" using a regular expression + const match = path.match(/\/([^/]+)\.svg$/); + + if (match && match[1]) { + return match[1]; // Return the matched component name + } else { + return null; // Return null if the pattern doesn't match + } +}; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 4fd1083987..0bc2abcdd3 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18875,4 +18875,59 @@ section.ai-message-prompt-input-wrapper { .cm-editor { max-height: 100px !important; } +} + + +.basic.tree { + list-style: none; + margin: 0; + padding: 23.4px; +} +.basic .tree-node, +.basic .tree-node-group { + list-style: none; + margin: 0; + padding: 0; +} + +.basic .tree-branch-wrapper, +.basic .tree-node__leaf { + outline: none; +} + +// .basic .tree-node--focused { +// outline-color: rgb(77, 144, 254); +// outline-style: auto; +// outline-width: 2px; +// display: block; +// } + +.basic .tree-node__branch { + display: block; +} + +.basic .tree-node { + cursor: pointer; +} + +.node-expansion-icon { + width:20px; + height:20px; + margin-right: 2px; + display: flex; + align-items: center; + justify-content: center; +} + +.node-icon { + display: flex; + align-items: center; + justify-content: center; + margin-right: 6px; +} + +.node-label { + display: flex; + align-items: center; + font-size:12px; } \ No newline at end of file From 12428260ab9ba648fbbd23af0bb593ee706630b8 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Sat, 19 Apr 2025 16:22:26 +0530 Subject: [PATCH 04/84] Added modules container --- .../assets/images/icons/module-editor.svg | 3 + .../modules/blank-module-list-icon-1.svg | 3 + .../modules/blank-module-list-icon-2.svg | 3 + .../assets/images/modules/blank-module.svg | 3 + frontend/ee | 2 +- frontend/src/AppBuilder/AppBuilder.jsx | 13 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 72 ++++-- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 31 ++- .../src/AppBuilder/AppCanvas/Container.jsx | 71 +++++- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 8 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 2 + .../AppBuilder/AppCanvas/appCanvasUtils.js | 36 ++- .../AppBuilder/CodeBuilder/Elements/Query.jsx | 100 ++++++++ .../src/AppBuilder/CodeBuilder/TypeMapping.js | 1 + .../CodeEditor/DynamicFxTypeRenderer.jsx | 2 + .../CodeEditor/SingleLineCodeEditor.jsx | 49 ++-- frontend/src/AppBuilder/CodeEditor/utils.js | 3 + .../src/AppBuilder/Header/EditorHeader.jsx | 24 +- .../RightTopHeaderButtons.jsx | 4 +- .../LeftSidebar/GlobalSettings/index.jsx | 19 +- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 137 +++++----- .../LeftSidebarInspector.jsx | 15 +- .../ComponentConfigurationTab.jsx | 3 +- .../ComponentModuleTab/ComponentModuleTab.jsx | 32 +++ .../ComponentModuleTab/index.js | 1 + .../ComponentModuleTab/styles.scss | 19 ++ .../ComponentsManagerTab.jsx | 24 +- .../DragLayer.jsx | 13 +- .../constants.js | 0 .../RightSideBar/ComponentManagerTab/index.js | 1 + .../ComponentsManagerTab/index.js | 1 - .../Inspector/Components/DefaultComponent.jsx | 2 +- .../RightSideBar/Inspector/Elements/Code.jsx | 17 +- .../RightSideBar/Inspector/Inspector.jsx | 140 +++++----- .../RightSideBar/Inspector/Utils.js | 14 +- .../AppBuilder/RightSideBar/RightSideBar.jsx | 8 +- frontend/src/AppBuilder/Viewer/Viewer.jsx | 97 ++++--- .../WidgetManager/configs/widgetConfig.js | 4 + .../AppBuilder/WidgetManager/widgets/index.js | 4 + .../WidgetManager/widgets/moduleContainer.js | 36 +++ .../WidgetManager/widgets/moduleViewer.js | 30 +++ .../AppBuilder/_contexts/ModuleContext.jsx | 23 +- .../src/AppBuilder/_helpers/editorHelpers.js | 5 + frontend/src/AppBuilder/_hooks/useAppData.js | 62 ++++- frontend/src/AppBuilder/_stores/ast.js | 9 +- .../_stores/slices/DependencyClass.js | 2 +- .../src/AppBuilder/_stores/slices/appSlice.js | 1 - .../_stores/slices/componentsSlice.js | 11 +- .../AppBuilder/_stores/slices/eventsSlice.js | 1 - .../AppBuilder/_stores/slices/moduleSlice.js | 5 + .../_stores/slices/resolvedSlice.js | 45 +++- frontend/src/AppBuilder/_stores/store.js | 2 + frontend/src/AppBuilder/_stores/utils.js | 4 +- frontend/src/AppLoader/AppLoader.jsx | 10 +- .../Editor/ControlledComponentToRender.jsx | 4 +- frontend/src/_helpers/editorHelpers.js | 239 +++++++++--------- frontend/src/_services/apps.service.js | 24 +- frontend/src/_styles/theme.scss | 19 +- .../_ui/Icon/solidIcons/EmptyStateModules.jsx | 25 ++ frontend/src/_ui/Icon/solidIcons/index.js | 3 + .../ModuleContainer/ModuleContainer.jsx | 8 + .../components/ModuleContainer/index.js | 1 + .../ModuleContainerBlank.jsx | 8 + .../components/ModuleContainerBlank/index.js | 1 + .../ModuleContainerInspector.jsx | 8 + .../ModuleContainerInspector/index.js | 1 + .../ModuleEditorBanner/ModuleEditorBanner.jsx | 8 + .../components/ModuleEditorBanner/index.js | 1 + .../ModuleManager/ModuleManager.jsx | 8 + .../Modules/components/ModuleManager/index.js | 1 + .../components/ModuleViewer/ModuleViewer.jsx | 8 + .../Modules/components/ModuleViewer/index.js | 1 + .../ModuleViewerInspector.jsx | 8 + .../components/ModuleViewerInspector/index.js | 1 + .../ModuleWidgetBox/ModuleWidgetBox.jsx | 8 + .../components/ModuleWidgetBox/index.js | 1 + .../src/modules/Modules/components/index.js | 19 ++ frontend/src/modules/Modules/index.js | 2 + frontend/src/modules/index.js | 2 + server/ee | 2 +- server/src/modules/app/constants/modules.ts | 1 + server/src/modules/app/module.ts | 2 + .../apps/services/component.service.ts | 4 +- .../apps/services/widget-config/index.js | 4 + .../services/widget-config/moduleContainer.js | 36 +++ .../services/widget-config/moduleViewer.js | 31 +++ .../src/modules/modules/IModulesController.ts | 6 + server/src/modules/modules/constants/index.ts | 4 + server/src/modules/modules/module.ts | 52 ++++ .../src/modules/modules/modules.controller.ts | 20 ++ 90 files changed, 1352 insertions(+), 446 deletions(-) create mode 100644 frontend/assets/images/icons/module-editor.svg create mode 100644 frontend/assets/images/modules/blank-module-list-icon-1.svg create mode 100644 frontend/assets/images/modules/blank-module-list-icon-2.svg create mode 100644 frontend/assets/images/modules/blank-module.svg create mode 100644 frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/ComponentsManagerTab.jsx (90%) rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/DragLayer.jsx (80%) rename frontend/src/AppBuilder/RightSideBar/{ComponentsManagerTab => ComponentManagerTab}/constants.js (100%) create mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js delete mode 100644 frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js create mode 100644 frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js create mode 100644 frontend/src/AppBuilder/_stores/slices/moduleSlice.js create mode 100644 frontend/src/_ui/Icon/solidIcons/EmptyStateModules.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainer/ModuleContainer.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainer/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleContainerBlank/ModuleContainerBlank.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainerBlank/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleContainerInspector/ModuleContainerInspector.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleContainerInspector/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleEditorBanner/ModuleEditorBanner.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleEditorBanner/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleManager/ModuleManager.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleManager/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleViewer/ModuleViewer.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleViewer/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleViewerInspector/ModuleViewerInspector.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleViewerInspector/index.js create mode 100644 frontend/src/modules/Modules/components/ModuleWidgetBox/ModuleWidgetBox.jsx create mode 100644 frontend/src/modules/Modules/components/ModuleWidgetBox/index.js create mode 100644 frontend/src/modules/Modules/components/index.js create mode 100644 frontend/src/modules/Modules/index.js create mode 100644 server/src/modules/apps/services/widget-config/moduleContainer.js create mode 100644 server/src/modules/apps/services/widget-config/moduleViewer.js create mode 100644 server/src/modules/modules/IModulesController.ts create mode 100644 server/src/modules/modules/constants/index.ts create mode 100644 server/src/modules/modules/module.ts create mode 100644 server/src/modules/modules/modules.controller.ts diff --git a/frontend/assets/images/icons/module-editor.svg b/frontend/assets/images/icons/module-editor.svg new file mode 100644 index 0000000000..e0b55223c3 --- /dev/null +++ b/frontend/assets/images/icons/module-editor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-1.svg b/frontend/assets/images/modules/blank-module-list-icon-1.svg new file mode 100644 index 0000000000..1f9c188e27 --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-1.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module-list-icon-2.svg b/frontend/assets/images/modules/blank-module-list-icon-2.svg new file mode 100644 index 0000000000..4ca21df25a --- /dev/null +++ b/frontend/assets/images/modules/blank-module-list-icon-2.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/modules/blank-module.svg b/frontend/assets/images/modules/blank-module.svg new file mode 100644 index 0000000000..35207b8ca1 --- /dev/null +++ b/frontend/assets/images/modules/blank-module.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/ee b/frontend/ee index 715a830c7a..45c8130ae5 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69 +Subproject commit 45c8130ae5ee82abdfe1843c25dbdf694aac3433 diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 30b0d6d97b..48b280aa7c 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -22,10 +22,11 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; // const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel')); // TODO: split Loader into separate component and remove editor loading state from Editor -export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode }) => { +export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType }) => { useAppData(appId, moduleId, darkMode); const isEditorLoading = useStore((state) => state.isEditorLoading); const currentMode = useStore((state) => state.currentMode); + const isModuleEditor = appType === 'module'; const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode); @@ -46,15 +47,15 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
Loading...
}> - - + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - + + - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 365c3bbc59..e8183cc535 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Container } from './Container'; import Grid from './Grid'; import { EditorSelecto } from './Selecto'; -import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleId, useIsModuleMode } from '@/AppBuilder/_contexts/ModuleContext'; import { HotkeyProvider } from './HotkeyProvider'; import './appCanvas.scss'; import useStore from '@/AppBuilder/_stores/store'; @@ -18,7 +18,7 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); const canvasHeight = useStore((state) => state.canvasHeight); @@ -41,6 +41,10 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); + const moduleId = useModuleId(); + const isModuleMode = useIsModuleMode(); + + const hideSidebar = isModuleMode || isPagesSidebarHidden; useEffect(() => { // Need to remove this if we shift setExposedVariable Logic outside of components @@ -60,6 +64,45 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { return () => window.removeEventListener('resize', handleResize); }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]); + const styles = useMemo(() => { + const canvasBgColor = + currentMode === 'view' + ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) + : !isAppDarkMode + ? '#EBEBEF' + : '#2F3C4C'; + + if (isModuleMode) { + return { + borderLeft: 'none', + height: '100%', + background: canvasBgColor, + }; + } + + return { + borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', + height: currentMode === 'edit' ? canvasContainerHeight : '100%', + background: canvasBgColor, + marginLeft: + isViewerSidebarPinned && !hideSidebar && currentLayout !== 'mobile' && currentMode !== 'edit' + ? pageSidebarStyle === 'icon' + ? '65px' + : '210px' + : 'auto', + }; + }, [ + currentMode, + isAppDarkMode, + isModuleMode, + editorMarginLeft, + canvasContainerHeight, + isViewerSidebarPinned, + hideSidebar, + currentLayout, + pageSidebarStyle, + ]); + return (
{ { 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned }, { 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' } )} - style={{ - // transform: `scale(1)`, - borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid', - height: currentMode === 'edit' ? canvasContainerHeight : '100%', - background: - currentMode === 'view' - ? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor) - : !isAppDarkMode - ? '#EBEBEF' - : '#2F3C4C', - marginLeft: - isViewerSidebarPinned && !isPagesSidebarHidden && currentLayout !== 'mobile' && currentMode !== 'edit' - ? pageSidebarStyle === 'icon' - ? '65px' - : '210px' - : 'auto', - }} + style={styles} >
{ {environmentLoadingState !== 'loading' && (
{ canvasMaxWidth={canvasMaxWidth} isViewerSidebarPinned={isViewerSidebarPinned} pageSidebarStyle={pageSidebarStyle} + appType={appType} />
)} {currentMode === 'view' || (currentLayout === 'mobile' && isAutoMobileLayout) ? null : ( - + )}
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 4c558f3cc2..ad972f7e90 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -18,6 +18,7 @@ export const ConfigHandle = ({ showHandle, componentType, visibility, + hideDelete, }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow); @@ -123,20 +124,22 @@ export const ConfigHandle = ({ data-cy={`${componentName.toLowerCase()}-inspect-button`} className="config-handle-inspect" /> - { - deleteComponents([id]); - }} - data-cy={`${componentName.toLowerCase()}-delete-button`} - > - - + {!hideDelete && ( + { + deleteComponents([id]); + }} + data-cy={`${componentName.toLowerCase()}-delete-button`} + > + + + )}
)} diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index e622e1a2cd..6a5afd83e2 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -20,7 +20,7 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; - +import { ModuleContainerBlank } from '@/modules/Modules/components'; //TODO: Revisit the logic of height (dropRef) /* @@ -44,6 +44,7 @@ export const Container = React.memo( isViewerSidebarPinned, pageSidebarStyle, componentType, + appType, }) => { const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); @@ -63,13 +64,13 @@ export const Container = React.memo( }, [componentType, index, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ - accept: 'box', + accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', hover: (item) => { item.canvasRef = realCanvasRef?.current; item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); }, - drop: async ({ componentType }, monitor) => { + drop: async ({ componentType, component }, monitor) => { const didDrop = monitor.didDrop(); if (didDrop) return; if (componentType === 'PDF' && !isPDFSupported()) { @@ -78,15 +79,40 @@ export const Container = React.memo( ); return; } + + const moduleInfo = component?.moduleId + ? { + moduleId: component.moduleId, + versionId: component.versionId, + environmentId: component.environmentId, + moduleName: component.displayName, + moduleContainer: component.moduleContainer, + } + : undefined; + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { - const parentComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const parentComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); const newComponents = [parentComponent, ...childComponents]; await addComponentToCurrentPage(newComponents); // setSelectedComponents([parentComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } else { - const newComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id); + const newComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); await addComponentToCurrentPage([newComponent]); // setSelectedComponents([newComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); @@ -97,7 +123,11 @@ export const Container = React.memo( }), }); - const showEmptyContainer = currentMode === 'edit' && id === 'canvas' && components.length === 0 && !isOverCurrent; + const showEmptyContainer = + currentMode === 'edit' && + (id === 'canvas' || componentType === 'ModuleContainer') && + components.length === 0 && + !isOverCurrent; function getContainerCanvasWidth() { if (canvasWidth !== undefined) { @@ -146,6 +176,24 @@ export const Container = React.memo( [setLastCanvasClickPosition] ); + /* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. + Hence display is set to none on dragover and removed only when the component is added */ + + const renderEmptyContainer = () => { + if (components && components?.length !== 0) return; + + const styles = { + display: showEmptyContainer ? 'block' : 'none', + ...(componentType === 'ModuleContainer' ? { height: '100%', width: '100%' } : {}), + }; + + return ( +
+ {componentType === 'ModuleContainer' ? : } +
+ ); + }; + return (
{ @@ -213,14 +263,7 @@ export const Container = React.memo( /> ))}
- - {/* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag. - Hence display is set to none on dragover and removed only when the component is added */} - {(!components || components?.length === 0) && ( -
- -
- )} + {renderEmptyContainer()}
); } diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 44f148a000..abddc4681f 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -37,7 +37,7 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout }) { +export default function Grid({ gridWidth, currentLayout, appType }) { const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); @@ -329,6 +329,11 @@ export default function Grid({ gridWidth, currentLayout }) { }; const isComponentVisible = (id) => { + // Return TRUE if it is a module container + if (appType === 'module') { + return true; + } + const component = getResolvedComponent(id); let visibility; if (isArray(component)) { @@ -641,6 +646,7 @@ export default function Grid({ gridWidth, currentLayout }) { }} onResizeEnd={(e) => { try { + console.log('end--- e.target.id', e.target.id); useGridStore.getState().actions.setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 0f1cb3359f..ac60a5790f 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -91,6 +91,8 @@ const WidgetWrapper = memo( showHandle={isWidgetActive} componentType={componentType} visibility={visibility} + customClassName={componentType === 'ModuleContainer' ? 'module-container' : ''} + hideDelete={componentType === 'ModuleContainer'} /> )} { +export const addNewWidgetToTheEditor = ( + componentType, + eventMonitorObject, + currentLayout, + realCanvasRef, + parentId, + moduleInfo = undefined +) => { const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); @@ -42,6 +49,25 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas']; const width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth); + let customLayouts = undefined; + + if (moduleInfo) { + componentData.definition.properties.moduleAppId = { value: moduleInfo.moduleId }; + componentData.definition.properties.moduleVersionId = { value: moduleInfo.versionId }; + componentData.definition.properties.moduleEnvironmentId = { value: moduleInfo.environmentId }; + customLayouts = moduleInfo.moduleContainer.layouts; + + const inputItems = Object.values( + moduleInfo.moduleContainer.component.definition.properties?.input_items?.value ?? {} + ); + + console.log('inputItems--- ', inputItems); + + for (const { name, default_value } of inputItems) { + componentData.definition.properties[name] = { value: default_value }; + } + } + if (currentLayout === 'mobile') { componentData.definition.others.showOnDesktop.value = `{{false}}`; componentData.definition.others.showOnMobile.value = `{{true}}`; @@ -59,14 +85,14 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre [currentLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[currentLayout].width : width, + height: customLayouts ? customLayouts[currentLayout].height : defaultHeight, }, [nonActiveLayout]: { top: top, left: left, - width, - height: defaultHeight, + width: customLayouts ? customLayouts[nonActiveLayout].width : width, + height: customLayouts ? customLayouts[nonActiveLayout].height : defaultHeight, }, }, withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component), diff --git a/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx new file mode 100644 index 0000000000..f182a5cbb9 --- /dev/null +++ b/frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx @@ -0,0 +1,100 @@ +import React, { useCallback, useMemo } from 'react'; +import SelectComponent from '@/_ui/Select'; +import { components } from 'react-select'; +import Check from '@/_ui/Icon/solidIcons/Check'; +import useStore from '@/AppBuilder/_stores/store'; + +const Option = (props) => { + return ( + +
+ {props.label} + {props.isSelected && ( + + + + )} +
+
+ ); +}; + +const selectCustomStyles = { + control: (base, state) => { + return { + ...base, + border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc', + boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none', + backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)', + '&:hover': { + border: '1px solid #3E63DD !important', + boxShadow: '0px 0px 6px #3E63DD', + }, + borderRadius: '6px', + width: '144px', + minHeight: '32px', + }; + }, + + dropdownIndicator: (base) => ({ + ...base, + padding: '4px', + }), + menuList: (base) => ({ + ...base, + padding: '4px', + }), + option: (base, state) => ({ + ...base, + backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white', + color: '#11181C', + borderRadius: '6px', + }), +}; + +export const Query = ({ value, onChange, meta }) => { + const dataQueries = useStore((state) => state.dataQuery.getCurrentModuleQueries('canvas')); + const options = dataQueries + .filter((query) => !(meta?.skipKinds ?? []).includes(query.kind)) + .map((query) => ({ name: query.name, value: query.id })); + + const onValueChange = useCallback( + (value) => { + console.log('value--- ', value, options); + onChange(value); + }, + [onChange, options] + ); + + // const cleanedValue = useMemo(() => { + // if (initialValue) { + // return initialValue.replace('{{queries.', '').replace('}}', ''); + // } + // return ''; + // }, [initialValue]); + + return ( +
+
e.stopPropagation()}> + null, + Option, + }} + /> +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js index eb01df1241..a249f52676 100644 --- a/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js +++ b/frontend/src/AppBuilder/CodeBuilder/TypeMapping.js @@ -20,4 +20,5 @@ export const TypeMapping = { visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }; diff --git a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx index 20b196476d..ca639e4643 100644 --- a/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx +++ b/frontend/src/AppBuilder/CodeEditor/DynamicFxTypeRenderer.jsx @@ -18,6 +18,7 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput'; import { Datepicker } from '../CodeBuilder/Elements/Datepicker'; import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput'; import { TimePicker } from '../CodeBuilder/Elements/TimePicker'; +import { Query } from '../CodeBuilder/Elements/Query'; const AllElements = { Color, @@ -38,6 +39,7 @@ const AllElements = { TableRowHeightInput, Datepicker, TimePicker, + Query, }; export const DynamicFxTypeRenderer = ({ paramType, ...restProps }) => { diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 5506a94fed..c1c0e03aa5 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -442,6 +442,33 @@ const DynamicEditorBridge = (props) => { setForceCodeBox(fxActive); }, [component, fxActive]); + const renderFx = () => { + if (paramType === 'query' || (paramLabel !== 'Type' && isFxNotRequired === undefined)) { + return null; + } + return ( +
+ { + if (codeShow) { + setForceCodeBox(false); + onFxPress(false); + } else { + setForceCodeBox(true); + onFxPress(true); + } + }} + dataCy={cyLabel} + /> +
+ ); + }; + const fxClass = isEventManagerParam ? 'justify-content-start' : 'justify-content-end'; return (
@@ -459,27 +486,7 @@ const DynamicEditorBridge = (props) => { )}
- {paramLabel !== 'Type' && isFxNotRequired === undefined && ( -
- { - if (codeShow) { - setForceCodeBox(false); - onFxPress(false); - } else { - setForceCodeBox(true); - onFxPress(true); - } - }} - dataCy={cyLabel} - /> -
- )} + {renderFx()}
{!codeShow && ( diff --git a/frontend/src/AppBuilder/CodeEditor/utils.js b/frontend/src/AppBuilder/CodeEditor/utils.js index 03473a629f..04f92b352e 100644 --- a/frontend/src/AppBuilder/CodeEditor/utils.js +++ b/frontend/src/AppBuilder/CodeEditor/utils.js @@ -143,6 +143,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor 'queries', 'globals', 'page', + 'input', 'constants', 'moment', '_', @@ -157,6 +158,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor isJsCode ? state?.queries : undefined, isJsCode ? state?.globals : undefined, isJsCode ? state?.page : undefined, + isJsCode ? state?.input : undefined, state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly. moment, _, @@ -353,6 +355,7 @@ export const FxParamTypeMapping = Object.freeze({ visibility: 'Visibility', numberInput: 'NumberInput', tableRowHeightInput: 'TableRowHeightInput', + query: 'Query', }); export function computeCoercion(oldValue, newValue) { diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index fb779c6269..c5420c98bf 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -12,8 +12,8 @@ import RightTopHeaderButtons from './RightTopHeaderButtons/RightTopHeaderButtons import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; - -export const EditorHeader = ({ darkMode }) => { +import { ModuleEditorBanner } from '@/modules/Modules/components'; +export const EditorHeader = ({ darkMode, isModuleEditor }) => { const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ isSaving: state.app.isSaving, @@ -72,7 +72,10 @@ export const EditorHeader = ({ darkMode }) => { }} >
- +
+ {isModuleEditor && } + +
@@ -96,20 +99,21 @@ export const EditorHeader = ({ darkMode }) => { {shouldEnableMultiplayer && }
-
- {/*
*/} + {!isModuleEditor &&
}
- {/* {editingVersion && ( */} - - {/* )} */} - + {!isModuleEditor && ( + <> + + + + )}
- +
diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index d22a2978c8..021887c824 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -9,12 +9,12 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; -const RightTopHeaderButtons = () => { +const RightTopHeaderButtons = ({ isModuleEditor }) => { return (
- + {!isModuleEditor && }
); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx index 869677928c..1e649961d8 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx @@ -8,9 +8,26 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import AppModeToggle from './AppModeToggle'; -const GlobalSettings = ({ darkMode }) => { +const GlobalSettings = ({ darkMode, isModuleEditor }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); + if (isModuleEditor) { + return ( +
+
+ + + +
+
+ +
+
+
+
+ ); + } + return ( <>
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 4146c4a28e..4a3507c59e 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -22,6 +22,7 @@ export const BaseLeftSidebar = ({ switchDarkMode, renderAISideBarTrigger = () => null, renderAIChat = () => null, + isModuleEditor = false, }) => { const [ pinned, @@ -142,6 +143,7 @@ export const BaseLeftSidebar = ({ // globalSettingsChanged={globalSettingsChanged} // globalSettings={appDefinition.globalSettings} darkMode={darkMode} + isModuleEditor={isModuleEditor} // toggleAppMaintenance={toggleAppMaintenance} // isMaintenanceOn={isMaintenanceOn} // app={app} @@ -156,72 +158,79 @@ export const BaseLeftSidebar = ({ return null; } + const renderCommonItems = () => { + return ( + <> + handleSelectedSidebarItem('inspect')} + darkMode={darkMode} + icon="inspect" + className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} + tip="Inspector" + ref={setSideBarBtnRefs('inspect')} + /> + + handleSelectedSidebarItem('debugger')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + count={unreadErrorCount} + tip="Debugger" + ref={setSideBarBtnRefs('debugger')} + /> + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> + + ); + }; + + const renderLeftSidebarItems = () => { + if (isModuleEditor) { + return renderCommonItems(); + } + return ( + <> + {renderAISideBarTrigger({ + selectedSidebarItem: selectedSidebarItem, + onClick: () => handleSelectedSidebarItem('tooljetai'), + darkMode: darkMode, + icon: 'tooljetai', + className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, + tip: 'Build with AI', + ref: setSideBarBtnRefs('tooljetai'), + })} + handleSelectedSidebarItem('page')} + darkMode={darkMode} + icon="page" + className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} + tip="Pages" + ref={setSideBarBtnRefs('page')} + /> + {renderCommonItems()} + + ); + }; + return (
- {renderAISideBarTrigger({ - selectedSidebarItem: selectedSidebarItem, - onClick: () => handleSelectedSidebarItem('tooljetai'), - darkMode: darkMode, - icon: 'tooljetai', - className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`, - tip: 'Build with AI', - ref: setSideBarBtnRefs('tooljetai'), - })} - handleSelectedSidebarItem('page')} - darkMode={darkMode} - icon="page" - className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`} - tip="Pages" - ref={setSideBarBtnRefs('page')} - /> - - handleSelectedSidebarItem('inspect')} - darkMode={darkMode} - icon="inspect" - className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`} - tip="Inspector" - ref={setSideBarBtnRefs('inspect')} - /> - - handleSelectedSidebarItem('debugger')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - count={unreadErrorCount} - tip="Debugger" - ref={setSideBarBtnRefs('debugger')} - /> - handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - /> - - {/* {dataSources?.length > 0 && ( - handleSelectedSidebarItem('datasource')} - icon="datasource" - className={`left-sidebar-item left-sidebar-layout sidebar-datasources`} - tip="Sources" - ref={setSideBarBtnRefs('datasource')} - /> - )} */} - + {renderLeftSidebarItems()} { // if tooljetai is open don't close diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 3adca4be98..a1e483d8e1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -24,6 +24,8 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow); const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); + const exposedModuleInputs = useStore((state) => state.getAllExposedValues().input || {}, shallow); + const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); const pathToBeInspected = useStore((state) => state.pathToBeInspected); @@ -74,6 +76,8 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]); + const sortedModuleInputs = useMemo(() => sortAndReduce(exposedModuleInputs), [exposedModuleInputs]); + const memoizedJSONData = React.useMemo(() => { const jsontreeData = {}; @@ -83,10 +87,19 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { jsontreeData['variables'] = sortedVariables; jsontreeData['page'] = sortedPageVariables; jsontreeData['constants'] = sortedConstants; + if (sortedModuleInputs) jsontreeData['input'] = sortedModuleInputs; return jsontreeData; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); + }, [ + sortedComponents, + sortedQueries, + sortedVariables, + sortedConstants, + sortedPageVariables, + sortedGlobalVariables, + sortedModuleInputs, + ]); const handleNodeExpansion = (path, data, currentNode) => { if (pathToBeInspected && path?.length > 0) { diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx index e0ddb28922..2f4b785b6a 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentConfigurationTab/ComponentConfigurationTab.jsx @@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; -export const ComponentConfigurationTab = ({ darkMode }) => { +export const ComponentConfigurationTab = ({ darkMode, isModuleEditor }) => { const selectedComponentId = useStore((state) => state.selectedComponents?.[0], shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); if (!selectedComponentId) { @@ -17,6 +17,7 @@ export const ComponentConfigurationTab = ({ darkMode }) => { darkMode={darkMode} selectedComponentId={selectedComponentId} pages={[]} + isModuleEditor={isModuleEditor} /> ); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx new file mode 100644 index 0000000000..f1126ab4d5 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/ComponentModuleTab.jsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import './styles.scss'; + +export const ComponentModuleTab = ({ onChangeTab }) => { + const [activeTab, setActiveTab] = useState(1); + + const handleChangeTab = (tab) => { + setActiveTab(tab); + onChangeTab(tab); + }; + + return ( +
+
+ + +
+
+ ); +}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js new file mode 100644 index 0000000000..a9bf63add0 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/index.js @@ -0,0 +1 @@ +export { ComponentModuleTab as default } from './ComponentModuleTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss new file mode 100644 index 0000000000..51fdce0d61 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentModuleTab/styles.scss @@ -0,0 +1,19 @@ +.tj-tabs-container-outer { + padding-top: 0px; + gap: 10px; + height: 36px; + margin-bottom: 8px; + margin-top: 16px; +} + +.tj-tabs-container { + padding: 2px; + gap: 2px; + display: flex; + + width: 100%; + height: 100%; + background: var(--slate4); + border-radius: 6px; + +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx similarity index 90% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx index 2ad7977496..ca98316a73 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx @@ -7,18 +7,21 @@ import Fuse from 'fuse.js'; import { SearchBox } from '@/_components'; import { DragLayer } from './DragLayer'; import useStore from '@/AppBuilder/_stores/store'; +import ComponentModuleTab from './ComponentModuleTab'; +import { ModuleManager } from '@/modules/Modules/components'; // TODO: Hardcode all the component-section mapping in a constant file and just loop over it // TODO: styling // TODO: scrolling // TODO: searching -export const ComponentsManagerTab = ({ darkMode }) => { +export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const componentList = useMemo(() => { return componentTypes.map((component) => component.component); }, [componentTypes]); const [filteredComponents, setFilteredComponents] = useState(componentList); + const [activeTab, setActiveTab] = useState(1); const _shouldFreeze = useStore((state) => state.getShouldFreeze()); const isAutoMobileLayout = useStore((state) => state.currentLayout === 'mobile' && state.getIsAutoMobileLayout()); const shouldFreeze = _shouldFreeze || isAutoMobileLayout; @@ -157,9 +160,24 @@ export const ComponentsManagerTab = ({ darkMode }) => { } } + const handleChangeTab = (tab) => { + setActiveTab(tab); + }; + + const renderSection = () => { + if (activeTab === 1) { + return
{segregateSections()}
; + } + return ; + }; + return (
-

Components

+ {isModuleEditor ? ( +

Components

+ ) : ( + + )}
{ width={266} />
-
{segregateSections()}
+ {renderSection()}
); }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx similarity index 80% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 77274cd658..cae0c00624 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -1,11 +1,13 @@ import React, { useEffect } from 'react'; import { WidgetBox } from '../WidgetBox'; +import { ModuleWidgetBox } from '@/modules/Modules/components'; import { useDrag, useDragLayer } from 'react-dnd'; import { getEmptyImage } from 'react-dnd-html5-backend'; import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; import { NO_OF_GRIDS } from '@/AppBuilder/AppCanvas/appCanvasConstants'; -export const DragLayer = ({ index, component }) => { +export const DragLayer = ({ index, component, isModuleTab = false }) => { + // const currentLayout = useStore((state) => state.currentLayout, shallow); const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', @@ -19,12 +21,17 @@ export const DragLayer = ({ index, component }) => { preview(getEmptyImage(), { captureDraggingState: true }); }, []); + // const size = isModuleTab + // ? component.module_container.layouts[currentLayout] + // : component.defaultSize || { width: 30, height: 40 }; + const size = component.defaultSize || { width: 30, height: 40 }; + return ( <> {isDragging && } -
- +
+ {isModuleTab ? : }
); diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js similarity index 100% rename from frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/constants.js rename to frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/constants.js diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js new file mode 100644 index 0000000000..6f24ff7948 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/index.js @@ -0,0 +1 @@ +export { ComponentsManagerTab as default } from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js deleted file mode 100644 index ddb70f8220..0000000000 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './ComponentsManagerTab'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx index d680f21d1e..f131e2ea81 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DefaultComponent.jsx @@ -6,7 +6,7 @@ import { renderElement } from '../Utils'; import i18next from 'i18next'; import { resolveReferences } from '@/_helpers/utils'; // import { AllComponents } from '@/Editor/Box'; -import { AllComponents } from '@/_helpers/editorHelpers'; +import { AllComponents } from '@/AppBuilder/_helpers/editorHelpers'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx index c74e023c0b..f2b0ff7594 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Elements/Code.jsx @@ -20,14 +20,23 @@ export const Code = ({ placeholder, validationFn, isHidden = false, + customMeta, }) => { const currentState = useCurrentState(); - let initialValue = !_.isEmpty(definition) - ? definition.value - : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + function getInitialValue() { + if (customMeta && customMeta.defaultValue) { + return customMeta.defaultValue; + } + return !_.isEmpty(definition) + ? definition.value + : getDefinitionInitialValue(paramType, param.name, component, currentState, definition.value); + } - const paramMeta = accordian ? componentMeta[paramType]?.[param.name] : componentMeta[paramType][param.name]; + let initialValue = getInitialValue(); + const paramMeta = accordian + ? customMeta ?? componentMeta[paramType]?.[param.name] + : customMeta ?? componentMeta[paramType][param.name]; const displayName = paramMeta.displayName || param.name; function handleCodeChanged(value) { diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index 6a3465403d..a69cd94470 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -39,6 +39,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes'; import { copyComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils.js'; import DatetimePickerV2 from './Components/DatetimePickerV2.jsx'; +import { ModuleContainerInspector, ModuleViewerInspector, ModuleEditorBanner } from '@/modules/Modules/components'; const INSPECTOR_HEADER_OPTIONS = [ { @@ -105,6 +106,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte definition: allComponents?.[selectedComponentId]?.component.definition, }; + const isModuleContainer = componentMeta.component === 'ModuleContainer'; + const component = { id: selectedComponentId, component: JSON.parse(JSON.stringify(componentMeta)), @@ -443,8 +446,39 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ showHeaderActionsMenu })]); + const renderAppNameInput = () => { + if (isModuleContainer) { + return ; + } + + return ( +
+ setNewComponentName(e.target.value)} + type="text" + onBlur={() => handleComponentNameChange(newComponentName)} + className="w-100 inspector-edit-widget-name" + value={newComponentName} + ref={inputRef} + data-cy="edit-widget-name" + /> +
+ ); + }; + + const renderTabs = () => ( + + ); + return ( -
+
clearSelectedComponents()}> @@ -456,68 +490,50 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
-
-
- setNewComponentName(e.target.value)} - type="text" - onBlur={() => handleComponentNameChange(newComponentName)} - className="w-100 inspector-edit-widget-name" - value={newComponentName} - ref={inputRef} - data-cy="edit-widget-name" - /> -
-
-
- - - {INSPECTOR_HEADER_OPTIONS.map((option) => ( -
{ - e.stopPropagation(); - handleInspectorHeaderActions(option.value); - }} - > -
{option.icon}
+
{renderAppNameInput()}
+ {!isModuleContainer && ( +
+ + + {INSPECTOR_HEADER_OPTIONS.map((option) => (
{ + e.stopPropagation(); + handleInspectorHeaderActions(option.value); + }} > - {option?.label} +
{option.icon}
+
+ {option?.label} +
-
- ))} - - - } - > - setShowHeaderActionsMenu(true)}> - - - -
-
-
- - - {propertiesTab} - - - {stylesTab} - - + ))} + + + } + > + setShowHeaderActionsMenu(true)}> + + + +
+ )}
+ +
{renderTabs()}
@@ -731,6 +747,12 @@ const GetAccordion = React.memo( case 'TimePicker': return ; + case 'ModuleContainer': + return ; + + case 'ModuleViewer': + return ; + default: { return ; } diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js index 62ee032172..141b6a6261 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Utils.js @@ -28,13 +28,14 @@ export function renderCustomStyles( components = {}, accordian, darkMode = false, - placeholder = '' + placeholder = '', + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; const paramTypeDefinition = componentDefinition[paramType] || {}; const definition = paramTypeDefinition[param] || {}; - const meta = componentMeta[paramType]?.[accordian]?.[param]; + const meta = customMeta ?? componentMeta[paramType]?.[accordian]?.[param]; if ( componentConfig.component == 'DropDown' || @@ -92,7 +93,7 @@ export function renderCustomStyles( return ( <> ); @@ -125,7 +127,8 @@ export function renderElement( components = {}, darkMode = false, placeholder = '', - validationFn + validationFn, + customMeta ) { const componentConfig = component.component; const componentDefinition = componentConfig.definition; @@ -155,7 +158,7 @@ export function renderElement( return ( ); } diff --git a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx index 88ea0f477a..5efc153286 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx @@ -1,11 +1,11 @@ import React from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { ComponentConfigurationTab } from './ComponentConfigurationTab'; -import { ComponentsManagerTab } from './ComponentsManagerTab'; +import ComponentsManagerTab from './ComponentManagerTab'; import cx from 'classnames'; import { PageSettings } from './PageSettingsTab'; -export const RightSideBar = ({ darkMode }) => { +export const RightSideBar = ({ darkMode, isModuleEditor }) => { const activeTab = useStore((state) => state.activeRightSideBarTab); const pageSettingSelected = useStore((state) => state.pageSettingSelected); @@ -15,9 +15,9 @@ export const RightSideBar = ({ darkMode }) => {
{pageSettingSelected && } {activeTab === 'components' ? ( - + ) : ( - + )}
diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index af820fca13..abf0ef6646 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -16,11 +16,20 @@ import { shallow } from 'zustand/shallow'; import Popups from '../Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; -export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, environmentId, versionId } = {}) => { +export const Viewer = ({ + id: appId, + darkMode, + moduleId = 'canvas', + switchDarkMode, + environmentId, + versionId, + moduleMode = false, + appType = 'front-end', +} = {}) => { const DEFAULT_CANVAS_WIDTH = 1292; const { t } = useTranslation(); const [isSidebarPinned, setIsSidebarPinned] = useState(localStorage.getItem('isPagesSidebarPinned') !== 'false'); - useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }); + useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); const { isEditorLoading, @@ -69,18 +78,20 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow); const deviceWindowWidth = window.screen.width - 5; + const hideSidebar = moduleMode || isPagesSidebarHidden; + const computeCanvasMaxWidth = useCallback(() => { if (globalSettings?.maxCanvasWidth) { return globalSettings.maxCanvasWidth; } if (globalSettings?.canvasMaxWidthType === 'px') { - return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!isPagesSidebarHidden ? 200 : 0); + return (+globalSettings?.canvasMaxWidth || DEFAULT_CANVAS_WIDTH) - (!hideSidebar ? 200 : 0); } if (globalSettings?.canvasMaxWidthType === '%') { return +globalSettings?.canvasMaxWidth + '%'; } return DEFAULT_CANVAS_WIDTH; - }, [globalSettings, isPagesSidebarHidden]); + }, [globalSettings, hideSidebar]); const toggleSidebarPinned = useCallback(() => { const newValue = !isSidebarPinned; @@ -119,6 +130,45 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod }; }, []); + const renderHeader = () => { + if (moduleMode) { + return null; + } + + if (currentLayout !== 'mobile') { + return ( + + ); + } + + return ( + <> + {currentLayout === 'mobile' && !isMobilePreviewMode && ( + + )} + + ); + }; + if (isEditorLoading) { return (
@@ -144,32 +194,8 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod className={cx('viewer wrapper', { 'mobile-layout': currentLayout, 'theme-dark dark-theme': darkMode })} > - - {currentLayout !== 'mobile' && ( - - )} - {currentLayout === 'mobile' && !isMobilePreviewMode && ( - - )} + + {renderHeader()}
- {currentLayout !== 'mobile' && !isPagesSidebarHidden && ( + {currentLayout !== 'mobile' && !hideSidebar && !moduleMode && (
- {currentLayout === 'mobile' && isMobilePreviewMode && ( + {currentLayout === 'mobile' && isMobilePreviewMode && !moduleMode && ( )} - +
{/* {!licenseValid && isAppLoaded && } */} {isMobilePreviewMode &&
} diff --git a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js index be2855c476..1b32dc7e13 100644 --- a/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js +++ b/frontend/src/AppBuilder/WidgetManager/configs/widgetConfig.js @@ -58,6 +58,8 @@ import { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, } from '../widgets'; export const widgets = [ @@ -120,4 +122,6 @@ export const widgets = [ linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, ]; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/index.js b/frontend/src/AppBuilder/WidgetManager/widgets/index.js index ce0e73fdf5..65eda8c036 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/index.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/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'; export { buttonConfig, @@ -120,4 +122,6 @@ export { linkConfig, iconConfig, boundedBoxConfig, + moduleContainerConfig, + moduleViewerConfig, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleContainer.js new file mode 100644 index 0000000000..bf488095a6 --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/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/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js new file mode 100644 index 0000000000..b5d2962cde --- /dev/null +++ b/frontend/src/AppBuilder/WidgetManager/widgets/moduleViewer.js @@ -0,0 +1,30 @@ +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' }, + }, + }, +}; diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index 1c8e64a843..f07e30ad65 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -2,16 +2,31 @@ import React, { createContext, useContext } from 'react'; export const ModuleContext = createContext(); -export const ModuleProvider = ({ moduleId, children }) => { - return {children}; +export const ModuleProvider = ({ moduleId, isModuleMode, appType, children }) => { + return {children}; }; 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; }; diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js index 5e817c7f7e..a920328b66 100644 --- a/frontend/src/AppBuilder/_helpers/editorHelpers.js +++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js @@ -67,6 +67,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) { @@ -145,6 +148,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 8f1c350f99..3caabf5db2 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, @@ -53,7 +54,14 @@ 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 { state } = useLocation(); const [currentSession, setCurrentSession] = useState(); const setEditorLoading = useStore((state) => state.setEditorLoading); @@ -76,8 +84,8 @@ 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 events = useStore((state) => state.eventsSlice.module[moduleId]?.events || []); + const pages = useStore((state) => state.modules[moduleId]?.pages || []); const currentPageId = useStore((state) => state.currentPageId); const setResolvedConstants = useStore((state) => state.setResolvedConstants); const setSecrets = useStore((state) => state.setSecrets); @@ -108,6 +116,10 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v 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 initModules = useStore((state) => state.initModules, shallow); + const setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); const setConversationZeroState = useStore((state) => state.ai?.setConversationZeroState); @@ -148,16 +160,22 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v }; useEffect(() => { - if (pageSwitchInProgress) { + if (!moduleMode) return; + initModules(moduleId); + }, [initModules, moduleId, moduleMode]); + + useEffect(() => { + 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(() => { + if (moduleMode) return; const subscription = authenticationService.currentSession .pipe( distinctUntilChanged((prev, curr) => { @@ -185,16 +203,16 @@ 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) { + if (!currentSession || moduleMode) { return; } const queryParams = getPreviewQueryParams(); @@ -415,9 +433,10 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v document.title = retrieveWhiteLabelText(); }; }); - }, [setApp, setEditorLoading, currentSession]); + }, [setApp, setEditorLoading, currentSession, moduleMode]); useEffect(() => { + if (moduleMode) return; if (isComponentLayoutReady) { runOnLoadQueries().then(() => { let startingPage = pages.find((page) => page.id === currentPageId); @@ -427,13 +446,15 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v handleEvent('onPageLoad', currentPageEvents, {}); }); } - }, [isComponentLayoutReady]); + }, [isComponentLayoutReady, moduleMode]); useEffect(() => { + if (moduleMode) return; fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: app.appName }); - }, [app.appName]); + }, [app.appName, moduleMode]); useEffect(() => { + if (moduleMode) return; const exposedTheme = appMode && appMode !== 'auto' ? appMode : localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; const isEnvChanged = @@ -540,7 +561,22 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v setEditorLoading(false); }); } - }, [selectedEnvironment?.id, currentVersionId]); + }, [selectedEnvironment?.id, currentVersionId, moduleMode]); + + 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]); }; 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 39cc7a4986..9f2501f444 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'; diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index c4448a3bbf..c90f12e746 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -50,6 +50,16 @@ const initialState = { export const createComponentsSlice = (set, get) => ({ ...initialState, + initializeComponentsSlice: (moduleId) => { + set( + (state) => { + state.modules[moduleId] = { ...state.modules[moduleId], ...initialState.modules.canvas }; + }, + false, + 'initializeComponentsSlice' + ); + }, + setPages: (pages = [], moduleId = 'canvas') => { set( (state) => { @@ -643,7 +653,6 @@ export const createComponentsSlice = (set, get) => ({ moduleId ); updatedPropertyValue[index] = updatedValue; - console.log('updatedPropertyValue', updatedPropertyValue); if (allRefs.length) { generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); } diff --git a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index 948ac39b39..60ac1b6fe7 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'; 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/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index ebbd524fd1..94c7433e90 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,18 @@ export const DEFAULT_COMPONENT_STRUCTURE = { export const createResolvedSlice = (set, get) => ({ ...initialState, + initializeResolvedSlice: (moduleId) => { + set( + (state) => { + state.resolvedStore.modules[moduleId] = { + ...state.resolvedStore.modules[moduleId], + ...initialState.resolvedStore.modules.canvas, + }; + }, + false, + 'initializeResolvedSlice' + ); + }, setResolvedGlobals: (objKey, values, moduleId = 'canvas') => { set( (state) => { @@ -469,6 +472,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 = {}; } @@ -581,4 +587,23 @@ 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}`); + }, + clearModuleInputs: (moduleId = 'canvas') => { + set((state) => { + state.resolvedStore.modules[moduleId].exposedValues.input = {}; + }); + }, }); diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 4d1392fc7c..92b982f6fd 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -28,6 +28,7 @@ import { createDebuggerSlice } from './slices/debuggerSlice'; import { createGitSyncSlice } from './slices/gitSyncSlice'; import { createAiSlice } from './slices/aiSlice'; import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice'; +import { createModuleSlice } from './slices/moduleSlice'; export default create( zustandDevTools( @@ -60,6 +61,7 @@ export default create( ...createGitSyncSlice(...state), ...createAiSlice(...state), ...createWhiteLabellingSlice(...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 bb08c620da..2bc297ed47 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..2808a04545 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/_helpers/editorHelpers.js b/frontend/src/_helpers/editorHelpers.js index 1f97cc42ee..2a0ed161db 100644 --- a/frontend/src/_helpers/editorHelpers.js +++ b/frontend/src/_helpers/editorHelpers.js @@ -1,59 +1,60 @@ -import { Button } from '@/Editor/Components/Button'; -import { Image } from '@/Editor/Components/Image/Image'; -import { Text } from '@/Editor/Components/Text'; -import { Table } from '@/Editor/Components/Table/Table'; -import { TextInput } from '@/Editor/Components/TextInput'; -import { NumberInput } from '@/Editor/Components/NumberInput'; -import { TextArea } from '@/Editor/Components/TextArea'; -import { Container } from '@/Editor/Components/Container'; -import { Tabs } from '@/Editor/Components/Tabs'; -import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; -import { DropDown } from '@/Editor/Components/DropDown'; -import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; -import { Checkbox } from '@/Editor/Components/Checkbox'; -import { Datepicker } from '@/Editor/Components/Datepicker'; -import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; -import { Multiselect } from '@/Editor/Components/Multiselect'; -import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; -import { Modal } from '@/Editor/Components/Modal'; -import { Chart } from '@/Editor/Components/Chart'; -import { Map as MapComponent } from '@/Editor/Components/Map/Map'; -import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; -import { ToggleSwitch } from '@/Editor/Components/Toggle'; -import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; +// import { Button } from '@/Editor/Components/Button'; +// import { Image } from '@/Editor/Components/Image/Image'; +// import { Text } from '@/Editor/Components/Text'; +// import { Table } from '@/Editor/Components/Table/Table'; +// import { TextInput } from '@/Editor/Components/TextInput'; +// import { NumberInput } from '@/Editor/Components/NumberInput'; +// import { TextArea } from '@/Editor/Components/TextArea'; -import { RadioButton } from '@/Editor/Components/RadioButton'; -import { StarRating } from '@/Editor/Components/StarRating'; -import { Divider } from '@/Editor/Components/Divider'; -import { FilePicker } from '@/Editor/Components/FilePicker'; -import { PasswordInput } from '@/Editor/Components/PasswordInput'; -import { Calendar } from '@/Editor/Components/Calendar'; -import { Listview } from '@/Editor/Components/Listview'; -import { IFrame } from '@/Editor/Components/IFrame'; -import { CodeEditor } from '@/Editor/Components/CodeEditor'; -import { Timer } from '@/Editor/Components/Timer'; -import { Statistics } from '@/Editor/Components/Statistics'; -import { Pagination } from '@/Editor/Components/Pagination'; -import { Tags } from '@/Editor/Components/Tags'; -import { Spinner } from '@/Editor/Components/Spinner'; -import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; -import { RangeSlider } from '@/Editor/Components/RangeSlider'; -import { Timeline } from '@/Editor/Components/Timeline'; -import { SvgImage } from '@/Editor/Components/SvgImage'; -import { Html } from '@/Editor/Components/Html'; -import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; -import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; -import { VerticalDivider } from '@/Editor/Components/verticalDivider'; -import { ColorPicker } from '@/Editor/Components/ColorPicker'; -import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; -import { Kanban } from '@/Editor/Components/Kanban/Kanban'; -import { Steps } from '@/Editor/Components/Steps'; -import { TreeSelect } from '@/Editor/Components/TreeSelect'; -import { Icon } from '@/Editor/Components/Icon'; -import { Link } from '@/Editor/Components/Link'; -import { Form } from '@/Editor/Components/Form/Form'; -import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; -import { isPDFSupported } from '@/_helpers/appUtils'; +// import { Container } from '@/Editor/Components/Container'; +// import { Tabs } from '@/Editor/Components/Tabs'; +// import { RichTextEditor } from '@/Editor/Components/RichTextEditor'; +// import { DropDown } from '@/Editor/Components/DropDown'; +// import { DropdownV2 } from '@/Editor/Components/DropdownV2/DropdownV2'; +// import { Checkbox } from '@/Editor/Components/Checkbox'; +// import { Datepicker } from '@/Editor/Components/Datepicker'; +// import { DaterangePicker } from '@/Editor/Components/DaterangePicker'; +// import { Multiselect } from '@/Editor/Components/Multiselect'; +// import { MultiselectV2 } from '@/Editor/Components/MultiselectV2/MultiselectV2'; +// import { Modal } from '@/Editor/Components/Modal'; +// import { Chart } from '@/Editor/Components/Chart'; +// import { Map as MapComponent } from '@/Editor/Components/Map/Map'; +// import { QrScanner } from '@/Editor/Components/QrScanner/QrScanner'; +// import { ToggleSwitch } from '@/Editor/Components/Toggle'; +// import { ToggleSwitchV2 } from '@/Editor/Components/ToggleV2'; + +// import { RadioButton } from '@/Editor/Components/RadioButton'; +// import { StarRating } from '@/Editor/Components/StarRating'; +// import { Divider } from '@/Editor/Components/Divider'; +// import { FilePicker } from '@/Editor/Components/FilePicker'; +// import { PasswordInput } from '@/Editor/Components/PasswordInput'; +// import { Calendar } from '@/Editor/Components/Calendar'; +// import { Listview } from '@/Editor/Components/Listview'; +// import { IFrame } from '@/Editor/Components/IFrame'; +// import { CodeEditor } from '@/Editor/Components/CodeEditor'; +// import { Timer } from '@/Editor/Components/Timer'; +// import { Statistics } from '@/Editor/Components/Statistics'; +// import { Pagination } from '@/Editor/Components/Pagination'; +// import { Tags } from '@/Editor/Components/Tags'; +// import { Spinner } from '@/Editor/Components/Spinner'; +// import { CircularProgressBar } from '@/Editor/Components/CirularProgressbar'; +// import { RangeSlider } from '@/Editor/Components/RangeSlider'; +// import { Timeline } from '@/Editor/Components/Timeline'; +// import { SvgImage } from '@/Editor/Components/SvgImage'; +// import { Html } from '@/Editor/Components/Html'; +// import { ButtonGroup } from '@/Editor/Components/ButtonGroup'; +// import { CustomComponent } from '@/Editor/Components/CustomComponent/CustomComponent'; +// import { VerticalDivider } from '@/Editor/Components/verticalDivider'; +// import { ColorPicker } from '@/Editor/Components/ColorPicker'; +// import { KanbanBoard } from '@/Editor/Components/KanbanBoard/KanbanBoard'; +// import { Kanban } from '@/Editor/Components/Kanban/Kanban'; +// import { Steps } from '@/Editor/Components/Steps'; +// import { TreeSelect } from '@/Editor/Components/TreeSelect'; +// import { Icon } from '@/Editor/Components/Icon'; +// import { Link } from '@/Editor/Components/Link'; +// import { Form } from '@/Editor/Components/Form/Form'; +// import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox'; +// import { isPDFSupported } from '@/_helpers/appUtils'; import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { useEditorStore } from '@/_stores/editorStore'; import './requestIdleCallbackPolyfill'; @@ -74,71 +75,71 @@ export function memoizeFunction(func) { }; } -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 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/_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/theme.scss b/frontend/src/_styles/theme.scss index 9445ed810d..ea548eea8e 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -807,6 +807,15 @@ button { } } + .editor .viewer .main { + height: auto !important; + + .canvas-container { + top: 0; + right: 0; + } + } + @media screen and (max-height: 450px) { .sidebar { padding-top: 15px; @@ -1553,6 +1562,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; @@ -9119,7 +9135,8 @@ tbody { } .global-settings-app-wrapper { - max-width: 190px; + max-width: 350px; + margin-right: 10px; } .version-manager-container { 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/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 34a2410e1d..97a65e82f4 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -229,6 +229,7 @@ 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'; const Icon = (props) => { switch (props.name) { @@ -692,6 +693,8 @@ const Icon = (props) => { return ; case 'ai-crown': return ; + case 'empty-state-modules': + return ; default: return ; } 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/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 0eefbb71a1..683647f83d 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 0eefbb71a1d5288f49641af5efaaab25970f27d1 +Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 diff --git a/server/src/modules/app/constants/modules.ts b/server/src/modules/app/constants/modules.ts index d3a04367ab..6eb8993fc4 100644 --- a/server/src/modules/app/constants/modules.ts +++ b/server/src/modules/app/constants/modules.ts @@ -36,4 +36,5 @@ export enum MODULES { IMPORT_EXPORT_RESOURCES = 'ImportExportResources', TEMPLATES = 'Templates', AI = 'ai', + MODULES = 'modules', } diff --git a/server/src/modules/app/module.ts b/server/src/modules/app/module.ts index c0ca97e4be..d297877243 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'; @@ -92,6 +93,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), ]; diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts index a7538f5f40..82ed31fd8b 100644 --- a/server/src/modules/apps/services/component.service.ts +++ b/server/src/modules/apps/services/component.service.ts @@ -95,7 +95,9 @@ export class ComponentsService implements IComponentsService { if (componentData.type === 'Table' && _.isArray(objValue)) { return srcValue; } else if ( - (componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') && + (componentData.type === 'DropdownV2' || + componentData.type === 'MultiselectV2' || + componentData.type === 'ModuleContainer') && _.isArray(objValue) ) { return _.isArray(srcValue) ? srcValue : Object.values(srcValue); diff --git a/server/src/modules/apps/services/widget-config/index.js b/server/src/modules/apps/services/widget-config/index.js index bef9a2ea99..b434d02269 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'; const widgets = { buttonConfig, @@ -120,6 +122,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/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..dd871434d0 --- /dev/null +++ b/server/src/modules/modules/module.ts @@ -0,0 +1,52 @@ +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'; +@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), + ], + 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.'); + } +} From 10f946130fbe7786b63cb18c1512f6bd1c157b51 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 22 Apr 2025 02:49:10 +0530 Subject: [PATCH 05/84] Inspector Revamp --- .../assets/images/icons/editor/file-code.svg | 3 + frontend/package-lock.json | 23 ++ frontend/package.json | 1 + .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 13 +- .../LeftSidebarInspector/DefaultCopyIcon.jsx | 19 ++ .../LeftSidebarInspector/HiddenOptions.jsx | 127 +++++++++++ .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 202 ++++++++++++------ .../LeftSidebarInspector/JSONViewer.jsx | 69 ++++++ .../LeftSidebarInspector.jsx | 80 +++---- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 136 ++++++++++++ .../LeftSidebarInspector/TreeViewHeader.jsx | 148 +++++++++++++ .../useCallbackActions.js | 69 +++++- .../LeftSidebar/LeftSidebarInspector/utils.js | 97 +++++++-- .../_stores/slices/componentsSlice.js | 5 + .../_stores/slices/inspectorSlice.js | 34 +++ frontend/src/AppBuilder/_stores/store.js | 2 + frontend/src/_styles/components.scss | 6 +- frontend/src/_styles/theme.scss | 195 ++++++++++++++++- 18 files changed, 1083 insertions(+), 146 deletions(-) create mode 100644 frontend/assets/images/icons/editor/file-code.svg create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx create mode 100644 frontend/src/AppBuilder/_stores/slices/inspectorSlice.js diff --git a/frontend/assets/images/icons/editor/file-code.svg b/frontend/assets/images/icons/editor/file-code.svg new file mode 100644 index 0000000000..4dc470055c --- /dev/null +++ b/frontend/assets/images/icons/editor/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 56c9e6c5c1..4f841128fc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -106,6 +106,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", @@ -34033,6 +34034,11 @@ "hermes-estree": "0.23.1" } }, + "node_modules/highlight-words-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz", + "integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ==" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "license": "BSD-3-Clause", @@ -41628,6 +41634,23 @@ } } }, + "node_modules/react-highlight-words": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.21.0.tgz", + "integrity": "sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==", + "dependencies": { + "highlight-words-core": "^1.2.0", + "memoize-one": "^4.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/react-highlight-words/node_modules/memoize-one": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" + }, "node_modules/react-hot-toast": { "version": "2.4.1", "license": "MIT", diff --git a/frontend/package.json b/frontend/package.json index 789fdae8df..c5bf6fb965 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -101,6 +101,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-highlight-words": "^0.21.0", "react-hot-toast": "^2.4.0", "react-hotkeys-hook": "^4.3.5", "react-i18next": "^12.1.5", diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index f6d6419ac4..a5fde2712b 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -154,17 +154,18 @@ const RenderWidget = ({ ? null : ['hover', 'focus'] : !resolvedGeneralProperties?.tooltip?.toString().trim() - ? null - : ['hover', 'focus'] + ? null + : ['hover', 'focus'] } overlay={(props) => renderTooltip({ props, text: inCanvas - ? `${SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) - ? resolvedProperties?.tooltip - : resolvedGeneralProperties?.tooltip - }` + ? `${ + SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component) + ? resolvedProperties?.tooltip + : resolvedGeneralProperties?.tooltip + }` : `${t(`widget.${component?.name}.description`, component?.description)}`, }) } diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx new file mode 100644 index 0000000000..fa46cf9694 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +export const DefaultCopyIcon = () => ( + + + +); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx new file mode 100644 index 0000000000..c2055f4b8e --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from 'react'; +import { ToolTip } from '@/_components/ToolTip'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import cx from 'classnames'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; + +export const HiddenOptions = (props) => { + const { nodeSpecificFilteredActions, generalActionsFiltered, darkMode, setActionClicked, data } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const closeMenu = () => { + setShowMenu(false); + setActionClicked(false); + }; + + const copyPath = () => { + generalActionsFiltered[0].dispatchAction(data?.selectedNodePath); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActionsFiltered[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderOptions = () => { + return nodeSpecificFilteredActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + if (icon) { + return ( +
+ + {/* ${name === 'Go to component' ? '' : currentNode} */} + { + event.stopPropagation(); + dispatchAction(data); + }} + > + + + +
+ ); + } + }); + }; + + return ( +
+ {renderOptions()} + + +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + > + + Copy value +
+
+
+ + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + setActionClicked((prev) => !prev); + }} + className="copy-menu-options-icon" + style={{ + outline: 'none', + }} + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 87194f3ca2..6be923a0e1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -1,79 +1,145 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import TreeView, { flattenTree } from 'react-accessible-treeview'; -import WidgetIcon from '@/../assets/images/icons/widgets'; -import SolidIcon from '@/_ui/Icon/SolidIcons'; -import { ToolTip } from '@/_components/ToolTip'; -import { extractComponentName } from './utils'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import Fuse from 'fuse.js'; +import JSONViewer from './JSONViewer'; +import { SearchBox } from '@/_components'; +import { Node } from './Node'; +import { v4 as uuidv4 } from 'uuid'; +import { isEmpty } from 'lodash'; + +const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { + const searchValue = useStore((state) => state.inspectorSearchValue, shallow); + // const getSelectedNodes = useStore((state) => state.getSelectedNodes, shallow); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); + const [selectedNodePath, setSelectedNodePath] = React.useState(null); + const selectedNodes = useStore((state) => state.selectedNodes, shallow); + + function fuzzySearch(query, searchablePaths) { + const list = Array.from(searchablePaths); + const fuse = new Fuse(list, { threshold: 0.3 }); + return fuse.search(query).map((result) => result.item); + } + + const [searchedSet, pathSet] = useMemo(() => { + const result = fuzzySearch(searchValue, searchablePaths); + const expandedIdSet = new Set(); + result.forEach((id) => { + const pathArray = id.split('.'); + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdSet.has(parentPath)) { + expandedIdSet.add(parentPath); + } + } + }); + return [new Set(result), expandedIdSet]; + }, [searchValue, JSON.stringify(searchablePaths)]); + + // const recursiveFn = (obj) => { + // if (!obj || typeof obj !== 'object') return []; + // let isCompletelyExposed = false; + // obj?.children?.forEach((child) => { + // const { id } = child; + // if (searchedSet.has(id)) { + // isCompletelyExposed = true; + // } + // }); + // const newChildren = + // obj?.children + // ?.filter((child) => { + // return isCompletelyExposed || pathSet.has(child.id); + // }) + // ?.map((child) => { + // return recursiveFn(child); + // }) || []; + + // return { + // ...obj, + // children: newChildren, + // }; + // }; + + // const formattedData = useMemo(() => { + // return searchValue ? recursiveFn(data) : data; + // }, [data, searchValue]); + + const key = useMemo(() => { + return uuidv4(); + }, [JSON.stringify(data), selectedNodePath]); -const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, callbackActions = [] }) => { const flattendedData = flattenTree(data); - const renderNodeIcons = (node) => { - const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; - if (icon && icon?.iconPath) { - return ( - - ); - } - if (icon && icon.jsx) { - if (icon?.tooltipMessage) { - return ( - -
{icon.jsx()}
-
- ); - } - return icon.jsx(); - } + const backFn = () => { + setSelectedNodePath(null); }; + const selectedData = selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + const expandedIds = [...Array.from(pathSet), ...selectedNodes]; + + const filteredIds = useMemo(() => { + const expandedIdsSet = new Set(expandedIds); + const filtered = flattendedData.filter((item) => { + const { metadata } = item || {}; + const { path } = metadata || {}; + return expandedIdsSet.has(path); + }); + return filtered.map((item) => item.id); + }, [flattendedData, expandedIds]); + + console.log('selectedData', selectedData); return ( - { - const { element, getNodeProps, level, handleSelect, handleExpand, isExpanded, isDisabled, isBranch } = props; - const nodeIcon = renderNodeIcons(element.name); - const metadata = element.metadata || {}; - const { type } = metadata; - - const actions = callbackActions.filter((action) => [type, 'all'].includes(action.for)); - - return ( -
- {(isBranch || level === 1) && ( -
- {isExpanded ? ( - - ) : ( - - )} -
- )} - {nodeIcon &&
{nodeIcon}
} -
- {element.name} -
+ <> + {!selectedNodePath || isEmpty(selectedData) ? ( +
+
+ setSearchValue(e.target.value)} + onClearCallback={() => setSearchValue('')} + placeholder={`Search`} + customClass={`tj-inspector-search-input tj-text-xsm`} + showClearButton={false} + width={300} + />
- ); - }} - /> + + { + const { element } = props; + const { metadata } = element || {}; + const { path } = metadata || {}; + const data = { + nodeName: element.name, + selectedNodePath: path, + }; + + return ( + + ); + }} + /> +
+ ) : ( + + )} + ); }; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx new file mode 100644 index 0000000000..bddd778522 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONViewer.jsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { JSONTree } from 'react-json-tree'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft'; +import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight'; +import { getTheme } from './utils'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { TreeViewHeader } from './TreeViewHeader'; +import useCallbackActions from './useCallbackActions'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +export const JSONViewer = (props) => { + const { data, path, darkMode, backFn } = props; + const [theme, setTheme] = useState(() => getTheme(darkMode)); + const callbackActions = useCallbackActions() || []; + const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions; + const optionsData = { + nodeName: path?.split('.')?.slice(-1)?.[0] || '', + selectedNodePath: path, + }; + + const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || []; + + useEffect(() => { + setTheme(() => getTheme(darkMode)); + }, [darkMode]); + + return ( +
+ + + { + const key = keyPath[0]; + if (!key && key != 0) return ''; + return key; + }} + valueRenderer={(raw, value) => { + if (typeof value === 'function') { + return ( + + function + + ); + } + + return {raw}; + }} + /> +
+ ); +}; + +export default JSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 6cbd53ed8f..4e3dbf9c73 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { HeaderSection } from '@/_ui/LeftSidebar'; @@ -7,7 +7,7 @@ import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; -import useCallbackActions from './useCallbackActions'; + import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { @@ -19,31 +19,39 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); - const pathToBeInspected = useStore((state) => state.pathToBeInspected); + const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants'])); + const iconsList = useIconList({ exposedComponentsVariables, componentIdNameMapping, exposedQueries, }); - const callbackActions = useCallbackActions(); - console.log('callbackActions', callbackActions); const sortedComponents = useMemo(() => { - return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables); + return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables, searchablePaths.current); }, [exposedComponentsVariables, componentIdNameMapping]); const sortedQueries = useMemo(() => { - return formatInspectorQueryData(queryNameIdMapping, exposedQueries); + return formatInspectorQueryData(queryNameIdMapping, exposedQueries, searchablePaths.current); }, [exposedQueries, queryNameIdMapping]); - const sortedVariables = useMemo(() => formatInspectorDataMisc(exposedVariables), [exposedVariables]); + const sortedVariables = useMemo( + () => formatInspectorDataMisc(exposedVariables, 'variables', searchablePaths.current), + [exposedVariables] + ); - const sortedConstants = useMemo(() => formatInspectorDataMisc(exposedConstants), [exposedConstants]); + const sortedConstants = useMemo( + () => formatInspectorDataMisc(exposedConstants, 'constants', searchablePaths.current), + [exposedConstants] + ); - const sortedPageVariables = useMemo(() => formatInspectorDataMisc(exposedPageVariables), [exposedPageVariables]); + const sortedPageVariables = useMemo( + () => formatInspectorDataMisc(exposedPageVariables, 'page', searchablePaths.current), + [exposedPageVariables] + ); const sortedGlobalVariables = useMemo( - () => formatInspectorDataMisc(exposedGlobalVariables), + () => formatInspectorDataMisc(exposedGlobalVariables, 'globals', searchablePaths.current), [exposedGlobalVariables] ); @@ -52,28 +60,40 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { name: '', children: [ { + id: 'queries', name: 'Queries', children: sortedQueries, + metadata: { path: 'queries' }, }, { + id: 'components', name: 'Components', children: sortedComponents, + metadata: { path: 'components' }, }, { + id: 'globals', name: 'Globals', children: sortedGlobalVariables, + metadata: { path: 'globals' }, }, { + id: 'variables', name: 'Variables', children: sortedVariables, + metadata: { path: 'variables' }, }, { + id: 'page', name: 'Page', children: sortedPageVariables, + metadata: { path: 'page' }, }, { + id: 'constants', name: 'Constants', children: sortedConstants, + metadata: { path: 'constants' }, }, ], }; @@ -81,29 +101,13 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { return jsontreeData; }, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]); - const handleNodeExpansion = (path, data, currentNode) => { - if (pathToBeInspected && path?.length > 0) { - const shouldExpand = pathToBeInspected.includes(path[path.length - 1]); - - // Scroll to the component in the inspector - if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) { - const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`); - if (target) { - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - } - - return shouldExpand; - } else return false; - }; - return (
- +
{ darkMode={darkMode} styles={{ width: '28px', padding: 0 }} data-cy={`left-sidebar-inspector`} - variant="tertiary" + variant="ghostBlack" className="left-sidebar-header-btn" leftIcon={pinned ? 'unpin' : 'pin'} iconWidth="14" @@ -120,28 +124,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
+
- {/* */}
); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx new file mode 100644 index 0000000000..27fafb22ae --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import WidgetIcon from '@/../assets/images/icons/widgets'; +import { extractComponentName } from './utils'; +import { ToolTip } from '@/_components/ToolTip'; +import Highlighter from 'react-highlight-words'; +import cx from 'classnames'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import OverflowTooltip from '@/_components/OverflowTooltip'; +import { HiddenOptions } from './HiddenOptions'; +import useCallbackActions from './useCallbackActions'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; + +const renderNodeIcons = (node, iconsList, darkMode) => { + const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0]; + if (icon && icon?.iconPath) { + return ( + + ); + } + if (icon && icon.jsx) { + if (icon?.tooltipMessage) { + return ( + +
{icon.jsx()}
+
+ ); + } + return icon.jsx(); + } +}; + +export const Node = (props) => { + const { + element, + getNodeProps, + level, + handleSelect, + handleExpand, + isExpanded, + isDisabled, + isBranch, + darkMode, + setSelectedNodePath, + searchValue, + iconsList, + data, + } = props; + + const [actionClicked, setActionClicked] = useState(false); + const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow); + const callbackActions = useCallbackActions() || []; + const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode); + const metadata = element.metadata || {}; + const { type } = metadata; + const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for)); + const onSelect = (node) => { + const { isBranch, element } = node || {}; + const { metadata } = element || {}; + const { path } = metadata || {}; + if (!isBranch) { + setSelectedNodePath(path); + } else { + setSelectedNodePath(null); + } + + setSelectedNodes(path); + }; + const nodeSpecificFilteredActions = + nodeSpecificActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }) || []; + + const generalActions = callbackActions.filter((action) => action.for === 'all'); + const generalActionsFiltered = generalActions?.[0]?.actions?.filter((action) => { + return action.enableInspectorTreeView; + }); + + return ( + //
+
onSelect(props)} + style={{ + marginLeft: 22 * (level - 1), + opacity: isDisabled ? 0.5 : 1, + height: level === 1 ? '28px' : '32px', + display: 'flex', + alignItems: 'center', + color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', + cursor: isBranch || level === 1 ? 'pointer' : 'default', + }} + > + {(isBranch || level === 1) && ( +
+ {isExpanded ? ( + + ) : ( + + )} +
+ )} +
+ {nodeIcon &&
{nodeIcon}
} +
+ + + +
+
+ +
+
+
+ //
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx new file mode 100644 index 0000000000..7b6d43acfb --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx @@ -0,0 +1,148 @@ +import React, { useEffect, useState } from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; +import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft'; +import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; +import cx from 'classnames'; +import { ToolTip } from '@/_components/ToolTip'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { DefaultCopyIcon } from './DefaultCopyIcon'; + +export const TreeViewHeader = (props) => { + const { path, backFn, darkMode, data, nodeSpecificActions, type, generalActions } = props; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + const [showMenu, setShowMenu] = useState(false); + const pathArray = path.split('.'); + const parentNode = pathArray[0]; + + const closeMenu = () => { + setShowMenu(false); + }; + + const copyPath = () => { + generalActions[0].dispatchAction(data?.selectedNodePath); + }; + + const copyValue = () => { + const value = getResolvedValue(`{{${data?.selectedNodePath}}}`); + generalActions[0].dispatchAction(value); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (event.target.closest('.copy-menu-options') === null) { + closeMenu(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderOptions = () => { + return ( + <> +
+
{ + event.stopPropagation(); + copyPath(); + closeMenu(); + }} + className="option" + > + + Copy path +
+
{ + event.stopPropagation(); + copyValue(); + closeMenu(); + }} + className="option" + > + + Copy value +
+
+ + {nodeSpecificActions?.map((actionOption, index) => { + const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + if (icon) { + return ( +
+ { + event.stopPropagation(); + dispatchAction(data); + setShowMenu(false); + }} + className="option" + > + + {name} + +
+ ); + } + })} + + ); + }; + + return ( +
+
+ +
+ + + {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} + + + {pathArray.length > 1 && + pathArray.slice(1).map((item, index) => ( + <> + + + {item.charAt(0).toUpperCase() + item.slice(1)} + + + ))} + + + {renderOptions()} + + } + > +
{ + event.stopPropagation(); + setShowMenu((prev) => !prev); + }} + className="copy-menu-options-icon json-viewer-options-btn" + style={{ + outline: 'none', + }} + > + +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 937fe45b2c..6651e5813f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -10,19 +10,34 @@ const useCallbackActions = () => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const runQuery = useStore((state) => state.queryPanel.runQuery); const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll); + const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getQueryIdFromName = useStore((state) => state.getQueryIdFromName, shallow); const handleRemoveComponent = (component) => { - deleteComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + deleteComponents([componentId]); }; const handleSelectComponentOnEditor = (component) => { - if (currentPageComponents?.[component.id]) { - setSelectedComponents([component.id]); + const { nodeName } = component; + const componentId = getComponentIdFromName(nodeName); + if (currentPageComponents?.[componentId]) { + setSelectedComponents([componentId]); } }; - const handleRunQuery = (query, currentNode) => { - runQuery(query.id, currentNode, undefined, 'edit', {}, true); + const handleRunQuery = (data) => { + const { nodeName } = data; + const queryId = getQueryIdFromName(nodeName); + runQuery(queryId, nodeName, undefined, 'edit', {}, true); + }; + + const selectQuery = (data) => { + const { nodeName } = data; + const id = getQueryIdFromName(nodeName); + setSelectedQuery(id); }; const copyToClipboard = (data) => { @@ -32,7 +47,8 @@ const useCallbackActions = () => { }; const handleAutoScrollToComponent = (data) => { - const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id); + const componentId = getComponentIdFromName(data.nodeName); + const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId); if (!isAccessible) { if (isOnCanvas) { toast.success( @@ -60,6 +76,16 @@ const useCallbackActions = () => { src: 'assets/images/icons/editor/play.svg', width: 8, height: 8, + enableInspectorTreeView: false, + }, + { + name: 'View query', + dispatchAction: selectQuery, + icon: true, + src: 'assets/images/icons/editor/file-code.svg', + width: 14, + height: 14, + enableInspectorTreeView: true, }, ], enableForAllChildren: false, @@ -68,10 +94,30 @@ const useCallbackActions = () => { { for: 'components', actions: [ - { name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true }, - { name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, iconName: 'select' }, + { + name: 'Select Widget', + dispatchAction: handleSelectComponentOnEditor, + icon: false, + onSelect: true, + enableInspectorTreeView: false, + }, + { + name: 'Go to component', + dispatchAction: handleAutoScrollToComponent, + icon: true, + iconName: 'select', + enableInspectorTreeView: true, + }, ...(!shouldFreeze - ? [{ name: 'Delete Component', dispatchAction: handleRemoveComponent, icon: true, iconName: 'trash' }] + ? [ + { + name: 'Delete Component', + dispatchAction: handleRemoveComponent, + icon: true, + iconName: 'trash', + enableInspectorTreeView: false, + }, + ] : []), ], enableForAllChildren: false, @@ -79,7 +125,10 @@ const useCallbackActions = () => { }, { for: 'all', - actions: [{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false }], + actions: [ + { name: 'Copy value', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + { name: 'Copy path', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true }, + ], }, ]; return callbackActions; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 5fc6176bf4..13ad6cc3f4 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -1,21 +1,41 @@ -export const formatInspectorDataMisc = (obj) => { +export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => { if (typeof obj !== 'object' || obj === null) return []; const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path, level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || (path === 'page.variables' ? level > 2 : level > 1)) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data.reduce((acc, [name, value]) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'misc' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'misc', + path: currentPath, + ...((path === 'page.variables' ? level === 2 : level === 1) && { + data: typeof value === 'object' ? JSON.stringify(value) : value, + }), + }, + }, + ]; }, []); }; - return reduceData(data); + return reduceData(data, type); }; -export const formatInspectorComponentData = (componentIdNameMapping, exposedComponentsVariables) => { +export const formatInspectorComponentData = ( + componentIdNameMapping, + exposedComponentsVariables, + searchablePaths = new Set() +) => { const data = Object.entries(componentIdNameMapping) .map(([key, name]) => ({ key, @@ -24,23 +44,37 @@ export const formatInspectorComponentData = (componentIdNameMapping, exposedComp })) .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path = 'components', level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || level > 1) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data .filter((item) => item.name) .reduce((acc, { key, name, value }) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'components' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'components', + path: currentPath, + ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), + }, + }, + ]; }, []); }; return reduceData(data); }; -export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => { +export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => { const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); const _sortedQueries = Object.entries(exposedQueries) .map(([key, value]) => ({ @@ -50,16 +84,30 @@ export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries) => })) .sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); - const reduceData = (obj) => { + const reduceData = (obj, path = 'queries', level = 1) => { let data = obj; - if (!obj || typeof obj !== 'object') return []; + if (!obj || typeof obj !== 'object' || level > 1) return []; else if (!Array.isArray(obj)) { data = Object.entries(obj); } return data .filter((item) => item.name) .reduce((acc, { id, name, value }) => { - return [...acc, { name, children: reduceData(value), metadata: { type: 'queries' } }]; + const currentPath = path + `.${name}`; + searchablePaths.add(currentPath); + return [ + ...acc, + { + id: currentPath, + name, + children: reduceData(value, currentPath, level + 1), + metadata: { + type: 'queries', + path: currentPath, + ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), + }, + }, + ]; }, []); }; @@ -76,3 +124,26 @@ export const extractComponentName = (path) => { return null; // Return null if the pattern doesn't match } }; + +export const getTheme = (darkMode) => { + return { + scheme: 'custom', + author: 'chris kempson (http://chriskempson.com)', + base00: 'transparent', + base01: '#303030', + base02: '#505050', + base03: '#b0b0b0', + base04: '#d0d0d0', + base05: '#1B1F24', + base06: '#f5f5f5', + base07: '#ffffff', + base08: '#fb0120', + base09: '#9467BD', + base0A: '#fda331', + base0B: '#2CA02C', + base0C: '#76c7b7', + base0D: '#e4e0db', + base0E: '#d381c3', + base0F: '#be643c', + }; +}; diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index b746f1237b..cd7dc8a361 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'; @@ -1574,6 +1575,10 @@ 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] || []; diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js new file mode 100644 index 0000000000..b0b8a95e84 --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js @@ -0,0 +1,34 @@ +const initialState = { + selectedNodes: new Set(), + searchedNodes: new Set(), + inspectorSearchValue: '', + inspectorSearchResults: new Set(), +}; + +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 }); + }, +}); diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 4d1392fc7c..547975d8e0 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -28,6 +28,7 @@ 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'; export default create( zustandDevTools( @@ -60,6 +61,7 @@ export default create( ...createGitSyncSlice(...state), ...createAiSlice(...state), ...createWhiteLabellingSlice(...state), + ...createInspectorSlice(...state), })), { name: 'App Builder Store', anonymousActionType: 'unknown' } ) diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index e84756dca7..6390f20c20 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -95,10 +95,10 @@ $btn-dark-color: #FFFFFF; } .leftsidebar-panel-header { - background-color: var(--slate3); - padding: 12px 16px; + // background-color: var(--slate3); + padding: 12px 16px 8px 16px; min-height: 52px; - border-bottom: 1px solid var(--slate5); + // border-bottom: 1px solid var(--slate5); .panel-header-container { diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 0bc2abcdd3..4d4d57341a 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18881,8 +18881,10 @@ section.ai-message-prompt-input-wrapper { .basic.tree { list-style: none; margin: 0; - padding: 23.4px; + padding: 20px; + padding-top: 0px; } + .basic .tree-node, .basic .tree-node-group { list-style: none; @@ -18930,4 +18932,195 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; font-size:12px; +} + + +.tj-inspector-search-input { + width: 300px; + height: 32px; + border-radius: 6px; + background-color: var(--base) !important; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; +} + +.node-content { + display: flex; + align-items: center; + padding: 0px 8px; + height: 100%; + width: 100%; + border-radius: 6px; +} + +.node-content-active { + .copy-menu-options-icon { + background-color: var(--interactive-overlays-fill-hover); + } +} + +.node-content-hoverable:hover, .node-content-active { + background-color: var(--interactive-overlays-fill-hover); + .node-actions { + display: flex; + } + +} + +.json-viewer { + font-size: 11px; + + ul { + background-color: transparent !important; + padding-left: 6px !important; + padding-right: 10px !important; + // li{ + // text-indent: 0px !important; + // padding-top: 0px !important; + // height:18.46px; + // } + // ul > li:hover { + // background-color: var(--interactive-overlays-fill-hover); // or any color you prefer + // } + } + + + +} + +.json-viewer-node-value { + font-weight: 500; +} + +.json-viewer-header { + font-size: 12px; + font-weight: 500; + display: flex; + flex-direction: row; + margin-left:20px; + margin-bottom:18px; + margin-right: 18px; +} + +.json-viewer-back-btn { + display: flex; + align-items: center; + + &:hover { + cursor: pointer; + background-color:var(--interactive-overlays-fill-hover); + border-radius: 4px; + } + +} + +.json-viewer-options-btn { + display: flex; + align-items: center; + margin-left: auto; + + &:hover { + cursor: pointer; + background-color:var(--interactive-overlays-fill-hover); + border-radius: 4px; + } +} + + +.node-highlight { + background-color: #FFD43B; + padding:0px; +} + +.node-actions { + + justify-content: center; + align-items: center; + margin-left:auto; + margin-right: 24px; + display: none; + gap: 4px; +} + +.node-action-icon { + display:flex; + justify-content: center; + align-items: center; + height: 20px; + width: 20px; + border-radius: 4px; + margin-right:4px; + + &:hover { + background-color: var(--button-outline-hover); + } + +} + +.copy-menu-options-icon { + border: 1px solid var(--border-weak, #E4E7EB); + display: flex; + justify-content: center; + align-items: center; + width: 20px; + height: 20px; + border-radius: 4px; + box-shadow: var(--elevation-100-box-shadow); + background-color: var(--base); + + &:hover { + background-color: var(--button-outline-hover); + } +} + +.copy-menu-options{ + width: 144px; + border: none; + background: transparent; + border-radius: 10px; + &.dark-theme { + .popover-body { + box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; + } + } + .popover-body { + width: 100%; + padding: 8px; + border-radius: 10px; + background: var(--background-surface-layer-01); + box-shadow: 0px 0px 1px 0px rgba(48, 50, 51, 0.05), 0px 8px 16px 0px rgba(48, 50, 51, 0.1); + + .menu-options { + .option { + display: flex; + padding: 6px 8px; + align-items: center; + gap: 6px; + height: 30px; + align-self: stretch; + border-radius: 6px; + cursor: pointer; + &:hover{ + background: rgba(136, 144, 153, 0.08); + } + } + } + } +} + + +.invisible-overlay { + position: fixed; + width:287px; + height:20px; + left:28px; + z-index: 9999; + transform: translateY(-3px); + + &:hover { + background-color: var(--interactive-overlays-fill-hover); + border-radius: 4px; + } } \ No newline at end of file From 773cbcf4dc2a278b4cceee634d43085a44253254 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Fri, 25 Apr 2025 21:12:32 +0530 Subject: [PATCH 06/84] Added the logic to drop the modules and load the modules as a viewer --- frontend/ee | 2 +- frontend/src/AppBuilder/AppBuilder.jsx | 29 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 39 ++- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 9 +- .../src/AppBuilder/AppCanvas/Container.jsx | 14 +- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 21 +- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 37 ++- frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 4 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 36 ++- .../AppBuilder/AppCanvas/appCanvasUtils.js | 11 +- .../src/AppBuilder/AppCanvas/selecto.scss | 15 +- .../AppBuilder/AppCanvas/useSidebarMargin.js | 4 +- .../src/AppBuilder/CodeEditor/PreviewBox.jsx | 4 +- .../CodeEditor/SingleLineCodeEditor.jsx | 4 +- .../AppBuilder/Header/AppVersionsManager.jsx | 12 +- .../AppBuilder/Header/CreateVersionModal.jsx | 4 +- .../src/AppBuilder/Header/EditAppName.jsx | 9 +- .../AppBuilder/Header/EditVersionModal.jsx | 4 +- .../src/AppBuilder/Header/EditorHeader.jsx | 7 +- .../PromoteVersionButton.jsx | 4 +- .../ReleaseVersionButton.jsx | 4 +- .../RightTopHeaderButtons.jsx | 12 +- .../LeftSidebar/Debugger/Debugger.jsx | 2 +- .../LeftSidebar/GlobalSettings/AppExport.jsx | 4 +- .../GlobalSettings/CanvasSettings.jsx | 4 +- .../LeftSidebar/GlobalSettings/SlugInput.jsx | 10 +- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 7 +- .../LeftSidebarInspector.jsx | 26 +- .../LeftSidebar/PageMenu/PageHandlerMenu.jsx | 6 +- .../LeftSidebar/PageMenu/PageMenuItem.jsx | 12 +- .../QueryManager/QueryEditors/Workflows.jsx | 4 +- .../src/AppBuilder/QueryPanel/QueryCard.jsx | 4 +- .../Inspector/Components/DatetimePickerV2.jsx | 4 +- .../RightSideBar/Inspector/EventManager.jsx | 8 +- .../Viewer/MobileNavigationMenu.jsx | 12 +- frontend/src/AppBuilder/Viewer/PageGroup.jsx | 4 +- frontend/src/AppBuilder/Viewer/Viewer.jsx | 34 ++- .../Viewer/ViewerSidebarNavigation.jsx | 6 +- .../AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 4 +- frontend/src/AppBuilder/Widgets/Modal.jsx | 4 +- .../AppBuilder/Widgets/ModalV2/ModalV2.jsx | 4 +- .../src/AppBuilder/Widgets/Table/Table.jsx | 6 +- .../AppBuilder/_contexts/ModuleContext.jsx | 16 +- frontend/src/AppBuilder/_hooks/useAppData.js | 204 ++++++++------ .../src/AppBuilder/_stores/slices/appSlice.js | 146 +++++++--- .../_stores/slices/componentsSlice.js | 252 +++++++++++------- .../_stores/slices/createSelectors.js | 13 - .../_stores/slices/dataQuerySlice.js | 67 +++-- .../_stores/slices/debuggerSlice.js | 22 +- .../_stores/slices/dependencySlice.js | 47 ++-- .../AppBuilder/_stores/slices/eventsSlice.js | 156 ++++++----- .../_stores/slices/leftSideBarSlice.js | 6 +- .../AppBuilder/_stores/slices/loaderSlice.js | 43 ++- .../AppBuilder/_stores/slices/modeSlice.js | 31 ++- .../_stores/slices/multiplayerSlice.js | 4 +- .../_stores/slices/pageMenuSlice.js | 20 +- .../_stores/slices/queryPanelSlice.js | 136 ++++++---- .../_stores/slices/resolvedSlice.js | 57 ++-- frontend/src/AppLoader/AppLoader.jsx | 2 +- frontend/src/_styles/theme.scss | 17 +- .../PromoteVersionButton.jsx | 4 +- .../PromoteConfirmationModal.jsx | 4 +- .../ReleaseVersionButton.jsx | 4 +- server/ee | 2 +- server/src/modules/apps/constants/index.ts | 1 + server/src/modules/apps/service.ts | 12 +- .../src/modules/apps/services/page.service.ts | 16 ++ server/src/modules/apps/util.service.ts | 51 +++- 68 files changed, 1153 insertions(+), 630 deletions(-) delete mode 100644 frontend/src/AppBuilder/_stores/slices/createSelectors.js diff --git a/frontend/ee b/frontend/ee index 45c8130ae5..f00cc3fde4 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 45c8130ae5ee82abdfe1843c25dbdf694aac3433 +Subproject commit f00cc3fde44dcb082aa81d75abc9f5e879f3aade diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 48b280aa7c..5190312efa 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -14,6 +14,7 @@ import EditorHeader from '@/AppBuilder/Header'; import LeftSidebar from '@/AppBuilder/LeftSidebar'; import Popups from './Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import { shallow } from 'zustand/shallow'; // const EditorHeader = lazy(() => import('@/AppBuilder/Header')); // const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar')); @@ -22,13 +23,13 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; // const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel')); // TODO: split Loader into separate component and remove editor loading state from Editor -export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType }) => { +export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType = 'front-end' }) => { useAppData(appId, moduleId, darkMode); - const isEditorLoading = useStore((state) => state.isEditorLoading); - const currentMode = useStore((state) => state.currentMode); + const isEditorLoading = useStore((state) => state.loaderStore.modules[moduleId].isEditorLoading, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const isModuleEditor = appType === 'module'; - const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode); + const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow); const changeToDarkMode = (newMode) => { updateIsTJDarkMode(newMode); @@ -46,19 +47,19 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod return (
- Loading...
}> - - - - {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - + + Loading...
}> + + + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } + - - - + + +
); diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e8183cc535..e708926dc4 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -18,15 +18,19 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = false }) => { + const moduleId = useModuleId(); + const isModuleMode = useIsModuleMode(); const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); - const canvasHeight = useStore((state) => state.canvasHeight); - const creationMode = useStore((state) => state.app.creationMode); - const environmentLoadingState = useStore((state) => state.environmentLoadingState || state.isEditorLoading); - const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth()); + const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight); + const creationMode = useStore((state) => state.appStore.modules[moduleId].app.creationMode); + const environmentLoadingState = useStore( + (state) => state.environmentLoadingState || state.loaderStore.modules[moduleId].isEditorLoading + ); + const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth(moduleId)); const gridWidth = canvasWidth / NO_OF_GRIDS; - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const pageSidebarStyle = useStore((state) => state?.pageSettings?.definition?.properties?.style, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const queryPanelHeight = useStore((state) => state?.queryPanel?.queryPanelHeight || 0); @@ -41,8 +45,6 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); - const moduleId = useModuleId(); - const isModuleMode = useIsModuleMode(); const hideSidebar = isModuleMode || isPagesSidebarHidden; @@ -55,14 +57,29 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType }) => { useEffect(() => { function handleResize() { - const _canvasWidth = document.getElementById('real-canvas')?.getBoundingClientRect()?.width; + const _canvasWidth = + moduleId === 'canvas' + ? document.getElementById('real-canvas')?.getBoundingClientRect()?.width + : document.getElementById(moduleId)?.getBoundingClientRect()?.width; if (_canvasWidth !== 0) setCanvasWidth(_canvasWidth); } - window.addEventListener('resize', handleResize); + + if (moduleId === 'canvas') { + window.addEventListener('resize', handleResize); + } else { + const elem = document.getElementById(moduleId); + const resizeObserver = new ResizeObserver(handleResize); + if (elem) resizeObserver.observe(elem); + + return () => { + if (elem) resizeObserver.unobserve(elem); + resizeObserver.disconnect(); + }; + } handleResize(); return () => window.removeEventListener('resize', handleResize); - }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]); + }, [currentLayout, canvasMaxWidth, isViewerSidebarPinned, moduleId]); const styles = useMemo(() => { const canvasBgColor = diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index ad972f7e90..10a738adcb 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -4,6 +4,7 @@ import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -18,10 +19,11 @@ export const ConfigHandle = ({ showHandle, componentType, visibility, - hideDelete, + isModuleContainer, }) => { + const moduleId = useModuleId(); const shouldFreeze = useStore((state) => state.getShouldFreeze()); - const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow); + const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow); const isMultipleComponentsSelected = useStore( (state) => (findHighestLevelofSelection(state?.selectedComponents)?.length > 1 ? true : false), shallow @@ -42,6 +44,7 @@ export const ConfigHandle = ({ // If one component is hovered and one is selected, show the handle for the hovered component return ( isWidgetHovered || + isModuleContainer || (showHandle && (!isMultipleComponentsSelected || (isModal && isModalOpen)) && !anyComponentHovered) ); }, shallow); @@ -124,7 +127,7 @@ export const ConfigHandle = ({ data-cy={`${componentName.toLowerCase()}-inspect-button`} className="config-handle-inspect" /> - {!hideDelete && ( + {!isModuleContainer && ( { diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 6a5afd83e2..b12f9a9634 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -21,6 +21,7 @@ import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; //TODO: Revisit the logic of height (dropRef) /* @@ -46,8 +47,10 @@ export const Container = React.memo( componentType, appType, }) => { + const moduleId = useModuleId(); const realCanvasRef = useRef(null); - const components = useStore((state) => state.getContainerChildrenMapping(id), shallow); + const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); + const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); @@ -56,12 +59,13 @@ export const Container = React.memo( shallow ); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); - const currentMode = useStore((state) => state.currentMode, shallow); + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); + const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; - }, [componentType, index, currentMode]); + }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', @@ -144,7 +148,7 @@ export const Container = React.memo( } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; useEffect(() => { - useGridStore.getState().actions.setSubContainerWidths(id, getContainerCanvasWidth() / NO_OF_GRIDS); + useGridStore.getState().actions.setSubContainerWidths(id, gridWidth); // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasWidth, listViewMode, columns]); @@ -229,7 +233,7 @@ export const Container = React.memo( }} className={cx('real-canvas', { 'sub-canvas': id !== 'canvas', - 'show-grid': isOverCurrent && (index === 0 || index === null), + 'show-grid': isOverCurrent && (index === 0 || index === null) && currentMode === 'edit', })} id={id === 'canvas' ? 'real-canvas' : `canvas-${id}`} data-cy="real-canvas" diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index abddc4681f..adef3eea83 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -29,7 +29,7 @@ import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; - +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -38,12 +38,13 @@ const RESIZABLE_CONFIG = { export const GRID_HEIGHT = 10; export default function Grid({ gridWidth, currentLayout, appType }) { + const moduleId = useModuleId(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [boxList, setBoxList] = useState([]); - const currentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + const currentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const selectedComponents = useStore((state) => state.selectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); @@ -128,7 +129,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const noOfBoxs = Object.values(boxList || []).length; useEffect(() => { - updateCanvasBottomHeight(boxList); + updateCanvasBottomHeight(boxList, moduleId); noOfBoxs != 0; // eslint-disable-next-line react-hooks/exhaustive-deps }, [noOfBoxs, triggerCanvasUpdater]); @@ -329,12 +330,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }; const isComponentVisible = (id) => { - // Return TRUE if it is a module container - if (appType === 'module') { - return true; - } - - const component = getResolvedComponent(id); + const component = getResolvedComponent(id, null, moduleId); let visibility; if (isArray(component)) { visibility = component?.[0]?.properties?.visibility ?? component?.[0]?.styles?.visibility ?? null; @@ -415,6 +411,10 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const moveableBox = document.querySelector(`.moveable-control-box`); const showConfigHandle = (e) => { const targetId = e.target.offsetParent.getAttribute('target-id'); + const componentType = getComponentTypeFromId(targetId); + if (componentType === 'ModuleContainer') { + return; + } useStore.getState().setHoveredComponentBoundaryId(targetId); }; const hideConfigHandle = () => { @@ -646,7 +646,6 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }} onResizeEnd={(e) => { try { - console.log('end--- e.target.id', e.target.id); useGridStore.getState().actions.setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index b26586dc08..5e72d09797 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -7,6 +7,8 @@ import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; import { BOX_PADDING } from './appCanvasConstants'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; + const shouldAddBoxShadowAndVisibility = [ 'Table', 'TextInput', @@ -38,23 +40,27 @@ const RenderWidget = ({ inCanvas = false, darkMode, }) => { - const componentDefinition = useStore((state) => state.getComponentDefinition(id), shallow); + const moduleId = useModuleId(); + const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow); const component = componentDefinition?.component; const componentName = component?.name; const [key, setKey] = useState(Math.random()); const resolvedProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.properties, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.properties, + shallow + ); + const resolvedStyles = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.styles, shallow ); - const resolvedStyles = useStore((state) => state.getResolvedComponent(id, subContainerIndex)?.styles, shallow); const fireEvent = useStore((state) => state.eventsSlice.fireEvent, shallow); const resolvedGeneralProperties = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.general, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.general, shallow ); const resolvedGeneralStyles = useStore( - (state) => state.getResolvedComponent(id, subContainerIndex)?.generalStyles, + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.generalStyles, shallow ); const unResolvedValidation = componentDefinition?.component?.definition?.validation || {}; @@ -64,10 +70,13 @@ const RenderWidget = ({ const setExposedValue = useStore((state) => state.setExposedValue, shallow); const setExposedValues = useStore((state) => state.setExposedValues, shallow); const setDefaultExposedValues = useStore((state) => state.setDefaultExposedValues, shallow); - const resolvedValidation = useStore((state) => state.getResolvedComponent(id)?.validation, shallow); + const resolvedValidation = useStore( + (state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.validation, + shallow + ); const parentId = component?.parent; const customResolvables = useStore( - (state) => state.resolvedStore.modules.canvas?.customResolvables?.[parentId], + (state) => state.resolvedStore.modules[moduleId]?.customResolvables?.[parentId], shallow ); const { t } = useTranslation(); @@ -100,31 +109,31 @@ const RenderWidget = ({ (key, value) => { // Check if the component is inside the subcontainer and it has its own onOptionChange(setExposedValue) function if (onOptionChange === null) { - setExposedValue(id, key, value); + setExposedValue(id, key, value, moduleId); // Trigger an update when the child components is directly linked to any component - updateDependencyValues(`components.${id}.${key}`); + updateDependencyValues(`components.${id}.${key}`, moduleId); } else { onOptionChange(key, value, id, subContainerIndex); } }, - [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange] + [id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange, moduleId] ); const setExposedVariables = useCallback( (exposedValues) => { if (onOptionsChange === null) { - setExposedValues(id, 'components', exposedValues); + setExposedValues(id, 'components', exposedValues, moduleId); } else { onOptionsChange(exposedValues, id, subContainerIndex); } }, - [id, setExposedValues, onOptionsChange] + [id, setExposedValues, onOptionsChange, moduleId] ); const fireEventWrapper = useCallback( (eventName, options) => { - fireEvent(eventName, id, 'canvas', customResolvables?.[subContainerIndex] ?? {}, options); + fireEvent(eventName, id, moduleId, customResolvables?.[subContainerIndex] ?? {}, options); return Promise.resolve(); }, - [fireEvent, id, customResolvables, subContainerIndex] + [fireEvent, id, customResolvables, subContainerIndex, moduleId] ); const onComponentClick = useStore((state) => state.eventsSlice.onComponentClickEvent); diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index c92cf75a1e..24426f6050 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -5,8 +5,10 @@ import './selecto.scss'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; import { findHighestLevelofSelection } from './Grid/gridUtils'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { + const moduleId = useModuleId(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); @@ -16,7 +18,7 @@ export const EditorSelecto = () => { const filterSelectedComponentsByHighestLevel = (selectedIds) => { const highestLevelComponents = findHighestLevelofSelection( selectedIds.map((id) => { - const component = getComponentDefinition(id); + const component = getComponentDefinition(id, moduleId); return { ...component, id, diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index ac60a5790f..42b07dacbe 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -6,6 +6,8 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle'; import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { NO_OF_GRIDS } from './appCanvasConstants'; const WidgetWrapper = memo( ({ @@ -20,23 +22,27 @@ const WidgetWrapper = memo( mode, darkMode, }) => { + const moduleId = useModuleId(); const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow); const stylesDefinition = useStore( - (state) => state.getComponentDefinition(id)?.component?.definition?.styles, + (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - const layoutData = useStore((state) => state.getComponentDefinition(id)?.layouts?.[currentLayout], shallow); + let layoutData = useStore((state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); - const componentType = useStore((state) => state.getComponentDefinition(id)?.component?.component, shallow); + const componentType = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.component?.component, + shallow + ); const setHoveredComponentForGrid = useStore((state) => state.setHoveredComponentForGrid, shallow); const canShowInCurrentLayout = useStore((state) => { - const others = state.getResolvedComponent(id, subContainerIndex)?.others; + const others = state.getResolvedComponent(id, subContainerIndex, moduleId)?.others; return others?.[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop']; }); const visibility = useStore((state) => { - const component = state.getResolvedComponent(id, subContainerIndex); + const component = state.getResolvedComponent(id, subContainerIndex, moduleId); if (component?.properties?.visibility === false || component?.styles?.visibility === false) return false; return true; }); @@ -45,6 +51,13 @@ const WidgetWrapper = memo( return null; } + // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer + if (componentType === 'ModuleContainer' && mode === 'view') { + layoutData.top = 0; + layoutData.left = 0; + layoutData.width = NO_OF_GRIDS; + } + const width = gridWidth * layoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); const styles = { @@ -55,6 +68,8 @@ const WidgetWrapper = memo( border: visibility === false ? `1px solid var(--border-default)` : 'none', }; + const isModuleContainer = componentType === 'ModuleContainer'; + if (!componentType) return null; return ( <> @@ -65,6 +80,7 @@ const WidgetWrapper = memo( 'position-absolute': readOnly, 'active-target': isWidgetActive, 'opacity-0': isDragging || isResizing, + 'module-container': isModuleContainer, })} data-id={`${id}`} id={id} @@ -74,12 +90,12 @@ const WidgetWrapper = memo( // zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null, ...styles, }} - onMouseEnter={(e) => { - if (isDragging) return; + onMouseEnter={() => { + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(id); }} onMouseLeave={() => { - if (isDragging) return; + if (isDragging || isModuleContainer) return; setHoveredComponentForGrid(''); }} > @@ -91,8 +107,8 @@ const WidgetWrapper = memo( showHandle={isWidgetActive} componentType={componentType} visibility={visibility} - customClassName={componentType === 'ModuleContainer' ? 'module-container' : ''} - hideDelete={componentType === 'ModuleContainer'} + customClassName={isModuleContainer ? 'module-container' : ''} + isModuleContainer={isModuleContainer} /> )} 1 ? 's' : ''} pasted successfully`); } -export const getCanvasWidth = (currentLayout) => { - if (currentLayout === 'mobile') { - return CANVAS_WIDTHS.deviceWindowWidth; +export const getCanvasWidth = (moduleId = 'canvas') => { + if (moduleId !== 'canvas') { + return '100%'; } + + // if (currentLayout === 'mobile') { + // return CANVAS_WIDTHS.deviceWindowWidth; + // } const windowWidth = window.innerWidth; const widthInPx = windowWidth - (CANVAS_WIDTHS.leftSideBarWidth + CANVAS_WIDTHS.rightSideBarWidth); const canvasMaxWidth = useStore.getState().globalSettings.canvasMaxWidth; diff --git a/frontend/src/AppBuilder/AppCanvas/selecto.scss b/frontend/src/AppBuilder/AppCanvas/selecto.scss index 5602b35d5a..c0f750642c 100644 --- a/frontend/src/AppBuilder/AppCanvas/selecto.scss +++ b/frontend/src/AppBuilder/AppCanvas/selecto.scss @@ -1,20 +1,21 @@ .active-target { outline: 1px solid #4af; -} +} -.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { +.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { outline: 1px solid #4af; z-index: 4 !important; } -.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { +.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { // outline: 1px solid #4af; z-index: 4 !important; } +.main-editor-canvas .widget-target.module-container { + outline: dotted 2px #CCD1D5 !important; +} + // .main-editor-canvas .widget-target:hover { // outline: 1px solid #4af; -// } - - - +// } \ No newline at end of file diff --git a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js index 969e60f398..b9ea871ca2 100644 --- a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js +++ b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js @@ -3,11 +3,13 @@ import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const useSidebarMargin = (canvasContainerRef) => { + const moduleId = useModuleId(); const [editorMarginLeft, setEditorMarginLeft] = useState(0); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); useEffect(() => { if (mode !== 'view') setEditorMarginLeft(isSidebarOpen ? LEFT_SIDEBAR_WIDTH : 0); diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx index 2429973c25..8234bf8af6 100644 --- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx @@ -13,6 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { Overlay } from 'react-bootstrap'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const sanitizeLargeDataset = (data, callback) => { const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -90,11 +91,12 @@ export const PreviewBox = ({ isWorkspaceVariable, validationFn, }) => { + const moduleId = useModuleId(); const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); const [coersionData, setCoersionData] = useState(null); const [largeDataset, setLargeDataset] = useState(false); - const globals = useStore((state) => state.getAllExposedValues().constants || {}, shallow); + const globals = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow); const secrets = useStore((state) => state.getSecrets(), shallow); const getPreviewContent = (content, type) => { diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index c1c0e03aa5..8f36315a64 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -22,8 +22,10 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { + const moduleId = useModuleId(); const { initialValue, onChange, enablePreview = true, portalProps } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); @@ -32,7 +34,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r const [errorStateActive, setErrorStateActive] = useState(false); const [cursorInsidePreview, setCursorInsidePreview] = useState(false); const validationFn = restProps?.validationFn; - const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow); + const componentDefinition = useStore((state) => state.getComponentDefinition(componentId, moduleId), shallow); const parentId = componentDefinition?.component?.parent; const customResolvables = useStore((state) => state.resolvedStore.modules.canvas?.customResolvables, shallow); diff --git a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx index 05009fd531..6ec4a22dd1 100644 --- a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx +++ b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx @@ -6,6 +6,7 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { decodeEntities } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const appVersionLoadingStatus = Object.freeze({ loading: 'loading', @@ -14,6 +15,7 @@ const appVersionLoadingStatus = Object.freeze({ }); export const AppVersionsManager = function ({ darkMode }) { + const moduleId = useModuleId(); const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ @@ -54,15 +56,15 @@ export const AppVersionsManager = function ({ darkMode }) { deleteVersionAction: state.deleteVersionAction, selectedEnvironment: state.selectedEnvironment, currentLayout: state.currentLayout, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, releasedVersionId: state.releasedVersionId, editingVersion: state.editingVersion, setCurrentVersionId: state.setCurrentVersionId, currentVersionId: state.currentVersionId, - creationMode: state.app.creationMode, - isPublic: state.app.isPublic, - isViewer: state.isViewer, - currentMode: state.currentMode, + creationMode: state.appStore.modules[moduleId].app.creationMode, + isPublic: state.appStore.modules[moduleId].app.isPublic, + isViewer: state.appStore.modules[moduleId].isViewer, + currentMode: state.modeStore.modules[moduleId].currentMode, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index a4cb87ad55..888b91963d 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import Select from '@/_ui/Select'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CreateVersionModal = ({ showCreateAppVersion, @@ -17,6 +18,7 @@ const CreateVersionModal = ({ fetchingOrgGit, handleCommitOnVersionCreation = () => {}, }) => { + const moduleId = useModuleId(); const [isCreatingVersion, setIsCreatingVersion] = useState(false); const [versionName, setVersionName] = useState(''); @@ -35,7 +37,7 @@ const CreateVersionModal = ({ developmentVersions: state.developmentVersions, featureAccess: state.license.featureAccess, editingVersion: state.currentVersionId, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, currentVersionId: state.currentVersionId, setCurrentVersionId: state.setCurrentVersionId, selectedVersion: state.selectedVersion, diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 7211bb085b..94da928483 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -8,10 +8,17 @@ import { InfoOrErrorBox } from './InfoOrErrorBox'; import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; function EditAppName() { + const moduleId = useModuleId(); const [appId, appName, setAppName, appCreationMode] = useStore( - (state) => [state.app.appId, state.app.appName, state.setAppName, state.app.creationMode], + (state) => [ + state.appStore.modules[moduleId].app.appId, + state.appStore.modules[moduleId].app.appName, + state.setAppName, + state.appStore.modules[moduleId].app.creationMode, + ], shallow ); diff --git a/frontend/src/AppBuilder/Header/EditVersionModal.jsx b/frontend/src/AppBuilder/Header/EditVersionModal.jsx index 3a126c6285..bb79468ef3 100644 --- a/frontend/src/AppBuilder/Header/EditVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/EditVersionModal.jsx @@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => { + const moduleId = useModuleId(); const [isEditingVersion, setIsEditingVersion] = useState(false); const { updateVersionNameAction, @@ -15,7 +17,7 @@ export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) (state) => ({ updateVersionNameAction: state.updateVersionNameAction, selectedVersion: state.selectedVersion, - appId: state.app.appId, + appId: state.appStore.modules[moduleId].app.appId, }), shallow ); diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index c5420c98bf..dfa8d8940f 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -13,11 +13,14 @@ import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; import { ModuleEditorBanner } from '@/modules/Modules/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; + export const EditorHeader = ({ darkMode, isModuleEditor }) => { + const moduleId = useModuleId(); const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ - isSaving: state.app.isSaving, - saveError: state.app.saveError, + isSaving: state.appStore.modules[moduleId].app.isSaving, + saveError: state.appStore.modules[moduleId].app.saveError, isVersionReleased: state.isVersionReleased, }), shallow diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx index f8902157bc..a3638c9a97 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx @@ -4,12 +4,14 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import PromoteConfirmationModal from './PromoteConfirmationModal'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { + const moduleId = useModuleId(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = 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/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx index 494eaa52e9..3052f05d17 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const moduleId = useModuleId(); 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/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index 021887c824..d709945ca9 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -8,6 +8,7 @@ import { isEmpty } from 'lodash'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RightTopHeaderButtons = ({ isModuleEditor }) => { return ( @@ -21,6 +22,7 @@ const RightTopHeaderButtons = ({ isModuleEditor }) => { }; const PreviewAndShareIcons = () => { + const moduleId = useModuleId(); const { featureAccess, currentPageHandle, @@ -36,14 +38,14 @@ const PreviewAndShareIcons = () => { } = useStore( (state) => ({ featureAccess: state.license?.featureAccess, - currentPageHandle: state?.currentPageHandle, + currentPageHandle: state?.modules[moduleId].currentPageHandle, selectedEnvironment: state.selectedEnvironment, isVersionReleased: state.releasedVersionId === state.selectedVersion?.id, editingVersion: state.editingVersion, - appId: state.app.appId, - app: state.app.app, - slug: state.app.slug, - isPublic: state.app.isPublic, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app.app, + slug: state.appStore.modules[moduleId].app.slug, + isPublic: state.appStore.modules[moduleId].app.isPublic, currentVersionId: state.currentVersionId, selectedVersion: state.selectedVersion, }), diff --git a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx index 7b775e2b08..3823e2bfff 100644 --- a/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/Debugger/Debugger.jsx @@ -10,7 +10,7 @@ function Debugger({ pinned, setPinned }) { shallow ); - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules.canvas.currentPageId); const logsToBeShown = logs.filter((log) => log.page === currentPageId); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx index 4ffa20d26d..bea8953829 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx @@ -3,11 +3,13 @@ import { Button } from '@/components/ui/Button/Button'; import ExportAppModal from '@/HomePage/ExportAppModal'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const AppExport = ({ darkMode }) => { + const moduleId = useModuleId(); const { app } = useStore( (state) => ({ - app: state.app, + app: state.appStore.modules[moduleId].app, }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx index 84d808f345..5d8edf4f41 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx @@ -11,8 +11,10 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton'; import { useTranslation } from 'react-i18next'; import { Confirm } from '@/Editor/Viewer/Confirm'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CanvasSettings = ({ darkMode }) => { + const moduleId = useModuleId(); const { globalSettings, globalSettingsChanged, @@ -24,7 +26,7 @@ const CanvasSettings = ({ darkMode }) => { (state) => ({ globalSettings: state.globalSettings, updateGlobalSettings: state.updateGlobalSettings, - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, globalSettingsChanged: state.globalSettingsChanged, toggleAppMaintenance: state.toggleAppMaintenance, resolveOthers: state.resolveOthers, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx index ae69b4cbf6..269ea98e20 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx @@ -8,9 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes'; import useStore from '@/AppBuilder/_stores/store'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; // import { useStore } from '@/store'; const SlugInput = () => { + const moduleId = useModuleId(); const { slug: oldSlug, appId, @@ -20,11 +22,11 @@ const SlugInput = () => { } = useStore( (state) => ({ globalSettings: state.globalSettings, - slug: state.app.slug, - appId: state.app.appId, - app: state.app, + slug: state.appStore.modules[moduleId].app.slug, + appId: state.appStore.modules[moduleId].app.appId, + app: state.appStore.modules[moduleId].app, setApp: state.setApp, - currentPage: state.modules.canvas.pages[state.currentPageId], + currentPage: state.modules[moduleId].pages[state.modules[moduleId].currentPageIndex], }), shallow ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 4a3507c59e..c2c43f7647 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -10,6 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector'; import GlobalSettings from './GlobalSettings'; import '../../_styles/left-sidebar.scss'; import Debugger from './Debugger/Debugger'; +import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; // TODO: remove passing refs to LeftSidebarItem and use state @@ -24,6 +25,8 @@ export const BaseLeftSidebar = ({ renderAIChat = () => null, isModuleEditor = false, }) => { + const moduleId = useModuleId(); + const appType = useAppType(); const [ pinned, selectedSidebarItem, @@ -41,7 +44,7 @@ export const BaseLeftSidebar = ({ state.selectedSidebarItem, state.setIsLeftSideBarPinned, state.setSelectedSidebarItem, - state.currentMode, + state.modeStore.modules[moduleId].currentMode, state.queryPanel.queryPanelHeight, state.debugger.unreadErrorCount, state.debugger.resetUnreadErrorCount, @@ -99,6 +102,8 @@ export const BaseLeftSidebar = ({ // popoverContentHeight={popoverContentHeight} setPinned={setPinned} pinned={pinned} + moduleId={moduleId} + appType={appType} /> ); case 'tooljetai': diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index a1e483d8e1..6ba24ba7be 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -7,6 +7,7 @@ import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; import useCallbackActions from './useCallbackActions'; +import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const sortAndReduce = (obj) => { return Object.entries(obj) @@ -17,17 +18,17 @@ const sortAndReduce = (obj) => { }, {}); }; -const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { - const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); - const exposedQueries = useStore((state) => state.getAllExposedValues().queries || {}, shallow); - const exposedVariables = useStore((state) => state.getAllExposedValues().variables || {}, shallow); - const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow); - const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); - const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); - const exposedModuleInputs = useStore((state) => state.getAllExposedValues().input || {}, shallow); +const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }) => { + const exposedComponentsVariables = useStore((state) => state.getAllExposedValues(moduleId).components, shallow); + const exposedQueries = useStore((state) => state.getAllExposedValues(moduleId).queries || {}, shallow); + const exposedVariables = useStore((state) => state.getAllExposedValues(moduleId).variables || {}, shallow); + const exposedConstants = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow); + const exposedPageVariables = useStore((state) => state.getAllExposedValues(moduleId).page || {}, shallow); + const exposedGlobalVariables = useStore((state) => state.getAllExposedValues(moduleId).globals || {}, shallow); + const exposedModuleInputs = useStore((state) => state.getAllExposedValues(moduleId).input || {}, shallow); - const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); - const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); + const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(moduleId), shallow); + const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(moduleId), shallow); const pathToBeInspected = useStore((state) => state.pathToBeInspected); const iconsList = useIconList({ exposedComponentsVariables, @@ -87,7 +88,9 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { jsontreeData['variables'] = sortedVariables; jsontreeData['page'] = sortedPageVariables; jsontreeData['constants'] = sortedConstants; - if (sortedModuleInputs) jsontreeData['input'] = sortedModuleInputs; + if (appType === 'module') { + jsontreeData['input'] = sortedModuleInputs; + } return jsontreeData; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -99,6 +102,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { sortedPageVariables, sortedGlobalVariables, sortedModuleInputs, + appType, ]); const handleNodeExpansion = (path, data, currentNode) => { diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index e2a395ecab..9cd9e5dcf5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -2,8 +2,10 @@ import React from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { Button } from '@/_ui/LeftSidebar'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const PageHandlerMenu = ({ darkMode }) => { + const moduleId = useModuleId(); const setShowEditingPopover = useStore((state) => state.setShowEditingPopover); const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal); @@ -42,7 +44,7 @@ export const PageHandlerMenu = ({ darkMode }) => { closePageEditPopover(); }; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const page = editingPage; const isHomePage = page?.id === homePageId; const showMenu = showEditingPopover; @@ -116,7 +118,7 @@ export const PageHandlerMenu = ({ darkMode }) => { text="Mark home" iconSrc={'assets/images/icons/home.svg'} closeMenu={() => {}} - callback={() => markAsHomePage(editingPage.id)} + callback={() => markAsHomePage(editingPage.id, moduleId)} /> )} diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index ca193084fb..962f40554b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -16,12 +16,14 @@ import { RenameInput } from './RenameInput'; import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { - const homePageId = useStore((state) => state.app.homePageId); + const moduleId = useModuleId(); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const isHomePage = page.id === homePageId; - const currentPageId = useStore((state) => state.currentPageId); + const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); const isSelected = page.id === currentPageId; const isHidden = page?.hidden ?? false; const isDisabled = page?.disabled ?? false; @@ -135,9 +137,9 @@ export const PageMenuItem = withRouter( if (currentPageId === page.id) { return; } - switchPage(page.id, page.handle); - setCurrentPageHandle(page.handle); - }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle]); + switchPage(page.id, page.handle, [], moduleId); + setCurrentPageHandle(page.handle, moduleId); + }, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle, moduleId]); const handlePageMenuSettings = useCallback( (event) => { diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index 9aa77601e5..367a71e5c3 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,13 +5,15 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export function Workflows({ options, optionsChanged, currentState }) { + const moduleId = useModuleId(); const [workflowOptions, setWorkflowOptions] = useState([]); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); useEffect(() => { appsService.getWorkflows(appId).then(({ workflows }) => { diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx index fbad517a81..afe6aa6604 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx @@ -13,9 +13,11 @@ import useStore from '@/AppBuilder/_stores/store'; import { Confirm } from '@/Editor/Viewer/Confirm'; // TODO: enable delete query confirmation popup import { debounce } from 'lodash'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => { - const appId = useStore((state) => state.app.appId); + const moduleId = useModuleId(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow); const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery); diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx index 8a24e5eb92..20d7e8c66f 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import useStore from '@/AppBuilder/_stores/store'; import styles from '@/_ui/Select/styles'; import moment from 'moment-timezone'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const DATE_FORMAT_OPTIONS = [ { @@ -101,12 +102,13 @@ const DatetimePickerV2 = ({ componentMeta, componentName, darkMode, ...restProps allComponents, pages, } = restProps; + const moduleId = useModuleId(); const items = []; const additionalActions = []; const properties = []; const formatting = []; const validations = Object.keys(componentMeta.validation || {}); - const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties); + const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id, null, moduleId)?.properties); const isDateFormatFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false; const isTimeFormatFxOn = componentMeta?.definition?.properties?.timeFormat?.fxActive || false; const dateFormat = resolvedProperties?.dateFormat ?? resolvedProperties?.format; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index c3add3f1cc..c1a51eab11 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -30,6 +30,7 @@ import { appService } from '@/_services'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const EventManager = ({ sourceId, @@ -43,6 +44,7 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { + const moduleId = useModuleId(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup @@ -57,7 +59,7 @@ export const EventManager = ({ const allAppEvents = useEvents(); const { createAppVersionEventHandlers, deleteAppVersionEventHandler, updateAppVersionEventHandlers } = useEventActions(); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const eventsUpdatedLoader = useStore((state) => state.eventsSlice.getEventsUpdatedLoader(), shallow); const eventsCreatedLoader = useStore((state) => state.eventsSlice.getEventsCreatedLoader(), shallow); @@ -95,9 +97,9 @@ export const EventManager = ({ return a.index - b.index; }); - setEvents(sortedEvents || []); + setEvents(sortedEvents || [], moduleId); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(currentEvents)]); + }, [JSON.stringify(currentEvents), moduleId]); let actionOptions = ActionTypes.map((action) => { return { name: action.name, value: action.id }; diff --git a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx index e6c8179328..c6759bc931 100644 --- a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx +++ b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx @@ -8,11 +8,13 @@ import Cross from '@/_ui/Icon/solidIcons/Cross'; import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import * as Icons from '@tabler/icons-react'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch, currentPageId, icon }) => { + const moduleId = useModuleId(); const [isExpanded, setIsExpanded] = useState(true); const groupActive = currentPage.pageGroupId === pageGroup?.id; - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const handleToggle = () => { setIsExpanded(!isExpanded); }; @@ -78,8 +80,9 @@ const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch }; const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, currentPage }) => { + const moduleId = useModuleId(); const tree = buildTree(pages); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
@@ -123,6 +126,7 @@ const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, cu }; const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeToDarkMode, showDarkModeToggle }) => { + const moduleId = useModuleId(); const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); const license = useStore((state) => state.license); @@ -134,7 +138,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; var styles = { bmBurgerButton: { @@ -189,7 +193,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan const isLicensed = !_.get(license, 'featureAccess.licenseStatus.isExpired', true) && _.get(license, 'featureAccess.licenseStatus.isLicenseValid', false); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return ( <> diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 94a55d85a3..28fa683df6 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -8,6 +8,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import OverflowTooltip from '@/_components/OverflowTooltip'; import cx from 'classnames'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; @@ -141,11 +142,12 @@ const RenderPageGroup = ({ export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => { // Don't render empty folders if displaying only icons + const moduleId = useModuleId(); const tree = buildTree(pages, !!labelStyle?.label?.hidden); const currentPageId = useStore((state) => state.currentPageId); const currentPage = pages.find((page) => page.id === currentPageId); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return (
{/* page.id)}> */} diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index abf0ef6646..79de594291 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -15,6 +15,7 @@ import ViewerSidebarNavigation from './ViewerSidebarNavigation'; import { shallow } from 'zustand/shallow'; import Popups from '../Popups'; import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; +import Spinner from '@/_ui/Spinner'; export const Viewer = ({ id: appId, @@ -50,28 +51,29 @@ export const Viewer = ({ toggleCurrentLayout, } = useStore( (state) => ({ - isEditorLoading: state.isEditorLoading, - currentMode: state.currentMode, + isEditorLoading: state.loaderStore.modules[moduleId].isEditorLoading, + currentMode: state.modeStore.modules[moduleId].currentMode, currentLayout: state.currentLayout, editingVersion: state.editingVersion, selectedVersion: state.selectedVersion, currentCanvasWidth: state.currentCanvasWidth, - appName: state.app.appName, - homePageId: state?.app.homepageId, - currentPageId: state.currentPageId, + appName: state.appStore.modules[moduleId].app.appName, + homePageId: state.appStore.modules[moduleId].app.homepageId, + currentPageId: state.modules[moduleId].currentPageId, globalSettings: state.globalSettings, - pages: state.modules.canvas.pages, + pages: state.modules[moduleId].pages, modules: state.modules, globalSettingsChanged: state.globalSettingsChanged, pageSettings: state.pageSettings, updateCanvasHeight: state.updateCanvasBottomHeight, - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, setIsViewer: state.setIsViewer, toggleCurrentLayout: state.toggleCurrentLayout, }), shallow ); - const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow); + + const getCurrentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow); const currentPageComponents = useMemo(() => getCurrentPageComponents, [getCurrentPageComponents]); const changeDarkMode = useStore((state) => state.changeDarkMode); const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); @@ -124,9 +126,9 @@ export const Viewer = ({ useEffect(() => { const isMobileDevice = deviceWindowWidth < 600; toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop'); - setIsViewer(true); + setIsViewer(true, moduleId); return () => { - setIsViewer(false); + setIsViewer(false, moduleId); }; }, []); @@ -171,8 +173,8 @@ export const Viewer = ({ if (isEditorLoading) { return ( -
- +
+ {moduleMode ? : }
); } else if (isMaintenanceOn) { @@ -252,9 +254,13 @@ export const Viewer = ({ changeToDarkMode={changeToDarkMode} /> )} - +
- {/* {!licenseValid && isAppLoaded && } */} {isMobilePreviewMode &&
} {isMobilePreviewMode &&
}
diff --git a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx index d913bffd91..94a43bee72 100644 --- a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx +++ b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx @@ -9,6 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { RenderPageAndPageGroup } from './PageGroup'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const ViewerSidebarNavigation = ({ isMobileDevice, @@ -20,10 +21,11 @@ export const ViewerSidebarNavigation = ({ isSidebarPinned, toggleSidebarPinned, }) => { + const moduleId = useModuleId(); const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {}; const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); - const homePageId = useStore((state) => state.app.homePageId); + const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const license = useStore((state) => state.license); if (isMobileDevice) { @@ -95,7 +97,7 @@ export const ViewerSidebarNavigation = ({ version: selectedVersionName, env: selectedEnvironmentName, }; - switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true); + switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), moduleId); }; const isLicensed = diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index 8aa7b578ac..a9e1c58ae9 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -26,6 +26,7 @@ import cx from 'classnames'; import { useGridStore } from '@/_stores/gridStore'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ @@ -40,11 +41,12 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { + const moduleId = useModuleId(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; const { accentColor } = styles; - const mode = useStore((state) => state.currentMode, shallow); + const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const [lastSelectedCard, setLastSelectedCard] = useState({}); // eslint-disable-next-line react-hooks/exhaustive-deps const columnDataAsObj = useMemo(() => convertArrayToObj(columnData), [JSON.stringify(columnData)]); diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index e0f099205f..fe1c433b83 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -7,6 +7,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { debounce } from 'lodash'; var tinycolor = require('tinycolor2'); +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ id, @@ -20,6 +21,7 @@ export const Modal = function Modal({ dataCy, height, }) { + const moduleId = useModuleId(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, @@ -48,7 +50,7 @@ export const Modal = function Modal({ const titleAlignment = properties.titleAlignment ?? 'left'; const size = properties.size ?? 'lg'; const [modalWidth, setModalWidth] = useState(); - const mode = useStore((state) => 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..3c9329a3ea 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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; export const ModalV2 = function Modal({ id, @@ -29,6 +30,7 @@ export const ModalV2 = function Modal({ dataCy, height, }) { + const moduleId = useModuleId(); 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/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 7d378e7d70..0473de3280 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 { useModuleId } 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 = useModuleId(); + 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 f07e30ad65..bbdf89d936 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -2,8 +2,12 @@ import React, { createContext, useContext } from 'react'; export const ModuleContext = createContext(); -export const ModuleProvider = ({ moduleId, isModuleMode, appType, children }) => { - return {children}; +export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor, children }) => { + return ( + + {children} + + ); }; export const useModuleId = () => { @@ -30,3 +34,11 @@ export const useAppType = () => { } 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/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 3caabf5db2..4104868eda 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -28,7 +28,7 @@ import { distinctUntilChanged } from 'rxjs'; import { convertAllKeysToSnakeCase } from '../_stores/utils'; import { getPreviewQueryParams } from '@/_helpers/routes'; import { useLocation, useMatch, useParams } from 'react-router-dom'; - +import { useMounted } from '@/_hooks/use-mount'; /** * this is to normalize the query transformation options to match the expected schema. Takes care of corrupted data. * This will get redundanted once api response for appdata is made uniform across all the endpoints. @@ -62,11 +62,15 @@ const useAppData = ( { 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); @@ -86,7 +90,7 @@ const useAppData = ( 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 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); @@ -110,7 +114,7 @@ const useAppData = ( const setIsEditorFreezed = useStore((state) => state.setIsEditorFreezed); const appMode = useStore((state) => state.globalSettings.appMode); 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); @@ -118,7 +122,6 @@ const useAppData = ( const setModulesIsLoading = useStore((state) => state.setModulesIsLoading, shallow); const setModulesList = useStore((state) => state.setModulesList, shallow); - const initModules = useStore((state) => state.initModules, shallow); const setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); @@ -128,7 +131,7 @@ const useAppData = ( 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 match = useMatch('/applications/:slug/:pageHandle'); @@ -159,11 +162,6 @@ const useAppData = ( } }; - useEffect(() => { - if (!moduleMode) return; - initModules(moduleId); - }, [initModules, moduleId, moduleMode]); - useEffect(() => { if (pageSwitchInProgress && !moduleMode) { const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId); @@ -175,7 +173,7 @@ const useAppData = ( }, [pageSwitchInProgress, currentPageId, moduleMode]); useEffect(() => { - if (moduleMode) return; + // if (moduleMode) return; const subscription = authenticationService.currentSession .pipe( distinctUntilChanged((prev, curr) => { @@ -212,14 +210,32 @@ const useAppData = ( }, [appMode, darkMode, moduleId]); useEffect(() => { - if (!currentSession || moduleMode) { + 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 (moduleMode) { + // appDataPromise = appService.fetchApp(appId); + // appDataPromise.then(async (result) => { + // let appData = { ...result }; + // console.log('appData--- ', appData); + // return; + // // setApp({ + // // appName: appData.name, + // // appId: appData.id, + // // slug: appData.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 (isPublicAccess) { appDataPromise = appService.fetchAppBySlug(slug); } else { @@ -275,7 +291,7 @@ const useAppData = ( editorEnvironment?.name ); - setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); + !moduleMode && setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public); @@ -298,36 +314,42 @@ const useAppData = ( 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); - setGlobalSettings( - mapKeys(appData.editing_version?.global_settings || appData.global_settings, (value, key) => camelCase(key)) + 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); + setGlobalSettings( + mapKeys(appData.editing_version?.global_settings || appData.global_settings, (value, key) => camelCase(key)) + ); + setPageSettings( + computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_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); - if (initialLoadRef.current) { + 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); @@ -344,18 +366,23 @@ const useAppData = ( // navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`); } - setCurrentPageHandle(startingPage.handle); - updateFeatureAccess(); + setCurrentPageHandle(startingPage.handle, moduleId); setCurrentPageId(startingPage.id, moduleId); - setResolvedPageConstants({ - id: startingPage?.id, - handle: startingPage?.handle, - name: startingPage?.name, - }); + setResolvedPageConstants( + { + id: startingPage?.id, + handle: startingPage?.handle, + name: startingPage?.name, + }, + moduleId + ); setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); - setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); - setAppHomePageId(homePageId); + updateEventsField('events', appData.events, moduleId); + if (!moduleMode) { + updateFeatureAccess(); + setCurrentVersionId(appData.editing_version?.id || appData.current_version_id); + } + setAppHomePageId(homePageId, moduleId); const queryData = isPublicAccess || (mode !== 'edit' && appData.is_public) @@ -363,10 +390,13 @@ const useAppData = ( : 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); + setQueries(dataQueries, moduleId); if (dataQueries?.length > 0) { - setSelectedQuery(dataQueries[0]?.id); - initialiseResolvedQuery(dataQueries.map((query) => query.id)); + !moduleMode && setSelectedQuery(dataQueries[0]?.id); + initialiseResolvedQuery( + dataQueries.map((query) => query.id), + moduleId + ); } const constants = constantsResp?.constants; @@ -380,26 +410,31 @@ const useAppData = ( orgSecrets[constant.name] = constant.value; } }); - setResolvedConstants(orgConstants); - setSecrets(orgSecrets); + setResolvedConstants(orgConstants, moduleId); + setSecrets(orgSecrets, moduleId); } 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)))); + 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); // TODO: set mode based on the slug/appDef + setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef if ( + !moduleMode && state.ai && state?.prompt && initialLoadRef.current && @@ -412,7 +447,7 @@ const useAppData = ( showWalkthrough = false; } // fetchDataSources(appData.editing_version.id, editorEnvironment.id); - if (!isPublicAccess) { + 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( @@ -421,24 +456,25 @@ const useAppData = ( editorEnvironment.id ); } - useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed - updateReleasedVersionId(appData.current_version_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); + setEditorLoading(false, moduleId); initialLoadRef.current = false; // only show if app is not created from prompt - if (showWalkthrough) initEditorWalkThrough(); - checkAndSetTrueBuildSuggestionsFlag(); + if (showWalkthrough && !moduleMode) initEditorWalkThrough(); + !moduleMode && checkAndSetTrueBuildSuggestionsFlag(); return () => { document.title = retrieveWhiteLabelText(); }; }); - }, [setApp, setEditorLoading, currentSession, moduleMode]); + }, [setApp, setEditorLoading, currentSession, moduleMode, moduleId]); useEffect(() => { - if (moduleMode) return; 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 @@ -462,13 +498,13 @@ const useAppData = ( 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); @@ -503,7 +539,7 @@ const useAppData = ( ); setCurrentPageId(startingPage.id, moduleId); setComponentNameIdMapping(moduleId); - updateEventsField('events', appData.events); + updateEventsField('events', appData.events, moduleId); // const queryData = await dataqueryService.getAll(currentVersionId); if (isEnvChanged) { @@ -529,7 +565,7 @@ const useAppData = ( 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)); @@ -558,10 +594,10 @@ const useAppData = ( setQueryMapping(moduleId); initDependencyGraph(moduleId); - setEditorLoading(false); + setEditorLoading(false, moduleId); }); } - }, [selectedEnvironment?.id, currentVersionId, moduleMode]); + }, [selectedEnvironment?.id, currentVersionId, moduleMode, moduleId]); useEffect(() => { if (moduleMode) return; diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index 9f2501f444..4ee2e8ac9e 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -9,26 +9,84 @@ import { replaceEntityReferencesWithIds } from '../utils'; import _ from 'lodash'; const initialState = { - app: {}, - canvasHeight: null, isSaving: false, - globalSettings: {}, pageSwitchInProgress: false, isTJDarkMode: localStorage.getItem('darkMode') === 'true', + globalSettings: {}, 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) => { + 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, currentMode, setCanvasHeight } = get(); const maxHeight = Object.values(components).reduce((max, component) => { const layout = component?.layouts?.[currentLayout]; @@ -40,23 +98,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 { @@ -64,9 +124,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; @@ -77,10 +137,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', @@ -93,7 +153,7 @@ export const createAppSlice = (set, get) => ({ console.error('Error updating page:', error); } }, - switchPage: (pageId, handle, queryParams = []) => { + switchPage: (pageId, handle, queryParams = [], moduleId = 'canvas') => { get().debugger.resetUnreadErrorCount(); // reset stores if (get().pageSwitchInProgress) { @@ -120,15 +180,15 @@ export const createAppSlice = (set, get) => ({ const isPreview = currentMode !== '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; @@ -136,7 +196,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; navigate( `/${isPreview ? 'applications' : getWorkspaceId() + '/apps'}/${slug ?? appId}/${handle}?${queryParamsString}`, @@ -147,11 +207,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); @@ -159,8 +222,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 = []; @@ -176,26 +240,30 @@ 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; + }, 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 c90f12e746..741846c7b4 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -29,19 +29,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: [], @@ -53,7 +53,8 @@ export const createComponentsSlice = (set, get) => ({ initializeComponentsSlice: (moduleId) => { set( (state) => { - state.modules[moduleId] = { ...state.modules[moduleId], ...initialState.modules.canvas }; + state.modules[moduleId] = { ...initialState.modules.canvas }; + state.containerChildrenMapping[moduleId] = []; }, false, 'initializeComponentsSlice' @@ -80,16 +81,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] = []; } @@ -99,10 +100,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' @@ -159,7 +160,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]) => { @@ -172,12 +173,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' @@ -187,7 +189,7 @@ export const createComponentsSlice = (set, get) => ({ [componentId]: { component: { name: newName } }, }; - saveComponentChanges(diff, 'components', 'update'); + saveComponentChanges(diff, 'components', 'update', moduleId); renameComponentNameIdMapping(oldName, newName, moduleId); }, @@ -237,7 +239,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) => { @@ -246,9 +256,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); } }); } @@ -340,7 +350,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) : []; @@ -382,7 +392,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] = []; @@ -435,14 +445,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); } @@ -637,7 +647,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,7 +672,15 @@ export const createComponentsSlice = (set, get) => ({ ); updatedPropertyValue[index] = updatedValue; if (allRefs.length) { - generateDependencyGraphForRefs(allRefs, componentId, paramType, propertyWithArrayValue, unResolvedValue); + generateDependencyGraphForRefs( + allRefs, + componentId, + paramType, + propertyWithArrayValue, + unResolvedValue, + false, + moduleId + ); } } }); @@ -671,7 +697,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 }; } @@ -709,7 +735,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( @@ -726,7 +752,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 = {}; @@ -762,9 +788,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; } @@ -795,7 +821,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 ( @@ -809,7 +837,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 = @@ -863,7 +891,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, @@ -874,7 +902,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 }) @@ -899,7 +927,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; @@ -908,7 +940,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)) { @@ -929,7 +961,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; @@ -941,12 +973,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); @@ -955,7 +993,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 }); @@ -963,7 +1001,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 @@ -989,7 +1027,7 @@ export const createComponentsSlice = (set, get) => ({ 'deleteComponents' ); componentNames.forEach((componentName) => { - deleteComponentNameIdMapping(componentName); + deleteComponentNameIdMapping(componentName, moduleId); }); }, @@ -1038,12 +1076,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]) => { @@ -1067,8 +1107,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 ); } @@ -1080,7 +1120,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 ============ @@ -1147,7 +1187,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'); } }, @@ -1163,7 +1203,7 @@ export const createComponentsSlice = (set, get) => ({ { skipUndoRedo = false, saveAfterAction = true } = {} ) => { const { - currentPageIndex, + getCurrentPageIndex, saveComponentChanges, withUndoRedo, updateResolvedValues, @@ -1174,12 +1214,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( @@ -1219,8 +1261,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'); } @@ -1271,18 +1313,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); } }, @@ -1315,8 +1357,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 ); } @@ -1328,7 +1370,7 @@ export const createComponentsSlice = (set, get) => ({ } state.containerChildrenMapping[newParentId].push(componentId); } else { - state.containerChildrenMapping.canvas.push(componentId); + state.containerChildrenMapping[moduleId].push(componentId); } }, skipUndoRedo), false, @@ -1379,7 +1421,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,19 +1453,17 @@ export const createComponentsSlice = (set, get) => ({ state.focusedParentId = 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 @@ -1447,7 +1487,7 @@ export const createComponentsSlice = (set, get) => ({ .finally(() => { set( (state) => { - state.app.isSaving = false; + state.appStore.modules[moduleId].app.isSaving = false; }, false, 'setAppSavingChanges' @@ -1473,7 +1513,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; @@ -1490,38 +1531,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]; }, @@ -1531,24 +1578,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 = { @@ -1578,7 +1627,7 @@ export const createComponentsSlice = (set, get) => ({ }, 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]) => { @@ -1607,8 +1656,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( @@ -1622,7 +1671,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 @@ -1637,7 +1686,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, @@ -1686,24 +1735,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, [] @@ -1711,7 +1760,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( @@ -1734,7 +1783,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]; @@ -1831,8 +1881,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; @@ -1856,8 +1906,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' @@ -1890,4 +1940,6 @@ export const createComponentsSlice = (set, get) => ({ state.modalsOpenOnCanvas = newModalOpenOnCanvas; }); }, + checkIfComponentIsModule: (componentId, moduleId = 'canvas') => + get().getComponentDefinition(componentId, moduleId)?.component?.component === 'ModuleViewer', }); 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 7f7868416d..fee6c8f863 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -21,10 +21,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) => { @@ -48,7 +58,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; @@ -120,12 +130,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) => { @@ -219,8 +233,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) => { @@ -265,12 +279,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 + ); const events = getEventsByComponentsId(queryToClone.id); @@ -403,12 +421,23 @@ export const createDataQuerySlice = (set, get) => ({ }) .finally(() => setIsAppSaving(false)); }, 500), - runOnLoadQueries: async () => { + runOnLoadQueries: async (moduleId = 'canvas') => { const queries = get().dataQuery.queries.modules.canvas; 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/eventsSlice.js b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js index 60ac1b6fe7..8c577f1022 100644 --- a/frontend/src/AppBuilder/_stores/slices/eventsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/eventsSlice.js @@ -58,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 { @@ -71,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') => { @@ -97,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 ); }, @@ -128,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) => { @@ -163,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) => { @@ -265,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(); @@ -286,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; @@ -295,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 { @@ -309,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 { @@ -320,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 ( @@ -384,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( @@ -477,7 +480,7 @@ 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(); @@ -567,7 +570,7 @@ export const createEventsSlice = (set, get) => ({ eventId, false, false, - 'canvas' + moduleId ); } catch (error) { get().eventsSlice.logError('run_query', 'run-query', error, eventObj, { @@ -819,13 +822,8 @@ export const createEventsSlice = (set, get) => ({ 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(); @@ -844,7 +842,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.disabled) { const resolvedQueryParams = []; @@ -864,7 +862,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 @@ -888,14 +886,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; @@ -928,7 +926,7 @@ export const createEventsSlice = (set, get) => ({ parameters: processedParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setVariable = (key = '', value = '') => { @@ -938,7 +936,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -948,7 +946,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -958,7 +956,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'unset-custom-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); } }; @@ -968,14 +966,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 = '') => { @@ -990,7 +988,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'show-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const closeModal = (modalName = '') => { @@ -1005,7 +1003,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'close-modal', modal, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setLocalStorage = (key = '', value = '') => { @@ -1014,7 +1012,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const copyToClipboard = (contentToCopy = '') => { @@ -1022,7 +1020,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'copy-to-clipboard', contentToCopy, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const goToApp = (slug = '', queryParams = []) => { @@ -1031,7 +1029,7 @@ export const createEventsSlice = (set, get) => ({ slug, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const generateFile = (fileName, fileType, data) => { @@ -1045,7 +1043,7 @@ export const createEventsSlice = (set, get) => ({ data, fileType, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const setPageVariable = (key = '', value = '') => { @@ -1054,7 +1052,7 @@ export const createEventsSlice = (set, get) => ({ key, value, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const getPageVariable = (key = '') => { @@ -1062,7 +1060,7 @@ export const createEventsSlice = (set, get) => ({ actionId: 'get-page-variable', key, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const unsetPageVariable = (key = '') => { @@ -1070,10 +1068,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', { @@ -1081,7 +1079,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; @@ -1098,7 +1096,7 @@ export const createEventsSlice = (set, get) => ({ pageId, queryParams, }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logInfo = (log) => { @@ -1107,7 +1105,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log-info', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const logError = (log) => { @@ -1116,7 +1114,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log-error', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; const log = (log) => { @@ -1125,7 +1123,7 @@ export const createEventsSlice = (set, get) => ({ const lineNumberMatch = stackLine.match(/:(\d+):\d+\)$/); const lineNumber = lineNumberMatch ? lineNumberMatch[1] : 'unknown'; const event = { actionId: 'log', description: `${log}, Line ${lineNumber - 2}`, eventType: 'customLog' }; - return executeAction(event, mode, {}); + return executeAction(event, mode, {}, moduleId); }; return { diff --git a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js index 367ca4cf0c..4bff1a5988 100644 --- a/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/leftSideBarSlice.js @@ -37,9 +37,9 @@ export const createLeftSideBarSlice = (set, get) => ({ 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 +66,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/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 9403142033..b09287d443 100644 --- a/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/pageMenuSlice.js @@ -192,10 +192,8 @@ export const createPageMenuSlice = (set, get) => { updatePageGroupName: (pageId, value) => updatePageGroupName(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; @@ -218,7 +216,7 @@ export const createPageMenuSlice = (set, get) => { 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.'); @@ -243,11 +241,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) => { + deletePageGroup: async (pageGroupId, deleteAssociatedPages = false, moduleId = 'canvas') => { const { app, currentVersionId } = get(); const pages = get().modules.canvas.pages; - const homePageId = get().app.homePageId; + const homePageId = get().appStore.modules[moduleId].app.homePageId; const diff = { pageId: pageGroupId, deleteAssociatedPages, @@ -260,7 +258,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; } } @@ -302,14 +300,14 @@ export const createPageMenuSlice = (set, get) => { await savePageChanges(app.appId, currentVersionId, pageGroupId, diff, 'delete'); } }, - markAsHomePage: async (pageId) => { + markAsHomePage: async (pageId, moduleId = 'canvas') => { const { app, currentVersionId, editingPage } = get(); const diff = { homePageId: pageId, }; set((state) => { - state.app.homePageId = pageId; + state.appStore.modules[moduleId].app.homePageId = pageId; state.showEditingPopover = false; state.editingPage = null; }); @@ -317,7 +315,7 @@ export const createPageMenuSlice = (set, get) => { }, 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] = { diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index fd49d2a5e4..733d2854f7 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: ( @@ -212,7 +220,7 @@ export const createQueryPanelSlice = (set, get) => ({ dataQuery: dataQuerySlice, queryPanel, setResolvedQuery, - app, + appStore, selectedEnvironment, isPublicAccess, currentVersionId, @@ -244,14 +252,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(''); } @@ -274,8 +286,8 @@ export const createQueryPanelSlice = (set, get) => ({ // const queryState = { ...getCurrentState(), parameters }; const queryState = { ...get().getAllExposedValues('canvas'), 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 = { @@ -305,18 +317,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, @@ -400,16 +416,20 @@ export const createQueryPanelSlice = (set, get) => ({ isQuerySuccessLog: false, }); - setResolvedQuery(queryId, { - isLoading: false, - ...(query.kind === 'restapi' - ? { - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } - : {}), - }); + setResolvedQuery( + queryId, + { + isLoading: false, + ...(query.kind === 'restapi' + ? { + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } + : {}), + }, + moduleId + ); resolve(data); onEvent('onDataQueryFailure', queryEvents); @@ -426,9 +446,13 @@ export const createQueryPanelSlice = (set, get) => ({ 'edit' ); if (finalData.status === 'failed') { - setResolvedQuery(queryId, { - isLoading: false, - }); + setResolvedQuery( + queryId, + { + isLoading: false, + }, + moduleId + ); resolve(finalData); onEvent('onDataQueryFailure', queryEvents); @@ -460,14 +484,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); @@ -527,7 +555,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(), @@ -683,14 +711,14 @@ export const createQueryPanelSlice = (set, get) => ({ 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: () => { @@ -842,12 +870,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 { @@ -885,7 +911,7 @@ export const createQueryPanelSlice = (set, get) => ({ return isValidCode; } - const currentState = getAllExposedValues(); + // const currentState = getAllExposedValues(); let result = {}, error = null; @@ -895,7 +921,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); @@ -930,7 +956,7 @@ 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: () => { diff --git a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js index 94c7433e90..e60acddd7e 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -45,7 +45,6 @@ export const createResolvedSlice = (set, get) => ({ set( (state) => { state.resolvedStore.modules[moduleId] = { - ...state.resolvedStore.modules[moduleId], ...initialState.resolvedStore.modules.canvas, }; }, @@ -74,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') => { @@ -88,7 +87,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`constants.${key}`); + get().updateDependencyValues(`constants.${key}`, moduleId); }); }, @@ -111,7 +110,7 @@ export const createResolvedSlice = (set, get) => ({ 'setResolvedPageConstants' ); Object.entries(constants).forEach(([key, value]) => { - get().updateDependencyValues(`page.${key}`); + get().updateDependencyValues(`page.${key}`, moduleId); }); }, @@ -124,7 +123,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setVariables' ); - get().updateDependencyValues(`variables.${key}`); + get().updateDependencyValues(`variables.${key}`, moduleId); }, getVariable: (key, moduleId = 'canvas') => { @@ -139,8 +138,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); }, // page.variables @@ -152,7 +151,7 @@ export const createResolvedSlice = (set, get) => ({ false, 'setPageVariable' ); - get().updateDependencyValues(`page.variables.${key}`); + get().updateDependencyValues(`page.variables.${key}`, moduleId); }, getPageVariable: (key, moduleId = 'canvas') => { @@ -166,8 +165,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); }, setResolvedQuery: (queryId, details, moduleId = 'canvas') => { @@ -184,13 +183,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] = { @@ -219,7 +218,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) => { @@ -248,7 +247,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) => { @@ -309,7 +308,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') => { @@ -330,7 +329,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); }); }, @@ -339,7 +338,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); @@ -395,7 +394,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]; @@ -548,7 +547,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 }; @@ -599,11 +598,29 @@ export const createResolvedSlice = (set, get) => ({ false, 'setModuleInputs' ); - get().updateDependencyValues(`input.${key}`); + 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/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index 2808a04545..e276360843 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -14,7 +14,7 @@ const AppLoader = (props) => { switch (appType) { case 'front-end': - return ; + return ; case 'workflow': return ; case 'module': diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index ea548eea8e..133cdd366d 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -807,7 +807,7 @@ button { } } - .editor .viewer .main { + .viewer .main { height: auto !important; .canvas-container { @@ -2724,6 +2724,14 @@ hr { max-height: 10px; z-index: 100; min-width: 108px; + + &.module-container { + .handle-content { + cursor: move; + color: #fff; + background: #c6cad0 !important; + } + } } @@ -7571,6 +7579,13 @@ tbody { .apploader { height: 100vh; + &.module-mode { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + .app-container { height: 100%; display: flex; 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 b75a1a8e32..2eada0e175 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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { + const moduleId = useModuleId(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = 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 40abc2cf2d..f876dc86fe 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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteConfirmationModal = React.memo(({ data, onClose }) => { + const moduleId = useModuleId(); 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..3052f05d17 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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { + const moduleId = useModuleId(); 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/server/ee b/server/ee index 683647f83d..9bc998392e 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 +Subproject commit 9bc998392ea9af9934a5099881c28a3ff7edeab1 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/service.ts b/server/src/modules/apps/service.ts index 60c6f8bc15..f17704776c 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -18,7 +18,7 @@ import { VersionReleaseDto, } from './dto'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { APP_TYPES, FEATURE_KEY } from './constants'; +import { FEATURE_KEY } from './constants'; import { camelizeKeys, decamelizeKeys } from 'humps'; import { App } from '@entities/app.entity'; import { AppsUtilService } from './util.service'; @@ -37,6 +37,7 @@ import { DataSource } from '@entities/data_source.entity'; import { AppVersion } from '@entities/app_version.entity'; 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'; @@ -57,7 +58,8 @@ 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; @@ -210,6 +212,12 @@ export class AppsService implements IAppsService { apps = await this.appsUtilService.all(user, parseInt(page || '1'), searchKey, type); } + if (type === 'module') { + for (const app of apps) { + app.moduleContainer = await this.pageService.findModuleContainer(app); + } + } + const totalCount = await this.appsUtilService.count(user, searchKey, type); const totalPageCount = folderId ? totalFolderCount : totalCount; diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts index fa0b2864e0..7b8c3dbc8a 100644 --- a/server/src/modules/apps/services/page.service.ts +++ b/server/src/modules/apps/services/page.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { Page } from '@entities/page.entity'; +import { App } from '@entities/app.entity'; import { ComponentsService } from './component.service'; import { CreatePageDto, UpdatePageDto } from '../dto/page'; import { dbTransactionWrap, dbTransactionForAppVersionAssociationsUpdate } from 'src/helpers/database.helper'; @@ -14,6 +15,7 @@ import { PageHelperService } from './page.util.service'; import * as _ from 'lodash'; import { AppVersion } from '@entities/app_version.entity'; import { IPageService } from '../interfaces/services/IPageService'; +import { find } from 'lodash'; @Injectable() export class PageService implements IPageService { @@ -304,4 +306,18 @@ export class PageService implements IPageService { return await this.pageHelperService.rearrangePagesOrderPostDeletion(pageExists, manager); }, appVersionId); } + + async findModuleContainer(app: App): Promise { + const version = app.appVersions[0]; + const pages = await this.findPagesForVersion(version.id); + const page = pages[0]; + const components = await this.componentsService.getAllComponents(page.id); + + const moduleContainer = find( + Object.values(components), + (component) => component?.component?.component === 'ModuleContainer' + ); + + return moduleContainer; + } } diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 36def10913..4f17f17fa4 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'; @Injectable() export class AppsUtilService implements IAppsUtilService { @@ -89,8 +91,51 @@ 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: [] }, + }, + 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, @@ -407,6 +452,10 @@ export class AppsUtilService implements IAppsUtilService { .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) { From bcccc00615fe4b8877b01407b19665654a7042b5 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 09:51:32 +0700 Subject: [PATCH 07/84] Added a logic to search the modules --- frontend/src/AppBuilder/AppBuilder.jsx | 2 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 16 ++++++++--- .../ComponentsManagerTab.jsx | 27 ++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index 5190312efa..cee2abc0e4 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -54,7 +54,7 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e708926dc4..85693efceb 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -18,7 +18,7 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = false }) => { +export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEditor, isViewer = false }) => { const moduleId = useModuleId(); const isModuleMode = useIsModuleMode(); const canvasContainerRef = useRef(); @@ -147,7 +147,12 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isViewer = fa )} - + {environmentLoadingState !== 'loading' && (
+ )}
diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx index ca98316a73..d3bc00cdfa 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/ComponentsManagerTab.jsx @@ -21,6 +21,7 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { }, [componentTypes]); const [filteredComponents, setFilteredComponents] = useState(componentList); + const [searchQuery, setSearchQuery] = useState(''); const [activeTab, setActiveTab] = useState(1); const _shouldFreeze = useStore((state) => state.getShouldFreeze()); const isAutoMobileLayout = useStore((state) => state.currentLayout === 'mobile' && state.getIsAutoMobileLayout()); @@ -29,9 +30,14 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const handleSearchQueryChange = useCallback( debounce((e) => { const { value } = e.target; - filterComponents(value); + setSearchQuery(value); + + if (activeTab === 1) { + filterComponents(value); + } + // No need to filter modules here as we pass searchQuery to ModuleManager }, 125), - [] + [activeTab] ); const filterComponents = useCallback((value) => { @@ -162,13 +168,15 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { const handleChangeTab = (tab) => { setActiveTab(tab); + // When changing tabs, we don't need to reset the search + // The search query will be applied to the new tab }; const renderSection = () => { if (activeTab === 1) { return
{segregateSections()}
; } - return ; + return ; }; return ( @@ -184,10 +192,17 @@ export const ComponentsManagerTab = ({ darkMode, isModuleEditor }) => { initialValue={''} callBack={(e) => handleSearchQueryChange(e)} onClearCallback={() => { - filterComponents(''); + setSearchQuery(''); + if (activeTab === 1) { + filterComponents(''); + } }} - placeholder={t('globals.searchComponents', 'Search widgets')} - customClass={`tj-widgets-search-input tj-text-xsm`} + placeholder={ + activeTab === 1 + ? t('globals.searchComponents', 'Search widgets') + : t('globals.searchModules', 'Search modules') + } + customClass={`tj-widgets-search-input tj-text-xsm`} showClearButton={false} width={266} /> From a6badfa7e9e3fb204a604716c2817903a1b05923 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 11:42:53 +0700 Subject: [PATCH 08/84] Added a logic to restrict the movement from modules to canvas & restricted deleting the module container --- frontend/ee | 2 +- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 5 +++- .../src/AppBuilder/AppCanvas/Container.jsx | 3 +- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 25 +++++++++++----- .../AppCanvas/Grid/helpers/dragEnd.js | 29 +++++++++++++------ .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 23 ++++++++++++++- .../AppCanvas/appCanvasConstants.js | 13 +++++++++ .../_stores/slices/componentsSlice.js | 4 ++- 8 files changed, 82 insertions(+), 22 deletions(-) diff --git a/frontend/ee b/frontend/ee index f00cc3fde4..b88b2cc56c 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit f00cc3fde44dcb082aa81d75abc9f5e879f3aade +Subproject commit b88b2cc56c66aaa54cd31b7197f74939103e085b diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 10a738adcb..6c4040671b 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -5,6 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { DROPPABLE_PARENTS } from '../appCanvasConstants'; const CONFIG_HANDLE_HEIGHT = 20; const BUFFER_HEIGHT = 1; @@ -69,7 +70,9 @@ export const ConfigHandle = ({ if (componentType === 'Tabs') { setFocusedParentId(`${id}-${currentTab}`); } else { - setFocusedParentId(id); + if (DROPPABLE_PARENTS.has(componentType)) { + setFocusedParentId(id); + } } }} > diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index b12f9a9634..629e435892 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -68,13 +68,14 @@ export const Container = React.memo( }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ - accept: appType === 'module' && componentType !== 'ModuleContainer' ? [] : 'box', + accept: 'box', hover: (item) => { item.canvasRef = realCanvasRef?.current; item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); }, drop: async ({ componentType, component }, monitor) => { + if (appType === 'module' && componentType !== 'ModuleContainer') return; const didDrop = monitor.didDrop(); if (didDrop) return; if (componentType === 'PDF' && !isPDFSupported()) { diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index adef3eea83..baf7c18b53 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -28,7 +28,7 @@ import { resolveWidgetFieldValue } from '@/_helpers/utils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; -import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; +import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { @@ -37,7 +37,7 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout, appType }) { +export default function Grid({ gridWidth, currentLayout, appType, isModuleEditor }) { const moduleId = useModuleId(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); @@ -50,6 +50,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const isGroupHandleHoverd = useIsGroupHandleHoverd(); + const openModalWidgetId = useOpenModalWidgetId(); const moveableRef = useRef(null); const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow); @@ -454,9 +455,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { widgetId = widgetId.split('-').slice(0, -1).join('-'); widgetType = boxList.find(({ id }) => id === widgetId)?.component?.component; } - if ( - !['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'Listview', 'Container', 'Table'].includes(widgetType) - ) { + if (!DROPPABLE_PARENTS.has(widgetType)) { isDroppable = false; } } @@ -470,10 +469,15 @@ export default function Grid({ gridWidth, currentLayout, appType }) { .map(({ component }) => component.component); const parentId = draggedOverElemId?.length > 36 ? draggedOverElemId.slice(0, 36) : draggedOverElemId; const parentWidgetType = getComponentTypeFromId(parentId); - const restrictedWidgetsTobeDropped = + let restrictedWidgetsTobeDropped = RESTRICTED_WIDGETS_CONFIG?.[parentWidgetType]?.filter((widgetType) => widgetsTypeToBeDropped.includes(widgetType) ) || []; + + if (isModuleEditor && parentId === undefined) { + restrictedWidgetsTobeDropped = widgetsTypeToBeDropped; + // useGridStore.getState().actions.setIsGroupHandleHoverd(false); + } const isParentChangeAllowed = isEmpty(restrictedWidgetsTobeDropped); if (!isParentChangeAllowed) { @@ -498,7 +502,12 @@ export default function Grid({ gridWidth, currentLayout, appType }) { }); // Show error message - toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + if (isModuleEditor) { + // Added this to hide configHandle when multiple components were dragged using the configHandle and placed outside the module + setSelectedComponents([]); + } else { + toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`); + } } const parentElm = draggedOverElem || document.getElementById('real-canvas'); @@ -853,7 +862,7 @@ export default function Grid({ gridWidth, currentLayout, appType }) { if (!e.lastEvent) return; // Build the drag context from the event - const dragContext = dragContextBuilder({ event: e, widgets: boxList }); + const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor }); const { target, source, dragged } = dragContext; const targetSlotId = target?.slotId; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js index a9405d043e..0c78f8730a 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js @@ -54,6 +54,7 @@ import { RESTRICTED_WIDGETS_CONFIG, RESTRICTED_WIDGET_SLOTS_CONFIG, } from '@/AppBuilder/WidgetManager/configs/restrictedWidgetsConfig'; +import { DROPPABLE_PARENTS } from '../../appCanvasConstants'; const CANVAS_ID = 'canvas'; const REAL_CANVAS_ID = 'real-canvas'; @@ -84,8 +85,6 @@ export class DragEntity { * This class helps determine if a slot is valid and handles various properties like modals. */ export class DropAreaEntity { - static dropAreaWidgets = ['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'ModalV2', 'Listview', 'Container', 'Table']; - constructor(widget, slotId) { this.widget = widget; // The widget that owns this slot this.id = widget?.id || CANVAS_ID; // ID of the widget @@ -119,7 +118,7 @@ export class DropAreaEntity { // Determines if the slot is a valid drop target get isDroppable() { - return DropAreaEntity.dropAreaWidgets.includes(this.widgetType); + return DROPPABLE_PARENTS.has(this.widgetType); } // Returns the type of slot (header, footer, body, etc.) @@ -143,7 +142,7 @@ export class DropAreaEntity { * - Any restrictions based on parent-child relationships */ export class DragContext { - constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets }) { + constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets, isModuleEditor = false }) { const sourceWidgetId = sourceSlotId?.slice(0, 36); const sourceWidget = getWidgetById(widgets, sourceWidgetId); @@ -156,6 +155,7 @@ export class DragContext { this.target = new DropAreaEntity(targetWidget, targetSlotId); this.dragged = new DragEntity(draggedWidget); this.widgets = widgets; + this.isModuleEditor = isModuleEditor; } /** @@ -168,27 +168,38 @@ export class DragContext { } get isDroppable() { - const { dragged, target } = this; + const { dragged, target, isModuleEditor } = this; + + // If the target is the canvas and we are in module editor, + // then we don't want to drop the widget outside the module + if (isModuleEditor && target.id === 'canvas') { + return false; + } const restrictedWidgetsOnTarget = RESTRICTED_WIDGETS_CONFIG?.[target.widgetType] || []; const restrictedWidgetsOnTargetSlot = RESTRICTED_WIDGET_SLOTS_CONFIG?.[target.slotType] || []; const restrictedWidgets = [...restrictedWidgetsOnTarget, ...restrictedWidgetsOnTargetSlot]; return !restrictedWidgets.includes(dragged.widgetType); - ß; } } /** * Constructs the **dragging context** by gathering all relevant details from the event. */ -export function dragContextBuilder({ event, widgets }) { +export function dragContextBuilder({ event, widgets, isModuleEditor = false }) { const draggedWidgetId = event.target.id; const draggedWidget = getWidgetById(widgets, draggedWidgetId); const sourceSlotId = draggedWidget.parent; // Initialize drag context - const context = new DragContext({ widgets, draggedWidgetId, sourceSlotId, targetSlotId: sourceSlotId }); + const context = new DragContext({ + widgets, + draggedWidgetId, + sourceSlotId, + targetSlotId: sourceSlotId, + isModuleEditor, + }); // Determine the potential drop target const targetSlotId = getDroppableSlotIdOnScreen(event, widgets); @@ -210,7 +221,7 @@ export const getDroppableSlotIdOnScreen = (event, widgets) => { .map((ele) => extractSlotId(ele)) .filter((slotId) => { const widgetType = getWidgetById(widgets, slotId.slice(0, 36))?.component?.component || CANVAS_ID; - return DropAreaEntity.dropAreaWidgets.includes(widgetType); + return DROPPABLE_PARENTS.has(widgetType); }); return slotId; diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 1aa54dfc7b..47ac8dcfd0 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -5,7 +5,7 @@ import { pasteComponents, copyComponents } from './appCanvasUtils'; import useKeyHooks from '@/_hooks/useKeyHooks'; import { shallow } from 'zustand/shallow'; -export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => { +export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth, isModuleEditor = false }) => { const canvasRef = useRef(null); const focusedParentId = useStore((state) => state.focusedParentId, shallow); const handleUndo = useStore((state) => state.handleUndo); @@ -18,10 +18,13 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow); const containerChildrenMapping = useStore((state) => state.containerChildrenMapping, shallow); + const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow); + useHotkeys('meta+z, control+z', handleUndo, { enabled: mode === 'edit' }); useHotkeys('meta+shift+z, control+shift+z', handleRedo, { enabled: mode === 'edit' }); const paste = async () => { + if (isModuleEditor && !focusedParentId) return; if (navigator.clipboard && typeof navigator.clipboard.readText === 'function') { try { const cliptext = await navigator.clipboard.readText(); @@ -61,6 +64,24 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } enableReleasedVersionPopupState(); return; } + + // Disable cut, copy, paste, delete shortcuts in module editor + // or when a ModuleContainer is selected + if (isModuleEditor) { + const selectedComponents = getSelectedComponents(); + if ( + selectedComponents.length > 0 && + selectedComponents.some((id) => { + const componentType = getComponentTypeFromId(id, 'canvas'); + return componentType === 'ModuleContainer'; + }) + ) { + if (['KeyC', 'KeyX', 'KeyV', 'Backspace'].includes(key)) { + return; + } + } + } + switch (key) { case 'Escape': handleEscapeKeyPress(); // clears the selected components diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js index e6a789fba0..cfadce5425 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js @@ -23,3 +23,16 @@ export const CONTAINER_FORM_CANVAS_PADDING = 7; export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1; export const BOX_PADDING = 2; + +export const DROPPABLE_PARENTS = new Set([ + 'Calendar', + 'Kanban', + 'Form', + 'Tabs', + 'Modal', + 'ModalV2', + 'Listview', + 'Container', + 'Table', + 'ModuleContainer', +]); diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index 741846c7b4..e29365fb8d 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -1451,7 +1451,9 @@ export const createComponentsSlice = (set, get) => ({ setFocusedParentId: (parentId) => { set((state) => { state.focusedParentId = parentId; - }); + }), + false, + { type: 'setFocusedParentId', payload: { parentId } }; }, saveComponentChanges: (diff, type, operation, moduleId = 'canvas') => { set( From 38861f5ca48ab48fcd6813b84b2546cc2cea9a51 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 12:11:41 +0700 Subject: [PATCH 09/84] Fix: unable to resize the module container --- server/src/modules/apps/util.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 4f17f17fa4..bd91930f83 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -100,6 +100,7 @@ export class AppsUtilService implements IAppsUtilService { properties: { inputItems: { value: [] }, outputItems: { value: [] }, + visibility: { value: '{{true}}' }, }, styles: { backgroundColor: { value: '#fff' }, From e3db264fae2ca10e76ecd2e1b2f0440cb37135f9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 17:10:32 +0700 Subject: [PATCH 10/84] Moved fetching of modules logic to appDef --- frontend/ee | 2 +- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 +- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 20 ++++++--- frontend/src/AppBuilder/_hooks/useAppData.js | 43 ++++++++++--------- server/src/modules/apps/service.ts | 3 +- .../src/modules/apps/services/page.service.ts | 16 +------ .../apps/services/page.util.service.ts | 4 ++ 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/frontend/ee b/frontend/ee index b88b2cc56c..51c8b41597 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit b88b2cc56c66aaa54cd31b7197f74939103e085b +Subproject commit 51c8b4159781d3fe664ab5c5ca15c97f1b485496 diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 85693efceb..e24be589d2 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -139,7 +139,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito >
diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 42b07dacbe..5a29c53f18 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -28,7 +28,14 @@ const WidgetWrapper = memo( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - let layoutData = useStore((state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], shallow); + let layoutData = useStore((state) => { + const layout = state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout]; + // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer + if (componentType === 'ModuleContainer' && mode === 'view') { + return { ...layout, top: 0, left: 0, width: NO_OF_GRIDS }; + } + return layout; + }, shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); @@ -51,12 +58,11 @@ const WidgetWrapper = memo( return null; } - // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer - if (componentType === 'ModuleContainer' && mode === 'view') { - layoutData.top = 0; - layoutData.left = 0; - layoutData.width = NO_OF_GRIDS; - } + // if (componentType === 'ModuleContainer' && mode === 'view') { + // layoutData.top = 0; + // layoutData.left = 0; + // layoutData.width = NO_OF_GRIDS; + // } const width = gridWidth * layoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 4104868eda..a900a84917 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -122,6 +122,9 @@ const useAppData = ( 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 setConversation = useStore((state) => state.ai?.setConversation); const setDocsConversation = useStore((state) => state.ai?.setDocsConversation); @@ -215,33 +218,30 @@ const useAppData = ( } let appDataPromise; - - // if (moduleMode) { - // appDataPromise = appService.fetchApp(appId); - // appDataPromise.then(async (result) => { - // let appData = { ...result }; - // console.log('appData--- ', appData); - // return; - // // setApp({ - // // appName: appData.name, - // // appId: appData.id, - // // slug: appData.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 (isPublicAccess) { - appDataPromise = appService.fetchAppBySlug(slug); + + 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); @@ -383,6 +383,9 @@ const useAppData = ( 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) diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index f17704776c..1e25e66206 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -214,7 +214,8 @@ export class AppsService implements IAppsService { if (type === 'module') { for (const app of apps) { - app.moduleContainer = await this.pageService.findModuleContainer(app); + const appVersionId = app?.appVersions[0]?.id; + app.moduleContainer = await this.pageService.findModuleContainer(appVersionId); } } diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts index 7b8c3dbc8a..02bea48dab 100644 --- a/server/src/modules/apps/services/page.service.ts +++ b/server/src/modules/apps/services/page.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { Page } from '@entities/page.entity'; -import { App } from '@entities/app.entity'; import { ComponentsService } from './component.service'; import { CreatePageDto, UpdatePageDto } from '../dto/page'; import { dbTransactionWrap, dbTransactionForAppVersionAssociationsUpdate } from 'src/helpers/database.helper'; @@ -15,7 +14,6 @@ import { PageHelperService } from './page.util.service'; import * as _ from 'lodash'; import { AppVersion } from '@entities/app_version.entity'; import { IPageService } from '../interfaces/services/IPageService'; -import { find } from 'lodash'; @Injectable() export class PageService implements IPageService { @@ -307,17 +305,7 @@ export class PageService implements IPageService { }, appVersionId); } - async findModuleContainer(app: App): Promise { - const version = app.appVersions[0]; - const pages = await this.findPagesForVersion(version.id); - const page = pages[0]; - const components = await this.componentsService.getAllComponents(page.id); - - const moduleContainer = find( - Object.values(components), - (component) => component?.component?.component === 'ModuleContainer' - ); - - return moduleContainer; + 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; + } } From 32de1c9929db97127fc1af7ca87a47cd4bc37ffc Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Mon, 28 Apr 2025 19:08:14 +0700 Subject: [PATCH 11/84] Fixed bug on query creation --- frontend/ee | 2 +- frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js | 4 ++-- server/ee | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/ee b/frontend/ee index 51c8b41597..425820a97a 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 51c8b4159781d3fe664ab5c5ca15c97f1b485496 +Subproject commit 425820a97a5dbb3c8a26037f7f21252e465d07f1 diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index fee6c8f863..93db833f13 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -110,7 +110,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 */ @@ -275,7 +275,7 @@ 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 diff --git a/server/ee b/server/ee index 9bc998392e..1363a2f4d5 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 9bc998392ea9af9934a5099881c28a3ff7edeab1 +Subproject commit 1363a2f4d5d936325b6dc28da9f952c5c2dba448 From 41db3e6817a25c118271554d7578a2e9ad6a05ea Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 29 Apr 2025 23:26:03 +0700 Subject: [PATCH 12/84] Fetched the app modules while previewing the app --- .../src/AppBuilder/AppCanvas/Container.jsx | 2 - .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 32 +++--- server/src/modules/apps/util.service.ts | 38 ++++++ server/src/modules/versions/repository.ts | 16 +++ server/src/modules/versions/service.ts | 108 ++++++++++-------- 5 files changed, 130 insertions(+), 66 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 629e435892..074d97cfeb 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -214,8 +214,6 @@ export const Container = React.memo( ? computeViewerBackgroundColor(darkMode, canvasBgColor) : id === 'canvas' ? canvasBgColor - : componentType === 'ModuleContainer' - ? 'inherit' : '#f0f0f0', width: getCanvasWidth(), maxWidth: (() => { diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 5a29c53f18..2e5c1c34ff 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -28,14 +28,10 @@ const WidgetWrapper = memo( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, shallow ); - let layoutData = useStore((state) => { - const layout = state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout]; - // Override layout data with 0 and width to 43 for ModuleContainer in view mode to make the child components relative to the ModuleViewer - if (componentType === 'ModuleContainer' && mode === 'view') { - return { ...layout, top: 0, left: 0, width: NO_OF_GRIDS }; - } - return layout; - }, shallow); + const layoutData = useStore( + (state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout], + shallow + ); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); const isResizing = useGridStore((state) => state.resizingComponentId === id); @@ -58,18 +54,18 @@ const WidgetWrapper = memo( return null; } - // if (componentType === 'ModuleContainer' && mode === 'view') { - // layoutData.top = 0; - // layoutData.left = 0; - // layoutData.width = NO_OF_GRIDS; - // } + let newLayoutData = layoutData; - const width = gridWidth * layoutData?.width; + if (componentType === 'ModuleContainer' && mode === 'view') { + newLayoutData = { ...layoutData, top: 0, left: 0, width: NO_OF_GRIDS }; + } + + const width = gridWidth * newLayoutData?.width; const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition); const styles = { width: width + 'px', height: visibility === false ? '10px' : `${height}px`, - transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`, + transform: `translate(${newLayoutData.left * gridWidth}px, ${newLayoutData.top}px)`, WebkitFontSmoothing: 'antialiased', border: visibility === false ? `1px solid var(--border-default)` : 'none', }; @@ -108,8 +104,8 @@ const WidgetWrapper = memo( {mode == 'edit' && ( { + 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; + } } 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 e50b66b50b..1a46b18100 100644 --- a/server/src/modules/versions/service.ts +++ b/server/src/modules/versions/service.ts @@ -112,62 +112,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) { From 5149a136af9061d8ef2aaa4879783a1f97c76323 Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Wed, 30 Apr 2025 03:07:40 +0530 Subject: [PATCH 13/84] fix: page permissions demo bugs --- .../LeftSidebar/PageMenu/PageHandlerMenu.jsx | 2 +- .../LeftSidebar/PageMenu/PageMenuItem.jsx | 39 ++++++++++++++++++- .../LeftSidebar/PageMenu/PagePermission.jsx | 33 ++++++---------- .../LeftSidebar/PageMenu/style.scss | 4 ++ frontend/src/AppBuilder/Viewer/PageGroup.jsx | 2 - frontend/src/_styles/components.scss | 6 +++ .../_ui/Icon/solidIcons/EnterrpiseCrown.jsx | 19 +++++++++ frontend/src/_ui/Icon/solidIcons/index.js | 3 ++ server/ee | 2 +- .../page-permissions.repository.ts | 12 ++---- server/src/modules/versions/module.ts | 2 + 11 files changed, 89 insertions(+), 35 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/EnterrpiseCrown.jsx diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index 1c5c3124f0..497b697b25 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -164,7 +164,7 @@ export const PageHandlerMenu = ({ darkMode }) => { >
Page permission
- {!licenseValid && } + {!licenseValid && }
); diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index a113251b61..6c74e7b6af 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -17,6 +17,7 @@ import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { shallow } from 'zustand/shallow'; +import { ToolTip } from '@/_components/ToolTip'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { @@ -151,6 +152,36 @@ export const PageMenuItem = withRouter( [popoverRef.current, page] ); + function getTooltip() { + const permission = page?.permissions?.length ? page?.permissions[0] : null; + if (!permission) return ''; + const users = permission.users || []; + const isSingle = permission.type === 'SINGLE'; + const isGroup = permission.type === 'GROUP'; + + if (users.length === 0) return null; + + if (isSingle) { + if (users.length === 1) { + const email = users[0].user.email; + return `Access restricted to ${email}`; + } else { + return `Access restricted to ${users.length} users`; + } + } + + if (isGroup) { + if (users.length === 1) { + const groupName = users[0].permissionGroup?.name ?? 'Group'; + return `Access restricted to ${groupName} group`; + } else { + return `Access restricted to ${users.length} groups`; + } + } + + return ''; + } + return (
setIsHovered(true)} @@ -200,7 +231,13 @@ export const PageMenuItem = withRouter(
- {licenseValid && restricted && } + {licenseValid && restricted && ( + +
+ +
+
+ )}
{!shouldFreeze && ( diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx index 6a4a1c516a..e82f251665 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx @@ -34,7 +34,6 @@ export default function PagePermission({ darkMode }) { const [showConfirmDelete, setShowConfirmDelete] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isPermissionsLoading, setPermissionsLoading] = useState(true); - const [pageToDelete, setPageToDelete] = useState(null); const [initialSelectedGroups, setInitialSelectedGroups] = useState([]); const [initialSelectedUsers, setInitialSelectedUsers] = useState([]); const [initalPagePermissionType, setInitialPagePermissionType] = useState('all'); @@ -42,7 +41,7 @@ export default function PagePermission({ darkMode }) { useEffect(() => { if (!showPagePermissionModal) return; const fetchPagePermission = () => { - appPermissionService.getPagePermission(appId, editingPage?.id || pageToDelete).then((data) => { + appPermissionService.getPagePermission(appId, editingPage?.id).then((data) => { if (data) { if (data[0] && data[0]?.type === PERMISSION_TYPES.group) { const groups = @@ -55,7 +54,6 @@ export default function PagePermission({ darkMode }) { setInitialPagePermissionType(data[0]?.type?.toLowerCase()); setPagePermission(data); toggleUserGroupSelect(true); - setPageToDelete(null); setInitialSelectedGroups(groups); data?.length && setSelectedUserGroups(groups); } else if (data[0] && data[0]?.type === PERMISSION_TYPES.single) { @@ -74,7 +72,6 @@ export default function PagePermission({ darkMode }) { setInitialPagePermissionType(data[0]?.type?.toLowerCase()); setPagePermission(data); toggleUsersSelect(true); - setPageToDelete(null); setInitialSelectedUsers(users); data?.length && setSelectedUsers(users); } @@ -83,7 +80,7 @@ export default function PagePermission({ darkMode }) { }); }; fetchPagePermission(); - }, [showPagePermissionModal, pageToDelete]); + }, [showPagePermissionModal]); const isSelectionUnchanged = useMemo(() => { if (pagePermissionType === 'group') { @@ -237,13 +234,12 @@ export default function PagePermission({ darkMode }) { const deletePagePermission = () => { setIsLoading(true); appPermissionService - .deletePagePermission(appId, pageToDelete) + .deletePagePermission(appId, editingPage?.id) .then((data) => { toast.success('Permission successfully deleted!', { className: 'text-nowrap w-auto mw-100', }); - updatePageWithPermissions(pageToDelete, []); - setPageToDelete(null); + updatePageWithPermissions(editingPage?.id, []); }) .catch(() => { toast.error('Permission could not be deleted. Please try again!', { @@ -284,25 +280,18 @@ export default function PagePermission({ darkMode }) { isLoading={isLoading} handleClose={handlePagePermissionModalClose} confirmBtnProps={{ - title: pagePermission ? 'Update' : pagePermissionType === 'all' ? 'Default permission' : 'Create permission', + title: pagePermission + ? 'Save changes' + : pagePermissionType === 'all' + ? 'Default permission' + : 'Create permission', disabled: isPermissionsLoading || isSelectionUnchanged, tooltipMessage: '', + leftIcon: pagePermission && 'save', + className: 'action-btn-page-permission', }} darkMode={darkMode} className="page-permissions-modal" - headerAction={() => - pagePermission && ( - { - setPageToDelete(editingPage?.id); - togglePagePermissionModal(false); - setShowConfirmDelete(true); - }} - > - - - ) - } >
{isPermissionsLoading ? ( diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss b/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss index 968218b106..a3adc9ec20 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/style.scss @@ -374,4 +374,8 @@ .spinner-center { min-height: 250px; } +} + +.modal-base .modal-footer .action-btn-page-permission svg path { + fill: var(--indigo1) !important; } \ No newline at end of file diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 0311115b09..1739263fff 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -11,8 +11,6 @@ import cx from 'classnames'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; - console.log({ page, homePageId }); - console.log({ isHomePage }); const iconName = isHomePage && !page.icon ? 'IconHome2' : page.icon; const IconElement = Icons?.[iconName] ?? Icons?.['IconFileDescription']; return (page.hidden || page.disabled) && page?.restricted ? null : ( diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 074338602e..059841e6c7 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -242,10 +242,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/_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/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 5daa4b7c91..a190dd4c52 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -234,6 +234,7 @@ import NewTabSmall from './NewTabSmall.jsx'; import Code from './Code.jsx'; import WorkflowV3 from './WorkflowV3.jsx'; import WorkspaceV3 from './WorkspaceV3.jsx'; +import EnterpriseCrown from './EnterrpiseCrown.jsx'; const Icon = (props) => { switch (props.name) { @@ -355,6 +356,8 @@ const Icon = (props) => { return ; case 'enterprisev3': return ; + case 'enterprisecrown': + return ; case 'lockGradient': return ; case 'datasourceGradient': diff --git a/server/ee b/server/ee index 84ec48d0f6..bd6745ee0a 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 84ec48d0f64fd6dc5f7677f71a5119219cc4ada4 +Subproject commit bd6745ee0a9344d5fcf42a79a50b8185ec43643a diff --git a/server/src/modules/app-permissions/repositories/page-permissions.repository.ts b/server/src/modules/app-permissions/repositories/page-permissions.repository.ts index 7caa5f4318..a36be08bc7 100644 --- a/server/src/modules/app-permissions/repositories/page-permissions.repository.ts +++ b/server/src/modules/app-permissions/repositories/page-permissions.repository.ts @@ -19,14 +19,10 @@ export class PagePermissionsRepository extends Repository { }); return pagePermissions.map((permission) => { - if (permission.type === PAGE_PERMISSION_TYPE.GROUP) { - return { - ...permission, - groups: permission.users, - users: undefined, - }; - } - return permission; + return { + ...permission, + users: permission.users, + }; }); }, manager || this.manager); } diff --git a/server/src/modules/versions/module.ts b/server/src/modules/versions/module.ts index 1f08dc76bb..80929f4a3a 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: [ From 42c72fbde1722a97264f5e5e128506056e9762d5 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 09:42:37 +0700 Subject: [PATCH 14/84] Fixed bugs on preview header and view by slug --- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 +- .../src/AppBuilder/AppCanvas/Container.jsx | 3 +- .../src/AppBuilder/Viewer/PreviewSettings.jsx | 24 +++--- frontend/src/AppBuilder/Viewer/Viewer.jsx | 7 +- frontend/src/AppBuilder/_hooks/useAppData.js | 6 ++ .../slices/environmentsAndVersionsSlice.js | 4 +- .../src/Editor/Viewer/PreviewSettings.jsx | 6 +- server/ee | 2 +- server/src/modules/apps/service.ts | 78 +++++++++++-------- 9 files changed, 78 insertions(+), 54 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index e24be589d2..2b45319c02 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -46,7 +46,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const getPageId = useStore((state) => state.getCurrentPageId, shallow); - const hideSidebar = isModuleMode || isPagesSidebarHidden; + const hideSidebar = isModuleMode || isPagesSidebarHidden || appType === 'module'; useEffect(() => { // Need to remove this if we shift setExposedVariable Logic outside of components diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 074d97cfeb..1238406e6b 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -159,7 +159,8 @@ export const Container = React.memo( !isPagesSidebarHidden && isViewerSidebarPinned && currentLayout !== 'mobile' && - currentMode !== 'edit' + currentMode !== 'edit' && + appType !== 'module' ) { return `calc(100% - ${pageSidebarStyle === 'icon' ? '65px' : '210px'})`; } diff --git a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx index 633a6dcf0e..e914a9bee4 100644 --- a/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx +++ b/frontend/src/AppBuilder/Viewer/PreviewSettings.jsx @@ -9,8 +9,10 @@ import HeaderActions from '@/AppBuilder/Header/HeaderActions'; import { AppEnvironments } from '@/modules/Appbuilder/components'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useAppType } from '@/AppBuilder/_contexts/ModuleContext'; const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { + const appType = useAppType(); const { setShowUndoRedoBtn, editingVersion } = useStore( (state) => ({ setShowUndoRedoBtn: state?.setShowUndoRedoBtn, @@ -32,14 +34,14 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { Preview settings - {editingVersion && ( + {editingVersion && appType !== 'module' && ( <>
)} - +
@@ -85,13 +87,17 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => { {previewNavbar && ( - - - -
- - - + {appType !== 'module' && ( + <> + + + +
+ + + + + )}
{ const DEFAULT_CANVAS_WIDTH = 1292; const { t } = useTranslation(); const [isSidebarPinned, setIsSidebarPinned] = useState(localStorage.getItem('isPagesSidebarPinned') !== 'false'); - useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); + const appType = useAppData(appId, moduleId, darkMode, 'view', { environmentId, versionId }, moduleMode); const { isEditorLoading, @@ -80,7 +79,7 @@ export const Viewer = ({ const canvasBgColor = useStore((state) => state.getCanvasBackgroundColor('canvas', darkMode), shallow); const deviceWindowWidth = window.screen.width - 5; - const hideSidebar = moduleMode || isPagesSidebarHidden; + const hideSidebar = moduleMode || isPagesSidebarHidden || appType === 'module'; const computeCanvasMaxWidth = useCallback(() => { if (globalSettings?.maxCanvasWidth) { @@ -223,7 +222,7 @@ export const Viewer = ({
{ try { const head = document.head || document.getElementsByTagName('head')[0]; @@ -314,6 +316,8 @@ const useAppData = ( const homePageId = appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id; + appTypeRef.current = appData.type; + setApp( { appName: appData.name, @@ -616,6 +620,8 @@ const useAppData = ( ); // 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/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/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/server/ee b/server/ee index 1363a2f4d5..3219183a1f 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1363a2f4d5d936325b6dc28da9f952c5c2dba448 +Subproject commit 3219183a1f54f368ed135f652261fbf7ff183687 diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 1e25e66206..d6d6397e05 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -348,44 +348,54 @@ 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) { - this.eventEmitter.emit('auditLogEntry', { - userId: user.id, - organizationId: user.organizationId, - resourceId: app.id, - resourceType: MODULES.APP, - resourceName: app.name, - actionType: MODULE_INFO.APP.GET_BY_SLUG, - }); - } + if (app?.isPublic && user) { + this.eventEmitter.emit('auditLogEntry', { + userId: user.id, + organizationId: user.organizationId, + resourceId: app.id, + resourceType: MODULES.APP, + resourceName: app.name, + actionType: MODULE_INFO.APP.GET_BY_SLUG, + }); + } - // 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) { From eed8014509c9965b92144b7cf387f9a9fe88c3a9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 10:35:32 +0700 Subject: [PATCH 15/84] Cleaned up the logic of using module context --- frontend/src/AppBuilder/AppBuilder.jsx | 8 +++---- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 21 +++++-------------- .../AppCanvas/ConfigHandle/ConfigHandle.jsx | 4 ++-- .../src/AppBuilder/AppCanvas/Container.jsx | 4 ++-- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 6 +++--- .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 4 +++- .../src/AppBuilder/AppCanvas/RenderWidget.jsx | 4 ++-- frontend/src/AppBuilder/AppCanvas/Selecto.jsx | 4 ++-- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 4 ++-- .../AppBuilder/AppCanvas/useSidebarMargin.js | 4 ++-- .../src/AppBuilder/CodeEditor/PreviewBox.jsx | 4 ++-- .../CodeEditor/SingleLineCodeEditor.jsx | 4 ++-- .../AppBuilder/Header/AppVersionsManager.jsx | 4 ++-- .../AppBuilder/Header/CreateVersionModal.jsx | 4 ++-- .../src/AppBuilder/Header/EditAppName.jsx | 4 ++-- .../AppBuilder/Header/EditVersionModal.jsx | 4 ++-- .../src/AppBuilder/Header/EditorHeader.jsx | 6 +++--- .../PromoteVersionButton.jsx | 4 ++-- .../ReleaseVersionButton.jsx | 4 ++-- .../RightTopHeaderButtons.jsx | 4 ++-- .../LeftSidebar/GlobalSettings/AppExport.jsx | 4 ++-- .../GlobalSettings/CanvasSettings.jsx | 4 ++-- .../LeftSidebar/GlobalSettings/SlugInput.jsx | 4 ++-- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 6 ++---- .../LeftSidebar/PageMenu/PageHandlerMenu.jsx | 4 ++-- .../LeftSidebar/PageMenu/PageMenuItem.jsx | 4 ++-- .../Components/QueryManagerHeader.jsx | 4 ++-- .../QueryManager/QueryEditors/Workflows.jsx | 4 ++-- .../src/AppBuilder/QueryPanel/QueryCard.jsx | 4 ++-- .../Inspector/Components/DatetimePickerV2.jsx | 4 ++-- .../RightSideBar/Inspector/EventManager.jsx | 4 ++-- .../AppBuilder/RightSideBar/RightSideBar.jsx | 6 ++++-- .../Viewer/MobileNavigationMenu.jsx | 8 +++---- frontend/src/AppBuilder/Viewer/PageGroup.jsx | 4 ++-- frontend/src/AppBuilder/Viewer/Viewer.jsx | 2 +- .../Viewer/ViewerSidebarNavigation.jsx | 4 ++-- .../AppBuilder/Widgets/Kanban/KanbanBoard.jsx | 4 ++-- frontend/src/AppBuilder/Widgets/Modal.jsx | 4 ++-- .../AppBuilder/Widgets/ModalV2/ModalV2.jsx | 4 ++-- .../src/AppBuilder/Widgets/Table/Table.jsx | 4 ++-- .../AppBuilder/_contexts/ModuleContext.jsx | 8 +++++++ .../PromoteVersionButton.jsx | 4 ++-- .../PromoteConfirmationModal.jsx | 4 ++-- .../ReleaseVersionButton.jsx | 4 ++-- 44 files changed, 105 insertions(+), 106 deletions(-) diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index cee2abc0e4..9f7a8f4e2d 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -49,14 +49,14 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod Loading...
}> - - + + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - + - + diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 2b45319c02..163c81e692 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Container } from './Container'; import Grid from './Grid'; import { EditorSelecto } from './Selecto'; -import { useModuleId, useIsModuleMode } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { HotkeyProvider } from './HotkeyProvider'; import './appCanvas.scss'; import useStore from '@/AppBuilder/_stores/store'; @@ -18,9 +18,8 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; -export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEditor, isViewer = false }) => { - const moduleId = useModuleId(); - const isModuleMode = useIsModuleMode(); +export const AppCanvas = ({ appId, isViewerSidebarPinned, isViewer = false }) => { + const { moduleId, isModuleMode, appType } = useModuleContext(); const canvasContainerRef = useRef(); const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow); const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight); @@ -147,12 +146,7 @@ export const AppCanvas = ({ appId, isViewerSidebarPinned, appType, isModuleEdito )} - + {environmentLoadingState !== 'loading' && (
+ )}
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx index 6c4040671b..9215bb6a21 100644 --- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx +++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx @@ -4,7 +4,7 @@ import './configHandle.scss'; import useStore from '@/AppBuilder/_stores/store'; import { findHighestLevelofSelection } from '../Grid/gridUtils'; import SolidIcon from '@/_ui/Icon/solidIcons/index'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { DROPPABLE_PARENTS } from '../appCanvasConstants'; const CONFIG_HANDLE_HEIGHT = 20; @@ -22,7 +22,7 @@ export const ConfigHandle = ({ visibility, isModuleContainer, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const shouldFreeze = useStore((state) => state.getShouldFreeze()); const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow); const isMultipleComponentsSelected = useStore( diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 1238406e6b..78ec45823a 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -21,7 +21,7 @@ import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; //TODO: Revisit the logic of height (dropRef) /* @@ -47,7 +47,7 @@ export const Container = React.memo( componentType, appType, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index baf7c18b53..eb3b8f7b47 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -29,7 +29,7 @@ import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -37,8 +37,8 @@ const RESIZABLE_CONFIG = { }; export const GRID_HEIGHT = 10; -export default function Grid({ gridWidth, currentLayout, appType, isModuleEditor }) { - const moduleId = useModuleId(); +export default function Grid({ gridWidth, currentLayout }) { + const { moduleId, isModuleEditor } = useModuleContext(); const lastDraggedEventsRef = useRef(null); const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow); const setComponentLayout = useStore((state) => state.setComponentLayout, shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 47ac8dcfd0..5177e27af8 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -4,8 +4,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { pasteComponents, copyComponents } from './appCanvasUtils'; import useKeyHooks from '@/_hooks/useKeyHooks'; import { shallow } from 'zustand/shallow'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth, isModuleEditor = false }) => { +export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => { + const { isModuleEditor } = useModuleContext(); const canvasRef = useRef(null); const focusedParentId = useStore((state) => state.focusedParentId, shallow); const handleUndo = useStore((state) => state.handleUndo); diff --git a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx index 5e72d09797..0a7402d566 100644 --- a/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx +++ b/frontend/src/AppBuilder/AppCanvas/RenderWidget.jsx @@ -7,7 +7,7 @@ import { renderTooltip } from '@/_helpers/appUtils'; import { useTranslation } from 'react-i18next'; import ErrorBoundary from '@/_ui/ErrorBoundary'; import { BOX_PADDING } from './appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const shouldAddBoxShadowAndVisibility = [ 'Table', @@ -40,7 +40,7 @@ const RenderWidget = ({ inCanvas = false, darkMode, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow); const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow); const component = componentDefinition?.component; diff --git a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx index 24426f6050..dfa27d3638 100644 --- a/frontend/src/AppBuilder/AppCanvas/Selecto.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Selecto.jsx @@ -5,10 +5,10 @@ import './selecto.scss'; import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants'; import { shallow } from 'zustand/shallow'; import { findHighestLevelofSelection } from './Grid/gridUtils'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditorSelecto = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab); const setSelectedComponents = useStore((state) => state.setSelectedComponents); const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow); diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 2e5c1c34ff..0bd9df01e6 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -6,7 +6,7 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle'; import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { NO_OF_GRIDS } from './appCanvasConstants'; const WidgetWrapper = memo( @@ -22,7 +22,7 @@ const WidgetWrapper = memo( mode, darkMode, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow); const stylesDefinition = useStore( (state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles, diff --git a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js index b9ea871ca2..d80cbd76cc 100644 --- a/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js +++ b/frontend/src/AppBuilder/AppCanvas/useSidebarMargin.js @@ -3,10 +3,10 @@ import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const useSidebarMargin = (canvasContainerRef) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [editorMarginLeft, setEditorMarginLeft] = useState(0); const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow); const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); diff --git a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx index 8234bf8af6..f62fbc52c4 100644 --- a/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx +++ b/frontend/src/AppBuilder/CodeEditor/PreviewBox.jsx @@ -13,7 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { Overlay } from 'react-bootstrap'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const sanitizeLargeDataset = (data, callback) => { const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -91,7 +91,7 @@ export const PreviewBox = ({ isWorkspaceVariable, validationFn, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [resolvedValue, setResolvedValue] = useState(''); const [error, setError] = useState(null); const [coersionData, setCoersionData] = useState(null); diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 8f36315a64..5e93abffcf 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -22,10 +22,10 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { initialValue, onChange, enablePreview = true, portalProps } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); diff --git a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx index 6ec4a22dd1..56070684c0 100644 --- a/frontend/src/AppBuilder/Header/AppVersionsManager.jsx +++ b/frontend/src/AppBuilder/Header/AppVersionsManager.jsx @@ -6,7 +6,7 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { decodeEntities } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const appVersionLoadingStatus = Object.freeze({ loading: 'loading', @@ -15,7 +15,7 @@ const appVersionLoadingStatus = Object.freeze({ }); export const AppVersionsManager = function ({ darkMode }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx index 888b91963d..702ffb42f9 100644 --- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import Select from '@/_ui/Select'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CreateVersionModal = ({ showCreateAppVersion, @@ -18,7 +18,7 @@ const CreateVersionModal = ({ fetchingOrgGit, handleCommitOnVersionCreation = () => {}, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isCreatingVersion, setIsCreatingVersion] = useState(false); const [versionName, setVersionName] = useState(''); diff --git a/frontend/src/AppBuilder/Header/EditAppName.jsx b/frontend/src/AppBuilder/Header/EditAppName.jsx index 94da928483..e9dff6074a 100644 --- a/frontend/src/AppBuilder/Header/EditAppName.jsx +++ b/frontend/src/AppBuilder/Header/EditAppName.jsx @@ -8,10 +8,10 @@ import { InfoOrErrorBox } from './InfoOrErrorBox'; import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; function EditAppName() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [appId, appName, setAppName, appCreationMode] = useStore( (state) => [ state.appStore.modules[moduleId].app.appId, diff --git a/frontend/src/AppBuilder/Header/EditVersionModal.jsx b/frontend/src/AppBuilder/Header/EditVersionModal.jsx index bb79468ef3..c22dd74991 100644 --- a/frontend/src/AppBuilder/Header/EditVersionModal.jsx +++ b/frontend/src/AppBuilder/Header/EditVersionModal.jsx @@ -4,10 +4,10 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isEditingVersion, setIsEditingVersion] = useState(false); const { updateVersionNameAction, diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index dfa8d8940f..302c462e03 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -13,10 +13,10 @@ import BuildSuggestions from './BuildSuggestions'; import GitSyncManager from './GitSyncManager'; import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; import { ModuleEditorBanner } from '@/modules/Modules/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const EditorHeader = ({ darkMode, isModuleEditor }) => { - const moduleId = useModuleId(); +export const EditorHeader = ({ darkMode }) => { + const { moduleId, isModuleEditor } = useModuleContext(); const { isSaving, saveError, isVersionReleased } = useStore( (state) => ({ isSaving: state.appStore.modules[moduleId].app.isSaving, diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx index a3638c9a97..263439228b 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/PromoteVersionButton.jsx @@ -4,10 +4,10 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import PromoteConfirmationModal from './PromoteConfirmationModal'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx index 3052f05d17..48b77cc238 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/ReleaseVersionButton.jsx @@ -8,10 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( diff --git a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx index d709945ca9..9b144bdd88 100644 --- a/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx +++ b/frontend/src/AppBuilder/Header/RightTopHeaderButtons/RightTopHeaderButtons.jsx @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import useStore from '@/AppBuilder/_stores/store'; import { PromoteReleaseButton } from '@/modules/Appbuilder/components'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RightTopHeaderButtons = ({ isModuleEditor }) => { return ( @@ -22,7 +22,7 @@ const RightTopHeaderButtons = ({ isModuleEditor }) => { }; const PreviewAndShareIcons = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { featureAccess, currentPageHandle, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx index bea8953829..49c424fc04 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/AppExport.jsx @@ -3,10 +3,10 @@ import { Button } from '@/components/ui/Button/Button'; import ExportAppModal from '@/HomePage/ExportAppModal'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const AppExport = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { app } = useStore( (state) => ({ app: state.appStore.modules[moduleId].app, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx index 5d8edf4f41..efe3fb8766 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/CanvasSettings.jsx @@ -11,10 +11,10 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton'; import { useTranslation } from 'react-i18next'; import { Confirm } from '@/Editor/Viewer/Confirm'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const CanvasSettings = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { globalSettings, globalSettingsChanged, diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx index 269ea98e20..536f3fd462 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/SlugInput.jsx @@ -8,11 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes'; import useStore from '@/AppBuilder/_stores/store'; import { useTranslation } from 'react-i18next'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; // import { useStore } from '@/store'; const SlugInput = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { slug: oldSlug, appId, diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index c2c43f7647..1c377807c3 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -10,7 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector'; import GlobalSettings from './GlobalSettings'; import '../../_styles/left-sidebar.scss'; import Debugger from './Debugger/Debugger'; -import { useModuleId, useAppType } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; // TODO: remove passing refs to LeftSidebarItem and use state @@ -23,10 +23,8 @@ export const BaseLeftSidebar = ({ switchDarkMode, renderAISideBarTrigger = () => null, renderAIChat = () => null, - isModuleEditor = false, }) => { - const moduleId = useModuleId(); - const appType = useAppType(); + const { moduleId, isModuleEditor, appType } = useModuleContext(); const [ pinned, selectedSidebarItem, diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx index 9cd9e5dcf5..9c2562c7ed 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageHandlerMenu.jsx @@ -2,10 +2,10 @@ import React from 'react'; import { Overlay, Popover } from 'react-bootstrap'; import { Button } from '@/_ui/LeftSidebar'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const PageHandlerMenu = ({ darkMode }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const setShowEditingPopover = useStore((state) => state.setShowEditingPopover); const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal); diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx index 962f40554b..044eb376ae 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenuItem.jsx @@ -16,11 +16,11 @@ import { RenameInput } from './RenameInput'; import IconSelector from './IconSelector'; import { withRouter } from '@/_hoc/withRouter'; import OverflowTooltip from '@/_components/OverflowTooltip'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const PageMenuItem = withRouter( memo(({ darkMode, page, navigate }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); const isHomePage = page.id === homePageId; const currentPageId = useStore((state) => state.modules[moduleId].currentPageId); diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 75cbe9cef7..b6764ce8e5 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -12,10 +12,10 @@ import { Button } from 'react-bootstrap'; import { decodeEntities } from '@/_helpers/utils'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const updateQuerySuggestions = useStore((state) => state.queryPanel.updateQuerySuggestions); const previewQuery = useStore((state) => state.queryPanel.previewQuery); const renameQuery = useStore((state) => state.dataQuery.renameQuery); diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx index 367a71e5c3..2fdd002906 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Workflows.jsx @@ -5,10 +5,10 @@ import CodeHinter from '@/AppBuilder/CodeEditor'; import './workflows-query.scss'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export function Workflows({ options, optionsChanged, currentState }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [workflowOptions, setWorkflowOptions] = useState([]); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx index afe6aa6604..dc6f075d33 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryCard.jsx @@ -13,10 +13,10 @@ import useStore from '@/AppBuilder/_stores/store'; import { Confirm } from '@/Editor/Viewer/Confirm'; // TODO: enable delete query confirmation popup import { debounce } from 'lodash'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow); diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx index 20d7e8c66f..665f7b6755 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/DatetimePickerV2.jsx @@ -9,7 +9,7 @@ import cx from 'classnames'; import useStore from '@/AppBuilder/_stores/store'; import styles from '@/_ui/Select/styles'; import moment from 'moment-timezone'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const DATE_FORMAT_OPTIONS = [ { @@ -102,7 +102,7 @@ const DatetimePickerV2 = ({ componentMeta, componentName, darkMode, ...restProps allComponents, pages, } = restProps; - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const items = []; const additionalActions = []; const properties = []; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index c1a51eab11..01b797ca91 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -30,7 +30,7 @@ import { appService } from '@/_services'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const EventManager = ({ sourceId, @@ -44,7 +44,7 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup diff --git a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx index 5efc153286..3322b9e143 100644 --- a/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx +++ b/frontend/src/AppBuilder/RightSideBar/RightSideBar.jsx @@ -4,8 +4,10 @@ import { ComponentConfigurationTab } from './ComponentConfigurationTab'; import ComponentsManagerTab from './ComponentManagerTab'; import cx from 'classnames'; import { PageSettings } from './PageSettingsTab'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const RightSideBar = ({ darkMode, isModuleEditor }) => { +export const RightSideBar = ({ darkMode }) => { + const { isModuleEditor } = useModuleContext(); const activeTab = useStore((state) => state.activeRightSideBarTab); const pageSettingSelected = useStore((state) => state.pageSettingSelected); @@ -17,7 +19,7 @@ export const RightSideBar = ({ darkMode, isModuleEditor }) => { {activeTab === 'components' ? ( ) : ( - + )}
diff --git a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx index c6759bc931..0862634c77 100644 --- a/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx +++ b/frontend/src/AppBuilder/Viewer/MobileNavigationMenu.jsx @@ -8,10 +8,10 @@ import Cross from '@/_ui/Icon/solidIcons/Cross'; import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import * as Icons from '@tabler/icons-react'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch, currentPageId, icon }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isExpanded, setIsExpanded] = useState(true); const groupActive = currentPage.pageGroupId === pageGroup?.id; const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); @@ -80,7 +80,7 @@ const RenderGroup = ({ pages, pageGroup, currentPage, darkMode, handlepageSwitch }; const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, currentPage }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const tree = buildTree(pages); const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId); return ( @@ -126,7 +126,7 @@ const RenderPageGroups = ({ pages, handlepageSwitch, darkMode, currentPageId, cu }; const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeToDarkMode, showDarkModeToggle }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); const license = useStore((state) => state.license); diff --git a/frontend/src/AppBuilder/Viewer/PageGroup.jsx b/frontend/src/AppBuilder/Viewer/PageGroup.jsx index 28fa683df6..6dab237909 100644 --- a/frontend/src/AppBuilder/Viewer/PageGroup.jsx +++ b/frontend/src/AppBuilder/Viewer/PageGroup.jsx @@ -8,7 +8,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { buildTree } from '../LeftSidebar/PageMenu/Tree/utilities'; import OverflowTooltip from '@/_components/OverflowTooltip'; import cx from 'classnames'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const RenderPage = ({ page, currentPageId, switchPageWrapper, labelStyle, computeStyles, darkMode, homePageId }) => { const isHomePage = page.id === homePageId; @@ -142,7 +142,7 @@ const RenderPageGroup = ({ export const RenderPageAndPageGroup = ({ pages, labelStyle, computeStyles, darkMode, switchPageWrapper }) => { // Don't render empty folders if displaying only icons - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const tree = buildTree(pages, !!labelStyle?.label?.hidden); const currentPageId = useStore((state) => state.currentPageId); diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index e71c26b28e..8931d8df1d 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -195,7 +195,7 @@ export const Viewer = ({ className={cx('viewer wrapper', { 'mobile-layout': currentLayout, 'theme-dark dark-theme': darkMode })} > - + {renderHeader()}
diff --git a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx index 94a43bee72..3e4a4b4810 100644 --- a/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx +++ b/frontend/src/AppBuilder/Viewer/ViewerSidebarNavigation.jsx @@ -9,7 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { APP_HEADER_HEIGHT } from '../AppCanvas/appCanvasConstants'; import OverflowTooltip from '@/_components/OverflowTooltip'; import { RenderPageAndPageGroup } from './PageGroup'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ViewerSidebarNavigation = ({ isMobileDevice, @@ -21,7 +21,7 @@ export const ViewerSidebarNavigation = ({ isSidebarPinned, toggleSidebarPinned, }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const { definition: { styles = {}, properties = {} } = {} } = useStore((state) => state.pageSettings) || {}; const selectedVersionName = useStore((state) => state.selectedVersion?.name); const selectedEnvironmentName = useStore((state) => state.selectedEnvironment?.name); diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx index a9e1c58ae9..2032c08aa5 100644 --- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx +++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx @@ -26,7 +26,7 @@ import cx from 'classnames'; import { useGridStore } from '@/_stores/gridStore'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ @@ -41,7 +41,7 @@ const dropAnimation = { const TRASH_ID = 'void'; export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const updateCustomResolvables = useStore((state) => state.updateCustomResolvables, shallow); const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps; const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties; diff --git a/frontend/src/AppBuilder/Widgets/Modal.jsx b/frontend/src/AppBuilder/Widgets/Modal.jsx index fe1c433b83..e76070a1db 100644 --- a/frontend/src/AppBuilder/Widgets/Modal.jsx +++ b/frontend/src/AppBuilder/Widgets/Modal.jsx @@ -7,7 +7,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { debounce } from 'lodash'; var tinycolor = require('tinycolor2'); -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const Modal = function Modal({ id, @@ -21,7 +21,7 @@ export const Modal = function Modal({ dataCy, height, }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, diff --git a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx index 3c9329a3ea..01c07cf49f 100644 --- a/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx +++ b/frontend/src/AppBuilder/Widgets/ModalV2/ModalV2.jsx @@ -16,7 +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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; export const ModalV2 = function Modal({ id, @@ -30,7 +30,7 @@ export const ModalV2 = function Modal({ dataCy, height, }) { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [showModal, setShowModal] = useState(false); const { closeOnClickingOutside = false, diff --git a/frontend/src/AppBuilder/Widgets/Table/Table.jsx b/frontend/src/AppBuilder/Widgets/Table/Table.jsx index 0473de3280..5921fd0e34 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Table.jsx @@ -49,7 +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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +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) => { @@ -88,7 +88,7 @@ export const Table = React.memo( // events, // setProperty, }) => { - const moduleId = useModuleId(); + 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); diff --git a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx index bbdf89d936..d0f2b96c31 100644 --- a/frontend/src/AppBuilder/_contexts/ModuleContext.jsx +++ b/frontend/src/AppBuilder/_contexts/ModuleContext.jsx @@ -10,6 +10,14 @@ export const ModuleProvider = ({ moduleId, isModuleMode, appType, isModuleEditor ); }; +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) { 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 2eada0e175..cb3ae6a1d1 100644 --- a/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx +++ b/frontend/src/modules/common/components/BasePromoteReleaseButton/components/PromoteVersionButton/PromoteVersionButton.jsx @@ -4,10 +4,10 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { PromoteConfirmationModal } from './components'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteVersionButton = () => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promoteModalData, setPromoteModalData] = useState(null); const { isSaving, editingVersion, appVersionEnvironment, environments, selectedEnvironment } = useStore( (state) => ({ 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 f876dc86fe..46e05dadb8 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,10 +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 { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PromoteConfirmationModal = React.memo(({ data, onClose }) => { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [promotingEnvironment, setPromotingEnvironment] = useState(false); const darkMode = localStorage.getItem('darkMode') === 'true' || false; const currentVersionId = useStore((state) => state.currentVersionId); 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 3052f05d17..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,10 +8,10 @@ import { shallow } from 'zustand/shallow'; import '@/_styles/versions.scss'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useStore from '@/AppBuilder/_stores/store'; -import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const ReleaseVersionButton = function DeployVersionButton() { - const moduleId = useModuleId(); + const { moduleId } = useModuleContext(); const [isReleasing, setIsReleasing] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore( From 5a90163c0b6a77930bf44bbc42f829a03c6ace8e Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Wed, 30 Apr 2025 10:54:51 +0700 Subject: [PATCH 16/84] Fixed bug on running modules query from app. Passed version ID from modules property while running the queries --- frontend/ee | 2 +- frontend/src/AppBuilder/AppCanvas/Container.jsx | 1 + .../src/AppBuilder/_stores/slices/queryPanelSlice.js | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/ee b/frontend/ee index 425820a97a..b4e614bfdb 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 425820a97a5dbb3c8a26037f7f21252e465d07f1 +Subproject commit b4e614bfdb8fe855bd4141b1a33dca9ab9ab6c8e diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 78ec45823a..c6a1b979c1 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -85,6 +85,7 @@ export const Container = React.memo( return; } + // IMPORTANT: This logic needs to be changed when we implement the module versioning const moduleInfo = component?.moduleId ? { moduleId: component.moduleId, diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 733d2854f7..865bd1c8e5 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -284,7 +284,8 @@ 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(moduleId), queries: get().getQueryNameIdMapping(moduleId), @@ -342,11 +343,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 ); } From 58f640b1ed4a76cd9612c78274dbd858642e5dba Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 2 May 2025 02:42:21 +0530 Subject: [PATCH 17/84] Inspector revamped --- .../CustomJSONViewer/Components/ArrayNode.jsx | 11 ++ .../Components/BooleanNode.jsx | 12 ++ .../Components/FunctionNode.jsx | 12 ++ .../CustomJSONViewer/Components/NullNode.jsx | 12 ++ .../Components/NumberNode.jsx | 12 ++ .../Components/ObjectNode.jsx | 11 ++ .../CustomJSONViewer/Components/Row.jsx | 123 ++++++++++++++++++ .../Components/StringNode.jsx | 12 ++ .../CustomJSONViewer/CustomJSONViewer.jsx | 17 +++ .../CustomJSONViewer/styles.scss | 74 +++++++++++ .../LeftSidebarInspector/DefaultCopyIcon.jsx | 6 +- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 7 +- .../LeftSidebarInspector/JSONViewer.jsx | 42 +----- .../useCallbackActions.js | 8 +- .../LeftSidebar/LeftSidebarInspector/utils.js | 27 +--- 15 files changed, 313 insertions(+), 73 deletions(-) create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx create mode 100644 frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx new file mode 100644 index 0000000000..f9a0acf5ef --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ArrayNode.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const ArrayNode = ({ value }) => { + return ( +
+ {`[${value.length}]`} +
+ ); +}; + +export default ArrayNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx new file mode 100644 index 0000000000..d692a9c10c --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/BooleanNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const BooleanNode = ({ value }) => { + return ( +
+ {value.toString()} +
+ ); +}; + +export default BooleanNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx new file mode 100644 index 0000000000..456b8238c4 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/FunctionNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const FunctionNode = () => { + return ( +
+ function +
+ ); +}; + +export default FunctionNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx new file mode 100644 index 0000000000..40ff32737a --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NullNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NullNode = ({ value }) => { + return ( +
+ {value === null ? 'null' : 'undefined'} +
+ ); +}; + +export default NullNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx new file mode 100644 index 0000000000..8eb6e981b9 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/NumberNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const NumberNode = ({ value }) => { + return ( +
+ {value} +
+ ); +}; + +export default NumberNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx new file mode 100644 index 0000000000..e89082cbd1 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/ObjectNode.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const ObjectNode = ({ value }) => { + return ( +
+ {`{${Object.keys(value).length}}`} +
+ ); +}; + +export default ObjectNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx new file mode 100644 index 0000000000..ed5ef5a10c --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -0,0 +1,123 @@ +import React, { useState } from 'react'; +import StringNode from './StringNode'; +import FunctionNode from './FunctionNode'; +import NumberNode from './NumberNode'; +import BooleanNode from './BooleanNode'; +import NullNode from './NullNode'; +import ArrayNode from './ArrayNode'; +import ObjectNode from './ObjectNode'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { ToolTip } from '@/_components/ToolTip'; +import { DefaultCopyIcon } from '../../DefaultCopyIcon'; +import { copyToClipboard } from '../../utils'; + +const Row = ({ label, value, level = 1, absolutePath }) => { + const [isExpanded, setIsExpanded] = useState(false); + const Node = () => { + if (typeof value === 'string') { + return ; + } else if (typeof value === 'undefined' || value === null) { + return ; + } else if (typeof value === 'number') { + return ; + } else if (typeof value === 'boolean') { + return ; + } else if (Array.isArray(value)) { + return ; + } else if (typeof value === 'object') { + return ; + } else if (typeof value === 'function') { + return ; + } + }; + + const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null; + const isArray = Array.isArray(value); + + return ( +
+
1 ? '8px' : '0px' }}> +
setIsExpanded((prev) => !prev)}> +
+ {(isArray || isObject) && + (isExpanded ? ( + + ) : ( + + ))} +
+
+ {label} +
+
+ +
+
+ + { + copyToClipboard(absolutePath); + }} + className="copy-to-clipboard json-viewer-action-icon" + > + + + + + { + copyToClipboard(value); + }} + className="json-viewer-action-icon" + > + + + +
+
+
+ {isExpanded && isObject && ( +
+ {Object.entries(value).map(([key, val]) => ( + + ))} +
+ )} + {isExpanded && isArray && ( +
+ {value.map((item, index) => { + return ( + + ); + })} +
+ )} +
+ ); +}; + +export default Row; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx new file mode 100644 index 0000000000..3c4bcc3644 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/StringNode.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import OverflowTooltip from '@/_components/OverflowTooltip'; + +const StringNode = ({ value }) => { + return ( +
+ {`"${value}"`} +
+ ); +}; + +export default StringNode; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx new file mode 100644 index 0000000000..b5e392ee66 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/CustomJSONViewer.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Row from './Components/Row'; +import './styles.scss'; + +const CustomJSONViewer = ({ data, absolutePath }) => { + let modifiedData = data; + if (typeof data !== 'object') modifiedData = { '': data }; + return ( +
+ {Object.entries(modifiedData).map(([key, value], index) => { + return ; + })} +
+ ); +}; + +export default CustomJSONViewer; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss new file mode 100644 index 0000000000..62a2adb902 --- /dev/null +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -0,0 +1,74 @@ +.custom-json-viewer { + margin-left: 16px; + margin-right: 16px; + font-family: "IBM Plex Sans"; + font-size: 12px; + color: var(--text-default, #1B1F24); + + + .json-viewer-row-container { + &:hover { + background-color: var(--interactive-overlays-fill-hover); + + .json-viewer-actions-container { + display: flex; + } + } + } + + .json-viewer-row { + width: 100%; + display: flex; + height: 20px; + align-items: center; + overflow: hidden; + + .json-viewer-expand-icon { + width: 12px; + height: 12px; + display: flex; + align-items: center; + justify-content: center; + } + + .json-viewer-label-container { + margin-left: 8px; + margin-right: 4px; + flex-shrink: 0; /* don’t shrink */ + white-space: nowrap; + } + + .json-viewer-value-container { + flex: 1; /* take available space */ + min-width: 0; /* allow shrinkage */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .json-viewer-actions-container { + display:none; + margin-left: auto; + margin-right: 4px; + height: 12px; + width:40px; + align-items: center; + flex-shrink: 0; + + .json-viewer-action-icon { + display: flex; + align-items: center; + justify-content: center; + height: 20px; + width: 20px; + + &:hover { + cursor: pointer; + background-color: var(--button-outline-hover); + border-radius: 4px; + } + } + } + + } +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx index fa46cf9694..6d26f2676b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -1,9 +1,9 @@ import React from 'react'; -export const DefaultCopyIcon = () => ( +export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( { const searchValue = useStore((state) => state.inspectorSearchValue, shallow); - // const getSelectedNodes = useStore((state) => state.getSelectedNodes, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); const [selectedNodePath, setSelectedNodePath] = React.useState(null); @@ -38,6 +37,7 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths return [new Set(result), expandedIdSet]; }, [searchValue, JSON.stringify(searchablePaths)]); + // Do not remove this code, once we have the data in the correct format, we can use this function to filter the data // const recursiveFn = (obj) => { // if (!obj || typeof obj !== 'object') return []; // let isCompletelyExposed = false; @@ -89,10 +89,9 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths return filtered.map((item) => item.id); }, [flattendedData, expandedIds]); - console.log('selectedData', selectedData); return ( <> - {!selectedNodePath || isEmpty(selectedData) ? ( + {!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
{ const { data, path, darkMode, backFn } = props; - const [theme, setTheme] = useState(() => getTheme(darkMode)); + const callbackActions = useCallbackActions() || []; const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions'; const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions; @@ -23,10 +16,6 @@ export const JSONViewer = (props) => { const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || []; - useEffect(() => { - setTheme(() => getTheme(darkMode)); - }, [darkMode]); - return (
{ data={optionsData} type={type} /> - - { - const key = keyPath[0]; - if (!key && key != 0) return ''; - return key; - }} - valueRenderer={(raw, value) => { - if (typeof value === 'function') { - return ( - - function - - ); - } - - return {raw}; - }} - /> +
); }; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 6651e5813f..3b256afc2a 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -1,7 +1,7 @@ import { toast } from 'react-hot-toast'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -// import { runQuery } from '@/AppBuilder/_utils/queryPanel'; +import { copyToClipboard } from './utils'; const useCallbackActions = () => { const deleteComponents = useStore((state) => state.deleteComponents, shallow); @@ -40,12 +40,6 @@ const useCallbackActions = () => { setSelectedQuery(id); }; - const copyToClipboard = (data) => { - const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); - navigator.clipboard.writeText(stringified); - return toast.success('Copied to the clipboard', { position: 'top-center' }); - }; - const handleAutoScrollToComponent = (data) => { const componentId = getComponentIdFromName(data.nodeName); const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 13ad6cc3f4..36a2afb8f5 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -1,3 +1,5 @@ +import { toast } from 'react-hot-toast'; + export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => { if (typeof obj !== 'object' || obj === null) return []; const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' })); @@ -125,25 +127,8 @@ export const extractComponentName = (path) => { } }; -export const getTheme = (darkMode) => { - return { - scheme: 'custom', - author: 'chris kempson (http://chriskempson.com)', - base00: 'transparent', - base01: '#303030', - base02: '#505050', - base03: '#b0b0b0', - base04: '#d0d0d0', - base05: '#1B1F24', - base06: '#f5f5f5', - base07: '#ffffff', - base08: '#fb0120', - base09: '#9467BD', - base0A: '#fda331', - base0B: '#2CA02C', - base0C: '#76c7b7', - base0D: '#e4e0db', - base0E: '#d381c3', - base0F: '#be643c', - }; +export const copyToClipboard = (data) => { + const stringified = JSON.stringify(data, null, 2).replace(/\\/g, ''); + navigator.clipboard.writeText(stringified); + return toast.success('Copied to the clipboard', { position: 'top-center' }); }; From 58078fdaeb4f3986bdf34f0f85ca7fc9f5daf6ae Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Fri, 2 May 2025 14:03:58 +0530 Subject: [PATCH 18/84] Updated to sprint 12 commits --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index dcd948d284..518f3334b1 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit dcd948d284b5f14a868480830e09b90496db8572 +Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a diff --git a/server/ee b/server/ee index 683647f83d..84ec48d0f6 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 683647f83d3efeeadbe69c40b8e8dd5ba4e8ea06 +Subproject commit 84ec48d0f64fd6dc5f7677f71a5119219cc4ada4 From f1820e96206ddf5ee993915a1df619effec3afbe Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 6 May 2025 03:35:22 +0530 Subject: [PATCH 19/84] Inspector revamp fixes --- .../CustomJSONViewer/Components/Row.jsx | 2 +- .../CustomJSONViewer/styles.scss | 1 + .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 96 ++++++++++++------ .../LeftSidebarInspector.jsx | 23 ++--- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 45 ++++++--- .../LeftSidebarInspector/TreeViewHeader.jsx | 2 + .../LeftSidebar/LeftSidebarInspector/utils.js | 45 +-------- .../_stores/slices/inspectorSlice.js | 99 +++++++++++++++++++ frontend/src/_styles/theme.scss | 40 ++++++-- 9 files changed, 241 insertions(+), 112 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index ed5ef5a10c..ca8396875b 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -43,7 +43,7 @@ const Row = ({ label, value, level = 1, absolutePath }) => { (isExpanded ? ( { const searchValue = useStore((state) => state.inspectorSearchValue, shallow); + const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow); + const getComponentDefinition = useStore((state) => state.getComponentDefinition, shallow); const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow); const [selectedNodePath, setSelectedNodePath] = React.useState(null); @@ -76,7 +78,27 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths setSelectedNodePath(null); }; - const selectedData = selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + const selectedData = (() => { + if (selectedNodePath?.startsWith('components.')) { + // Split the selectedNode path using . and grab the second element if it exists + const pathArray = selectedNodePath.split('.'); + const componentName = pathArray?.[1]; + const componentId = getComponentIdFromName(componentName); + const component = getComponentDefinition(componentId); + const parent = component?.component?.parent; + if (parent) { + const parentComponent = getComponentDefinition(parent); + const parentType = parentComponent?.component?.component; + if (parentType === 'Form') { + return { + id: componentId, + }; + } + } + } + return selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {}; + })(); + const expandedIds = [...Array.from(pathSet), ...selectedNodes]; const filteredIds = useMemo(() => { @@ -86,7 +108,20 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths const { path } = metadata || {}; return expandedIdsSet.has(path); }); - return filtered.map((item) => item.id); + + return filtered + .map((item) => item.id) + .filter((path) => { + const pathArray = path.split('.'); + // One by one combine and check if the path is in expandedIds or not + for (let i = pathArray.length - 1; i > 0; i--) { + const parentPath = pathArray.slice(0, i).join('.'); + if (!expandedIdsSet.has(parentPath)) { + return false; + } + } + return true; + }); }, [flattendedData, expandedIds]); return ( @@ -105,35 +140,36 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths width={300} />
+
+ { + const { element } = props; + const { metadata } = element || {}; + const { path } = metadata || {}; + const data = { + nodeName: element.name, + selectedNodePath: path, + }; - { - const { element } = props; - const { metadata } = element || {}; - const { path } = metadata || {}; - const data = { - nodeName: element.name, - selectedNodePath: path, - }; - - return ( - - ); - }} - /> + return ( + + ); + }} + /> +
) : ( diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx index 4e3dbf9c73..734f863080 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/LeftSidebarInspector.jsx @@ -7,8 +7,8 @@ import JSONTreeViewerV2 from './JSONTreeViewerV2'; import _ from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import useIconList from './useIconList'; - -import { formatInspectorComponentData, formatInspectorDataMisc, formatInspectorQueryData } from './utils'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; +import { formatInspectorDataMisc, formatInspectorQueryData } from './utils'; const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow); @@ -18,6 +18,7 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => { const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow); const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow); const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow); + const formatInspectorComponentData = useStore((state) => state.formatInspectorComponentData, shallow); const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow); const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants'])); @@ -109,18 +110,14 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
- setPinned(!pinned)} - darkMode={darkMode} - styles={{ width: '28px', padding: 0 }} - data-cy={`left-sidebar-inspector`} - variant="ghostBlack" - className="left-sidebar-header-btn" - leftIcon={pinned ? 'unpin' : 'pin'} - iconWidth="14" - fill={`var(--slate12)`} - > + variant="ghost" + fill="var(--icon-strong,#6A727C)" + size="medium" + />
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 27fafb22ae..1b01f48daf 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -9,6 +9,7 @@ import OverflowTooltip from '@/_components/OverflowTooltip'; import { HiddenOptions } from './HiddenOptions'; import useCallbackActions from './useCallbackActions'; import useStore from '@/AppBuilder/_stores/store'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; import { shallow } from 'zustand/shallow'; const renderNodeIcons = (node, iconsList, darkMode) => { @@ -56,21 +57,26 @@ export const Node = (props) => { const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow); const callbackActions = useCallbackActions() || []; const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode); + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); const metadata = element.metadata || {}; - const { type } = metadata; + const { type, path } = metadata; const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for)); + const onExpand = (node) => { + const { element } = node || {}; + const { metadata } = element || {}; + const { path } = metadata || {}; + setSelectedNodes(path); + }; + const onSelect = (node) => { const { isBranch, element } = node || {}; const { metadata } = element || {}; - const { path } = metadata || {}; - if (!isBranch) { + const { path, type } = metadata || {}; + if (type) { setSelectedNodePath(path); - } else { - setSelectedNodePath(null); } - - setSelectedNodes(path); }; + const nodeSpecificFilteredActions = nodeSpecificActions?.[0]?.actions?.filter((action) => { return action.enableInspectorTreeView; @@ -84,27 +90,34 @@ export const Node = (props) => { return ( //
onSelect(props)} style={{ - marginLeft: 22 * (level - 1), + marginLeft: level > 1 ? 12 : 0, + // paddingLeft: '16px', opacity: isDisabled ? 0.5 : 1, height: level === 1 ? '28px' : '32px', display: 'flex', alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', - cursor: isBranch || level === 1 ? 'pointer' : 'default', + // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > - {(isBranch || level === 1) && ( + {!['queries', 'globals', 'variables'].includes(type) && (
- {isExpanded ? ( - - ) : ( - + {(isBranch || level === 1 || path === 'page.variables') && ( + onExpand(props)} + variant="ghost" + fill="var(--icon-default,#ACB2B9)" + size="small" + /> )}
)} +
onSelect(props)} className={cx('node-content', { 'node-content-hoverable': level !== 1, 'node-content-active': actionClicked, @@ -112,7 +125,7 @@ export const Node = (props) => { > {nodeIcon &&
{nodeIcon}
}
- + { className="copy-menu-options-icon json-viewer-options-btn" style={{ outline: 'none', + border: 'none', + boxShadow: 'none', }} > diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js index 36a2afb8f5..203b5ea298 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/utils.js @@ -19,7 +19,7 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) name, children: reduceData(value, currentPath, level + 1), metadata: { - type: 'misc', + type: type, path: currentPath, ...((path === 'page.variables' ? level === 2 : level === 1) && { data: typeof value === 'object' ? JSON.stringify(value) : value, @@ -33,49 +33,6 @@ export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) return reduceData(data, type); }; -export const formatInspectorComponentData = ( - componentIdNameMapping, - exposedComponentsVariables, - searchablePaths = new Set() -) => { - const data = Object.entries(componentIdNameMapping) - .map(([key, name]) => ({ - key, - name: name || key, - value: exposedComponentsVariables[key] ?? { id: key }, - })) - .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' || level > 1) return []; - else if (!Array.isArray(obj)) { - data = Object.entries(obj); - } - return data - .filter((item) => item.name) - .reduce((acc, { key, name, value }) => { - const currentPath = path + `.${name}`; - searchablePaths.add(currentPath); - return [ - ...acc, - { - id: currentPath, - name, - children: reduceData(value, currentPath, level + 1), - metadata: { - type: 'components', - path: currentPath, - ...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }), - }, - }, - ]; - }, []); - }; - - return reduceData(data); -}; - export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => { const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name])); const _sortedQueries = Object.entries(exposedQueries) diff --git a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js index b0b8a95e84..521fff3b1b 100644 --- a/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/inspectorSlice.js @@ -31,4 +31,103 @@ export const createInspectorSlice = (set, get) => ({ setInspectorSearchResults: (results) => { set({ inspectorSearchResults: results }); }, + 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}`; + searchablePaths.add(currentPath); + 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: currentPath, + name, + children: reduceData(children, currentPath, level + 1), + metadata: { + type: 'components', + path: currentPath, + parentType: parentType, + }, + }, + ]; + }, []); + }; + + return reduceData(data); + }, }); diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 69bf8326e3..993dd6bdf3 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18877,12 +18877,24 @@ section.ai-message-prompt-input-wrapper { } } +.json-tree-view { + ul { + margin-left:16px !important; + border-left: 1px solid var(--slate6, #D7DBDF); + } + + ul[role="tree"] { + border-left: none !important; + } +} .basic.tree { list-style: none; margin: 0; - padding: 20px; + padding: 0px; + padding-right:20px; padding-top: 0px; + } .basic .tree-node, @@ -18919,6 +18931,7 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; justify-content: center; + flex-shrink: 0; } .node-icon { @@ -18932,6 +18945,8 @@ section.ai-message-prompt-input-wrapper { display: flex; align-items: center; font-size:12px; + flex:1; + min-width: 0px; } @@ -19016,16 +19031,24 @@ section.ai-message-prompt-input-wrapper { } +// .json-viewer-options-btn { +// display: flex; +// align-items: center; +// margin-left: auto; + +// &:hover { +// cursor: pointer; +// background-color:var(--interactive-overlays-fill-hover); +// border-radius: 4px; +// } +// } + .json-viewer-options-btn { - display: flex; - align-items: center; margin-left: auto; - &:hover { - cursor: pointer; - background-color:var(--interactive-overlays-fill-hover); - border-radius: 4px; - } + + align-items: center; + } @@ -19052,6 +19075,7 @@ section.ai-message-prompt-input-wrapper { width: 20px; border-radius: 4px; margin-right:4px; + flex-shrink: 0; &:hover { background-color: var(--button-outline-hover); From 4804ae4a706a7d504439b118e17a853cdf117f22 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 11:40:22 +0700 Subject: [PATCH 20/84] Merged with main & fixed the bugs --- .../GlobalSettings/MaintenanceMode.jsx | 4 +++- .../LeftSidebar/PageMenu/PagePermission.jsx | 10 +++++++--- frontend/src/AppBuilder/Viewer/Viewer.jsx | 2 +- frontend/src/AppBuilder/_hooks/useAppData.js | 15 ++++++++++----- frontend/src/_helpers/routes.js | 1 + server/ee | 2 +- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx index c2b969146f..8b1b1cece0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/MaintenanceMode.jsx @@ -3,12 +3,14 @@ import useStore from '@/AppBuilder/_stores/store'; import SwitchComponent from '@/components/ui/Switch/Index'; import { shallow } from 'zustand/shallow'; import { Confirm } from '@/Editor/Viewer/Confirm'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const MaintenanceMode = ({ darkMode }) => { + const { moduleId } = useModuleContext(); const [showConfirmation, setConfirmationShow] = useState(false); const { isMaintenanceOn, toggleAppMaintenance } = useStore( (state) => ({ - isMaintenanceOn: state.app.isMaintenanceOn, + isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn, toggleAppMaintenance: state.toggleAppMaintenance, }), shallow diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx index 6a4a1c516a..92464e4573 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx @@ -8,6 +8,7 @@ import { appPermissionService } from '@/_services'; import { ConfirmDialog } from '@/_components'; import toast from 'react-hot-toast'; import Spinner from '@/_ui/Spinner'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const PERMISSION_TYPES = { single: 'SINGLE', @@ -16,10 +17,11 @@ const PERMISSION_TYPES = { }; export default function PagePermission({ darkMode }) { + const { moduleId } = useModuleContext(); const showPagePermissionModal = useStore((state) => state.showPagePermissionModal); const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal); const editingPage = useStore((state) => state.editingPage); - const appId = useStore((state) => state.app.appId); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const selectedUserGroups = useStore((state) => state.selectedUserGroups); const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups); const selectedUsers = useStore((state) => state.selectedUsers); @@ -360,7 +362,8 @@ export default function PagePermission({ darkMode }) { } const UserGroupSelect = () => { - const appId = useStore((state) => state.app.appId); + const { moduleId } = useModuleContext(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const selectedUserGroups = useStore((state) => state.selectedUserGroups); const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups); const [userGroups, setUserGroups] = useState([]); @@ -420,7 +423,8 @@ const UserGroupSelect = () => { }; const UserSelect = () => { - const appId = useStore((state) => state.app.appId); + const { moduleId } = useModuleContext(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const editingPage = useStore((state) => state.editingPage); const selectedUsers = useStore((state) => state.selectedUsers); const setSelectedUsers = useStore((state) => state.setSelectedUsers); diff --git a/frontend/src/AppBuilder/Viewer/Viewer.jsx b/frontend/src/AppBuilder/Viewer/Viewer.jsx index 8931d8df1d..48bfc7e632 100644 --- a/frontend/src/AppBuilder/Viewer/Viewer.jsx +++ b/frontend/src/AppBuilder/Viewer/Viewer.jsx @@ -202,7 +202,7 @@ export const Viewer = ({
diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 9440efca40..18abcc63be 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -358,13 +358,13 @@ const useAppData = ( global_settings.theme = baseTheme; } setGlobalSettings(global_settings); - - setPages(pages, moduleId); - setPageSettings( - computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_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); @@ -513,10 +513,15 @@ const useAppData = ( }; }) .catch((error) => { + console.log('error--- ', error); + if (isPublicAccess) { if (mode !== 'edit') { handleError('view', error); } + } else if (moduleMode) { + setEditorLoading(false, moduleId); + toast.error('Error fetching module data'); } }); }, [setApp, setEditorLoading, currentSession]); 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/server/ee b/server/ee index e4bada4648..f779cb9954 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e4bada464821218151cfa0c6ed1c7b31e18fa2c4 +Subproject commit f779cb99549ae248dbca9e3d0f658359651ede75 From 9e4ac4963921d2741226c30251c8ffd34553a893 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:02:04 +0700 Subject: [PATCH 21/84] Fix submodule reference --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index f779cb9954..7cc6976f78 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit f779cb99549ae248dbca9e3d0f658359651ede75 +Subproject commit 7cc6976f7801f538c64b1b13679c4d9117a8baa1 From fa89057aedccd523f02254b575d147a2f3ffefe9 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:09:41 +0700 Subject: [PATCH 22/84] Fixed the frontend/ee reference and BE error --- frontend/ee | 2 +- server/src/modules/modules/module.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 0d68bbb8d9..1594d113de 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 0d68bbb8d929d3a06df845e7292d086980b0dc9d +Subproject commit 1594d113de132885db9e9b1263299692a74abcbc diff --git a/server/src/modules/modules/module.ts b/server/src/modules/modules/module.ts index dd871434d0..a7af218b81 100644 --- a/server/src/modules/modules/module.ts +++ b/server/src/modules/modules/module.ts @@ -11,6 +11,7 @@ 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 { @@ -33,6 +34,7 @@ export class ModulesModule { await AppEnvironmentsModule.register(configs), await DataSourcesModule.register(configs), await AiModule.register(configs), + await AppPermissionsModule.register(configs), ], controllers: [ModulesController], providers: [ From c2e448e039e6e48413b513cd6270707b5dd885cb Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 6 May 2025 13:56:36 +0700 Subject: [PATCH 23/84] Hide the global settings --- frontend/ee | 2 +- .../LeftSidebar/GlobalSettings/index.jsx | 19 +-------------- .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 24 +++++++++---------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/frontend/ee b/frontend/ee index 1594d113de..5d358dc6e0 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 1594d113de132885db9e9b1263299692a74abcbc +Subproject commit 5d358dc6e0d3780383ed6b392a2e048b55e15c3b diff --git a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx index 3495239f41..d85917c797 100644 --- a/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/GlobalSettings/index.jsx @@ -11,26 +11,9 @@ import { ThemeSelect } from '@/modules/Appbuilder/components'; import MaintenanceMode from './MaintenanceMode'; import HideHeaderToggle from './HideHeaderToggle'; -const GlobalSettings = ({ darkMode, isModuleEditor }) => { +const GlobalSettings = ({ darkMode }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); - if (isModuleEditor) { - return ( -
-
- - - -
-
- -
-
-
-
- ); - } - return ( <>
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index 0f49eb8ef9..74c25264f1 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -192,18 +192,6 @@ export const BaseLeftSidebar = ({ tip="Debugger" ref={setSideBarBtnRefs('debugger')} /> - handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - isModuleEditor={isModuleEditor} - /> ); }; @@ -233,6 +221,18 @@ export const BaseLeftSidebar = ({ ref={setSideBarBtnRefs('page')} /> {renderCommonItems()} + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> ); }; From 53ba068947fd4cff18d6d51dc96a250471dcf567 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Thu, 8 May 2025 03:27:27 +0530 Subject: [PATCH 24/84] Design changes done --- .../LeftSidebarInspector/HiddenOptions.jsx | 7 +-- .../LeftSidebarInspector/JSONTreeViewerV2.jsx | 15 ++++++- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 44 ++++++++++--------- .../LeftSidebarInspector/TreeViewHeader.jsx | 29 ++++++------ .../LeftSidebarInspector/useIconList.js | 33 ++++++++++---- frontend/src/_styles/components.scss | 5 +-- frontend/src/_styles/theme.scss | 39 +++++++++++----- .../ui/Input/CommonInput/TextInput.jsx | 1 + frontend/src/components/ui/Input/Index.jsx | 2 + frontend/src/components/ui/Input/Input.jsx | 2 +- 10 files changed, 112 insertions(+), 65 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index c2055f4b8e..a2a4cebf80 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -74,7 +74,7 @@ export const HiddenOptions = (props) => { {renderOptions()} { setShowMenu((prev) => !prev); setActionClicked((prev) => !prev); }} - className="copy-menu-options-icon" + className="node-action-icon" style={{ outline: 'none', + ...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }), }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx index 43d29ed12c..a47d9fa060 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/JSONTreeViewerV2.jsx @@ -7,6 +7,7 @@ import JSONViewer from './JSONViewer'; import { SearchBox } from '@/_components'; import { Node } from './Node'; import { v4 as uuidv4 } from 'uuid'; +import InputComponent from '@/components/ui/Input/Index'; import { isEmpty } from 'lodash'; const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => { @@ -128,8 +129,8 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths <> {!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
-
- + {/* setSearchValue(e.target.value)} @@ -138,6 +139,16 @@ const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths customClass={`tj-inspector-search-input tj-text-xsm`} showClearButton={false} width={300} + /> */} + + setSearchValue(e.target.value)} + onClear={() => setSearchValue('')} + size="medium" + placeholder="Search" + value={searchValue} + {...(searchValue && { trailingAction: 'clear' })} />
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 1b01f48daf..86dffbf180 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -19,20 +19,21 @@ const renderNodeIcons = (node, iconsList, darkMode) => { ); } + if (icon && icon.jsx) { if (icon?.tooltipMessage) { return ( -
{icon.jsx()}
+
{icon.jsx({ height: 14, width: 14 })}
); } - return icon.jsx(); + return icon.jsx({ height: 14, width: 14 }); } }; @@ -91,30 +92,31 @@ export const Node = (props) => { //
1 ? 12 : 0, + // marginLeft: level > 1 ? 12 : 0, // paddingLeft: '16px', opacity: isDisabled ? 0.5 : 1, - height: level === 1 ? '28px' : '32px', + height: '20px', display: 'flex', alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', + fontWeight: level === 1 ? 500 : 400, // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > - {!['queries', 'globals', 'variables'].includes(type) && ( -
- {(isBranch || level === 1 || path === 'page.variables') && ( - onExpand(props)} - variant="ghost" - fill="var(--icon-default,#ACB2B9)" - size="small" - /> - )} -
- )} + {/* {!['queries', 'globals', 'variables'].includes(type) && ( */} +
+ {(isBranch || level === 1 || path === 'page.variables') && ( + onExpand(props)} + variant="ghost" + fill="var(--icon-default,#ACB2B9)" + size="small" + /> + )} +
+ {/* )} */}
onSelect(props)} @@ -134,7 +136,7 @@ export const Node = (props) => { />
-
+
{ return (
-
+ {/*
+
*/} +
+ {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} + + {pathArray.length > 1 && + pathArray.slice(1).map((item, index) => ( + <> + + + {item.charAt(0).toUpperCase() + item.slice(1)} + + + ))}
- - {parentNode.charAt(0).toUpperCase() + parentNode.slice(1)} - - - {pathArray.length > 1 && - pathArray.slice(1).map((item, index) => ( - <> - - - {item.charAt(0).toUpperCase() + item.slice(1)} - - - ))} - { const query = dataQueries.find((dataQuery) => dataQuery.id === queryId); if (!isEmpty(query)) { - return { iconName: query?.name, jsx: () => }; + return { + iconName: query?.name, + jsx: ({ height = 16, width = 16 }) => , + }; } }); @@ -37,7 +40,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -47,7 +52,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -62,7 +69,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.setChecked) { icons.push({ iconName: 'setChecked', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setValue as an alternative', isInfoIcon: true, @@ -78,7 +87,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.disable) { icons.push({ iconName: 'disable', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative', isInfoIcon: true, @@ -88,7 +99,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.visibility) { icons.push({ iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, @@ -98,7 +111,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos if (componentExposedVariables.loading) { icons.push({ iconName: 'loading', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setLoading as an alternative', isInfoIcon: true, @@ -112,7 +127,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos return [ { iconName: 'visibility', - jsx: () => , + jsx: ({ height = 16, width = 16 }) => ( + + ), className: 'component-icon', tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative', isInfoIcon: true, diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 9fbe2f33f4..27736f0a6e 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 8px 16px; - min-height: 52px; - // border-bottom: 1px solid var(--slate5); + padding: 12px 16px 0px 16px; .panel-header-container { diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 993dd6bdf3..03d5f363a9 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18878,13 +18878,24 @@ section.ai-message-prompt-input-wrapper { } .json-tree-view { - ul { - margin-left:16px !important; - border-left: 1px solid var(--slate6, #D7DBDF); - } + // ul { + // margin-left:16px !important; + // border-left: 1px solid var(--slate6, #D7DBDF); + // } ul[role="tree"] { + margin-left:16px !important; border-left: none !important; + ul { + margin-left:16px !important; + border-left: none !important; + ul { + margin-left:10px !important; + padding-left:16px !important; + border-left: 1px solid var(--slate6, #D7DBDF) !important; + } + } + } } @@ -18936,9 +18947,11 @@ section.ai-message-prompt-input-wrapper { .node-icon { display: flex; - align-items: center; + align-items: flex-end; justify-content: center; margin-right: 6px; + height: 14px; + width: 14px; } .node-label { @@ -18964,10 +18977,10 @@ section.ai-message-prompt-input-wrapper { .node-content { display: flex; align-items: center; - padding: 0px 8px; + padding: 0px 0px 0px 8px; height: 100%; width: 100%; - border-radius: 6px; + } .node-content-active { @@ -18978,6 +18991,7 @@ section.ai-message-prompt-input-wrapper { .node-content-hoverable:hover, .node-content-active { background-color: var(--interactive-overlays-fill-hover); + cursor: pointer; .node-actions { display: flex; } @@ -19014,9 +19028,13 @@ section.ai-message-prompt-input-wrapper { font-weight: 500; display: flex; flex-direction: row; - margin-left:20px; - margin-bottom:18px; + margin-left:16px; + margin-bottom:12px; margin-right: 18px; + margin-top: 8px; + height:28px; + align-items: center; + } .json-viewer-back-btn { @@ -19058,11 +19076,9 @@ section.ai-message-prompt-input-wrapper { } .node-actions { - justify-content: center; align-items: center; margin-left:auto; - margin-right: 24px; display: none; gap: 4px; } @@ -19104,6 +19120,7 @@ section.ai-message-prompt-input-wrapper { border: none; background: transparent; border-radius: 10px; + top: -5px !important; &.dark-theme { .popover-body { box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000; diff --git a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx index 1834d4799d..1dc3ce8d67 100644 --- a/frontend/src/components/ui/Input/CommonInput/TextInput.jsx +++ b/frontend/src/components/ui/Input/CommonInput/TextInput.jsx @@ -46,6 +46,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/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 33c3194f0b..610232eadf 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -19,7 +19,7 @@ const Input = React.forwardRef(({ className, size, type, ...props }, ref) => { type={isPasswordField && isPasswordVisible ? 'text' : type} className={cn( inputVariants({ size }), - `tw-relative tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`, + `tw-peer tw-flex tw-text-[12px]/[18px] tw-w-full tw-rounded-[8px] tw-border-[1px] tw-border-solid tw-bg-background-surface-layer-01 tw-py-[7px] tw-text-text-default focus-visible:tw-ring-[1px] focus-visible:tw-ring-offset-[1px] focus-visible:tw-ring-border-accent-strong focus-visible:tw-ring-offset-border-accent-strong focus-visible:tw-border-transparent disabled:tw-cursor-not-allowed ${props.styles}`, className )} ref={ref} From 52abc1e283b8c33b4e3e018269d9b469bb40dd77 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Thu, 8 May 2025 15:56:12 +0700 Subject: [PATCH 25/84] Fix: Fixed issue on running queries from app & duplicate component --- .../AppBuilder/AppCanvas/HotkeyProvider.jsx | 2 +- .../_stores/slices/queryPanelSlice.js | 33 ++++++++++++------- .../_stores/slices/resolvedSlice.js | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx index 5177e27af8..018383c61f 100644 --- a/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx +++ b/frontend/src/AppBuilder/AppCanvas/HotkeyProvider.jsx @@ -78,7 +78,7 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth } return componentType === 'ModuleContainer'; }) ) { - if (['KeyC', 'KeyX', 'KeyV', 'Backspace'].includes(key)) { + if (['KeyC', 'KeyX', 'KeyV', 'KeyD', 'Backspace'].includes(key)) { return; } } diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 59f3ddba98..7344f54aec 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -471,7 +471,8 @@ export const createQueryPanelSlice = (set, get) => ({ query.options.transformation, query.options.transformationLanguage, query, - 'edit' + 'edit', + moduleId ); if (finalData.status === 'failed') { setResolvedQuery( @@ -683,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); @@ -743,7 +745,7 @@ 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)) { @@ -758,17 +760,17 @@ export const createQueryPanelSlice = (set, get) => ({ }, 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; }, }; @@ -810,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); @@ -1012,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] = { @@ -1028,17 +1037,17 @@ export const createQueryPanelSlice = (set, get) => ({ }, 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 16de3dc461..46627307f9 100644 --- a/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/resolvedSlice.js @@ -533,7 +533,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: {}, From 2ec0506c8f75ed89d21461a4d78c7b7944d24dd4 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Sun, 11 May 2025 17:00:57 +0530 Subject: [PATCH 26/84] Padding issues and icon issues fixed --- .../CustomJSONViewer/Components/Row.jsx | 2 +- .../CustomJSONViewer/styles.scss | 1 - .../LeftSidebarInspector/DefaultCopyIcon.jsx | 6 ++--- .../LeftSidebarInspector/HiddenOptions.jsx | 8 +++--- .../LeftSidebar/LeftSidebarInspector/Node.jsx | 5 +++- .../LeftSidebarInspector/TreeViewHeader.jsx | 10 ++++---- .../useCallbackActions.js | 6 ++--- frontend/src/_styles/theme.scss | 6 ++--- frontend/src/_ui/Icon/solidIcons/Corners.jsx | 25 +++++++++++++++++++ frontend/src/_ui/Icon/solidIcons/FileCode.jsx | 25 +++++++++++++++++++ frontend/src/_ui/Icon/solidIcons/index.js | 6 +++++ 11 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/Corners.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/FileCode.jsx diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx index ca8396875b..4cc61fd1a4 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/Components/Row.jsx @@ -82,7 +82,7 @@ const Row = ({ label, value, level = 1, absolutePath }) => { }} className="json-viewer-action-icon" > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss index b44967c39d..4121cc0e4f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/CustomJSONViewer/styles.scss @@ -50,7 +50,6 @@ .json-viewer-actions-container { display:none; margin-left: auto; - margin-right: 4px; height: 12px; width:40px; align-items: center; diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx index 6d26f2676b..c71b8ad5c3 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/DefaultCopyIcon.jsx @@ -1,11 +1,11 @@ import React from 'react'; -export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( +export const DefaultCopyIcon = ({ height = 12, width = 12, fill = '#6A727C' }) => ( @@ -13,7 +13,7 @@ export const DefaultCopyIcon = ({ height = 12, width = 12 }) => ( fillRule="evenodd" clipRule="evenodd" d="M19.3113 4.68871C18.5834 3.9609 17.4034 3.9609 16.6757 4.68871L15.8421 5.5223C15.4237 5.94071 14.7453 5.94071 14.3269 5.5223C13.9084 5.10389 13.9084 4.42549 14.3269 4.00707L15.1605 3.17348C16.7251 1.60884 19.2619 1.60884 20.8266 3.17348C22.3911 4.73811 22.3911 7.2749 20.8266 8.83954L19.9929 9.67313C19.5746 10.0916 18.8961 10.0916 18.4777 9.67313C18.0593 9.25471 18.0593 8.57633 18.4777 8.1579L19.3113 7.32431C20.0391 6.59651 20.0391 5.41651 19.3113 4.68871ZM17.406 6.59394C17.8244 7.01236 17.8244 7.69074 17.406 8.10917L15.1982 10.317C14.7798 10.7354 14.1014 10.7354 13.683 10.317C13.2645 9.89856 13.2645 9.22017 13.683 8.80174L15.8908 6.59394C16.3091 6.17551 16.9876 6.17551 17.406 6.59394ZM12.6651 7.184C13.0835 7.60241 13.0835 8.28081 12.6651 8.69923L11.8315 9.53283C11.1037 10.2606 11.1037 11.4406 11.8315 12.1684C12.5593 12.8962 13.7393 12.8962 14.4671 12.1684L15.3007 11.3348C15.7191 10.9164 16.3974 10.9164 16.8159 11.3348C17.2343 11.7533 17.2343 12.4316 16.8159 12.8501L15.9823 13.6837C14.4177 15.2483 11.8809 15.2483 10.3162 13.6837C8.75161 12.119 8.75161 9.58223 10.3162 8.0176L11.1498 7.184C11.5683 6.76559 12.2467 6.76559 12.6651 7.184ZM17.245 14.9463C14.983 17.2083 11.3156 17.2083 9.05356 14.9463C6.79156 12.6843 6.79156 9.01691 9.05356 6.7549L9.52276 6.28571H4.14286C2.95939 6.28571 2 7.2451 2 8.42857V19.8571C2 21.0406 2.95939 22 4.14286 22H15.5714C16.7549 22 17.7143 21.0406 17.7143 19.8571V14.4771L17.245 14.9463Z" - fill={'#C1C8CD'} + fill={'fill'} /> ); diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx index a2a4cebf80..6c5f1daad0 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/HiddenOptions.jsx @@ -54,7 +54,7 @@ export const HiddenOptions = (props) => { dispatchAction(data); }} > - +
@@ -89,7 +89,7 @@ export const HiddenOptions = (props) => { }} className="option" > - + Copy path
{ }} className="option" > - + Copy value
@@ -120,7 +120,7 @@ export const HiddenOptions = (props) => { ...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }), }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx index 86dffbf180..bd7143ee9d 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/Node.jsx @@ -100,6 +100,8 @@ export const Node = (props) => { alignItems: 'center', color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)', fontWeight: level === 1 ? 500 : 400, + marginTop: level === 1 ? 4 : 0, + marginBottom: level === 1 ? 4 : 0, // borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none', }} > @@ -136,12 +138,13 @@ export const Node = (props) => { />
-
+
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx index 6d6951279e..bb5c6c486f 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/TreeViewHeader.jsx @@ -55,7 +55,7 @@ export const TreeViewHeader = (props) => { }} className="option" > - + Copy path
{ }} className="option" > - + Copy value
{nodeSpecificActions?.map((actionOption, index) => { - const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption; + const { name, icon, src, iconName, dispatchAction, width = 16, height = 16 } = actionOption; if (icon) { return (
@@ -85,7 +85,7 @@ export const TreeViewHeader = (props) => { }} className="option" > - + {name}
@@ -141,7 +141,7 @@ export const TreeViewHeader = (props) => { boxShadow: 'none', }} > - +
diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js index 3b256afc2a..1c4685b585 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebarInspector/useCallbackActions.js @@ -67,7 +67,7 @@ const useCallbackActions = () => { name: 'Run Query', dispatchAction: handleRunQuery, icon: true, - src: 'assets/images/icons/editor/play.svg', + iconName: 'play01', width: 8, height: 8, enableInspectorTreeView: false, @@ -76,7 +76,7 @@ const useCallbackActions = () => { name: 'View query', dispatchAction: selectQuery, icon: true, - src: 'assets/images/icons/editor/file-code.svg', + iconName: 'file-code', width: 14, height: 14, enableInspectorTreeView: true, @@ -99,7 +99,7 @@ const useCallbackActions = () => { name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, - iconName: 'select', + iconName: 'corners', enableInspectorTreeView: true, }, ...(!shouldFreeze diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 03d5f363a9..2009a3097b 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -18938,7 +18938,7 @@ section.ai-message-prompt-input-wrapper { .node-expansion-icon { width:20px; height:20px; - margin-right: 2px; + margin-right: 4px; display: flex; align-items: center; justify-content: center; @@ -18977,7 +18977,6 @@ section.ai-message-prompt-input-wrapper { .node-content { display: flex; align-items: center; - padding: 0px 0px 0px 8px; height: 100%; width: 100%; @@ -19090,7 +19089,6 @@ section.ai-message-prompt-input-wrapper { height: 20px; width: 20px; border-radius: 4px; - margin-right:4px; flex-shrink: 0; &:hover { @@ -19118,7 +19116,7 @@ section.ai-message-prompt-input-wrapper { .copy-menu-options{ width: 144px; border: none; - background: transparent; + background-color: var(--background-surface-layer-01); border-radius: 10px; top: -5px !important; &.dark-theme { 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/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/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 5daa4b7c91..8b643b5b65 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -234,6 +234,8 @@ import NewTabSmall from './NewTabSmall.jsx'; import Code from './Code.jsx'; import WorkflowV3 from './WorkflowV3.jsx'; import WorkspaceV3 from './WorkspaceV3.jsx'; +import FileCode from './FileCode.jsx'; +import Corners from './Corners.jsx'; const Icon = (props) => { switch (props.name) { @@ -369,6 +371,8 @@ const Icon = (props) => { return ; case 'expand': return ; + case 'file-code': + return ; case 'file01': return ; case 'filedownload': @@ -531,6 +535,8 @@ const Icon = (props) => { return ; case 'comments': return ; + case 'corners': + return ; case 'share': return ; case 'shield': From 20dacb0d0d0c57a8b5b76fe7a93727ef3fe3efeb Mon Sep 17 00:00:00 2001 From: Vijaykant Yadav Date: Mon, 12 May 2025 13:30:39 +0530 Subject: [PATCH 27/84] fix: import bugs --- frontend/src/HomePage/HomePage.jsx | 44 ++++- server/src/dto/import-resources.dto.ts | 6 +- server/src/helpers/error_type.constant.ts | 2 + .../page-permissions.repository.ts | 12 +- .../services/app-import-export.service.ts | 155 +++++++++++++++--- .../import-export-resources/service.ts | 12 ++ 6 files changed, 201 insertions(+), 30 deletions(-) diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 0e04b4b2a3..0e40e0078c 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -116,6 +116,8 @@ class HomePageComponent extends React.Component { shouldAutoImportPlugin: false, dependentPlugins: [], dependentPluginsDetail: {}, + showMissingGroupsModal: false, + missingGroups: [], }; } @@ -356,17 +358,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 +397,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); @@ -888,6 +901,8 @@ class HomePageComponent extends React.Component { showGroupMigrationBanner, dependentPlugins, dependentPluginsDetail, + showMissingGroupsModal, + missingGroups, } = this.state; const modalConfigs = { create: { @@ -953,6 +968,29 @@ class HomePageComponent extends React.Component { configs={modalConfigs} onCommitChange={this.handleCommitChange} /> + this.importFile(fileContent, fileName, true)} + show={showMissingGroupsModal} + isLoading={importingApp} + handleClose={() => this.setState({ showMissingGroupsModal: false })} + confirmBtnProps={{ + title: 'Import', + tooltipMessage: '', + }} + darkMode={this.props.darkMode} + > +

+ The following group permissions are missing for page permissions. Are you sure you want to continue? +

+
+ {missingGroups.map((item, index) => ( +
+ {`${index + 1}. ${item}`} +
+ ))} +
+
{showRenameAppModal && ( this.setState({ showRenameAppModal: true })} 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/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-permissions/repositories/page-permissions.repository.ts b/server/src/modules/app-permissions/repositories/page-permissions.repository.ts index a36be08bc7..7caa5f4318 100644 --- a/server/src/modules/app-permissions/repositories/page-permissions.repository.ts +++ b/server/src/modules/app-permissions/repositories/page-permissions.repository.ts @@ -19,10 +19,14 @@ export class PagePermissionsRepository extends Repository { }); return pagePermissions.map((permission) => { - return { - ...permission, - users: permission.users, - }; + if (permission.type === PAGE_PERMISSION_TYPE.GROUP) { + return { + ...permission, + groups: permission.users, + users: undefined, + }; + } + return permission; }); }, manager || this.manager); } 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 6cd6b3ce8f..5b1d09f4c8 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'; interface AppResourceMappings { defaultDataSourceIdMapping: Record; dataQueryMapping: Record; @@ -51,7 +56,17 @@ type DefaultDataSourceName = | 'tooljetdbdefault' | 'workflowsdefault'; -type NewRevampedComponent = 'Text' | 'TextInput' | 'PasswordInput' | 'NumberInput' | 'Table' | 'Button' | 'Checkbox' | 'Divider' | 'VerticalDivider' | 'Link'; +type NewRevampedComponent = + | 'Text' + | 'TextInput' + | 'PasswordInput' + | 'NumberInput' + | 'Table' + | 'Button' + | 'Checkbox' + | 'Divider' + | 'VerticalDivider' + | 'Link'; const DefaultDataSourceNames: DefaultDataSourceName[] = [ 'restapidefault', @@ -82,7 +97,7 @@ export class AppImportExportService { protected appEnvironmentUtilService: AppEnvironmentUtilService, protected readonly entityManager: EntityManager, protected componentsService: ComponentsService - ) { } + ) {} async export(user: User, id: string, searchParams: any = {}): Promise<{ appV2: App }> { // https://github.com/typeorm/typeorm/issues/3857 @@ -174,23 +189,41 @@ 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 - .createQueryBuilder(Component, 'components') - .leftJoinAndSelect('components.layouts', 'layouts') - .where('components.pageId IN(:...pageId)', { - pageId: pages.map((v) => v.id), - }) - .orderBy('components.created_at', 'ASC') - .getMany() + .createQueryBuilder(Component, 'components') + .leftJoinAndSelect('components.layouts', 'layouts') + .where('components.pageId IN(:...pageId)', { + pageId: pages.map((v) => v.id), + }) + .orderBy('components.created_at', 'ASC') + .getMany() : []; const events = await manager @@ -202,7 +235,7 @@ export class AppImportExportService { .getMany(); appToExport['components'] = components; - appToExport['pages'] = pages; + appToExport['pages'] = pagesWithPermissionGroups; appToExport['events'] = events; appToExport['dataQueries'] = dataQueries; appToExport['dataSources'] = dataSources; @@ -800,6 +833,10 @@ export class AppImportExportService { }); } + if (page.permissions) { + pageCreated.permissions = page.permissions; + } + appResourceMappings.pagesMapping[page.id] = pageCreated.id; isHomePage = importingAppVersion.homePageId === page.id; @@ -808,6 +845,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 = {}; @@ -924,6 +964,7 @@ export class AppImportExportService { }); } } + // relink page groups const updateArr = []; for (const { pageId, groupId } of pageGroupIdArr) { @@ -1059,10 +1100,10 @@ export class AppImportExportService { const options = importingDataSource.kind === 'tooljetdb' ? this.replaceTooljetDbTableIds( - importingQuery.options, - externalResourceMappings['tooljet_database'], - organizationId - ) + importingQuery.options, + externalResourceMappings['tooljet_database'], + organizationId + ) : importingQuery.options; const newQuery = manager.create(DataQuery, { @@ -1315,6 +1356,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, @@ -1627,10 +1738,10 @@ export class AppImportExportService { options: dataSourceId == defaultDataSourceIds['tooljetdb'] ? this.replaceTooljetDbTableIds( - query.options, - externalResourceMappings['tooljet_database'], - user.organizationId - ) + query.options, + externalResourceMappings['tooljet_database'], + user.organizationId + ) : query.options, }); await manager.save(newQuery); 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); From d52959572b8772d0b9007831159b90bc6e97ae30 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 9 May 2025 18:19:07 +0530 Subject: [PATCH 28/84] Revert "Added edition check in migrations." This reverts commit 4025d2305044ef1fa4f1910aad6ce14f99bd7d3c. --- server/migrations/1744610362161-CreatePagePermissions.ts | 6 ------ server/migrations/1744611380594-CreatePageUsers.ts | 6 ------ 2 files changed, 12 deletions(-) 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', From 317f18b4fa67b4b613575ec7e1e6fd6aab129e92 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 9 May 2025 18:19:17 +0530 Subject: [PATCH 29/84] Revert "Added useful indexes to the entities for query optimization" This reverts commit 962b7dc8b14078b9c8afd54f118fa855fe915c40. --- server/src/entities/group_permissions.entity.ts | 2 -- server/src/entities/group_users.entity.ts | 3 --- server/src/entities/page_users.entity.ts | 5 +---- 3 files changed, 1 insertion(+), 9 deletions(-) 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; From e41ce8a5ab66766dd3db246cb4c9288f5b1881ee Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 9 May 2025 18:21:45 +0530 Subject: [PATCH 30/84] Remove repositories from module imports --- server/src/modules/apps/module.ts | 10 +--------- server/src/modules/workflows/module.ts | 1 - 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/server/src/modules/apps/module.ts b/server/src/modules/apps/module.ts index 15b5903fb2..5da2bd5992 100644 --- a/server/src/modules/apps/module.ts +++ b/server/src/modules/apps/module.ts @@ -38,15 +38,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/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], From 6fdda6c9ef93a6cc05bb5d0b2a5a1022e2a43f68 Mon Sep 17 00:00:00 2001 From: Kavin Venkatachalam Date: Tue, 13 May 2025 21:30:42 +0530 Subject: [PATCH 31/84] Added a feature to pass a query and run query from app to modules --- .../RightSideBar/Inspector/EventManager.jsx | 47 ++++++++++++------- .../AppBuilder/_stores/slices/eventsSlice.js | 22 +++++++-- frontend/src/_styles/theme.scss | 10 +++- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx index 37cb9cb0b6..f979d0c676 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/EventManager.jsx @@ -46,11 +46,13 @@ export const EventManager = ({ customEventRefs = undefined, component, }) => { - const { moduleId } = useModuleContext(); + const { moduleId, isModuleEditor } = useModuleContext(); const components = useStore((state) => state.getCurrentPageComponents()); const pages = useStore((state) => _.get(state, 'modules.canvas.pages', []), shallow).filter( (page) => !page.disabled && !page.isPageGroup ); + const moduleInputDummyQueries = useStore((state) => state.getModuleInputDummyQueries(), shallow); + const dataQueries = useStore((state) => { const queries = state.dataQuery?.queries?.modules?.canvas || []; if (callerQueryId) { @@ -396,6 +398,12 @@ export const EventManager = ({ return defaultValue; }; + const constructDataQueryOptions = () => { + const queries = dataQueries.filter((qry) => isQueryRunnable(qry)).map((qry) => ({ name: qry.name, value: qry.id })); + const moduleInputs = Object.entries(moduleInputDummyQueries).map(([key, value]) => ({ name: value, value: key })); + return [...queries, ...moduleInputs]; + }; + function eventPopover(event, index) { return (