mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
Add right-click menu, move and copy effects (#3148)
This commit is contained in:
@@ -49,7 +49,7 @@ class DirColumnNav extends React.Component {
|
|||||||
imageIndex: 0,
|
imageIndex: 0,
|
||||||
isCopyDialogShow: false,
|
isCopyDialogShow: false,
|
||||||
isMoveDialogShow: false,
|
isMoveDialogShow: false,
|
||||||
isMutipleOperation:false,
|
isMutipleOperation: false,
|
||||||
};
|
};
|
||||||
this.isNodeMenuShow = true;
|
this.isNodeMenuShow = true;
|
||||||
}
|
}
|
||||||
@@ -71,10 +71,18 @@ class DirColumnNav extends React.Component {
|
|||||||
this.setState({opNode: node});
|
this.setState({opNode: node});
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'New Folder':
|
case 'New Folder':
|
||||||
this.onAddFolderToggle();
|
if (!node) {
|
||||||
|
this.onAddFolderToggle('root');
|
||||||
|
} else {
|
||||||
|
this.onAddFolderToggle();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'New File':
|
case 'New File':
|
||||||
this.onAddFileToggle();
|
if (!node) {
|
||||||
|
this.onAddFileToggle('root');
|
||||||
|
} else {
|
||||||
|
this.onAddFileToggle();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'Rename':
|
case 'Rename':
|
||||||
this.onRenameToggle();
|
this.onRenameToggle();
|
||||||
@@ -126,7 +134,7 @@ class DirColumnNav extends React.Component {
|
|||||||
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
|
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
|
||||||
}
|
}
|
||||||
|
|
||||||
onCopyToggle =() => {
|
onCopyToggle = () => {
|
||||||
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow})
|
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,8 @@ const propTypes = {
|
|||||||
onMenuItemClick: PropTypes.func.isRequired,
|
onMenuItemClick: PropTypes.func.isRequired,
|
||||||
onFreezedItem: PropTypes.func.isRequired,
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
onUnFreezedItem: PropTypes.func.isRequired,
|
onUnFreezedItem: PropTypes.func.isRequired,
|
||||||
|
registerHandlers: PropTypes.func,
|
||||||
|
unregisterHandlers: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TreeNodeMenu extends React.Component {
|
class TreeNodeMenu extends React.Component {
|
||||||
@@ -91,6 +93,8 @@ class TreeNodeMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
this.state.isItemMenuShow ? this.props.unregisterHandlers() : this.props.registerHandlers()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
|
@@ -17,6 +17,8 @@ const propTypes = {
|
|||||||
onFreezedItem: PropTypes.func.isRequired,
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
onUnFreezedItem: PropTypes.func.isRequired,
|
onUnFreezedItem: PropTypes.func.isRequired,
|
||||||
onMenuItemClick: PropTypes.func,
|
onMenuItemClick: PropTypes.func,
|
||||||
|
registerHandlers: PropTypes.func,
|
||||||
|
unregisterHandlers: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
class TreeNodeView extends React.Component {
|
class TreeNodeView extends React.Component {
|
||||||
@@ -36,6 +38,7 @@ class TreeNodeView extends React.Component {
|
|||||||
isHighlight: true,
|
isHighlight: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.props.onNodeChanged(this.props.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseLeave = () => {
|
onMouseLeave = () => {
|
||||||
@@ -45,6 +48,7 @@ class TreeNodeView extends React.Component {
|
|||||||
isHighlight: false,
|
isHighlight: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.props.onNodeChanged(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
onNodeClick = () => {
|
onNodeClick = () => {
|
||||||
@@ -127,6 +131,9 @@ class TreeNodeView extends React.Component {
|
|||||||
onFreezedItem={this.props.onFreezedItem}
|
onFreezedItem={this.props.onFreezedItem}
|
||||||
onMenuItemClick={this.onMenuItemClick}
|
onMenuItemClick={this.onMenuItemClick}
|
||||||
onUnFreezedItem={this.onUnFreezedItem}
|
onUnFreezedItem={this.onUnFreezedItem}
|
||||||
|
onNodeChanged={this.props.onNodeChanged}
|
||||||
|
registerHandlers={this.props.registerHandlers}
|
||||||
|
unregisterHandlers={this.props.unregisterHandlers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -165,6 +172,8 @@ class TreeNodeView extends React.Component {
|
|||||||
onMenuItemClick={this.onMenuItemClick}
|
onMenuItemClick={this.onMenuItemClick}
|
||||||
onUnFreezedItem={this.onUnFreezedItem}
|
onUnFreezedItem={this.onUnFreezedItem}
|
||||||
onFreezedItem={this.props.onFreezedItem}
|
onFreezedItem={this.props.onFreezedItem}
|
||||||
|
registerHandlers={this.props.registerHandlers}
|
||||||
|
unregisterHandlers={this.props.unregisterHandlers}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
165
frontend/src/components/tree-view/tree-view-contextmenu.js
Normal file
165
frontend/src/components/tree-view/tree-view-contextmenu.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
import '../../css/tree-view-contextmenu.css'
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onMenuItemClick: PropTypes.func.isRequired,
|
||||||
|
node: PropTypes.object,
|
||||||
|
mousePosition: PropTypes.object,
|
||||||
|
closeRightMenu: PropTypes.func,
|
||||||
|
registerHandlers:PropTypes.func,
|
||||||
|
unregisterHandlers:PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
class TreeViewContextMenu extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isItemMenuShow: false,
|
||||||
|
menuList: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let menuList = this.caculateMenuList();
|
||||||
|
this.setState({menuList: menuList});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.calculateMenuDistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('click', this.listenClick);
|
||||||
|
document.removeEventListener('mousemove', this.handleContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
caculateMenuList() {
|
||||||
|
let { node } = this.props;
|
||||||
|
let menuList = [];
|
||||||
|
if (!node) {
|
||||||
|
menuList = ['New Folder', 'New File'];
|
||||||
|
} else {
|
||||||
|
if (node.object.type === 'dir') {
|
||||||
|
menuList = ['New Folder', 'New File', 'Copy', 'Move', 'Rename', 'Delete'];
|
||||||
|
} else {
|
||||||
|
menuList = ['Rename', 'Delete', 'Copy', 'Move', 'Open in New Tab'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menuList;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateMenuDistance = () => {
|
||||||
|
let mousePosition = this.props.mousePosition;
|
||||||
|
let rightTreeMenu = document.querySelector('.right-tree-menu');
|
||||||
|
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
|
||||||
|
|
||||||
|
if (mousePosition.clientY + rightTreeMenuHeight > document.body.clientHeight) {
|
||||||
|
rightTreeMenu.style.top = mousePosition.clientY - rightTreeMenuHeight + 'px';
|
||||||
|
} else {
|
||||||
|
rightTreeMenu.style.top = mousePosition.clientY + 'px';
|
||||||
|
}
|
||||||
|
rightTreeMenu.style.left = mousePosition.clientX + 'px';
|
||||||
|
|
||||||
|
document.addEventListener('click', this.listenClick);
|
||||||
|
document.addEventListener('mousemove', this.handleContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
translateMenuItem = (menuItem) => {
|
||||||
|
let translateResult = '';
|
||||||
|
switch(menuItem) {
|
||||||
|
case 'New Folder':
|
||||||
|
translateResult = gettext('New Folder');
|
||||||
|
break;
|
||||||
|
case 'New File':
|
||||||
|
translateResult = gettext('New File');
|
||||||
|
break;
|
||||||
|
case 'Rename':
|
||||||
|
translateResult = gettext('Rename');
|
||||||
|
break;
|
||||||
|
case 'Copy':
|
||||||
|
translateResult = gettext('Copy');
|
||||||
|
break;
|
||||||
|
case 'Move':
|
||||||
|
translateResult = gettext('Move');
|
||||||
|
break;
|
||||||
|
case 'Delete':
|
||||||
|
translateResult = gettext('Delete');
|
||||||
|
break;
|
||||||
|
case 'Open in New Tab':
|
||||||
|
translateResult = gettext('Open in New Tab');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return translateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleContextMenu = (e) => {
|
||||||
|
let mousePosition = this.props.mousePosition;
|
||||||
|
let rightTreeMenu = document.querySelector('.right-tree-menu');
|
||||||
|
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
|
||||||
|
let rightTreeMenuWidth = rightTreeMenu.offsetWidth;
|
||||||
|
|
||||||
|
if (mousePosition.clientY + rightTreeMenuHeight > document.body.clientHeight) {
|
||||||
|
if ((e.clientX >= mousePosition.clientX) && (e.clientX <= (mousePosition.clientX + rightTreeMenuWidth)) && (e.clientY <= mousePosition.clientY) && (e.clientY >= (mousePosition.clientY - rightTreeMenuHeight))) {
|
||||||
|
this.props.unregisterHandlers();
|
||||||
|
} else {
|
||||||
|
this.props.registerHandlers();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((e.clientX >= mousePosition.clientX) && (e.clientX <= (mousePosition.clientX + rightTreeMenuWidth)) && (e.clientY >= mousePosition.clientY) && (e.clientY <= (mousePosition.clientY + rightTreeMenuHeight))) {
|
||||||
|
this.props.unregisterHandlers();
|
||||||
|
} else {
|
||||||
|
this.props.registerHandlers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listenClick = (e) => {
|
||||||
|
let mousePosition = this.props.mousePosition;
|
||||||
|
let rightTreeMenu = document.querySelector('.right-tree-menu');
|
||||||
|
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
|
||||||
|
let rightTreeMenuWidth = rightTreeMenu.offsetWidth;
|
||||||
|
|
||||||
|
if ((e.clientX <= mousePosition.clientX) || (e.clientX >= (mousePosition.clientX + rightTreeMenuWidth))) {
|
||||||
|
this.props.closeRightMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mousePosition.clientY + rightTreeMenuHeight > document.body.clientHeight) {
|
||||||
|
if ((e.clientY <= (mousePosition.clientY - rightTreeMenuHeight)) || e.clientY >= mousePosition.clientY) {
|
||||||
|
this.props.closeRightMenu();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((e.clientY <= mousePosition.clientY) || (e.clientY >= (mousePosition.clientY + rightTreeMenuHeight))) {
|
||||||
|
this.props.closeRightMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClick = (event) => {
|
||||||
|
let operation = event.target.dataset.toggle;
|
||||||
|
let node = this.props.node;
|
||||||
|
this.props.onMenuItemClick(operation, node);
|
||||||
|
this.props.closeRightMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='right-tree-menu'>
|
||||||
|
{this.state.menuList.map((menuItem, index) => {
|
||||||
|
return (
|
||||||
|
<button className='right-tree-item' key={index} data-toggle={menuItem} onClick={this.onMenuItemClick}>{this.translateMenuItem(menuItem)}</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeViewContextMenu.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default TreeViewContextMenu;
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TreeNodeView from './tree-node-view';
|
import TreeNodeView from './tree-node-view';
|
||||||
|
|
||||||
|
import TreeViewContextMenu from './tree-view-contextmenu'
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
repoPermission: PropTypes.bool,
|
repoPermission: PropTypes.bool,
|
||||||
isNodeMenuShow: PropTypes.bool.isRequired,
|
isNodeMenuShow: PropTypes.bool.isRequired,
|
||||||
@@ -21,9 +23,21 @@ class TreeView extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isItemFreezed: false,
|
isItemFreezed: false,
|
||||||
|
isRightMenuShow: false,
|
||||||
|
nodeData: null,
|
||||||
|
fileData: null,
|
||||||
|
mousePosition: {clientX: '', clientY: ''},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.registerHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unregisterHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
onNodeDragStart = (e, node) => {
|
onNodeDragStart = (e, node) => {
|
||||||
// todo
|
// todo
|
||||||
}
|
}
|
||||||
@@ -36,6 +50,46 @@ class TreeView extends React.Component {
|
|||||||
this.setState({isItemFreezed: false});
|
this.setState({isItemFreezed: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextMenu = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
isRightMenuShow:false,
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
isRightMenuShow:true,
|
||||||
|
fileData:this.state.nodeData,
|
||||||
|
mousePosition: {clientX: e.clientX, clientY: e.clientY}
|
||||||
|
})
|
||||||
|
},40)
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterHandlers = () => {
|
||||||
|
let treeView = document.querySelector('.tree-view');
|
||||||
|
treeView.removeEventListener('contextmenu', this.contextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerHandlers = () => {
|
||||||
|
let treeView = document.querySelector('.tree-view');
|
||||||
|
treeView.addEventListener('contextmenu', this.contextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeChanged = (node) => {
|
||||||
|
this.setState({
|
||||||
|
nodeData:node
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
closeRightMenu = () => {
|
||||||
|
this.setState({
|
||||||
|
isRightMenuShow:false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClick = (operation, node) => {
|
||||||
|
this.props.onMenuItemClick(operation, node)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="tree-view tree">
|
<div className="tree-view tree">
|
||||||
@@ -53,7 +107,20 @@ class TreeView extends React.Component {
|
|||||||
onNodeDragStart={this.onNodeDragStart}
|
onNodeDragStart={this.onNodeDragStart}
|
||||||
onFreezedItem={this.onFreezedItem}
|
onFreezedItem={this.onFreezedItem}
|
||||||
onUnFreezedItem={this.onUnFreezedItem}
|
onUnFreezedItem={this.onUnFreezedItem}
|
||||||
|
onNodeChanged={this.onNodeChanged}
|
||||||
|
registerHandlers={this.registerHandlers}
|
||||||
|
unregisterHandlers={this.unregisterHandlers}
|
||||||
/>
|
/>
|
||||||
|
{this.state.isRightMenuShow && (
|
||||||
|
<TreeViewContextMenu
|
||||||
|
node={this.state.fileData}
|
||||||
|
onMenuItemClick={this.onMenuItemClick}
|
||||||
|
mousePosition={this.state.mousePosition}
|
||||||
|
closeRightMenu={this.closeRightMenu}
|
||||||
|
registerHandlers={this.registerHandlers}
|
||||||
|
unregisterHandlers={this.unregisterHandlers}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -39,6 +39,7 @@
|
|||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
padding: 12px 12px 12px 0;
|
padding: 12px 12px 12px 0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-inner {
|
.tree-node-inner {
|
||||||
|
35
frontend/src/css/tree-view-contextmenu.css
Normal file
35
frontend/src/css/tree-view-contextmenu.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.right-tree-menu {
|
||||||
|
position: absolute;
|
||||||
|
left: 15rem;
|
||||||
|
top: 20rem;
|
||||||
|
z-index: 1000;
|
||||||
|
float: left;
|
||||||
|
min-width: 10rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
margin: 0.125rem 0 0;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: #212529;
|
||||||
|
text-align: left;
|
||||||
|
list-style: none;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.right-tree-item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.25rem 1.5rem;
|
||||||
|
clear: both;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #878E9C;
|
||||||
|
text-align: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.right-tree-item:hover {
|
||||||
|
color: #15181A;
|
||||||
|
background-color: #F8F9FA;
|
||||||
|
}
|
Reference in New Issue
Block a user