Skip to content

Commit

Permalink
FEATURE: batch operations on tree nodes (#2568)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimaip authored Dec 2, 2019
1 parent d2e1c94 commit 48233fe
Show file tree
Hide file tree
Showing 55 changed files with 849 additions and 432 deletions.
2 changes: 1 addition & 1 deletion Classes/Controller/BackendController.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public function indexAction(NodeInterface $node = null)
$this->view->assign('user', $user);
$this->view->assign('documentNode', $node);
$this->view->assign('site', $siteNode);
$this->view->assign('clipboardNode', $this->clipboard->getNodeContextPath());
$this->view->assign('clipboardNodes', $this->clipboard->getNodeContextPaths());
$this->view->assign('clipboardMode', $this->clipboard->getMode());
$this->view->assign('headScripts', $this->styleAndJavascriptInclusionService->getHeadScripts());
$this->view->assign('headStylesheets', $this->styleAndJavascriptInclusionService->getHeadStylesheets());
Expand Down
27 changes: 21 additions & 6 deletions Classes/Controller/BackendServiceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Neos\Flow\Mvc\ResponseInterface;
use Neos\Flow\Mvc\View\JsonView;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Property\PropertyMapper;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface;
use Neos\Neos\Service\PublishingService;
Expand Down Expand Up @@ -116,6 +117,12 @@ class BackendServiceController extends ActionController
*/
protected $clipboard;

/**
* @Flow\Inject
* @var PropertyMapper
*/
protected $propertyMapper;

/**
* @Flow\Inject
* @var ContentDimensionPresetSourceInterface
Expand Down Expand Up @@ -348,12 +355,16 @@ public function changeBaseWorkspaceAction(string $targetWorkspaceName, NodeInter
/**
* Persists the clipboard node on copy
*
* @param NodeInterface $node
* @param array $nodes
* @return void
*/
public function copyNodeAction(NodeInterface $node)
public function copyNodesAction(array $nodes)
{
$this->clipboard->copyNode($node);
// TODO @christianm want's to have a property mapper for this
$nodes = array_map(function ($node) {
return $this->propertyMapper->convert($node, NodeInterface::class);
}, $nodes);
$this->clipboard->copyNodes($nodes);
}

/**
Expand All @@ -369,12 +380,16 @@ public function clearClipboardAction()
/**
* Persists the clipboard node on cut
*
* @param NodeInterface $node
* @param array $nodes
* @return void
*/
public function cutNodeAction(NodeInterface $node)
public function cutNodesAction(array $nodes)
{
$this->clipboard->cutNode($node);
// TODO @christianm want's to have a property mapper for this
$nodes = array_map(function ($node) {
return $this->propertyMapper->convert($node, NodeInterface::class);
}, $nodes);
$this->clipboard->cutNodes($nodes);
}

public function getWorkspaceInfoAction()
Expand Down
4 changes: 2 additions & 2 deletions Classes/FlowQueryOperations/NeosUiDefaultNodesOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function canEvaluate($context)
public function evaluate(FlowQuery $flowQuery, array $arguments)
{
list($siteNode, $documentNode) = $flowQuery->getContext();
list($baseNodeType, $loadingDepth, $toggledNodes, $clipboardNodeContextPath) = $arguments;
list($baseNodeType, $loadingDepth, $toggledNodes, $clipboardNodesContextPaths) = $arguments;

// Collect all parents of documentNode up to siteNode
$parents = [];
Expand Down Expand Up @@ -96,7 +96,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
$nodes[] = $documentNode;
}

if ($clipboardNodeContextPath) {
foreach ($clipboardNodesContextPaths as $clipboardNodeContextPath) {
$clipboardNode = $this->propertyMapper->convert($clipboardNodeContextPath, NodeInterface::class);
if ($clipboardNode && !in_array($clipboardNode, $nodes)) {
$nodes[] = $clipboardNode;
Expand Down
28 changes: 16 additions & 12 deletions Classes/Service/NodeClipboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class NodeClipboard
/**
* @var string
*/
protected $nodeContextPath = '';
protected $nodeContextPaths = [];

/**
* @var string one of the NodeClipboard::MODE_* constants
Expand All @@ -37,26 +37,30 @@ class NodeClipboard
/**
* Save copied node to clipboard.
*
* @param NodeInterface $node
* @param NodeInterface[] $nodes
* @return void
* @Flow\Session(autoStart=true)
*/
public function copyNode(NodeInterface $node)
public function copyNodes(array $nodes)
{
$this->nodeContextPath = $node->getContextPath();
$this->nodeContextPaths = array_map(function ($node) {
return $node->getContextPath();
}, $nodes);
$this->mode = self::MODE_COPY;
}

/**
* Save cut node to clipboard.
* Save cut nodes to clipboard.
*
* @param NodeInterface $node
* @param NodeInterface[] $nodes
* @return void
* @Flow\Session(autoStart=true)
*/
public function cutNode(NodeInterface $node)
public function cutNodes(array $nodes)
{
$this->nodeContextPath = $node->getContextPath();
$this->nodeContextPaths = array_map(function ($node) {
return $node->getContextPath();
}, $nodes);
$this->mode = self::MODE_MOVE;
}

Expand All @@ -68,18 +72,18 @@ public function cutNode(NodeInterface $node)
*/
public function clear()
{
$this->nodeContextPath = '';
$this->nodeContextPaths = [];
$this->mode = '';
}

/**
* Get clipboard node.
*
* @return string $nodeContextPath
* @return array $nodeContextPath
*/
public function getNodeContextPath()
public function getNodeContextPaths()
{
return $this->nodeContextPath ? $this->nodeContextPath : '';
return $this->nodeContextPaths ? $this->nodeContextPaths : [];
}

/**
Expand Down
12 changes: 6 additions & 6 deletions Configuration/Routes.Service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@
httpMethods: ['POST']

-
name: 'Copy node to clipboard'
uriPattern: 'copy-node'
name: 'Copy nodes to clipboard'
uriPattern: 'copy-nodes'
defaults:
'@controller': 'BackendService'
'@action': 'copyNode'
'@action': 'copyNodes'
httpMethods: ['POST']

-
name: 'Cut node to clipboard'
uriPattern: 'cut-node'
name: 'Cut nodes to clipboard'
uriPattern: 'cut-nodes'
defaults:
'@controller': 'BackendService'
'@action': 'cutNode'
'@action': 'cutNodes'
httpMethods: ['POST']

-
Expand Down
4 changes: 2 additions & 2 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Neos:
byContextPath: '${Neos.Ui.NodeInfo.defaultNodesForBackend(site, documentNode, controllerContext)}'
siteNode: '${q(site).property(''_contextPath'')}'
documentNode: '${q(documentNode).property(''_contextPath'')}'
clipboard: '${clipboardNode || null}'
clipboard: '${clipboardNodes || []}'
clipboardMode: '${clipboardMode || null}'
contentDimensions:
byName: '${Neos.Ui.ContentDimensions.contentDimensionsByName()}'
Expand Down Expand Up @@ -156,7 +156,7 @@ Neos:
pageTree:
isLoading: false
hasError: false
focused: '${q(documentNode).property(''_contextPath'')}'
focused: '${[q(documentNode).property(''_contextPath'')]}'
active: '${q(documentNode).property(''_contextPath'')}'
remote:
isSaving: false
Expand Down
8 changes: 4 additions & 4 deletions Resources/Private/Fusion/Backend/Root.fusion
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ backend = Neos.Fusion:Template {
changeBaseWorkspace = Neos.Fusion:UriBuilder {
action = 'changeBaseWorkspace'
}
copyNode = Neos.Fusion:UriBuilder {
action = 'copyNode'
copyNodes = Neos.Fusion:UriBuilder {
action = 'copyNodes'
}
cutNode = Neos.Fusion:UriBuilder {
action = 'cutNode'
cutNodes = Neos.Fusion:UriBuilder {
action = 'cutNodes'
}
clearClipboard = Neos.Fusion:UriBuilder {
action = 'clearClipboard'
Expand Down
18 changes: 18 additions & 0 deletions Resources/Private/Translations/en/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,24 @@
<trans-unit id="syncUriPathSegment" xml:space="preserve">
<source>Syncronize with the title property</source>
</trans-unit>
<trans-unit id="nodes" xml:space="preserve">
<source>nodes</source>
</trans-unit>
<trans-unit id="contentElementsSelected" xml:space="preserve">
<source>content elements selected</source>
</trans-unit>
<trans-unit id="documentsSelected" xml:space="preserve">
<source>documents selected</source>
</trans-unit>
<trans-unit id="inspectorMutlipleContentNodesSelectedTooltip" xml:space="preserve">
<source>Select a single document in order to be able to edit its properties</source>
</trans-unit>
<trans-unit id="inspectorMutlipleDocumentNodesSelectedTooltip" xml:space="preserve">
<source>Select a single content element in order to be able to edit its properties</source>
</trans-unit>
<trans-unit id="deleteXNodes" xml:space="preserve">
<source>Delete {amount} nodes</source>
</trans-unit>
</body>
</file>
</xliff>
6 changes: 6 additions & 0 deletions packages/neos-ts-interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export enum InsertPosition {
AFTER = 'after'
}

export enum SelectionModeTypes {
SINGLE_SELECT = 'SINGLE_SELECT',
MULTIPLE_SELECT = 'MULTIPLE_SELECT',
RANGE_SELECT = 'RANGE_SELECT'
}

export interface ValidatorConfiguration {
[propName: string]: any;
}
Expand Down
20 changes: 10 additions & 10 deletions packages/neos-ui-backend-connector/src/Endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export interface Routes {
publish: string;
discard: string;
changeBaseWorkspace: string;
copyNode: string;
cutNode: string;
copyNodes: string;
cutNodes: string;
clearClipboard: string;
loadTree: string;
flowQuery: string;
Expand Down Expand Up @@ -107,8 +107,8 @@ export default (routes: Routes) => {
})).then(response => fetchWithErrorHandling.parseJson(response))
.catch(reason => fetchWithErrorHandling.generalErrorHandler(reason));

const copyNode = (node: NodeContextPath) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: routes.ui.service.copyNode,
const copyNodes = (nodes: NodeContextPath[]) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: routes.ui.service.copyNodes,

method: 'POST',
credentials: 'include',
Expand All @@ -117,13 +117,13 @@ export default (routes: Routes) => {
'Content-Type': 'application/json'
},
body: JSON.stringify({
node
nodes
})
})).then(response => fetchWithErrorHandling.parseJson(response))
.catch(reason => fetchWithErrorHandling.generalErrorHandler(reason));

const cutNode = (node: NodeContextPath) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: routes.ui.service.cutNode,
const cutNodes = (nodes: NodeContextPath[]) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: routes.ui.service.cutNodes,

method: 'POST',
credentials: 'include',
Expand All @@ -132,7 +132,7 @@ export default (routes: Routes) => {
'Content-Type': 'application/json'
},
body: JSON.stringify({
node
nodes
})
})).then(response => fetchWithErrorHandling.parseJson(response))
.catch(reason => fetchWithErrorHandling.generalErrorHandler(reason));
Expand Down Expand Up @@ -610,8 +610,8 @@ export default (routes: Routes) => {
publish,
discard,
changeBaseWorkspace,
copyNode,
cutNode,
copyNodes,
cutNodes,
clearClipboard,
createImageVariant,
loadMasterPlugins,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {NodeTypeName, NodeContextPath} from '@neos-project/neos-ts-interfaces';

export default () => (baseNodeType: NodeTypeName, loadingDepth: number | undefined, toggledNodes: NodeContextPath[], clipboardNodeContextPath: NodeContextPath) => ({
export default () => (baseNodeType: NodeTypeName, loadingDepth: number | undefined, toggledNodes: NodeContextPath[], clipboardNodesContextPaths: NodeContextPath[]) => ({
type: 'neosUiDefaultNodes',
payload: [baseNodeType, loadingDepth, toggledNodes, clipboardNodeContextPath]
payload: [baseNodeType, loadingDepth, toggledNodes, clipboardNodesContextPaths]
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {$get} from 'plow-js';

import {selectors, actions} from '@neos-project/neos-ui-redux-store';
import IconButton from '@neos-project/react-ui-components/src/IconButton/';
Expand All @@ -15,7 +14,7 @@ import {neos} from '@neos-project/neos-ui-decorators';
const isAllowedToAddChildOrSiblingNodesSelector = selectors.CR.Nodes.makeIsAllowedToAddChildOrSiblingNodes(nodeTypesRegistry);

return state => {
const focusedNodeContextPath = $get('cr.nodes.focused.contextPath', state);
const focusedNodeContextPath = selectors.CR.Nodes.focusedNodePathSelector(state);
const isAllowedToAddChildOrSiblingNodes = isAllowedToAddChildOrSiblingNodesSelector(state, {
reference: focusedNodeContextPath
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {selectors, actions} from '@neos-project/neos-ui-redux-store';
const canBePastedSelector = selectors.CR.Nodes.makeCanBePastedSelector(nodeTypesRegistry);

return (state, {contextPath}) => {
const clipboardNodeContextPath = selectors.CR.Nodes.clipboardNodeContextPathSelector(state);
const canBePasted = canBePastedSelector(state, {
subject: clipboardNodeContextPath,
reference: contextPath
});
const clipboardNodesContextPaths = selectors.CR.Nodes.clipboardNodesContextPathsSelector(state);
const canBePasted = (clipboardNodesContextPaths.every(clipboardNodeContextPath => {
return canBePastedSelector(state, {
subject: clipboardNodeContextPath,
reference: contextPath
});
}));

return {canBePasted};
};
Expand Down
Loading

0 comments on commit 48233fe

Please sign in to comment.