1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-07 09:51:26 +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

@@ -0,0 +1,33 @@
import assign from 'object-assign';
import { store } from './helpers';
export const MENU_SHOW = 'REACT_CONTEXTMENU_SHOW';
export const MENU_HIDE = 'REACT_CONTEXTMENU_HIDE';
export function dispatchGlobalEvent(eventName, opts, target = window) {
// Compatibale with IE
// @see http://stackoverflow.com/questions/26596123/internet-explorer-9-10-11-event-constructor-doesnt-work
let event;
if (typeof window.CustomEvent === 'function') {
event = new window.CustomEvent(eventName, { detail: opts });
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(eventName, false, true, opts);
}
if (target) {
target.dispatchEvent(event);
assign(store, opts);
}
}
export function showMenu(opts = {}, target) {
dispatchGlobalEvent(MENU_SHOW, assign({}, opts, { type: MENU_SHOW }), target);
}
export function hideMenu(opts = {}, target) {
dispatchGlobalEvent(MENU_HIDE, assign({}, opts, { type: MENU_HIDE }), target);
}

View File

@@ -0,0 +1,227 @@
import React from 'react';
import PropTypes from 'prop-types';
import listener from './globalEventListener';
import { hideMenu } from './actions';
import { callIfExists } from './helpers';
const propTypes = {
id: PropTypes.string.isRequired,
rtl: PropTypes.bool,
onMenuItemClick: PropTypes.func.isRequired,
onShowMenu: PropTypes.func,
onHideMenu: PropTypes.func,
};
class ContextMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0,
isVisible: false,
currentObject: null,
menuList: [],
};
}
componentDidMount() {
this.listenId = listener.register(this.handleShow, this.handleHide);
}
componentDidUpdate () {
if (this.state.isVisible) {
const wrapper = window.requestAnimationFrame || setTimeout;
wrapper(() => {
const { x, y } = this.state;
const { top, left } = this.props.rtl ? this.getRTLMenuPosition(x, y) : this.getMenuPosition(x, y);
wrapper(() => {
if (!this.menu) return;
this.menu.style.top = `${top}px`;
this.menu.style.left = `${left}px`;
this.menu.style.opacity = 1;
this.menu.style.pointerEvents = 'auto';
});
});
} else {
if (!this.menu) return;
this.menu.style.opacity = 0;
this.menu.style.pointerEvents = 'none';
}
}
componentWillUnmount() {
if (this.listenId) {
listener.unregister(this.listenId);
}
this.unregisterHandlers();
}
registerHandlers = () => {
document.addEventListener('mousedown', this.handleOutsideClick);
document.addEventListener('touchstart', this.handleOutsideClick);
document.addEventListener('scroll', this.handleHide);
document.addEventListener('contextmenu', this.handleHide);
document.addEventListener('keydown', this.handleKeyNavigation);
window.addEventListener('resize', this.handleHide);
}
unregisterHandlers = () => {
document.removeEventListener('mousedown', this.handleOutsideClick);
document.removeEventListener('touchstart', this.handleOutsideClick);
document.removeEventListener('scroll', this.handleHide);
document.removeEventListener('contextmenu', this.handleHide);
document.removeEventListener('keydown', this.handleKeyNavigation);
window.removeEventListener('resize', this.handleHide);
}
handleShow = (e) => {
if (e.detail.id !== this.props.id || this.state.isVisible) return;
const { x, y } = e.detail.position;
const { currentObject, menuList} = e.detail;
this.setState({ isVisible: true, x, y, currentObject, menuList });
this.registerHandlers();
callIfExists(this.props.onShowMenu, e);
}
handleHide = (e) => {
if (this.state.isVisible && (!e.detail || !e.detail.id || e.detail.id === this.props.id)) {
this.unregisterHandlers();
this.setState({ isVisible: false});
callIfExists(this.props.onHideMenu, e);
}
}
handleOutsideClick = (e) => {
if (!this.menu.contains(e.target)) hideMenu();
}
handleMouseLeave = (event) => {
event.preventDefault();
if (this.props.hideOnLeave) hideMenu();
}
handleContextMenu = (e) => {
this.handleHide(e);
}
handleKeyNavigation = (e) => {
if (this.state.isVisible === false) {
return;
}
e.preventDefault();
this.hideMenu(e);
}
hideMenu = (e) => {
if (e.keyCode === 27 || e.keyCode === 13) { // ECS or enter
hideMenu();
}
}
getMenuPosition = (x = 0, y = 0) => {
let menuStyles = {
top: y,
left: x
};
if (!this.menu) return menuStyles;
const { innerWidth, innerHeight } = window;
const rect = this.menu.getBoundingClientRect();
if (y + rect.height > innerHeight) {
menuStyles.top -= rect.height;
}
if (x + rect.width > innerWidth) {
menuStyles.left -= rect.width;
}
if (menuStyles.top < 0) {
menuStyles.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
}
if (menuStyles.left < 0) {
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
}
return menuStyles;
}
getRTLMenuPosition = (x = 0, y = 0) => {
let menuStyles = {
top: y,
left: x
};
if (!this.menu) return menuStyles;
const { innerWidth, innerHeight } = window;
const rect = this.menu.getBoundingClientRect();
// Try to position the menu on the left side of the cursor
menuStyles.left = x - rect.width;
if (y + rect.height > innerHeight) {
menuStyles.top -= rect.height;
}
if (menuStyles.left < 0) {
menuStyles.left += rect.width;
}
if (menuStyles.top < 0) {
menuStyles.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
}
if (menuStyles.left + rect.width > innerWidth) {
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
}
return menuStyles;
}
onMenuItemClick = (event) => {
event.stopPropagation();
let operation = event.target.dataset.operation;
let currentObject = this.state.currentObject;
this.props.onMenuItemClick(operation, currentObject, event);
}
render() {
const inlineStyle = { position: 'fixed', opacity: 0, pointerEvents: 'none', display: 'block' };
return (
<div role="menu" className="seafile-contextmenu dropdown-menu" style={inlineStyle} ref={menu => { this.menu = menu; }}>
{this.state.menuList.map((menuItem, index) => {
if (menuItem === 'Divider') {
return <div key={index} className="seafile-divider dropdown-divider"></div>
} else {
return (
<button
key={index}
className="seafile-contextmenu-item dropdown-item"
data-operation={menuItem.key}
onClick={this.onMenuItemClick}
>
{menuItem.value}
</button>
);
}
})}
</div>
);
}
}
ContextMenu.propTypes = propTypes;
export default ContextMenu;

