import React from 'react';
import { appService, authenticationService, orgEnvironmentVariableService, organizationService } from '@/_services';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Container } from './Container';
import { Confirm } from './Viewer/Confirm';
import { ViewerNavigation } from './Viewer/ViewerNavigation';
import {
onComponentOptionChanged,
onComponentOptionsChanged,
onComponentClick,
onQueryConfirmOrCancel,
onEvent,
runQuery,
computeComponentState,
} from '@/_helpers/appUtils';
import queryString from 'query-string';
import ViewerLogoIcon from './Icons/viewer-logo.svg';
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
import {
resolveReferences,
safelyParseJSON,
stripTrailingSlash,
getSubpath,
excludeWorkspaceIdFromURL,
} from '@/_helpers/utils';
import { withTranslation } from 'react-i18next';
import _ from 'lodash';
import { Navigate } from 'react-router-dom';
import Spinner from '@/_ui/Spinner';
import { toast } from 'react-hot-toast';
import { withRouter } from '@/_hoc/withRouter';
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
class ViewerComponent extends React.Component {
constructor(props) {
super(props);
const deviceWindowWidth = window.screen.width - 5;
const isMobileDevice = deviceWindowWidth < 600;
const pageHandle = this.props?.params?.pageHandle;
const slug = this.props.params.slug;
const appId = this.props.params.id;
const versionId = this.props.params.versionId;
this.subscription = null;
this.state = {
slug,
appId,
versionId,
deviceWindowWidth,
currentLayout: isMobileDevice ? 'mobile' : 'desktop',
currentUser: null,
isLoading: true,
users: null,
appDefinition: { pages: {} },
currentState: {
queries: {},
components: {},
globals: {
currentUser: {},
theme: { name: props.darkMode ? 'dark' : 'light' },
urlparams: {},
environment_variables: {},
page: {
handle: pageHandle,
},
},
variables: {},
},
queryConfirmationList: [],
isAppLoaded: false,
errorAppId: null,
errorVersionId: null,
errorDetails: null,
pages: {},
};
}
setStateForApp = (data) => {
const copyDefinition = _.cloneDeep(data.definition);
const pagesObj = copyDefinition.pages || {};
const newDefinition = {
...copyDefinition,
pages: pagesObj,
};
this.setState({
app: data,
isLoading: false,
isAppLoaded: true,
appDefinition: newDefinition || { components: {} },
});
};
setStateForContainer = async (data) => {
const currentUser = this.state.currentUser;
let userVars = {};
if (currentUser) {
userVars = {
email: currentUser.email,
firstName: currentUser.first_name,
lastName: currentUser.last_name,
groups: authenticationService.currentSessionValue?.group_permissions.map((group) => group.group),
};
}
let mobileLayoutHasWidgets = false;
if (this.state.currentLayout === 'mobile') {
const currentComponents = data.definition.pages[data.definition.homePageId].components;
mobileLayoutHasWidgets =
Object.keys(currentComponents).filter((componentId) => currentComponents[componentId]['layouts']['mobile'])
.length > 0;
}
let queryState = {};
data.data_queries.forEach((query) => {
if (query.pluginId || query?.plugin?.id) {
queryState[query.name] = {
...query.plugin.manifestFile.data.source.exposedVariables,
...this.state.currentState.queries[query.name],
};
} else {
queryState[query.name] = {
...DataSourceTypes.find((source) => source.kind === query.kind).exposedVariables,
...this.state.currentState.queries[query.name],
};
}
});
const variables = await this.fetchOrgEnvironmentVariables(data.slug, data.is_public);
const pages = Object.entries(data.definition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
const homePageId = data.definition.homePageId;
const startingPageHandle = this.props?.params?.pageHandle;
const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId;
const currentPage = pages.find((page) => page.id === currentPageId);
useDataQueriesStore.getState().actions.setDataQueries(data.data_queries);
this.setState(
{
currentUser,
currentSidebarTab: 2,
currentLayout: mobileLayoutHasWidgets ? 'mobile' : 'desktop',
canvasWidth:
this.state.currentLayout === 'desktop'
? '100%'
: mobileLayoutHasWidgets
? `${this.state.deviceWindowWidth}px`
: '1292px',
selectedComponent: null,
currentState: {
queries: queryState,
components: {},
globals: {
currentUser: userVars, // currentUser is updated in setupViewer function as well
theme: { name: this.props.darkMode ? 'dark' : 'light' },
urlparams: JSON.parse(JSON.stringify(queryString.parse(this.props.location.search))),
},
variables: {},
page: {
id: currentPage.id,
handle: currentPage.handle,
name: currentPage.name,
variables: {},
},
...variables,
},
dataQueries: data.data_queries,
currentPageId: currentPage.id,
pages: {},
},
() => {
computeComponentState(this, data?.definition?.pages[currentPage.id]?.components).then(async () => {
this.setState({ initialComputationOfStateDone: true });
console.log('Default component state computed and set');
this.runQueries(data.data_queries);
// eslint-disable-next-line no-unsafe-optional-chaining
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
for (const event of events ?? []) {
await this.handleEvent(event.eventId, event);
}
});
}
);
};
runQueries = (data_queries) => {
data_queries.forEach((query) => {
if (query.options.runOnPageLoad) {
runQuery(this, query.id, query.name, undefined, 'view');
}
});
};
fetchOrgEnvironmentVariables = async (slug, isPublic) => {
const variables = {
client: {},
server: {},
};
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;
});
return variables;
};
loadApplicationBySlug = (slug) => {
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,
});
});
};
loadApplicationByVersion = (appId, versionId) => {
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(
() => {
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);
const currentSessionValue = authenticationService.currentSessionValue;
if (
errorObj?.organizationId &&
this.state.currentUser &&
currentSessionValue.current_organization_id !== errorObj?.organizationId
) {
this.switchOrganization(errorObj?.organizationId, appId, versionId);
return;
}
return