Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly display dirty and deleted nodes in flat lists #11

Merged
merged 28 commits into from Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a71e47e
Update Neos version to >= 3.3 in composer.json
danielkestler Sep 10, 2018
a89ae04
Remove paging in flat lists (this is just for our use case!)
danielkestler Sep 10, 2018
c491683
Add isPaginated setting to control pagination / loading button, add s…
danielkestler Sep 11, 2018
d479fe1
Bugfix for list icon styling (overlapping/fixed elements)
danielkestler Sep 14, 2018
b75def7
Change "isPaginated"/"disablePagination" to opt-in instead of opt-out
danielkestler Sep 17, 2018
53a3dab
Merge pull request #1 from psmb/master
danielkestler Oct 12, 2018
ec6a139
Correctly display dirty and deleted nodes in flat lists
danielkestler Oct 12, 2018
7d4bffa
Implement a better way to get information about removed nodes
danielkestler Oct 12, 2018
13d895a
Add "hidden" icon to hidden nodes
danielkestler Oct 12, 2018
9b6da5a
Show hiddenBefore and hiddenAfter node icon, bugfix for "delayed" isH…
danielkestler Oct 15, 2018
809da03
FEATURE: allow to select nodetype
dimaip Nov 2, 2018
9452dca
FEATURE: add i18n (#14)
dimaip Nov 12, 2018
5ace307
Merge branch 'master' into nodetype-select
dimaip Nov 12, 2018
08fa62c
Merge branch 'master' into nodetype-select
dimaip Nov 12, 2018
0c869ba
Use the new extensibility option
dimaip Nov 12, 2018
2592814
TASK: reload on workspace or dimension change (#15)
dimaip Dec 4, 2018
fc60640
Merge branch 'master' into nodetype-select
dimaip Dec 4, 2018
1c03ac6
TASK: make sure newReferenceNode is always loaded
dimaip Dec 4, 2018
f6f0978
BUGFIX: big refactoring to fix loading of newReferenceNodePath
dimaip Dec 5, 2018
5f8f47f
BUGFIX: silly typo
dimaip Dec 5, 2018
65a5759
BUGFIX: move the locking to class' state
dimaip Dec 5, 2018
5b833d7
Merge pull request #13 from psmb/nodetype-select
dimaip Dec 5, 2018
ac0b353
BUGFIX: Encode context path
Dec 7, 2018
3bd2d85
Update Resources/Private/FlatNav/src/makeFlatNavContainer.js
dimaip Dec 10, 2018
0d8001b
Use ES6 string literals
Dec 10, 2018
06ef559
Merge pull request #16 from DrillSergeant/bugfix-encodedcontextpath
dimaip Dec 10, 2018
a7ff8f9
BUGFIX: added id to TabPanel and fixed tab height - https://github.co…
pmaas Mar 27, 2019
0d4ac87
Merge branch 'master' into upstream
danielkestler Mar 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 10 additions & 2 deletions Classes/Controller/StandardController.php
Expand Up @@ -62,7 +62,14 @@ public function queryAction($preset, $nodeContextPath, $page = 1)
];
$nodes = \Neos\Eel\Utility::evaluateEelExpression($expression, $this->eelEvaluator, $contextVariables, $this->defaultContextConfiguration);
$nodeInfoHelper = new NodeInfoHelper();
$result = $nodeInfoHelper->renderNodes($nodes, $this->getControllerContext(), true);

$result = [];
foreach ($nodes as $node) {
$nodeInfo = $nodeInfoHelper->renderNodeWithMinimalPropertiesAndChildrenInformation($node, $this->getControllerContext());
$nodeInfo['properties']['_removed'] = $node->isRemoved();
$nodeInfo['properties']['_hidden'] = $node->isHidden();
$result[] = $nodeInfo;
}
$this->view->assign('value', $result);
}

