PromucFlow_constructor/app/client/src/sagas/NavigationSagas.ts

319 lines
9.4 KiB
TypeScript
Raw Normal View History

2022-12-15 04:06:13 +00:00
import { all, call, fork, put, select, takeEvery } from "redux-saga/effects";
import { setFocusHistory } from "actions/focusHistoryActions";
import { getCurrentFocusInfo } from "selectors/focusHistorySelectors";
import { FocusState } from "reducers/uiReducers/focusHistoryReducer";
import { FocusElementsConfig } from "navigation/FocusElements";
import {
FocusEntity,
FocusEntityInfo,
identifyEntityFromPath,
isSameBranch,
shouldStoreURLforFocus,
} from "navigation/FocusEntity";
import { getAction, getPlugin } from "selectors/entitiesSelector";
import { Action } from "entities/Action";
import { Plugin } from "api/PluginApi";
import log from "loglevel";
import { Location } from "history";
2022-12-15 04:06:13 +00:00
import history, {
AppsmithLocationState,
NavigationMethod,
} from "utils/history";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
import {
ReduxAction,
ReduxActionTypes,
} from "ce/constants/ReduxActionConstants";
import { getCurrentThemeDetails } from "selectors/themeSelectors";
import { BackgroundTheme, changeAppBackground } from "sagas/ThemeSaga";
import { updateRecentEntitySaga } from "sagas/GlobalSearchSagas";
let previousPath: string;
let previousHash: string | undefined;
2022-12-15 04:06:13 +00:00
function* appBackgroundHandler() {
const currentTheme: BackgroundTheme = yield select(getCurrentThemeDetails);
changeAppBackground(currentTheme);
}
function* handleRouteChange(
action: ReduxAction<{ location: Location<AppsmithLocationState> }>,
) {
const { hash, pathname, state } = action.payload.location;
try {
2022-12-15 04:06:13 +00:00
yield call(logNavigationAnalytics, action.payload);
yield call(contextSwitchingSaga, pathname, state, hash);
yield call(appBackgroundHandler);
const entityInfo = identifyEntityFromPath(pathname, hash);
yield fork(updateRecentEntitySaga, entityInfo);
} catch (e) {
log.error("Error in focus change", e);
} finally {
previousPath = pathname;
previousHash = hash;
}
}
2022-12-15 04:06:13 +00:00
function* logNavigationAnalytics(payload: {
location: Location<AppsmithLocationState>;
}) {
const {
location: { hash, pathname, state },
} = payload;
const recentEntityIds: Array<string> = yield select(getRecentEntityIds);
const currentEntity = identifyEntityFromPath(pathname, hash);
const previousEntity = identifyEntityFromPath(previousPath, previousHash);
const isRecent = recentEntityIds.some(
(entityId) => entityId === currentEntity.id,
);
AnalyticsUtil.logEvent("ROUTE_CHANGE", {
toPath: pathname + hash,
fromPath: previousPath + previousHash || undefined,
navigationMethod: state?.invokedBy,
isRecent,
recentLength: recentEntityIds.length,
toType: currentEntity.entity,
fromType: previousEntity.entity,
});
}
function* handlePageChange(
action: ReduxAction<{
pageId: string;
currPath: string;
currParamString: string;
fromPath: string;
fromParamString: string;
}>,
) {
const {
currParamString,
currPath,
fromParamString,
fromPath,
pageId,
} = action.payload;
try {
const fromPageId = identifyEntityFromPath(fromPath)?.pageId;
2022-12-15 04:06:13 +00:00
if (fromPageId && fromPageId !== pageId) {
yield call(storeStateOfPage, fromPageId, fromPath, fromParamString);
yield call(setStateOfPage, pageId, currPath, currParamString);
}
} catch (e) {
log.error("Error on page change", e);
}
}
function* contextSwitchingSaga(
pathname: string,
state: AppsmithLocationState,
hash?: string,
) {
if (previousPath) {
// store current state
yield call(storeStateOfPath, previousPath, previousHash);
// while switching from selected widget state to API, Query or Datasources directly, store Canvas state as well
if (shouldStoreStateForCanvas(previousPath, pathname, previousHash, hash)) {
yield call(storeStateOfPath, previousPath);
}
}
// Check if it should restore the stored state of the path
if (shouldSetState(previousPath, pathname, previousHash, hash, state)) {
// restore old state for new path
yield call(setStateOfPath, pathname, hash);
}
}
function* storeStateOfPath(path: string, hash?: string) {
const focusHistory: FocusState | undefined = yield select(
getCurrentFocusInfo,
hash ? `${path}${hash}` : path,
);
const entityInfo: FocusEntityInfo = focusHistory
? focusHistory.entityInfo
: identifyEntityFromPath(path, hash);
const selectors = FocusElementsConfig[entityInfo.entity];
const state: Record<string, any> = {};
for (const selectorInfo of selectors) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
state[selectorInfo.name] = yield select(selectorInfo.selector);
}
yield put(
setFocusHistory(hash ? `${path}${hash}` : path, {
entityInfo,
state,
}),
);
}
function* setStateOfPath(path: string, hash?: string) {
const focusHistory: FocusState = yield select(
getCurrentFocusInfo,
hash ? `${path}${hash}` : path,
);
const entityInfo: FocusEntityInfo = focusHistory
? focusHistory.entityInfo
: identifyEntityFromPath(path, hash);
const selectors = FocusElementsConfig[entityInfo.entity];
if (focusHistory) {
for (const selectorInfo of selectors) {
yield put(selectorInfo.setter(focusHistory.state[selectorInfo.name]));
}
} else {
const subType: string | undefined = yield call(
getEntitySubType,
entityInfo,
);
for (const selectorInfo of selectors) {
const { defaultValue, subTypes } = selectorInfo;
if (subType && subTypes && subType in subTypes) {
yield put(selectorInfo.setter(subTypes[subType].defaultValue));
} else if (defaultValue !== undefined) {
yield put(selectorInfo.setter(defaultValue));
}
}
}
}
function* storeStateOfPage(
pageId: string,
fromPath: string,
fromParam: string | undefined,
) {
const entity = FocusEntity.PAGE;
const selectors = FocusElementsConfig[entity];
const state: Record<string, any> = {};
for (const selectorInfo of selectors) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
state[selectorInfo.name] = yield select(selectorInfo.selector);
}
if (shouldStoreURLforFocus(fromPath)) {
if (fromPath) {
state._routingURL = fromPath;
}
if (fromParam !== undefined) {
state._paramString = fromParam;
}
}
const entityInfo = { entity, id: pageId };
yield put(setFocusHistory(pageId, { entityInfo, state }));
}
function* setStateOfPage(
pageId: string,
currPath: string,
paramString: string,
) {
const focusHistory: FocusState = yield select(getCurrentFocusInfo, pageId);
const entity = FocusEntity.PAGE;
const selectors = FocusElementsConfig[entity];
if (focusHistory) {
for (const selectorInfo of selectors) {
yield put(selectorInfo.setter(focusHistory.state[selectorInfo.name]));
}
if (
focusHistory.state._routingURL &&
focusHistory.state._routingURL !== currPath &&
isSameBranch(focusHistory.state._paramString, paramString)
) {
history.push(
`${focusHistory.state._routingURL}${focusHistory.state._paramString ||
""}`,
);
}
} else {
for (const selectorInfo of selectors) {
if ("defaultValue" in selectorInfo)
yield put(selectorInfo.setter(selectorInfo.defaultValue));
}
}
}
function* getEntitySubType(entityInfo: FocusEntityInfo) {
if ([FocusEntity.API, FocusEntity.QUERY].includes(entityInfo.entity)) {
const action: Action = yield select(getAction, entityInfo.id);
const plugin: Plugin = yield select(getPlugin, action.pluginId);
return plugin.packageName;
}
}
/**
* This method returns boolean to indicate if state should be restored to the path
* @param prevPath
* @param currPath
* @param prevHash
* @param currHash
* @param state
* @returns
*/
function shouldSetState(
prevPath: string,
currPath: string,
prevHash?: string,
currHash?: string,
state?: AppsmithLocationState,
) {
2022-12-15 04:06:13 +00:00
if (
state &&
state.invokedBy &&
state.invokedBy === NavigationMethod.CommandClick
) {
// If it is a command click navigation, we will set the state
return true;
}
const prevFocusEntity = identifyEntityFromPath(prevPath, prevHash).entity;
const currFocusEntity = identifyEntityFromPath(currPath, currHash).entity;
// While switching from selected widget state to canvas,
// it should not be restored stored state for canvas
return !(
prevFocusEntity === FocusEntity.PROPERTY_PANE &&
currFocusEntity === FocusEntity.CANVAS &&
prevPath === currPath
);
}
/**
* This method returns boolean if it should store an additional intermediate state
* @param prevPath
* @param currPath
* @param prevHash
* @param currHash
* @returns
*/
function shouldStoreStateForCanvas(
prevPath: string,
currPath: string,
prevHash?: string,
currHash?: string,
) {
const prevFocusEntity = identifyEntityFromPath(prevPath, prevHash).entity;
const currFocusEntity = identifyEntityFromPath(currPath, currHash).entity;
// while moving from selected widget state directly to some other state,
// it should also store selected widgets as well
return (
prevFocusEntity === FocusEntity.PROPERTY_PANE &&
currFocusEntity !== FocusEntity.PROPERTY_PANE &&
(currFocusEntity !== FocusEntity.CANVAS || prevPath !== currPath)
);
}
export default function* rootSaga() {
yield all([takeEvery(ReduxActionTypes.ROUTE_CHANGED, handleRouteChange)]);
yield all([takeEvery(ReduxActionTypes.PAGE_CHANGED, handlePageChange)]);
}