From 653078feb01a155a4020877d820bdbd9cc2d96bb Mon Sep 17 00:00:00 2001 From: Sherfin Shamsudeen Date: Mon, 5 Feb 2024 19:14:14 +0530 Subject: [PATCH 01/34] Superstore based state management architecture (#8678) * Add a new type of component called "Module" * Introduce superstore and load editorStore from it * Load queryPanelStore from superstore * Load useDataQueriesStore from superstore * Load datasourcesStore from superstore * Load currentStateStore from superstore * Load appversionStore from superstore * Load appDataStore from superstore * Refactor superstore * Module component and module mode in Viewer * Create module in dashboard * Remove unnecessary code --- frontend/src/Editor/Box.jsx | 2 + frontend/src/Editor/Container.jsx | 9 +- frontend/src/Editor/Editor.jsx | 197 +++-- frontend/src/Editor/EditorSelecto.jsx | 24 +- .../Inspector/Components/Table/Table.jsx | 15 +- .../SidebarDebugger/useDebugger.js | 11 +- frontend/src/Editor/ManageAppUsers.jsx | 15 +- .../Components/QueryManagerHeader.jsx | 4 +- frontend/src/Editor/QueryPanel/QueryCard.jsx | 4 +- frontend/src/Editor/QueryPanel/QueryPanel.jsx | 41 +- frontend/src/Editor/SubContainer.jsx | 11 +- frontend/src/Editor/Viewer.jsx | 289 ++++--- frontend/src/HomePage/HomePage.jsx | 25 +- frontend/src/_contexts/ModuleContext.jsx | 11 + frontend/src/_helpers/appUtils.js | 535 +++++++------ frontend/src/_helpers/utils.js | 33 +- frontend/src/_stores/appDataStore.js | 249 +++--- frontend/src/_stores/appVersionStore.js | 70 +- frontend/src/_stores/currentStateStore.js | 82 +- frontend/src/_stores/dataQueriesStore.js | 716 ++++++++++-------- frontend/src/_stores/dataSourcesStore.js | 106 +-- frontend/src/_stores/editorStore.js | 170 +++-- frontend/src/_stores/queryPanelStore.js | 94 ++- frontend/src/_stores/storeHelper.js | 10 +- frontend/src/_stores/superStore.js | 44 ++ frontend/src/index.jsx | 10 +- server/src/controllers/apps.controller.v2.ts | 2 + 27 files changed, 1672 insertions(+), 1107 deletions(-) create mode 100644 frontend/src/_contexts/ModuleContext.jsx create mode 100644 frontend/src/_stores/superStore.js 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'] = { From ca7141f289dac49e055677dd409b9123f96f8d22 Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Thu, 22 Feb 2024 13:19:10 +0530 Subject: [PATCH 02/34] fix: hide loader only after app load --- frontend/src/_components/PrivateRoute.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/_components/PrivateRoute.jsx b/frontend/src/_components/PrivateRoute.jsx index 28274635b5..1a6e719f4c 100644 --- a/frontend/src/_components/PrivateRoute.jsx +++ b/frontend/src/_components/PrivateRoute.jsx @@ -68,7 +68,9 @@ export const PrivateRoute = ({ children }) => { setUserValidationStatus(true); /* When route changes (not hard reload). will validate the access */ validateRoutes(session?.group_permissions, () => { - setUserValidationStatus(false); + if (session.load_app) { + setUserValidationStatus(false); + } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname, session]); From 9bac53905005366416081b2456a6cbccd9aa6b86 Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Thu, 22 Feb 2024 13:20:16 +0530 Subject: [PATCH 03/34] Revert "fix: hide loader only after app load" This reverts commit ca7141f289dac49e055677dd409b9123f96f8d22. --- frontend/src/_components/PrivateRoute.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/_components/PrivateRoute.jsx b/frontend/src/_components/PrivateRoute.jsx index 1a6e719f4c..28274635b5 100644 --- a/frontend/src/_components/PrivateRoute.jsx +++ b/frontend/src/_components/PrivateRoute.jsx @@ -68,9 +68,7 @@ export const PrivateRoute = ({ children }) => { setUserValidationStatus(true); /* When route changes (not hard reload). will validate the access */ validateRoutes(session?.group_permissions, () => { - if (session.load_app) { - setUserValidationStatus(false); - } + setUserValidationStatus(false); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname, session]); From 2a8d672ee03dc10625d5bf2d6c234b3c1a1e4582 Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Fri, 5 Apr 2024 17:53:05 +0530 Subject: [PATCH 04/34] fix: not able to save datasource + module option shown in create app dropdown (#9308) --- .../src/Editor/DataSourceManager/DataSourceManager.jsx | 9 ++++++--- frontend/src/HomePage/HomePage.jsx | 7 ------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx index fa506f7375..abe6b6ca38 100644 --- a/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx +++ b/frontend/src/Editor/DataSourceManager/DataSourceManager.jsx @@ -21,14 +21,17 @@ import { withTranslation, useTranslation } from 'react-i18next'; import { camelizeKeys, decamelizeKeys } from 'humps'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import SolidIcon from '@/_ui/Icon/SolidIcons'; -import { useAppVersionStore } from '@/_stores/appVersionStore'; import { ConfirmDialog } from '@/_components'; import { deepEqual } from '../../_helpers/utils'; import { shallow } from 'zustand/shallow'; import { useDataSourcesStore } from '../../_stores/dataSourcesStore'; import { withRouter } from '@/_hoc/withRouter'; +import { useSuperStore } from '@/_stores/superStore'; +import { ModuleContext } from '@/_contexts/ModuleContext'; class DataSourceManagerComponent extends React.Component { + static contextType = ModuleContext; + constructor(props) { super(props); @@ -38,7 +41,6 @@ class DataSourceManagerComponent extends React.Component { let options = {}; let dataSourceMeta = {}; let datasourceName = ''; - if (props.selectedDataSource) { selectedDataSource = props.selectedDataSource; options = selectedDataSource.options; @@ -197,7 +199,8 @@ class DataSourceManagerComponent extends React.Component { const name = selectedDataSource.name; const kind = selectedDataSource.kind; const pluginId = selectedDataSourcePluginId; - const appVersionId = useAppVersionStore?.getState()?.editingVersion?.id; + const appVersionId = useSuperStore.getState().modules[this.context].useAppVersionStore?.getState() + ?.editingVersion?.id; const currentEnvironment = this.props.currentEnvironment?.id; const scope = this.state?.scope || selectedDataSource?.scope; diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 476ee16e69..8b8d12339b 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -800,13 +800,6 @@ 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')} -
From 3c36ec75edddc0449f80e4200d94edb7d06d53ab Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Mon, 8 Apr 2024 14:52:19 +0530 Subject: [PATCH 05/34] fix: removed duplicate currentstate fetching in runpy --- frontend/src/_helpers/appUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/_helpers/appUtils.js b/frontend/src/_helpers/appUtils.js index 200df4a95a..51982d0437 100644 --- a/frontend/src/_helpers/appUtils.js +++ b/frontend/src/_helpers/appUtils.js @@ -126,7 +126,7 @@ export function getDataFromLocalStorage(key) { } const evaluatePythonCode = async (options) => { - const { _ref, query, mode, isPreview, code, queryResult } = options; + const { _ref, query, mode, currentState, isPreview, code, queryResult } = options; let pyodide; try { pyodide = await loadPyodide(); @@ -140,7 +140,7 @@ const evaluatePythonCode = async (options) => { } const log = (line) => console.log({ line }); let result = {}; - const currentState = getCurrentState(_ref.moduleName); + try { const appStateVars = currentState['variables'] ?? {}; From b798cbf69038f3d2aecc2212e1726ee1ae00f196 Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Fri, 19 Apr 2024 12:53:51 +0530 Subject: [PATCH 06/34] feat: added support for annotation in plotly charts (#9160) --- frontend/src/Editor/Components/Chart.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/Editor/Components/Chart.jsx b/frontend/src/Editor/Components/Chart.jsx index 0898fa197a..0c099ab891 100644 --- a/frontend/src/Editor/Components/Chart.jsx +++ b/frontend/src/Editor/Components/Chart.jsx @@ -121,6 +121,7 @@ export const Chart = function Chart({ b: padding, t: padding, }, + ...(chartLayout.annotations && { annotations: chartLayout.annotations }), barmode: barmode, hoverlabel: { namelength: -1 }, }; From b5974b2b8d4569110dba7e08ce606fc6cde4b512 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade <133095394+nakulnagargade@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:55:16 +0530 Subject: [PATCH 07/34] feat: Add light/dark/auto mode theme setting at application level (#9091) * Add app mode in global settings * Fix width of app mode toggle * Resolve code comments * Fix design comments * Fix when canvas bg is empty * Product feedback * Fix dark theme issue in table * Code refactor * Always enable dark mode toggle in editor * Fix canvas container color * fix: update currentstate theme on appMode change * fix: check theme as string from localstorage * Update frontend/src/Editor/Header/AppModeToggle.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * Update frontend/src/Editor/Viewer/DesktopHeader.jsx Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> * fix: check app theme when switching global theme * fix: check if appmode is empty and show toggle button in viewer * fix: set appmode on editor load from appdef --------- Co-authored-by: Johnson Cherian Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> --- frontend/assets/translations/en.json | 1 + frontend/src/App/App.jsx | 38 +++++++++--- .../AppVersionsManager/AppVersionsManager.jsx | 5 +- frontend/src/Editor/Box.jsx | 2 +- .../src/Editor/Components/Table/Filter.jsx | 4 +- .../src/Editor/Components/Table/Table.jsx | 2 +- frontend/src/Editor/Container.jsx | 2 +- frontend/src/Editor/Editor.jsx | 59 ++++++++++++------- frontend/src/Editor/Header/AppModeToggle.jsx | 56 ++++++++++++++++++ frontend/src/Editor/Header/GlobalSettings.jsx | 6 +- frontend/src/Editor/Header/HeaderActions.jsx | 2 +- frontend/src/Editor/Header/index.js | 4 +- frontend/src/Editor/LeftSidebar/index.jsx | 9 +-- frontend/src/Editor/QueryPanel/QueryPanel.jsx | 6 +- frontend/src/Editor/Viewer.jsx | 21 ++++--- frontend/src/Editor/Viewer/DesktopHeader.jsx | 24 ++++++-- frontend/src/Editor/Viewer/MobileHeader.jsx | 11 +++- .../Editor/Viewer/MobileNavigationMenu.jsx | 32 +++++----- .../src/Editor/Viewer/PreviewSettings.jsx | 4 +- frontend/src/_components/DarkModeToggle.jsx | 5 +- frontend/src/_hooks/useAppDarkMode.js | 46 +++++++++++++++ frontend/src/_stores/appDataStore.js | 2 + frontend/src/_stores/editorStore.js | 2 + frontend/src/_styles/designtheme.scss | 2 +- frontend/src/_styles/left-sidebar.scss | 18 ++++++ frontend/src/_styles/table-component.scss | 20 ++++--- frontend/src/_styles/theme.scss | 6 +- .../src/_ui/JSONTreeViewer/JSONNodeValue.jsx | 32 +++++++--- frontend/src/_ui/Select/SelectComponent.jsx | 7 ++- .../src/services/app_import_export.service.ts | 1 + server/src/services/apps.service.ts | 1 + 31 files changed, 326 insertions(+), 104 deletions(-) create mode 100644 frontend/src/Editor/Header/AppModeToggle.jsx create mode 100644 frontend/src/_hooks/useAppDarkMode.js diff --git a/frontend/assets/translations/en.json b/frontend/assets/translations/en.json index 2614646d85..9a5dc0003a 100644 --- a/frontend/assets/translations/en.json +++ b/frontend/assets/translations/en.json @@ -947,6 +947,7 @@ "maxWidthOfCanvas": "Max width of canvas", "maxHeightOfCanvas": "Max height of canvas", "backgroundColorOfCanvas": "Canvas background", + "appMode": "App mode", "exportApp": "Export app" }, "Back": { diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index 54630aa41c..39d719732d 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -31,19 +31,21 @@ import 'react-tooltip/dist/react-tooltip.css'; import { getWorkspaceIdOrSlugFromURL } from '@/_helpers/routes'; import ErrorPage from '@/_components/ErrorComponents/ErrorPage'; import WorkspaceConstants from '@/WorkspaceConstants'; -import { useAppDataStore } from '@/_stores/appDataStore'; import { useSuperStore } from '../_stores/superStore'; import { ModuleContext } from '../_contexts/ModuleContext'; +import cx from 'classnames'; +import useAppDarkMode from '@/_hooks/useAppDarkMode'; import { ManageOrgUsers } from '@/ManageOrgUsers'; import { ManageGroupPermissions } from '@/ManageGroupPermissions'; import OrganizationLogin from '@/_components/OrganizationLogin/OrganizationLogin'; import { ManageOrgVars } from '@/ManageOrgVars'; const AppWrapper = (props) => { + const { isAppDarkMode } = useAppDarkMode(); return ( - + ); @@ -58,6 +60,7 @@ class AppComponent extends React.Component { currentUser: null, fetchedMetadata: false, darkMode: localStorage.getItem('darkMode') === 'true', + isEditorOrViewer: '', }; } updateSidebarNAV = (val) => { @@ -101,19 +104,19 @@ class AppComponent extends React.Component { switchDarkMode = (newMode) => { this.setState({ darkMode: newMode }); + useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.updateIsTJDarkMode(newMode); localStorage.setItem('darkMode', newMode); }; render() { - const { updateAvailable, darkMode } = this.state; - + const { updateAvailable, darkMode, isEditorOrViewer } = this.state; let toastOptions = { style: { wordBreak: 'break-all', }, }; - if (darkMode) { + if (isEditorOrViewer === 'viewer' ? this.props.isAppDarkMode : darkMode) { toastOptions = { className: 'toast-dark-mode', style: { @@ -128,7 +131,12 @@ class AppComponent extends React.Component { const { updateSidebarNAV } = this; return ( <> -
+
{updateAvailable && (

Update available

@@ -184,7 +192,11 @@ class AppComponent extends React.Component { path="/:workspaceId/apps/:slug/:pageHandle?/*" element={ - + this.setState({ isEditorOrViewer: value })} + /> } /> @@ -202,7 +214,11 @@ class AppComponent extends React.Component { path="/applications/:slug/:pageHandle?" element={ - + this.setState({ isEditorOrViewer: value })} + /> } /> @@ -211,7 +227,11 @@ class AppComponent extends React.Component { path="/applications/:slug/versions/:versionId/:pageHandle?" element={ - + this.setState({ isEditorOrViewer: value })} + /> } /> diff --git a/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx b/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx index 6c71dc6a43..6d7af072a1 100644 --- a/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx +++ b/frontend/src/Editor/AppVersionsManager/AppVersionsManager.jsx @@ -19,6 +19,7 @@ export const AppVersionsManager = function ({ onVersionDelete, isEditable = true, isViewer, + darkMode, }) { const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading); const [deleteVersion, setDeleteVersion] = useState({ @@ -53,8 +54,6 @@ export const AppVersionsManager = function ({ }; }, [appVersions]); - const darkMode = localStorage.getItem('darkMode') === 'true'; - const selectVersion = (id) => { appVersionService .getAppVersionData(appId, id) @@ -173,8 +172,8 @@ export const AppVersionsManager = function ({ value={editingVersion?.id} onChange={(id) => selectVersion(id)} {...customSelectProps} - className={` ${darkMode && 'dark-theme'}`} isEditable={isEditable} + darkMode={darkMode} />
diff --git a/frontend/src/Editor/Box.jsx b/frontend/src/Editor/Box.jsx index 96b958dfc7..495cc962a1 100644 --- a/frontend/src/Editor/Box.jsx +++ b/frontend/src/Editor/Box.jsx @@ -159,6 +159,7 @@ export const Box = memo( isResizing, adjustHeightBasedOnAlignment, currentLayout, + darkMode, }) => { const { t } = useTranslation(); const backgroundColor = yellow ? 'yellow' : ''; @@ -206,7 +207,6 @@ export const Box = memo( ? validateProperties(resolvedGeneralStyles, componentMeta.generalStyles) : [resolvedGeneralStyles, []]; - const darkMode = localStorage.getItem('darkMode') === 'true'; const { variablesExposedForPreview, exposeToCodeHinter } = useContext(EditorContext) || {}; let styles = { diff --git a/frontend/src/Editor/Components/Table/Filter.jsx b/frontend/src/Editor/Components/Table/Filter.jsx index fd1664606f..c158d852b7 100644 --- a/frontend/src/Editor/Components/Table/Filter.jsx +++ b/frontend/src/Editor/Components/Table/Filter.jsx @@ -172,7 +172,7 @@ export function Filter(props) { } return ( -
+

@@ -207,6 +207,7 @@ export function Filter(props) { className={`${darkMode ? 'select-search-dark' : 'select-search'} mb-0`} styles={selectStyles('100%')} useCustomStyles={true} + darkMode={darkMode} />

@@ -222,6 +223,7 @@ export function Filter(props) { styles={selectStyles('100%')} dataCy={`select-coloumn-dropdown-${index ?? ''}`} useCustomStyles={true} + darkMode={darkMode} />
diff --git a/frontend/src/Editor/Components/Table/Table.jsx b/frontend/src/Editor/Components/Table/Table.jsx index 19cb66c7ef..68b16b4bec 100644 --- a/frontend/src/Editor/Components/Table/Table.jsx +++ b/frontend/src/Editor/Components/Table/Table.jsx @@ -1055,7 +1055,7 @@ export function Table({
{ // Dont update first time to skip // redundant save on app definition load diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index bd8f72fb2e..6b8eb2ac13 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -64,13 +64,14 @@ import { useMounted } from '@/_hooks/use-mount'; import EditorSelecto from './EditorSelecto'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; - +import useAppDarkMode from '@/_hooks/useAppDarkMode'; 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'; +import cx from 'classnames'; setAutoFreeze(false); enablePatches(); @@ -173,6 +174,7 @@ const EditorComponent = (props) => { const [showPageDeletionConfirmation, setShowPageDeletionConfirmation] = useState(null); const [isDeletingPage, setIsDeletingPage] = useState(false); + const { isAppDarkMode, appMode, onAppModeChange } = useAppDarkMode(); const [undoStack, setUndoStack] = useState([]); const [redoStack, setRedoStack] = useState([]); @@ -245,6 +247,7 @@ const EditorComponent = (props) => { if (config.ENABLE_MULTIPLAYER_EDITING) props?.provider?.disconnect(); useSuperStore.getState().modules[moduleName].useEditorStore.getState().actions.setIsEditorActive(false); prevAppDefinition.current = null; + props.setEditorOrViewer(''); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -457,7 +460,7 @@ const EditorComponent = (props) => { const $componentDidMount = async () => { window.addEventListener('message', handleMessage); - + props.setEditorOrViewer('editor'); await fetchApp(props.params.pageHandle, true); await fetchApps(0); await fetchOrgEnvironmentVariables(); @@ -601,15 +604,17 @@ const EditorComponent = (props) => { }; const changeDarkMode = (newMode) => { - useSuperStore - .getState() - .modules[moduleName].useCurrentStateStore.getState() - .actions.setCurrentState({ - globals: { - ...currentState.globals, - theme: { name: newMode ? 'dark' : 'light' }, - }, - }); + if (appMode === 'auto') { + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + globals: { + ...currentState.globals, + theme: { name: newMode ? 'dark' : 'light' }, + }, + }); + } props.switchDarkMode(newMode); }; @@ -651,9 +656,11 @@ const EditorComponent = (props) => { }; const computeCanvasBackgroundColor = () => { - const { canvasBackgroundColor } = appDefinition?.globalSettings ?? '#edeff5'; + const canvasBackgroundColor = appDefinition?.globalSettings?.canvasBackgroundColor + ? appDefinition?.globalSettings?.canvasBackgroundColor + : '#edeff5'; if (['#2f3c4c', '#edeff5'].includes(canvasBackgroundColor)) { - return props.darkMode ? '#2f3c4c' : '#edeff5'; + return isAppDarkMode ? '#2f3c4c' : '#edeff5'; } return canvasBackgroundColor; }; @@ -694,6 +701,7 @@ const EditorComponent = (props) => { .getState() .modules[moduleName].useAppVersionStore.getState() .actions.updateEditingVersion(data.editing_version); + if (!releasedVersionId || !versionSwitched) { useSuperStore .getState() @@ -734,6 +742,7 @@ const EditorComponent = (props) => { }; setCurrentPageId(homePageId); + onAppModeChange(appJson?.globalSettings?.appMode); useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().actions.setCurrentState({ page: currentpageData, @@ -1712,7 +1721,7 @@ const EditorComponent = (props) => { if (isLoading) { return ( -
+
@@ -1736,7 +1745,7 @@ const EditorComponent = (props) => { ); } return ( -
+
0} message={`Do you want to run this query - ${queryConfirmationList[0]?.queryName}?`} @@ -1830,14 +1839,18 @@ const EditorComponent = (props) => { id="main-editor-canvas" >
{
)} {defaultComponentStateComputed && ( - <> +
{ canvasWidth={getCanvasWidth()} onDragging={(isDragging) => setIsDragging(isDragging)} /> - +
)}
@@ -1931,7 +1944,7 @@ const EditorComponent = (props) => { />
-
+
{ />
{config.COMMENT_FEATURE_ENABLE && showComments && ( - +
+ +
)}
diff --git a/frontend/src/Editor/Header/AppModeToggle.jsx b/frontend/src/Editor/Header/AppModeToggle.jsx new file mode 100644 index 0000000000..f66a196287 --- /dev/null +++ b/frontend/src/Editor/Header/AppModeToggle.jsx @@ -0,0 +1,56 @@ +import React, { useContext } from 'react'; +import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup'; +import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem'; +import { useTranslation } from 'react-i18next'; +import useAppDarkMode from '@/_hooks/useAppDarkMode'; +import { useSuperStore } from '@/_stores/superStore'; +import { ModuleContext } from '@/_contexts/ModuleContext'; +// import { ModuleContext } from '../_contexts/ModuleContext'; + +const APP_MODES = [ + { label: 'Auto', value: 'auto' }, + { label: 'Light', value: 'light' }, + { label: 'Dark', value: 'dark' }, +]; + +const AppModeToggle = ({ globalSettingsChanged }) => { + const moduleName = useContext(ModuleContext); + const { onAppModeChange, appMode } = useAppDarkMode(); + const { t } = useTranslation(); + + return ( +
+ {t('leftSidebar.Settings.appMode', 'App mode')} +
+ { + onAppModeChange(value); + let exposedTheme = value; + if (value === 'auto') { + exposedTheme = localStorage.getItem('darkMode') === 'true' ? 'dark' : 'light'; + } + useSuperStore + .getState() + .modules[moduleName].useCurrentStateStore.getState() + .actions.setCurrentState({ + globals: { + ...useSuperStore.getState().modules[moduleName].useCurrentStateStore.getState().globals, + theme: { name: exposedTheme }, + }, + }); + globalSettingsChanged({ appMode: value }); + }} + defaultValue={appMode} + > + {APP_MODES.map((appMode) => ( + + {appMode.label} + + ))} + +
+
+ ); +}; + +export default AppModeToggle; diff --git a/frontend/src/Editor/Header/GlobalSettings.jsx b/frontend/src/Editor/Header/GlobalSettings.jsx index cb0a95cb29..170c6183bd 100644 --- a/frontend/src/Editor/Header/GlobalSettings.jsx +++ b/frontend/src/Editor/Header/GlobalSettings.jsx @@ -15,6 +15,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore'; import { shallow } from 'zustand/shallow'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore'; +import AppModeToggle from './AppModeToggle'; export const GlobalSettings = ({ globalSettings, @@ -42,7 +43,6 @@ export const GlobalSettings = ({ }), shallow ); - const { app, slug: oldSlug } = useAppInfo(); const coverStyles = { @@ -148,7 +148,7 @@ export const GlobalSettings = ({ /> )}
-
+
@@ -380,7 +380,7 @@ export const GlobalSettings = ({
- +

Export app

diff --git a/frontend/src/Editor/Header/HeaderActions.jsx b/frontend/src/Editor/Header/HeaderActions.jsx index 314f7a3569..6b8535252b 100644 --- a/frontend/src/Editor/Header/HeaderActions.jsx +++ b/frontend/src/Editor/Header/HeaderActions.jsx @@ -13,8 +13,8 @@ function HeaderActions({ showToggleLayoutBtn, showUndoRedoBtn, showFullWidth, + darkMode, }) { - const darkMode = localStorage.getItem('darkMode') === 'true'; const { currentLayout, toggleCurrentLayout } = useEditorStore( (state) => ({ currentLayout: state.currentLayout, diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js index 4d9751f207..d155e1b884 100644 --- a/frontend/src/Editor/Header/index.js +++ b/frontend/src/Editor/Header/index.js @@ -81,7 +81,7 @@ export default function EditorHeader({ const shouldRenderReleaseButton = !!app?.id; return ( -
+
@@ -116,6 +116,7 @@ export default function EditorHeader({ handleRedo={handleRedo} showToggleLayoutBtn showUndoRedoBtn + darkMode={darkMode} />
@@ -153,6 +154,7 @@ export default function EditorHeader({ setAppDefinitionFromVersion={setAppDefinitionFromVersion} onVersionDelete={onVersionDelete} isPublic={isPublic ?? false} + darkMode={darkMode} /> )}
diff --git a/frontend/src/Editor/LeftSidebar/index.jsx b/frontend/src/Editor/LeftSidebar/index.jsx index e6adb30771..120e6e9b40 100644 --- a/frontend/src/Editor/LeftSidebar/index.jsx +++ b/frontend/src/Editor/LeftSidebar/index.jsx @@ -21,6 +21,7 @@ import useDebugger from './SidebarDebugger/useDebugger'; import { GlobalSettings } from '../Header/GlobalSettings'; import { resolveReferences } from '@/_helpers/utils'; import { useCurrentState } from '@/_stores/currentStateStore'; +import cx from 'classnames'; export const LeftSidebar = forwardRef((props, ref) => { const router = useRouter(); @@ -71,9 +72,10 @@ export const LeftSidebar = forwardRef((props, ref) => { }), shallow ); - const { showComments } = useEditorStore( + const { showComments, appMode } = useEditorStore( (state) => ({ showComments: state?.showComments, + appMode: state?.appMode, }), shallow ); @@ -84,7 +86,6 @@ export const LeftSidebar = forwardRef((props, ref) => { currentPageId, isDebuggerOpen: !!selectedSidebarItem, }); - const sideBarBtnRefs = useRef({}); useEffect(() => { @@ -232,7 +233,7 @@ export const LeftSidebar = forwardRef((props, ref) => { }, [JSON.stringify(resolveReferences(backgroundFxQuery, currentState))]); return ( -
+
handleSelectedSidebarItem('page')} @@ -314,9 +315,9 @@ export const LeftSidebar = forwardRef((props, ref) => { />
)} +
- {/* */}
); diff --git a/frontend/src/Editor/QueryPanel/QueryPanel.jsx b/frontend/src/Editor/QueryPanel/QueryPanel.jsx index 000e754535..61f29c84fc 100644 --- a/frontend/src/Editor/QueryPanel/QueryPanel.jsx +++ b/frontend/src/Editor/QueryPanel/QueryPanel.jsx @@ -3,7 +3,6 @@ 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 { useQueryPanelActions } from '@/_stores/queryPanelStore'; import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore'; @@ -12,6 +11,7 @@ import { cloneDeep, isEmpty, isEqual } from 'lodash'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { ModuleContext } from '../../_contexts/ModuleContext'; import { useSuperStore } from '../../_stores/superStore'; +import cx from 'classnames'; const QueryPanel = ({ dataQueriesChanged, @@ -150,7 +150,7 @@ const QueryPanel = ({ }, []); return ( - <> +
- +
); }; diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index 275872b129..6177292189 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -48,6 +48,7 @@ import ViewerSidebarNavigation from './Viewer/ViewerSidebarNavigation'; import MobileHeader from './Viewer/MobileHeader'; import DesktopHeader from './Viewer/DesktopHeader'; import './Viewer/viewer.scss'; +import useAppDarkMode from '@/_hooks/useAppDarkMode'; class ViewerComponent extends React.Component { static contextType = ModuleContext; @@ -59,7 +60,7 @@ class ViewerComponent extends React.Component { const slug = this.props.params.slug; this.subscription = null; - + this.props.setEditorOrViewer('viewer'); this.state = { slug, deviceWindowWidth, @@ -92,6 +93,7 @@ class ViewerComponent extends React.Component { appDefData.homePageId = data.homePageId; appDefData.showViewerNavigation = data.showViewerNavigation; } + const appMode = data.globalSettings?.appMode || data?.editing_version?.globalSettings?.appMode; useSuperStore .getState() .modules[this.context].useAppVersionStore.getState() @@ -100,6 +102,7 @@ class ViewerComponent extends React.Component { .getState() .modules[this.context].useAppVersionStore.getState() .actions.updateReleasedVersionId(data.currentVersionId); + useSuperStore.getState().modules[this.context].useEditorStore.getState().actions.setAppMode(appMode); this.setState({ app: data, isLoading: false, @@ -359,8 +362,6 @@ 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) { useSuperStore.getState().modules[this.context].useAppDataStore.getState().actions.setAppId(appId); @@ -608,7 +609,6 @@ class ViewerComponent extends React.Component { } = this.state; const moduleName = this.context; - const currentCanvasWidth = canvasWidth; const queryConfirmationList = this.props?.queryConfirmationList ?? []; const canvasMaxWidth = this.computeCanvasMaxWidth(); @@ -643,7 +643,12 @@ class ViewerComponent extends React.Component { ); } else { return ( -
+
0} message={'Do you want to run this query?'} @@ -653,6 +658,7 @@ class ViewerComponent extends React.Component { onCancel={() => onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationList[0], false, 'view')} queryConfirmationData={queryConfirmationList[0]} key={queryConfirmationList[0]?.queryName} + darkMode={this.props.darkMode} /> {this.props.currentLayout !== 'mobile' && ( @@ -695,7 +701,6 @@ class ViewerComponent extends React.Component { false} // function not relevant in viewer snapToGrid={true} appLoading={isLoading} - darkMode={this.props.darkMode} onEvent={this.handleEvent} mode="view" deviceWindowWidth={isMobilePreviewMode ? '450px' : deviceWindowWidth} @@ -769,6 +773,7 @@ class ViewerComponent extends React.Component { canvasWidth={this.getCanvasWidth()} dataQueries={dataQueries} currentPageId={this.state.currentPageId} + darkMode={this.props.darkMode} /> )} @@ -814,6 +819,7 @@ const withStore = (Component) => (props) => { shallow ); const { updateState } = useAppDataActions(); + const { isAppDarkMode } = useAppDarkMode(); return ( (props) => { currentLayout={currentLayout} updateState={updateState} queryConfirmationList={queryConfirmationList} + darkMode={isAppDarkMode} /> ); }; diff --git a/frontend/src/Editor/Viewer/DesktopHeader.jsx b/frontend/src/Editor/Viewer/DesktopHeader.jsx index 3a4182304e..9e40fb3b07 100644 --- a/frontend/src/Editor/Viewer/DesktopHeader.jsx +++ b/frontend/src/Editor/Viewer/DesktopHeader.jsx @@ -10,6 +10,7 @@ import { redirectToDashboard } from '@/_helpers/routes'; import classNames from 'classnames'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import PreviewSettings from './PreviewSettings'; +import { useEditorStore } from '@/_stores/editorStore'; const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDefinitionFromVersion }) => { const { isVersionReleased, editingVersion } = useAppVersionStore( @@ -19,6 +20,13 @@ const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDe }), shallow ); + const { showDarkModeToggle } = useEditorStore( + (state) => ({ + showDarkModeToggle: state.appMode === 'auto' || !state.appMode, + }), + shallow + ); + const _renderAppNameAndLogo = () => (
)} - - - + {showDarkModeToggle && ( + + + + )} ); } @@ -76,9 +86,11 @@ const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDe darkMode={darkMode} /> )} -
- -
+ {showDarkModeToggle && ( +
+ +
+ )}
); }; diff --git a/frontend/src/Editor/Viewer/MobileHeader.jsx b/frontend/src/Editor/Viewer/MobileHeader.jsx index 662e349749..129cf8b3ce 100644 --- a/frontend/src/Editor/Viewer/MobileHeader.jsx +++ b/frontend/src/Editor/Viewer/MobileHeader.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import _, { isEmpty } from 'lodash'; // eslint-disable-next-line import/no-unresolved import LogoIcon from '@assets/images/rocket.svg'; @@ -11,6 +11,7 @@ import classNames from 'classnames'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import PreviewSettings from './PreviewSettings'; import MobileNavigationMenu from './MobileNavigationMenu'; +import { useEditorStore } from '@/_stores/editorStore'; const MobileHeader = ({ showHeader, @@ -30,6 +31,12 @@ const MobileHeader = ({ }), shallow ); + const { showDarkModeToggle } = useEditorStore( + (state) => ({ + showDarkModeToggle: state.appMode === 'auto', + }), + shallow + ); // Fetch the version parameter from the query string const searchParams = new URLSearchParams(window.location.search); @@ -67,6 +74,7 @@ const MobileHeader = ({ darkMode={darkMode} changeDarkMode={changeDarkMode} showHeader={showHeader} + showDarkModeToggle={showDarkModeToggle} /> ); @@ -80,6 +88,7 @@ const MobileHeader = ({ ); const _renderDarkModeBtn = (args) => { + if (!showDarkModeToggle) return null; const styles = args?.styles ?? {}; return ( { +const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, changeDarkMode, showDarkModeToggle }) => { const [hamburgerMenuOpen, setHamburgerMenuOpen] = useState(false); const handlepageSwitch = (pageId) => { setHamburgerMenuOpen(false); @@ -102,22 +102,24 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan
-
-
-
-
- + {showDarkModeToggle && ( +
+
+
+
+ +
-
+ )} ); diff --git a/frontend/src/Editor/Viewer/PreviewSettings.jsx b/frontend/src/Editor/Viewer/PreviewSettings.jsx index 3ec6acbf29..1035472e09 100644 --- a/frontend/src/Editor/Viewer/PreviewSettings.jsx +++ b/frontend/src/Editor/Viewer/PreviewSettings.jsx @@ -32,6 +32,7 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead onVersionDelete={noop} isEditable={false} isViewer + darkMode={darkMode} /> )} @@ -98,12 +99,13 @@ const PreviewSettings = ({ isMobileLayout, setAppDefinitionFromVersion, showHead onVersionDelete={noop} isEditable={false} isViewer + darkMode={darkMode} /> )}
layout - +
)} diff --git a/frontend/src/_components/DarkModeToggle.jsx b/frontend/src/_components/DarkModeToggle.jsx index d797fa7a17..ae7fa16fd3 100644 --- a/frontend/src/_components/DarkModeToggle.jsx +++ b/frontend/src/_components/DarkModeToggle.jsx @@ -3,6 +3,7 @@ import { useSpring, animated } from 'react-spring'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import Tooltip from 'react-bootstrap/Tooltip'; import { useTranslation } from 'react-i18next'; +import classnames from 'classnames'; export const DarkModeToggle = function DarkModeToggle({ darkMode = false, @@ -13,6 +14,7 @@ export const DarkModeToggle = function DarkModeToggle({ const toggleDarkMode = () => { switchDarkMode(!darkMode); }; + const { t } = useTranslation(); const properties = { sun: { @@ -51,6 +53,7 @@ export const DarkModeToggle = function DarkModeToggle({ {darkMode @@ -60,7 +63,7 @@ export const DarkModeToggle = function DarkModeToggle({ } >
{ + const { appMode, setAppMode } = useEditorStore( + (state) => ({ + appMode: state.appMode, + setAppMode: state.actions.setAppMode, + }), + shallow + ); + + const { isTJDarkMode } = useAppDataStore( + (state) => ({ + isTJDarkMode: state.isTJDarkMode, + }), + shallow + ); + + const handleAppModeChange = useCallback( + (appMode = 'auto') => { + setAppMode(appMode); + }, + [setAppMode] + ); + + const isAppDarkMode = useMemo(() => { + if (appMode === 'light') { + return false; + } else if (appMode === 'dark') { + return true; + } else { + return localStorage.getItem('darkMode') === 'true'; + } + }, [appMode, isTJDarkMode]); + + return { + onAppModeChange: handleAppModeChange, + appMode, + isAppDarkMode, + }; +}; + +export default useAppDarkMode; diff --git a/frontend/src/_stores/appDataStore.js b/frontend/src/_stores/appDataStore.js index 2ec71fd23c..dc458b7f53 100644 --- a/frontend/src/_stores/appDataStore.js +++ b/frontend/src/_stores/appDataStore.js @@ -35,6 +35,7 @@ export function createAppDataStore(moduleName) { eventsCreatedLoader: false, actionsUpdatedLoader: false, eventToDeleteLoaderIndex: null, + isTJDarkMode: localStorage.getItem('darkMode') === 'true', }; return create( zustandDevTools( @@ -147,6 +148,7 @@ export function createAppDataStore(moduleName) { setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })), setMetadata: (metadata) => set(() => ({ metadata })), setEventToDeleteLoaderIndex: (index) => set(() => ({ eventToDeleteLoaderIndex: index })), + updateIsTJDarkMode: (isTJDarkMode) => set({ isTJDarkMode }), }, }), { name: 'App Data Store' } diff --git a/frontend/src/_stores/editorStore.js b/frontend/src/_stores/editorStore.js index 7c421e5a40..16e63b254e 100644 --- a/frontend/src/_stores/editorStore.js +++ b/frontend/src/_stores/editorStore.js @@ -45,6 +45,7 @@ export function createEditorStore(moduleName) { currentPageId: null, currentSessionId: uuid(), moduleName, + appMode: 'auto', }; return create( @@ -93,6 +94,7 @@ export function createEditorStore(moduleName) { }); }, setCurrentPageId: (currentPageId) => set({ currentPageId }), + setAppMode: (appMode) => set({ appMode }), }, }), { name: STORE_NAME } diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index 7fda3ce450..64688aa219 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -61,7 +61,7 @@ @import "@radix-ui/colors/goldDark.css"; @import "@radix-ui/colors/bronzeDark.css"; -:root { +:root, .light-theme { --base: #FCFCFD; --base-black: #18181A; --text-black-000: #000000; diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index eb09912d85..a6935ebfc8 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -79,6 +79,8 @@ max-width: 350px; } + + .queryData { &.close { max-height: 0; @@ -158,6 +160,22 @@ } } +.global-settings-panel { + .app-mode-switch { + .ToggleGroup { + width : 100%; + } + .ToggleGroupItem { + font-size: 12px; + } + .ToggleGroupItem[data-state='on'] { + .toggle-item { + font-size: 12px; + } + } + } +} + .debugger { .leftsidebar-panel-header { border-bottom: none; diff --git a/frontend/src/_styles/table-component.scss b/frontend/src/_styles/table-component.scss index afc811bfb0..471232b87d 100644 --- a/frontend/src/_styles/table-component.scss +++ b/frontend/src/_styles/table-component.scss @@ -581,16 +581,18 @@ align-items: center; background: inherit; } - .warning-no-data-text{ - color: var(--slate11) !important; - /* Paragraph/Small/Medium */ - font-size: 14px; - font-style: normal; - font-weight: 500; - line-height: 20px; /* 142.857% */ - } } + .warning-no-data-text{ + color: var(--slate11) !important; + /* Paragraph/Small/Medium */ + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; /* 142.857% */ + } + + } .table-striped > tbody > tr:nth-of-type(even){ background-color: var(--slate2) ; @@ -639,4 +641,4 @@ } .th:has(.resizer:hover){ border-right:4px solid var(--slate5, #ECEEF0) !important; -} \ No newline at end of file +} diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 076ce114da..761b42754d 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -724,7 +724,7 @@ button { } .canvas-area { - background: #F9F9FB; + // background: #F9F9FB; margin: 0px auto; .resizer { @@ -775,7 +775,7 @@ button { .canvas-area { width: 1280px; - background: #F9F9FB; + // background: #F9F9FB; margin: 0px auto; background-size: 80px 80px; background-repeat: repeat; @@ -3097,7 +3097,7 @@ input:focus-visible { } .main .canvas-container .canvas-area { - background: #2f3c4c; + // background: #2f3c4c; } diff --git a/frontend/src/_ui/JSONTreeViewer/JSONNodeValue.jsx b/frontend/src/_ui/JSONTreeViewer/JSONNodeValue.jsx index bb75df2c12..ddcf368fd3 100644 --- a/frontend/src/_ui/JSONTreeViewer/JSONNodeValue.jsx +++ b/frontend/src/_ui/JSONTreeViewer/JSONNodeValue.jsx @@ -1,6 +1,8 @@ import React from 'react'; +import useAppDarkMode from '@/_hooks/useAppDarkMode'; const JSONTreeValueNode = ({ data, type }) => { + const { appMode } = useAppDarkMode(); if (type === 'Function') { return; // const functionString = `${data.toString().split('{')[0].trim()}{...}`; @@ -23,13 +25,29 @@ const JSONTreeValueNode = ({ data, type }) => { const clsForUndefinedOrNull = (type === 'Undefined' || type === 'Null') && 'badge badge-secondary'; return ( - - {value} - + <> + + {value} + + {appMode === 'auto' && (data === 'light' || data === 'dark') && ( + + auto + + )} + ); }; diff --git a/frontend/src/_ui/Select/SelectComponent.jsx b/frontend/src/_ui/Select/SelectComponent.jsx index 3e371fe68d..2cda4eef4e 100644 --- a/frontend/src/_ui/Select/SelectComponent.jsx +++ b/frontend/src/_ui/Select/SelectComponent.jsx @@ -9,9 +9,10 @@ export const SelectComponent = ({ onChange, closeMenuOnSelect, classNamePrefix, + darkMode, ...restProps }) => { - const darkMode = localStorage.getItem('darkMode') === 'true'; + const isDarkMode = darkMode ?? localStorage.getItem('darkMode') === 'true'; const { isMulti = false, styles = {}, @@ -30,7 +31,7 @@ export const SelectComponent = ({ isDisabled = false, } = restProps; - const customStyles = useCustomStyles ? styles : defaultStyles(darkMode, width, height, styles); + const customStyles = useCustomStyles ? styles : defaultStyles(isDarkMode, width, height, styles); const selectOptions = Array.isArray(options) && options.length === 0 ? options @@ -76,7 +77,7 @@ export const SelectComponent = ({ maxMenuHeight={maxMenuHeight} menuPortalTarget={useMenuPortal ? document.body : menuPortalTarget} closeMenuOnSelect={closeMenuOnSelect ?? true} - classNamePrefix={`${darkMode && 'dark-theme'} ${'react-select'}`} + classNamePrefix={`${isDarkMode && 'dark-theme'} ${'react-select'}`} /> ); }; diff --git a/server/src/services/app_import_export.service.ts b/server/src/services/app_import_export.service.ts index d0966082cb..44b4ed1a1a 100644 --- a/server/src/services/app_import_export.service.ts +++ b/server/src/services/app_import_export.service.ts @@ -1225,6 +1225,7 @@ export class AppImportExportService { canvasMaxHeight: 2400, canvasBackgroundColor: '#edeff5', backgroundFxQuery: '', + appMode: 'auto', }; } else { version.globalSettings = appVersion.definition?.globalSettings; diff --git a/server/src/services/apps.service.ts b/server/src/services/apps.service.ts index 3271c3253c..461c5ab620 100644 --- a/server/src/services/apps.service.ts +++ b/server/src/services/apps.service.ts @@ -147,6 +147,7 @@ export class AppsService { canvasMaxHeight: 2400, canvasBackgroundColor: '#edeff5', backgroundFxQuery: '', + appMode: 'auto', }; await manager.save(appVersion); From 8a6de8f199dcc9260884981079ed8a5805aaa1c6 Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Fri, 19 Apr 2024 13:00:22 +0530 Subject: [PATCH 08/34] fix :: Event triggering onclose of modal gets triggered on each refresh (#8388) * fix :: event triggering onclose of modal gets triggered on each render * fix * fix * close event triggering multiple times bugfix * fix : modal firing close event multiple times * revert * cleanup --- frontend/src/Editor/Components/Modal.jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/Editor/Components/Modal.jsx b/frontend/src/Editor/Components/Modal.jsx index 0f2a2cec73..be57a1cdc5 100644 --- a/frontend/src/Editor/Components/Modal.jsx +++ b/frontend/src/Editor/Components/Modal.jsx @@ -42,6 +42,7 @@ export const Modal = function Modal({ boxShadow, } = styles; const parentRef = useRef(null); + const isInitialRender = useRef(true); const title = properties.title ?? ''; const size = properties.size ?? 'lg'; @@ -62,14 +63,24 @@ export const Modal = function Modal({ }, [setShowModal]); useEffect(() => { + if (isInitialRender.current) { + isInitialRender.current = false; + return; + } const canShowModal = exposedVariables.show ?? false; - setShowModal(exposedVariables.show ?? false); fireEvent(canShowModal ? 'onOpen' : 'onClose'); + setShowModal(exposedVariables.show ?? false); const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0]; inputRef?.blur(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [exposedVariables.show]); + function hideModal() { + setShowModal(false); + setExposedVariable('show', false); + fireEvent('onClose'); + } + useEffect(() => { const handleModalOpen = () => { const canvasElement = document.getElementsByClassName('canvas-area')[0]; @@ -127,11 +138,6 @@ export const Modal = function Modal({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [showModal, modalHeight]); - function hideModal() { - setShowModal(false); - setExposedVariable('show', false); - fireEvent('onClose'); - } const backwardCompatibilityCheck = height == '34' || modalHeight != undefined ? true : false; const customStyles = { From c8ed3fc1cd3006e130c88b95340568966bc19b2c Mon Sep 17 00:00:00 2001 From: Kiran Ashok Date: Fri, 19 Apr 2024 13:03:23 +0530 Subject: [PATCH 09/34] feat: Adding new color swatches and update new components with color variable (#8935) * feat : added design theme new colors * fix : adding new color swatch to input components * fix : colors, number input with height less than 16px and number input arrow in padding none mdode * fix : icon color * few color fixes * fixes * fix : icon color * icon color fixes * fix: color on disabled * fix : icon ill * feat :: add new icons for number inputs * remove unwanted file * render preview app fix * fix : arrows in number input * add hover color for number input * update colors with opacity * Revert "Merge branch 'appbuilder-1.6' into feature/color-theming" This reverts commit 7a59814e58e8498186ad46d45434250e4e73836e, reversing changes made to 9ef7867889c0f8fa39a7a5a7147e625cd0432fc7. * Revert "Revert "Merge branch 'appbuilder-1.6' into feature/color-theming"" This reverts commit 02dcd8acf0b0a2e0a1f9e921429ec20ca09805ca. * revert few styles * fix : Able to update the number using arrows in the disable state * fix : scrolling in color picker * fix : color picker scroll issue --------- Co-authored-by: Adish M --- .../Editor/CodeBuilder/Elements/BoxShadow.jsx | 2 +- .../src/Editor/CodeBuilder/Elements/Color.jsx | 2 +- .../src/Editor/Components/NumberInput.jsx | 83 +++++++----- .../src/Editor/Components/PasswordInput.jsx | 47 ++++--- frontend/src/Editor/Components/TextInput.jsx | 38 ++++-- .../src/Editor/WidgetManager/widgetConfig.js | 31 ++--- frontend/src/_styles/designtheme.scss | 125 ++++++++++++++++-- frontend/src/_styles/theme.scss | 48 +++++-- frontend/src/_ui/Icon/solidIcons/Eye1.jsx | 2 +- .../src/_ui/Icon/solidIcons/EyeDisable.jsx | 2 +- .../Icon/solidIcons/TriangleDownCenter.jsx | 22 +++ .../_ui/Icon/solidIcons/TriangleUpCenter.jsx | 22 +++ frontend/src/_ui/Icon/solidIcons/index.js | 6 + .../solidIcons/new-export-1682542240466.json | 23 ---- frontend/src/_ui/Label.jsx | 6 +- 15 files changed, 326 insertions(+), 133 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/TriangleDownCenter.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/TriangleUpCenter.jsx delete mode 100644 frontend/src/_ui/Icon/solidIcons/new-export-1682542240466.json diff --git a/frontend/src/Editor/CodeBuilder/Elements/BoxShadow.jsx b/frontend/src/Editor/CodeBuilder/Elements/BoxShadow.jsx index ec4a41b4c7..1ceb87a1c1 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/BoxShadow.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/BoxShadow.jsx @@ -29,7 +29,7 @@ export const BoxShadow = ({ value, onChange, cyLabel }) => { const colorPickerStyle = { position: 'absolute', - bottom: '260px', + top: '-220px', }; useEffect(() => { diff --git a/frontend/src/Editor/CodeBuilder/Elements/Color.jsx b/frontend/src/Editor/CodeBuilder/Elements/Color.jsx index 66a3c984f0..f13c6bb4a6 100644 --- a/frontend/src/Editor/CodeBuilder/Elements/Color.jsx +++ b/frontend/src/Editor/CodeBuilder/Elements/Color.jsx @@ -46,7 +46,7 @@ export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowP { 'inspector-color-input-popover': colorPickerPosition === 'top' } )} > - + <>{ColorPicker()} diff --git a/frontend/src/Editor/Components/NumberInput.jsx b/frontend/src/Editor/Components/NumberInput.jsx index df2e9140c2..66f388f331 100644 --- a/frontend/src/Editor/Components/NumberInput.jsx +++ b/frontend/src/Editor/Components/NumberInput.jsx @@ -39,7 +39,7 @@ export const NumberInput = function NumberInput({ accentColor, } = styles; - const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#fff' : styles.textColor; + const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#CFD3D8' : styles.textColor; const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState) ?? false; const minValue = resolveReferences(component?.definition?.validation?.minValue?.value, currentState) ?? null; const maxValue = resolveReferences(component?.definition?.validation?.maxValue?.value, currentState) ?? null; @@ -165,22 +165,28 @@ export const NumberInput = function NumberInput({ const computedStyles = { height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4, borderRadius: `${borderRadius}px`, - color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor, - borderColor: isFocused - ? accentColor - : ['#D7DBDF'].includes(borderColor) - ? darkMode - ? '#6D757D7A' - : '#6A727C47' - : borderColor, - '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), - backgroundColor: - darkMode && ['#ffffff', '#ffffffff', '#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, - boxShadow: boxShadow, padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px', overflow: 'hidden', textOverflow: 'ellipsis', + color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)', + borderColor: isFocused + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disable || loading + ? 'var(--borders-disabled-on-white-dimmed)' + : 'var(--borders-default)', + '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), + backgroundColor: !['#ffffff', '#ffffffff', '#fff'].includes(backgroundColor) + ? backgroundColor + : disable || loading + ? darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + : 'var(--surfaces-surface-01)', }; const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side'; @@ -278,7 +284,6 @@ export const NumberInput = function NumberInput({ <>
{showValidationError && visibility && (
{showValidationError && validationError} diff --git a/frontend/src/Editor/Components/PasswordInput.jsx b/frontend/src/Editor/Components/PasswordInput.jsx index a6e0cd521f..bcf98d5650 100644 --- a/frontend/src/Editor/Components/PasswordInput.jsx +++ b/frontend/src/Editor/Components/PasswordInput.jsx @@ -60,21 +60,28 @@ export const PasswordInput = function PasswordInput({ const computedStyles = { height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4, borderRadius: `${borderRadius}px`, - color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor, - borderColor: isFocused - ? accentColor - : ['#D7DBDF'].includes(borderColor) + backgroundColor: !['#ffffff', '#fff'].includes(backgroundColor) + ? backgroundColor + : disable || loading ? darkMode - ? '#6D757D7A' - : '#6A727C47' - : borderColor, - '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), - backgroundColor: darkMode && ['#fff', '#ffffff'].includes(backgroundColor) ? '#313538' : backgroundColor, + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + : 'var(--surfaces-surface-01)', boxShadow: boxShadow, padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px', - overflow: 'hidden', textOverflow: 'ellipsis', + color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)', + borderColor: isFocused + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disable || loading + ? '1px solid var(--borders-disabled-on-white)' + : 'var(--borders-default)', + '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), }; const loaderStyle = { @@ -232,7 +239,6 @@ export const PasswordInput = function PasswordInput({ <>
- +
)} { if (e.key === 'Enter') { @@ -363,11 +374,13 @@ export const PasswordInput = function PasswordInput({
{showValidationError && visibility && (
{showValidationError && validationError} diff --git a/frontend/src/Editor/Components/TextInput.jsx b/frontend/src/Editor/Components/TextInput.jsx index c729baa3eb..875a91d405 100644 --- a/frontend/src/Editor/Components/TextInput.jsx +++ b/frontend/src/Editor/Components/TextInput.jsx @@ -58,16 +58,25 @@ export const TextInput = function TextInput({ const computedStyles = { height: height == 36 ? (padding == 'default' ? '36px' : '40px') : padding == 'default' ? height : height + 4, borderRadius: `${borderRadius}px`, - color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor, + color: textColor !== '#1B1F24' ? textColor : disable || loading ? 'var(--text-disabled)' : 'var(--text-primary)', borderColor: isFocused - ? accentColor - : ['#D7DBDF'].includes(borderColor) - ? darkMode - ? '#6D757D7A' - : '#6A727C47' - : borderColor, + ? accentColor != '4368E3' + ? accentColor + : 'var(--primary-accent-strong)' + : borderColor != '#CCD1D5' + ? borderColor + : disable || loading + ? '1px solid var(--borders-disabled-on-white)' + : 'var(--borders-default)', '--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(), - backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor, + backgroundColor: + backgroundColor != '#fff' + ? backgroundColor + : disable || loading + ? darkMode + ? 'var(--surfaces-app-bg-default)' + : 'var(--surfaces-surface-03)' + : 'var(--surfaces-surface-01)', boxShadow: boxShadow, padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px', overflow: 'hidden', @@ -238,8 +247,7 @@ export const TextInput = function TextInput({ const renderInput = () => ( <>
0 && width > 0) || (auto && width == 0 && label && label?.length != 0) ? `${labelWidth + 11}px` - : '11px', //23 :: is 10 px inside the input + 1 px border + 12px margin right + : '11px', //11 :: is 10 px inside the input + 1 px border + 12px margin right position: 'absolute', top: `${ defaultAlignment === 'side' @@ -289,7 +297,7 @@ export const TextInput = function TextInput({ : '50%' }`, transform: ' translateY(-50%)', - color: iconColor, + color: iconColor !== '#CFD3D859' ? iconColor : 'var(--icons-weak-disabled)', zIndex: 3, }} stroke={1.5} @@ -300,7 +308,7 @@ export const TextInput = function TextInput({ ref={textInputRef} className={`tj-text-input-widget ${ !isValid && showValidationError ? 'is-invalid' : '' - } validation-without-icon ${darkMode && 'dark-theme-placeholder'}`} + } validation-without-icon`} onKeyUp={(e) => { if (e.key === 'Enter') { setValue(e.target.value); @@ -338,11 +346,13 @@ export const TextInput = function TextInput({
{showValidationError && visibility && (
{showValidationError && validationError} diff --git a/frontend/src/Editor/WidgetManager/widgetConfig.js b/frontend/src/Editor/WidgetManager/widgetConfig.js index 608044e689..612d84c9f2 100644 --- a/frontend/src/Editor/WidgetManager/widgetConfig.js +++ b/frontend/src/Editor/WidgetManager/widgetConfig.js @@ -1700,17 +1700,17 @@ export const widgets = [ }, events: [], styles: { - textColor: { value: '#11181C' }, - borderColor: { value: '#6A727C47' }, + textColor: { value: '#1B1F24' }, + borderColor: { value: '#CCD1D5' }, accentColor: { value: '#4368E3' }, - errTextColor: { value: '#DB4324' }, + errTextColor: { value: '#D72D39' }, borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, - iconColor: { value: '#C1C8CD' }, + iconColor: { value: '#CFD3D859' }, direction: { value: 'left' }, width: { value: '{{33}}' }, alignment: { value: 'side' }, - color: { value: '#11181C' }, + color: { value: '#1B1F24' }, auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, @@ -1989,15 +1989,16 @@ export const widgets = [ styles: { borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, - borderColor: { value: '#6A727C47' }, + borderColor: { value: '#CCD1D5' }, accentColor: { value: '#4368E3' }, - errTextColor: { value: '#DB4324' }, - textColor: { value: '#232e3c' }, - iconColor: { value: '#C1C8CD' }, + errTextColor: { value: '#D72D39' }, + textColor: { value: '#1B1F24' }, + color: { value: '#1B1F24' }, + + iconColor: { value: '#CFD3D859' }, direction: { value: 'left' }, width: { value: '{{33}}' }, alignment: { value: 'side' }, - color: { value: '#11181C' }, auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, @@ -2273,15 +2274,15 @@ export const widgets = [ styles: { borderRadius: { value: '{{6}}' }, backgroundColor: { value: '#fff' }, - borderColor: { value: '#6A727C47' }, + borderColor: { value: '#CCD1D5' }, accentColor: { value: '#4368E3' }, - errTextColor: { value: '#DB4324' }, - textColor: { value: '#11181C' }, - iconColor: { value: '#C1C8CD' }, + errTextColor: { value: '#D72D39' }, + textColor: { value: '#1B1F24' }, + iconColor: { value: '#CFD3D859' }, direction: { value: 'left' }, width: { value: '{{33}}' }, alignment: { value: 'side' }, - color: { value: '#11181C' }, + color: { value: '#1B1F24' }, auto: { value: '{{true}}' }, padding: { value: 'default' }, boxShadow: { value: '0px 0px 0px 0px #00000040' }, diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index 64688aa219..b9ff5c077e 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -67,18 +67,68 @@ --text-black-000: #000000; --slate12: #313739; - --tj-text-input-widget-border-default: #6A727C47; - --tj-text-input-widget-field-default: #FFFFFF; - --tj-text-input-widget-hover: #F1F3F5; - --tj-text-input-widget-error: #DB4324; - --tj-text-input-widget-disabled: #DFE3E6; - --tj-text-input-widget-border-clicked: #3E63DD; --controls-switch-tab: #fff; --controls-switch-tag: #CCD1D54D; --text-disabled: #889099; --text-default: #1B1F24; + // new design theme + --primary-brand: #4368E3; + --primary-accent-strong: #4368E3; + --primary-accent-subtle: rgba(67, 104, 227, 30%); + --primary-white: #FFFFFF; + --primary-black: #1B1F24; + + // text + --text-primary: #1B1F24; + --text-placeholder: #6A727C; + --text-disbled: #ACB2B9; + --text-on-solid: #FFFFFF; + + + // status + --status-error-strong: #D72D39; + --status-error-subtle: rgba(215, 45, 57, 30%); + --status-warning-strong: #BF4F03; + --status-warning-subtle: rgba(191, 79, 3, 30%); + --status-success-strong: #1E823B; + --status-success-subtle: rgba(30, 130, 59, 30%); + + //icon + --icons-strong: #6A727C; + --icons-default: #6A727C; + --icons-weak-disabled: #6A727C; + --icons-disabled-on-white: #6A727C; + --icons-on-solid: #6A727C; + + // borders + --borders-strong: #ACB2B9; + --borders-default: #CCD1D5; + --borders-weak-disabled: #CCD1D5; + --borders-disabled-on-white: #E4E7EB; + --borders-disabled-on-white-dimmed: rgba(246, 248, 250, 80%); + + // interactive overlays + --interactive-overlays-fill-hover: rgba(27, 31, 36, 8%); + --interactive-overlays-fill-pressed: rgba(27, 31, 36, 10%); + --interactive-overlays-border-hover: rgba(27, 31, 36, 12%); + --interactive-overlays-border-pressed: rgba(27, 31, 36, 16%); + --interactive-overlays-focus-outline: #4368E3; + --interactive-overlays-focus-inner-shadow: #FFFFFF; + --interactive-overlays-column-resize: #1B1F244D; + + + //surfaces + --surfaces-app-bg-default: #F6F6F6; + --surfaces-surface-01: #FFFFFF; + --surfaces-surface-02: #F6F8FA; + --surfaces-surface-03: #E4E7EB; + + // utilities + --utilities-scrollbar: rgba(106, 114, 124, 12%); + + } .dark-theme { @@ -88,16 +138,67 @@ --text-black-000: #ffffff; --slate1: #1f2936; - --tj-text-input-widget-border-default: #6D757D7A; - --tj-text-input-widget-field-default: #313538; - --tj-text-input-widget-hover: #292D2F; - --tj-text-input-widget-error: #EC5E41; - --tj-text-input-widget-disabled: #2B2F3199; - --tj-text-input-widget-border-clicked: #3E63DD; --controls-switch-tab: #2B3036; --controls-switch-tag: #121518; --text-disabled: #6D757D; --text-default: #FAFCFF; + + // new design theme + --primary-brand: #4A6DD9; + --primary-accent-strong: #4A6DD9; + --primary-accent-subtle: rgba(74, 109, 217, 30%); + --primary-white: #FAFCFF; + --primary-black: #121518; + + // text + --text-primary: #CFD3D8; + --text-placeholder: #858C94; + --text-disbled: #545B64; + --text-on-solid: #FAFCFF; + + + // status + --status-error-strong: #D03F43; + --status-error-subtle: rgba(208, 63, 67, 30%); + --status-warning-strong: #BA5722; + --status-warning-subtle: rgba(186, 87, 34, 30%); + --status-success-strong: #318344; + --status-success-subtle: rgba(49, 131, 68, 30%); + + //icon + --icons-strong: rgba(207, 211, 216, 90%); + --icons-default: rgba(207, 211, 216, 65%); + --icons-weak-disabled: rgba(207, 211, 216, 35%); + --icons-disabled-on-white: rgba(207, 211, 216, 20%); + --icons-on-solid: #FAFCFF; + + // borders + --borders-strong: #545B64; + --borders-default: #3C434B; + --borders-weak-disabled: #2B3036; + --borders-disabled-on-white: rgba(43, 48, 54, 20%); + --borders-disabled-on-white-dimmed: rgba(43, 48, 54, 20%); + + + // interactive overlays + + --interactive-overlays-fill-hover: rgba(255, 255, 255, 12%); + --interactive-overlays-fill-pressed: rgba(255, 255, 255, 20%); + --interactive-overlays-border-hover: rgba(255, 255, 255, 20%); + --interactive-overlays-border-pressed: rgba(255, 255, 255, 28%); + --interactive-overlays-focus-outline: #4A6DD9; + --interactive-overlays-focus-inner-shadow: #121518; + --interactive-overlays-column-resize: #FFFFFF80; + + + //surfaces + --surfaces-app-bg-default: #121518; + --surfaces-surface-01: #1E2226; + --surfaces-surface-02: #2B3036; + --surfaces-surface-03: #3C434B; + + // utilities + --utilities-scrollbar: rgba(109, 117, 125, 16%); } \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 761b42754d..1ab2a54466 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -2903,8 +2903,7 @@ input:focus-visible { } .sketch-picker { - position: absolute; - left: -192px; + position: relative; top: 0px; border-radius: 6px !important; border: 1px solid var(--slate5, #E6E8EB) !important; @@ -2929,6 +2928,7 @@ input:focus-visible { .boxshadow-picker { .sketch-picker { left: -209px !important; + position: absolute !important; } } @@ -5316,7 +5316,7 @@ div#driver-page-overlay { .sso-card-wrapper { background: var(--base); min-height: 100%; - //height: calc(100vh - 156px); + // height: calc(100vh - 156px) !important; display: grid; grid-template-rows: auto 1fr auto; @@ -5378,6 +5378,7 @@ div#driver-page-overlay { margin-bottom: 4px !important; color: var(--slate12); } + .card-footer { display: flex; justify-content: flex-end; @@ -5390,13 +5391,15 @@ div#driver-page-overlay { align-Self: 'stretch'; height: 88px; } + .card-body { height: 467px; padding: 24px; - .form-group{ - .tj-app-input{ - .form-control{ - &:disabled{ + + .form-group { + .tj-app-input { + .form-control { + &:disabled { background: var(--slate3) !important; } } @@ -12372,6 +12375,7 @@ tbody { .sketch-picker { left: 70px !important; top: 207px; + position: absolute !important; } } @@ -12427,6 +12431,8 @@ tbody { .sketch-picker { left: 7px; width: 170px !important; + position: absolute !important; + } } @@ -12639,37 +12645,41 @@ tbody { } .modal-custom-height { - height: 700px !important; /* Set the desired width */ + height: 700px !important; + /* Set the desired width */ } + .tj-text-input-widget { - border: 1px solid var(--tj-text-input-widget-border-default); - background-color: var(--tj-text-input-widget-field-default); + border: 1px solid var(--borders-default); + background-color: var(--surfaces-surface-01); width: 100%; padding: 0px; z-index: 2; + &:disabled { + background-color: var(--surfaces-surface-03); + } &:hover:not(:focus) { border: 1px solid var(--tblr-input-border-color-darker) !important; } &.is-invalid { - border: 1px solid var(--tj-text-input-widget-error) !important; // For example, a red border for invalid input + border: 1px solid var(--status-error-strong) !important; // For example, a red border for invalid input } &:focus { outline: none !important; - border: 1px solid var(--tj-text-input-widget-border-clicked); + border: 1px solid var(--primary-accent-strong); } &:active { - // border: 1px solid var(--tj-text-input-widget-border-clicked) !important; outline: none !important; } &::placeholder { - color: #7E868C; + color: var(--text-placeholder) !important; } } @@ -12726,3 +12736,13 @@ tbody { -moz-appearance: textfield !important; } } + +.number-input-arrow { + &:hover { + background-color: var(--interactive-overlays-fill-hover) !important; + } + + &:active { + background-color: var(--interactive-overlays-fill-pressed) !important; + } +} \ No newline at end of file diff --git a/frontend/src/_ui/Icon/solidIcons/Eye1.jsx b/frontend/src/_ui/Icon/solidIcons/Eye1.jsx index d7c7142631..10115a521f 100644 --- a/frontend/src/_ui/Icon/solidIcons/Eye1.jsx +++ b/frontend/src/_ui/Icon/solidIcons/Eye1.jsx @@ -5,7 +5,7 @@ const Eye1 = ({ style, fill = '#C1C8CD', width = '25', className = '', viewBox = width={width} height={width} viewBox={viewBox} - fill="none" + fill={fill} xmlns="http://www.w3.org/2000/svg" className={className} style={style} diff --git a/frontend/src/_ui/Icon/solidIcons/EyeDisable.jsx b/frontend/src/_ui/Icon/solidIcons/EyeDisable.jsx index a6538817bd..d465ccda86 100644 --- a/frontend/src/_ui/Icon/solidIcons/EyeDisable.jsx +++ b/frontend/src/_ui/Icon/solidIcons/EyeDisable.jsx @@ -6,7 +6,7 @@ const EyeDisable = ({ style, fill = '#C1C8CD', width = '25', className = '', vie width={width} height={width} viewBox={viewBox} - fill="none" + fill={fill} xmlns="http://www.w3.org/2000/svg" className={className} style={style} diff --git a/frontend/src/_ui/Icon/solidIcons/TriangleDownCenter.jsx b/frontend/src/_ui/Icon/solidIcons/TriangleDownCenter.jsx new file mode 100644 index 0000000000..9f9dc1ce84 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/TriangleDownCenter.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const TriangleDownCenter = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24', style }) => ( + + + +); + +export default TriangleDownCenter; diff --git a/frontend/src/_ui/Icon/solidIcons/TriangleUpCenter.jsx b/frontend/src/_ui/Icon/solidIcons/TriangleUpCenter.jsx new file mode 100644 index 0000000000..684f83f1c4 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/TriangleUpCenter.jsx @@ -0,0 +1,22 @@ +import React from 'react'; + +const TriangleUpCenter = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 24 24', style }) => ( + + + +); + +export default TriangleUpCenter; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index da972e6e07..faa894ab36 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -158,6 +158,8 @@ import Uppercase from './Uppercase.jsx'; import Lowercase from './Lowercase.jsx'; import Capitalize from './Capitalize.jsx'; import Oblique from './Oblique.jsx'; +import TriangleUpCenter from './TriangleUpCenter.jsx'; +import TriangleDownCenter from './TriangleDownCenter.jsx'; const Icon = (props) => { switch (props.name) { @@ -479,6 +481,10 @@ const Icon = (props) => { return ; case 'oblique': return ; + case 'TriangleUpCenter': + return ; + case 'TriangleDownCenter': + return ; default: return ; } diff --git a/frontend/src/_ui/Icon/solidIcons/new-export-1682542240466.json b/frontend/src/_ui/Icon/solidIcons/new-export-1682542240466.json deleted file mode 100644 index 0e283d4824..0000000000 --- a/frontend/src/_ui/Icon/solidIcons/new-export-1682542240466.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tooljet_database": [ - { - "id": "a7426184-4d4d-4191-9f74-d668cc779ef9", - "table_name": "new", - "schema": { - "columns": [ - { - "column_name": "id", - "data_type": "integer", - "column_default": "nextval('\"a7426184-4d4d-4191-9f74-d668cc779ef9_id_seq\"'::regclass)", - "character_maximum_length": null, - "numeric_precision": 32, - "is_nullable": "NO", - "constraint_type": "PRIMARY KEY", - "keytype": "PRIMARY KEY" - } - ] - } - } - ], - "tooljet_version": "2.4.9" -} \ No newline at end of file diff --git a/frontend/src/_ui/Label.jsx b/frontend/src/_ui/Label.jsx index 0a4f91d0cc..58ca13645b 100644 --- a/frontend/src/_ui/Label.jsx +++ b/frontend/src/_ui/Label.jsx @@ -1,12 +1,11 @@ import React from 'react'; -function Label({ label, width, labelRef, darkMode, color, defaultAlignment, direction, auto, isMandatory, _width }) { +function Label({ label, width, labelRef, color, defaultAlignment, direction, auto, isMandatory, _width }) { return ( <> {label && (width > 0 || auto) && (