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-app-polyfill": "^2.0.0",
|
||||||
"react-chartjs-2": "5.3.0",
|
"react-chartjs-2": "5.3.0",
|
||||||
"react-cookies": "^0.1.0",
|
"react-cookies": "^0.1.0",
|
||||||
"react-dnd": "^2.6.0",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^2.6.0",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-i18next": "^10.12.2",
|
"react-i18next": "^10.12.2",
|
||||||
"react-mentions": "4.4.10",
|
"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": {
|
"node_modules/@emotion/serialize": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
||||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
|
"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": {
|
"node_modules/@replit/codemirror-lang-csharp": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@replit/codemirror-lang-csharp/-/codemirror-lang-csharp-6.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@replit/codemirror-lang-csharp/-/codemirror-lang-csharp-6.2.0.tgz",
|
||||||
@@ -8290,6 +8292,7 @@
|
|||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/asn1.js": {
|
"node_modules/asn1.js": {
|
||||||
@@ -11436,12 +11439,6 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"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": {
|
"node_modules/dlv": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
|
||||||
@@ -11450,15 +11447,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dnd-core": {
|
"node_modules/dnd-core": {
|
||||||
"version": "2.6.0",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
|
||||||
"integrity": "sha512-5BfQHIp0XVd4ioF0q4GyUeHQQNCbqP+0SnUiP9TssoQ50wrP1NgSzDqZkjD5pFngsVz9txGin6rvTQD7w0qC3w==",
|
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asap": "^2.0.6",
|
"@react-dnd/asap": "^5.0.1",
|
||||||
"invariant": "^2.0.0",
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
"lodash": "^4.2.0",
|
"redux": "^4.2.0"
|
||||||
"redux": "^3.7.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dns-packet": {
|
"node_modules/dns-packet": {
|
||||||
@@ -13247,7 +13242,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
@@ -14913,10 +14907,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "2.5.5",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
"license": "BSD-3-Clause"
|
"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": {
|
"node_modules/hoopy": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
@@ -23210,29 +23211,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dnd": {
|
"node_modules/react-dnd": {
|
||||||
"version": "2.6.0",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||||
"integrity": "sha512-2KHNpeg2SyaxXYq+xO1TM+tOtN9hViI41otJuiYiu6DRYGw+WMvDFDMP4aw7zIKRRm1xd0gizXuKWhb8iJYHBw==",
|
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"disposables": "^1.0.1",
|
"@react-dnd/invariant": "^4.0.1",
|
||||||
"dnd-core": "^2.6.0",
|
"@react-dnd/shallowequal": "^4.0.1",
|
||||||
"hoist-non-react-statics": "^2.1.0",
|
"dnd-core": "^16.0.1",
|
||||||
"invariant": "^2.1.0",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"lodash": "^4.2.0",
|
"hoist-non-react-statics": "^3.3.2"
|
||||||
"prop-types": "^15.5.10"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"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": {
|
"node_modules/react-dnd-html5-backend": {
|
||||||
"version": "2.6.0",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
||||||
"integrity": "sha512-8gOfBfqFikWmXvAGSZz1mgoctwkcsKdUC9POt/WGnMoZwGB4ivB0Ex5D6pwHTNjvAs0ixqqWdJKy57CzjDg5Sg==",
|
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.2.0"
|
"dnd-core": "^16.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
@@ -23577,15 +23589,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "3.7.2",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/redux/-/redux-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.2.1",
|
"@babel/runtime": "^7.9.2"
|
||||||
"lodash-es": "^4.2.1",
|
|
||||||
"loose-envify": "^1.1.0",
|
|
||||||
"symbol-observable": "^1.0.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
@@ -27108,15 +27116,6 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/symbol-tree": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
|
@@ -41,8 +41,8 @@
|
|||||||
"react-app-polyfill": "^2.0.0",
|
"react-app-polyfill": "^2.0.0",
|
||||||
"react-chartjs-2": "5.3.0",
|
"react-chartjs-2": "5.3.0",
|
||||||
"react-cookies": "^0.1.0",
|
"react-cookies": "^0.1.0",
|
||||||
"react-dnd": "^2.6.0",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^2.6.0",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-i18next": "^10.12.2",
|
"react-i18next": "^10.12.2",
|
||||||
"react-mentions": "4.4.10",
|
"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 PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { DragSource, DropTarget } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import CustomizeSelect from '../../../../../components/customize-select';
|
import CustomizeSelect from '../../../../../components/customize-select';
|
||||||
import Icon from '../../../../../components/icon';
|
import Icon from '../../../../../components/icon';
|
||||||
import { gettext } from '../../../../../utils/constants';
|
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 { COLUMNS_ICON_CONFIG, SORT_TYPE, SORT_COLUMN_OPTIONS } from '../../../../constants';
|
||||||
import { getGroupbyGranularityByColumn, isShowGroupCountType, getSelectedCountType, getDefaultCountType } from '../../../../utils/group';
|
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: {
|
groupby: {
|
||||||
column_key: 'xxx',
|
column_key: 'xxx',
|
||||||
@@ -59,10 +16,46 @@ const dropCollect = (connect, monitor) => ({
|
|||||||
sort_type: 'xxx',
|
sort_type: 'xxx',
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const GroupbyItem = ({
|
const GroupbyItem = ({ showDragBtn, index, readOnly, groupby, columns, onDelete, onUpdate, onMove }) => {
|
||||||
isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget,
|
const ref = useRef(null);
|
||||||
showDragBtn, index, readOnly, groupby, columns, onDelete, onUpdate
|
|
||||||
}) => {
|
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(() => {
|
const column = useMemo(() => {
|
||||||
return getColumnByKey(columns, groupby.column_key);
|
return getColumnByKey(columns, groupby.column_key);
|
||||||
}, [groupby, columns]);
|
}, [groupby, columns]);
|
||||||
@@ -165,59 +158,58 @@ const GroupbyItem = ({
|
|||||||
onUpdate(newGroupby, index);
|
onUpdate(newGroupby, index);
|
||||||
}, [groupby, index, onUpdate]);
|
}, [groupby, index, onUpdate]);
|
||||||
|
|
||||||
return connectDropTarget(
|
return (
|
||||||
connectDragPreview(
|
<div
|
||||||
<div
|
ref={ref}
|
||||||
className={classnames('groupby-item',
|
className={classnames('groupby-item',
|
||||||
{ 'group-can-drop-top': isOver && canDrop && isDragging },
|
{ 'group-can-drop-top': isOver && canDrop && dropPosition === 'top' },
|
||||||
{ 'group-can-drop': isOver && canDrop && !isDragging }
|
{ 'group-can-drop-bottom': isOver && canDrop && dropPosition === 'bottom' }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}>
|
<div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}>
|
||||||
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="condition">
|
<div className="condition">
|
||||||
<div className="groupby-column">
|
<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
|
<CustomizeSelect
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
value={selectedColumn}
|
value={selectedCountType}
|
||||||
options={columnsOptions}
|
onSelectOption={selectCountType}
|
||||||
onSelectOption={selectColumn}
|
options={countTypeOptions}
|
||||||
searchable={true}
|
|
||||||
searchPlaceholder={gettext('Search property')}
|
|
||||||
noOptionsPlaceholder={gettext('No results')}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)
|
{!readOnly && showDragBtn && (
|
||||||
|
<div ref={drag} className="groupby-drag">
|
||||||
|
<Icon symbol="drag" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -229,16 +221,7 @@ GroupbyItem.propTypes = {
|
|||||||
columns: PropTypes.array,
|
columns: PropTypes.array,
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
onUpdate: PropTypes.func,
|
onUpdate: PropTypes.func,
|
||||||
|
onMove: PropTypes.func,
|
||||||
// drag
|
|
||||||
isDragging: PropTypes.bool,
|
|
||||||
isOver: PropTypes.bool,
|
|
||||||
canDrop: PropTypes.bool,
|
|
||||||
connectDropTarget: PropTypes.func,
|
|
||||||
connectDragSource: PropTypes.func,
|
|
||||||
connectDragPreview: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DropTarget('sfMetadataGroupbyItem', dropTarget, dropCollect)(
|
export default GroupbyItem;
|
||||||
DragSource('sfMetadataGroupbyItem', dragSource, dragCollect)(GroupbyItem)
|
|
||||||
);
|
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { DropTarget } from 'react-dnd';
|
|
||||||
import html5DragDropContext from '../../../../../pages/wiki2/wiki-nav/html5DragDropContext';
|
|
||||||
import GroupbyItem from './groupby-item';
|
import GroupbyItem from './groupby-item';
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
|
|
||||||
@@ -47,8 +45,4 @@ Groupbys.propTypes = {
|
|||||||
onMove: PropTypes.func,
|
onMove: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DndGroupbysContainer = DropTarget('sfMetadataGroupbyItem', {}, connect => ({
|
export default Groupbys;
|
||||||
connectDropTarget: connect.dropTarget()
|
|
||||||
}))(Groupbys);
|
|
||||||
|
|
||||||
export default html5DragDropContext(DndGroupbysContainer);
|
|
||||||
|
@@ -27,19 +27,28 @@
|
|||||||
position: relative;
|
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 {
|
.sf-metadata-groupbys-popover .groupbys-list .groupby-item.group-can-drop-top::before {
|
||||||
content: '';
|
content: '';
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
background: #666;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 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 {
|
.sf-metadata-groupbys-popover .groupby-item .delete-groupby {
|
||||||
|
@@ -49,19 +49,28 @@
|
|||||||
fill: #212529;
|
fill: #212529;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-edit-option-container.sf-metadata-edit-option-can-drop {
|
.sf-metadata-edit-option-container.sf-metadata-edit-option-drop-over-top::before {
|
||||||
border-bottom: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sf-metadata-edit-option-container.sf-metadata-edit-option-can-drop-top::before {
|
|
||||||
content: '';
|
content: '';
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
background: #666;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 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,
|
.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 PropTypes from 'prop-types';
|
||||||
import { DragSource, DropTarget } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Icon from '../../../../../components/icon';
|
import Icon from '../../../../../components/icon';
|
||||||
import IconBtn from '../../../../../components/icon-btn';
|
import IconBtn from '../../../../../components/icon-btn';
|
||||||
@@ -9,56 +9,49 @@ import Name from './name';
|
|||||||
|
|
||||||
import './index.css';
|
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 = ({
|
const Option = ({
|
||||||
isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget,
|
|
||||||
isViewing, isDeleting, isEditing, isPredefined,
|
isViewing, isDeleting, isEditing, isPredefined,
|
||||||
option,
|
option, index,
|
||||||
onDelete: propsDelete, onUpdate,
|
onDelete: propsDelete, onUpdate, onMove,
|
||||||
onMouseLeave, onMouseEnter: propsMouseEnter, onToggleFreeze, onOpenNameEditor, onCloseNameEditor,
|
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) => {
|
const onDelete = useCallback((event) => {
|
||||||
event.nativeEvent.stopImmediatePropagation();
|
event.nativeEvent.stopImmediatePropagation();
|
||||||
@@ -69,49 +62,45 @@ const Option = ({
|
|||||||
propsMouseEnter(option.id);
|
propsMouseEnter(option.id);
|
||||||
}, [option, propsMouseEnter]);
|
}, [option, propsMouseEnter]);
|
||||||
|
|
||||||
return connectDropTarget(
|
return (
|
||||||
connectDragPreview(
|
<div
|
||||||
<div
|
ref={ref}
|
||||||
className={classnames('sf-metadata-edit-option-container', {
|
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-deleting-option': isDeleting,
|
'sf-metadata-edit-option-drop-over-top': isOver && canDrop && dropPosition === 'top',
|
||||||
'sf-metadata-edit-option-can-drop-top': isOver && canDrop && isDragging,
|
'sf-metadata-edit-option-drop-over-bottom': isOver && canDrop && dropPosition === 'bottom',
|
||||||
'sf-metadata-edit-option-viewing': isViewing,
|
'sf-metadata-edit-option-viewing': isViewing,
|
||||||
'sf-metadata-edit-option-editing': isEditing,
|
'sf-metadata-edit-option-editing': isEditing,
|
||||||
'sf-metadata-edit-option-disabled': isPredefined,
|
'sf-metadata-edit-option-disabled': isPredefined,
|
||||||
})}
|
})}
|
||||||
onMouseEnter={() => onMouseEnter()}
|
onMouseEnter={() => onMouseEnter()}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
>
|
>
|
||||||
{connectDragSource(
|
<div ref={drag} className="sf-metadata-edit-option-drag-container">
|
||||||
<div className="sf-metadata-edit-option-drag-container">
|
<Icon symbol="drag" />
|
||||||
<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>
|
</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 = {
|
Option.propTypes = {
|
||||||
// normal
|
|
||||||
option: PropTypes.object,
|
option: PropTypes.object,
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
isPredefined: PropTypes.bool,
|
isPredefined: PropTypes.bool,
|
||||||
@@ -126,16 +115,6 @@ Option.propTypes = {
|
|||||||
onToggleFreeze: PropTypes.func,
|
onToggleFreeze: PropTypes.func,
|
||||||
onOpenNameEditor: PropTypes.func.isRequired,
|
onOpenNameEditor: PropTypes.func.isRequired,
|
||||||
onCloseNameEditor: 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)(
|
export default Option;
|
||||||
DragSource('sfMetadataSingleSelectOption', dragSource, dragCollect)(Option)
|
|
||||||
);
|
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { DropTarget } from 'react-dnd';
|
|
||||||
import html5DragDropContext from '../../../../pages/wiki2/wiki-nav/html5DragDropContext';
|
|
||||||
|
|
||||||
const OptionsContainer = ({ inputRef, options }) => {
|
const OptionsContainer = ({ inputRef, options }) => {
|
||||||
if (!Array.isArray(options) || options.length === 0) return null;
|
if (!Array.isArray(options) || options.length === 0) return null;
|
||||||
@@ -17,8 +15,4 @@ OptionsContainer.propTypes = {
|
|||||||
options: PropTypes.array
|
options: PropTypes.array
|
||||||
};
|
};
|
||||||
|
|
||||||
const DndOptionsContainer = DropTarget('sfMetadataSingleSelectOption', {}, connect => ({
|
export default OptionsContainer;
|
||||||
connectDropTarget: connect.dropTarget()
|
|
||||||
}))(OptionsContainer);
|
|
||||||
|
|
||||||
export default html5DragDropContext(DndOptionsContainer);
|
|
||||||
|
@@ -7,6 +7,8 @@ import classnames from 'classnames';
|
|||||||
import MediaQuery from 'react-responsive';
|
import MediaQuery from 'react-responsive';
|
||||||
import { Modal } from 'reactstrap';
|
import { Modal } from 'reactstrap';
|
||||||
import { navigate } from '@gatsbyjs/reach-router';
|
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 { gettext, siteRoot, username } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
@@ -2352,89 +2354,90 @@ class LibContentView extends React.Component {
|
|||||||
}
|
}
|
||||||
const detailDirent = currentDirent || currentNode?.object || null;
|
const detailDirent = currentDirent || currentNode?.object || null;
|
||||||
return (
|
return (
|
||||||
<MetadataStatusProvider repoID={repoID} repoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView} statusCallback={this.metadataStatusCallback} >
|
<DndProvider backend={HTML5Backend}>
|
||||||
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} tagsChangedCallback={this.tagsChangedCallback} >
|
<MetadataStatusProvider repoID={repoID} repoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView} statusCallback={this.metadataStatusCallback} >
|
||||||
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} >
|
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} tagsChangedCallback={this.tagsChangedCallback} >
|
||||||
<CollaboratorsProvider repoID={repoID}>
|
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} >
|
||||||
<div className="main-panel-center flex-row">
|
<CollaboratorsProvider repoID={repoID}>
|
||||||
<div className="cur-view-container">
|
<div className="main-panel-center flex-row">
|
||||||
{this.state.currentRepoInfo.status === 'read-only' &&
|
<div className="cur-view-container">
|
||||||
|
{this.state.currentRepoInfo.status === 'read-only' &&
|
||||||
<div className="readonly-tip-message">
|
<div className="readonly-tip-message">
|
||||||
{gettext('This library has been set to read-only by admin and cannot be updated.')}
|
{gettext('This library has been set to read-only by admin and cannot be updated.')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className="cur-view-path lib-cur-view-path">
|
<div className="cur-view-path lib-cur-view-path">
|
||||||
<div className={classnames(
|
<div className={classnames(
|
||||||
'cur-view-path-left', {
|
'cur-view-path-left', {
|
||||||
'w-100': !isDesktop,
|
'w-100': !isDesktop,
|
||||||
'animation-children': isDirentSelected
|
'animation-children': isDirentSelected
|
||||||
})}>
|
})}>
|
||||||
{isDirentSelected ? (
|
{isDirentSelected ? (
|
||||||
currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
|
currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
|
||||||
<MetadataPathToolbar repoID={repoID} repoInfo={currentRepoInfo} mode={currentMode} path={path} />
|
<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
|
<CurDirPath
|
||||||
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}
|
currentRepoInfo={this.state.currentRepoInfo}
|
||||||
enableDirPrivateShare={enableDirPrivateShare}
|
repoID={this.props.repoID}
|
||||||
updateDirent={this.updateDirent}
|
repoName={this.state.currentRepoInfo.repo_name}
|
||||||
unSelectDirent={this.unSelectDirent}
|
repoEncrypted={this.state.repoEncrypted}
|
||||||
onFilesTagChanged={this.onFileTagChanged}
|
|
||||||
showShareBtn={showShareBtn}
|
|
||||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||||
showDirentDetail={this.showDirentDetail}
|
pathPrefix={this.props.pathPrefix}
|
||||||
currentMode={this.state.currentMode}
|
currentPath={this.state.path}
|
||||||
onItemConvert={this.onConvertItem}
|
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}
|
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>
|
||||||
<CurDirPath
|
{isDesktop &&
|
||||||
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 className="cur-view-path-right py-1">
|
<div className="cur-view-path-right py-1">
|
||||||
<DirTool
|
<DirTool
|
||||||
repoID={this.props.repoID}
|
repoID={this.props.repoID}
|
||||||
@@ -2453,135 +2456,136 @@ class LibContentView extends React.Component {
|
|||||||
onCloseDetail={this.closeDirentDetail}
|
onCloseDetail={this.closeDirentDetail}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className='cur-view-content lib-content-container' onScroll={this.onItemsScroll}>
|
<div className='cur-view-content lib-content-container' onScroll={this.onItemsScroll}>
|
||||||
{this.state.pathExist ?
|
{this.state.pathExist ?
|
||||||
<DirColumnView
|
<DirColumnView
|
||||||
isSidePanelFolded={this.props.isSidePanelFolded}
|
isSidePanelFolded={this.props.isSidePanelFolded}
|
||||||
isTreePanelShown={this.state.isTreePanelShown}
|
isTreePanelShown={this.state.isTreePanelShown}
|
||||||
isDirentDetailShow={this.state.isDirentDetailShow}
|
isDirentDetailShow={this.state.isDirentDetailShow}
|
||||||
currentMode={this.state.currentMode}
|
currentMode={this.state.currentMode}
|
||||||
path={this.state.path}
|
path={this.state.path}
|
||||||
repoID={this.props.repoID}
|
repoID={this.props.repoID}
|
||||||
currentRepoInfo={this.state.currentRepoInfo}
|
currentRepoInfo={this.state.currentRepoInfo}
|
||||||
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
isGroupOwnedRepo={this.state.isGroupOwnedRepo}
|
||||||
userPerm={userPerm}
|
userPerm={userPerm}
|
||||||
enableDirPrivateShare={enableDirPrivateShare}
|
enableDirPrivateShare={enableDirPrivateShare}
|
||||||
isTreeDataLoading={this.state.isTreeDataLoading}
|
isTreeDataLoading={this.state.isTreeDataLoading}
|
||||||
treeData={this.state.treeData}
|
treeData={this.state.treeData}
|
||||||
currentNode={this.state.currentNode}
|
currentNode={this.state.currentNode}
|
||||||
onNodeClick={this.onTreeNodeClick}
|
onNodeClick={this.onTreeNodeClick}
|
||||||
onNodeCollapse={this.onTreeNodeCollapse}
|
onNodeCollapse={this.onTreeNodeCollapse}
|
||||||
onNodeExpanded={this.onTreeNodeExpanded}
|
onNodeExpanded={this.onTreeNodeExpanded}
|
||||||
onAddFolderNode={this.onAddFolder}
|
onAddFolderNode={this.onAddFolder}
|
||||||
onAddFileNode={this.onAddFile}
|
onAddFileNode={this.onAddFile}
|
||||||
onRenameNode={this.onRenameTreeNode}
|
onRenameNode={this.onRenameTreeNode}
|
||||||
onDeleteNode={this.onDeleteTreeNode}
|
onDeleteNode={this.onDeleteTreeNode}
|
||||||
isViewFile={this.state.isViewFile}
|
isViewFile={this.state.isViewFile}
|
||||||
isFileLoading={this.state.isFileLoading}
|
isFileLoading={this.state.isFileLoading}
|
||||||
filePermission={this.state.filePermission}
|
filePermission={this.state.filePermission}
|
||||||
content={this.state.content}
|
content={this.state.content}
|
||||||
viewId={this.state.viewId}
|
viewId={this.state.viewId}
|
||||||
tagId={this.state.tagId}
|
tagId={this.state.tagId}
|
||||||
lastModified={this.state.lastModified}
|
lastModified={this.state.lastModified}
|
||||||
latestContributor={this.state.latestContributor}
|
latestContributor={this.state.latestContributor}
|
||||||
onLinkClick={this.onLinkClick}
|
onLinkClick={this.onLinkClick}
|
||||||
isRepoInfoBarShow={isRepoInfoBarShow}
|
isRepoInfoBarShow={isRepoInfoBarShow}
|
||||||
repoTags={this.state.repoTags}
|
repoTags={this.state.repoTags}
|
||||||
usedRepoTags={this.state.usedRepoTags}
|
usedRepoTags={this.state.usedRepoTags}
|
||||||
updateUsedRepoTags={this.updateUsedRepoTags}
|
updateUsedRepoTags={this.updateUsedRepoTags}
|
||||||
isDirentListLoading={this.state.isDirentListLoading}
|
isDirentListLoading={this.state.isDirentListLoading}
|
||||||
direntList={direntItemsList}
|
direntList={direntItemsList}
|
||||||
fullDirentList={this.state.direntList}
|
fullDirentList={this.state.direntList}
|
||||||
sortBy={this.state.sortBy}
|
sortBy={this.state.sortBy}
|
||||||
sortOrder={this.state.sortOrder}
|
sortOrder={this.state.sortOrder}
|
||||||
sortItems={this.sortItems}
|
sortItems={this.sortItems}
|
||||||
onAddFolder={this.onAddFolder}
|
onAddFolder={this.onAddFolder}
|
||||||
onAddFile={this.onAddFile}
|
onAddFile={this.onAddFile}
|
||||||
onItemClick={this.onItemClick}
|
onItemClick={this.onItemClick}
|
||||||
onItemSelected={this.onDirentSelected}
|
onItemSelected={this.onDirentSelected}
|
||||||
onItemDelete={this.onMainPanelItemDelete}
|
onItemDelete={this.onMainPanelItemDelete}
|
||||||
onItemRename={this.onMainPanelItemRename}
|
onItemRename={this.onMainPanelItemRename}
|
||||||
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
||||||
renameFileCallback={this.renameItemAjaxCallback}
|
renameFileCallback={this.renameItemAjaxCallback}
|
||||||
onItemMove={this.onMoveItem}
|
onItemMove={this.onMoveItem}
|
||||||
moveFileCallback={this.moveItemsAjaxCallback}
|
moveFileCallback={this.moveItemsAjaxCallback}
|
||||||
onItemCopy={this.onCopyItem}
|
onItemCopy={this.onCopyItem}
|
||||||
copyFileCallback={this.copyItemsAjaxCallback}
|
copyFileCallback={this.copyItemsAjaxCallback}
|
||||||
convertFileCallback={this.convertFileAjaxCallback}
|
convertFileCallback={this.convertFileAjaxCallback}
|
||||||
onItemConvert={this.onConvertItem}
|
onItemConvert={this.onConvertItem}
|
||||||
onDirentClick={this.onDirentClick}
|
onDirentClick={this.onDirentClick}
|
||||||
updateDirent={this.updateDirent}
|
updateDirent={this.updateDirent}
|
||||||
isAllItemSelected={this.state.isAllDirentSelected}
|
isAllItemSelected={this.state.isAllDirentSelected}
|
||||||
onAllItemSelected={this.onAllDirentSelected}
|
onAllItemSelected={this.onAllDirentSelected}
|
||||||
selectedDirentList={this.state.selectedDirentList}
|
selectedDirentList={this.state.selectedDirentList}
|
||||||
onSelectedDirentListUpdate={this.onSelectedDirentListUpdate}
|
onSelectedDirentListUpdate={this.onSelectedDirentListUpdate}
|
||||||
onItemsMove={this.onMoveItems}
|
onItemsMove={this.onMoveItems}
|
||||||
onItemsCopy={this.onCopyItems}
|
onItemsCopy={this.onCopyItems}
|
||||||
onItemsDelete={this.onDeleteItems}
|
onItemsDelete={this.onDeleteItems}
|
||||||
onFileTagChanged={this.onFileTagChanged}
|
onFileTagChanged={this.onFileTagChanged}
|
||||||
showDirentDetail={this.showDirentDetail}
|
showDirentDetail={this.showDirentDetail}
|
||||||
onItemsScroll={this.onItemsScroll}
|
onItemsScroll={this.onItemsScroll}
|
||||||
eventBus={this.props.eventBus}
|
eventBus={this.props.eventBus}
|
||||||
updateCurrentDirent={this.updateCurrentDirent}
|
updateCurrentDirent={this.updateCurrentDirent}
|
||||||
updateCurrentPath={this.updatePath}
|
updateCurrentPath={this.updatePath}
|
||||||
toggleShowDirentToolbar={this.toggleShowDirentToolbar}
|
toggleShowDirentToolbar={this.toggleShowDirentToolbar}
|
||||||
updateTreeNode={this.updateTreeNode}
|
updateTreeNode={this.updateTreeNode}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<div className="message err-tip">{gettext('Folder does not exist.')}</div>
|
<div className="message err-tip">{gettext('Folder does not exist.')}</div>
|
||||||
}
|
}
|
||||||
{!isCustomPermission && this.state.isDirentDetailShow && (
|
{!isCustomPermission && this.state.isDirentDetailShow && (
|
||||||
<Detail
|
<Detail
|
||||||
path={detailPath}
|
path={detailPath}
|
||||||
repoID={this.props.repoID}
|
repoID={this.props.repoID}
|
||||||
currentRepoInfo={{ ...this.state.currentRepoInfo }}
|
currentRepoInfo={{ ...this.state.currentRepoInfo }}
|
||||||
dirent={detailDirent}
|
dirent={detailDirent}
|
||||||
repoTags={this.state.repoTags}
|
repoTags={this.state.repoTags}
|
||||||
fileTags={this.state.isViewFile ? this.state.fileTags : []}
|
fileTags={this.state.isViewFile ? this.state.fileTags : []}
|
||||||
onFileTagChanged={this.onFileTagChanged}
|
onFileTagChanged={this.onFileTagChanged}
|
||||||
onClose={this.closeDirentDetail}
|
onClose={this.closeDirentDetail}
|
||||||
currentMode={this.state.currentMode}
|
currentMode={this.state.currentMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</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>
|
</div>
|
||||||
{canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
|
{isCopyMoveProgressDialogShow && (
|
||||||
<FileUploader
|
<CopyMoveDirentProgressDialog
|
||||||
ref={uploader => this.uploader = uploader}
|
type={this.state.asyncOperationType}
|
||||||
dragAndDrop={true}
|
asyncOperatedFilesLength={this.state.asyncOperatedFilesLength}
|
||||||
path={this.state.path}
|
asyncOperationProgress={this.state.asyncOperationProgress}
|
||||||
repoID={this.props.repoID}
|
toggleDialog={this.onMoveProgressDialogToggle}
|
||||||
direntList={this.state.direntList}
|
|
||||||
onFileUploadSuccess={this.onFileUploadSuccess}
|
|
||||||
isCustomPermission={isCustomPermission}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
{isDeleteFolderDialogOpen && (
|
||||||
{isCopyMoveProgressDialogShow && (
|
<DeleteFolderDialog
|
||||||
<CopyMoveDirentProgressDialog
|
repoID={this.props.repoID}
|
||||||
type={this.state.asyncOperationType}
|
path={this.state.folderToDelete}
|
||||||
asyncOperatedFilesLength={this.state.asyncOperatedFilesLength}
|
deleteFolder={this.deleteFolder}
|
||||||
asyncOperationProgress={this.state.asyncOperationProgress}
|
toggleDialog={this.toggleDeleteFolderDialog}
|
||||||
toggleDialog={this.onMoveProgressDialogToggle}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
<MediaQuery query="(max-width: 767.8px)">
|
||||||
{isDeleteFolderDialogOpen && (
|
<Modal zIndex="1030" isOpen={!isDesktop && this.state.isTreePanelShown} toggle={this.toggleTreePanel} contentClassName="d-none"></Modal>
|
||||||
<DeleteFolderDialog
|
</MediaQuery>
|
||||||
repoID={this.props.repoID}
|
</CollaboratorsProvider>
|
||||||
path={this.state.folderToDelete}
|
</MetadataProvider>
|
||||||
deleteFolder={this.deleteFolder}
|
</TagsProvider>
|
||||||
toggleDialog={this.toggleDeleteFolderDialog}
|
</MetadataStatusProvider>
|
||||||
/>
|
</DndProvider>
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import NameEditPopover from '../../common/name-edit-popover';
|
import NameEditPopover from '../../common/name-edit-popover';
|
||||||
import NavItemIcon from '../../common/nav-item-icon';
|
import NavItemIcon from '../../common/nav-item-icon';
|
||||||
import PageDropdownMenu from './page-dropdownmenu';
|
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 AddNewPageDialog from '../add-new-page-dialog';
|
||||||
import Icon from '../../../../components/icon';
|
import Icon from '../../../../components/icon';
|
||||||
import DraggedPageItem from './dragged-page-item';
|
|
||||||
import CustomIcon from '../../custom-icon';
|
import CustomIcon from '../../custom-icon';
|
||||||
import { eventBus } from '../../../../components/common/event-bus';
|
import { eventBus } from '../../../../components/common/event-bus';
|
||||||
import { INSERT_POSITION } from '../constants';
|
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) {
|
const ref = useRef(null);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
const [, drag] = useDrag(() => ({
|
||||||
this.unsubscribeEvent = eventBus.subscribe('update-wiki-current-page', this.updateSelected);
|
type: 'wiki-page',
|
||||||
}
|
item: () => ({
|
||||||
|
idx: pageIndex,
|
||||||
|
data: { ...page, index: pageIndex },
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
componentWillUnmount() {
|
const [{ isOver, canDrop }, drop] = useDrop(() => ({
|
||||||
this.unsubscribeEvent();
|
accept: 'wiki-page',
|
||||||
this.setState = () => {
|
hover: (item, monitor) => {
|
||||||
return;
|
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 = () => {
|
if (moved_page_id === target_page_id) return;
|
||||||
const isSelected = this.props.getCurrentPageId() === this.props.page.id;
|
|
||||||
if (isSelected !== this.state.isSelected) {
|
if (target._path && target._path.includes(moved_page_id)) {
|
||||||
this.setState({ isSelected });
|
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 = () => {
|
const onMouseLeave = () => {
|
||||||
this.setState({ isMouseEnter: true });
|
setIsMouseEntered(false);
|
||||||
if (this.state.isSelected) return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseMove = () => {
|
const onClickPageItem = () => {
|
||||||
if (!this.state.isMouseEnter) this.setState({ isMouseEnter: true });
|
if (!isShowNameEditor) {
|
||||||
|
setCurrentPage(page.id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseLeave = () => {
|
const onChangeName = (name) => {
|
||||||
this.setState({ isMouseEnter: false });
|
setPageName(name);
|
||||||
if (this.state.isSelected) return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleNameEditor = (e) => {
|
const savePageProperties = useCallback(() => {
|
||||||
if (e) e.stopPropagation();
|
const { name, id } = page;
|
||||||
this.setState({ isShowNameEditor: !this.state.isShowNameEditor }, () => {
|
const currentPageName = pageName.trim();
|
||||||
if (!this.state.isShowNameEditor) {
|
if (currentPageName !== name) {
|
||||||
this.savePageProperties();
|
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 = () => {
|
const toggleDropdown = useCallback(() => {
|
||||||
this.setState({ isShowInsertPage: !this.state.isShowInsertPage });
|
const isShow = !isShowOperationDropdown;
|
||||||
};
|
setIsShowOperationDropdown(isShow);
|
||||||
|
changeItemFreeze(isShow);
|
||||||
|
}, [isShowOperationDropdown]);
|
||||||
|
|
||||||
toggleInsertSiblingPage = (position) => {
|
const toggleInsertSiblingPage = useCallback((position) => {
|
||||||
let insertPosition = null;
|
let insertPosition = null;
|
||||||
if (position === INSERT_POSITION.BELOW || position === INSERT_POSITION.ABOVE) {
|
if (position === INSERT_POSITION.BELOW || position === INSERT_POSITION.ABOVE) {
|
||||||
insertPosition = position;
|
insertPosition = position;
|
||||||
}
|
}
|
||||||
this.setState({
|
setInsertPosition(insertPosition);
|
||||||
insertPosition,
|
setIsShowAddSiblingPage(!isShowAddSiblingPage);
|
||||||
isShowAddSiblingPage: !this.state.isShowAddSiblingPage,
|
}, [isShowAddSiblingPage]);
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
savePageProperties = () => {
|
const toggleInsertPage = useCallback(() => {
|
||||||
const { name, id } = this.props.page;
|
setIsShowInsertPage(!isShowInsertPage);
|
||||||
const pageName = this.state.pageName.trim();
|
}, [isShowInsertPage]);
|
||||||
if (pageName !== name) {
|
|
||||||
const isUpdateBySide = true;
|
const onAddNewPage = useCallback((newPage) => {
|
||||||
this.props.onUpdatePage(id, { name: pageName }, isUpdateBySide);
|
const folded = getFoldState(page.id);
|
||||||
|
if (folded) {
|
||||||
|
toggleExpand(page.id);
|
||||||
}
|
}
|
||||||
};
|
addPageInside(Object.assign({ parentPageId: page.id }, newPage));
|
||||||
|
}, [page, addPageInside, getFoldState, toggleExpand]);
|
||||||
|
|
||||||
onChangeName = (newName) => {
|
const onAddSiblingPage = useCallback((newPage) => {
|
||||||
this.setState({ pageName: newName });
|
addSiblingPage(newPage, parentPageId, insertPosition, page.id, toggleInsertSiblingPage);
|
||||||
};
|
}, [page, parentPageId, addSiblingPage, insertPosition, toggleInsertSiblingPage]);
|
||||||
|
|
||||||
toggleDropdown = () => {
|
const updateSelected = useCallback(() => {
|
||||||
const isShowOperationDropdown = !this.state.isShowOperationDropdown;
|
const isCurrentSelected = getCurrentPageId() === page.id;
|
||||||
this.setState({ isShowOperationDropdown });
|
if (isSelected !== isCurrentSelected) {
|
||||||
this.changeItemFreeze(isShowOperationDropdown);
|
setIsSelected(isCurrentSelected);
|
||||||
};
|
|
||||||
|
|
||||||
changeItemFreeze = (isFreeze) => {
|
|
||||||
if (isFreeze) {
|
|
||||||
this.pageItemRef.classList.add('wiki-page-freezed');
|
|
||||||
} else {
|
|
||||||
this.pageItemRef.classList.remove('wiki-page-freezed');
|
|
||||||
}
|
}
|
||||||
};
|
}, [page, isSelected, getCurrentPageId]);
|
||||||
|
|
||||||
setDocUuid = (docUuid) => {
|
useEffect(() => {
|
||||||
window.seafile['docUuid'] = docUuid;
|
const unsubscribeUpdateCurrentPage = eventBus.subscribe('update-wiki-current-page', updateSelected);
|
||||||
};
|
|
||||||
|
|
||||||
getPageChildrenStyle = () => {
|
return () => {
|
||||||
const folded = this.props.getFoldState(this.props.page.id);
|
unsubscribeUpdateCurrentPage();
|
||||||
return folded ? { height: 0, overflowY: 'hidden' } : { height: 'auto', overflowY: 'visible' };
|
};
|
||||||
};
|
}, [updateSelected]);
|
||||||
|
|
||||||
onClickPageChildren = (e) => {
|
const renderPage = (page, index, pagesLength, isOnlyOnePage) => {
|
||||||
e.stopPropagation();
|
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
renderPage = (page, index, pagesLength, isOnlyOnePage) => {
|
|
||||||
if (!page) return;
|
if (!page) return;
|
||||||
const { pages, pathStr } = this.props;
|
if (!pages.find(item => item.id === page.id)) return;
|
||||||
const id = page.id;
|
|
||||||
if (!pages.find(item => item.id === id)) return;
|
|
||||||
return (
|
return (
|
||||||
<DraggedPageItem
|
<PageItem
|
||||||
key={id}
|
key={page.id}
|
||||||
pagesLength={pagesLength}
|
page={Object.assign({}, pages.find(item => item.id === page.id), page)}
|
||||||
isOnlyOnePage={isOnlyOnePage}
|
|
||||||
page={Object.assign({}, pages.find(item => item.id === id), page)}
|
|
||||||
pageIndex={index}
|
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}
|
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}
|
pathStr={pathStr + '-' + page.id}
|
||||||
getCurrentPageId={this.props.getCurrentPageId}
|
getCurrentPageId={getCurrentPageId}
|
||||||
addPageInside={this.props.addPageInside}
|
addPageInside={addPageInside}
|
||||||
getFoldState={this.props.getFoldState}
|
addSiblingPage={addSiblingPage}
|
||||||
toggleExpand={this.props.toggleExpand}
|
getFoldState={getFoldState}
|
||||||
setClassName={this.props.setClassName}
|
toggleExpand={toggleExpand}
|
||||||
getClassName={this.props.getClassName}
|
setClassName={setClassName}
|
||||||
layerDragProps={this.props.layerDragProps}
|
getClassName={getClassName}
|
||||||
addSiblingPage={this.props.addSiblingPage}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleExpand = (e) => {
|
const childNumber = Array.isArray(page.children) ? page.children.length : 0;
|
||||||
e.stopPropagation();
|
const customIcon = page.icon;
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
const navItemId = `page-editor-${page.id}`;
|
||||||
this.props.toggleExpand(this.props.page.id);
|
const pageChildrenStyle = getFoldState(page.id) ? {
|
||||||
this.forceUpdate();
|
height: 0, overflowY: 'hidden'
|
||||||
|
} : {
|
||||||
|
height: 'auto', overflowY: 'visible'
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickPageItem = () => {
|
if (wikiPermission === 'rw') {
|
||||||
if (!this.state.isShowNameEditor) {
|
return (
|
||||||
this.props.setCurrentPage(this.props.page.id);
|
<>
|
||||||
}
|
<div
|
||||||
};
|
id={navItemId}
|
||||||
|
ref={ref}
|
||||||
onAddNewPage = (newPage) => {
|
className={classnames('wiki-page-item', {
|
||||||
const { page } = this.props;
|
'selected-page': isSelected,
|
||||||
const folded = this.props.getFoldState(page.id);
|
'dragged-page-over': isOver && canDrop && !dropPosition,
|
||||||
if (folded) {
|
'page-can-drop-top': isOver && canDrop && dropPosition === 'top',
|
||||||
this.props.toggleExpand(page.id);
|
'page-can-drop-bottom': isOver && canDrop && dropPosition === 'bottom',
|
||||||
}
|
})}
|
||||||
this.props.addPageInside(Object.assign({ parentPageId: this.props.page.id }, newPage));
|
onMouseEnter={onMouseEnter}
|
||||||
};
|
onMouseMove={onMouseMove}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
onAddSiblingPage = (newPage) => {
|
>
|
||||||
const { page } = this.props;
|
<div className="wiki-page-item-main" onClick={onClickPageItem}>
|
||||||
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(
|
|
||||||
<div
|
<div
|
||||||
className={this.getPageClassName()}
|
className="wiki-page-content"
|
||||||
ref={ref => this.pageItemRef = ref}
|
style={pathStr ? {
|
||||||
onMouseEnter={this.onMouseEnter}
|
marginLeft: (pathStr.split('-').length - 1) * 24
|
||||||
onMouseMove={this.onMouseMove}
|
} : {}}
|
||||||
onMouseLeave={this.onMouseLeave}
|
|
||||||
id={navItemId}
|
|
||||||
>
|
>
|
||||||
<div className="wiki-page-item-main" onClick={this.onClickPageItem}>
|
{childNumber === 0 && (customIcon ? (
|
||||||
<div className='wiki-page-content' style={pathStr ? { marginLeft: `${(pathStr.split('-').length - 1) * 24}px` } : {}}>
|
<CustomIcon icon={customIcon} />
|
||||||
{childNumber === 0 && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'file'} disable={true} />)
|
) : (
|
||||||
}
|
<NavItemIcon symbol={'file'} disable={true} />
|
||||||
{(!isMouseEnter && childNumber > 0) && (customIcon ? <CustomIcon icon={customIcon} /> : <NavItemIcon symbol={'files'} disable={true} />)}
|
))}
|
||||||
{(isMouseEnter && childNumber > 0) &&
|
{(!isMouseEntered && childNumber > 0) && (customIcon ? (
|
||||||
<div className='nav-item-icon' onClick={this.toggleExpand} role='button'>
|
<CustomIcon icon={customIcon} />
|
||||||
<i className={`sf3-font-down sf3-font ${folded ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
) : (
|
||||||
</div>
|
<NavItemIcon symbol={'file'} disable={true} />
|
||||||
}
|
))}
|
||||||
<span className="wiki-page-title text-truncate" title={page.name}>{page.name}</span>
|
{(isMouseEntered && childNumber > 0) && (
|
||||||
{isShowNameEditor && (
|
<div role="button" className="nav-item-icon" onClick={() => toggleExpand(page.id)}>
|
||||||
<NameEditPopover
|
<i className={`sf3-font-down sf3-font ${getFoldState(page.id) ? 'rotate-270' : ''}`} aria-hidden="true"></i>
|
||||||
oldName={pageName}
|
|
||||||
targetId={navItemId}
|
|
||||||
onChangeName={this.onChangeName}
|
|
||||||
toggleEditor={this.toggleNameEditor}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="d-none d-md-flex">
|
<span className="wiki-page-title text-truncate" title={page.name}>{page.name}</span>
|
||||||
<div className="more-wiki-page-operation" onClick={this.toggleDropdown}>
|
{isShowNameEditor && (
|
||||||
<Icon symbol={'more-level'} />
|
<NameEditPopover
|
||||||
{this.state.isShowOperationDropdown &&
|
oldName={pageName}
|
||||||
<PageDropdownMenu
|
target_page_id={navItemId}
|
||||||
page={page}
|
onChangeName={onChangeName}
|
||||||
pages={this.props.pages}
|
toggleEditor={toggleNameEditor}
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{this.state.isShowAddSiblingPage &&
|
</div>
|
||||||
<AddNewPageDialog
|
</div>
|
||||||
toggle={this.toggleInsertSiblingPage}
|
<div className="d-none d-md-flex">
|
||||||
onAddNewPage={this.onAddSiblingPage}
|
<div className="more-wiki-page-operation" onClick={toggleDropdown}>
|
||||||
title={gettext('Add page')}
|
<Icon symbol="more-level" />
|
||||||
insertPosition={this.state.insertPosition}
|
{isShowOperationDropdown && (
|
||||||
page={this.props.page}
|
<PageDropdownMenu
|
||||||
|
page={page}
|
||||||
|
pages={pages}
|
||||||
|
pagesLength={pagesLength}
|
||||||
|
isOnlyOnePage={isOnlyOnePage}
|
||||||
|
toggle={toggleDropdown}
|
||||||
|
toggleNameEditor={toggleNameEditor}
|
||||||
|
duplicatePage={duplicatePage}
|
||||||
|
onDeletePage={() => onDeletePage(page.id)}
|
||||||
|
toggleInsertSiblingPage={toggleInsertSiblingPage}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)))}
|
<div className="wiki-add-page-btn" onClick={toggleInsertPage} role='button'>
|
||||||
<div className="page-children" style={this.getPageChildrenStyle()} onClick={this.onClickPageChildren}>
|
<span className='sf3-font sf3-font-enlarge' aria-hidden="true"></span>
|
||||||
{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>
|
</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>
|
</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>
|
||||||
);
|
<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 = {
|
PageItem.propTypes = {
|
||||||
isOver: PropTypes.bool,
|
|
||||||
canDrop: PropTypes.bool,
|
|
||||||
isDragging: PropTypes.bool,
|
|
||||||
draggedPage: PropTypes.object,
|
|
||||||
page: PropTypes.object,
|
page: PropTypes.object,
|
||||||
pages: PropTypes.array,
|
pages: PropTypes.array,
|
||||||
pageIndex: PropTypes.number,
|
pageIndex: PropTypes.number,
|
||||||
pagesLength: PropTypes.number,
|
pagesLength: PropTypes.number,
|
||||||
connectDragSource: PropTypes.func,
|
parentPageId: PropTypes.string,
|
||||||
connectDragPreview: PropTypes.func,
|
addSiblingPage: PropTypes.func,
|
||||||
connectDropTarget: PropTypes.func,
|
|
||||||
duplicatePage: PropTypes.func,
|
duplicatePage: PropTypes.func,
|
||||||
setCurrentPage: PropTypes.func,
|
setCurrentPage: PropTypes.func,
|
||||||
onUpdatePage: PropTypes.func,
|
onUpdatePage: PropTypes.func,
|
||||||
|
@@ -2,9 +2,9 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import { DropTarget, DragLayer } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import html5DragDropContext from './html5DragDropContext';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import DraggedPageItem from './pages/dragged-page-item';
|
import PageItem from './pages/page-item';
|
||||||
import { gettext, wikiPermission } from '../../../utils/constants';
|
import { gettext, wikiPermission } from '../../../utils/constants';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
@@ -30,28 +30,34 @@ class WikiNav extends Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
idFoldedStatusMap: {}, // Move idFoldedStatusMap to state
|
||||||
|
};
|
||||||
this.folderClassNameCache = '';
|
this.folderClassNameCache = '';
|
||||||
this.lastScrollTop = 0;
|
this.lastScrollTop = 0;
|
||||||
this.wikiNavBodyRef = React.createRef();
|
this.wikiNavBodyRef = React.createRef();
|
||||||
// set init pages are all folded
|
// Initialize pages as folded
|
||||||
this.idFoldedStatusMap = {};
|
const idFoldedStatusMap = {};
|
||||||
props.pages.forEach((page) => {
|
props.pages.forEach((page) => {
|
||||||
this.idFoldedStatusMap[page.id] = true;
|
idFoldedStatusMap[page.id] = true;
|
||||||
});
|
});
|
||||||
|
this.state.idFoldedStatusMap = idFoldedStatusMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFoldState = (pageId) => {
|
getFoldState = (pageId) => {
|
||||||
return this.idFoldedStatusMap[pageId];
|
return this.state.idFoldedStatusMap[pageId];
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleExpand = (pageId) => {
|
toggleExpand = (pageId) => {
|
||||||
const idFoldedStatusMap = this.idFoldedStatusMap;
|
this.setState((prevState) => {
|
||||||
if (idFoldedStatusMap[pageId]) {
|
const idFoldedStatusMap = { ...prevState.idFoldedStatusMap };
|
||||||
delete idFoldedStatusMap[pageId];
|
if (idFoldedStatusMap[pageId]) {
|
||||||
} else {
|
delete idFoldedStatusMap[pageId];
|
||||||
idFoldedStatusMap[pageId] = true;
|
} else {
|
||||||
}
|
idFoldedStatusMap[pageId] = true;
|
||||||
this.idFoldedStatusMap = idFoldedStatusMap;
|
}
|
||||||
|
return { idFoldedStatusMap };
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -72,12 +78,12 @@ class WikiNav extends Component {
|
|||||||
return this.folderClassNameCache;
|
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 { pages } = this.props;
|
||||||
const id = page.id;
|
const id = page.id;
|
||||||
if (!pages.find(item => item.id === id)) return;
|
if (!pages.find(item => item.id === id)) return;
|
||||||
return (
|
return (
|
||||||
<DraggedPageItem
|
<PageItem
|
||||||
key={id}
|
key={id}
|
||||||
pagesLength={pagesLength}
|
pagesLength={pagesLength}
|
||||||
isOnlyOnePage={isOnlyOnePage}
|
isOnlyOnePage={isOnlyOnePage}
|
||||||
@@ -94,10 +100,10 @@ class WikiNav extends Component {
|
|||||||
getCurrentPageId={this.props.getCurrentPageId}
|
getCurrentPageId={this.props.getCurrentPageId}
|
||||||
addPageInside={this.props.addPageInside}
|
addPageInside={this.props.addPageInside}
|
||||||
addSiblingPage={this.props.addSiblingPage}
|
addSiblingPage={this.props.addSiblingPage}
|
||||||
|
idFoldedStatusMap={this.idFoldedStatusMap}
|
||||||
getFoldState={this.getFoldState}
|
getFoldState={this.getFoldState}
|
||||||
toggleExpand={this.toggleExpand}
|
toggleExpand={this.toggleExpand}
|
||||||
id_page_map={id_page_map}
|
id_page_map={id_page_map}
|
||||||
layerDragProps={layerDragProps}
|
|
||||||
setClassName={this.setClassName}
|
setClassName={this.setClassName}
|
||||||
getClassName={this.getClassName}
|
getClassName={this.getClassName}
|
||||||
/>
|
/>
|
||||||
@@ -105,7 +111,7 @@ class WikiNav extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
renderStructureBody = React.forwardRef((layerDragProps, ref) => {
|
renderStructureBody = () => {
|
||||||
const { navigation, pages } = this.props;
|
const { navigation, pages } = this.props;
|
||||||
const pagesLen = pages.length;
|
const pagesLen = pages.length;
|
||||||
const isOnlyOnePage = pagesLen === 1;
|
const isOnlyOnePage = pagesLen === 1;
|
||||||
@@ -131,7 +137,7 @@ class WikiNav extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{navigation.map((item, index) => {
|
{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' &&
|
{wikiPermission === 'rw' &&
|
||||||
<>
|
<>
|
||||||
@@ -146,27 +152,15 @@ class WikiNav extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
collect = (monitor) => {
|
|
||||||
return {
|
|
||||||
item: monitor.getItem(),
|
|
||||||
itemType: monitor.getItemType(),
|
|
||||||
clientOffset: monitor.getClientOffset(),
|
|
||||||
isDragging: monitor.isDragging()
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const StructureBody = html5DragDropContext(
|
|
||||||
DropTarget('WikiNav', {}, connect => ({
|
|
||||||
connectDropTarget: connect.dropTarget()
|
|
||||||
}))(DragLayer(this.collect)(this.renderStructureBody))
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div className='wiki-nav'>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<StructureBody />
|
<div className='wiki-nav'>
|
||||||
</div>
|
{this.renderStructureBody()}
|
||||||
|
</div>
|
||||||
|
</DndProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user