diff --git a/frontend/src/AppLoader/AppLoader.jsx b/frontend/src/AppLoader/AppLoader.jsx index b956efdfb4..1d34e0dec2 100644 --- a/frontend/src/AppLoader/AppLoader.jsx +++ b/frontend/src/AppLoader/AppLoader.jsx @@ -7,18 +7,32 @@ import config from 'config'; import { safelyParseJSON, stripTrailingSlash, redirectToDashboard, getSubpath, getWorkspaceId } from '@/_helpers/utils'; import { toast } from 'react-hot-toast'; import { useParams } from 'react-router-dom'; +import { useAppDataActions } from '@/_stores/appDataStore'; +import Spinner from '@/_ui/Spinner'; const AppLoaderComponent = (props) => { const params = useParams(); const appId = params.id; + const [shouldLoadApp, setShouldLoadApp] = React.useState(false); + + const { updateState } = useAppDataActions(); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => loadAppDetails(), []); const loadAppDetails = () => { - appService.getApp(appId, 'edit').catch((error) => { - handleError(error); - }); + appService + .getApp(appId, 'edit') + .then((data) => { + updateState({ + app: data, + }); + setShouldLoadApp(true); + }) + .catch((error) => { + handleError(error); + }); }; const switchOrganization = (orgId) => { @@ -63,7 +77,13 @@ const AppLoaderComponent = (props) => { } }; - return config.ENABLE_MULTIPLAYER_EDITING ? : ; + if (!shouldLoadApp) return ; + + return config.ENABLE_MULTIPLAYER_EDITING ? ( + + ) : ( + + ); }; export const AppLoader = withTranslation()(AppLoaderComponent); diff --git a/frontend/src/Editor/AppVersionsManager/List.jsx b/frontend/src/Editor/AppVersionsManager/List.jsx index 4d577b5688..0e75636288 100644 --- a/frontend/src/Editor/AppVersionsManager/List.jsx +++ b/frontend/src/Editor/AppVersionsManager/List.jsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import cx from 'classnames'; -import { appVersionService, appEnvironmentService } from '@/_services'; +import { appVersionService } from '@/_services'; import { CustomSelect } from './CustomSelect'; import { toast } from 'react-hot-toast'; import { shallow } from 'zustand/shallow'; @@ -12,7 +12,6 @@ export const AppVersionsManager = function ({ setAppDefinitionFromVersion, onVersionDelete, }) { - const [appVersions, setAppVersions] = useState([]); const [appVersionStatus, setGetAppVersionStatus] = useState(''); const [deleteVersion, setDeleteVersion] = useState({ versionId: '', @@ -20,29 +19,16 @@ export const AppVersionsManager = function ({ showModal: false, }); - const { editingVersion } = useAppVersionStore( + const { editingVersion, appVersions, setAppVersions } = useAppVersionStore( (state) => ({ editingVersion: state.editingVersion, + appVersions: state.appVersions, + setAppVersions: state.actions?.setAppVersions, }), shallow ); const darkMode = localStorage.getItem('darkMode') === 'true'; - useEffect(() => { - setGetAppVersionStatus('loading'); - appEnvironmentService - .getVersionsByEnvironment(appId) - .then((data) => { - setAppVersions(data.appVersions); - setGetAppVersionStatus('success'); - }) - .catch((error) => { - toast.error(error); - setGetAppVersionStatus('failure'); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const selectVersion = (id) => { appVersionService .getOne(appId, id) diff --git a/frontend/src/Editor/EditorFunc.jsx b/frontend/src/Editor/EditorFunc.jsx index 4ed5391399..87fec23114 100644 --- a/frontend/src/Editor/EditorFunc.jsx +++ b/frontend/src/Editor/EditorFunc.jsx @@ -1,5 +1,11 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { appService, authenticationService, appVersionService, orgEnvironmentVariableService } from '@/_services'; +import { + appService, + authenticationService, + appVersionService, + orgEnvironmentVariableService, + appEnvironmentService, +} from '@/_services'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import _, { @@ -55,19 +61,19 @@ import { withRouter } from '@/_hoc/withRouter'; import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError'; import { useDataSourcesStore } from '@/_stores/dataSourcesStore'; import { useDataQueries, useDataQueriesStore } from '@/_stores/dataQueriesStore'; -import { useAppVersionStore } from '@/_stores/appVersionStore'; +import { useAppVersionStore, useAppVersionActions } from '@/_stores/appVersionStore'; import { useQueryPanelStore } from '@/_stores/queryPanelStore'; import { useCurrentStateStore, useCurrentState } from '@/_stores/currentStateStore'; import { computeAppDiff, resetAllStores } from '@/_stores/utils'; import { setCookie } from '@/_helpers/cookie'; import { shallow } from 'zustand/shallow'; import { useEditorActions, useEditorState, useEditorStore } from '@/_stores/editorStore'; -import { useAppDataActions, useAppDataStore, useAppInfo } from '@/_stores/appDataStore'; +import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore'; import { useMounted } from '@/_hooks/use-mount'; // eslint-disable-next-line import/no-unresolved import { diff } from 'deep-object-diff'; -import { camelizeKeys, decamelizeKeys } from 'humps'; +import { camelizeKeys } from 'humps'; setAutoFreeze(false); enablePatches(); @@ -78,12 +84,88 @@ function setWindowTitle(name) { const decimalToHex = (alpha) => (alpha === 0 ? '00' : Math.round(255 * alpha).toString(16)); +const buildComponentMetaDefinition = (components = {}, events = []) => { + for (const componentId in components) { + const currentComponentData = components[componentId]; + const componentEvents = events + .filter((event) => event.sourceId === componentId) + ?.map((event) => ({ ...event.event, id: event.id })); + const componentMeta = componentTypes.find((comp) => currentComponentData.component.component === comp.component); + + const mergedDefinition = { + ...componentMeta.definition, + events: componentEvents, + properties: { + ...componentMeta.definition.properties, + ...currentComponentData?.component.definition.properties, + }, + + styles: { + ...componentMeta.definition.styles, + ...currentComponentData?.component.definition.styles, + }, + validations: { + ...componentMeta.definition.validations, + ...currentComponentData?.component.definition.validations, + }, + }; + + const mergedComponent = { + component: { + ...componentMeta, + ...currentComponentData.component, + }, + layouts: { + ...currentComponentData.layouts, + }, + withDefaultChildren: componentMeta.withDefaultChildren ?? false, + }; + + mergedComponent.component.definition = mergedDefinition; + + components[componentId] = mergedComponent; + } + + return components; +}; + +const buildAppDefinition = (data) => { + const editingVersion = _.omit(camelizeKeys(data.editing_version), ['definition', 'updatedAt', 'createdAt', 'name']); + + editingVersion['currentVersionId'] = editingVersion.id; + _.unset(editingVersion, 'id'); + + const eventsData = data?.events; + + const pages = data.pages.reduce((acc, page) => { + const currentComponents = buildComponentMetaDefinition(_.cloneDeep(page?.components), eventsData); + + page.components = currentComponents; + + acc[page.id] = page; + + return acc; + }, {}); + + const appJSON = { + globalSettings: editingVersion.globalSettings, + homePageId: editingVersion.homePageId, + showHideViewerNavigation: editingVersion.showHideViewerNavigation ?? true, + pages: pages, + }; + + return appJSON; +}; + const EditorComponent = (props) => { const { socket } = createWebsocketConnection(props?.params?.id); const mounted = useMounted(); const { updateState, updateAppDefinitionDiff, updateAppVersion } = useAppDataActions(); const { updateEditorState, updateQueryConfirmationList } = useEditorActions(); + + const { setAppVersions } = useAppVersionActions(); + const { noOfVersionsSupported, appDefinition, @@ -104,8 +186,18 @@ const EditorComponent = (props) => { const dataQueries = useDataQueries(); - const { isMaintenanceOn, appId, app, currentUser, currentVersionId, appDefinitionDiff, appDiffOptions, events } = - useAppInfo(); + const { + isMaintenanceOn, + appId, + app, + appName, + slug, + currentUser, + currentVersionId, + appDefinitionDiff, + appDiffOptions, + events, + } = useAppInfo(); const [currentPageId, setCurrentPageId] = useState(null); const [zoomLevel, setZoomLevel] = useState(1); @@ -299,8 +391,9 @@ const EditorComponent = (props) => { const $componentDidMount = async () => { window.addEventListener('message', handleMessage); + await fetchApp(props.params.pageHandle, true); + await fetchApps(0); - await fetchApp(props.params.pageHandle); await fetchOrgEnvironmentVariables(); initComponentVersioning(); initRealtimeSave(); @@ -323,6 +416,9 @@ const EditorComponent = (props) => { updateState({ appId: props?.params?.id }); useCurrentStateStore.getState().actions.setCurrentState({ globals }); + + getCanvasWidth(); + initEditorWalkThrough(); }; const fetchDataQueries = async (id, selectFirstQuery = false, runQueriesOnAppLoad = false) => { @@ -604,94 +700,36 @@ const EditorComponent = (props) => { //!-------- - const buildComponentMetaDefinition = (components = {}, events = []) => { - for (const componentId in components) { - const currentComponentData = components[componentId]; - const componentEvents = events - .filter((event) => event.sourceId === componentId) - ?.map((event) => ({ ...event.event, id: event.id })); - const componentMeta = componentTypes.find((comp) => currentComponentData.component.component === comp.component); - - const mergedDefinition = { - ...componentMeta.definition, - events: componentEvents, - properties: { - ...componentMeta.definition.properties, - ...currentComponentData?.component.definition.properties, - }, - - styles: { - ...componentMeta.definition.styles, - ...currentComponentData?.component.definition.styles, - }, - validations: { - ...componentMeta.definition.validations, - ...currentComponentData?.component.definition.validations, - }, - }; - - const mergedComponent = { - component: { - ...componentMeta, - ...currentComponentData.component, - }, - layouts: { - ...currentComponentData.layouts, - }, - withDefaultChildren: componentMeta.withDefaultChildren ?? false, - }; - - mergedComponent.component.definition = mergedDefinition; - - components[componentId] = mergedComponent; - } - - return components; - }; - - const buildAppDefinition = (data) => { - const editingVersion = _.omit(camelizeKeys(data.editing_version), ['definition', 'updatedAt', 'createdAt', 'name']); - - editingVersion['currentVersionId'] = editingVersion.id; - _.unset(editingVersion, 'id'); - - const eventsData = data?.events; - - const pages = data.pages.reduce((acc, page) => { - const currentComponents = buildComponentMetaDefinition(_.cloneDeep(page?.components), eventsData); - - page.components = currentComponents; - - acc[page.id] = page; - - return acc; - }, {}); - - const appJSON = { - globalSettings: editingVersion.globalSettings, - homePageId: editingVersion.homePageId, - showHideViewerNavigation: editingVersion.showHideViewerNavigation ?? true, - pages: pages, - }; - - return appJSON; - }; - //****** */ - const fetchApp = async (startingPageHandle) => { + const fetchApp = async (startingPageHandle, onMount = false) => { const _appId = props?.params?.id; const callBack = async (data) => { useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version); useAppVersionStore.getState().actions.updateReleasedVersionId(data.current_version_id); + + const appVersions = await appEnvironmentService.getVersionsByEnvironment(data?.id); + setAppVersions(appVersions.appVersions); + + updateState({ + slug: data.slug, + isMaintenanceOn: data?.is_maintenance_on, + organizationId: data?.organization_id, + isPublic: data?.is_public, + appName: data?.name, + userId: data?.user_id, + appId: data?.id, + events: data.events, + }); + await fetchDataSources(data.editing_version?.id); await fetchDataQueries(data.editing_version?.id, true, true); + const appDefData = buildAppDefinition(data); const appJson = appDefData; const pages = data.pages; - const events = data.events; const startingPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id; const homePageId = !startingPageId || startingPageId === 'null' ? appJson.homePageId : startingPageId; @@ -705,18 +743,6 @@ const EditorComponent = (props) => { setCurrentPageId(homePageId); - updateState({ - app: data, - slug: data.slug, - isMaintenanceOn: data?.is_maintenance_on, - organizationId: data?.organization_id, - isPublic: data?.is_public, - appName: data?.name, - userId: data?.user_id, - appId: data?.id, - events: events, - }); - useCurrentStateStore.getState().actions.setCurrentState({ page: currentpageData, }); @@ -729,17 +755,13 @@ const EditorComponent = (props) => { for (const event of appJson.pages[homePageId]?.events ?? []) { await handleEvent(event.eventId, event); } - getCanvasWidth(); }; - updateState({ - isLoading: true, - }); - - await appService - .getApp(_appId) - .then(callBack) - .finally(() => initEditorWalkThrough()); + if (!onMount) { + await appService.getApp(_appId).then(callBack); + } else { + callBack(app); + } }; // !-------- @@ -1498,12 +1520,27 @@ const EditorComponent = (props) => { : ''; const deviceWindowWidth = 450; - if (!appDefinition?.homePageId) { + if (isLoading) { return ( -
-
-
{/* */}
-
Loading...
+
+
+
+
+
+
+ +
+ {Array.from(Array(4)).map((_item, index) => ( + + ))} +
+ +
+ + +
+
+
); @@ -1541,6 +1578,10 @@ const EditorComponent = (props) => { onVersionRelease={onVersionRelease} saveEditingVersion={saveEditingVersion} onVersionDelete={onVersionDelete} + isMaintenanceOn={isMaintenanceOn} + appName={appName} + appId={appId} + slug={slug} />
@@ -1558,9 +1599,9 @@ const EditorComponent = (props) => { appDefinition={{ components: appDefinition?.pages[currentPageId]?.components ?? {}, selectedComponent: selectedComponents ? selectedComponents[selectedComponents.length - 1] : {}, - pages: appDefinition?.pages, - homePageId: appDefinition.homePageId, - showViewerNavigation: appDefinition.showViewerNavigation, + pages: appDefinition?.pages ?? {}, + homePageId: appDefinition?.homePageId ?? null, + showViewerNavigation: appDefinition?.showViewerNavigation, }} setSelectedComponent={setSelectedComponent} removeComponent={removeComponent} diff --git a/frontend/src/Editor/Header/index.js b/frontend/src/Editor/Header/index.js index 06200fa4c0..37e83e691c 100644 --- a/frontend/src/Editor/Header/index.js +++ b/frontend/src/Editor/Header/index.js @@ -14,7 +14,7 @@ import config from 'config'; import { useUpdatePresence } from '@y-presence/react'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import { shallow } from 'zustand/shallow'; -import { useAppDataActions, useAppInfo, useCurrentUser } from '@/_stores/appDataStore'; +import { useAppDataActions, useCurrentUser } from '@/_stores/appDataStore'; export default function EditorHeader({ darkMode, @@ -34,11 +34,13 @@ export default function EditorHeader({ onVersionRelease, saveEditingVersion, onVersionDelete, + isMaintenanceOn, + appName, + appId, + slug, }) { const currentUser = useCurrentUser(); - const { isMaintenanceOn, appName, appId, slug } = useAppInfo(); - const { updateState } = useAppDataActions(); const handleSlugChange = (newSlug) => { @@ -54,6 +56,7 @@ export default function EditorHeader({ ); const updatePresence = useUpdatePresence(); + useEffect(() => { const initialPresence = { firstName: currentUser?.first_name ?? '', diff --git a/frontend/src/_stores/appVersionStore.js b/frontend/src/_stores/appVersionStore.js index 38e456479f..74f1f7909c 100644 --- a/frontend/src/_stores/appVersionStore.js +++ b/frontend/src/_stores/appVersionStore.js @@ -5,6 +5,7 @@ const initialState = { isUserEditingTheVersion: false, releasedVersionId: null, isVersionReleased: false, + appVersions: [], }; export const useAppVersionStore = create( @@ -21,8 +22,11 @@ export const useAppVersionStore = create( 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);