1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 02:10:24 +00:00

Contextmenu improve (#3238)

* add a commen contextmenu component

* optimized translate for menu

* repair contextmenu bug

* optimized share btn show code

* repair showShareBtn bug

* optimized contextmenu

* optimized contextmenu code

* complete dirent-item-menu logic

* optimized contextmenu code

* complete dirent-container-menu logic

* complete tree-node-contextmenu logic

* delete unnecessary code

* optimized contextmenu func

* repair bug

* optimized code style

* optimized code style

* add a dirent-none-view for dir-list-view mode

* optimized dirent-container-menu&dirent-item-menu

* add select-item contextmenu

* repair rebase bug
This commit is contained in:
杨顺强
2019-04-11 21:04:47 +08:00
committed by Daniel Pan
parent 1534d1e737
commit 5d2a2b238c
20 changed files with 1175 additions and 968 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import listener from '../context-menu/globalEventListener';
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
import { gettext } from '../../utils/constants';
@@ -8,9 +9,6 @@ const propTypes = {
onMenuItemClick: PropTypes.func.isRequired,
onFreezedItem: PropTypes.func.isRequired,
onUnFreezedItem: PropTypes.func.isRequired,
registerHandlers: PropTypes.func,
unregisterHandlers: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
};
class TreeNodeMenu extends React.Component {
@@ -26,11 +24,22 @@ class TreeNodeMenu extends React.Component {
componentDidMount() {
let menuList = this.caculateMenuList();
this.setState({menuList: menuList});
this.listenerId = listener.register(this.onShowMenu, this.onHideMenu);
}
componentWillReceiveProps(nextProps) {
if (nextProps.appMenuType !== 'item_op_menu') {
this.setState({isItemMenuShow: false});
componentWillUnmount () {
if (this.listenerId) {
listener.unregister(this.listenerId);
}
}
onShowMenu = () => {
// nothing todo
}
onHideMenu = () => {
if (this.state.isItemMenuShow) {
this.setState({isItemMenuShow: false});
this.props.onUnFreezedItem();
}
}

View File

@@ -22,6 +22,7 @@ const propTypes = {
onNodeDragMove: PropTypes.func,
onNodeDrop: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
handleContextClick: PropTypes.func.isRequired,
};
class TreeNodeView extends React.Component {
@@ -35,12 +36,12 @@ class TreeNodeView extends React.Component {
};
}
componentWillReceiveProps(nextProp) {
if (nextProp.appMenuType === 'list_view_contextmenu' && nextProp.appMenuType === 'item_contextmenu') {
componentWillReceiveProps(nextProps) {
if (!nextProps.isItemFreezed) {
this.setState({
isShowOperationMenu: false,
isHighlight: false,
})
});
}
}
@@ -51,7 +52,6 @@ class TreeNodeView extends React.Component {
isHighlight: true,
});
}
this.props.onNodeChanged(this.props.node)
}
onMouseLeave = () => {
@@ -61,7 +61,6 @@ class TreeNodeView extends React.Component {
isHighlight: false,
});
}
this.props.onNodeChanged(null)
}
onNodeClick = () => {
@@ -112,6 +111,22 @@ class TreeNodeView extends React.Component {
this.props.onMenuItemClick(operation, node);
}
onItemMouseDown = (event) => {
event.stopPropagation();
if (event.button === 2) {
return;
}
}
onItemContextMenu = (event) => {
this.handleContextClick(event);
}
handleContextClick = (event) => {
this.props.handleContextClick(event, this.props.node);
this.setState({isShowOperationMenu: false});
}
getNodeTypeAndIcon = () => {
let { node } = this.props;
let icon = '';
@@ -175,6 +190,7 @@ class TreeNodeView extends React.Component {
onNodeDragEnter={this.props.onNodeDragEnter}
onNodeDragLeave={this.props.onNodeDragLeave}
appMenuType={this.props.appMenuType}
handleContextClick={this.props.handleContextClick}
/>
);
})}
@@ -191,9 +207,15 @@ class TreeNodeView extends React.Component {
}
return (
<div className="tree-node">
<div type={type} title={node.object.name}
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
className={`tree-node-inner text-nowrap ${hlClass} ${node.path === '/'? 'hide': ''} ${this.state.isNodeDropShow ? 'tree-node-drop' : ''}`}>
<div
type={type}
className={`tree-node-inner text-nowrap ${hlClass} ${node.path === '/'? 'hide': ''} ${this.state.isNodeDropShow ? 'tree-node-drop' : ''}`}
title={node.object.name}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onMouseDown={this.onItemMouseDown}
onContextMenu={this.onItemContextMenu}
>
<div className="tree-node-text" draggable="true" onDragStart={this.onNodeDragStart} onClick={this.onNodeClick} onDragEnter={this.onNodeDragEnter} onDragLeave={this.onNodeDragLeave} onDragOver={this.onNodeDragMove} onDrop={this.onNodeDrop}>{node.object.name}</div>
<div className="left-icon">
{type === 'dir' && (!node.isLoaded || (node.isLoaded && node.hasChildren())) && (

View File

@@ -1,165 +0,0 @@
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;

View File

@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import TextTranslation from '../../utils/text-translation';
import TreeNodeView from './tree-node-view';
import TreeViewContextMenu from './tree-view-contextmenu'
import ContextMenu from '../context-menu/context-menu';
import { hideMenu, showMenu } from '../context-menu/actions';
const propTypes = {
repoPermission: PropTypes.bool,
@@ -15,8 +16,7 @@ const propTypes = {
onNodeCollapse: PropTypes.func.isRequired,
onItemMove: PropTypes.func,
currentRepoInfo: PropTypes.object,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
selectedDirentList: PropTypes.array.isRequired,
};
const PADDING_LEFT = 20;
@@ -27,26 +27,10 @@ class TreeView extends React.Component {
super(props);
this.state = {
isItemFreezed: false,
isRightMenuShow: false,
nodeData: null,
fileData: null,
mousePosition: {clientX: '', clientY: ''},
isTreeViewDropTipShow: false,
};
}
componentDidMount() {
this.registerHandlers();
}
componentDidUpdate() {
this.registerHandlers();
}
componentWillUnmount() {
this.unregisterHandlers();
}
onItemMove = (repo, dirent, selectedPath, currentPath) => {
this.props.onItemMove(repo, dirent, selectedPath, currentPath);
}
@@ -130,59 +114,96 @@ class TreeView extends React.Component {
onFreezedItem = () => {
this.setState({isItemFreezed: true});
this.props.switchAnotherMenuToShow('item_op_menu');
}
onUnFreezedItem = () => {
this.setState({isItemFreezed: false});
}
contextMenu = (e) => {
e.preventDefault();
this.props.switchAnotherMenuToShow('tree_contextmenu');
this.setState({
isRightMenuShow:false,
}, () => {
this.setState({
isRightMenuShow: true,
fileData: this.state.nodeData,
mousePosition: {clientX: e.clientX, clientY: e.clientY}
})
})
}
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,
})
this.onUnFreezedItem();
}
onMenuItemClick = (operation, node) => {
this.props.onMenuItemClick(operation, node)
this.props.onMenuItemClick(operation, node);
hideMenu();
}
onMouseDown = (event) => {
event.stopPropagation();
if (event.button === 2) {
return;
}
}
onContextMenu = (event) => {
this.handleContextClick(event);
}
handleContextClick = (event, node) => {
if (this.props.selectedDirentList.length) {
return;
}
event.preventDefault();
event.stopPropagation();
let x = event.clientX || (event.touches && event.touches[0].pageX);
let y = event.clientY || (event.touches && event.touches[0].pageY);
if (this.props.posX) {
x -= this.props.posX;
}
if (this.props.posY) {
y -= this.props.posY;
}
hideMenu();
let menuList = this.getMenuList(node);
let showMenuConfig = {
id: 'tree-node-contextmenu',
position: { x, y },
target: event.target,
currentObject: node,
menuList: menuList,
};
showMenu(showMenuConfig);
}
getMenuList = (node) => {
let menuList = [];
let { NEW_FOLDER, NEW_FILE, COPY, MOVE, RENAME, DELETE, OPEN_VIA_CLIENT} = TextTranslation;
if (!node) {
return [NEW_FOLDER, NEW_FILE];
}
if (node.object.type === 'dir') {
menuList = [NEW_FOLDER, NEW_FILE, COPY, MOVE, RENAME, DELETE];
} else {
menuList = [RENAME, DELETE, COPY, MOVE, OPEN_VIA_CLIENT];
}
return menuList;
}
onShowMenu = () => {
this.onFreezedItem();
}
onHideMenu = () => {
this.onUnFreezedItem();
}
render() {
return (
<div className={`tree-view tree ${this.state.isTreeViewDropTipShow ? 'tree-view-drop' : ''}`} onDrop={this.onNodeDrop} onDragEnter={this.onNodeDragEnter} onDragLeave={this.onNodeDragLeave}>
<div
className={`tree-view tree ${this.state.isTreeViewDropTipShow ? 'tree-view-drop' : ''}`}
onDrop={this.onNodeDrop}
onDragEnter={this.onNodeDragEnter}
onDragLeave={this.onNodeDragLeave}
onMouseDown={this.onMouseDown}
onContextMenu={this.onContextMenu}
>
<TreeNodeView
repoPermission={this.props.repoPermission}
node={this.props.treeData.root}
@@ -197,25 +218,18 @@ class TreeView extends React.Component {
onNodeDragStart={this.onNodeDragStart}
onFreezedItem={this.onFreezedItem}
onUnFreezedItem={this.onUnFreezedItem}
onNodeChanged={this.onNodeChanged}
registerHandlers={this.registerHandlers}
unregisterHandlers={this.unregisterHandlers}
onNodeDragMove={this.onNodeDragMove}
onNodeDrop={this.onNodeDrop}
onNodeDragEnter={this.onNodeDragEnter}
onNodeDragLeave={this.onNodeDragLeave}
appMenuType={this.props.appMenuType}
handleContextClick={this.handleContextClick}
/>
<ContextMenu
id={'tree-node-contextmenu'}
onMenuItemClick={this.onMenuItemClick}
onHideMenu={this.onHideMenu}
onShowMenu={this.onShowMenu}
/>
{this.state.isRightMenuShow && this.props.appMenuType === 'tree_contextmenu' && (
<TreeViewContextMenu
node={this.state.fileData}
onMenuItemClick={this.onMenuItemClick}
mousePosition={this.state.mousePosition}
closeRightMenu={this.closeRightMenu}
registerHandlers={this.registerHandlers}
unregisterHandlers={this.unregisterHandlers}
/>
)}
</div>
);
}