View File

@@ -0,0 +1,45 @@
import { MENU_SHOW, MENU_HIDE } from './actions';
import { uniqueId, hasOwnProp, canUseDOM } from './helpers';
class GlobalEventListener {
constructor() {
this.callbacks = {};
if (canUseDOM) {
window.addEventListener(MENU_SHOW, this.handleShowEvent);
window.addEventListener(MENU_HIDE, this.handleHideEvent);
}
}
handleShowEvent = (event) => {
for (const id in this.callbacks) {
if (hasOwnProp(this.callbacks, id)) this.callbacks[id].show(event);
}
}
handleHideEvent = (event) => {
for (const id in this.callbacks) {
if (hasOwnProp(this.callbacks, id)) this.callbacks[id].hide(event);
}
}
register = (showCallback, hideCallback) => {
const id = uniqueId();
this.callbacks[id] = {
show: showCallback,
hide: hideCallback
};
return id;
}
unregister = (id) => {
if (id && this.callbacks[id]) {
delete this.callbacks[id];
}
}
}
export default new GlobalEventListener();

View File

@@ -0,0 +1,17 @@
export function callIfExists(func, ...args) {
return (typeof func === 'function') && func(...args);
}
export function hasOwnProp(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
export function uniqueId() {
return Math.random().toString(36).substring(7);
}
export const store = {};
export const canUseDOM = Boolean(
typeof window !== 'undefined' && window.document && window.document.createElement
);