diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7b90428f3d..693a9f22f1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -46,8 +46,8 @@
"react-app-polyfill": "^2.0.0",
"react-chartjs-2": "5.3.0",
"react-cookies": "^0.1.0",
- "react-dnd": "^2.6.0",
- "react-dnd-html5-backend": "^2.6.0",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.3.1",
"react-i18next": "^10.12.2",
"react-mentions": "4.4.10",
@@ -3105,19 +3105,6 @@
}
}
},
- "node_modules/@emotion/react/node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
- "node_modules/@emotion/react/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
- },
"node_modules/@emotion/serialize": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
@@ -5390,6 +5377,21 @@
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
},
+ "node_modules/@react-dnd/asap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+ "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+ },
+ "node_modules/@react-dnd/invariant": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+ "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+ },
+ "node_modules/@react-dnd/shallowequal": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+ },
"node_modules/@replit/codemirror-lang-csharp": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/@replit/codemirror-lang-csharp/-/codemirror-lang-csharp-6.2.0.tgz",
@@ -8290,6 +8292,7 @@
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/asn1.js": {
@@ -11436,12 +11439,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/disposables": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/disposables/-/disposables-1.0.2.tgz",
- "integrity": "sha512-q1XTvs/XGdfubRSemB2+QRhJjIX4PerKkSom+i8Nkw3hCv6xISNrgaN442n2BunyBI4x77Om4ZAzSlqmhM9pwA==",
- "license": "Apache-2.0"
- },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
@@ -11450,15 +11447,13 @@
"license": "MIT"
},
"node_modules/dnd-core": {
- "version": "2.6.0",
- "resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-2.6.0.tgz",
- "integrity": "sha512-5BfQHIp0XVd4ioF0q4GyUeHQQNCbqP+0SnUiP9TssoQ50wrP1NgSzDqZkjD5pFngsVz9txGin6rvTQD7w0qC3w==",
- "license": "BSD-3-Clause",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+ "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
"dependencies": {
- "asap": "^2.0.6",
- "invariant": "^2.0.0",
- "lodash": "^4.2.0",
- "redux": "^3.7.1"
+ "@react-dnd/asap": "^5.0.1",
+ "@react-dnd/invariant": "^4.0.1",
+ "redux": "^4.2.0"
}
},
"node_modules/dns-packet": {
@@ -13247,7 +13242,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-glob": {
@@ -14913,10 +14907,17 @@
}
},
"node_modules/hoist-non-react-statics": {
- "version": "2.5.5",
- "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
- "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
- "license": "BSD-3-Clause"
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/hoopy": {
"version": "0.1.4",
@@ -23210,29 +23211,40 @@
}
},
"node_modules/react-dnd": {
- "version": "2.6.0",
- "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-2.6.0.tgz",
- "integrity": "sha512-2KHNpeg2SyaxXYq+xO1TM+tOtN9hViI41otJuiYiu6DRYGw+WMvDFDMP4aw7zIKRRm1xd0gizXuKWhb8iJYHBw==",
- "license": "BSD-3-Clause",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+ "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
"dependencies": {
- "disposables": "^1.0.1",
- "dnd-core": "^2.6.0",
- "hoist-non-react-statics": "^2.1.0",
- "invariant": "^2.1.0",
- "lodash": "^4.2.0",
- "prop-types": "^15.5.10"
+ "@react-dnd/invariant": "^4.0.1",
+ "@react-dnd/shallowequal": "^4.0.1",
+ "dnd-core": "^16.0.1",
+ "fast-deep-equal": "^3.1.3",
+ "hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
- "react": "*"
+ "@types/hoist-non-react-statics": ">= 3.3.1",
+ "@types/node": ">= 12",
+ "@types/react": ">= 16",
+ "react": ">= 16.14"
+ },
+ "peerDependenciesMeta": {
+ "@types/hoist-non-react-statics": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
}
},
"node_modules/react-dnd-html5-backend": {
- "version": "2.6.0",
- "resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.6.0.tgz",
- "integrity": "sha512-8gOfBfqFikWmXvAGSZz1mgoctwkcsKdUC9POt/WGnMoZwGB4ivB0Ex5D6pwHTNjvAs0ixqqWdJKy57CzjDg5Sg==",
- "license": "BSD-3-Clause",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+ "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
"dependencies": {
- "lodash": "^4.2.0"
+ "dnd-core": "^16.0.1"
}
},
"node_modules/react-dom": {
@@ -23577,15 +23589,11 @@
}
},
"node_modules/redux": {
- "version": "3.7.2",
- "resolved": "https://registry.npmmirror.com/redux/-/redux-3.7.2.tgz",
- "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
- "license": "MIT",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dependencies": {
- "lodash": "^4.2.1",
- "lodash-es": "^4.2.1",
- "loose-envify": "^1.1.0",
- "symbol-observable": "^1.0.3"
+ "@babel/runtime": "^7.9.2"
}
},
"node_modules/reflect.getprototypeof": {
@@ -27108,15 +27116,6 @@
"node": ">=4"
}
},
- "node_modules/symbol-observable": {
- "version": "1.2.0",
- "resolved": "https://registry.npmmirror.com/symbol-observable/-/symbol-observable-1.2.0.tgz",
- "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 9b809772fb..35b164bd52 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -41,8 +41,8 @@
"react-app-polyfill": "^2.0.0",
"react-chartjs-2": "5.3.0",
"react-cookies": "^0.1.0",
- "react-dnd": "^2.6.0",
- "react-dnd-html5-backend": "^2.6.0",
+ "react-dnd": "^16.0.1",
+ "react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.3.1",
"react-i18next": "^10.12.2",
"react-mentions": "4.4.10",
diff --git a/frontend/src/metadata/components/popover/groupbys-popover/groupbys/groupby-item.js b/frontend/src/metadata/components/popover/groupbys-popover/groupbys/groupby-item.js
index d2a91a1f8a..a059630371 100644
--- a/frontend/src/metadata/components/popover/groupbys-popover/groupbys/groupby-item.js
+++ b/frontend/src/metadata/components/popover/groupbys-popover/groupbys/groupby-item.js
@@ -1,7 +1,7 @@
-import React, { Fragment, useCallback, useMemo } from 'react';
+import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import { DragSource, DropTarget } from 'react-dnd';
+import { useDrag, useDrop } from 'react-dnd';
import CustomizeSelect from '../../../../../components/customize-select';
import Icon from '../../../../../components/icon';
import { gettext } from '../../../../../utils/constants';
@@ -9,49 +9,6 @@ import { getColumnByKey } from '../../../../utils/column';
import { COLUMNS_ICON_CONFIG, SORT_TYPE, SORT_COLUMN_OPTIONS } from '../../../../constants';
import { getGroupbyGranularityByColumn, isShowGroupCountType, getSelectedCountType, getDefaultCountType } from '../../../../utils/group';
-const dragSource = {
- beginDrag: props => {
- return { idx: props.index, data: props.groupby, mode: 'sfMetadataGroupbyItem' };
- },
- endDrag(props, monitor) {
- const groupSource = monitor.getItem();
- const didDrop = monitor.didDrop();
- let groupTarget = {};
- if (!didDrop) {
- return { groupSource, groupTarget };
- }
- },
- isDragging(props) {
- const { index, dragged } = props;
- const { idx } = dragged;
- return idx > index;
- }
-};
-
-const dropTarget = {
- drop(props, monitor) {
- const groupSource = monitor.getItem();
- const { index: targetIdx } = props;
- if (targetIdx !== groupSource.idx) {
- let groupTarget = { idx: targetIdx, data: props.groupby };
- props.onMove(groupSource, groupTarget);
- }
- }
-};
-
-const dragCollect = (connect, monitor) => ({
- connectDragSource: connect.dragSource(),
- connectDragPreview: connect.dragPreview(),
- isDragging: monitor.isDragging(),
-});
-
-const dropCollect = (connect, monitor) => ({
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver(),
- canDrop: monitor.canDrop(),
- dragged: monitor.getItem(),
-});
-
/*
groupby: {
column_key: 'xxx',
@@ -59,10 +16,46 @@ const dropCollect = (connect, monitor) => ({
sort_type: 'xxx',
}
*/
-const GroupbyItem = ({
- isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget,
- showDragBtn, index, readOnly, groupby, columns, onDelete, onUpdate
-}) => {
+const GroupbyItem = ({ showDragBtn, index, readOnly, groupby, columns, onDelete, onUpdate, onMove }) => {
+ const ref = useRef(null);
+
+ const [dropPosition, setDropPosition] = useState(null);
+
+ const [, drag, preview] = useDrag({
+ type: 'sfMetadataGroupbyItem',
+ item: () => ({
+ idx: index,
+ data: groupby,
+ }),
+ });
+
+ const [{ isOver, canDrop }, drop] = useDrop({
+ accept: 'sfMetadataGroupbyItem',
+ hover: (item, monitor) => {
+ if (!ref.current) return;
+
+ const hoverBoundingRect = ref.current.getBoundingClientRect();
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
+ const clientOffset = monitor.getClientOffset();
+ const hoverClientY = clientOffset.y - hoverBoundingRect.top;
+
+ const newPosition = hoverClientY < hoverMiddleY ? 'top' : 'bottom';
+ setDropPosition(newPosition);
+ },
+ drop: (item) => {
+ if (item.idx === index) return;
+ if (item.idx === index - 1 && dropPosition === 'top') return;
+ if (item.idx === index + 1 && dropPosition === 'bottom') return;
+ onMove(item, { idx: index, data: groupby });
+ },
+ collect: (monitor) => ({
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ })
+ });
+
+ drop(preview(ref));
+
const column = useMemo(() => {
return getColumnByKey(columns, groupby.column_key);
}, [groupby, columns]);
@@ -165,59 +158,58 @@ const GroupbyItem = ({
onUpdate(newGroupby, index);
}, [groupby, index, onUpdate]);
- return connectDropTarget(
- connectDragPreview(
-
- {!readOnly && (
-
-
-
- )}
-
-
+ return (
+
+ {!readOnly && (
+
+
+
+ )}
+
+
+
+
+ {isShowGroupCountType(column) && (
+
- {isShowGroupCountType(column) && (
-
-
-
- )}
-
- {(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && (
-
- )}
-
-
- {!readOnly && showDragBtn && connectDragSource(
-
-
-
)}
+
+ {(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && (
+
+ )}
+
- )
+ {!readOnly && showDragBtn && (
+
+
+
+ )}
+
);
};
@@ -229,16 +221,7 @@ GroupbyItem.propTypes = {
columns: PropTypes.array,
onDelete: PropTypes.func,
onUpdate: PropTypes.func,
-
- // drag
- isDragging: PropTypes.bool,
- isOver: PropTypes.bool,
- canDrop: PropTypes.bool,
- connectDropTarget: PropTypes.func,
- connectDragSource: PropTypes.func,
- connectDragPreview: PropTypes.func,
+ onMove: PropTypes.func,
};
-export default DropTarget('sfMetadataGroupbyItem', dropTarget, dropCollect)(
- DragSource('sfMetadataGroupbyItem', dragSource, dragCollect)(GroupbyItem)
-);
+export default GroupbyItem;
diff --git a/frontend/src/metadata/components/popover/groupbys-popover/groupbys/index.js b/frontend/src/metadata/components/popover/groupbys-popover/groupbys/index.js
index 8cd808133a..b546d95fb6 100644
--- a/frontend/src/metadata/components/popover/groupbys-popover/groupbys/index.js
+++ b/frontend/src/metadata/components/popover/groupbys-popover/groupbys/index.js
@@ -1,8 +1,6 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import { DropTarget } from 'react-dnd';
-import html5DragDropContext from '../../../../../pages/wiki2/wiki-nav/html5DragDropContext';
import GroupbyItem from './groupby-item';
import { gettext } from '../../../../../utils/constants';
@@ -47,8 +45,4 @@ Groupbys.propTypes = {
onMove: PropTypes.func,
};
-const DndGroupbysContainer = DropTarget('sfMetadataGroupbyItem', {}, connect => ({
- connectDropTarget: connect.dropTarget()
-}))(Groupbys);
-
-export default html5DragDropContext(DndGroupbysContainer);
+export default Groupbys;
diff --git a/frontend/src/metadata/components/popover/groupbys-popover/index.css b/frontend/src/metadata/components/popover/groupbys-popover/index.css
index 2defd503ee..65a5b45dfc 100644
--- a/frontend/src/metadata/components/popover/groupbys-popover/index.css
+++ b/frontend/src/metadata/components/popover/groupbys-popover/index.css
@@ -27,19 +27,28 @@
position: relative;
}
-.sf-metadata-groupbys-popover .groupbys-list .groupby-item.group-can-drop {
- border-bottom: 1px solid #666;
-}
-
.sf-metadata-groupbys-popover .groupbys-list .groupby-item.group-can-drop-top::before {
content: '';
- width: 100%;
- height: 1px;
- background: #666;
- display: block;
position: absolute;
top: 0;
left: 0;
+ width: 100%;
+ height: 2px;
+ background-color: rgb(200, 220, 240);
+ border-radius: 2px;
+ z-index: 1;
+}
+
+.sf-metadata-groupbys-popover .groupbys-list .groupby-item.group-can-drop-bottom::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background-color: rgb(200, 220, 240);
+ border-radius: 2px;
+ z-index: 1;
}
.sf-metadata-groupbys-popover .groupby-item .delete-groupby {
diff --git a/frontend/src/metadata/components/popover/options-popover/option/index.css b/frontend/src/metadata/components/popover/options-popover/option/index.css
index 310cf6f8f7..5f83cb5f26 100644
--- a/frontend/src/metadata/components/popover/options-popover/option/index.css
+++ b/frontend/src/metadata/components/popover/options-popover/option/index.css
@@ -49,19 +49,28 @@
fill: #212529;
}
-.sf-metadata-edit-option-container.sf-metadata-edit-option-can-drop {
- border-bottom: 1px solid #666;
-}
-
-.sf-metadata-edit-option-container.sf-metadata-edit-option-can-drop-top::before {
+.sf-metadata-edit-option-container.sf-metadata-edit-option-drop-over-top::before {
content: '';
- width: 100%;
- height: 1px;
- background: #666;
- display: block;
position: absolute;
top: 0;
left: 0;
+ width: 100%;
+ height: 2px;
+ background-color: rgb(200, 220, 240);
+ border-radius: 2px;
+ z-index: 1;
+}
+
+.sf-metadata-edit-option-container.sf-metadata-edit-option-drop-over-bottom::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background-color: rgb(200, 220, 240);
+ border-radius: 2px;
+ z-index: 1;
}
.sf-metadata-edit-option-container.sf-metadata-edit-deleting-option,
diff --git a/frontend/src/metadata/components/popover/options-popover/option/index.js b/frontend/src/metadata/components/popover/options-popover/option/index.js
index 6f0d36ec61..ecdb5ae916 100644
--- a/frontend/src/metadata/components/popover/options-popover/option/index.js
+++ b/frontend/src/metadata/components/popover/options-popover/option/index.js
@@ -1,6 +1,6 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
-import { DragSource, DropTarget } from 'react-dnd';
+import { useDrag, useDrop } from 'react-dnd';
import classnames from 'classnames';
import Icon from '../../../../../components/icon';
import IconBtn from '../../../../../components/icon-btn';
@@ -9,56 +9,49 @@ import Name from './name';
import './index.css';
-const dragSource = {
- beginDrag: props => {
- return { idx: props.index, data: props.option, mode: 'sfMetadataSingleSelectOption' };
- },
- endDrag(props, monitor) {
- const optionSource = monitor.getItem();
- const didDrop = monitor.didDrop();
- let optionTarget = {};
- if (!didDrop) {
- return { optionSource, optionTarget };
- }
- },
- isDragging(props, monitor) {
- const { index, dragged } = props;
- const { idx } = dragged;
- return idx > index;
- }
-};
-
-const dragCollect = (connect, monitor) => ({
- connectDragSource: connect.dragSource(),
- connectDragPreview: connect.dragPreview(),
- isDragging: monitor.isDragging()
-});
-
-const dropTarget = {
- drop(props, monitor) {
- const optionSource = monitor.getItem();
- const { index: targetIdx } = props;
- if (targetIdx !== optionSource.idx) {
- const optionTarget = { idx: targetIdx, data: props.option };
- props.onMove(optionSource, optionTarget);
- }
- }
-};
-
-const dropCollect = (connect, monitor) => ({
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver(),
- canDrop: monitor.canDrop(),
- dragged: monitor.getItem()
-});
-
const Option = ({
- isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget,
isViewing, isDeleting, isEditing, isPredefined,
- option,
- onDelete: propsDelete, onUpdate,
+ option, index,
+ onDelete: propsDelete, onUpdate, onMove,
onMouseLeave, onMouseEnter: propsMouseEnter, onToggleFreeze, onOpenNameEditor, onCloseNameEditor,
}) => {
+ const ref = useRef(null);
+ const [dropPosition, setDropPosition] = useState(null);
+
+ const [, drag, preview] = useDrag({
+ type: 'sfMetadataSingleSelectOption',
+ item: () => ({
+ idx: index,
+ data: option,
+ }),
+ });
+
+ const [{ isOver, canDrop }, drop] = useDrop({
+ accept: 'sfMetadataSingleSelectOption',
+ hover: (item, monitor) => {
+ if (!ref.current) return;
+
+ const hoverBoundingRect = ref.current.getBoundingClientRect();
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
+ const clientOffset = monitor.getClientOffset();
+ const hoverClientY = clientOffset.y - hoverBoundingRect.top;
+
+ const newPosition = hoverClientY < hoverMiddleY ? 'top' : 'bottom';
+ setDropPosition(newPosition);
+ },
+ drop: (item) => {
+ if (item.idx === index) return;
+ if (item.idx === index - 1 && dropPosition === 'top') return;
+ if (item.idx === index + 1 && dropPosition === 'bottom') return;
+ onMove(item, { idx: index, data: option });
+ },
+ collect: (monitor) => ({
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ })
+ });
+
+ drop(preview(ref));
const onDelete = useCallback((event) => {
event.nativeEvent.stopImmediatePropagation();
@@ -69,49 +62,45 @@ const Option = ({
propsMouseEnter(option.id);
}, [option, propsMouseEnter]);
- return connectDropTarget(
- connectDragPreview(
-
onMouseEnter()}
- onMouseLeave={onMouseLeave}
- >
- {connectDragSource(
-
-
-
- )}
-
-
-
-
-
- {(isViewing || isDeleting) && (
-
- )}
-
+ return (
+
onMouseEnter()}
+ onMouseLeave={onMouseLeave}
+ >
+
+
- )
+
+
+
+
+
+ {(isViewing || isDeleting) && (
+
+ )}
+
+
);
};
Option.propTypes = {
- // normal
option: PropTypes.object,
index: PropTypes.number,
isPredefined: PropTypes.bool,
@@ -126,16 +115,6 @@ Option.propTypes = {
onToggleFreeze: PropTypes.func,
onOpenNameEditor: PropTypes.func.isRequired,
onCloseNameEditor: PropTypes.func.isRequired,
-
- // drag
- isOver: PropTypes.bool,
- canDrop: PropTypes.bool,
- dragged: PropTypes.object,
- connectDragSource: PropTypes.func.isRequired,
- connectDropTarget: PropTypes.func.isRequired,
- connectDragPreview: PropTypes.func.isRequired,
};
-export default DropTarget('sfMetadataSingleSelectOption', dropTarget, dropCollect)(
- DragSource('sfMetadataSingleSelectOption', dragSource, dragCollect)(Option)
-);
+export default Option;
diff --git a/frontend/src/metadata/components/popover/options-popover/options-container.js b/frontend/src/metadata/components/popover/options-popover/options-container.js
index 04b9db553d..44d2c2768a 100644
--- a/frontend/src/metadata/components/popover/options-popover/options-container.js
+++ b/frontend/src/metadata/components/popover/options-popover/options-container.js
@@ -1,7 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { DropTarget } from 'react-dnd';
-import html5DragDropContext from '../../../../pages/wiki2/wiki-nav/html5DragDropContext';
const OptionsContainer = ({ inputRef, options }) => {
if (!Array.isArray(options) || options.length === 0) return null;
@@ -17,8 +15,4 @@ OptionsContainer.propTypes = {
options: PropTypes.array
};
-const DndOptionsContainer = DropTarget('sfMetadataSingleSelectOption', {}, connect => ({
- connectDropTarget: connect.dropTarget()
-}))(OptionsContainer);
-
-export default html5DragDropContext(DndOptionsContainer);
+export default OptionsContainer;
diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js
index 39087421b2..e8fe4833d7 100644
--- a/frontend/src/pages/lib-content-view/lib-content-view.js
+++ b/frontend/src/pages/lib-content-view/lib-content-view.js
@@ -7,6 +7,8 @@ import classnames from 'classnames';
import MediaQuery from 'react-responsive';
import { Modal } from 'reactstrap';
import { navigate } from '@gatsbyjs/reach-router';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
import { gettext, siteRoot, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
@@ -2352,89 +2354,90 @@ class LibContentView extends React.Component {
}
const detailDirent = currentDirent || currentNode?.object || null;
return (
-
-
-
-
-
-
- {this.state.currentRepoInfo.status === 'read-only' &&
+
+
+
+
+
+
+
+ {this.state.currentRepoInfo.status === 'read-only' &&
{gettext('This library has been set to read-only by admin and cannot be updated.')}
- }
-
-
- {isDirentSelected ? (
- currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
-
+ }
+
+
+ {isDirentSelected ? (
+ currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
+
+ ) : (
+
+ )
) : (
-
- )
- ) : (
-
- )}
-
- {isDesktop &&
+ )}
+
+ {isDesktop &&
- }
-
-
- {this.state.pathExist ?
-
- :
-
{gettext('Folder does not exist.')}
- }
- {!isCustomPermission && this.state.isDirentDetailShow && (
-
- )}
+ }
+
+
+ {this.state.pathExist ?
+
+ :
+
{gettext('Folder does not exist.')}
+ }
+ {!isCustomPermission && this.state.isDirentDetailShow && (
+
+ )}
+
+ {canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
+
this.uploader = uploader}
+ dragAndDrop={true}
+ path={this.state.path}
+ repoID={this.props.repoID}
+ direntList={this.state.direntList}
+ onFileUploadSuccess={this.onFileUploadSuccess}
+ isCustomPermission={isCustomPermission}
+ />
+ )}
- {canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
-
this.uploader = uploader}
- dragAndDrop={true}
- path={this.state.path}
- repoID={this.props.repoID}
- direntList={this.state.direntList}
- onFileUploadSuccess={this.onFileUploadSuccess}
- isCustomPermission={isCustomPermission}
+ {isCopyMoveProgressDialogShow && (
+
)}
-
- {isCopyMoveProgressDialogShow && (
-
- )}
- {isDeleteFolderDialogOpen && (
-
- )}
-
-
-
-
-
-
-
+ {isDeleteFolderDialogOpen && (
+
+ )}
+
+
+
+
+
+
+
+
);
}
}
diff --git a/frontend/src/pages/wiki2/wiki-nav/html5DragDropContext.js b/frontend/src/pages/wiki2/wiki-nav/html5DragDropContext.js
deleted file mode 100644
index a9708ef978..0000000000
--- a/frontend/src/pages/wiki2/wiki-nav/html5DragDropContext.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import { DragDropContext } from 'react-dnd';
-import HTML5Backend from 'react-dnd-html5-backend';
-
-export default DragDropContext(HTML5Backend);
diff --git a/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js b/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js
deleted file mode 100644
index 7410a37358..0000000000
--- a/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { DragSource, DropTarget } from 'react-dnd';
-import PageItem from './page-item';
-import wikiAPI from '../../../../utils/wiki-api';
-import { wikiId, gettext } from '../../../../utils/constants';
-import { Utils } from '../../../../utils/utils';
-import toaster from '../../../../components/toast';
-
-const dragSource = {
- beginDrag: props => {
- return {
- idx: props.pageIndex,
- data: { ...props.page, index: props.pageIndex },
- mode: 'wiki-page',
- };
- },
- endDrag(props, monitor) {
- const viewSource = monitor.getItem();
- const didDrop = monitor.didDrop();
- let viewTarget = {};
- if (!didDrop) {
- return { viewSource, viewTarget };
- }
- },
- isDragging(props) {
- const { draggedPage, pageIndex: targetIndex } = props;
- const { idx } = draggedPage;
- return idx > targetIndex;
- }
-};
-
-const dropTarget = {
- drop(props, monitor) {
- const dragSource = monitor.getItem();
- const className = props.getClassName();
- let move_position;
- if (className.includes('page-can-drop-bottom')) {
- move_position = 'move_below';
- } else if (className.includes('page-can-drop-top')) {
- move_position = 'move_above';
- } else if (className.includes('dragged-page-over')) {
- move_position = 'move_into';
- }
- if (!move_position) return;
- if (dragSource.mode === 'wiki-page') {
- const targetPage = props.page;
- const draggedPage = dragSource.data;
- const moved_page_id = draggedPage.id;
- const target_page_id = targetPage.id;
- if (moved_page_id === target_page_id) {
- return;
- }
- if (targetPage._path && targetPage._path.includes(moved_page_id)) {
- toaster.danger(gettext('Cannot move parent page to child page'));
- return;
- }
- wikiAPI.moveWiki2Page(wikiId, moved_page_id, target_page_id, move_position).then(res => {
- props.onMovePage({ moved_page_id, target_page_id, move_position });
- }).catch((error) => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
- return;
- }
-};
-
-const dragCollect = (connect, monitor) => ({
- connectDragSource: connect.dragSource(),
- connectDragPreview: connect.dragPreview(),
- isDragging: monitor.isDragging()
-});
-
-const dropCollect = (connect, monitor) => ({
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver(),
- canDrop: monitor.canDrop(),
- draggedPage: monitor.getItem()
-});
-
-export default DropTarget('WikiNav', dropTarget, dropCollect)(
- DragSource('WikiNav', dragSource, dragCollect)(PageItem)
-);
diff --git a/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js b/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js
index 05d830cc5e..3d1e46f84d 100644
--- a/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js
+++ b/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js
@@ -1,354 +1,413 @@
-import React, { Component } from 'react';
+import React, { useState, useRef, useEffect, useCallback } from 'react';
+import { useDrag, useDrop } from 'react-dnd';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import NameEditPopover from '../../common/name-edit-popover';
import NavItemIcon from '../../common/nav-item-icon';
import PageDropdownMenu from './page-dropdownmenu';
-import { gettext, wikiPermission } from '../../../../utils/constants';
+import { gettext, wikiId, wikiPermission } from '../../../../utils/constants';
import AddNewPageDialog from '../add-new-page-dialog';
import Icon from '../../../../components/icon';
-import DraggedPageItem from './dragged-page-item';
import CustomIcon from '../../custom-icon';
import { eventBus } from '../../../../components/common/event-bus';
import { INSERT_POSITION } from '../constants';
+import toaster from '../../../../components/toast';
+import wikiAPI from '../../../../utils/wiki-api';
+import { Utils } from '../../../../utils/utils';
-class PageItem extends Component {
+const PageItem = ({
+ page,
+ pageIndex,
+ pages,
+ pagesLength,
+ parentPageId,
+ duplicatePage,
+ setCurrentPage,
+ onUpdatePage,
+ onDeletePage,
+ onMovePage,
+ isOnlyOnePage,
+ pathStr,
+ toggleExpand,
+ getCurrentPageId,
+ addSiblingPage,
+ addPageInside,
+ getFoldState,
+ updateWikiConfig,
+ getClassName,
+ setClassName,
+}) => {
+ const [isShowNameEditor, setIsShowNameEditor] = useState(false);
+ const [isShowOperationDropdown, setIsShowOperationDropdown] = useState(false);
+ const [isShowInsertPage, setIsShowInsertPage] = useState(false);
+ const [isShowAddSiblingPage, setIsShowAddSiblingPage] = useState(false);
+ const [insertPosition, setInsertPosition] = useState('');
+ const [dropPosition, setDropPosition] = useState(null);
+ const [pageName, setPageName] = useState(page.name || '');
+ const [isSelected, setIsSelected] = useState(page.id === getCurrentPageId());
+ const [isMouseEntered, setIsMouseEntered] = useState(false);
- constructor(props) {
- super(props);
- this.state = {
- isShowNameEditor: false,
- isShowOperationDropdown: false,
- isShowInsertPage: false,
- isShowAddSiblingPage: false,
- insertPosition: '',
- pageName: props.page.name || '',
- isSelected: props.getCurrentPageId() === props.page.id,
- isMouseEnter: false,
- };
- this.pageItemRef = React.createRef();
- }
+ const ref = useRef(null);
- componentDidMount() {
- this.unsubscribeEvent = eventBus.subscribe('update-wiki-current-page', this.updateSelected);
- }
+ const [, drag] = useDrag(() => ({
+ type: 'wiki-page',
+ item: () => ({
+ idx: pageIndex,
+ data: { ...page, index: pageIndex },
+ }),
+ }));
- componentWillUnmount() {
- this.unsubscribeEvent();
- this.setState = () => {
- return;
- };
- }
+ const [{ isOver, canDrop }, drop] = useDrop(() => ({
+ accept: 'wiki-page',
+ hover: (item, monitor) => {
+ if (!ref.current) return;
+ const hoverBoundingRect = ref.current.getBoundingClientRect();
+ const height = hoverBoundingRect.bottom - hoverBoundingRect.top;
+ const clientOffset = monitor.getClientOffset();
+ const hoverClientY = clientOffset.y - hoverBoundingRect.top;
+ if (hoverClientY < 10) {
+ setDropPosition('top');
+ item.dropPosition = 'move_above';
+ } else if (hoverClientY > height - 10) {
+ setDropPosition('bottom');
+ item.dropPosition = 'move_below';
+ } else {
+ setDropPosition(null);
+ item.dropPosition = 'move_into';
+ }
+ },
+ drop: (item, monitor) => {
+ const target = page;
+ const source = item.data;
+ const moved_page_id = source.id;
+ const target_page_id = target.id;
- updateSelected = () => {
- const isSelected = this.props.getCurrentPageId() === this.props.page.id;
- if (isSelected !== this.state.isSelected) {
- this.setState({ isSelected });
+ if (moved_page_id === target_page_id) return;
+
+ if (target._path && target._path.includes(moved_page_id)) {
+ toaster.danger(gettext('Cannot move parent page to child page'));
+ return;
+ }
+
+ const move_position = item.dropPosition || 'move_into';
+ wikiAPI.moveWiki2Page(wikiId, moved_page_id, target_page_id, move_position).then(res => {
+ onMovePage({ moved_page_id, target_page_id, move_position });
+ setDropPosition(null);
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ },
+ collect: (monitor) => ({
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop()
+ })
+ }));
+
+ drag(drop(ref));
+
+ const onMouseEnter = () => {
+ setIsMouseEntered(true);
+ };
+
+ const onMouseMove = () => {
+ if (!isMouseEntered) {
+ setIsMouseEntered(true);
}
};
- onMouseEnter = () => {
- this.setState({ isMouseEnter: true });
- if (this.state.isSelected) return;
+ const onMouseLeave = () => {
+ setIsMouseEntered(false);
};
- onMouseMove = () => {
- if (!this.state.isMouseEnter) this.setState({ isMouseEnter: true });
+ const onClickPageItem = () => {
+ if (!isShowNameEditor) {
+ setCurrentPage(page.id);
+ }
};
- onMouseLeave = () => {
- this.setState({ isMouseEnter: false });
- if (this.state.isSelected) return;
+ const onChangeName = (name) => {
+ setPageName(name);
};
- toggleNameEditor = (e) => {
- if (e) e.stopPropagation();
- this.setState({ isShowNameEditor: !this.state.isShowNameEditor }, () => {
- if (!this.state.isShowNameEditor) {
- this.savePageProperties();
+ const savePageProperties = useCallback(() => {
+ const { name, id } = page;
+ const currentPageName = pageName.trim();
+ if (currentPageName !== name) {
+ const isUpdateBySide = true;
+ onUpdatePage(id, { name: currentPageName }, isUpdateBySide);
+ }
+ }, [page, pageName, onUpdatePage]);
+
+ const toggleNameEditor = useCallback((e) => {
+ if (e) {
+ e.stopPropagation();
+ }
+ setIsShowNameEditor(!isShowNameEditor, () => {
+ if (!isShowNameEditor) {
+ savePageProperties();
}
});
+ }, [isShowNameEditor, savePageProperties]);
+
+ const changeItemFreeze = (isFreeze) => {
+ if (isFreeze) {
+ ref.current.classList.add('wiki-page-freezed');
+ } else {
+ ref.current.classList.remove('wiki-page-freezed');
+ }
};
- toggleInsertPage = () => {
- this.setState({ isShowInsertPage: !this.state.isShowInsertPage });
- };
+ const toggleDropdown = useCallback(() => {
+ const isShow = !isShowOperationDropdown;
+ setIsShowOperationDropdown(isShow);
+ changeItemFreeze(isShow);
+ }, [isShowOperationDropdown]);
- toggleInsertSiblingPage = (position) => {
+ const toggleInsertSiblingPage = useCallback((position) => {
let insertPosition = null;
if (position === INSERT_POSITION.BELOW || position === INSERT_POSITION.ABOVE) {
insertPosition = position;
}
- this.setState({
- insertPosition,
- isShowAddSiblingPage: !this.state.isShowAddSiblingPage,
- });
- };
+ setInsertPosition(insertPosition);
+ setIsShowAddSiblingPage(!isShowAddSiblingPage);
+ }, [isShowAddSiblingPage]);
- savePageProperties = () => {
- const { name, id } = this.props.page;
- const pageName = this.state.pageName.trim();
- if (pageName !== name) {
- const isUpdateBySide = true;
- this.props.onUpdatePage(id, { name: pageName }, isUpdateBySide);
+ const toggleInsertPage = useCallback(() => {
+ setIsShowInsertPage(!isShowInsertPage);
+ }, [isShowInsertPage]);
+
+ const onAddNewPage = useCallback((newPage) => {
+ const folded = getFoldState(page.id);
+ if (folded) {
+ toggleExpand(page.id);
}
- };
+ addPageInside(Object.assign({ parentPageId: page.id }, newPage));
+ }, [page, addPageInside, getFoldState, toggleExpand]);
- onChangeName = (newName) => {
- this.setState({ pageName: newName });
- };
+ const onAddSiblingPage = useCallback((newPage) => {
+ addSiblingPage(newPage, parentPageId, insertPosition, page.id, toggleInsertSiblingPage);
+ }, [page, parentPageId, addSiblingPage, insertPosition, toggleInsertSiblingPage]);
- toggleDropdown = () => {
- const isShowOperationDropdown = !this.state.isShowOperationDropdown;
- this.setState({ isShowOperationDropdown });
- this.changeItemFreeze(isShowOperationDropdown);
- };
-
- changeItemFreeze = (isFreeze) => {
- if (isFreeze) {
- this.pageItemRef.classList.add('wiki-page-freezed');
- } else {
- this.pageItemRef.classList.remove('wiki-page-freezed');
+ const updateSelected = useCallback(() => {
+ const isCurrentSelected = getCurrentPageId() === page.id;
+ if (isSelected !== isCurrentSelected) {
+ setIsSelected(isCurrentSelected);
}
- };
+ }, [page, isSelected, getCurrentPageId]);
- setDocUuid = (docUuid) => {
- window.seafile['docUuid'] = docUuid;
- };
+ useEffect(() => {
+ const unsubscribeUpdateCurrentPage = eventBus.subscribe('update-wiki-current-page', updateSelected);
- getPageChildrenStyle = () => {
- const folded = this.props.getFoldState(this.props.page.id);
- return folded ? { height: 0, overflowY: 'hidden' } : { height: 'auto', overflowY: 'visible' };
- };
+ return () => {
+ unsubscribeUpdateCurrentPage();
+ };
+ }, [updateSelected]);
- onClickPageChildren = (e) => {
- e.stopPropagation();
- e.nativeEvent.stopImmediatePropagation();
- };
-
- renderPage = (page, index, pagesLength, isOnlyOnePage) => {
+ const renderPage = (page, index, pagesLength, isOnlyOnePage) => {
if (!page) return;
- const { pages, pathStr } = this.props;
- const id = page.id;
- if (!pages.find(item => item.id === id)) return;
+ if (!pages.find(item => item.id === page.id)) return;
return (
-
item.id === id), page)}
+ item.id === page.id), page)}
pageIndex={index}
- parentPageId={this.props.page.id}
- duplicatePage={this.props.duplicatePage}
- setCurrentPage={this.props.setCurrentPage}
- onUpdatePage={this.props.onUpdatePage}
- onDeletePage={this.props.onDeletePage}
- onMovePage={this.props.onMovePage}
- updateWikiConfig={this.props.updateWikiConfig}
pages={pages}
+ pagesLength={pagesLength}
+ parentPageId={page.id}
+ isOblyOnePage={isOnlyOnePage}
+ duplicatePage={duplicatePage}
+ setCurrentPage={setCurrentPage}
+ onUpdatePage={onUpdatePage}
+ onDeletePage={onDeletePage}
+ onMovePage={onMovePage}
+ updateWikiConfig={updateWikiConfig}
pathStr={pathStr + '-' + page.id}
- getCurrentPageId={this.props.getCurrentPageId}
- addPageInside={this.props.addPageInside}
- getFoldState={this.props.getFoldState}
- toggleExpand={this.props.toggleExpand}
- setClassName={this.props.setClassName}
- getClassName={this.props.getClassName}
- layerDragProps={this.props.layerDragProps}
- addSiblingPage={this.props.addSiblingPage}
+ getCurrentPageId={getCurrentPageId}
+ addPageInside={addPageInside}
+ addSiblingPage={addSiblingPage}
+ getFoldState={getFoldState}
+ toggleExpand={toggleExpand}
+ setClassName={setClassName}
+ getClassName={getClassName}
/>
);
};
- toggleExpand = (e) => {
- e.stopPropagation();
- e.nativeEvent.stopImmediatePropagation();
- this.props.toggleExpand(this.props.page.id);
- this.forceUpdate();
+ const childNumber = Array.isArray(page.children) ? page.children.length : 0;
+ const customIcon = page.icon;
+ const navItemId = `page-editor-${page.id}`;
+ const pageChildrenStyle = getFoldState(page.id) ? {
+ height: 0, overflowY: 'hidden'
+ } : {
+ height: 'auto', overflowY: 'visible'
};
- onClickPageItem = () => {
- if (!this.state.isShowNameEditor) {
- this.props.setCurrentPage(this.props.page.id);
- }
- };
-
- onAddNewPage = (newPage) => {
- const { page } = this.props;
- const folded = this.props.getFoldState(page.id);
- if (folded) {
- this.props.toggleExpand(page.id);
- }
- this.props.addPageInside(Object.assign({ parentPageId: this.props.page.id }, newPage));
- };
-
- onAddSiblingPage = (newPage) => {
- const { page } = this.props;
- this.props.addSiblingPage(newPage, this.props.parentPageId, this.state.insertPosition, page.id, this.toggleInsertSiblingPage);
- };
-
- getPageClassName = () => {
- const { isOver, canDrop, layerDragProps } = this.props;
- const isOverPage = isOver && canDrop;
- const readonly = wikiPermission === 'r' || wikiPermission === 'public';
- if (!isOverPage || ! layerDragProps || !layerDragProps.clientOffset) {
- return classnames('wiki-page-item', { 'selected-page': this.state.isSelected }, { 'readonly': readonly });
- }
- let y = layerDragProps.clientOffset.y;
- let top = this.pageItemRef.getBoundingClientRect().y;
- const className = classnames(
- 'wiki-page-item',
- { 'dragged-page-over': (top + 10 < y && y < top + 30) },
- { 'page-can-drop-top': (top + 10 > y) },
- { 'page-can-drop-bottom': (top + 30 < y) },
- { 'selected-page': this.state.isSelected },
- { 'readonly': readonly },
- );
- this.props.setClassName(className);
- return className;
- };
-
- render() {
- const { connectDragSource, connectDragPreview, connectDropTarget, page, pagesLength, isOnlyOnePage, pathStr } = this.props;
- const { isShowNameEditor, pageName, isSelected, isMouseEnter } = this.state;
- if (isSelected) this.setDocUuid(page.docUuid);
- let navItemId = `page-editor-${page.id}`;
- let childNumber = Array.isArray(page.children) ? page.children.length : 0;
- const customIcon = page.icon;
- const folded = this.props.getFoldState(page.id);
- if (wikiPermission === 'rw') {
- return (
-
- {connectDragSource(connectDropTarget(connectDragPreview(
+ if (wikiPermission === 'rw') {
+ return (
+ <>
+
+
this.pageItemRef = ref}
- onMouseEnter={this.onMouseEnter}
- onMouseMove={this.onMouseMove}
- onMouseLeave={this.onMouseLeave}
- id={navItemId}
+ className="wiki-page-content"
+ style={pathStr ? {
+ marginLeft: (pathStr.split('-').length - 1) * 24
+ } : {}}
>
-
-
- {childNumber === 0 && (customIcon ?
:
)
- }
- {(!isMouseEnter && childNumber > 0) && (customIcon ?
:
)}
- {(isMouseEnter && childNumber > 0) &&
-
-
-
- }
-
{page.name}
- {isShowNameEditor && (
-
- )}
+ {childNumber === 0 && (customIcon ? (
+
+ ) : (
+
+ ))}
+ {(!isMouseEntered && childNumber > 0) && (customIcon ? (
+
+ ) : (
+
+ ))}
+ {(isMouseEntered && childNumber > 0) && (
+
toggleExpand(page.id)}>
+
-
-
-
-
- {this.state.isShowOperationDropdown &&
-
- }
-
-
-
-
-
- {this.state.isShowInsertPage &&
-
{page.name}
+ {isShowNameEditor && (
+
- }
- {this.state.isShowAddSiblingPage &&
-
+
+
+
+
+ {isShowOperationDropdown && (
+
onDeletePage(page.id)}
+ toggleInsertSiblingPage={toggleInsertSiblingPage}
/>
- }
+ )}
- )))}
-
- {page.children &&
- page.children.map((item, index) => {
- return this.renderPage(item, index, pagesLength, isOnlyOnePage);
- })
- }
-
-
- );
- } else {
- // permission is 'r' or 'public'
- return (
-
-
this.pageItemRef = ref}
- onMouseEnter={this.onMouseEnter}
- onMouseMove={this.onMouseMove}
- onMouseLeave={this.onMouseLeave}
- id={navItemId}
- >
-
-
- {childNumber === 0 && (customIcon ?
:
)
- }
- {(!isMouseEnter && childNumber > 0) && (customIcon ?
:
)}
- {(isMouseEnter && childNumber > 0) &&
-
-
-
- }
-
{page.name}
-
+
+
-
-
-
- {page.children &&
- page.children.map((item, index) => {
- return this.renderPage(item, index, pagesLength, isOnlyOnePage);
- })
- }
+ {isShowInsertPage && (
+
+ )}
+ {isShowAddSiblingPage && (
+
+ )}
- );
- }
+
{
+ e.stopPropagation();
+ e.nativeEvent.stopImmediatePropagation();
+ }}
+ >
+ {page.children && page.children.map((item, index) => renderPage(item, index, pagesLength, isOnlyOnePage))}
+
+ >
+ );
}
-}
+
+ return (
+ <>
+
+
+
+ {childNumber === 0 && (customIcon ? (
+
+ ) : (
+
+ ))}
+ {(!isMouseEntered && childNumber > 0) && (customIcon ? (
+
+ ) : (
+
+ ))}
+ {(isMouseEntered && childNumber > 0) && (
+
toggleExpand(page.id)}>
+
+
+ )}
+
{page.name}
+
+
+
+
+
{
+ e.stopPropagation();
+ e.nativeEvent.stopImmediatePropagation();
+ }}
+ >
+ {page.children && page.children.map((item, index) => renderPage(item, index, pagesLength, isOnlyOnePage))}
+
+ >
+ );
+};
PageItem.propTypes = {
- isOver: PropTypes.bool,
- canDrop: PropTypes.bool,
- isDragging: PropTypes.bool,
- draggedPage: PropTypes.object,
page: PropTypes.object,
pages: PropTypes.array,
pageIndex: PropTypes.number,
pagesLength: PropTypes.number,
- connectDragSource: PropTypes.func,
- connectDragPreview: PropTypes.func,
- connectDropTarget: PropTypes.func,
+ parentPageId: PropTypes.string,
+ addSiblingPage: PropTypes.func,
duplicatePage: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
diff --git a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js
index f835a38a4c..5eea6e1ea0 100644
--- a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js
+++ b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js
@@ -2,9 +2,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { UncontrolledTooltip } from 'reactstrap';
-import { DropTarget, DragLayer } from 'react-dnd';
-import html5DragDropContext from './html5DragDropContext';
-import DraggedPageItem from './pages/dragged-page-item';
+import { DndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+import PageItem from './pages/page-item';
import { gettext, wikiPermission } from '../../../utils/constants';
import { Utils } from '../../../utils/utils';
@@ -30,28 +30,34 @@ class WikiNav extends Component {
constructor(props) {
super(props);
+ this.state = {
+ idFoldedStatusMap: {}, // Move idFoldedStatusMap to state
+ };
this.folderClassNameCache = '';
this.lastScrollTop = 0;
this.wikiNavBodyRef = React.createRef();
- // set init pages are all folded
- this.idFoldedStatusMap = {};
+ // Initialize pages as folded
+ const idFoldedStatusMap = {};
props.pages.forEach((page) => {
- this.idFoldedStatusMap[page.id] = true;
+ idFoldedStatusMap[page.id] = true;
});
+ this.state.idFoldedStatusMap = idFoldedStatusMap;
}
getFoldState = (pageId) => {
- return this.idFoldedStatusMap[pageId];
+ return this.state.idFoldedStatusMap[pageId];
};
toggleExpand = (pageId) => {
- const idFoldedStatusMap = this.idFoldedStatusMap;
- if (idFoldedStatusMap[pageId]) {
- delete idFoldedStatusMap[pageId];
- } else {
- idFoldedStatusMap[pageId] = true;
- }
- this.idFoldedStatusMap = idFoldedStatusMap;
+ this.setState((prevState) => {
+ const idFoldedStatusMap = { ...prevState.idFoldedStatusMap };
+ if (idFoldedStatusMap[pageId]) {
+ delete idFoldedStatusMap[pageId];
+ } else {
+ idFoldedStatusMap[pageId] = true;
+ }
+ return { idFoldedStatusMap };
+ });
};
componentDidUpdate(prevProps) {
@@ -72,12 +78,12 @@ class WikiNav extends Component {
return this.folderClassNameCache;
};
- renderPage = (page, index, pagesLength, isOnlyOnePage, id_page_map, layerDragProps) => {
+ renderPage = (page, index, pagesLength, isOnlyOnePage, id_page_map) => {
const { pages } = this.props;
const id = page.id;
if (!pages.find(item => item.id === id)) return;
return (
-
@@ -105,7 +111,7 @@ class WikiNav extends Component {
};
// eslint-disable-next-line
- renderStructureBody = React.forwardRef((layerDragProps, ref) => {
+ renderStructureBody = () => {
const { navigation, pages } = this.props;
const pagesLen = pages.length;
const isOnlyOnePage = pagesLen === 1;
@@ -131,7 +137,7 @@ class WikiNav extends Component {
}
{navigation.map((item, index) => {
- return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map, layerDragProps);
+ return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map);
})}
{wikiPermission === 'rw' &&
<>
@@ -146,27 +152,15 @@ class WikiNav extends Component {
}
);
- });
-
- collect = (monitor) => {
- return {
- item: monitor.getItem(),
- itemType: monitor.getItemType(),
- clientOffset: monitor.getClientOffset(),
- isDragging: monitor.isDragging()
- };
};
render() {
- const StructureBody = html5DragDropContext(
- DropTarget('WikiNav', {}, connect => ({
- connectDropTarget: connect.dropTarget()
- }))(DragLayer(this.collect)(this.renderStructureBody))
- );
return (
-
-
-
+
+
+ {this.renderStructureBody()}
+
+
);
}
}