diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index c68b9a4c3f..2cb23190fa 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -69,6 +69,7 @@ import { useTranslation } from 'react-i18next'; import { useCurrentState } from '@/_stores/currentStateStore'; import { useAppInfo } from '@/_stores/appDataStore'; import WidgetIcon from '@/../assets/images/icons/widgets'; +import { useModuleName } from '../_contexts/ModuleContext'; const AllComponents = { Button, @@ -153,6 +154,7 @@ export const Box = memo( const { t } = useTranslation(); const backgroundColor = yellow ? 'yellow' : ''; const currentState = useCurrentState(); + const moduleName = useModuleName(); let styles = { height: '100%', diff --git a/frontend/src/Editor/Container.jsx b/frontend/src/Editor/Container.jsx index 2c0824f341..521d9851f0 100644 --- a/frontend/src/Editor/Container.jsx +++ b/frontend/src/Editor/Container.jsx @@ -1,5 +1,5 @@ /* eslint-disable import/no-named-as-default */ -import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; +import React, { useCallback, useState, useEffect, useRef, useMemo, useContext } from 'react'; import cx from 'classnames'; import { useDrop, useDragLayer } from 'react-dnd'; import { ItemTypes } from './ItemTypes'; @@ -17,8 +17,10 @@ import { addComponents, addNewWidgetToTheEditor } from '@/_helpers/appUtils'; import { useCurrentState } from '@/_stores/currentStateStore'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import { useEditorStore } from '@/_stores/editorStore'; +import { useSuperStore } from '@/_stores/superStore'; import { useAppInfo } from '@/_stores/appDataStore'; import { shallow } from 'zustand/shallow'; +import { ModuleContext } from '../_contexts/ModuleContext'; import _ from 'lodash'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; @@ -51,6 +53,8 @@ export const Container = ({ // redundant save on app definition load const firstUpdate = useRef(true); + const moduleName = useContext(ModuleContext); + const { showComments, currentLayout } = useEditorStore( (state) => ({ showComments: state?.showComments, @@ -334,7 +338,8 @@ export const Container = ({ let newBoxes = { ...boxes }; - for (const selectedComponent of useEditorStore.getState().selectedComponents) { + for (const selectedComponent of useSuperStore.getState().modules[moduleName].useEditorStore.getState() + .selectedComponents) { newBoxes = produce(newBoxes, (draft) => { if (draft[selectedComponent.id]) { const topOffset = draft[selectedComponent.id].layouts[currentLayout].top; diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 94e06d4fd5..9ecbda356e 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState, useContext } from 'react'; import { appService, authenticationService, @@ -32,6 +32,7 @@ import { buildComponentMetaDefinition, } from '@/_helpers/appUtils'; import { Confirm } from './Viewer/Confirm'; +// eslint-disable-next-line import/no-unresolved import { Tooltip as ReactTooltip } from 'react-tooltip'; import CommentNotifications from './CommentNotifications'; import { WidgetManager } from './WidgetManager'; @@ -54,7 +55,6 @@ import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError' import { useDataSourcesStore } from '@/_stores/dataSourcesStore'; import { useDataQueriesStore } from '@/_stores/dataQueriesStore'; import { useAppVersionStore, useAppVersionActions, useAppVersionState } from '@/_stores/appVersionStore'; -import { useQueryPanelStore } from '@/_stores/queryPanelStore'; import { useCurrentStateStore, useCurrentState, getCurrentState } from '@/_stores/currentStateStore'; import { computeAppDiff, computeComponentPropertyDiff, isParamFromTableColumn, resetAllStores } from '@/_stores/utils'; import { setCookie } from '@/_helpers/cookie'; @@ -70,6 +70,8 @@ import useDebouncedArrowKeyPress from '@/_hooks/useDebouncedArrowKeyPress'; import { getQueryParams } from '@/_helpers/routes'; import RightSidebarTabManager from './RightSidebarTabManager'; import { shallow } from 'zustand/shallow'; +import { ModuleContext } from '../_contexts/ModuleContext'; +import { useSuperStore } from '../_stores/superStore'; setAutoFreeze(false); enablePatches(); @@ -81,6 +83,7 @@ function setWindowTitle(name) { const decimalToHex = (alpha) => (alpha === 0 ? '00' : Math.round(255 * alpha).toString(16)); const EditorComponent = (props) => { + const moduleName = useContext(ModuleContext); const { socket } = createWebsocketConnection(props?.params?.id); const isSocketOpen = useSocketOpen(socket); const mounted = useMounted(); @@ -220,18 +223,21 @@ const EditorComponent = (props) => { updateState({ currentUser: appUserDetails, }); - useCurrentStateStore.getState().actions.setCurrentState({ - globals: { - ...currentState.globals, - theme: { name: props?.darkMode ? 'dark' : 'light' }, - urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))), - currentUser: userVars, - /* Constant value.it will only change for viewer */ - mode: { - value: 'edit', + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + globals: { + ...currentState.globals, + theme: { name: props?.darkMode ? 'dark' : 'light' }, + urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))), + currentUser: userVars, + /* Constant value.it will only change for viewer */ + mode: { + value: 'edit', + }, }, - }, - }); + }); } }); @@ -243,7 +249,7 @@ const EditorComponent = (props) => { socket && socket?.close(); subscription.unsubscribe(); if (config.ENABLE_MULTIPLAYER_EDITING) props?.provider?.disconnect(); - useEditorStore.getState().actions.setIsEditorActive(false); + useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.setIsEditorActive(false); prevAppDefinition.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -261,9 +267,9 @@ const EditorComponent = (props) => { if (mounted && didAppDefinitionChanged && currentPageId) { const components = appDefinition?.pages[currentPageId]?.components || {}; - computeComponentState(components); + computeComponentState(components, moduleName); - if (useEditorStore.getState().isUpdatingEditorStateInProcess) { + if (useSuperStore.getState().modules[moduleName].useEditorStore.getState().isUpdatingEditorStateInProcess) { autoSave(); } } @@ -273,7 +279,7 @@ const EditorComponent = (props) => { useEffect( () => { const components = appDefinition?.pages?.[currentPageId]?.components || {}; - computeComponentState(components); + computeComponentState(components, moduleName); }, // eslint-disable-next-line react-hooks/exhaustive-deps [currentPageId] @@ -299,7 +305,7 @@ const EditorComponent = (props) => { useEffect(() => { if (mounted) { - useCurrentStateStore.getState().actions.setCurrentState({ + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ layout: currentLayout, }); } @@ -328,12 +334,14 @@ const EditorComponent = (props) => { const getEditorRef = () => { const editorRef = { - appDefinition: useEditorStore.getState().appDefinition, - queryConfirmationList: useEditorStore.getState().queryConfirmationList, + appDefinition: useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition, + queryConfirmationList: useSuperStore.getState().modules[moduleName].useEditorStore.getState() + .queryConfirmationList, updateQueryConfirmationList: updateQueryConfirmationList, navigate: props.navigate, switchPage: switchPage, - currentPageId: useEditorStore.getState().currentPageId, + currentPageId: useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId, + moduleName, }; return editorRef; }; @@ -363,7 +371,7 @@ const EditorComponent = (props) => { } }); - useCurrentStateStore.getState().actions.setCurrentState({ + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ server: server_variables, client: client_variables, }); @@ -379,7 +387,7 @@ const EditorComponent = (props) => { orgConstants[constant.name] = constantValue; }); - useCurrentStateStore.getState().actions.setCurrentState({ + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ constants: orgConstants, }); }); @@ -480,18 +488,22 @@ const EditorComponent = (props) => { }; const fetchDataQueries = async (id, selectFirstQuery = false, runQueriesOnAppLoad = false) => { - await useDataQueriesStore + await useSuperStore .getState() - .actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad, getEditorRef()); + .modules[moduleName].useDataQueriesStore.getState() + .actions.fetchDataQueries(id, selectFirstQuery, runQueriesOnAppLoad, getEditorRef(), moduleName); }; const fetchDataSources = (id) => { - useDataSourcesStore.getState().actions.fetchDataSources(id); + useSuperStore.getState().modules[moduleName].useDataSourcesStore.getState().actions.fetchDataSources(id); }; const fetchGlobalDataSources = () => { const { current_organization_id: organizationId } = currentUser; - useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId); + useSuperStore + .getState() + .modules[moduleName].useDataSourcesStore.getState() + .actions.fetchGlobalDataSources(organizationId); }; const onVersionDelete = () => { @@ -562,18 +574,21 @@ const EditorComponent = (props) => { const computeCanvasContainerHeight = () => { // 45 = (height of header) // 85 = (the height of the query panel header when minimised) + (height of header) - return `calc(${100}% - ${Math.max(useQueryPanelStore.getState().queryPanelHeight + 45, 85)}px)`; + return `calc(${100}% - ${Math.max( + useSuperStore.getState().modules[moduleName].useQueryPanelStore.getState().queryPanelHeight + 45, + 85 + )}px)`; }; const handleQueryPaneDragging = (bool) => setIsQueryPaneDragging(bool); const handleQueryPaneExpanding = (bool) => setIsQueryPaneExpanded(bool); const handleOnComponentOptionChanged = (component, optionName, value) => { - return onComponentOptionChanged(component, optionName, value); + return onComponentOptionChanged(moduleName, component, optionName, value); }; const handleOnComponentOptionsChanged = (component, options) => { - return onComponentOptionsChanged(component, options); + return onComponentOptionsChanged(moduleName, component, options); }; const handleComponentClick = (id, component) => { @@ -584,21 +599,24 @@ const EditorComponent = (props) => { const sideBarDebugger = { error: (data) => { - debuggerActions.error(data); + debuggerActions.error(data, moduleName); }, flush: () => { - debuggerActions.flush(); + debuggerActions.flush(moduleName); }, - generateErrorLogs: (errors) => debuggerActions.generateErrorLogs(errors), + generateErrorLogs: (errors) => debuggerActions.generateErrorLogs(errors, moduleName), }; const changeDarkMode = (newMode) => { - useCurrentStateStore.getState().actions.setCurrentState({ - globals: { - ...currentState.globals, - theme: { name: newMode ? 'dark' : 'light' }, - }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + globals: { + ...currentState.globals, + theme: { name: newMode ? 'dark' : 'light' }, + }, + }); props.switchDarkMode(newMode); }; @@ -613,7 +631,10 @@ const EditorComponent = (props) => { }; const setSelectedComponent = (id, component, multiSelect = false) => { - const isAlreadySelected = useEditorStore.getState()?.selectedComponents.find((component) => component.id === id); + const isAlreadySelected = useSuperStore + .getState() + .modules[moduleName].useEditorStore.getState() + ?.selectedComponents.find((component) => component.id === id); if (!isAlreadySelected) { setSelectedComponents([{ id, component }], multiSelect); @@ -621,7 +642,10 @@ const EditorComponent = (props) => { }; const onVersionRelease = (versionId) => { - useAppVersionStore.getState().actions.updateReleasedVersionId(versionId); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.updateReleasedVersionId(versionId); if (socket instanceof WebSocket && socket?.readyState === WebSocket.OPEN) { socket.send( @@ -673,9 +697,15 @@ const EditorComponent = (props) => { const callBack = async (data, startingPageHandle, versionSwitched = false) => { setWindowTitle(data.name); - useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.updateEditingVersion(data.editing_version); if (!releasedVersionId || !versionSwitched) { - useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.updateReleasedVersionId(data.current_version_id); } const appVersions = await appEnvironmentService.getVersionsByEnvironment(data?.id); @@ -712,7 +742,7 @@ const EditorComponent = (props) => { setCurrentPageId(homePageId); - useCurrentStateStore.getState().actions.setCurrentState({ + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ page: currentpageData, }); @@ -730,7 +760,10 @@ const EditorComponent = (props) => { }); } - await useDataSourcesStore.getState().actions.fetchGlobalDataSources(data?.organization_id); + await useSuperStore + .getState() + .modules[moduleName].useDataSourcesStore.getState() + .actions.fetchGlobalDataSources(data?.organization_id); await fetchDataSources(data.editing_version?.id); await fetchDataQueries(data.editing_version?.id, true, true); const currentPageEvents = data.events.filter((event) => event.target === 'page' && event.sourceId === homePageId); @@ -786,11 +819,13 @@ const EditorComponent = (props) => { }); } let updatedAppDefinition; - const copyOfAppDefinition = JSON.parse(JSON.stringify(useEditorStore.getState().appDefinition)); + const copyOfAppDefinition = JSON.parse( + JSON.stringify(useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition) + ); if (opts?.skipYmapUpdate && opts?.currentSessionId !== currentSessionId) { updatedAppDefinition = produce(copyOfAppDefinition, (draft) => { - const _currentPageId = useEditorStore.getState().currentPageId; + const _currentPageId = useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId; if (opts?.componentDeleting) { const currentPageComponentIds = Object.keys(copyOfAppDefinition.pages[_currentPageId]?.components); const newComponentIds = Object.keys(newDefinition.pages[_currentPageId]?.components); @@ -928,7 +963,7 @@ const EditorComponent = (props) => { }; const saveEditingVersion = (isUserSwitchedVersion = false) => { - const editingVersion = useAppVersionStore.getState().editingVersion; + const editingVersion = useSuperStore.getState().modules[moduleName].useAppVersionStore.getState().editingVersion; if (isVersionReleased && !isUserSwitchedVersion) { updateEditorState({ isUpdatingEditorStateInProcess: false, @@ -958,7 +993,10 @@ const EditorComponent = (props) => { ...editingVersion, ...{ definition: appDefinition }, }; - useAppVersionStore.getState().actions.updateEditingVersion(_editingVersion); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.updateEditingVersion(_editingVersion); if (config.ENABLE_MULTIPLAYER_EDITING) { props.ymap?.set('appDef', { @@ -1131,7 +1169,10 @@ const EditorComponent = (props) => { const componentDefinitionChanged = (componentDefinition, props) => { if (isVersionReleased) { - useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.enableReleasedVersionPopupState(); return; } @@ -1189,7 +1230,10 @@ const EditorComponent = (props) => { componentDeleted: true, }); } else { - useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.enableReleasedVersionPopupState(); } }; @@ -1197,7 +1241,9 @@ const EditorComponent = (props) => { const gridWidth = (1 * 100) / 43; // width of the canvas grid in percentage const _appDefinition = _.cloneDeep(appDefinition); let newComponents = _appDefinition?.pages[currentPageId].components; - const selectedComponents = useEditorStore.getState()?.selectedComponents; + const selectedComponents = useSuperStore + .getState() + .modules[moduleName].useEditorStore.getState()?.selectedComponents; for (const selectedComponent of selectedComponents) { let top = newComponents[selectedComponent.id].layouts[currentLayout].top; @@ -1229,7 +1275,7 @@ const EditorComponent = (props) => { const copyComponents = () => cloneComponents( - useEditorStore.getState()?.selectedComponents, + useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents, appDefinition, currentPageId, appDefinitionChanged, @@ -1238,13 +1284,16 @@ const EditorComponent = (props) => { const cutComponents = () => { if (isVersionReleased) { - useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.enableReleasedVersionPopupState(); return; } cloneComponents( - useEditorStore.getState()?.selectedComponents, + useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents, appDefinition, currentPageId, appDefinitionChanged, @@ -1255,11 +1304,14 @@ const EditorComponent = (props) => { const cloningComponents = () => { if (isVersionReleased) { - useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.enableReleasedVersionPopupState(); return; } cloneComponents( - useEditorStore.getState()?.selectedComponents, + useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents, appDefinition, currentPageId, appDefinitionChanged, @@ -1269,7 +1321,7 @@ const EditorComponent = (props) => { }; const handleEditorEscapeKeyPress = () => { - if (useEditorStore.getState()?.selectedComponents?.length > 0) { + if (useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents?.length > 0) { updateEditorState({ selectedComponents: [], }); @@ -1277,7 +1329,9 @@ const EditorComponent = (props) => { }; const removeComponents = () => { - const selectedComponents = useEditorStore.getState()?.selectedComponents; + const selectedComponents = useSuperStore + .getState() + .modules[moduleName].useEditorStore.getState()?.selectedComponents; if (!isVersionReleased && selectedComponents?.length > 1) { let newDefinition = cloneDeep(appDefinition); @@ -1293,7 +1347,10 @@ const EditorComponent = (props) => { }); } } else if (isVersionReleased) { - useAppVersionStore.getState().actions.enableReleasedVersionPopupState(); + useSuperStore + .getState() + .modules[moduleName].useAppVersionStore.getState() + .actions.enableReleasedVersionPopupState(); } }; @@ -1376,11 +1433,14 @@ const EditorComponent = (props) => { const globals = { ...currentState.globals, }; - useCurrentStateStore.getState().actions.setCurrentState({ globals, page }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ globals, page }); }; const navigateToPage = (queryParams = [], handle) => { - const appId = useAppDataStore.getState()?.appId; + const appId = useSuperStore.getState().modules[moduleName].useAppDataStore.getState()?.appId; const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&'); props?.navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${handle}?${queryParamsString}`, { @@ -1392,9 +1452,9 @@ const EditorComponent = (props) => { const switchPage = (pageId, queryParams = []) => { // This are fetched from store to handle runQueriesOnAppLoad - const currentPageId = useEditorStore.getState().currentPageId; - const appDefinition = useEditorStore.getState().appDefinition; - const pageHandle = getCurrentState().pageHandle; + const currentPageId = useSuperStore.getState().modules[moduleName].useEditorStore.getState().currentPageId; + const appDefinition = useSuperStore.getState().modules[moduleName].useEditorStore.getState().appDefinition; + const pageHandle = getCurrentState(moduleName).pageHandle; if (currentPageId === pageId && pageHandle === appDefinition?.pages[pageId]?.handle) { return; @@ -1417,7 +1477,10 @@ const EditorComponent = (props) => { ...currentState.globals, urlparams: JSON.parse(JSON.stringify(queryString.parse(queryParamsString))), }; - useCurrentStateStore.getState().actions.setCurrentState({ globals, page }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ globals, page }); setCurrentPageId(pageId); @@ -1646,7 +1709,7 @@ const EditorComponent = (props) => { const handleCanvasContainerMouseUp = (e) => { if ( ['real-canvas', 'modal'].includes(e.target.className) && - useEditorStore.getState()?.selectedComponents?.length + useSuperStore.getState().modules[moduleName].useEditorStore.getState()?.selectedComponents?.length ) { setSelectedComponents(EMPTY_ARRAY); } diff --git a/frontend/src/Editor/EditorSelecto.jsx b/frontend/src/Editor/EditorSelecto.jsx index 0f3bd3433d..2a7d177f07 100644 --- a/frontend/src/Editor/EditorSelecto.jsx +++ b/frontend/src/Editor/EditorSelecto.jsx @@ -1,7 +1,9 @@ -import React, { useCallback, memo } from 'react'; +import React, { useCallback, memo, useContext } from 'react'; import Selecto from 'react-selecto'; import { useEditorStore, EMPTY_ARRAY } from '@/_stores/editorStore'; import { shallow } from 'zustand/shallow'; +import { useSuperStore } from '../_stores/superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; const EditorSelecto = ({ selectionRef, @@ -20,11 +22,19 @@ const EditorSelecto = ({ shallow ); + const moduleName = useContext(ModuleContext); + const onAreaSelectionStart = useCallback( (e) => { - const isMultiSelect = e.inputEvent.shiftKey || useEditorStore.getState().selectedComponents.length > 0; + const isMultiSelect = + e.inputEvent.shiftKey || + useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectedComponents.length > 0; setSelectionInProgress(true); - setSelectedComponents([...(isMultiSelect ? useEditorStore.getState().selectedComponents : EMPTY_ARRAY)]); + setSelectedComponents([ + ...(isMultiSelect + ? useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectedComponents + : EMPTY_ARRAY), + ]); }, [setSelectionInProgress, setSelectedComponents] ); @@ -33,7 +43,7 @@ const EditorSelecto = ({ e.added.forEach((el) => { el.classList.add('resizer-select'); }); - if (useEditorStore.getState().selectionInProgress) { + if (useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress) { e.removed.forEach((el) => { el.classList.remove('resizer-select'); }); @@ -68,7 +78,8 @@ const EditorSelecto = ({ (e) => { if (selectionDragRef.current) { e.stop(); - useEditorStore.getState().selectionInProgress && setSelectionInProgress(false); + useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress && + setSelectionInProgress(false); } }, [setSelectionInProgress, selectionDragRef] @@ -76,7 +87,8 @@ const EditorSelecto = ({ const onAreaSelectionDragEnd = () => { selectionDragRef.current = false; - useEditorStore.getState().selectionInProgress && setSelectionInProgress(false); + useSuperStore.getState().modules[moduleName].useEditorStore.getState().selectionInProgress && + setSelectionInProgress(false); }; return ( diff --git a/frontend/src/Editor/Inspector/Components/Table/Table.jsx b/frontend/src/Editor/Inspector/Components/Table/Table.jsx index c7ccdcafc7..29d33ecb5f 100644 --- a/frontend/src/Editor/Inspector/Components/Table/Table.jsx +++ b/frontend/src/Editor/Inspector/Components/Table/Table.jsx @@ -17,8 +17,11 @@ import List from '@/ToolJetUI/List/List'; import { capitalize, has } from 'lodash'; import NoListItem from './NoListItem'; import { ProgramaticallyHandleProperties } from './ProgramaticallyHandleProperties'; -import { useAppDataStore } from '@/_stores/appDataStore'; +import { ModuleContext } from '../../../../_contexts/ModuleContext'; +import { useSuperStore } from '@/_stores/superStore'; class TableComponent extends React.Component { + static contextType = ModuleContext; + constructor(props) { super(props); @@ -749,13 +752,19 @@ class TableComponent extends React.Component { }; deleteEvents = (ref, eventTarget) => { - const events = useAppDataStore.getState().events.filter((event) => event.target === eventTarget); + const events = useSuperStore + .getState() + .modules[this.context].useAppDataStore.getState() + .events.filter((event) => event.target === eventTarget); const toDelete = events?.filter((e) => e.event?.ref === ref.ref); return new Promise.all( toDelete?.forEach((e) => { - return useAppDataStore.getState().actions.deleteAppVersionEventHandler(e.id); + return useSuperStore + .getState() + .modules[this.context].useAppDataStore.getState() + .actions.deleteAppVersionEventHandler(e.id); }) ); }; diff --git a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js index 75f9124fa4..024b3ad6fe 100644 --- a/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js +++ b/frontend/src/Editor/LeftSidebar/SidebarDebugger/useDebugger.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useCurrentStateStore } from '@/_stores/currentStateStore'; +import { useModuleName } from '@/_contexts/ModuleContext'; import { shallow } from 'zustand/shallow'; import { debuggerActions } from '@/_helpers/appUtils'; import { flow } from 'lodash'; @@ -11,6 +12,8 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => { const [unReadErrorCount, setUnReadErrorCount] = useState({ read: 0, unread: 0 }); const [allLog, setAllLog] = useState([]); + const moduleName = useModuleName(); + const { errors, succededQuery } = useCurrentStateStore( (state) => ({ errors: state.errors, @@ -43,7 +46,7 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => { (arr) => arr.filter(([key, value]) => value.data?.status), Object.fromEntries, ])(errors); - const newErrorLogs = debuggerActions.generateErrorLogs(newError); + const newErrorLogs = debuggerActions.generateErrorLogs(newError, moduleName); const newPageLevelErrorLogs = newErrorLogs.filter((error) => error.strace === 'page_level'); const newAppLevelErrorLogs = newErrorLogs.filter((error) => error.strace === 'app_level'); if (newErrorLogs) { @@ -64,19 +67,19 @@ const useDebugger = ({ currentPageId, isDebuggerOpen }) => { }; }); } - debuggerActions.flush(); + debuggerActions.flush(moduleName); // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ errors })]); useEffect(() => { - const successQueryLogs = debuggerActions.generateQuerySuccessLogs(succededQuery); + const successQueryLogs = debuggerActions.generateQuerySuccessLogs(succededQuery, moduleName); if (successQueryLogs?.length) { setAllLog((prevLogs) => { const temp = [...successQueryLogs, ...prevLogs]; const sortedDatesDesc = temp.sort((a, b) => moment(b.timestamp).diff(moment(a.timestamp))); return sortedDatesDesc; }); - debuggerActions.flushAllLog(); + debuggerActions.flushAllLog(moduleName); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify({ succededQuery })]); diff --git a/frontend/src/Editor/ManageAppUsers.jsx b/frontend/src/Editor/ManageAppUsers.jsx index 8c0d8f32cb..129da02bde 100644 --- a/frontend/src/Editor/ManageAppUsers.jsx +++ b/frontend/src/Editor/ManageAppUsers.jsx @@ -13,9 +13,12 @@ import SolidIcon from '@/_ui/Icon/SolidIcons'; import cx from 'classnames'; import { ToolTip } from '@/_components/ToolTip'; import { TOOLTIP_MESSAGES } from '@/_helpers/constants'; -import { useAppDataStore } from '@/_stores/appDataStore'; +import { useSuperStore } from '../_stores/superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; class ManageAppUsersComponent extends React.Component { + static contextType = ModuleContext; + constructor(props) { super(props); this.isUserAdmin = authenticationService.currentSessionValue?.admin; @@ -109,7 +112,10 @@ class ManageAppUsersComponent extends React.Component { ischangingVisibility: true, }); - useAppDataStore.getState().actions.updateState({ isPublic: newState }); + useSuperStore + .getState() + .modules[this.context].useAppDataStore.getState() + .actions.updateState({ isPublic: newState }); // eslint-disable-next-line no-unused-vars appsService @@ -165,7 +171,10 @@ class ManageAppUsersComponent extends React.Component { }); replaceEditorURL(value, this.props.pageHandle); - useAppDataStore.getState().actions.updateState({ slug: value }); + useSuperStore + .getState() + .modules[this.context].useAppDataStore.getState() + .actions.updateState({ slug: value }); }) .catch(({ error }) => { this.setState({ diff --git a/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx index 92f12fac49..9e84aea0f6 100644 --- a/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/Editor/QueryManager/Components/QueryManagerHeader.jsx @@ -20,8 +20,10 @@ import { useAppVersionStore } from '@/_stores/appVersionStore'; import { shallow } from 'zustand/shallow'; import { Tooltip } from 'react-tooltip'; import { Button } from 'react-bootstrap'; +import { useModuleName } from '@/_contexts/ModuleContext'; export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef }, ref) => { + const moduleName = useModuleName(); const { renameQuery } = useDataQueriesActions(); const selectedQuery = useSelectedQuery(); const selectedDataSource = useSelectedDataSource(); @@ -51,7 +53,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef }, return false; } - const isNewQueryNameAlreadyExists = checkExistingQueryName(newName); + const isNewQueryNameAlreadyExists = checkExistingQueryName(newName, moduleName); if (isNewQueryNameAlreadyExists) { toast.error('Query name already exists'); return false; diff --git a/frontend/src/Editor/QueryPanel/QueryCard.jsx b/frontend/src/Editor/QueryPanel/QueryCard.jsx index 3e0aa45909..a68f9953d5 100644 --- a/frontend/src/Editor/QueryPanel/QueryCard.jsx +++ b/frontend/src/Editor/QueryPanel/QueryCard.jsx @@ -10,8 +10,10 @@ import { shallow } from 'zustand/shallow'; import Copy from '@/_ui/Icon/solidIcons/Copy'; import DataSourceIcon from '../QueryManager/Components/DataSourceIcon'; import { isQueryRunnable } from '@/_helpers/utils'; +import { useModuleName } from '@/_contexts/ModuleContext'; export const QueryCard = ({ dataQuery, darkMode = false, editorRef, appId }) => { + const moduleName = useModuleName(); const selectedQuery = useSelectedQuery(); const { isDeletingQueryInProcess } = useDataQueriesStore(); const { deleteDataQueries, renameQuery, duplicateQuery } = useDataQueriesActions(); @@ -42,7 +44,7 @@ export const QueryCard = ({ dataQuery, darkMode = false, editorRef, appId }) => if (name === newName) { return setRenamingQuery(false); } - const isNewQueryNameAlreadyExists = checkExistingQueryName(newName); + const isNewQueryNameAlreadyExists = checkExistingQueryName(newName, moduleName); if (newName && !isNewQueryNameAlreadyExists) { renameQuery(dataQuery?.id, newName, editorRef); setRenamingQuery(false); diff --git a/frontend/src/Editor/QueryPanel/QueryPanel.jsx b/frontend/src/Editor/QueryPanel/QueryPanel.jsx index 9f102c6f1d..000e754535 100644 --- a/frontend/src/Editor/QueryPanel/QueryPanel.jsx +++ b/frontend/src/Editor/QueryPanel/QueryPanel.jsx @@ -1,15 +1,17 @@ -import React, { useState, useRef, useCallback, useEffect } from 'react'; +import React, { useState, useRef, useCallback, useEffect, useContext } from 'react'; import { useEventListener } from '@/_hooks/use-event-listener'; import { Tooltip } from 'react-tooltip'; import { QueryDataPane } from './QueryDataPane'; import QueryManager from '../QueryManager/QueryManager'; import useWindowResize from '@/_hooks/useWindowResize'; -import { useQueryPanelStore, useQueryPanelActions } from '@/_stores/queryPanelStore'; +import { useQueryPanelActions } from '@/_stores/queryPanelStore'; import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore'; import Maximize from '@/_ui/Icon/solidIcons/Maximize'; import { cloneDeep, isEmpty, isEqual } from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import { ModuleContext } from '../../_contexts/ModuleContext'; +import { useSuperStore } from '../../_stores/superStore'; const QueryPanel = ({ dataQueriesChanged, @@ -22,6 +24,7 @@ const QueryPanel = ({ onQueryPaneDragging, handleQueryPaneExpanding, }) => { + const moduleName = useContext(ModuleContext); const { updateQueryPanelHeight } = useQueryPanelActions(); const dataQueries = useDataQueries(); const queryManagerPreferences = useRef(JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {}); @@ -37,26 +40,28 @@ const QueryPanel = ({ const [windowSize, isWindowResizing] = useWindowResize(); useEffect(() => { - const queryPanelStoreListner = useQueryPanelStore.subscribe(({ selectedQuery }, prevState) => { - if (isEmpty(prevState?.selectedQuery) || isEmpty(selectedQuery)) { - return; - } + const queryPanelStoreListner = useSuperStore + .getState() + .modules[moduleName].useQueryPanelStore.subscribe(({ selectedQuery }, prevState) => { + if (isEmpty(prevState?.selectedQuery) || isEmpty(selectedQuery)) { + return; + } - if (prevState?.selectedQuery?.id !== selectedQuery.id) { - return; - } + if (prevState?.selectedQuery?.id !== selectedQuery.id) { + return; + } - //removing updated_at since this value changes whenever the data is updated in the BE - const formattedQuery = cloneDeep(selectedQuery); - delete formattedQuery.updated_at; + //removing updated_at since this value changes whenever the data is updated in the BE + const formattedQuery = cloneDeep(selectedQuery); + delete formattedQuery.updated_at; - const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {}); - delete formattedPrevQuery.updated_at; + const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {}); + delete formattedPrevQuery.updated_at; - if (!isEqual(formattedQuery, formattedPrevQuery)) { - useDataQueriesStore.getState().actions.saveData(selectedQuery); - } - }); + if (!isEqual(formattedQuery, formattedPrevQuery)) { + useSuperStore.getState().modules[moduleName].useDataQueriesStore.getState().actions.saveData(selectedQuery); + } + }); return queryPanelStoreListner; }, []); diff --git a/frontend/src/Editor/SubContainer.jsx b/frontend/src/Editor/SubContainer.jsx index 29371ed7ab..64dbd6023e 100644 --- a/frontend/src/Editor/SubContainer.jsx +++ b/frontend/src/Editor/SubContainer.jsx @@ -1,5 +1,5 @@ /* eslint-disable import/no-named-as-default */ -import React, { useCallback, useState, useEffect, useRef } from 'react'; +import React, { useCallback, useState, useEffect, useRef, useContext } from 'react'; import { useDrop, useDragLayer } from 'react-dnd'; import { ItemTypes } from './ItemTypes'; import { DraggableBox } from './DraggableBox'; @@ -15,9 +15,10 @@ import { useCurrentState } from '@/_stores/currentStateStore'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import { shallow } from 'zustand/shallow'; import { useMounted } from '@/_hooks/use-mount'; -import { useEditorStore } from '@/_stores/editorStore'; +import { useSuperStore } from '@/_stores/superStore'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; +import { ModuleContext } from '../_contexts/ModuleContext'; const NO_OF_GRIDS = 43; @@ -62,6 +63,8 @@ export const SubContainer = ({ Listview: 'listItem', }); + const moduleName = useContext(ModuleContext); + const customResolverVariable = widgetResolvables[parentComponent?.component]; const currentState = useCurrentState(); const { enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore( @@ -402,7 +405,9 @@ export const SubContainer = ({ let newBoxes = { ...boxes }; const subContainerHeight = canvasBounds.height - 30; - const selectedComponents = useEditorStore.getState().selectedComponents; + const selectedComponents = useSuperStore + .getState() + .modules[moduleName].useEditorStore.getState().selectedComponents; if (selectedComponents) { for (const selectedComponent of selectedComponents) { diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index c54da23168..3af82ec3bf 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -38,8 +38,12 @@ import { shallow } from 'zustand/shallow'; import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore'; import { getPreviewQueryParams, redirectToErrorPage } from '@/_helpers/routes'; import { ERROR_TYPES } from '@/_helpers/constants'; +import { useSuperStore } from '../_stores/superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; class ViewerComponent extends React.Component { + static contextType = ModuleContext; + constructor(props) { super(props); @@ -69,6 +73,7 @@ class ViewerComponent extends React.Component { navigate: this.props.navigate, switchPage: this.switchPage, currentPageId: this.state.currentPageId, + moduleName: this.context, }; } @@ -158,7 +163,7 @@ class ViewerComponent extends React.Component { const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId; const currentPage = pages.find((page) => page.id === currentPageId); - useDataQueriesStore.getState().actions.setDataQueries(dataQueries); + useSuperStore.getState().modules[this.context].useDataQueriesStore.getState().actions.setDataQueries(dataQueries); this.props.setCurrentState({ queries: queryState, components: {}, @@ -180,7 +185,10 @@ class ViewerComponent extends React.Component { ...variables, ...constants, }); - useEditorStore.getState().actions.toggleCurrentLayout(mobileLayoutHasWidgets ? 'mobile' : 'desktop'); + useSuperStore + .getState() + .modules[this.context].useEditorStore.getState() + .actions.toggleCurrentLayout(mobileLayoutHasWidgets ? 'mobile' : 'desktop'); this.props.updateState({ events: data.events ?? [] }); this.setState( { @@ -201,9 +209,8 @@ class ViewerComponent extends React.Component { () => { const components = appDefData?.pages[currentPageId]?.components || {}; - computeComponentState(components).then(async () => { + computeComponentState(components, this.context).then(async () => { this.setState({ initialComputationOfStateDone: true, defaultComponentStateComputed: true }); - console.log('Default component state computed and set'); this.runQueries(dataQueries); const currentPageEvents = this.state.events.filter( @@ -237,8 +244,6 @@ class ViewerComponent extends React.Component { variablesResult = constants; } - console.log('--org constant 2.0', { variablesResult }); - if (variablesResult && Array.isArray(variablesResult)) { variablesResult.map((constant) => { const constantValue = constant.values.find((value) => value.environmentName === 'production')['value']; @@ -317,7 +322,10 @@ class ViewerComponent extends React.Component { }; updateQueryConfirmationList = (queryConfirmationList) => - useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList); + useSuperStore + .getState() + .modules[this.context].useEditorStore.getState() + .actions.updateQueryConfirmationList(queryConfirmationList); setupViewer() { this.subscription = authenticationService.currentSession.subscribe((currentSession) => { @@ -325,9 +333,11 @@ class ViewerComponent extends React.Component { const appId = this.props.id; const versionId = this.props.versionId; + console.log({ slug, appId, versionId }); + if (currentSession?.load_app && slug) { if (currentSession?.group_permissions) { - useAppDataStore.getState().actions.setAppId(appId); + useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.setAppId(appId); const currentUser = currentSession.current_user; const userVars = { @@ -376,7 +386,10 @@ class ViewerComponent extends React.Component { componentDidMount() { this.setupViewer(); const isMobileDevice = this.state.deviceWindowWidth < 600; - useEditorStore.getState().actions.toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop'); + useSuperStore + .getState() + .modules[this.context].useEditorStore.getState() + .actions.toggleCurrentLayout(isMobileDevice ? 'mobile' : 'desktop'); window.addEventListener('message', this.handleMessage); } @@ -434,7 +447,10 @@ class ViewerComponent extends React.Component { name: targetPage.name, }, async () => { - computeComponentState(this.state.appDefinition?.pages[this.state.currentPageId].components).then(async () => { + computeComponentState( + this.state.appDefinition?.pages[this.state.currentPageId].components, + this.context + ).then(async () => { const currentPageEvents = this.state.events.filter( (event) => event.target === 'page' && event.sourceId === this.state.currentPageId ); @@ -547,6 +563,8 @@ class ViewerComponent extends React.Component { canvasWidth, } = this.state; + const moduleName = this.context; + const currentCanvasWidth = canvasWidth; const queryConfirmationList = this.props?.queryConfirmationList ?? []; @@ -589,89 +607,184 @@ class ViewerComponent extends React.Component { queryConfirmationData={queryConfirmationList[0]} key={queryConfirmationList[0]?.queryName} /> - - -
-
-
-
- {appDefinition?.showViewerNavigation && ( - - )} -
-
- {defaultComponentStateComputed && ( - <> - {isLoading ? ( -
-
-
-
-
- ) : ( - false} // function not relevant in viewer - snapToGrid={true} - appLoading={isLoading} - darkMode={this.props.darkMode} - onEvent={this.handleEvent} - mode="view" - deviceWindowWidth={deviceWindowWidth} - selectedComponent={this.state.selectedComponent} - onComponentClick={(id, component) => { - this.setState({ - selectedComponent: { id, component }, - }); - onComponentClick(this, id, component, 'view'); - }} - onComponentOptionChanged={(component, optionName, value) => { - return onComponentOptionChanged(component, optionName, value); - }} - onComponentOptionsChanged={onComponentOptionsChanged} - canvasWidth={this.getCanvasWidth()} - dataQueries={dataQueries} - currentPageId={this.state.currentPageId} - /> - )} - - )} + {!this.props.moduleMode && ( + + +
+
+
+
+ {appDefinition?.showViewerNavigation && !this.props.moduleMode && ( + + )} +
+
+ {defaultComponentStateComputed && ( + <> + {isLoading ? ( +
+
+
+
+
+ ) : ( + false} // function not relevant in viewer + snapToGrid={true} + appLoading={isLoading} + darkMode={this.props.darkMode} + onEvent={this.handleEvent} + mode="view" + deviceWindowWidth={deviceWindowWidth} + selectedComponent={this.state.selectedComponent} + onComponentClick={(id, component) => { + this.setState({ + selectedComponent: { id, component }, + }); + onComponentClick(this, id, component, 'view'); + }} + onComponentOptionChanged={(component, optionName, value) => { + return onComponentOptionChanged(this.context, component, optionName, value); + }} + onComponentOptionsChanged={(...props) => + onComponentOptionsChanged(this.context, ...props) + } + canvasWidth={this.getCanvasWidth()} + dataQueries={dataQueries} + currentPageId={this.state.currentPageId} + /> + )} + + )} +
-
- + + )} + {this.props.moduleMode && ( + <> + +
+
+
+
+ {appDefinition?.showViewerNavigation && !this.props.moduleMode && ( + + )} +
+
+ {defaultComponentStateComputed && ( + <> + {isLoading ? ( +
+
+
+
+
+ ) : ( + false} // function not relevant in viewer + snapToGrid={true} + appLoading={isLoading} + darkMode={this.props.darkMode} + onEvent={this.handleEvent} + mode="view" + deviceWindowWidth={deviceWindowWidth} + selectedComponent={this.state.selectedComponent} + onComponentClick={(id, component) => { + this.setState({ + selectedComponent: { id, component }, + }); + onComponentClick(this, id, component, 'view'); + }} + onComponentOptionChanged={(component, optionName, value) => { + return onComponentOptionChanged(this.context, component, optionName, value); + }} + onComponentOptionsChanged={(...props) => + onComponentOptionsChanged(this.context, ...props) + } + canvasWidth={this.getCanvasWidth()} + dataQueries={dataQueries} + currentPageId={this.state.currentPageId} + /> + )} + + )} +
+
+
+
+
+
+ + )}
); } diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 2f91da6bc2..3878d92506 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -63,6 +63,7 @@ class HomePageComponent extends React.Component { showTemplateLibraryModal: false, app: {}, showCreateAppModal: false, + showCreateModuleModal: false, showCreateAppFromTemplateModal: false, showImportAppModal: false, showCloneAppModal: false, @@ -131,11 +132,11 @@ class HomePageComponent extends React.Component { this.fetchFolders(); }; - createApp = async (appName) => { + createApp = async (appName, type) => { let _self = this; _self.setState({ creatingApp: true }); try { - const data = await appsService.createApp({ icon: sample(iconList), name: appName }); + const data = await appsService.createApp({ icon: sample(iconList), name: appName, type }); const workspaceId = getWorkspaceId(); _self.props.navigate(`/${workspaceId}/apps/${data.id}`); toast.success('App created successfully!'); @@ -540,11 +541,11 @@ class HomePageComponent extends React.Component { }; openCreateAppModal = () => { - this.setState({ showCreateAppModal: true }); + this.setState({ showCreateAppModal: true, showCreateModuleModal: true }); }; closeCreateAppModal = () => { - this.setState({ showCreateAppModal: false }); + this.setState({ showCreateAppModal: false, showCreateModuleModal: false }); }; render() { @@ -568,6 +569,7 @@ class HomePageComponent extends React.Component { appToBeDeleted, app, showCreateAppModal, + showCreateModuleModal, showImportAppModal, fileContent, fileName, @@ -577,13 +579,13 @@ class HomePageComponent extends React.Component { return (
- {showCreateAppModal && ( + {(showCreateAppModal || showCreateModuleModal) && ( this.createApp(name, showCreateAppModal ? 'front-end' : 'module')} show={this.openCreateAppModal} - title={'Create app'} - actionButton={'+ Create app'} + title={showCreateAppModal ? 'Create app' : 'Create module'} + actionButton={showCreateAppModal ? '+ Create app' : '+ Create module'} actionLoadingButton={'Creating'} /> )} @@ -795,6 +797,13 @@ class HomePageComponent extends React.Component { data-cy="import-option-input" /> + this.setState({ showCreateModuleModal: true })} + data-cy="create-module-button" + > + {this.props.t('homePage.header.createModule', 'Create module')} +
diff --git a/frontend/src/_contexts/ModuleContext.jsx b/frontend/src/_contexts/ModuleContext.jsx new file mode 100644 index 0000000000..f14cdaa5ae --- /dev/null +++ b/frontend/src/_contexts/ModuleContext.jsx @@ -0,0 +1,11 @@ +import { createContext, useContext } from 'react'; + +export const ModuleContext = createContext(null); + +export const useModuleName = () => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('useModuleName can only be used inside a ModuleContext'); + + return moduleName; +}; diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index eb50ec5fdd..452819b919 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -35,6 +35,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore'; import { camelizeKeys } from 'humps'; import { useAppDataStore } from '@/_stores/appDataStore'; import { useEditorStore } from '@/_stores/editorStore'; +import { useSuperStore } from '@/_stores/superStore'; const ERROR_TYPES = Object.freeze({ ReferenceError: 'ReferenceError', @@ -62,9 +63,10 @@ export function setCurrentStateAsync(_ref, changes) { }); } -export function onComponentOptionsChanged(component, options) { +export function onComponentOptionsChanged(moduleName, component, options) { const componentName = component.name; - const components = getCurrentState().components; + console.log({ moduleName }); + const components = getCurrentState(moduleName).components; let componentData = components[componentName]; componentData = componentData || {}; @@ -72,27 +74,36 @@ export function onComponentOptionsChanged(component, options) { componentData[option[0]] = option[1]; } - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); return Promise.resolve(); } -export function onComponentOptionChanged(component, option_name, value) { +export function onComponentOptionChanged(moduleName, component, option_name, value) { const componentName = component.name; - const components = getCurrentState().components; + const components = getCurrentState(moduleName).components; let componentData = components[componentName]; componentData = componentData || {}; componentData[option_name] = value; if (option_name !== 'id') { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); } else if (!componentData?.id) { - useCurrentStateStore.getState().actions.setCurrentState({ - components: { ...components, [componentName]: componentData }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { ...components, [componentName]: componentData }, + }); } return Promise.resolve(); @@ -133,7 +144,7 @@ async function executeRunPycode(_ref, code, query, isPreview, mode) { const evaluatePythonCode = async (pyodide) => { let result = {}; - const currentState = getCurrentState(); + const currentState = getCurrentState(_ref.moduleName); try { const appStateVars = currentState['variables'] ?? {}; @@ -284,7 +295,7 @@ export async function runTransformation( let result = []; - const currentState = getCurrentState() || {}; + const currentState = getCurrentState(_ref.moduleName) || {}; if (transformationLanguage === 'python') { result = await runPythonTransformation(currentState, data, transformation, query, mode); @@ -364,13 +375,13 @@ function showModal(_ref, modal, show) { const modalMeta = _ref.appDefinition.pages[_ref.currentPageId].components[modalId]; //! NeedToFix const _components = { - ...getCurrentState().components, + ...getCurrentState(_ref.moduleName).components, [modalMeta.component.name]: { - ...getCurrentState().components[modalMeta.component.name], + ...getCurrentState(_ref.moduleName).components[modalMeta.component.name], show: show, }, }; - useCurrentStateStore.getState().actions.setCurrentState({ + useSuperStore.getState().modules[_ref.moduleName].useCurrentStateStore.getState().actions.setCurrentState({ components: _components, }); return Promise.resolve(); @@ -402,14 +413,19 @@ export const executeAction = debounce(executeActionWithDebounce); function executeActionWithDebounce(_ref, event, mode, customVariables) { if (event) { if (event.runOnlyIf) { - const shouldRun = resolveReferences(event.runOnlyIf, getCurrentState(), undefined, customVariables); + const shouldRun = resolveReferences( + event.runOnlyIf, + getCurrentState(_ref.moduleName), + undefined, + customVariables + ); if (!shouldRun) { return false; } } switch (event.actionId) { case 'show-alert': { - const message = resolveReferences(event.message, getCurrentState(), undefined, customVariables); + const message = resolveReferences(event.message, getCurrentState(_ref.moduleName), undefined, customVariables); switch (event.alertType) { case 'success': case 'error': @@ -433,11 +449,15 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { const resolvedParams = {}; if (params) { Object.keys(params).map( - (param) => (resolvedParams[param] = resolveReferences(params[param], getCurrentState(), undefined)) + (param) => + (resolvedParams[param] = resolveReferences(params[param], getCurrentState(_ref.moduleName), undefined)) ); } const name = - useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId)?.name ?? queryName; + useSuperStore + .getState() + .modules[_ref.moduleName].useDataQueriesStore.getState() + .dataQueries.find((query) => query.id === queryId)?.name ?? queryName; return runQuery(_ref, queryId, name, undefined, mode, resolvedParams); } case 'logout': { @@ -445,20 +465,20 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { } case 'open-webpage': { - const url = resolveReferences(event.url, getCurrentState(), undefined, customVariables); + const url = resolveReferences(event.url, getCurrentState(_ref.moduleName), undefined, customVariables); window.open(url, '_blank'); return Promise.resolve(); } case 'go-to-app': { - const slug = resolveReferences(event.slug, getCurrentState(), undefined, customVariables); + const slug = resolveReferences(event.slug, getCurrentState(_ref.moduleName), undefined, customVariables); const queryParams = event.queryParams?.reduce( (result, queryParam) => ({ ...result, ...{ - [resolveReferences(queryParam[0], getCurrentState())]: resolveReferences( + [resolveReferences(queryParam[0], getCurrentState(_ref.moduleName))]: resolveReferences( queryParam[1], - getCurrentState(), + getCurrentState(_ref.moduleName), undefined, customVariables ), @@ -492,15 +512,20 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { return showModal(_ref, event.modal, false); case 'copy-to-clipboard': { - const contentToCopy = resolveReferences(event.contentToCopy, getCurrentState(), undefined, customVariables); + const contentToCopy = resolveReferences( + event.contentToCopy, + getCurrentState(_ref.moduleName), + undefined, + customVariables + ); copyToClipboard(contentToCopy); return Promise.resolve(); } case 'set-localstorage-value': { - const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); - const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables); + const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables); + const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables); localStorage.setItem(key, value); return Promise.resolve(); @@ -508,9 +533,11 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { case 'generate-file': { // const fileType = event.fileType; - const data = resolveReferences(event.data, getCurrentState(), undefined, customVariables) ?? []; - const fileName = resolveReferences(event.fileName, getCurrentState(), undefined, customVariables) ?? 'data.txt'; - const fileType = resolveReferences(event.fileType, getCurrentState(), undefined, customVariables) ?? 'csv'; + const data = resolveReferences(event.data, getCurrentState(_ref.moduleName), undefined, customVariables) ?? []; + const fileName = + resolveReferences(event.fileName, getCurrentState(_ref.moduleName), undefined, customVariables) ?? 'data.txt'; + const fileType = + resolveReferences(event.fileType, getCurrentState(_ref.moduleName), undefined, customVariables) ?? 'csv'; const fileData = { csv: generateCSV, plaintext: (plaintext) => plaintext, @@ -521,57 +548,69 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { } case 'set-table-page': { - setTablePageIndex(event.table, event.pageIndex); + setTablePageIndex(event.table, event.pageIndex, _ref); break; } case 'set-custom-variable': { - const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); - const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables); - const customAppVariables = { ...getCurrentState().variables }; + const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables); + const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables); + const customAppVariables = { ...getCurrentState(_ref.moduleName).variables }; customAppVariables[key] = value; - return useCurrentStateStore.getState().actions.setCurrentState({ - variables: customAppVariables, - }); + return useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + variables: customAppVariables, + }); } case 'unset-custom-variable': { - const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); - const customAppVariables = { ...getCurrentState().variables }; + const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables); + const customAppVariables = { ...getCurrentState(_ref.moduleName).variables }; delete customAppVariables[key]; - return useCurrentStateStore.getState().actions.setCurrentState({ - variables: customAppVariables, - }); + return useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + variables: customAppVariables, + }); } case 'set-page-variable': { - const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); - const value = resolveReferences(event.value, getCurrentState(), undefined, customVariables); + const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables); + const value = resolveReferences(event.value, getCurrentState(_ref.moduleName), undefined, customVariables); const customPageVariables = { - ...getCurrentState().page.variables, + ...getCurrentState(_ref.moduleName).page.variables, [key]: value, }; - return useCurrentStateStore.getState().actions.setCurrentState({ - page: { - ...getCurrentState().page, - variables: customPageVariables, - }, - }); + return useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + page: { + ...getCurrentState(_ref.moduleName).page, + variables: customPageVariables, + }, + }); } case 'unset-page-variable': { - const key = resolveReferences(event.key, getCurrentState(), undefined, customVariables); - const customPageVariables = _.omit(getCurrentState().page.variables, key); - return useCurrentStateStore.getState().actions.setCurrentState({ - page: { - ...getCurrentState().page, - variables: customPageVariables, - }, - }); + const key = resolveReferences(event.key, getCurrentState(_ref.moduleName), undefined, customVariables); + const customPageVariables = _.omit(getCurrentState(_ref.moduleName).page.variables, key); + return useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + page: { + ...getCurrentState(_ref.moduleName).page, + variables: customPageVariables, + }, + }); } case 'control-component': { - let component = Object.values(getCurrentState()?.components ?? {}).filter( + let component = Object.values(getCurrentState(_ref.moduleName)?.components ?? {}).filter( (component) => component.id === event.componentId )[0]; let action = ''; @@ -579,8 +618,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { // check if component id not found then try to find if its available as child widget else continue // with normal flow finding action if (component == undefined) { - component = _ref.appDefinition.pages[getCurrentState()?.page?.id].components[event.componentId].component; - const parent = Object.values(getCurrentState()?.components ?? {}).find( + component = + _ref.appDefinition.pages[getCurrentState(_ref.moduleName)?.page?.id].components[event.componentId] + .component; + const parent = Object.values(getCurrentState(_ref.moduleName)?.components ?? {}).find( (item) => item.id === component.parent ); const child = Object.values(parent?.children).find((item) => item.id === event.componentId); @@ -593,7 +634,7 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { } actionArguments = _.map(event.componentSpecificActionParams, (param) => ({ ...param, - value: resolveReferences(param.value, getCurrentState(), undefined, customVariables), + value: resolveReferences(param.value, getCurrentState(_ref.moduleName), undefined, customVariables), })); const actionPromise = action && action(...actionArguments.map((argument) => argument.value)); return actionPromise ?? Promise.resolve(); @@ -604,7 +645,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { // Don't allow switching to disabled page in editor as well as viewer if (!disabled) { - _ref.switchPage(event.pageId, resolveReferences(event.queryParams, getCurrentState(), [], customVariables)); + _ref.switchPage( + event.pageId, + resolveReferences(event.queryParams, getCurrentState(_ref.moduleName), [], customVariables) + ); } if (_ref.appDefinition.pages[event.pageId]) { if (disabled) { @@ -618,7 +662,10 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) { }, }, }; - useCurrentStateStore.getState().actions.setErrors(generalProps); + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setErrors(generalProps); } } @@ -641,44 +688,53 @@ export async function onEvent(_ref, eventName, events, options = {}, mode = 'edi if (eventName === 'onTrigger') { const { component, queryId, queryName, parameters } = options; - useCurrentStateStore.getState().actions.setCurrentState({ - components: { - ...getCurrentState().components, - [component.name]: { - ...getCurrentState().components[component.name], + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { + ...getCurrentState(_ref.moduleName).components, + [component.name]: { + ...getCurrentState(_ref.moduleName).components[component.name], + }, }, - }, - }); + }); runQuery(_ref, queryId, queryName, true, mode, parameters); } if (eventName === 'onCalendarEventSelect') { const { component, calendarEvent } = options; - useCurrentStateStore.getState().actions.setCurrentState({ - components: { - ...getCurrentState().components, - [component.name]: { - ...getCurrentState().components[component.name], - selectedEvent: { ...calendarEvent }, + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { + ...getCurrentState(_ref.moduleName).components, + [component.name]: { + ...getCurrentState(_ref.moduleName).components[component.name], + selectedEvent: { ...calendarEvent }, + }, }, - }, - }); + }); executeActionsForEventId(_ref, 'onCalendarEventSelect', events, mode, customVariables); } if (eventName === 'onCalendarSlotSelect') { const { component, selectedSlots } = options; - useCurrentStateStore.getState().actions.setCurrentState({ - components: { - ...getCurrentState().components, - [component.name]: { - ...getCurrentState().components[component.name], - selectedSlots, + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { + ...getCurrentState(_ref.moduleName).components, + [component.name]: { + ...getCurrentState(_ref.moduleName).components[component.name], + selectedSlots, + }, }, - }, - }); + }); executeActionsForEventId(_ref, 'onCalendarSlotSelect', events, mode, customVariables); } @@ -828,9 +884,9 @@ export function getQueryVariables(options, state) { } export function previewQuery(_ref, query, calledFromQuery = false, parameters = {}, hasParamSupport = false) { - const options = getQueryVariables(query.options, getCurrentState()); + const options = getQueryVariables(query.options, getCurrentState(_ref.moduleName)); - const queryPanelState = useQueryPanelStore.getState(); + const queryPanelState = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState(); const { queryPreviewData } = queryPanelState; const { setPreviewLoading, setPreviewData } = queryPanelState.actions; @@ -862,14 +918,14 @@ export function previewQuery(_ref, query, calledFromQuery = false, parameters = hasParamSupport ); } else if (query.kind === 'tooljetdb') { - queryExecutionPromise = tooljetDbOperations.perform(query, getCurrentState()); + queryExecutionPromise = tooljetDbOperations.perform(query, getCurrentState(_ref.moduleName)); } else if (query.kind === 'runpy') { queryExecutionPromise = executeRunPycode(_ref, query.options.code, query, true, 'edit'); } else { queryExecutionPromise = dataqueryService.preview( query, options, - useAppVersionStore.getState().editingVersion?.id + useSuperStore.getState().modules[_ref.moduleName].useAppVersionStore.getState().editingVersion?.id ); } @@ -944,15 +1000,19 @@ export function previewQuery(_ref, query, calledFromQuery = false, parameters = } export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = 'edit', parameters = {}) { - const query = useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId); - const queryEvents = useAppDataStore + const query = useSuperStore .getState() + .modules[_ref.moduleName].useDataQueriesStore.getState() + .dataQueries.find((query) => query.id === queryId); + const queryEvents = useSuperStore + .getState() + .modules[_ref.moduleName].useAppDataStore.getState() .events.filter((event) => event.target === 'data_query' && event.sourceId === queryId); let dataQuery = {}; - // const { setPreviewLoading, setPreviewData } = useQueryPanelStore.getState().actions; - const queryPanelState = useQueryPanelStore.getState(); + // const { setPreviewLoading, setPreviewData } = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState().actions; + const queryPanelState = useSuperStore.getState().modules[_ref.moduleName].useQueryPanelStore.getState(); const { queryPreviewData } = queryPanelState; const { setPreviewLoading, setPreviewData } = queryPanelState.actions; if (parameters?.shouldSetPreviewData) { @@ -967,11 +1027,12 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = return; } - const options = getQueryVariables(dataQuery.options, getCurrentState()); + const options = getQueryVariables(dataQuery.options, getCurrentState(_ref.moduleName)); if (dataQuery.options?.requestConfirmation) { - const queryConfirmationList = useEditorStore.getState().queryConfirmationList - ? [...useEditorStore.getState().queryConfirmationList] + const queryConfirmationList = useSuperStore.getState().modules[_ref.moduleName].useEditorStore.getState() + .queryConfirmationList + ? [...useSuperStore.getState().modules[_ref.moduleName].useEditorStore.getState().queryConfirmationList] : []; const queryConfirmation = { @@ -993,25 +1054,28 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = // eslint-disable-next-line no-unused-vars return new Promise(function (resolve, reject) { - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...getCurrentState().queries, - [queryName]: { - ...getCurrentState().queries[queryName], - isLoading: true, - data: [], - rawData: [], + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...getCurrentState(_ref.moduleName).queries, + [queryName]: { + ...getCurrentState(_ref.moduleName).queries[queryName], + isLoading: true, + data: [], + rawData: [], + }, }, - }, - errors: {}, - }); + errors: {}, + }); let queryExecutionPromise = null; if (query.kind === 'runjs') { queryExecutionPromise = executeMultilineJS(_self, query.options.code, query?.id, false, mode, parameters); } else if (query.kind === 'runpy') { queryExecutionPromise = executeRunPycode(_self, query.options.code, query, false, mode); } else if (query.kind === 'tooljetdb') { - queryExecutionPromise = tooljetDbOperations.perform(query, getCurrentState()); + queryExecutionPromise = tooljetDbOperations.perform(query, getCurrentState(_ref.moduleName)); } else { queryExecutionPromise = dataqueryService.run(queryId, options, query?.options); } @@ -1066,33 +1130,39 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = setPreviewData(errorData); } // errorData = query.kind === 'runpy' ? data.data : data; - useCurrentStateStore.getState().actions.setErrors({ - [queryName]: { - type: 'query', - kind: query.kind, - data: errorData, - options: options, - }, - }); + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setErrors({ + [queryName]: { + type: 'query', + kind: query.kind, + data: errorData, + options: options, + }, + }); - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...getCurrentState().queries, - [queryName]: _.assign( - { - ...getCurrentState().queries[queryName], - isLoading: false, - }, - query.kind === 'restapi' - ? { - request: data.data.requestObject, - response: data.data.responseObject, - responseHeaders: data.data.responseHeaders, - } - : {} - ), - }, - }); + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...getCurrentState(_ref.moduleName).queries, + [queryName]: _.assign( + { + ...getCurrentState(_ref.moduleName).queries[queryName], + isLoading: false, + }, + query.kind === 'restapi' + ? { + request: data.data.requestObject, + response: data.data.responseObject, + responseHeaders: data.data.responseHeaders, + } + : {} + ), + }, + }); resolve(data); onEvent(_self, 'onDataQueryFailure', queryEvents); if (mode !== 'view') { @@ -1119,23 +1189,29 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = 'edit' ); if (finalData.status === 'failed') { - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...getCurrentState().queries, - [queryName]: { - ...getCurrentState().queries[queryName], - isLoading: false, + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...getCurrentState(_ref.moduleName).queries, + [queryName]: { + ...getCurrentState(_ref.moduleName).queries[queryName], + isLoading: false, + }, }, - }, - }); + }); - useCurrentStateStore.getState().actions.setErrors({ - [queryName]: { - type: 'transformations', - data: finalData, - options: options, - }, - }); + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setErrors({ + [queryName]: { + type: 'transformations', + data: finalData, + options: options, + }, + }); resolve(finalData); onEvent(_self, 'onDataQueryFailure', queryEvents); return; @@ -1148,61 +1224,69 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = duration: notificationDuration, }); } - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...getCurrentState().queries, - [queryName]: _.assign( - { - ...getCurrentState().queries[queryName], - isLoading: false, - data: finalData, - rawData, - }, - query.kind === 'restapi' - ? { - request: data.request, - response: data.response, - responseHeaders: data.responseHeaders, - } - : {} - ), - }, - // Used to generate logs - succededQuery: { - [queryName]: { - type: 'query', - kind: query.kind, + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...getCurrentState(_ref.moduleName).queries, + [queryName]: _.assign( + { + ...getCurrentState(_ref.moduleName).queries[queryName], + isLoading: false, + data: finalData, + rawData, + }, + query.kind === 'restapi' + ? { + request: data.request, + response: data.response, + responseHeaders: data.responseHeaders, + } + : {} + ), }, - }, - }); + // Used to generate logs + succededQuery: { + [queryName]: { + type: 'query', + kind: query.kind, + }, + }, + }); resolve({ status: 'ok', data: finalData }); onEvent(_self, 'onDataQuerySuccess', queryEvents, mode); } }) .catch(({ error }) => { if (mode !== 'view') toast.error(error ?? 'Unknown error'); - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...getCurrentState().queries, - [queryName]: { - isLoading: false, + useSuperStore + .getState() + .modules[_ref.moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...getCurrentState(_ref.moduleName).queries, + [queryName]: { + isLoading: false, + }, }, - }, - }); + }); resolve({ status: 'failed', message: error }); }); }); } -export function setTablePageIndex(tableId, index) { +export function setTablePageIndex(tableId, index, _ref) { if (_.isEmpty(tableId)) { console.log('No table is associated with this event.'); return Promise.resolve(); } - const table = Object.entries(getCurrentState().components).filter((entry) => entry[1].id === tableId)[0][1]; - const newPageIndex = resolveReferences(index, getCurrentState()); + const table = Object.entries(getCurrentState(_ref.moduleName).components).filter( + (entry) => entry[1].id === tableId + )[0][1]; + const newPageIndex = resolveReferences(index, getCurrentState(_ref.moduleName)); table.setPage(newPageIndex ?? 1); return Promise.resolve(); } @@ -1223,10 +1307,10 @@ for computing component state. It replaces the previous try-catch block with a more efficient approach, precomputing the parent component types and using conditional checks for better performance and error handling.*/ -export function computeComponentState(components = {}) { +export function computeComponentState(components = {}, moduleName) { try { let componentState = {}; - const currentComponents = getCurrentState().components; + const currentComponents = getCurrentState(moduleName).components; // Precompute parent component types const parentComponentTypes = {}; @@ -1263,14 +1347,17 @@ export function computeComponentState(components = {}) { } }); - useCurrentStateStore.getState().actions.setCurrentState({ - components: { - ...componentState, - }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + components: { + ...componentState, + }, + }); return new Promise((resolve) => { - useEditorStore.getState().actions.updateEditorState({ + useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.updateEditorState({ defaultComponentStateComputed: true, }); resolve(); @@ -1294,14 +1381,17 @@ export const getSvgIcon = (key, height = 50, width = 50, iconFile = undefined, s }; export const debuggerActions = { - error: (errors) => { - useCurrentStateStore.getState().actions.setErrors({ - ...errors, - }); + error: (errors, moduleName) => { + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setErrors({ + ...errors, + }); }, - flush: () => { - useCurrentStateStore.getState().actions.setCurrentState({ + flush: (moduleName) => { + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ errors: {}, }); }, @@ -1395,8 +1485,8 @@ export const debuggerActions = { }); return querySuccesslogs; }, - flushAllLog: () => { - useCurrentStateStore.getState().actions.setCurrentState({ + flushAllLog: (moduleName) => { + useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ succededQuery: {}, }); }, @@ -1437,7 +1527,8 @@ export const cloneComponents = ( currentPageId, updateAppDefinition, isCloning = true, - isCut = false + isCut = false, + moduleName ) => { if (selectedComponents.length < 1) return getSelectedText(); @@ -1504,7 +1595,7 @@ export const cloneComponents = ( } return new Promise((resolve) => { - useEditorStore.getState().actions.updateEditorState({ + useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.updateEditorState({ currentSidebarTab: 2, }); resolve(); @@ -1814,8 +1905,11 @@ function convertMapSet(obj) { } } -export const checkExistingQueryName = (newName) => - useDataQueriesStore.getState().dataQueries.some((query) => query.name === newName); +export const checkExistingQueryName = (newName, moduleName) => + useSuperStore + .getState() + .modules[moduleName].useDataQueriesStore.getState() + .dataQueries.some((query) => query.name === newName); export const runQueries = (queries, _ref) => { queries.forEach((query) => { @@ -1825,30 +1919,33 @@ export const runQueries = (queries, _ref) => { }); }; -export const computeQueryState = (queries) => { +export const computeQueryState = (queries, moduleName) => { let queryState = {}; queries.forEach((query) => { if (query.plugin?.plugin_id) { queryState[query.name] = { ...query.plugin.manifest_file.data?.source?.exposedVariables, kind: query.plugin.manifest_file.data.source.kind, - ...getCurrentState().queries[query.name], + ...getCurrentState(moduleName).queries[query.name], }; } else { queryState[query.name] = { ...DataSourceTypes.find((source) => source.kind === query.kind)?.exposedVariables, kind: DataSourceTypes.find((source) => source.kind === query.kind)?.kind, - ...getCurrentState()?.queries[query.name], + ...getCurrentState(moduleName)?.queries[query.name], }; } }); - const hasDiffQueryState = !_.isEqual(getCurrentState()?.queries, queryState); + const hasDiffQueryState = !_.isEqual(getCurrentState(moduleName)?.queries, queryState); if (hasDiffQueryState) { - useCurrentStateStore.getState().actions.setCurrentState({ - queries: { - ...queryState, - }, - }); + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + queries: { + ...queryState, + }, + }); } }; diff --git a/frontend/src/_helpers/utils.js b/frontend/src/_helpers/utils.js index 697134f3fa..4991d72747 100644 --- a/frontend/src/_helpers/utils.js +++ b/frontend/src/_helpers/utils.js @@ -6,7 +6,7 @@ import JSON5 from 'json5'; import { previewQuery, executeAction } from '@/_helpers/appUtils'; import { toast } from 'react-hot-toast'; import { authenticationService } from '@/_services/authentication.service'; - +import { useSuperStore } from '../_stores/superStore'; import { useDataQueriesStore } from '@/_stores/dataQueriesStore'; import { getCurrentState } from '@/_stores/currentStateStore'; import { getWorkspaceIdOrSlugFromURL, getSubpath, returnWorkspaceIdIfNeed } from './routes'; @@ -418,7 +418,7 @@ export async function executeMultilineJS( parameters = {}, hasParamSupport = false ) { - const currentState = getCurrentState(); + const currentState = getCurrentState(_ref.moduleName); let result = {}, error = null; @@ -429,7 +429,10 @@ export async function executeMultilineJS( const actions = generateAppActions(_ref, queryId, mode, isPreview); - const queryDetails = useDataQueriesStore.getState().dataQueries.find((q) => q.id === queryId); + const queryDetails = useSuperStore + .getState() + .modules[_ref.moduleName].useDataQueriesStore.getState() + .dataQueries.find((q) => q.id === queryId); hasParamSupport = !hasParamSupport ? queryDetails?.options?.hasParamSupport : hasParamSupport; const defaultParams = @@ -459,7 +462,10 @@ export async function executeMultilineJS( params = {}; } const processedParams = {}; - const query = useDataQueriesStore.getState().dataQueries.find((q) => q.name === key); + const query = useSuperStore + .getState() + .modules.modules[_ref.moduleName].useDataQueriesStore.getState() + .dataQueries.find((q) => q.name === key); query.options.parameters?.forEach((arg) => (processedParams[arg.name] = params[arg.name])); return actions.runQuery(key, processedParams); }, @@ -576,14 +582,17 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => { : {}; const runQuery = (queryName = '', parameters) => { - const query = useDataQueriesStore.getState().dataQueries.find((query) => { - const isFound = query.name === queryName; - if (isPreview) { - return isFound; - } else { - return isFound && isQueryRunnable(query); - } - }); + const query = useSuperStore + .getState() + .modules[_ref.moduleName].useDataQueriesStore.getState() + .dataQueries.find((query) => { + const isFound = query.name === queryName; + if (isPreview) { + return isFound; + } else { + return isFound && isQueryRunnable(query); + } + }); const processedParams = {}; if (_.isEmpty(query) || queryId === query?.id) { diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index c12f2f9036..45df44de04 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -1,132 +1,147 @@ import { appVersionService } from '@/_services'; import { create, zustandDevTools } from './utils'; import { shallow } from 'zustand/shallow'; +import { useContext } from 'react'; +import { useSuperStore } from './superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; -const initialState = { - editingVersion: null, - currentUser: null, - apps: [], - appName: null, - slug: null, - isPublic: null, - isMaintenanceOn: null, - organizationId: null, - currentVersionId: null, - userId: null, - app: {}, - components: [], - pages: [], - layouts: [], - events: [], - eventHandlers: [], - appDefinitionDiff: null, - appDiffOptions: {}, - isSaving: false, - appId: null, - areOthersOnSameVersionAndPage: false, - appVersionPreviewLink: null, -}; +export function createAppDataStore(moduleName) { + const initialState = { + editingVersion: null, + currentUser: null, + apps: [], + appName: null, + slug: null, + isPublic: null, + isMaintenanceOn: null, + organizationId: null, + currentVersionId: null, + userId: null, + app: {}, + components: [], + pages: [], + layouts: [], + events: [], + eventHandlers: [], + appDefinitionDiff: null, + appDiffOptions: {}, + isSaving: false, + appId: null, + areOthersOnSameVersionAndPage: false, + appVersionPreviewLink: null, + moduleName, + }; + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + updateEditingVersion: (version) => set(() => ({ editingVersion: version })), + updateApps: (apps) => set(() => ({ apps: apps })), + updateState: (state) => set((prev) => ({ ...prev, ...state })), + updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })), + updateAppVersion: (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => { + return new Promise((resolve, reject) => { + get().actions.setIsSaving(true); + const isComponentCutProcess = get().appDiffOptions?.componentCut === true; -export const useAppDataStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - updateEditingVersion: (version) => set(() => ({ editingVersion: version })), - updateApps: (apps) => set(() => ({ apps: apps })), - updateState: (state) => set((prev) => ({ ...prev, ...state })), - updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })), - updateAppVersion: (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => { - return new Promise((resolve, reject) => { - useAppDataStore.getState().actions.setIsSaving(true); - const isComponentCutProcess = get().appDiffOptions?.componentCut === true; + appVersionService + .autoSaveApp( + appId, + versionId, + appDefinitionDiff.updateDiff, + appDefinitionDiff.type, + pageId, + appDefinitionDiff.operation, + isUserSwitchedVersion, + isComponentCutProcess + ) + .then(() => { + get().actions.setIsSaving(false); + }) + .catch((error) => { + get().actions.setIsSaving(false); + reject(error); + }) + .finally(() => resolve()); + }); + }, + updateAppVersionEventHandlers: async (events, updateType = 'update') => { + get().actions.setIsSaving(true); + const appId = get().appId; + const versionId = get().currentVersionId; - appVersionService - .autoSaveApp( - appId, - versionId, - appDefinitionDiff.updateDiff, - appDefinitionDiff.type, - pageId, - appDefinitionDiff.operation, - isUserSwitchedVersion, - isComponentCutProcess - ) - .then(() => { - useAppDataStore.getState().actions.setIsSaving(false); - }) - .catch((error) => { - useAppDataStore.getState().actions.setIsSaving(false); - reject(error); - }) - .finally(() => resolve()); - }); - }, - updateAppVersionEventHandlers: async (events, updateType = 'update') => { - useAppDataStore.getState().actions.setIsSaving(true); - const appId = get().appId; - const versionId = get().currentVersionId; + const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType); - const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType); + get().actions.setIsSaving(false); + const updatedEvents = get().events; - useAppDataStore.getState().actions.setIsSaving(false); - const updatedEvents = get().events; - - updatedEvents.forEach((e, index) => { - const toUpdate = response.find((r) => r.id === e.id); - if (toUpdate) { - updatedEvents[index] = toUpdate; - } - }); - - set(() => ({ events: updatedEvents })); - }, - - createAppVersionEventHandlers: async (event) => { - useAppDataStore.getState().actions.setIsSaving(true); - const appId = get().appId; - const versionId = get().currentVersionId; - - const updatedEvents = get().events; - const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event); - useAppDataStore.getState().actions.setIsSaving(false); - updatedEvents.push(response); - - set(() => ({ events: updatedEvents })); - }, - - deleteAppVersionEventHandler: async (eventId) => { - useAppDataStore.getState().actions.setIsSaving(true); - const appId = get().appId; - const versionId = get().currentVersionId; - - const updatedEvents = get().events; - - const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId); - useAppDataStore.getState().actions.setIsSaving(false); - if (response?.affected === 1) { - updatedEvents.splice( - updatedEvents.findIndex((e) => e.id === eventId), - 1 - ); + updatedEvents.forEach((e, index) => { + const toUpdate = response.find((r) => r.id === e.id); + if (toUpdate) { + updatedEvents[index] = toUpdate; + } + }); set(() => ({ events: updatedEvents })); - } - }, - autoUpdateEventStore: async (versionId) => { - const appId = get().appId; - const response = await appVersionService.findAllEventsWithSourceId(appId, versionId); + }, - set(() => ({ events: response })); + createAppVersionEventHandlers: async (event) => { + get().actions.setIsSaving(true); + const appId = get().appId; + const versionId = get().currentVersionId; + + const updatedEvents = get().events; + const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event); + get().actions.setIsSaving(false); + updatedEvents.push(response); + + set(() => ({ events: updatedEvents })); + }, + + deleteAppVersionEventHandler: async (eventId) => { + get().actions.setIsSaving(true); + const appId = get().appId; + const versionId = get().currentVersionId; + + const updatedEvents = get().events; + + const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId); + get().actions.setIsSaving(false); + if (response?.affected === 1) { + updatedEvents.splice( + updatedEvents.findIndex((e) => e.id === eventId), + 1 + ); + + set(() => ({ events: updatedEvents })); + } + }, + autoUpdateEventStore: async (versionId) => { + const appId = get().appId; + const response = await appVersionService.findAllEventsWithSourceId(appId, versionId); + + set(() => ({ events: response })); + }, + setIsSaving: (isSaving) => set(() => ({ isSaving })), + setAppId: (appId) => set(() => ({ appId })), + setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })), }, - setIsSaving: (isSaving) => set(() => ({ isSaving })), - setAppId: (appId) => set(() => ({ appId })), - setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })), - }, - }), - { name: 'App Data Store' } - ) -); + }), + { name: 'App Data Store' } + ) + ); +} + +export const useAppDataStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('module context not available'); + + const _useAppDataStore = useSuperStore((state) => state.modules[moduleName].useAppDataStore); + + return _useAppDataStore(callback, shallow); +}; export const useEditingVersion = () => useAppDataStore((state) => state.editingVersion, shallow); export const useIsSaving = () => useAppDataStore((state) => state.isSaving, shallow); diff --git a/frontend/src/_stores/appVersionStore.js b/frontend/src/_stores/appVersionStore.js index 1824c28e87..d84c38c1d8 100644 --- a/frontend/src/_stores/appVersionStore.js +++ b/frontend/src/_stores/appVersionStore.js @@ -1,33 +1,49 @@ import { create, zustandDevTools } from './utils'; +import { useContext } from 'react'; +import { useSuperStore } from './superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; -const initialState = { - editingVersion: null, - isUserEditingTheVersion: false, - releasedVersionId: null, - isVersionReleased: false, - appVersions: [], +export function createAppVersionStore(moduleName) { + const initialState = { + editingVersion: null, + isUserEditingTheVersion: false, + releasedVersionId: null, + isVersionReleased: false, + appVersions: [], + moduleName, + }; + + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + updateEditingVersion: (version) => + set({ editingVersion: version, isVersionReleased: get().releasedVersionId === version?.id }), + enableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: true }), + disableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: false }), + updateReleasedVersionId: (versionId) => + set({ + releasedVersionId: versionId, + isVersionReleased: get().editingVersion?.id ? get().editingVersion?.id === versionId : false, + }), + setAppVersions: (versions) => set({ appVersions: versions }), + }, + }), + { name: 'App Version Manager Store' } + ) + ); +} + +export const useAppVersionStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('module context not available'); + + const _useAppVersionStore = useSuperStore((state) => state.modules[moduleName].useAppVersionStore); + + return _useAppVersionStore(callback, shallow); }; -export const useAppVersionStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - updateEditingVersion: (version) => - set({ editingVersion: version, isVersionReleased: get().releasedVersionId === version?.id }), - enableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: true }), - disableReleasedVersionPopupState: () => set({ isUserEditingTheVersion: false }), - updateReleasedVersionId: (versionId) => - set({ - releasedVersionId: versionId, - isVersionReleased: get().editingVersion?.id ? get().editingVersion?.id === versionId : false, - }), - setAppVersions: (versions) => set({ appVersions: versions }), - }, - }), - { name: 'App Version Manager Store' } - ) -); - export const useAppVersionActions = () => useAppVersionStore((state) => state.actions); export const useAppVersionState = () => useAppVersionStore((state) => state); diff --git a/frontend/src/_stores/currentStateStore.js b/frontend/src/_stores/currentStateStore.js index 6bcb2ff579..223890bd98 100644 --- a/frontend/src/_stores/currentStateStore.js +++ b/frontend/src/_stores/currentStateStore.js @@ -1,41 +1,57 @@ import { shallow } from 'zustand/shallow'; import { create, zustandDevTools } from './utils'; import { omit } from 'lodash'; +import { useContext } from 'react'; +import { useSuperStore } from './superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; -const initialState = { - queries: {}, - components: {}, - globals: { - theme: { name: 'light' }, - urlparams: null, - }, - errors: {}, - variables: {}, - client: {}, - server: {}, - page: { - handle: '', +export function createCurrentStateStore(moduleName) { + const initialState = { + queries: {}, + components: {}, + globals: { + theme: { name: 'light' }, + urlparams: null, + }, + errors: {}, variables: {}, - }, - succededQuery: {}, -}; + client: {}, + server: {}, + page: { + handle: '', + variables: {}, + }, + succededQuery: {}, + moduleName, + }; -export const useCurrentStateStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - setCurrentState: (currentState) => { - set({ ...currentState }, false, { type: 'SET_CURRENT_STATE', currentState }); + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + setCurrentState: (currentState) => { + set({ ...currentState }, false, { type: 'SET_CURRENT_STATE', currentState }); + }, + setErrors: (error) => { + set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); + }, }, - setErrors: (error) => { - set({ errors: { ...get().errors, ...error } }, false, { type: 'SET_ERRORS', error }); - }, - }, - }), - { name: 'Current State' } - ) -); + }), + { name: 'Current State' } + ) + ); +} + +export const useCurrentStateStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('module context not available'); + + const _useCurrentStateStore = useSuperStore((state) => state.modules[moduleName].useCurrentStateStore); + + return _useCurrentStateStore(callback, shallow); +}; export const useCurrentState = () => // Omitting 'actions' here because we don't want to expose it to user @@ -55,6 +71,6 @@ export const useCurrentState = () => }; }, shallow); -export const getCurrentState = () => { - return omit(useCurrentStateStore.getState(), 'actions'); +export const getCurrentState = (moduleName) => { + return omit(useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState(), 'actions'); }; diff --git a/frontend/src/_stores/dataQueriesStore.js b/frontend/src/_stores/dataQueriesStore.js index 3651d367f4..a401aeda78 100644 --- a/frontend/src/_stores/dataQueriesStore.js +++ b/frontend/src/_stores/dataQueriesStore.js @@ -3,353 +3,394 @@ import { getDefaultOptions } from './storeHelper'; import { dataqueryService } from '@/_services'; // import debounce from 'lodash/debounce'; import { useAppDataStore } from '@/_stores/appDataStore'; -import { useQueryPanelStore } from '@/_stores/queryPanelStore'; -import { useAppVersionStore } from '@/_stores/appVersionStore'; import { runQueries } from '@/_helpers/appUtils'; import { v4 as uuidv4 } from 'uuid'; import { toast } from 'react-hot-toast'; import _, { isEmpty, throttle } from 'lodash'; -import { useEditorStore } from './editorStore'; +import { useSuperStore } from './superStore'; import { shallow } from 'zustand/shallow'; -import { useCurrentStateStore } from './currentStateStore'; +import { useContext } from 'react'; +import { ModuleContext } from '../_contexts/ModuleContext'; -const initialState = { - dataQueries: [], - sortBy: 'updated_at', - sortOrder: 'desc', - loadingDataQueries: true, - isDeletingQueryInProcess: false, - /** TODO: Below two params are primarily used only for websocket invocation post update. Can be removed onece websocket logic is revamped */ - // isCreatingQueryInProcess: false, - creatingQueryInProcessId: null, - isUpdatingQueryInProcess: false, - /** When a 'Create Data Query' operation is in progress, rename/update API calls are cached in the variable. */ - queuedActions: {}, -}; +export function createDataQueriesStore(moduleName) { + const initialState = { + dataQueries: [], + sortBy: 'updated_at', + sortOrder: 'desc', + loadingDataQueries: true, + isDeletingQueryInProcess: false, + /** TODO: Below two params are primarily used only for websocket invocation post update. Can be removed onece websocket logic is revamped */ + // isCreatingQueryInProcess: false, + creatingQueryInProcessId: null, + isUpdatingQueryInProcess: false, + /** When a 'Create Data Query' operation is in progress, rename/update API calls are cached in the variable. */ + queuedActions: {}, + moduleName, // TODOS: change this + }; -export const useDataQueriesStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - // TODO: Remove editor state while changing currentState - fetchDataQueries: async (appVersionId, selectFirstQuery = false, runQueriesOnAppLoad = false, ref) => { - set({ loadingDataQueries: true }); - const data = await dataqueryService.getAll(appVersionId); - set((state) => ({ - dataQueries: sortByAttribute(data.data_queries, state.sortBy, state.sortOrder), - loadingDataQueries: false, - })); + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + // TODO: Remove editor state while changing currentState + fetchDataQueries: async (appVersionId, selectFirstQuery = false, runQueriesOnAppLoad = false, ref) => { + set({ loadingDataQueries: true }); + const data = await dataqueryService.getAll(appVersionId); + set((state) => ({ + dataQueries: sortByAttribute(data.data_queries, state.sortBy, state.sortOrder), + loadingDataQueries: false, + })); - if (data.data_queries.length !== 0) { - const queryConfirmationList = []; - const updatedQueries = {}; - const currentQueries = useCurrentStateStore.getState().queries; + if (data.data_queries.length !== 0) { + const queryConfirmationList = []; + const updatedQueries = {}; + const currentQueries = useSuperStore + .getState() + .modules[get().moduleName].useCurrentStateStore.getState().queries; - data.data_queries.forEach(({ id, name, options }) => { - updatedQueries[name] = _.merge(currentQueries[name], { id: id }); - if (options && options?.requestConfirmation && options?.runOnPageLoad) { - queryConfirmationList.push({ queryId: id, queryName: name }); + data.data_queries.forEach(({ id, name, options }) => { + updatedQueries[name] = _.merge(currentQueries[name], { id: id }); + if (options && options?.requestConfirmation && options?.runOnPageLoad) { + queryConfirmationList.push({ queryId: id, queryName: name }); + } + }); + + if (queryConfirmationList.length !== 0) { + useSuperStore + .getState() + .modules[get().moduleName].useEditorStore.getState() + .actions.updateQueryConfirmationList(queryConfirmationList); } - }); - if (queryConfirmationList.length !== 0) { - useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList); + useSuperStore + .getState() + .modules[get().moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + ...useSuperStore.getState().modules[get().moduleName].useCurrentStateStore.getState(), + queries: updatedQueries, + }); } - useCurrentStateStore.getState().actions.setCurrentState({ - ...useCurrentStateStore.getState(), - queries: updatedQueries, - }); - } + // Compute query state to be added in the current state + const { actions, selectedQuery } = useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState(); + if (selectFirstQuery) { + actions.setSelectedQuery(data.data_queries[0]?.id, data.data_queries[0]); + } else if (selectedQuery?.id) { + const query = data.data_queries.find((query) => query.id === selectedQuery?.id); + actions.setSelectedQuery(query?.id); + } - // Compute query state to be added in the current state - const { actions, selectedQuery } = useQueryPanelStore.getState(); - if (selectFirstQuery) { - actions.setSelectedQuery(data.data_queries[0]?.id, data.data_queries[0]); - } else if (selectedQuery?.id) { - const query = data.data_queries.find((query) => query.id === selectedQuery?.id); - actions.setSelectedQuery(query?.id); - } - - // Runs query on loading application - if (runQueriesOnAppLoad) runQueries(data.data_queries, ref); - }, - setDataQueries: (dataQueries) => set({ dataQueries }), - deleteDataQueries: (queryId) => { - set({ isDeletingQueryInProcess: true }); - useAppDataStore.getState().actions.setIsSaving(true); - dataqueryService - .del(queryId) - .then(() => { - const { actions } = useQueryPanelStore.getState(); - const { dataQueries } = useDataQueriesStore.getState(); - const newSelectedQuery = dataQueries.find((query) => query.id !== queryId); - actions.setSelectedQuery(newSelectedQuery?.id || null); - if (!newSelectedQuery?.id) { - actions.setSelectedDataSource(null); - } - set((state) => ({ - isDeletingQueryInProcess: false, - dataQueries: state.dataQueries.filter((query) => query.id !== queryId), - })); - }) - .catch(() => { - set({ - isDeletingQueryInProcess: false, - }); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); - }, - updateDataQuery: (options) => { - set({ isUpdatingQueryInProcess: true }); - const { actions, selectedQuery } = useQueryPanelStore.getState(); - set((state) => ({ - isUpdatingQueryInProcess: false, - dataQueries: state.dataQueries.map((query) => { - if (query.id === selectedQuery.id) { - return { - ...query, - options: { ...options }, - }; - } - return query; - }), - })); - actions.setSelectedQuery(selectedQuery.id); - }, - // createDataQuery: (appId, appVersionId, options, kind, name, selectedDataSource, shouldRunQuery) => { - createDataQuery: (selectedDataSource, shouldRunQuery) => { - const appVersionId = useAppVersionStore.getState().editingVersion?.id; - const appId = useAppDataStore.getState().appId; - const { options, name } = getDefaultOptions(selectedDataSource); - const kind = selectedDataSource.kind; - const tempId = uuidv4(); - set({ creatingQueryInProcessId: tempId }); - const { actions, selectedQuery } = useQueryPanelStore.getState(); - const dataSourceId = selectedDataSource?.id !== 'null' ? selectedDataSource?.id : null; - const pluginId = selectedDataSource.pluginId || selectedDataSource.plugin_id; - useAppDataStore.getState().actions.setIsSaving(true); - const { dataQueries } = useDataQueriesStore.getState(); - const currDataQueries = [...dataQueries]; - set(() => ({ - dataQueries: [ - { - ...selectedQuery, - data_source_id: dataSourceId, - app_version_id: appVersionId, - options, - name, - kind, - id: tempId, - plugin: selectedDataSource.plugin, - }, - ...currDataQueries, - ], - })); - actions.setSelectedQuery(tempId); - actions.setNameInputFocussed(true); - dataqueryService - .create(appId, appVersionId, name, kind, options, dataSourceId, pluginId) - .then((data) => { - set((state) => ({ - creatingQueryInProcessId: null, - dataQueries: state.dataQueries.map((query) => { - if (query.id === tempId) { - return { - ...query, - id: data.id, - data_source_id: dataSourceId, - }; - } - return query; - }), - })); - actions.setSelectedQuery(data.id, data); - if (shouldRunQuery) actions.setQueryToBeRun(data); - - /** Checks if there is an API call cached. If yes execute it */ - if (!isEmpty(get()?.queuedActions?.renameQuery)) { - get().actions.renameQuery(data.id, get().queuedActions.renameQuery); - set({ queuedActions: { ...get().queuedActions, renameQuery: undefined } }); - } - - if (!isEmpty(get()?.queuedActions?.saveData)) { - get().actions.saveData({ ...get().queuedActions.saveData, id: data.id }); - set({ queuedActions: { ...get().queuedActions, saveData: undefined } }); - } - }) - .catch((error) => { - set((state) => ({ - creatingQueryInProcessId: null, - dataQueries: state.dataQueries.filter((query) => query.id !== tempId), - })); - actions.setSelectedQuery(null); - toast.error(`Failed to create query: ${error.message}`); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); - }, - renameQuery: (id, newName) => { - /** If query creation in progress, skips call and pushes the update to queue */ - if (get().creatingQueryInProcessId === id) { - set({ queuedActions: { ...get().queuedActions, renameQuery: newName } }); - return; - } - useAppDataStore.getState().actions.setIsSaving(true); - /** - * Seting name to store before api call for instant UI update and better UX. - * Name is again set to state post api call to handle if renaming fails in backend. - * */ - set((state) => ({ - dataQueries: state.dataQueries.map((query) => (query.id === id ? { ...query, name: newName } : query)), - })); - dataqueryService - .update(id, newName) - .then((data) => { - set((state) => ({ - dataQueries: state.dataQueries.map((query) => { - if (query.id === id) { - return { ...query, name: newName, updated_at: data.updated_at }; - } - return query; - }), - })); - useQueryPanelStore.getState().actions.setSelectedQuery(id); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); - }, - changeDataQuery: (newDataSource) => { - const { selectedQuery } = useQueryPanelStore.getState(); - set({ - isUpdatingQueryInProcess: true, - }); - useAppDataStore.getState().actions.setIsSaving(true); - dataqueryService - .changeQueryDataSource(selectedQuery?.id, newDataSource.id) - .then(() => { - set((state) => ({ - isUpdatingQueryInProcess: false, - dataQueries: state.dataQueries.map((query) => { - if (query?.id === selectedQuery?.id) { - return { ...query, dataSourceId: newDataSource?.id, data_source_id: newDataSource?.id }; - } - return query; - }), - })); - useQueryPanelStore.getState().actions.setSelectedQuery(selectedQuery.id); - useQueryPanelStore.getState().actions.setSelectedDataSource(newDataSource); - }) - .catch(() => { - set({ - isUpdatingQueryInProcess: false, - }); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); - }, - duplicateQuery: (id, appId) => { - set({ creatingQueryInProcessId: uuidv4() }); - const { actions } = useQueryPanelStore.getState(); - const { dataQueries } = useDataQueriesStore.getState(); - const queryToClone = { ...dataQueries.find((query) => query.id === id) }; - let newName = queryToClone.name + '_copy'; - const names = dataQueries.map(({ name }) => name); - let count = 0; - while (names.includes(newName)) { - count++; - newName = queryToClone.name + '_copy' + count.toString(); - } - queryToClone.name = newName; - - useAppDataStore.getState().actions.setIsSaving(true); - dataqueryService - .create( - appId, - queryToClone.app_version_id, - queryToClone.name, - queryToClone.kind, - queryToClone.options, - queryToClone.data_source_id, - queryToClone.pluginId - ) - .then((data) => { - set((state) => ({ - creatingQueryInProcessId: null, - dataQueries: [{ ...data, data_source_id: queryToClone.data_source_id }, ...state.dataQueries], - })); - actions.setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id }); - - const dataQueryEvents = useAppDataStore - .getState() - .events?.filter((event) => event.target === 'data_query' && event.sourceId === queryToClone.id); - - if (dataQueryEvents?.length === 0) return; - - return Promise.all( - dataQueryEvents.map((event) => { - const newEvent = { - event: { - ...event?.event, - }, - eventType: event?.target, - attachedTo: data.id, - index: event?.index, - }; - useAppDataStore.getState().actions?.createAppVersionEventHandlers(newEvent); - }) + // Runs query on loading application + if (runQueriesOnAppLoad) runQueries(data.data_queries, ref); + }, + setDataQueries: (dataQueries) => set({ dataQueries }), + deleteDataQueries: (queryId) => { + set({ isDeletingQueryInProcess: true }); + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + dataqueryService + .del(queryId) + .then(() => { + const { actions } = useSuperStore.getState().modules[get().moduleName].useQueryPanelStore.getState(); + const { dataQueries } = get(); + const newSelectedQuery = dataQueries.find((query) => query.id !== queryId); + actions.setSelectedQuery(newSelectedQuery?.id || null); + if (!newSelectedQuery?.id) { + actions.setSelectedDataSource(null); + } + set((state) => ({ + isDeletingQueryInProcess: false, + dataQueries: state.dataQueries.filter((query) => query.id !== queryId), + })); + }) + .catch(() => { + set({ + isDeletingQueryInProcess: false, + }); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) ); - }) - .catch((error) => { - console.error('error', error); - set({ - creatingQueryInProcessId: null, - }); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); + }, + updateDataQuery: (options) => { + set({ isUpdatingQueryInProcess: true }); + const { actions, selectedQuery } = useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState(); + set((state) => ({ + isUpdatingQueryInProcess: false, + dataQueries: state.dataQueries.map((query) => { + if (query.id === selectedQuery.id) { + return { + ...query, + options: { ...options }, + }; + } + return query; + }), + })); + actions.setSelectedQuery(selectedQuery.id); + }, + // createDataQuery: (appId, appVersionId, options, kind, name, selectedDataSource, shouldRunQuery) => { + createDataQuery: (selectedDataSource, shouldRunQuery) => { + const appVersionId = useSuperStore.getState().modules[get().moduleName].useAppVersionStore.getState() + .editingVersion?.id; + const appId = useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().appId; + const { options, name } = getDefaultOptions(selectedDataSource, get().moduleName); + const kind = selectedDataSource.kind; + const tempId = uuidv4(); + set({ creatingQueryInProcessId: tempId }); + const { actions, selectedQuery } = useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState(); + const dataSourceId = selectedDataSource?.id !== 'null' ? selectedDataSource?.id : null; + const pluginId = selectedDataSource.pluginId || selectedDataSource.plugin_id; + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + const { dataQueries } = get(); + const currDataQueries = [...dataQueries]; + set(() => ({ + dataQueries: [ + { + ...selectedQuery, + data_source_id: dataSourceId, + app_version_id: appVersionId, + options, + name, + kind, + id: tempId, + plugin: selectedDataSource.plugin, + }, + ...currDataQueries, + ], + })); + actions.setSelectedQuery(tempId); + actions.setNameInputFocussed(true); + dataqueryService + .create(appId, appVersionId, name, kind, options, dataSourceId, pluginId) + .then((data) => { + set((state) => ({ + creatingQueryInProcessId: null, + dataQueries: state.dataQueries.map((query) => { + if (query.id === tempId) { + return { + ...query, + id: data.id, + data_source_id: dataSourceId, + }; + } + return query; + }), + })); + actions.setSelectedQuery(data.id, data); + if (shouldRunQuery) actions.setQueryToBeRun(data); + + /** Checks if there is an API call cached. If yes execute it */ + if (!isEmpty(get()?.queuedActions?.renameQuery)) { + get().actions.renameQuery(data.id, get().queuedActions.renameQuery); + set({ queuedActions: { ...get().queuedActions, renameQuery: undefined } }); + } + + if (!isEmpty(get()?.queuedActions?.saveData)) { + get().actions.saveData({ ...get().queuedActions.saveData, id: data.id }); + set({ queuedActions: { ...get().queuedActions, saveData: undefined } }); + } + }) + .catch((error) => { + set((state) => ({ + creatingQueryInProcessId: null, + dataQueries: state.dataQueries.filter((query) => query.id !== tempId), + })); + actions.setSelectedQuery(null); + toast.error(`Failed to create query: ${error.message}`); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) + ); + }, + renameQuery: (id, newName) => { + /** If query creation in progress, skips call and pushes the update to queue */ + if (get().creatingQueryInProcessId === id) { + set({ queuedActions: { ...get().queuedActions, renameQuery: newName } }); + return; + } + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + /** + * Seting name to store before api call for instant UI update and better UX. + * Name is again set to state post api call to handle if renaming fails in backend. + * */ + set((state) => ({ + dataQueries: state.dataQueries.map((query) => (query.id === id ? { ...query, name: newName } : query)), + })); + dataqueryService + .update(id, newName) + .then((data) => { + set((state) => ({ + dataQueries: state.dataQueries.map((query) => { + if (query.id === id) { + return { ...query, name: newName, updated_at: data.updated_at }; + } + return query; + }), + })); + useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState() + .actions.setSelectedQuery(id); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) + ); + }, + changeDataQuery: (newDataSource) => { + const { selectedQuery } = useSuperStore.getState().modules[get().moduleName].useQueryPanelStore.getState(); + set({ + isUpdatingQueryInProcess: true, + }); + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + dataqueryService + .changeQueryDataSource(selectedQuery?.id, newDataSource.id) + .then(() => { + set((state) => ({ + isUpdatingQueryInProcess: false, + dataQueries: state.dataQueries.map((query) => { + if (query?.id === selectedQuery?.id) { + return { ...query, dataSourceId: newDataSource?.id, data_source_id: newDataSource?.id }; + } + return query; + }), + })); + useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState() + .actions.setSelectedQuery(selectedQuery.id); + useSuperStore + .getState() + .modules[get().moduleName].useQueryPanelStore.getState() + .actions.setSelectedDataSource(newDataSource); + }) + .catch(() => { + set({ + isUpdatingQueryInProcess: false, + }); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) + ); + }, + duplicateQuery: (id, appId) => { + set({ creatingQueryInProcessId: uuidv4() }); + const { actions } = useSuperStore.getState().modules[get().moduleName].useQueryPanelStore.getState(); + const { dataQueries } = get(); + const queryToClone = { ...dataQueries.find((query) => query.id === id) }; + let newName = queryToClone.name + '_copy'; + const names = dataQueries.map(({ name }) => name); + let count = 0; + while (names.includes(newName)) { + count++; + newName = queryToClone.name + '_copy' + count.toString(); + } + queryToClone.name = newName; + + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + dataqueryService + .create( + appId, + queryToClone.app_version_id, + queryToClone.name, + queryToClone.kind, + queryToClone.options, + queryToClone.data_source_id, + queryToClone.pluginId + ) + .then((data) => { + set((state) => ({ + creatingQueryInProcessId: null, + dataQueries: [{ ...data, data_source_id: queryToClone.data_source_id }, ...state.dataQueries], + })); + actions.setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id }); + + const dataQueryEvents = useAppDataStore + .getState() + .events?.filter((event) => event.target === 'data_query' && event.sourceId === queryToClone.id); + + if (dataQueryEvents?.length === 0) return; + + return Promise.all( + dataQueryEvents.map((event) => { + const newEvent = { + event: { + ...event?.event, + }, + eventType: event?.target, + attachedTo: data.id, + index: event?.index, + }; + useSuperStore + .getState() + .modules[get().moduleName].useAppDataStore.getState() + .actions?.createAppVersionEventHandlers(newEvent); + }) + ); + }) + .catch((error) => { + console.error('error', error); + set({ + creatingQueryInProcessId: null, + }); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) + ); + }, + saveData: throttle((newValues) => { + /** If query creation in progress, skips call and pushes the update to queue */ + if (get().creatingQueryInProcessId && get().creatingQueryInProcessId === newValues.id) { + set({ queuedActions: { ...get().queuedActions, saveData: newValues } }); + return; + } + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(true); + set({ isUpdatingQueryInProcess: true }); + dataqueryService + .update(newValues?.id, newValues?.name, newValues?.options) + .then((data) => { + localStorage.removeItem('transformation'); + set((state) => ({ + dataQueries: state.dataQueries.map((query) => { + if (query.id === newValues?.id) { + return { ...query, updated_at: data.updated_at }; + } + return query; + }), + isUpdatingQueryInProcess: false, + })); + }) + .catch(() => { + set({ + isUpdatingQueryInProcess: false, + }); + }) + .finally(() => + useSuperStore.getState().modules[get().moduleName].useAppDataStore.getState().actions.setIsSaving(false) + ); + }, 500), + sortDataQueries: (sortBy, sortOrder) => { + set(({ dataQueries, sortOrder: currSortOrder }) => { + const newSortOrder = sortOrder ? sortOrder : currSortOrder === 'asc' ? 'desc' : 'asc'; + return { + sortBy, + sortOrder: newSortOrder, + dataQueries: sortByAttribute(dataQueries, sortBy, newSortOrder), + }; + }); + }, }, - saveData: throttle((newValues) => { - /** If query creation in progress, skips call and pushes the update to queue */ - if (get().creatingQueryInProcessId && get().creatingQueryInProcessId === newValues.id) { - set({ queuedActions: { ...get().queuedActions, saveData: newValues } }); - return; - } - useAppDataStore.getState().actions.setIsSaving(true); - set({ isUpdatingQueryInProcess: true }); - dataqueryService - .update(newValues?.id, newValues?.name, newValues?.options) - .then((data) => { - localStorage.removeItem('transformation'); - set((state) => ({ - dataQueries: state.dataQueries.map((query) => { - if (query.id === newValues?.id) { - return { ...query, updated_at: data.updated_at }; - } - return query; - }), - isUpdatingQueryInProcess: false, - })); - }) - .catch(() => { - set({ - isUpdatingQueryInProcess: false, - }); - }) - .finally(() => useAppDataStore.getState().actions.setIsSaving(false)); - }, 500), - sortDataQueries: (sortBy, sortOrder) => { - set(({ dataQueries, sortOrder: currSortOrder }) => { - const newSortOrder = sortOrder ? sortOrder : currSortOrder === 'asc' ? 'desc' : 'asc'; - return { - sortBy, - sortOrder: newSortOrder, - dataQueries: sortByAttribute(dataQueries, sortBy, newSortOrder), - }; - }); - }, - }, - }), - { name: 'Data Queries Store' } - ) -); + }), + { name: 'Data Queries Store' } + ) + ); +} const sortByAttribute = (data, sortBy, order) => { if (order === 'asc') { @@ -360,6 +401,19 @@ const sortByAttribute = (data, sortBy, order) => { } }; +export const useDataQueriesStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) + throw Error( + 'useDataQueriesStore can only be called inside Module context. (hint: Wrap with ModuleContext.Provider)' + ); + + const _useDataQueriesStore = useSuperStore((state) => state.modules[moduleName].useDataQueriesStore); + + return _useDataQueriesStore(callback, shallow); +}; + export const useDataQueries = () => useDataQueriesStore((state) => state.dataQueries, shallow); export const useDataQueriesActions = () => useDataQueriesStore((state) => state.actions); export const useQueryCreationLoading = () => useDataQueriesStore((state) => !!state.creatingQueryInProcessId, shallow); diff --git a/frontend/src/_stores/dataSourcesStore.js b/frontend/src/_stores/dataSourcesStore.js index 0b02b6040c..60d26686c0 100644 --- a/frontend/src/_stores/dataSourcesStore.js +++ b/frontend/src/_stores/dataSourcesStore.js @@ -1,53 +1,69 @@ import { create, zustandDevTools } from './utils'; import { datasourceService, globalDatasourceService } from '@/_services'; +import { useContext } from 'react'; +import { useSuperStore } from './superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; -const initialState = { - dataSources: [], - loadingDataSources: true, - globalDataSources: [], - globalDataSourceStatus: { - isSaving: false, - isEditing: false, - unSavedModalVisible: false, - action: null, - saveAction: null, - }, +export function createDataSourcesStore(moduleName) { + const initialState = { + dataSources: [], + loadingDataSources: true, + globalDataSources: [], + globalDataSourceStatus: { + isSaving: false, + isEditing: false, + unSavedModalVisible: false, + action: null, + saveAction: null, + }, + moduleName, + }; + + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + fetchDataSources: (appId) => { + set({ loadingDataSources: true }); + datasourceService.getAll(appId).then((data) => { + set({ + dataSources: data.data_sources, + loadingDataSources: false, + }); + }); + }, + fetchGlobalDataSources: (organizationId) => { + globalDatasourceService.getAll(organizationId).then((data) => { + set({ + globalDataSources: data.data_sources, + }); + }); + }, + setGlobalDataSourceStatus: (status) => + set({ + globalDataSourceStatus: { + ...get().globalDataSourceStatus, + ...status, + }, + }), + }, + }), + { name: 'Data Source Store' } + ) + ); +} + +export const useDataSourcesStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('module context not available'); + + const _useDataSourcesStore = useSuperStore((state) => state.modules[moduleName].useDataSourcesStore); + + return _useDataSourcesStore(callback, shallow); }; -export const useDataSourcesStore = create( - zustandDevTools( - (set, get) => ({ - ...initialState, - actions: { - fetchDataSources: (appId) => { - set({ loadingDataSources: true }); - datasourceService.getAll(appId).then((data) => { - set({ - dataSources: data.data_sources, - loadingDataSources: false, - }); - }); - }, - fetchGlobalDataSources: (organizationId) => { - globalDatasourceService.getAll(organizationId).then((data) => { - set({ - globalDataSources: data.data_sources, - }); - }); - }, - setGlobalDataSourceStatus: (status) => - set({ - globalDataSourceStatus: { - ...get().globalDataSourceStatus, - ...status, - }, - }), - }, - }), - { name: 'Data Source Store' } - ) -); - export const useDataSources = () => useDataSourcesStore((state) => state.dataSources); export const useGlobalDataSources = () => useDataSourcesStore((state) => state.globalDataSources); export const useLoadingDataSources = () => useDataSourcesStore((state) => state.loadingDataSources); diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index adb55a9eef..7c421e5a40 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -1,5 +1,8 @@ import { create } from './utils'; import { v4 as uuid } from 'uuid'; +import { useContext } from 'react'; +import { useSuperStore } from './superStore'; +import { ModuleContext } from '../_contexts/ModuleContext'; const STORE_NAME = 'Editor'; export const EMPTY_ARRAY = []; @@ -14,84 +17,97 @@ const ACTIONS = { SET_IS_EDITOR_ACTIVE: 'SET_IS_EDITOR_ACTIVE', }; -const initialState = { - currentLayout: 'desktop', - showComments: false, - hoveredComponent: '', - selectionInProgress: false, - selectedComponents: EMPTY_ARRAY, - isEditorActive: false, - selectedComponent: null, - scrollOptions: { - container: null, - throttleTime: 0, - threshold: 0, - }, - canUndo: false, - canRedo: false, - currentVersion: {}, - noOfVersionsSupported: 100, - appDefinition: {}, - isUpdatingEditorStateInProcess: false, - saveError: false, - isLoading: true, - defaultComponentStateComputed: false, - showLeftSidebar: true, - queryConfirmationList: [], - currentPageId: null, - currentSessionId: uuid(), -}; - -export const useEditorStore = create( - //Redux Dev tools for this store are disabled since its freezing chrome tab - (set, get) => ({ - ...initialState, - actions: { - setShowComments: (showComments) => - set({ showComments }, false, { - type: ACTIONS.SET_HOVERED_COMPONENT, - showComments, - }), - toggleComments: () => - set({ showComments: !get().showComments }, false, { - type: ACTIONS.TOGGLE_COMMENTS, - }), - toggleCurrentLayout: (currentLayout) => - set({ currentLayout }, false, { - type: ACTIONS.TOGGLE_CURRENT_LAYOUT, - currentLayout, - }), - setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), - updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), - updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), - setHoveredComponent: (hoveredComponent) => - set({ hoveredComponent }, false, { - type: ACTIONS.SET_HOVERED_COMPONENT, - hoveredComponent, - }), - setSelectionInProgress: (isSelectionInProgress) => { - set( - { - isSelectionInProgress, - }, - false, - { type: ACTIONS.SET_SELECTION_IN_PROGRESS } - ); - }, - setSelectedComponents: (selectedComponents, isMulti = false) => { - const newSelectedComponents = isMulti - ? [...get().selectedComponents, ...selectedComponents] - : selectedComponents; - - set({ - selectedComponents: newSelectedComponents, - }); - }, - setCurrentPageId: (currentPageId) => set({ currentPageId }), +export function createEditorStore(moduleName) { + const initialState = { + currentLayout: 'desktop', + showComments: false, + hoveredComponent: '', + selectionInProgress: false, + selectedComponents: [], + isEditorActive: false, + selectedComponent: null, + scrollOptions: { + container: null, + throttleTime: 0, + threshold: 0, }, - }), - { name: STORE_NAME } -); + canUndo: false, + canRedo: false, + currentVersion: {}, + noOfVersionsSupported: 100, + appDefinition: {}, + isUpdatingEditorStateInProcess: false, + saveError: false, + isLoading: true, + defaultComponentStateComputed: false, + showLeftSidebar: true, + queryConfirmationList: [], + currentPageId: null, + currentSessionId: uuid(), + moduleName, + }; + + return create( + //Redux Dev tools for this store are disabled since its freezing chrome tab + (set, get) => ({ + ...initialState, + actions: { + setShowComments: (showComments) => + set({ showComments }, false, { + type: ACTIONS.SET_HOVERED_COMPONENT, + showComments, + }), + toggleComments: () => + set({ showComments: !get().showComments }, false, { + type: ACTIONS.TOGGLE_COMMENTS, + }), + toggleCurrentLayout: (currentLayout) => + set({ currentLayout }, false, { + type: ACTIONS.TOGGLE_CURRENT_LAYOUT, + currentLayout, + }), + setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })), + updateEditorState: (state) => set((prev) => ({ ...prev, ...state })), + updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }), + setHoveredComponent: (hoveredComponent) => + set({ hoveredComponent }, false, { + type: ACTIONS.SET_HOVERED_COMPONENT, + hoveredComponent, + }), + setSelectionInProgress: (isSelectionInProgress) => { + set( + { + isSelectionInProgress, + }, + false, + { type: ACTIONS.SET_SELECTION_IN_PROGRESS } + ); + }, + setSelectedComponents: (selectedComponents, isMulti = false) => { + const newSelectedComponents = isMulti + ? [...get().selectedComponents, ...selectedComponents] + : selectedComponents; + + set({ + selectedComponents: newSelectedComponents, + }); + }, + setCurrentPageId: (currentPageId) => set({ currentPageId }), + }, + }), + { name: STORE_NAME } + ); +} + +export const useEditorStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) throw Error('module context not available'); + + const _useEditorStore = useSuperStore((state) => state.modules[moduleName].useEditorStore); + + return _useEditorStore(callback, shallow); +}; export const useEditorActions = () => useEditorStore((state) => state.actions); export const useEditorState = () => useEditorStore((state) => state); diff --git a/frontend/src/_stores/queryPanelStore.js b/frontend/src/_stores/queryPanelStore.js index 83187b80e5..d0b1dba7bf 100644 --- a/frontend/src/_stores/queryPanelStore.js +++ b/frontend/src/_stores/queryPanelStore.js @@ -1,45 +1,67 @@ import { create, zustandDevTools } from './utils'; +import { useContext } from 'react'; +import { ModuleContext } from '../_contexts/ModuleContext'; import { useDataQueriesStore } from '@/_stores/dataQueriesStore'; +import { useSuperStore } from '@/_stores/superStore'; -const queryManagerPreferences = JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {}; -const initialState = { - queryPanelHeight: queryManagerPreferences?.isExpanded ? queryManagerPreferences?.queryPanelHeight : 95 ?? 70, - selectedQuery: null, - selectedDataSource: null, - queryToBeRun: null, - previewLoading: false, - queryPreviewData: '', - showCreateQuery: false, - nameInputFocussed: false, -}; +export function createQueryPanelStore(moduleName) { + const queryManagerPreferences = JSON.parse(localStorage.getItem('queryManagerPreferences')) ?? {}; + const initialState = { + queryPanelHeight: queryManagerPreferences?.isExpanded ? queryManagerPreferences?.queryPanelHeight : 95 ?? 70, + selectedQuery: null, + selectedDataSource: null, + queryToBeRun: null, + previewLoading: false, + queryPreviewData: '', + showCreateQuery: false, + nameInputFocussed: false, + moduleName, + }; -export const useQueryPanelStore = create( - zustandDevTools( - (set) => ({ - ...initialState, - actions: { - updateQueryPanelHeight: (newHeight) => set(() => ({ queryPanelHeight: newHeight })), - setSelectedQuery: (queryId) => { - set(() => { - if (queryId === null) { - return { selectedQuery: null }; - } - const query = useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId); - return { selectedQuery: query }; - }); + return create( + zustandDevTools( + (set, get) => ({ + ...initialState, + actions: { + updateQueryPanelHeight: (newHeight) => set(() => ({ queryPanelHeight: newHeight })), + setSelectedQuery: (queryId) => { + set(() => { + if (queryId === null) { + return { selectedQuery: null }; + } + const query = useSuperStore + .getState() + .modules[get().moduleName].useDataQueriesStore.getState() + .dataQueries.find((query) => query.id === queryId); + return { selectedQuery: query }; + }); + }, + setSelectedDataSource: (dataSource = null) => set({ selectedDataSource: dataSource }), + setQueryToBeRun: (query) => set({ queryToBeRun: query }), + setPreviewLoading: (status) => set({ previewLoading: status }), + setPreviewData: (data) => set({ queryPreviewData: data }), + setShowCreateQuery: (showCreateQuery) => set({ showCreateQuery }), + setNameInputFocussed: (nameInputFocussed) => set({ nameInputFocussed }), }, - setSelectedDataSource: (dataSource = null) => set({ selectedDataSource: dataSource }), - setQueryToBeRun: (query) => set({ queryToBeRun: query }), - setPreviewLoading: (status) => set({ previewLoading: status }), - setPreviewData: (data) => set({ queryPreviewData: data }), - setShowCreateQuery: (showCreateQuery) => set({ showCreateQuery }), - setNameInputFocussed: (nameInputFocussed) => set({ nameInputFocussed }), - }, - }), - { name: 'Query Panel Store' } - ) -); + }), + { name: 'Query Panel Store' } + ) + ); +} + +export const useQueryPanelStore = (callback, shallow) => { + const moduleName = useContext(ModuleContext); + + if (!moduleName) + throw Error( + 'useQueryPanelStore can only be called inside Module context. (hint: Wrap with ModuleContext.Provider)' + ); + + const _useQueryPanelStore = useSuperStore((state) => state.modules[moduleName].useQueryPanelStore); + + return _useQueryPanelStore(callback, shallow); +}; export const usePanelHeight = () => useQueryPanelStore((state) => state.queryPanelHeight); export const useSelectedQuery = () => useQueryPanelStore((state) => state.selectedQuery); diff --git a/frontend/src/_stores/storeHelper.js b/frontend/src/_stores/storeHelper.js index 5d8c1e5d5a..2ab350c74d 100644 --- a/frontend/src/_stores/storeHelper.js +++ b/frontend/src/_stores/storeHelper.js @@ -1,9 +1,9 @@ import { schemaUnavailableOptions } from '@/Editor/QueryManager/constants'; import { allOperations } from '@tooljet/plugins/client'; import { capitalize } from 'lodash'; -import { useDataQueriesStore } from '@/_stores/dataQueriesStore'; +import { useSuperStore } from './superStore'; -export const getDefaultOptions = (source) => { +export const getDefaultOptions = (source, moduleName) => { const isSchemaUnavailable = Object.keys(schemaUnavailableOptions).includes(source.kind); let options = {}; @@ -36,11 +36,11 @@ export const getDefaultOptions = (source) => { } } - return { options, name: computeQueryName(source.kind) }; + return { options, name: computeQueryName(source.kind, moduleName) }; }; -const computeQueryName = (kind) => { - const dataQueries = useDataQueriesStore.getState().dataQueries; +const computeQueryName = (kind, moduleName) => { + const dataQueries = useSuperStore.getState().modules[moduleName].useDataQueriesStore.getState().dataQueries; const currentQueriesForKind = dataQueries.filter((query) => query.kind === kind); let currentNumber = currentQueriesForKind.length + 1; diff --git a/frontend/src/_stores/superStore.js b/frontend/src/_stores/superStore.js new file mode 100644 index 0000000000..f0f4e80eda --- /dev/null +++ b/frontend/src/_stores/superStore.js @@ -0,0 +1,44 @@ +import { createDataQueriesStore } from './dataQueriesStore'; +import { createEditorStore } from './editorStore'; +import { createQueryPanelStore } from './queryPanelStore'; +import { createDataSourcesStore } from './dataSourcesStore'; +import { createCurrentStateStore } from './currentStateStore'; +import { create } from './utils'; +import { createAppVersionStore } from './appVersionStore'; +import { createAppDataStore } from './appDataStore'; +import { omit } from 'lodash'; + +const generateModule = (moduleName) => ({ + useEditorStore: createEditorStore(moduleName), + useQueryPanelStore: createQueryPanelStore(moduleName), + useDataQueriesStore: createDataQueriesStore(moduleName), + useDataSourcesStore: createDataSourcesStore(moduleName), + useCurrentStateStore: createCurrentStateStore(moduleName), + useAppVersionStore: createAppVersionStore(moduleName), + useAppDataStore: createAppDataStore(moduleName), +}); + +const mainModule = generateModule('#main'); + +export const useSuperStore = create((set, get) => ({ + modules: { + '#main': mainModule, + }, + + createModule: (moduleName) => { + const modules = get().modules; + const newModules = { + ...modules, + [moduleName]: generateModule(moduleName), + }; + + set(() => ({ modules: newModules })); + return true; + }, + + destroyModule: (moduleName) => { + const modules = get().modules; + const newModules = omit(modules, moduleName); + set(() => ({ modules: newModules })); + }, +})); diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 92d995444f..48106b1fe2 100755 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -10,6 +10,7 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; +import { ModuleContext } from './_contexts/ModuleContext'; const AppWithProfiler = Sentry.withProfiler(App); @@ -62,5 +63,12 @@ appService }); } }) - .then(() => render(, document.getElementById('app'))); + .then(() => + render( + + + , + document.getElementById('app') + ) + ); // .then(() => createRoot(document.getElementById('app')).render()); diff --git a/server/src/controllers/apps.controller.v2.ts b/server/src/controllers/apps.controller.v2.ts index 45944e7210..215e8db5c2 100644 --- a/server/src/controllers/apps.controller.v2.ts +++ b/server/src/controllers/apps.controller.v2.ts @@ -92,6 +92,8 @@ export class AppsControllerV2 { response['pages'] = pagesForVersion; response['events'] = eventsForVersion; + console.log({ app }); + //! if editing version exists, camelize the definition if (app.editingVersion && app.editingVersion.definition) { response['editing_version'] = {