2021-04-02 11:09:55 +00:00
|
|
|
import React from 'react';
|
2022-11-14 06:37:47 +00:00
|
|
|
import { appService, authenticationService, orgEnvironmentVariableService, organizationService } from '@/_services';
|
2021-04-30 06:31:32 +00:00
|
|
|
import { DndProvider } from 'react-dnd';
|
|
|
|
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
2021-04-02 11:09:55 +00:00
|
|
|
import { Container } from './Container';
|
2021-04-10 04:33:00 +00:00
|
|
|
import { Confirm } from './Viewer/Confirm';
|
2022-12-08 12:21:09 +00:00
|
|
|
import { ViewerNavigation } from './Viewer/ViewerNavigation';
|
2021-04-30 06:31:32 +00:00
|
|
|
import {
|
|
|
|
|
onComponentOptionChanged,
|
|
|
|
|
onComponentOptionsChanged,
|
|
|
|
|
onComponentClick,
|
2022-09-27 05:33:30 +00:00
|
|
|
onQueryConfirmOrCancel,
|
2021-04-30 06:31:32 +00:00
|
|
|
onEvent,
|
2021-08-30 11:43:27 +00:00
|
|
|
runQuery,
|
2021-09-21 13:48:28 +00:00
|
|
|
computeComponentState,
|
2021-04-26 05:32:02 +00:00
|
|
|
} from '@/_helpers/appUtils';
|
2021-06-27 07:29:55 +00:00
|
|
|
import queryString from 'query-string';
|
2022-11-14 06:37:47 +00:00
|
|
|
import ViewerLogoIcon from './Icons/viewer-logo.svg';
|
2022-01-21 11:12:33 +00:00
|
|
|
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
|
2023-04-06 11:12:58 +00:00
|
|
|
import {
|
|
|
|
|
resolveReferences,
|
|
|
|
|
safelyParseJSON,
|
|
|
|
|
stripTrailingSlash,
|
|
|
|
|
getSubpath,
|
|
|
|
|
excludeWorkspaceIdFromURL,
|
|
|
|
|
} from '@/_helpers/utils';
|
2022-09-14 08:04:49 +00:00
|
|
|
import { withTranslation } from 'react-i18next';
|
2022-12-08 12:21:09 +00:00
|
|
|
import _ from 'lodash';
|
2023-03-20 11:34:24 +00:00
|
|
|
import { Navigate } from 'react-router-dom';
|
2022-11-14 06:37:47 +00:00
|
|
|
import Spinner from '@/_ui/Spinner';
|
2022-12-20 09:23:59 +00:00
|
|
|
import { toast } from 'react-hot-toast';
|
2023-03-20 11:34:24 +00:00
|
|
|
import { withRouter } from '@/_hoc/withRouter';
|
2022-09-14 08:04:49 +00:00
|
|
|
|
2023-05-10 10:14:38 +00:00
|
|
|
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
|
|
|
|
|
2022-09-14 08:04:49 +00:00
|
|
|
class ViewerComponent extends React.Component {
|
2021-04-30 06:31:32 +00:00
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
2021-04-02 11:09:55 +00:00
|
|
|
|
2021-08-30 11:43:27 +00:00
|
|
|
const deviceWindowWidth = window.screen.width - 5;
|
|
|
|
|
const isMobileDevice = deviceWindowWidth < 600;
|
|
|
|
|
|
2023-03-20 11:34:24 +00:00
|
|
|
const pageHandle = this.props?.params?.pageHandle;
|
2022-12-08 12:21:09 +00:00
|
|
|
|
2023-03-20 11:34:24 +00:00
|
|
|
const slug = this.props.params.slug;
|
|
|
|
|
const appId = this.props.params.id;
|
|
|
|
|
const versionId = this.props.params.versionId;
|
2022-12-08 12:21:09 +00:00
|
|
|
|
2023-04-06 11:12:58 +00:00
|
|
|
this.subscription = null;
|
|
|
|
|
|
2021-04-30 06:31:32 +00:00
|
|
|
this.state = {
|
2022-12-08 12:21:09 +00:00
|
|
|
slug,
|
|
|
|
|
appId,
|
|
|
|
|
versionId,
|
2021-08-30 11:43:27 +00:00
|
|
|
deviceWindowWidth,
|
|
|
|
|
currentLayout: isMobileDevice ? 'mobile' : 'desktop',
|
2023-04-06 11:12:58 +00:00
|
|
|
currentUser: null,
|
2021-08-30 11:43:27 +00:00
|
|
|
isLoading: true,
|
2021-04-30 06:31:32 +00:00
|
|
|
users: null,
|
2022-12-08 12:21:09 +00:00
|
|
|
appDefinition: { pages: {} },
|
2021-04-30 06:31:32 +00:00
|
|
|
currentState: {
|
|
|
|
|
queries: {},
|
|
|
|
|
components: {},
|
|
|
|
|
globals: {
|
2022-01-21 20:01:26 +00:00
|
|
|
currentUser: {},
|
2022-03-25 10:15:44 +00:00
|
|
|
theme: { name: props.darkMode ? 'dark' : 'light' },
|
2021-08-30 11:43:27 +00:00
|
|
|
urlparams: {},
|
2022-07-01 10:50:37 +00:00
|
|
|
environment_variables: {},
|
2022-12-08 12:21:09 +00:00
|
|
|
page: {
|
|
|
|
|
handle: pageHandle,
|
|
|
|
|
},
|
2021-08-30 11:43:27 +00:00
|
|
|
},
|
2022-12-08 12:21:09 +00:00
|
|
|
variables: {},
|
2021-08-30 11:43:27 +00:00
|
|
|
},
|
2022-09-27 05:33:30 +00:00
|
|
|
queryConfirmationList: [],
|
2022-08-24 10:31:09 +00:00
|
|
|
isAppLoaded: false,
|
2022-11-14 06:37:47 +00:00
|
|
|
errorAppId: null,
|
|
|
|
|
errorVersionId: null,
|
|
|
|
|
errorDetails: null,
|
2022-12-08 12:21:09 +00:00
|
|
|
pages: {},
|
2021-04-30 06:31:32 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 11:43:27 +00:00
|
|
|
setStateForApp = (data) => {
|
2022-12-08 12:21:09 +00:00
|
|
|
const copyDefinition = _.cloneDeep(data.definition);
|
|
|
|
|
const pagesObj = copyDefinition.pages || {};
|
|
|
|
|
|
|
|
|
|
const newDefinition = {
|
|
|
|
|
...copyDefinition,
|
|
|
|
|
pages: pagesObj,
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-06 13:17:56 +00:00
|
|
|
this.setState({
|
|
|
|
|
app: data,
|
|
|
|
|
isLoading: false,
|
2022-08-24 10:31:09 +00:00
|
|
|
isAppLoaded: true,
|
2022-12-08 12:21:09 +00:00
|
|
|
appDefinition: newDefinition || { components: {} },
|
2021-10-06 13:17:56 +00:00
|
|
|
});
|
2021-08-30 11:43:27 +00:00
|
|
|
};
|
2021-04-02 11:09:55 +00:00
|
|
|
|
2022-07-01 10:50:37 +00:00
|
|
|
setStateForContainer = async (data) => {
|
2023-04-06 11:12:58 +00:00
|
|
|
const currentUser = this.state.currentUser;
|
2021-08-30 11:43:27 +00:00
|
|
|
let userVars = {};
|
2021-05-07 08:25:09 +00:00
|
|
|
|
2021-06-22 14:33:13 +00:00
|
|
|
if (currentUser) {
|
2021-05-07 08:25:09 +00:00
|
|
|
userVars = {
|
|
|
|
|
email: currentUser.email,
|
|
|
|
|
firstName: currentUser.first_name,
|
2021-08-30 11:43:27 +00:00
|
|
|
lastName: currentUser.last_name,
|
2023-04-06 11:12:58 +00:00
|
|
|
groups: authenticationService.currentSessionValue?.group_permissions.map((group) => group.group),
|
2021-05-07 08:25:09 +00:00
|
|
|
};
|
|
|
|
|
}
|
2021-12-03 12:57:53 +00:00
|
|
|
|
|
|
|
|
let mobileLayoutHasWidgets = false;
|
|
|
|
|
|
|
|
|
|
if (this.state.currentLayout === 'mobile') {
|
2022-12-08 12:21:09 +00:00
|
|
|
const currentComponents = data.definition.pages[data.definition.homePageId].components;
|
2021-12-08 08:25:22 +00:00
|
|
|
mobileLayoutHasWidgets =
|
2022-12-08 12:21:09 +00:00
|
|
|
Object.keys(currentComponents).filter((componentId) => currentComponents[componentId]['layouts']['mobile'])
|
|
|
|
|
.length > 0;
|
2021-12-03 12:57:53 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 11:12:33 +00:00
|
|
|
let queryState = {};
|
|
|
|
|
data.data_queries.forEach((query) => {
|
2023-03-16 12:47:25 +00:00
|
|
|
if (query.pluginId || query?.plugin?.id) {
|
2022-10-27 11:29:43 +00:00
|
|
|
queryState[query.name] = {
|
2022-11-08 12:00:41 +00:00
|
|
|
...query.plugin.manifestFile.data.source.exposedVariables,
|
2022-10-27 11:29:43 +00:00
|
|
|
...this.state.currentState.queries[query.name],
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
queryState[query.name] = {
|
|
|
|
|
...DataSourceTypes.find((source) => source.kind === query.kind).exposedVariables,
|
|
|
|
|
...this.state.currentState.queries[query.name],
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-01-21 11:12:33 +00:00
|
|
|
});
|
|
|
|
|
|
2022-07-25 10:48:12 +00:00
|
|
|
const variables = await this.fetchOrgEnvironmentVariables(data.slug, data.is_public);
|
2022-07-01 10:50:37 +00:00
|
|
|
|
2022-12-08 12:21:09 +00:00
|
|
|
const pages = Object.entries(data.definition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
|
|
|
|
|
const homePageId = data.definition.homePageId;
|
2023-03-20 11:34:24 +00:00
|
|
|
const startingPageHandle = this.props?.params?.pageHandle;
|
2022-12-08 12:21:09 +00:00
|
|
|
const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId;
|
|
|
|
|
const currentPage = pages.find((page) => page.id === currentPageId);
|
|
|
|
|
|
2023-05-10 10:14:38 +00:00
|
|
|
useDataQueriesStore.getState().actions.setDataQueries(data.data_queries);
|
|
|
|
|
|
2021-09-21 13:48:28 +00:00
|
|
|
this.setState(
|
|
|
|
|
{
|
2023-04-06 11:12:58 +00:00
|
|
|
currentUser,
|
2021-09-21 13:48:28 +00:00
|
|
|
currentSidebarTab: 2,
|
2021-12-03 12:57:53 +00:00
|
|
|
currentLayout: mobileLayoutHasWidgets ? 'mobile' : 'desktop',
|
2021-12-08 08:25:22 +00:00
|
|
|
canvasWidth:
|
|
|
|
|
this.state.currentLayout === 'desktop'
|
|
|
|
|
? '100%'
|
|
|
|
|
: mobileLayoutHasWidgets
|
|
|
|
|
? `${this.state.deviceWindowWidth}px`
|
|
|
|
|
: '1292px',
|
2021-09-21 13:48:28 +00:00
|
|
|
selectedComponent: null,
|
|
|
|
|
currentState: {
|
2022-01-21 11:12:33 +00:00
|
|
|
queries: queryState,
|
2021-09-21 13:48:28 +00:00
|
|
|
components: {},
|
|
|
|
|
globals: {
|
2023-04-14 10:08:13 +00:00
|
|
|
currentUser: userVars, // currentUser is updated in setupViewer function as well
|
2022-03-25 10:15:44 +00:00
|
|
|
theme: { name: this.props.darkMode ? 'dark' : 'light' },
|
2021-09-21 13:48:28 +00:00
|
|
|
urlparams: JSON.parse(JSON.stringify(queryString.parse(this.props.location.search))),
|
|
|
|
|
},
|
2022-12-08 12:21:09 +00:00
|
|
|
variables: {},
|
|
|
|
|
page: {
|
2023-01-07 05:43:33 +00:00
|
|
|
id: currentPage.id,
|
2022-12-08 12:21:09 +00:00
|
|
|
handle: currentPage.handle,
|
|
|
|
|
name: currentPage.name,
|
|
|
|
|
variables: {},
|
|
|
|
|
},
|
2022-07-01 10:50:37 +00:00
|
|
|
...variables,
|
2021-08-30 11:43:27 +00:00
|
|
|
},
|
2022-05-10 09:39:09 +00:00
|
|
|
dataQueries: data.data_queries,
|
2022-12-08 12:21:09 +00:00
|
|
|
currentPageId: currentPage.id,
|
|
|
|
|
pages: {},
|
2021-08-30 11:43:27 +00:00
|
|
|
},
|
2021-09-21 13:48:28 +00:00
|
|
|
() => {
|
2022-12-13 06:34:11 +00:00
|
|
|
computeComponentState(this, data?.definition?.pages[currentPage.id]?.components).then(async () => {
|
2022-12-13 05:35:13 +00:00
|
|
|
this.setState({ initialComputationOfStateDone: true });
|
2021-10-06 13:17:56 +00:00
|
|
|
console.log('Default component state computed and set');
|
|
|
|
|
this.runQueries(data.data_queries);
|
2023-03-20 11:34:24 +00:00
|
|
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
2022-12-13 06:34:11 +00:00
|
|
|
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
|
|
|
|
|
for (const event of events ?? []) {
|
|
|
|
|
await this.handleEvent(event.eventId, event);
|
|
|
|
|
}
|
2021-10-06 13:17:56 +00:00
|
|
|
});
|
2021-09-21 13:48:28 +00:00
|
|
|
}
|
|
|
|
|
);
|
2021-08-30 11:43:27 +00:00
|
|
|
};
|
|
|
|
|
|
2021-10-06 13:17:56 +00:00
|
|
|
runQueries = (data_queries) => {
|
|
|
|
|
data_queries.forEach((query) => {
|
|
|
|
|
if (query.options.runOnPageLoad) {
|
2022-08-16 10:38:36 +00:00
|
|
|
runQuery(this, query.id, query.name, undefined, 'view');
|
2021-10-06 13:17:56 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-25 10:48:12 +00:00
|
|
|
fetchOrgEnvironmentVariables = async (slug, isPublic) => {
|
2022-07-01 10:50:37 +00:00
|
|
|
const variables = {
|
|
|
|
|
client: {},
|
|
|
|
|
server: {},
|
|
|
|
|
};
|
2022-07-25 10:48:12 +00:00
|
|
|
|
|
|
|
|
let variablesResult;
|
|
|
|
|
if (!isPublic) {
|
|
|
|
|
variablesResult = await orgEnvironmentVariableService.getVariables();
|
|
|
|
|
} else {
|
|
|
|
|
variablesResult = await orgEnvironmentVariableService.getVariablesFromPublicApp(slug);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variablesResult.variables.map((variable) => {
|
|
|
|
|
variables[variable.variable_type][variable.variable_name] =
|
|
|
|
|
variable.variable_type === 'server' ? 'HiddenEnvironmentVariable' : variable.value;
|
2022-07-01 10:50:37 +00:00
|
|
|
});
|
|
|
|
|
return variables;
|
|
|
|
|
};
|
|
|
|
|
|
2021-08-30 11:43:27 +00:00
|
|
|
loadApplicationBySlug = (slug) => {
|
2022-11-14 06:37:47 +00:00
|
|
|
appService
|
|
|
|
|
.getAppBySlug(slug)
|
|
|
|
|
.then((data) => {
|
|
|
|
|
this.setStateForApp(data);
|
|
|
|
|
this.setStateForContainer(data);
|
|
|
|
|
this.setWindowTitle(data.name);
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorDetails: error,
|
|
|
|
|
errorAppId: slug,
|
|
|
|
|
errorVersionId: null,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-08-30 11:43:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadApplicationByVersion = (appId, versionId) => {
|
2022-11-14 06:37:47 +00:00
|
|
|
appService
|
|
|
|
|
.getAppByVersion(appId, versionId)
|
|
|
|
|
.then((data) => {
|
|
|
|
|
this.setStateForApp(data);
|
|
|
|
|
this.setStateForContainer(data);
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
errorDetails: error,
|
|
|
|
|
errorAppId: appId,
|
|
|
|
|
errorVersionId: versionId,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switchOrganization = (orgId, appId, versionId) => {
|
|
|
|
|
const path = `/applications/${appId}${versionId ? `/versions/${versionId}` : ''}`;
|
|
|
|
|
const sub_path = window?.public_config?.SUB_PATH ? stripTrailingSlash(window?.public_config?.SUB_PATH) : '';
|
|
|
|
|
|
|
|
|
|
organizationService.switchOrganization(orgId).then(
|
2023-04-06 11:12:58 +00:00
|
|
|
() => {
|
2022-11-14 06:37:47 +00:00
|
|
|
window.location.href = `${sub_path}${path}`;
|
|
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
return (window.location.href = `${sub_path}/login/${orgId}?redirectTo=${path}`);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handleError = (errorDetails, appId, versionId) => {
|
|
|
|
|
try {
|
|
|
|
|
if (errorDetails?.data) {
|
|
|
|
|
const statusCode = errorDetails.data?.statusCode;
|
|
|
|
|
if (statusCode === 403) {
|
|
|
|
|
const errorObj = safelyParseJSON(errorDetails.data?.message);
|
2023-04-06 11:12:58 +00:00
|
|
|
const currentSessionValue = authenticationService.currentSessionValue;
|
2022-11-14 06:37:47 +00:00
|
|
|
if (
|
|
|
|
|
errorObj?.organizationId &&
|
|
|
|
|
this.state.currentUser &&
|
2023-04-06 11:12:58 +00:00
|
|
|
currentSessionValue.current_organization_id !== errorObj?.organizationId
|
2022-11-14 06:37:47 +00:00
|
|
|
) {
|
|
|
|
|
this.switchOrganization(errorObj?.organizationId, appId, versionId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-03-20 11:34:24 +00:00
|
|
|
return <Navigate replace to={'/'} />;
|
2022-12-20 09:23:59 +00:00
|
|
|
} else if (statusCode === 401) {
|
2023-04-06 11:12:58 +00:00
|
|
|
window.location = `${getSubpath() ?? ''}/login?redirectTo=${this.props.location.pathname}`;
|
2022-12-20 09:23:59 +00:00
|
|
|
} else if (statusCode === 404) {
|
|
|
|
|
toast.error(errorDetails?.error ?? 'App not found', {
|
|
|
|
|
position: 'top-center',
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-03-20 11:34:24 +00:00
|
|
|
return <Navigate replace to={'/'} />;
|
2022-11-14 06:37:47 +00:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
2023-03-20 11:34:24 +00:00
|
|
|
return <Navigate replace to={'/'} />;
|
2022-11-14 06:37:47 +00:00
|
|
|
}
|
2021-08-30 11:43:27 +00:00
|
|
|
};
|
2021-04-04 13:11:17 +00:00
|
|
|
|
2023-04-06 11:12:58 +00:00
|
|
|
setupViewer() {
|
2023-03-20 11:34:24 +00:00
|
|
|
const slug = this.props.params.slug;
|
|
|
|
|
const appId = this.props.params.id;
|
|
|
|
|
const versionId = this.props.params.versionId;
|
2021-08-30 11:43:27 +00:00
|
|
|
|
2023-04-06 11:12:58 +00:00
|
|
|
this.subscription = authenticationService.currentSession.subscribe((currentSession) => {
|
2023-04-14 10:08:13 +00:00
|
|
|
if (currentSession?.load_app) {
|
|
|
|
|
if (currentSession?.group_permissions) {
|
|
|
|
|
const currentUser = currentSession.current_user;
|
|
|
|
|
const userVars = {
|
|
|
|
|
email: currentUser.email,
|
|
|
|
|
firstName: currentUser.first_name,
|
|
|
|
|
lastName: currentUser.last_name,
|
|
|
|
|
groups: currentSession?.group_permissions?.map((group) => group.group),
|
|
|
|
|
};
|
2023-04-06 11:12:58 +00:00
|
|
|
|
2023-04-14 10:08:13 +00:00
|
|
|
this.setState({
|
|
|
|
|
currentUser,
|
|
|
|
|
currentState: {
|
|
|
|
|
...this.state.currentState,
|
|
|
|
|
globals: {
|
|
|
|
|
...this.state.currentState.globals,
|
|
|
|
|
currentUser: userVars, // currentUser is updated in setStateForContainer function as well
|
|
|
|
|
},
|
2023-04-06 11:12:58 +00:00
|
|
|
},
|
2023-04-14 10:08:13 +00:00
|
|
|
userVars,
|
|
|
|
|
});
|
|
|
|
|
slug ? this.loadApplicationBySlug(slug) : this.loadApplicationByVersion(appId, versionId);
|
|
|
|
|
} else if (currentSession?.authentication_failed && !slug) {
|
|
|
|
|
const loginPath = (window.public_config?.SUB_PATH || '/') + 'login';
|
|
|
|
|
const pathname = getSubpath() ? window.location.pathname.replace(getSubpath(), '') : window.location.pathname;
|
|
|
|
|
window.location.href = loginPath + `?redirectTo=${excludeWorkspaceIdFromURL(pathname)}`;
|
|
|
|
|
} else {
|
|
|
|
|
slug && this.loadApplicationBySlug(slug);
|
|
|
|
|
}
|
2023-04-06 11:12:58 +00:00
|
|
|
}
|
|
|
|
|
this.setState({ isLoading: false });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
this.setupViewer();
|
2021-06-28 06:36:23 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-15 15:58:15 +00:00
|
|
|
componentDidUpdate(prevProps) {
|
2023-03-20 11:34:24 +00:00
|
|
|
if (this.props.params.slug && this.props.params.slug !== prevProps.params.slug) {
|
2021-09-15 15:58:15 +00:00
|
|
|
this.setState({ isLoading: true });
|
2023-03-20 11:34:24 +00:00
|
|
|
this.loadApplicationBySlug(this.props.params.slug);
|
2021-09-15 15:58:15 +00:00
|
|
|
}
|
2022-12-08 12:21:09 +00:00
|
|
|
|
2022-12-13 05:35:13 +00:00
|
|
|
if (this.state.initialComputationOfStateDone) this.handlePageSwitchingBasedOnURLparam();
|
2022-12-08 12:21:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handlePageSwitchingBasedOnURLparam() {
|
2023-03-20 11:34:24 +00:00
|
|
|
const handleOnURL = this.props.params.pageHandle;
|
2022-12-08 12:21:09 +00:00
|
|
|
const pageIdCorrespondingToHandleOnURL = handleOnURL
|
|
|
|
|
? this.findPageIdFromHandle(handleOnURL)
|
|
|
|
|
: this.state.appDefinition.homePageId;
|
|
|
|
|
const currentPageId = this.state.currentPageId;
|
|
|
|
|
|
|
|
|
|
if (pageIdCorrespondingToHandleOnURL != this.state.currentPageId) {
|
|
|
|
|
const targetPage = this.state.appDefinition.pages[pageIdCorrespondingToHandleOnURL];
|
|
|
|
|
this.setState(
|
|
|
|
|
{
|
|
|
|
|
pages: {
|
|
|
|
|
...this.state.pages,
|
|
|
|
|
[currentPageId]: {
|
|
|
|
|
...this.state.pages?.[currentPageId],
|
|
|
|
|
variables: {
|
|
|
|
|
...this.state.currentState?.page?.variables,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
currentPageId: pageIdCorrespondingToHandleOnURL,
|
|
|
|
|
handle: targetPage.handle,
|
|
|
|
|
name: targetPage.name,
|
|
|
|
|
currentState: {
|
|
|
|
|
...this.state.currentState,
|
|
|
|
|
globals: {
|
|
|
|
|
...this.state.currentState.globals,
|
|
|
|
|
urlparams: JSON.parse(JSON.stringify(queryString.parse(this.props.location.search))),
|
|
|
|
|
},
|
|
|
|
|
page: {
|
|
|
|
|
...this.state.currentState.page,
|
|
|
|
|
name: targetPage.name,
|
|
|
|
|
handle: targetPage.handle,
|
|
|
|
|
variables: this.state.pages?.[pageIdCorrespondingToHandleOnURL]?.variables ?? {},
|
2023-01-07 05:43:33 +00:00
|
|
|
id: pageIdCorrespondingToHandleOnURL,
|
2022-12-08 12:21:09 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async () => {
|
|
|
|
|
computeComponentState(this, this.state.appDefinition?.pages[this.state.currentPageId].components).then(
|
|
|
|
|
async () => {
|
2023-03-20 11:34:24 +00:00
|
|
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
2022-12-08 12:21:09 +00:00
|
|
|
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
|
|
|
|
|
for (const event of events ?? []) {
|
|
|
|
|
await this.handleEvent(event.eventId, event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
findPageIdFromHandle(handle) {
|
2023-01-02 12:06:48 +00:00
|
|
|
return (
|
|
|
|
|
Object.entries(this.state.appDefinition.pages).filter(([_id, page]) => page.handle === handle)?.[0]?.[0] ??
|
|
|
|
|
this.state.appDefinition.homePageId
|
|
|
|
|
);
|
2021-09-15 15:58:15 +00:00
|
|
|
}
|
|
|
|
|
|
2021-11-16 11:44:09 +00:00
|
|
|
getCanvasWidth = () => {
|
2023-06-21 12:17:31 +00:00
|
|
|
const canvasBoundingRect = document.getElementsByClassName('canvas-area')[0]?.getBoundingClientRect();
|
2021-11-16 11:44:09 +00:00
|
|
|
return canvasBoundingRect?.width;
|
2021-12-08 08:25:22 +00:00
|
|
|
};
|
2021-11-16 11:44:09 +00:00
|
|
|
|
2022-02-11 12:02:32 +00:00
|
|
|
setWindowTitle(name) {
|
2023-06-06 08:42:36 +00:00
|
|
|
document.title = name ?? 'My App';
|
2022-02-11 12:02:32 +00:00
|
|
|
}
|
|
|
|
|
|
2022-09-09 16:31:27 +00:00
|
|
|
computeCanvasBackgroundColor = () => {
|
2022-12-27 14:40:33 +00:00
|
|
|
const bgColor =
|
|
|
|
|
(this.state.appDefinition.globalSettings?.backgroundFxQuery ||
|
|
|
|
|
this.state.appDefinition.globalSettings?.canvasBackgroundColor) ??
|
2022-09-09 16:31:27 +00:00
|
|
|
'#edeff5';
|
2022-12-27 14:40:33 +00:00
|
|
|
const resolvedBackgroundColor = resolveReferences(bgColor, this.state.currentState);
|
2022-09-09 16:31:27 +00:00
|
|
|
if (['#2f3c4c', '#edeff5'].includes(resolvedBackgroundColor)) {
|
|
|
|
|
return this.props.darkMode ? '#2f3c4c' : '#edeff5';
|
|
|
|
|
}
|
|
|
|
|
return resolvedBackgroundColor;
|
|
|
|
|
};
|
|
|
|
|
|
2022-03-25 10:15:44 +00:00
|
|
|
changeDarkMode = (newMode) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
currentState: {
|
|
|
|
|
...this.state.currentState,
|
|
|
|
|
globals: {
|
|
|
|
|
...this.state.currentState.globals,
|
|
|
|
|
theme: { name: newMode ? 'dark' : 'light' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
showQuerySearchField: false,
|
|
|
|
|
});
|
|
|
|
|
this.props.switchDarkMode(newMode);
|
|
|
|
|
};
|
|
|
|
|
|
2022-12-08 12:21:09 +00:00
|
|
|
switchPage = (id, queryParams = []) => {
|
2023-05-10 05:40:35 +00:00
|
|
|
document.getElementById('real-canvas').scrollIntoView();
|
|
|
|
|
|
2023-01-09 12:11:39 +00:00
|
|
|
if (this.state.currentPageId === id) return;
|
|
|
|
|
|
2023-02-02 15:28:06 +00:00
|
|
|
const { handle } = this.state.appDefinition.pages[id];
|
2022-12-08 12:21:09 +00:00
|
|
|
|
|
|
|
|
const queryParamsString = queryParams.map(([key, value]) => `${key}=${value}`).join('&');
|
|
|
|
|
|
2023-03-20 11:34:24 +00:00
|
|
|
if (this.state.slug) this.props.navigate(`/applications/${this.state.slug}/${handle}?${queryParamsString}`);
|
2022-12-08 12:21:09 +00:00
|
|
|
else
|
2023-03-20 11:34:24 +00:00
|
|
|
this.props.navigate(
|
2022-12-08 12:21:09 +00:00
|
|
|
`/applications/${this.state.appId}/versions/${this.state.versionId}/${handle}?${queryParamsString}`
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handleEvent = (eventName, options) => onEvent(this, eventName, options, 'view');
|
|
|
|
|
|
|
|
|
|
computeCanvasMaxWidth = () => {
|
|
|
|
|
const { appDefinition } = this.state;
|
|
|
|
|
let computedCanvasMaxWidth = 1292;
|
|
|
|
|
|
|
|
|
|
if (appDefinition.globalSettings?.canvasMaxWidthType === 'px')
|
|
|
|
|
computedCanvasMaxWidth =
|
|
|
|
|
(+appDefinition.globalSettings?.canvasMaxWidth || 1292) - (appDefinition?.showViewerNavigation ? 200 : 0);
|
|
|
|
|
else if (appDefinition.globalSettings?.canvasMaxWidthType === '%')
|
|
|
|
|
computedCanvasMaxWidth = +appDefinition.globalSettings?.canvasMaxWidth + '%';
|
|
|
|
|
|
|
|
|
|
return computedCanvasMaxWidth;
|
|
|
|
|
};
|
|
|
|
|
|
2023-04-06 11:12:58 +00:00
|
|
|
componentWillUnmount() {
|
|
|
|
|
this.subscription && this.subscription.unsubscribe();
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-30 06:31:32 +00:00
|
|
|
render() {
|
2021-09-21 13:48:28 +00:00
|
|
|
const {
|
|
|
|
|
appDefinition,
|
|
|
|
|
isLoading,
|
2022-08-24 10:31:09 +00:00
|
|
|
isAppLoaded,
|
2021-09-21 13:48:28 +00:00
|
|
|
currentLayout,
|
|
|
|
|
deviceWindowWidth,
|
|
|
|
|
defaultComponentStateComputed,
|
2022-05-10 09:39:09 +00:00
|
|
|
dataQueries,
|
2022-09-27 05:33:30 +00:00
|
|
|
queryConfirmationList,
|
2022-11-14 06:37:47 +00:00
|
|
|
errorAppId,
|
|
|
|
|
errorVersionId,
|
|
|
|
|
errorDetails,
|
2022-12-08 12:21:09 +00:00
|
|
|
canvasWidth,
|
2021-09-21 13:48:28 +00:00
|
|
|
} = this.state;
|
2022-11-14 06:37:47 +00:00
|
|
|
|
2022-12-08 12:21:09 +00:00
|
|
|
const currentCanvasWidth = canvasWidth;
|
|
|
|
|
|
|
|
|
|
const canvasMaxWidth = this.computeCanvasMaxWidth();
|
|
|
|
|
|
|
|
|
|
if (this.state.app?.isLoading) {
|
2022-04-21 16:38:54 +00:00
|
|
|
return (
|
2022-11-14 06:37:47 +00:00
|
|
|
<div className="tooljet-logo-loader">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="loader-logo">
|
|
|
|
|
<ViewerLogoIcon />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="loader-spinner">
|
|
|
|
|
<Spinner />
|
2021-12-09 07:00:58 +00:00
|
|
|
</div>
|
2022-04-21 16:38:54 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2022-11-14 06:37:47 +00:00
|
|
|
if (this.state.app?.is_maintenance_on) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="maintenance_container">
|
|
|
|
|
<div className="card">
|
|
|
|
|
<div className="card-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
<h3>{this.props.t('viewer', 'Sorry!. This app is under maintenance')}</h3>
|
2022-04-21 16:38:54 +00:00
|
|
|
</div>
|
2022-11-14 06:37:47 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2022-12-19 12:55:46 +00:00
|
|
|
if (errorDetails) {
|
|
|
|
|
this.handleError(errorDetails, errorAppId, errorVersionId);
|
|
|
|
|
}
|
2023-05-30 03:53:21 +00:00
|
|
|
|
|
|
|
|
const pageArray = Object.values(this.state.appDefinition?.pages || {});
|
|
|
|
|
//checking if page is hidden
|
|
|
|
|
if (
|
|
|
|
|
pageArray.find((page) => page.handle === this.props.params.pageHandle)?.hidden &&
|
2023-06-21 12:17:31 +00:00
|
|
|
this.state.currentPageId !== this.state.appDefinition?.homePageId && //Prevent page crashing when home page is hidden
|
2023-05-30 03:53:21 +00:00
|
|
|
this.state.appDefinition?.pages?.[this.state.appDefinition?.homePageId]
|
|
|
|
|
) {
|
|
|
|
|
const homeHandle = this.state.appDefinition?.pages?.[this.state.appDefinition?.homePageId]?.handle;
|
|
|
|
|
let url = `/applications/${this.state.appId}/versions/${this.state.versionId}/${homeHandle}`;
|
|
|
|
|
if (this.state.slug) {
|
|
|
|
|
url = `/applications/${this.state.slug}/${homeHandle}`;
|
|
|
|
|
}
|
|
|
|
|
return <Navigate to={url} replace />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//checking if page exists
|
|
|
|
|
if (
|
|
|
|
|
!pageArray.find((page) => page.handle === this.props.params.pageHandle) &&
|
|
|
|
|
this.state.appDefinition?.pages?.[this.state.appDefinition?.homePageId]
|
|
|
|
|
) {
|
|
|
|
|
const homeHandle = this.state.appDefinition?.pages?.[this.state.appDefinition?.homePageId]?.handle;
|
|
|
|
|
let url = `/applications/${this.state.appId}/versions/${this.state.versionId}/${homeHandle}`;
|
|
|
|
|
if (this.state.slug) {
|
|
|
|
|
url = `/applications/${this.state.slug}/${homeHandle}`;
|
|
|
|
|
}
|
2023-06-15 07:27:17 +00:00
|
|
|
return <Navigate to={`${url}${this.props.params.pageHandle ? '' : window.location.search}`} replace />;
|
2023-05-30 03:53:21 +00:00
|
|
|
}
|
|
|
|
|
|
2022-11-14 06:37:47 +00:00
|
|
|
return (
|
|
|
|
|
<div className="viewer wrapper">
|
|
|
|
|
<Confirm
|
|
|
|
|
show={queryConfirmationList.length > 0}
|
|
|
|
|
message={'Do you want to run this query?'}
|
|
|
|
|
onConfirm={(queryConfirmationData) => onQueryConfirmOrCancel(this, queryConfirmationData, true, 'view')}
|
|
|
|
|
onCancel={() => onQueryConfirmOrCancel(this, queryConfirmationList[0], false, 'view')}
|
|
|
|
|
queryConfirmationData={queryConfirmationList[0]}
|
|
|
|
|
key={queryConfirmationList[0]?.queryName}
|
|
|
|
|
/>
|
|
|
|
|
<DndProvider backend={HTML5Backend}>
|
2022-12-08 12:21:09 +00:00
|
|
|
<ViewerNavigation.Header
|
|
|
|
|
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
|
|
|
|
|
appName={this.state.app?.name ?? null}
|
|
|
|
|
changeDarkMode={this.changeDarkMode}
|
|
|
|
|
darkMode={this.props.darkMode}
|
|
|
|
|
pages={Object.entries(this.state.appDefinition?.pages) ?? []}
|
|
|
|
|
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
|
|
|
|
|
switchPage={this.switchPage}
|
|
|
|
|
currentLayout={this.state.currentLayout}
|
|
|
|
|
/>
|
2022-11-14 06:37:47 +00:00
|
|
|
<div className="sub-section">
|
|
|
|
|
<div className="main">
|
|
|
|
|
<div className="canvas-container align-items-center">
|
2022-12-08 12:21:09 +00:00
|
|
|
<div className="areas d-flex flex-rows justify-content-center">
|
|
|
|
|
{appDefinition?.showViewerNavigation && (
|
|
|
|
|
<ViewerNavigation
|
|
|
|
|
isMobileDevice={this.state.currentLayout === 'mobile'}
|
|
|
|
|
canvasBackgroundColor={this.computeCanvasBackgroundColor()}
|
|
|
|
|
pages={Object.entries(this.state.appDefinition?.pages) ?? []}
|
|
|
|
|
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
|
|
|
|
|
switchPage={this.switchPage}
|
|
|
|
|
darkMode={this.props.darkMode}
|
|
|
|
|
/>
|
2022-11-14 06:37:47 +00:00
|
|
|
)}
|
2022-12-08 12:21:09 +00:00
|
|
|
<div
|
|
|
|
|
className="canvas-area"
|
|
|
|
|
style={{
|
|
|
|
|
width: currentCanvasWidth,
|
|
|
|
|
minHeight: +appDefinition.globalSettings?.canvasMaxHeight || 2400,
|
|
|
|
|
maxWidth: canvasMaxWidth,
|
|
|
|
|
maxHeight: +appDefinition.globalSettings?.canvasMaxHeight || 2400,
|
|
|
|
|
backgroundColor: this.computeCanvasBackgroundColor(),
|
|
|
|
|
margin: 0,
|
|
|
|
|
padding: 0,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{defaultComponentStateComputed && (
|
|
|
|
|
<>
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<div className="mx-auto mt-5 w-50 p-5">
|
|
|
|
|
<center>
|
|
|
|
|
<div className="spinner-border text-azure" role="status"></div>
|
|
|
|
|
</center>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<Container
|
|
|
|
|
appDefinition={appDefinition}
|
|
|
|
|
appDefinitionChanged={() => false} // function not relevant in viewer
|
|
|
|
|
snapToGrid={true}
|
|
|
|
|
appLoading={isLoading}
|
|
|
|
|
darkMode={this.props.darkMode}
|
|
|
|
|
onEvent={(eventName, options) => onEvent(this, eventName, options, 'view')}
|
|
|
|
|
mode="view"
|
|
|
|
|
deviceWindowWidth={deviceWindowWidth}
|
|
|
|
|
currentLayout={currentLayout}
|
|
|
|
|
currentState={this.state.currentState}
|
|
|
|
|
selectedComponent={this.state.selectedComponent}
|
|
|
|
|
onComponentClick={(id, component) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
selectedComponent: { id, component },
|
|
|
|
|
});
|
|
|
|
|
onComponentClick(this, id, component, 'view');
|
|
|
|
|
}}
|
|
|
|
|
onComponentOptionChanged={(component, optionName, value) => {
|
|
|
|
|
return onComponentOptionChanged(this, component, optionName, value);
|
|
|
|
|
}}
|
|
|
|
|
onComponentOptionsChanged={(component, options) =>
|
|
|
|
|
onComponentOptionsChanged(this, component, options)
|
|
|
|
|
}
|
|
|
|
|
canvasWidth={this.getCanvasWidth()}
|
|
|
|
|
dataQueries={dataQueries}
|
|
|
|
|
currentPageId={this.state.currentPageId}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2022-11-14 06:37:47 +00:00
|
|
|
</div>
|
2022-04-21 16:38:54 +00:00
|
|
|
</div>
|
2021-05-03 12:27:46 +00:00
|
|
|
</div>
|
2021-04-30 06:31:32 +00:00
|
|
|
</div>
|
2022-11-14 06:37:47 +00:00
|
|
|
</DndProvider>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-04-21 16:38:54 +00:00
|
|
|
}
|
2021-04-30 06:31:32 +00:00
|
|
|
}
|
2021-04-02 11:09:55 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-20 11:34:24 +00:00
|
|
|
export const Viewer = withTranslation()(withRouter(ViewerComponent));
|