Expand All @@ -81,7 +88,8 @@ public function getNewReferenceNodePathAction($preset, $nodeContextPath)
$expression = '${' . $this->presets[$preset]['newReferenceNodePath'] . '}';
$baseNode = $this->nodeService->getNodeFromContextPath($nodeContextPath, null, null, true);
$contextVariables = [
'node' => $baseNode
'node' => $baseNode,
'site' => $baseNode
];
$newReferenceNodePath = \Neos\Eel\Utility::evaluateEelExpression($expression, $this->eelEvaluator, $contextVariables, $this->defaultContextConfiguration);
$this->view->assign('value', $newReferenceNodePath);
Expand Down
6 changes: 6 additions & 0 deletions Configuration/Settings.yaml
@@ -1,5 +1,10 @@
Neos:
Neos:
userInterface:
translation:
autoInclude:
Psmb.FlatNav:
- Main
Ui:
resources:
javascript:
Expand All @@ -19,6 +24,7 @@ Neos:
# type: flat
# query: 'Search.query(node).nodeType("Your.Namespace:News").sortDesc("date").from((page - 1) * 20).limit(20).execute().toArray()'
# newReferenceNodePath: '/sites/site'
# # If now `newNodeType` is defined, then the nodetype selection dialog would be shown
# newNodeType: 'Your.Namespace:News'
# disablePagination: false
## Example without pagination
Expand Down
237 changes: 77 additions & 160 deletions Resources/Private/FlatNav/src/FlatNav.js
@@ -1,170 +1,35 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {$get, $transform} from 'plow-js';
import {Button, Icon, IconButton, Tabs} from '@neos-project/react-ui-components';
import {Button, Icon, IconButton} from '@neos-project/react-ui-components';
import {connect} from 'react-redux';
import {actions, selectors} from '@neos-project/neos-ui-redux-store';
import {neos} from '@neos-project/neos-ui-decorators';
import {fetchWithErrorHandling} from '@neos-project/neos-ui-backend-connector';
import HideSelectedNode from './HideSelectedNode';
import DeleteSelectedNode from './DeleteSelectedNode';
import mergeClassNames from 'classnames';
import style from './style.css';
import RefreshNodes from "./RefreshNodes";

const makeFlatNavContainer = OriginalPageTree => {
class FlatNavContainer extends Component {
state = {};

constructor(props) {
super(props);
Object.keys(this.props.options.presets).forEach(preset => {
this.state[preset] = {
page: 1,
isLoading: false,
isLoadingReferenceNodePath: false,
nodes: [],
moreNodesAvailable: true,
newReferenceNodePath: ''
};
});
}

makeResetNodes = preset => callback => {
this.setState({
[preset]: {
...this.state[preset],
page: 1,
nodes: [],
moreNodesAvailable: true
}
}, callback);
}

makeFetchNodes = preset => () => {
this.setState({
[preset]: {
...this.state[preset],
isLoading: true,
moreNodesAvailable: true
}
});
fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: `/flatnav/query?nodeContextPath=${this.props.siteNodeContextPath}&preset=${preset}&page=${this.state[preset].page}`,
method: 'GET',
credentials: 'include',
headers: {
'X-Flow-Csrftoken': csrfToken,
'Content-Type': 'application/json'
}
}))
.then(response => response && response.json())
.then(nodes => {
if (nodes.length > 0) {
const nodesMap = nodes.reduce((result, node) => {
result[node.contextPath] = node;
return result;
}, {});
this.props.merge(nodesMap);
this.setState({
[preset]: {
...this.state[preset],
page: this.state[preset].page + 1,
isLoading: false,
nodes: [...this.state[preset].nodes, ...Object.keys(nodesMap)],
moreNodesAvailable: true
}
});
} else {
this.setState({
[preset]: {
...this.state[preset],
isLoading: false,
moreNodesAvailable: false
}
});
}
});
};

