Skip to content

Commit

Permalink
TASK: Implement reloadNodes saga using the ReloadNodes query endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
grebaldi committed Apr 8, 2024
1 parent 33019f6 commit 569d4bd
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 2 deletions.
25 changes: 24 additions & 1 deletion packages/neos-ui-backend-connector/src/Endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface Routes {
generateUriPathSegment: string;
getWorkspaceInfo: string;
getAdditionalNodeMetadata: string;
reloadNodes: string;
};
};
core: {
Expand Down Expand Up @@ -694,6 +695,27 @@ export default (routes: Routes) => {
.then(response => fetchWithErrorHandling.parseJson(response))
.catch(reason => fetchWithErrorHandling.generalErrorHandler(reason));

const reloadNodes = (query: {
workspaceName: WorkspaceName;
dimensionSpacePoint: DimensionCombination;
siteId: NodeContextPath;
documentId: NodeContextPath;
ancestorsOfDocumentIds: NodeContextPath[];
toggledNodesIds: NodeContextPath[];
clipboardNodesIds: NodeContextPath[];
}) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: routes.ui.service.reloadNodes,
method: 'POST',
credentials: 'include',
headers: {
'X-Flow-Csrftoken': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({query})
}))
.then(response => fetchWithErrorHandling.parseJson(response))
.catch(reason => fetchWithErrorHandling.generalErrorHandler(reason));

return {
loadImageMetadata,
change,
Expand Down Expand Up @@ -726,6 +748,7 @@ export default (routes: Routes) => {
tryLogin,
contentDimensions,
impersonateStatus,
impersonateRestore
impersonateRestore,
reloadNodes
};
};
4 changes: 3 additions & 1 deletion packages/neos-ui-sagas/src/CR/NodeOperations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import moveDroppedNodes from './moveDroppedNodes';
import hideNode from './hideNode';
import showNode from './showNode';
import reloadState from './reloadState';
import {makeReloadNodes} from './reloadNodes';

export {
addNode,
Expand All @@ -15,5 +16,6 @@ export {
moveDroppedNodes,
hideNode,
showNode,
reloadState
reloadState,
makeReloadNodes
};
127 changes: 127 additions & 0 deletions packages/neos-ui-sagas/src/CR/NodeOperations/reloadNodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* This file is part of the Neos.Neos.Ui package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/
import {call, put, select} from 'redux-saga/effects';

import {actions, selectors} from '@neos-project/neos-ui-redux-store';
import {GlobalState} from '@neos-project/neos-ui-redux-store/src/System';
import {DimensionCombination, Node, NodeContextPath, NodeMap, WorkspaceName} from '@neos-project/neos-ts-interfaces';
import {AnyError} from '@neos-project/neos-ui-error';
import backend from '@neos-project/neos-ui-backend-connector';
// @ts-ignore
import {getGuestFrameDocument} from '@neos-project/neos-ui-guest-frame/src/dom';

// @TODO: This is a helper to gain type access to the available backend endpoints.
// It shouldn't be necessary to do this, and this hack should be removed once a
// better type API is available
import {default as Endpoints, Routes} from '@neos-project/neos-ui-backend-connector/src/Endpoints';
type Endpoints = ReturnType<typeof Endpoints>;

type ReloadNodesResponse =
| {
success: {
documentId: NodeContextPath;
nodes: NodeMap;
}
}
| {error: AnyError}

export const makeReloadNodes = (deps: {
routes?: Routes;
}) => {
const redirectToDefaultModule = makeRedirectToDefaultModule(deps);
const {reloadNodes: reloadNodesEndpoint} = backend.get().endpoints as Endpoints;

return function * reloadNodes() {
const workspaceName: WorkspaceName = yield select<GlobalState>(
(state) => state.cr.workspaces.personalWorkspace.name
);
const dimensionSpacePoint: DimensionCombination = yield select<GlobalState>(
(state) => state.cr.contentDimensions.active
);
const {siteNode: siteId, documentNode: documentId}: GlobalState['cr']['nodes'] =
yield select<GlobalState>((state) => state.cr.nodes);
if (!siteId || !documentId) {
redirectToDefaultModule();
return;
}

const documentNodeParentLine: ReturnType<
typeof selectors.CR.Nodes.documentNodeParentLineSelector
> = yield select<GlobalState>(selectors.CR.Nodes.documentNodeParentLineSelector);
const ancestorsOfDocumentIds = documentNodeParentLine
.map((nodeOrNull) => nodeOrNull?.contextPath)
.filter((nodeIdOrNull) => Boolean(nodeIdOrNull)) as NodeContextPath[];
const clipboardNodesIds: NodeContextPath[] = yield select<GlobalState>(
selectors.CR.Nodes.clipboardNodesContextPathsSelector
);
const toggledNodesIds: NodeContextPath[] = yield select<GlobalState>(
state => state?.ui?.pageTree?.toggled
);

yield put(actions.UI.PageTree.setAsLoading(siteId));

const result: ReloadNodesResponse = yield call(reloadNodesEndpoint, {
workspaceName,
dimensionSpacePoint,
siteId,
documentId,
ancestorsOfDocumentIds,
clipboardNodesIds,
toggledNodesIds
});

if ('success' in result) {
yield put(actions.CR.Nodes.setState({
siteNodeContextPath: siteId,
documentNodeContextPath: result.success.documentId,
nodes: result.success.nodes,
merge: false
}));

yield put(actions.UI.PageTree.setAsLoaded(siteId));

if (documentId === result.success.documentId) {
// If the document is still available, reload the guest frame
getGuestFrameDocument().location.reload();
} else {
// If it's gone try to navigate to the next available ancestor document
const availableAncestorDocumentNode: Node = yield select<GlobalState>(
selectors.CR.Nodes.byContextPathSelector(result.success.documentId)
);

if (availableAncestorDocumentNode) {
yield put(actions.UI.ContentCanvas.setSrc(availableAncestorDocumentNode.uri));
} else {
// We're doomed - there's no document left to navigate to
// In this (rather unlikely) case, we leave the UI and navigate
// to whatever default entry module is configured:
redirectToDefaultModule();
}
}
} else {
// If reloading failed on the server side, one of three things happened:
// 1. No document node could be found
// 2. No site node could be found
// 3. Some other, more profound error occurred
//
// No matter which scenario, we'd end up in an invalid UI state. This is
// why we need to escape to whatever default entry module is configured:
redirectToDefaultModule();
}
};
}

const makeRedirectToDefaultModule = (deps: {
routes?: Routes;
}) => {
return function redirectToDefaultModule() {
window.location.href = deps.routes?.core?.modules?.defaultModule!;
};
};

0 comments on commit 569d4bd

Please sign in to comment.