mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-27 07:44:50 +00:00
Upgrade/react dnd (#7767)
* upgrade react-dnd react-dnd-html5-backend * update groupbys * update single select option * update wiki nav --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
133
frontend/package-lock.json
generated
133
frontend/package-lock.json
generated
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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(
|
||||
<div
|
||||
className={classnames('groupby-item',
|
||||
{ 'group-can-drop-top': isOver && canDrop && isDragging },
|
||||
{ 'group-can-drop': isOver && canDrop && !isDragging }
|
||||
)}
|
||||
>
|
||||
{!readOnly && (
|
||||
<div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}>
|
||||
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
||||
</div>
|
||||
)}
|
||||
<div className="condition">
|
||||
<div className="groupby-column">
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classnames('groupby-item',
|
||||
{ 'group-can-drop-top': isOver && canDrop && dropPosition === 'top' },
|
||||
{ 'group-can-drop-bottom': isOver && canDrop && dropPosition === 'bottom' }
|
||||
)}
|
||||
>
|
||||
{!readOnly && (
|
||||
<div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}>
|
||||
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
||||
</div>
|
||||
)}
|
||||
<div className="condition">
|
||||
<div className="groupby-column">
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
value={selectedColumn}
|
||||
options={columnsOptions}
|
||||
onSelectOption={selectColumn}
|
||||
searchable={true}
|
||||
searchPlaceholder={gettext('Search property')}
|
||||
noOptionsPlaceholder={gettext('No results')}
|
||||
/>
|
||||
</div>
|
||||
{isShowGroupCountType(column) && (
|
||||
<div className="groupby-count-type">
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
value={selectedColumn}
|
||||
options={columnsOptions}
|
||||
onSelectOption={selectColumn}
|
||||
searchable={true}
|
||||
searchPlaceholder={gettext('Search property')}
|
||||
noOptionsPlaceholder={gettext('No results')}
|
||||
value={selectedCountType}
|
||||
onSelectOption={selectCountType}
|
||||
options={countTypeOptions}
|
||||
/>
|
||||
</div>
|
||||
{isShowGroupCountType(column) && (
|
||||
<div className="groupby-count-type">
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
value={selectedCountType}
|
||||
onSelectOption={selectCountType}
|
||||
options={countTypeOptions}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="groupby-predicate">
|
||||
{(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && (
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
value={selectedSortType}
|
||||
options={sortOptions}
|
||||
onSelectOption={selectSortType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!readOnly && showDragBtn && connectDragSource(
|
||||
<div className="groupby-drag">
|
||||
<Icon symbol="drag" />
|
||||
</div>
|
||||
)}
|
||||
<div className="groupby-predicate">
|
||||
{(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && (
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
value={selectedSortType}
|
||||
options={sortOptions}
|
||||
onSelectOption={selectSortType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
{!readOnly && showDragBtn && (
|
||||
<div ref={drag} className="groupby-drag">
|
||||
<Icon symbol="drag" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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(
|
||||
<div
|
||||
className={classnames('sf-metadata-edit-option-container', {
|
||||
'sf-metadata-edit-option-can-drop': isOver && canDrop && !isDragging,
|
||||
'sf-metadata-edit-deleting-option': isDeleting,
|
||||
'sf-metadata-edit-option-can-drop-top': isOver && canDrop && isDragging,
|
||||
'sf-metadata-edit-option-viewing': isViewing,
|
||||
'sf-metadata-edit-option-editing': isEditing,
|
||||
'sf-metadata-edit-option-disabled': isPredefined,
|
||||
})}
|
||||
onMouseEnter={() => onMouseEnter()}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{connectDragSource(
|
||||
<div className="sf-metadata-edit-option-drag-container">
|
||||
<Icon symbol="drag" />
|
||||
</div>
|
||||
)}
|
||||
<div className="sf-metadata-edit-option-content">
|
||||
<Color option={option} onChange={onUpdate} isViewing={isViewing} isPredefined={isPredefined} />
|
||||
<Name
|
||||
option={option}
|
||||
isPredefined={isPredefined}
|
||||
isEditing={isEditing}
|
||||
onChange={onUpdate}
|
||||
onToggleFreeze={onToggleFreeze}
|
||||
onOpen={onOpenNameEditor}
|
||||
onClose={onCloseNameEditor}
|
||||
/>
|
||||
</div>
|
||||
<div id={`sf-metadata-edit-option-more-operation-${option.id}`} className="sf-metadata-edit-option-more-operations">
|
||||
{(isViewing || isDeleting) && (
|
||||
<IconBtn className="sf-metadata-edit-option-operation-item" onClick={onDelete} symbol="delete" />
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classnames('sf-metadata-edit-option-container', {
|
||||
'sf-metadata-edit-deleting-option': isDeleting,
|
||||
'sf-metadata-edit-option-drop-over-top': isOver && canDrop && dropPosition === 'top',
|
||||
'sf-metadata-edit-option-drop-over-bottom': isOver && canDrop && dropPosition === 'bottom',
|
||||
'sf-metadata-edit-option-viewing': isViewing,
|
||||
'sf-metadata-edit-option-editing': isEditing,
|
||||
'sf-metadata-edit-option-disabled': isPredefined,
|
||||
})}
|
||||
onMouseEnter={() => onMouseEnter()}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<div ref={drag} className="sf-metadata-edit-option-drag-container">
|
||||
<Icon symbol="drag" />
|
||||
</div>
|
||||
)
|
||||
<div className="sf-metadata-edit-option-content">
|
||||
<Color option={option} onChange={onUpdate} isViewing={isViewing} isPredefined={isPredefined} />
|
||||
<Name
|
||||
option={option}
|
||||
isPredefined={isPredefined}
|
||||
isEditing={isEditing}
|
||||
onChange={onUpdate}
|
||||
onToggleFreeze={onToggleFreeze}
|
||||
onOpen={onOpenNameEditor}
|
||||
onClose={onCloseNameEditor}
|
||||
/>
|
||||
</div>
|
||||
<div id={`sf-metadata-edit-option-more-operation-${option.id}`} className="sf-metadata-edit-option-more-operations">
|
||||
{(isViewing || isDeleting) && (
|
||||
<IconBtn className="sf-metadata-edit-option-operation-item" onClick={onDelete} symbol="delete" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
@@ -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;
|
||||
|
@@ -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 (
|
||||
<MetadataStatusProvider repoID={repoID} repoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView} statusCallback={this.metadataStatusCallback} >
|
||||
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} tagsChangedCallback={this.tagsChangedCallback} >
|
||||
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} >
|
||||
<CollaboratorsProvider repoID={repoID}>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
{this.state.currentRepoInfo.status === 'read-only' &&
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<MetadataStatusProvider repoID={repoID} repoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView} statusCallback={this.metadataStatusCallback} >
|
||||
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} tagsChangedCallback={this.tagsChangedCallback} >
|
||||
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} >
|
||||
<CollaboratorsProvider repoID={repoID}>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
{this.state.currentRepoInfo.status === 'read-only' &&
|
||||
<div className="readonly-tip-message">
|
||||
{gettext('This library has been set to read-only by admin and cannot be updated.')}
|
||||
</div>
|
||||
}
|
||||
<div className="cur-view-path lib-cur-view-path">
|
||||
<div className={classnames(
|
||||
'cur-view-path-left', {
|
||||
'w-100': !isDesktop,
|
||||
'animation-children': isDirentSelected
|
||||
})}>
|
||||
{isDirentSelected ? (
|
||||
currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
|
||||
<MetadataPathToolbar repoID={repoID} repoInfo={currentRepoInfo} mode={currentMode} path={path} />
|
||||
}
|
||||
<div className="cur-view-path lib-cur-view-path">
|
||||
<div className={classnames(
|
||||
'cur-view-path-left', {
|
||||
'w-100': !isDesktop,
|
||||
'animation-children': isDirentSelected
|
||||
})}>
|
||||
{isDirentSelected ? (
|
||||
currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
|
||||
<MetadataPathToolbar repoID={repoID} repoInfo={currentRepoInfo} mode={currentMode} path={path} />
|
||||
) : (
|
||||
<SelectedDirentsToolbar
|
||||
repoID={this.props.repoID}
|
||||
path={this.state.path}
|
||||
userPerm={userPerm}
|
||||
repoEncrypted={this.state.repoEncrypted}
|
||||
repoTags={this.state.repoTags}
|
||||
selectedDirentList={this.state.selectedDirentList}
|
||||
direntList={direntItemsList}
|
||||
onItemsMove={this.onMoveItems}
|
||||
onItemsCopy={this.onCopyItems}
|
||||
onItemsDelete={this.onDeleteItems}
|
||||
onItemRename={this.onMainPanelItemRename}
|
||||
isRepoOwner={isRepoOwner}
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
updateDirent={this.updateDirent}
|
||||
unSelectDirent={this.unSelectDirent}
|
||||
onFilesTagChanged={this.onFileTagChanged}
|
||||
showShareBtn={showShareBtn}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
showDirentDetail={this.showDirentDetail}
|
||||
currentMode={this.state.currentMode}
|
||||
onItemConvert={this.onConvertItem}
|
||||
onAddFolder={this.onAddFolder}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<SelectedDirentsToolbar
|
||||
repoID={this.props.repoID}
|
||||
path={this.state.path}
|
||||
userPerm={userPerm}
|
||||
repoEncrypted={this.state.repoEncrypted}
|
||||
repoTags={this.state.repoTags}
|
||||
selectedDirentList={this.state.selectedDirentList}
|
||||
direntList={direntItemsList}
|
||||
onItemsMove={this.onMoveItems}
|
||||
onItemsCopy={this.onCopyItems}
|
||||
onItemsDelete={this.onDeleteItems}
|
||||
onItemRename={this.onMainPanelItemRename}
|
||||
isRepoOwner={isRepoOwner}
|
||||
<CurDirPath
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
updateDirent={this.updateDirent}
|
||||
unSelectDirent={this.unSelectDirent}
|
||||
onFilesTagChanged={this.onFileTagChanged}
|
||||
showShareBtn={showShareBtn}
|
||||
repoID={this.props.repoID}
|
||||
repoName={this.state.currentRepoInfo.repo_name}
|
||||
repoEncrypted={this.state.repoEncrypted}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
showDirentDetail={this.showDirentDetail}
|
||||
currentMode={this.state.currentMode}
|
||||
onItemConvert={this.onConvertItem}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentPath={this.state.path}
|
||||
userPerm={userPerm}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
onPathClick={this.onMainNavBarClick}
|
||||
fileTags={this.state.fileTags}
|
||||
direntList={direntItemsList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
toggleTreePanel={this.toggleTreePanel}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
showShareBtn={showShareBtn}
|
||||
onAddFolder={this.onAddFolder}
|
||||
onAddFile={this.onAddFile}
|
||||
onUploadFile={this.onUploadFile}
|
||||
onUploadFolder={this.onUploadFolder}
|
||||
fullDirentList={this.state.direntList}
|
||||
filePermission={this.state.filePermission}
|
||||
onFileTagChanged={this.onToolbarFileTagChanged}
|
||||
repoTags={this.state.repoTags}
|
||||
onItemMove={this.onMoveItem}
|
||||
isDesktop={isDesktop}
|
||||
loadDirentList={this.loadDirentList}
|
||||
onAddFolderNode={this.onAddFolder}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CurDirPath
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
repoID={this.props.repoID}
|
||||
repoName={this.state.currentRepoInfo.repo_name}
|
||||
repoEncrypted={this.state.repoEncrypted}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
currentPath={this.state.path}
|
||||
userPerm={userPerm}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
onPathClick={this.onMainNavBarClick}
|
||||
fileTags={this.state.fileTags}
|
||||
direntList={direntItemsList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
toggleTreePanel={this.toggleTreePanel}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
showShareBtn={showShareBtn}
|
||||
onAddFolder={this.onAddFolder}
|
||||
onAddFile={this.onAddFile}
|
||||
onUploadFile={this.onUploadFile}
|
||||
onUploadFolder={this.onUploadFolder}
|
||||
fullDirentList={this.state.direntList}
|
||||
filePermission={this.state.filePermission}
|
||||
onFileTagChanged={this.onToolbarFileTagChanged}
|
||||
repoTags={this.state.repoTags}
|
||||
onItemMove={this.onMoveItem}
|
||||
isDesktop={isDesktop}
|
||||
loadDirentList={this.loadDirentList}
|
||||
onAddFolderNode={this.onAddFolder}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isDesktop &&
|
||||
)}
|
||||
</div>
|
||||
{isDesktop &&
|
||||
<div className="cur-view-path-right py-1">
|
||||
<DirTool
|
||||
repoID={this.props.repoID}
|
||||
@@ -2453,135 +2456,136 @@ class LibContentView extends React.Component {
|
||||
onCloseDetail={this.closeDirentDetail}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='cur-view-content lib-content-container' onScroll={this.onItemsScroll}>
|
||||
{this.state.pathExist ?
|
||||
<DirColumnView
|
||||
isSidePanelFolded={this.props.isSidePanelFolded}
|
||||
isTreePanelShown={this.state.isTreePanelShown}
|
||||
isDirentDetailShow={this.state.isDirentDetailShow}
|
||||
currentMode={this.state.currentMode}
|
||||
path={this.state.path}
|
||||
repoID={this.props.repoID}
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
userPerm={userPerm}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
isTreeDataLoading={this.state.isTreeDataLoading}
|
||||
treeData={this.state.treeData}
|
||||
currentNode={this.state.currentNode}
|
||||
onNodeClick={this.onTreeNodeClick}
|
||||
onNodeCollapse={this.onTreeNodeCollapse}
|
||||
onNodeExpanded={this.onTreeNodeExpanded}
|
||||
onAddFolderNode={this.onAddFolder}
|
||||
onAddFileNode={this.onAddFile}
|
||||
onRenameNode={this.onRenameTreeNode}
|
||||
onDeleteNode={this.onDeleteTreeNode}
|
||||
isViewFile={this.state.isViewFile}
|
||||
isFileLoading={this.state.isFileLoading}
|
||||
filePermission={this.state.filePermission}
|
||||
content={this.state.content}
|
||||
viewId={this.state.viewId}
|
||||
tagId={this.state.tagId}
|
||||
lastModified={this.state.lastModified}
|
||||
latestContributor={this.state.latestContributor}
|
||||
onLinkClick={this.onLinkClick}
|
||||
isRepoInfoBarShow={isRepoInfoBarShow}
|
||||
repoTags={this.state.repoTags}
|
||||
usedRepoTags={this.state.usedRepoTags}
|
||||
updateUsedRepoTags={this.updateUsedRepoTags}
|
||||
isDirentListLoading={this.state.isDirentListLoading}
|
||||
direntList={direntItemsList}
|
||||
fullDirentList={this.state.direntList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onAddFolder={this.onAddFolder}
|
||||
onAddFile={this.onAddFile}
|
||||
onItemClick={this.onItemClick}
|
||||
onItemSelected={this.onDirentSelected}
|
||||
onItemDelete={this.onMainPanelItemDelete}
|
||||
onItemRename={this.onMainPanelItemRename}
|
||||
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
||||
renameFileCallback={this.renameItemAjaxCallback}
|
||||
onItemMove={this.onMoveItem}
|
||||
moveFileCallback={this.moveItemsAjaxCallback}
|
||||
onItemCopy={this.onCopyItem}
|
||||
copyFileCallback={this.copyItemsAjaxCallback}
|
||||
convertFileCallback={this.convertFileAjaxCallback}
|
||||
onItemConvert={this.onConvertItem}
|
||||
onDirentClick={this.onDirentClick}
|
||||
updateDirent={this.updateDirent}
|
||||
isAllItemSelected={this.state.isAllDirentSelected}
|
||||
onAllItemSelected={this.onAllDirentSelected}
|
||||
selectedDirentList={this.state.selectedDirentList}
|
||||
onSelectedDirentListUpdate={this.onSelectedDirentListUpdate}
|
||||
onItemsMove={this.onMoveItems}
|
||||
onItemsCopy={this.onCopyItems}
|
||||
onItemsDelete={this.onDeleteItems}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
showDirentDetail={this.showDirentDetail}
|
||||
onItemsScroll={this.onItemsScroll}
|
||||
eventBus={this.props.eventBus}
|
||||
updateCurrentDirent={this.updateCurrentDirent}
|
||||
updateCurrentPath={this.updatePath}
|
||||
toggleShowDirentToolbar={this.toggleShowDirentToolbar}
|
||||
updateTreeNode={this.updateTreeNode}
|
||||
/>
|
||||
:
|
||||
<div className="message err-tip">{gettext('Folder does not exist.')}</div>
|
||||
}
|
||||
{!isCustomPermission && this.state.isDirentDetailShow && (
|
||||
<Detail
|
||||
path={detailPath}
|
||||
repoID={this.props.repoID}
|
||||
currentRepoInfo={{ ...this.state.currentRepoInfo }}
|
||||
dirent={detailDirent}
|
||||
repoTags={this.state.repoTags}
|
||||
fileTags={this.state.isViewFile ? this.state.fileTags : []}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
onClose={this.closeDirentDetail}
|
||||
currentMode={this.state.currentMode}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
</div>
|
||||
<div className='cur-view-content lib-content-container' onScroll={this.onItemsScroll}>
|
||||
{this.state.pathExist ?
|
||||
<DirColumnView
|
||||
isSidePanelFolded={this.props.isSidePanelFolded}
|
||||
isTreePanelShown={this.state.isTreePanelShown}
|
||||
isDirentDetailShow={this.state.isDirentDetailShow}
|
||||
currentMode={this.state.currentMode}
|
||||
path={this.state.path}
|
||||
repoID={this.props.repoID}
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||
userPerm={userPerm}
|
||||
enableDirPrivateShare={enableDirPrivateShare}
|
||||
isTreeDataLoading={this.state.isTreeDataLoading}
|
||||
treeData={this.state.treeData}
|
||||
currentNode={this.state.currentNode}
|
||||
onNodeClick={this.onTreeNodeClick}
|
||||
onNodeCollapse={this.onTreeNodeCollapse}
|
||||
onNodeExpanded={this.onTreeNodeExpanded}
|
||||
onAddFolderNode={this.onAddFolder}
|
||||
onAddFileNode={this.onAddFile}
|
||||
onRenameNode={this.onRenameTreeNode}
|
||||
onDeleteNode={this.onDeleteTreeNode}
|
||||
isViewFile={this.state.isViewFile}
|
||||
isFileLoading={this.state.isFileLoading}
|
||||
filePermission={this.state.filePermission}
|
||||
content={this.state.content}
|
||||
viewId={this.state.viewId}
|
||||
tagId={this.state.tagId}
|
||||
lastModified={this.state.lastModified}
|
||||
latestContributor={this.state.latestContributor}
|
||||
onLinkClick={this.onLinkClick}
|
||||
isRepoInfoBarShow={isRepoInfoBarShow}
|
||||
repoTags={this.state.repoTags}
|
||||
usedRepoTags={this.state.usedRepoTags}
|
||||
updateUsedRepoTags={this.updateUsedRepoTags}
|
||||
isDirentListLoading={this.state.isDirentListLoading}
|
||||
direntList={direntItemsList}
|
||||
fullDirentList={this.state.direntList}
|
||||
sortBy={this.state.sortBy}
|
||||
sortOrder={this.state.sortOrder}
|
||||
sortItems={this.sortItems}
|
||||
onAddFolder={this.onAddFolder}
|
||||
onAddFile={this.onAddFile}
|
||||
onItemClick={this.onItemClick}
|
||||
onItemSelected={this.onDirentSelected}
|
||||
onItemDelete={this.onMainPanelItemDelete}
|
||||
onItemRename={this.onMainPanelItemRename}
|
||||
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
||||
renameFileCallback={this.renameItemAjaxCallback}
|
||||
onItemMove={this.onMoveItem}
|
||||
moveFileCallback={this.moveItemsAjaxCallback}
|
||||
onItemCopy={this.onCopyItem}
|
||||
copyFileCallback={this.copyItemsAjaxCallback}
|
||||
convertFileCallback={this.convertFileAjaxCallback}
|
||||
onItemConvert={this.onConvertItem}
|
||||
onDirentClick={this.onDirentClick}
|
||||
updateDirent={this.updateDirent}
|
||||
isAllItemSelected={this.state.isAllDirentSelected}
|
||||
onAllItemSelected={this.onAllDirentSelected}
|
||||
selectedDirentList={this.state.selectedDirentList}
|
||||
onSelectedDirentListUpdate={this.onSelectedDirentListUpdate}
|
||||
onItemsMove={this.onMoveItems}
|
||||
onItemsCopy={this.onCopyItems}
|
||||
onItemsDelete={this.onDeleteItems}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
showDirentDetail={this.showDirentDetail}
|
||||
onItemsScroll={this.onItemsScroll}
|
||||
eventBus={this.props.eventBus}
|
||||
updateCurrentDirent={this.updateCurrentDirent}
|
||||
updateCurrentPath={this.updatePath}
|
||||
toggleShowDirentToolbar={this.toggleShowDirentToolbar}
|
||||
updateTreeNode={this.updateTreeNode}
|
||||
/>
|
||||
:
|
||||
<div className="message err-tip">{gettext('Folder does not exist.')}</div>
|
||||
}
|
||||
{!isCustomPermission && this.state.isDirentDetailShow && (
|
||||
<Detail
|
||||
path={detailPath}
|
||||
repoID={this.props.repoID}
|
||||
currentRepoInfo={{ ...this.state.currentRepoInfo }}
|
||||
dirent={detailDirent}
|
||||
repoTags={this.state.repoTags}
|
||||
fileTags={this.state.isViewFile ? this.state.fileTags : []}
|
||||
onFileTagChanged={this.onFileTagChanged}
|
||||
onClose={this.closeDirentDetail}
|
||||
currentMode={this.state.currentMode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
|
||||
<FileUploader
|
||||
ref={uploader => this.uploader = uploader}
|
||||
dragAndDrop={true}
|
||||
path={this.state.path}
|
||||
repoID={this.props.repoID}
|
||||
direntList={this.state.direntList}
|
||||
onFileUploadSuccess={this.onFileUploadSuccess}
|
||||
isCustomPermission={isCustomPermission}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
|
||||
<FileUploader
|
||||
ref={uploader => this.uploader = uploader}
|
||||
dragAndDrop={true}
|
||||
path={this.state.path}
|
||||
repoID={this.props.repoID}
|
||||
direntList={this.state.direntList}
|
||||
onFileUploadSuccess={this.onFileUploadSuccess}
|
||||
isCustomPermission={isCustomPermission}
|
||||
{isCopyMoveProgressDialogShow && (
|
||||
<CopyMoveDirentProgressDialog
|
||||
type={this.state.asyncOperationType}
|
||||
asyncOperatedFilesLength={this.state.asyncOperatedFilesLength}
|
||||
asyncOperationProgress={this.state.asyncOperationProgress}
|
||||
toggleDialog={this.onMoveProgressDialogToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isCopyMoveProgressDialogShow && (
|
||||
<CopyMoveDirentProgressDialog
|
||||
type={this.state.asyncOperationType}
|
||||
asyncOperatedFilesLength={this.state.asyncOperatedFilesLength}
|
||||
asyncOperationProgress={this.state.asyncOperationProgress}
|
||||
toggleDialog={this.onMoveProgressDialogToggle}
|
||||
/>
|
||||
)}
|
||||
{isDeleteFolderDialogOpen && (
|
||||
<DeleteFolderDialog
|
||||
repoID={this.props.repoID}
|
||||
path={this.state.folderToDelete}
|
||||
deleteFolder={this.deleteFolder}
|
||||
toggleDialog={this.toggleDeleteFolderDialog}
|
||||
/>
|
||||
)}
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<Modal zIndex="1030" isOpen={!isDesktop && this.state.isTreePanelShown} toggle={this.toggleTreePanel} contentClassName="d-none"></Modal>
|
||||
</MediaQuery>
|
||||
</CollaboratorsProvider>
|
||||
</MetadataProvider>
|
||||
</TagsProvider>
|
||||
</MetadataStatusProvider>
|
||||
{isDeleteFolderDialogOpen && (
|
||||
<DeleteFolderDialog
|
||||
repoID={this.props.repoID}
|
||||
path={this.state.folderToDelete}
|
||||
deleteFolder={this.deleteFolder}
|
||||
toggleDialog={this.toggleDeleteFolderDialog}
|
||||
/>
|
||||
)}
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
<Modal zIndex="1030" isOpen={!isDesktop && this.state.isTreePanelShown} toggle={this.toggleTreePanel} contentClassName="d-none"></Modal>
|
||||
</MediaQuery>
|
||||
</CollaboratorsProvider>
|
||||
</MetadataProvider>
|
||||
</TagsProvider>
|
||||
</MetadataStatusProvider>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +0,0 @@
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
|
||||
export default DragDropContext(HTML5Backend);
|
@@ -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)
|
||||
);
|
@@ -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 (
|
||||
<DraggedPageItem
|
||||
key={id}
|
||||
pagesLength={pagesLength}
|
||||
isOnlyOnePage={isOnlyOnePage}
|
||||
page={Object.assign({}, pages.find(item => item.id === id), page)}
|
||||
<PageItem
|
||||
key={page.id}
|
||||
page={Object.assign({}, pages.find(item => 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 (
|
||||
<div>
|
||||
{connectDragSource(connectDropTarget(connectDragPreview(
|
||||
if (wikiPermission === 'rw') {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id={navItemId}
|
||||
ref={ref}
|
||||
className={classnames('wiki-page-item', {
|
||||
'selected-page': isSelected,
|
||||
'dragged-page-over': isOver && canDrop && !dropPosition,
|
||||
'page-can-drop-top': isOver && canDrop && dropPosition === 'top',
|
||||
'page-can-drop-bottom': isOver && canDrop && dropPosition === 'bottom',
|
||||
})}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<div className="wiki-page-item-main" onClick={onClickPageItem}>
|
||||
<div
|
||||
className={this.getPageClassName()}
|
||||
ref={ref => 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
|
||||
} : {}}
|
||||
>
|
||||
<div className="wiki-page-item-main" onClick={this.onClickPageItem}>
|
||||
<div className='wiki-page-content' style={pathStr ? { marginLeft: `${(pathStr.split('-').length - 1) * 24}px` } : {}}>
|
||||
{childNumber === 0 && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'file'} disable={true} />)
|
||||
}
|
||||
{(!isMouseEnter && childNumber > 0) && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'files'} disable={true} />)}
|
||||
{(isMouseEnter && childNumber > 0) &&
|
||||
<div className='nav-item-icon' onClick={this.toggleExpand} role='button'>
|
||||
<i className={`sf3-font-down sf3-font ${folded ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
||||
</div>
|
||||
}
|
||||
<span className="wiki-page-title text-truncate" title={page.name}>{page.name}</span>
|
||||
{isShowNameEditor && (
|
||||
<NameEditPopover
|
||||
oldName={pageName}
|
||||
targetId={navItemId}
|
||||
onChangeName={this.onChangeName}
|
||||
toggleEditor={this.toggleNameEditor}
|
||||
/>
|
||||
)}
|
||||
{childNumber === 0 && (customIcon ? (
|
||||
<CustomIcon icon={customIcon} />
|
||||
) : (
|
||||
<NavItemIcon symbol={'file'} disable={true} />
|
||||
))}
|
||||
{(!isMouseEntered && childNumber > 0) && (customIcon ? (
|
||||
<CustomIcon icon={customIcon} />
|
||||
) : (
|
||||
<NavItemIcon symbol={'file'} disable={true} />
|
||||
))}
|
||||
{(isMouseEntered && childNumber > 0) && (
|
||||
<div role="button" className="nav-item-icon" onClick={() => toggleExpand(page.id)}>
|
||||
<i className={`sf3-font-down sf3-font ${getFoldState(page.id) ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-none d-md-flex">
|
||||
<div className="more-wiki-page-operation" onClick={this.toggleDropdown}>
|
||||
<Icon symbol={'more-level'} />
|
||||
{this.state.isShowOperationDropdown &&
|
||||
<PageDropdownMenu
|
||||
page={page}
|
||||
pages={this.props.pages}
|
||||
pagesLength={pagesLength}
|
||||
isOnlyOnePage={isOnlyOnePage}
|
||||
toggle={this.toggleDropdown}
|
||||
toggleNameEditor={this.toggleNameEditor}
|
||||
duplicatePage={this.props.duplicatePage}
|
||||
onDeletePage={this.props.onDeletePage.bind(this, page.id)}
|
||||
toggleInsertSiblingPage={this.toggleInsertSiblingPage}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className="wiki-add-page-btn" onClick={this.toggleInsertPage} role='button'>
|
||||
<span className='sf3-font sf3-font-enlarge' aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.isShowInsertPage &&
|
||||
<AddNewPageDialog
|
||||
toggle={this.toggleInsertPage}
|
||||
onAddNewPage={this.onAddNewPage}
|
||||
title={gettext('Add page inside')}
|
||||
page={this.props.page}
|
||||
)}
|
||||
<span className="wiki-page-title text-truncate" title={page.name}>{page.name}</span>
|
||||
{isShowNameEditor && (
|
||||
<NameEditPopover
|
||||
oldName={pageName}
|
||||
target_page_id={navItemId}
|
||||
onChangeName={onChangeName}
|
||||
toggleEditor={toggleNameEditor}
|
||||
/>
|
||||
}
|
||||
{this.state.isShowAddSiblingPage &&
|
||||
<AddNewPageDialog
|
||||
toggle={this.toggleInsertSiblingPage}
|
||||
onAddNewPage={this.onAddSiblingPage}
|
||||
title={gettext('Add page')}
|
||||
insertPosition={this.state.insertPosition}
|
||||
page={this.props.page}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-none d-md-flex">
|
||||
<div className="more-wiki-page-operation" onClick={toggleDropdown}>
|
||||
<Icon symbol="more-level" />
|
||||
{isShowOperationDropdown && (
|
||||
<PageDropdownMenu
|
||||
page={page}
|
||||
pages={pages}
|
||||
pagesLength={pagesLength}
|
||||
isOnlyOnePage={isOnlyOnePage}
|
||||
toggle={toggleDropdown}
|
||||
toggleNameEditor={toggleNameEditor}
|
||||
duplicatePage={duplicatePage}
|
||||
onDeletePage={() => onDeletePage(page.id)}
|
||||
toggleInsertSiblingPage={toggleInsertSiblingPage}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)))}
|
||||
<div className="page-children" style={this.getPageChildrenStyle()} onClick={this.onClickPageChildren}>
|
||||
{page.children &&
|
||||
page.children.map((item, index) => {
|
||||
return this.renderPage(item, index, pagesLength, isOnlyOnePage);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// permission is 'r' or 'public'
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={this.getPageClassName()}
|
||||
ref={ref => this.pageItemRef = ref}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseMove={this.onMouseMove}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
id={navItemId}
|
||||
>
|
||||
<div className="wiki-page-item-main" onClick={this.onClickPageItem}>
|
||||
<div className='wiki-page-content' style={pathStr ? { marginLeft: `${(pathStr.split('-').length - 1) * 24}px` } : {}}>
|
||||
{childNumber === 0 && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'file'} disable={true} />)
|
||||
}
|
||||
{(!isMouseEnter && childNumber > 0) && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'files'} disable={true} />)}
|
||||
{(isMouseEnter && childNumber > 0) &&
|
||||
<div className='nav-item-icon' onClick={this.toggleExpand} role='button'>
|
||||
<i className={`sf3-font-down sf3-font ${folded ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
||||
</div>
|
||||
}
|
||||
<span className="wiki-page-title text-truncate" title={page.name}>{page.name}</span>
|
||||
</div>
|
||||
<div className="wiki-add-page-btn" onClick={toggleInsertPage} role='button'>
|
||||
<span className='sf3-font sf3-font-enlarge' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div className="d-none d-md-flex"></div>
|
||||
</div>
|
||||
<div className="page-children" style={this.getPageChildrenStyle()} onClick={this.onClickPageChildren}>
|
||||
{page.children &&
|
||||
page.children.map((item, index) => {
|
||||
return this.renderPage(item, index, pagesLength, isOnlyOnePage);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{isShowInsertPage && (
|
||||
<AddNewPageDialog
|
||||
toggle={toggleInsertPage}
|
||||
onAddNewPage={onAddNewPage}
|
||||
title={gettext('Add page inside')}
|
||||
page={page}
|
||||
/>
|
||||
)}
|
||||
{isShowAddSiblingPage && (
|
||||
<AddNewPageDialog
|
||||
toggle={toggleInsertSiblingPage}
|
||||
onAddNewPage={onAddSiblingPage}
|
||||
title={gettext('Add page')}
|
||||
insertPosition={insertPosition}
|
||||
page={page}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div
|
||||
className="page-children"
|
||||
style={pageChildrenStyle}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
}}
|
||||
>
|
||||
{page.children && page.children.map((item, index) => renderPage(item, index, pagesLength, isOnlyOnePage))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id={navItemId}
|
||||
ref={ref}
|
||||
className={classnames('wiki-page-item readonly', {
|
||||
'selected-page': isSelected,
|
||||
'dragged-page-over': isOver && canDrop,
|
||||
'page-can-drop-top': isOver && canDrop && dropPosition === 'top',
|
||||
'page-can-drop-bottom': isOver && canDrop && dropPosition === 'bottom',
|
||||
})}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<div className="wiki-page-item-main" onClick={onClickPageItem}>
|
||||
<div
|
||||
className="wiki-page-content"
|
||||
style={pathStr ? {
|
||||
marginLeft: (pathStr.split('-').length - 1) * 24,
|
||||
} : {}}
|
||||
>
|
||||
{childNumber === 0 && (customIcon ? (
|
||||
<CustomIcon icon={customIcon} />
|
||||
) : (
|
||||
<NavItemIcon symbol="file" disable={true} />
|
||||
))}
|
||||
{(!isMouseEntered && childNumber > 0) && (customIcon ? (
|
||||
<CustomIcon icon={customIcon} />
|
||||
) : (
|
||||
<NavItemIcon symbol="files" disable={true} />
|
||||
))}
|
||||
{(isMouseEntered && childNumber > 0) && (
|
||||
<div role="button" className="nav-item-icon" onClick={() => toggleExpand(page.id)}>
|
||||
<i className={`sf3-font-down sf3-font ${getFoldState(page.id) ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
||||
</div>
|
||||
)}
|
||||
<span className="wiki-page-title text-truncate">{page.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-none d-md-flex"></div>
|
||||
</div>
|
||||
<div
|
||||
className="page-children"
|
||||
style={pageChildrenStyle}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
}}
|
||||
>
|
||||
{page.children && page.children.map((item, index) => renderPage(item, index, pagesLength, isOnlyOnePage))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
|
@@ -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 (
|
||||
<DraggedPageItem
|
||||
<PageItem
|
||||
key={id}
|
||||
pagesLength={pagesLength}
|
||||
isOnlyOnePage={isOnlyOnePage}
|
||||
@@ -94,10 +100,10 @@ class WikiNav extends Component {
|
||||
getCurrentPageId={this.props.getCurrentPageId}
|
||||
addPageInside={this.props.addPageInside}
|
||||
addSiblingPage={this.props.addSiblingPage}
|
||||
idFoldedStatusMap={this.idFoldedStatusMap}
|
||||
getFoldState={this.getFoldState}
|
||||
toggleExpand={this.toggleExpand}
|
||||
id_page_map={id_page_map}
|
||||
layerDragProps={layerDragProps}
|
||||
setClassName={this.setClassName}
|
||||
getClassName={this.getClassName}
|
||||
/>
|
||||
@@ -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 {
|
||||
}
|
||||
</div>
|
||||
{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 {
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<div className='wiki-nav'>
|
||||
<StructureBody />
|
||||
</div>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className='wiki-nav'>
|
||||
{this.renderStructureBody()}
|
||||
</div>
|
||||
</DndProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user