makeGetNewReferenceNodePath = preset => () => {
this.setState({
[preset]: {
...this.state[preset],
isLoadingReferenceNodePath: true
}
});
fetchWithErrorHandling.withCsrfToken(csrfToken => ({
url: `/flatnav/getNewReferenceNodePath?nodeContextPath=${this.props.siteNodeContextPath}&preset=${preset}`,
method: 'GET',
credentials: 'include',
headers: {
'X-Flow-Csrftoken': csrfToken,
'Content-Type': 'application/json'
}
}))
.then(response => response && response.json())
.then(newReferenceNodePath => {
this.setState({
[preset]: {
...this.state[preset],
isLoading: false,
isLoadingReferenceNodePath: false,
newReferenceNodePath: newReferenceNodePath
}
});
});
};

render() {
return (
<Tabs>
{Object.keys(this.props.options.presets).map(presetName => {
const preset = this.props.options.presets[presetName];
return (
<Tabs.Panel key={presetName} icon={preset.icon} tooltip={preset.label}>
{preset.type === 'flat' && (<FlatNav
preset={preset}
fetchNodes={this.makeFetchNodes(presetName)}
resetNodes={this.makeResetNodes(presetName)}
fetchNewReferenceNodePath={this.makeGetNewReferenceNodePath(presetName)}
{...this.state[presetName]}
/>)}
{preset.type === 'tree' && (<OriginalPageTree />)}
</Tabs.Panel>
);
})}
</Tabs>
);
}
}
return neos(globalRegistry => ({
options: globalRegistry.get('frontendConfiguration').get('Psmb_FlatNav')
}))(connect($transform({
siteNodeContextPath: $get('cr.nodes.siteNode')
}), {
merge: actions.CR.Nodes.merge
})(FlatNavContainer));
};

export default makeFlatNavContainer;

@neos(globalRegistry => ({
nodeTypesRegistry: globalRegistry.get('@neos-project/neos-ui-contentrepository'),
serverFeedbackHandlers: globalRegistry.get('serverFeedbackHandlers')
serverFeedbackHandlers: globalRegistry.get('serverFeedbackHandlers'),
i18nRegistry: globalRegistry.get('i18n')
}))
@connect($transform({
nodeData: $get('cr.nodes.byContextPath'),
focused: $get('ui.pageTree.isFocused'),
siteNodeContextPath: $get('cr.nodes.siteNode')
siteNodeContextPath: $get('cr.nodes.siteNode'),
baseWorkspaceName: $get('cr.workspaces.personalWorkspace.baseWorkspace'),
publishableNodes: $get('cr.workspaces.personalWorkspace.publishableNodes')
}), {
setSrc: actions.UI.ContentCanvas.setSrc,
focus: actions.UI.PageTree.focus,
openNodeCreationDialog: actions.UI.NodeCreationDialog.open,
commenceNodeCreation: actions.CR.Nodes.commenceCreation,
selectNodeType: actions.UI.SelectNodeTypeModal.apply
selectNodeType: actions.UI.SelectNodeTypeModal.apply,
merge: actions.CR.Nodes.merge
})
class FlatNav extends Component {
export default class FlatNav extends Component {
static propTypes = {
nodes: PropTypes.array.isRequired,
preset: PropTypes.object.isRequired,
Expand All @@ -176,14 +41,20 @@ class FlatNav extends Component {
};

componentDidMount() {
this.populateTheState();
this.props.serverFeedbackHandlers.set('Neos.Neos.Ui:NodeCreated/DocumentAdded', this.handleNodeWasCreated, 'after Neos.Neos.Ui:NodeCreated/Main');
}

componentDidUpdate(prevProps) {
this.populateTheState();
}

populateTheState = () => {
if (this.props.nodes.length === 0) {
this.props.fetchNodes();
if (this.props.preset.newReferenceNodePath.indexOf('/') !== 0) {
this.props.fetchNewReferenceNodePath();
}
this.props.fetchNewReference();
}
this.props.serverFeedbackHandlers.set('Neos.Neos.Ui:NodeCreated/DocumentAdded', this.handleNodeWasCreated, 'after Neos.Neos.Ui:NodeCreated/Main');
}
};

handleNodeWasCreated = (feedbackPayload, {store}) => {
const state = store.getState();
Expand All @@ -197,40 +68,86 @@ class FlatNav extends Component {
}
}

createNode = () => {
buildNewReferenceNodePath = () => {
const context = this.props.siteNodeContextPath.split('@')[1];
const contextPath = (this.props.newReferenceNodePath || this.props.preset.newReferenceNodePath) + '@' + context;
this.props.commenceNodeCreation(contextPath);
this.props.selectNodeType('into', this.props.preset.newNodeType);
return this.props.newReferenceNodePath + '@' + context;
};

createNode = () => {
const contextPath = this.buildNewReferenceNodePath();
this.props.commenceNodeCreation(contextPath, undefined, 'into', this.props.preset.newNodeType || undefined);
}

refreshFlatNav = () => {
this.props.resetNodes(this.props.fetchNodes);
}

getNodeIconComponent(node) {
const nodeTypeName = $get('nodeType', node);
const nodeType = this.props.nodeTypesRegistry.getNodeType(nodeTypeName);
const isHidden = $get('properties._hidden', node);
const isHiddenBefore = $get('properties._hiddenBeforeDateTime', node);
const isHiddenAfter = $get('properties._hiddenAfterDateTime', node);

if (isHidden) {
return (
<span className="fa-layers fa-fw">
<Icon icon={$get('ui.icon', nodeType)} />
<Icon icon="circle" color="error" transform="shrink-3 down-6 right-4" />
<Icon icon="times" transform="shrink-7 down-6 right-4" />
</span>
);
}

if (isHiddenBefore || isHiddenAfter) {
return (
<span className="fa-layers fa-fw">
<Icon icon={$get('ui.icon', nodeType)} />
<Icon icon="circle" color="primaryBlue" transform="shrink-5 down-6 right-4" />
<Icon icon="clock" transform="shrink-9 down-6 right-4" />
</span>
);
}

return (
<Icon icon={$get('ui.icon', nodeType)} />
);
}

renderNodes = () => {
return this.props.nodes
.map(contextPath => {
const item = $get([contextPath], this.props.nodeData);

if (item) {
const nodeTypeName = $get('nodeType', item);
const nodeType = this.props.nodeTypesRegistry.getNodeType(nodeTypeName);
const isFocused = this.props.focused === contextPath;
const isDirty = this.props.publishableNodes.filter(i => (
$get('contextPath', i) === contextPath ||
$get('documentContextPath', i) === contextPath
)).count() > 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be length instead of count(), right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, since we stopped using immutable, i.e. 2.x+

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it here: #20

const isRemoved = $get('properties._removed', item);
const nodeIconComponent = this.getNodeIconComponent(item);

return (
<div
className={mergeClassNames({
[style.node]: true,
[style['node--focused']]: this.props.focused === contextPath
[style['node--focused']]: isFocused,
[style['node--dirty']]: isDirty,
[style['node--removed']]: isRemoved
})}
key={contextPath}
onClick={() => {
this.props.setSrc($get('uri', item));
this.props.focus(contextPath);
if ( ! isRemoved) {
this.props.setSrc($get('uri', item));
this.props.focus(contextPath);
}
}}
role="button"
>
<div
className={style.node__iconWrapper}>
<Icon icon={$get('ui.icon', nodeType)} />
{nodeIconComponent}
</div>
<span
className={style.node__label}>
Expand Down Expand Up @@ -267,7 +184,7 @@ class FlatNav extends Component {
spin={this.props.isLoading}
icon={this.props.isLoading ? 'spinner' : 'angle-double-down'}
/>
&nbsp;{this.props.isLoading ? 'Loading...' : 'Load more'}
&nbsp;{this.props.isLoading ? this.props.i18nRegistry.translate('Psmb.FlatNav:Main:loading') : this.props.i18nRegistry.translate('Psmb.FlatNav:Main:loadMore')}
</div>
</Button>)}
</div>
Expand Down