1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-09 19:01:42 +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
);

View File

@@ -10,7 +10,7 @@ import Move from '../../components/dialog/move-dirent-dialog';
import CreateFolder from '../../components/dialog/create-folder-dialog';
import CreateFile from '../../components/dialog/create-file-dialog';
import ImageDialog from '../../components/dialog/image-dialog';
import { siteRoot, gettext, thumbnailSizeForOriginal } from '../../utils/constants';
import { siteRoot, thumbnailSizeForOriginal } from '../../utils/constants';
import { Utils } from '../../utils/utils';
const propTypes = {
@@ -30,8 +30,7 @@ const propTypes = {
navRate: PropTypes.number,
inResizing: PropTypes.bool.isRequired,
currentRepoInfo: PropTypes.object.isRequired,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
selectedDirentList: PropTypes.array.isRequired,
};
class DirColumnNav extends React.Component {
@@ -270,8 +269,7 @@ class DirColumnNav extends React.Component {
onUnFreezedItem={this.onUnFreezedItem}
onItemMove={this.props.onItemMove}
currentRepoInfo={this.props.currentRepoInfo}
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
appMenuType={this.props.appMenuType}
selectedDirentList={this.props.selectedDirentList}
/>)
}
</div>

View File

@@ -48,9 +48,11 @@ const propTypes = {
// list
isDirentListLoading: PropTypes.bool.isRequired,
direntList: PropTypes.array.isRequired,
showShareBtn: PropTypes.bool.isRequired,
sortBy: PropTypes.string.isRequired,
sortOrder: PropTypes.string.isRequired,
sortItems: PropTypes.func.isRequired,
onAddFolder: PropTypes.func.isRequired,
onAddFile: PropTypes.func.isRequired,
updateDirent: PropTypes.func.isRequired,
onItemClick: PropTypes.func.isRequired,
@@ -63,8 +65,10 @@ const propTypes = {
onDirentClick: PropTypes.func.isRequired,
isAllItemSelected: PropTypes.bool.isRequired,
onAllItemSelected: PropTypes.func.isRequired,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
selectedDirentList: PropTypes.array.isRequired,
onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired,
onItemsDelete: PropTypes.func.isRequired,
};
class DirColumnView extends React.Component {
@@ -167,8 +171,7 @@ class DirColumnView extends React.Component {
currentRepoInfo={this.props.currentRepoInfo}
onItemMove={this.props.onItemMove}
onItemCopy={this.props.onItemCopy}
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
appMenuType={this.props.appMenuType}
selectedDirentList={this.props.selectedDirentList}
/>
<div className="dir-content-resize" onMouseDown={this.onResizeMouseDown}></div>
<div className="dir-content-main" style={{userSelect: select, flex: mainFlex}}>
@@ -202,9 +205,11 @@ class DirColumnView extends React.Component {
updateUsedRepoTags={this.props.updateUsedRepoTags}
isDirentListLoading={this.props.isDirentListLoading}
direntList={this.props.direntList}
showShareBtn={this.props.showShareBtn}
sortBy={this.props.sortBy}
sortOrder={this.props.sortOrder}
sortItems={this.props.sortItems}
onAddFolder={this.props.onAddFolder}
onAddFile={this.props.onAddFile}
onItemClick={this.props.onItemClick}
onItemSelected={this.props.onItemSelected}
@@ -217,9 +222,10 @@ class DirColumnView extends React.Component {
updateDirent={this.props.updateDirent}
isAllItemSelected={this.props.isAllItemSelected}
onAllItemSelected={this.props.onAllItemSelected}
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
appMenuType={this.props.appMenuType}
onAddFolder={this.props.onAddFolder}
selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove}
onItemsCopy={this.props.onItemsCopy}
onItemsDelete={this.props.onItemsDelete}
/>
)}
</div>

View File

@@ -1,11 +1,8 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import DirentNoneView from '../../components/dirent-list-view/dirent-none-view';
import RepoInfoBar from '../../components/repo-info-bar';
import ModalPortal from '../modal-portal';
import DirentListView from '../../components/dirent-list-view/dirent-list-view';
import CreateFile from '../../components/dialog/create-file-dialog';
import CreateFolder from '../../components/dialog/create-folder-dialog';
import DirentListMenu from '../dirent-list-view/dirent-right-menu';
const propTypes = {
path: PropTypes.string.isRequired,
@@ -20,9 +17,11 @@ const propTypes = {
updateUsedRepoTags: PropTypes.func.isRequired,
isDirentListLoading: PropTypes.bool.isRequired,
direntList: PropTypes.array.isRequired,
showShareBtn: PropTypes.bool.isRequired,
sortBy: PropTypes.string.isRequired,
sortOrder: PropTypes.string.isRequired,
sortItems: PropTypes.func.isRequired,
onAddFolder: PropTypes.func.isRequired,
onAddFile: PropTypes.func.isRequired,
onItemClick: PropTypes.func.isRequired,
onItemSelected: PropTypes.func.isRequired,
@@ -34,96 +33,26 @@ const propTypes = {
updateDirent: PropTypes.func.isRequired,
isAllItemSelected: PropTypes.bool.isRequired,
onAllItemSelected: PropTypes.func.isRequired,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
onAddFolder: PropTypes.func,
selectedDirentList: PropTypes.array.isRequired,
onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired,
onItemsDelete: PropTypes.func.isRequired,
};
class DirListView extends React.Component {
constructor(props) {
super(props);
this.state = {
isCreateFileDialogShow: false,
fileType: '',
isContainerContextmenuShow: false,
isCreateFolderDialogShow: false,
itemMousePosition: {clientX: '', clientY: ''},
}
}
componentDidUpdate() {
this.registerTableContainerContextmenuHandler();
}
componentWillUnmount() {
this.unregisterTableContainerContextmenuHandler();
}
registerTableContainerContextmenuHandler = () => {
let tableContainer = document.querySelector('.table-container');
if (tableContainer) {
tableContainer.addEventListener('contextmenu', this.tableContainerContextmenuHandler);
}
}
unregisterTableContainerContextmenuHandler = () => {
let tableContainer = document.querySelector('.table-container');
tableContainer.removeEventListener('contextmenu', this.tableContainerContextmenuHandler);
}
tableContainerContextmenuHandler = (e) => {
e.preventDefault();
this.props.switchAnotherMenuToShow('list_view_contextmenu');
this.setState({isContainerContextmenuShow: false}, () => {
this.setState({
isContainerContextmenuShow: true,
itemMousePosition: {clientX: e.clientX, clientY: e.clientY}
})
});
}
closeTableContainerRightMenu = () => {
this.setState({
isContainerContextmenuShow: false,
});
this.props.switchAnotherMenuToShow('item_op_menu');
}
onCreateFolderToggle = () => {
this.setState({
isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow,
});
}
onCreateFileToggle = () => {
this.setState({
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
fileType: ''
});
}
onAddFile = (filePath, isDraft) => {
this.setState({isCreateFileDialogShow: false});
this.props.onAddFile(filePath, isDraft);
}
onAddFolder = (dirPath) => {
this.setState({isCreateFolderDialogShow: false});
this.props.onAddFolder(dirPath);
}
checkDuplicatedName = (newName) => {
let direntList = this.props.direntList;
let isDuplicated = direntList.some(object => {
return object.name === newName;
});
return isDuplicated;
}
render() {
if (this.props.path === '/' && this.props.direntList.length === 0) {
return (
<DirentNoneView
path={this.props.path}
isDirentListLoading={this.props.isDirentListLoading}
onAddFile={this.props.onAddFile}
/>
);
}
return (
<Fragment>
{this.props.isRepoInfoBarShow && (
@@ -136,64 +65,36 @@ class DirListView extends React.Component {
updateUsedRepoTags={this.props.updateUsedRepoTags}
/>
)}
<div className="table-container">
<DirentListView
path={this.props.path}
currentRepoInfo={this.props.currentRepoInfo}
repoID={this.props.repoID}
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
enableDirPrivateShare={this.props.enableDirPrivateShare}
direntList={this.props.direntList}
sortBy={this.props.sortBy}
sortOrder={this.props.sortOrder}
sortItems={this.props.sortItems}
onAddFile={this.props.onAddFile}
onItemClick={this.props.onItemClick}
onItemSelected={this.props.onItemSelected}
onItemDelete={this.props.onItemDelete}
onItemRename={this.props.onItemRename}
onItemMove={this.props.onItemMove}
onItemCopy={this.props.onItemCopy}
onDirentClick={this.props.onDirentClick}
isDirentListLoading={this.props.isDirentListLoading}
updateDirent={this.props.updateDirent}
isAllItemSelected={this.props.isAllItemSelected}
onAllItemSelected={this.props.onAllItemSelected}
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
appMenuType={this.props.appMenuType}
/>
</div>
{this.state.isContainerContextmenuShow && this.props.appMenuType === 'list_view_contextmenu' && (
<DirentListMenu
mousePosition={this.state.itemMousePosition}
itemUnregisterHandlers={this.unregisterTableContainerContextmenuHandler}
itemRegisterHandlers={this.registerTableContainerContextmenuHandler}
closeRightMenu={this.closeTableContainerRightMenu}
onCreateFolderToggle={this.onCreateFolderToggle}
onCreateFileToggle={this.onCreateFileToggle}
/>
)}
{this.state.isCreateFolderDialogShow && (
<ModalPortal>
<CreateFolder
parentPath={this.props.path}
onAddFolder={this.onAddFolder}
checkDuplicatedName={this.checkDuplicatedName}
addFolderCancel={this.onCreateFolderToggle}
/>
</ModalPortal>
)}
{this.state.isCreateFileDialogShow && (
<ModalPortal>
<CreateFile
parentPath={this.props.path}
fileType={this.state.fileType}
onAddFile={this.onAddFile}
checkDuplicatedName={this.checkDuplicatedName}
addFileCancel={this.onCreateFileToggle}
/>
</ModalPortal>
)}
<DirentListView
path={this.props.path}
currentRepoInfo={this.props.currentRepoInfo}
repoID={this.props.repoID}
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
enableDirPrivateShare={this.props.enableDirPrivateShare}
direntList={this.props.direntList}
showShareBtn={this.props.showShareBtn}
sortBy={this.props.sortBy}
sortOrder={this.props.sortOrder}
sortItems={this.props.sortItems}
onAddFile={this.props.onAddFile}
onItemClick={this.props.onItemClick}
onItemSelected={this.props.onItemSelected}
onItemDelete={this.props.onItemDelete}
onItemRename={this.props.onItemRename}
onItemMove={this.props.onItemMove}
onItemCopy={this.props.onItemCopy}
onDirentClick={this.props.onDirentClick}
isDirentListLoading={this.props.isDirentListLoading}
updateDirent={this.props.updateDirent}
isAllItemSelected={this.props.isAllItemSelected}
onAllItemSelected={this.props.onAllItemSelected}
selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove}
onItemsCopy={this.props.onItemsCopy}
onItemsDelete={this.props.onItemsDelete}
onAddFile={this.props.onAddFile}
onAddFolder={this.props.onAddFolder}
/>
</Fragment>
);
}

View File

@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import MD5 from 'MD5';
import { UncontrolledTooltip } from 'reactstrap';
import { gettext, siteRoot, mediaUrl, canGenerateShareLink, canGenerateUploadLink } from '../../utils/constants';
import { gettext, siteRoot, mediaUrl } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
@@ -13,7 +13,6 @@ import ZipDownloadDialog from '../dialog/zip-download-dialog';
import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
import ShareDialog from '../dialog/share-dialog';
import DirentRightMenu from './dirent-right-menu';
import toaster from '../toast';
import '../../css/dirent-list-item.css';
@@ -22,6 +21,7 @@ const propTypes = {
path: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
isItemFreezed: PropTypes.bool.isRequired,
showShareBtn: PropTypes.bool.isRequired,
dirent: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
onFreezedItem: PropTypes.func.isRequired,
@@ -40,8 +40,8 @@ const propTypes = {
isAdmin: PropTypes.bool.isRequired,
repoEncrypted: PropTypes.bool.isRequired,
isGroupOwnedRepo: PropTypes.bool.isRequired,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
onItemMouseDown: PropTypes.func.isRequired,
onItemContextMenu: PropTypes.func.isRequired,
};
class DirentListItem extends React.Component {
@@ -60,70 +60,19 @@ class DirentListItem extends React.Component {
isShowTagTooltip: false,
isDragTipShow: false,
isDropTipshow: false,
enterItemData: '',
enterItemIndex: -1,
contextmenuItemData: {},
contextmenuItemIndex: -1,
isItemContextMenuShow: false,
};
this.zipToken = null;
}
componentWillReceiveProps(nextProp) {
if (nextProp.appMenuType === 'list_view_contextmenu' || nextProp.appMenuType === 'item_contextmenu') {
componentWillReceiveProps(nextProps) {
if (!nextProps.isItemFreezed) {
this.setState({
highlight: false,
isOperationShow: false,
})
});
}
}
componentDidUpdate() {
this.itemRegisterHandlers();
}
componentWillUnmount() {
this.itemUnregisterHandlers();
}
itemUnregisterHandlers = () => {
let itemTbody = document.querySelector('tbody');
itemTbody.removeEventListener('contextmenu', this.itemRightContextMenu);
}
itemRegisterHandlers = () => {
let itemTbody = document.querySelector('tbody');
if (itemTbody) {
itemTbody.addEventListener('contextmenu', this.itemRightContextMenu);
}
}
itemRightContextMenu = (e) =>{
e.preventDefault();
e.stopPropagation();
this.props.switchAnotherMenuToShow('item_contextmenu');
this.setState({
isItemContextMenuShow: false,
itemMousePosition: {clientX: e.clientX, clientY: e.clientY},
contextmenuItemData: this.state.enterItemData,
contextmenuItemIndex: this.state.enterItemIndex,
})
setTimeout(() => {
this.setState({
isItemContextMenuShow: true,
})
},40)
}
closeRightMenu = () => {
this.setState({
isItemContextMenuShow: false,
});
this.onUnfreezedItem();
this.props.switchAnotherMenuToShow('item_op_menu');
}
//UI Interactive
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
@@ -132,11 +81,7 @@ class DirentListItem extends React.Component {
isOperationShow: true,
});
}
this.setState({
isDragTipShow: true,
enterItemData: this.props.dirent,
enterItemIndex: this.props.itemIndex,
});
this.setState({isDragTipShow: true});
}
onMouseOver = () => {
@@ -146,10 +91,7 @@ class DirentListItem extends React.Component {
isOperationShow: true,
});
}
this.setState({
isDragTipShow: true,
enterItemData: this.props.dirent,
enterItemIndex: this.props.itemIndex});
this.setState({isDragTipShow: true});
}
onMouseLeave = () => {
@@ -159,14 +101,17 @@ class DirentListItem extends React.Component {
isOperationShow: false,
});
}
this.setState({
isDragTipShow: false,
enterItemData: '',
enterItemIndex: -1,
});
this.setState({isDragTipShow: false});
}
onUnfreezedItem = () => {
let dirent = this.props.dirent;
// scenes 1: dirent isSelected --> this have Highest level
// scenes 2: dirent contextmenu show
// scenes 3: dirent operation menu show
if (dirent.isSelected) {
return;
}
this.setState({
highlight: false,
isOperationShow: false,
@@ -176,6 +121,7 @@ class DirentListItem extends React.Component {
//buiness handler
onItemSelected = () => {
this.props.onFreezedItem();
this.props.onItemSelected(this.props.dirent);
}
@@ -228,8 +174,17 @@ class DirentListItem extends React.Component {
this.setState({isShareDialogShow: !this.state.isShareDialogShow});
}
onMenuItemClick = (operation) => {
onMenuItemClick = (operation, event) => {
switch(operation) {
case 'Download':
this.onItemDownload(event);
break;
case 'Share':
this.onItemShare(event);
break;
case 'Delete':
this.onItemDelete(event);
break;
case 'Rename':
this.onItemRenameToggle();
break;
@@ -460,6 +415,15 @@ class DirentListItem extends React.Component {
this.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath);
}
onItemMouseDown = (event) => {
this.props.onItemMouseDown(event);
}
onItemContextMenu = (event) => {
let dirent = this.props.dirent;
this.props.onItemContextMenu(event, dirent);
}
render() {
let { path, dirent } = this.props;
let direntPath = Utils.joinPath(path, dirent.name);
@@ -478,20 +442,26 @@ class DirentListItem extends React.Component {
let iconUrl = Utils.getDirentIcon(dirent);
const { repoEncrypted, isRepoOwner, isAdmin } = this.props;
let showShare = false;
if (!repoEncrypted &&
(dirent.permission == 'rw' || dirent.permission == 'r')) {
showShare = dirent.type == 'file' ? canGenerateShareLink :
(canGenerateShareLink || canGenerateUploadLink || (isRepoOwner || isAdmin));
}
let trClass = this.state.highlight ? 'tr-highlight ' : '';
trClass += this.state.isDropTipshow ? 'tr-drop-effect' : '';
return (
<Fragment>
<tr className={trClass} draggable="true" onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} onClick={this.onDirentClick} onDragStart={this.onItemDragStart} onDragEnter={this.onItemDragEnter} onDragOver={this.onItemDragOver} onDragLeave={this.onItemDragLeave} onDrop={this.onItemDragDrop}>
<tr
className={trClass}
draggable="true"
onMouseEnter={this.onMouseEnter}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
onClick={this.onDirentClick}
onDragStart={this.onItemDragStart}
onDragEnter={this.onItemDragEnter}
onDragOver={this.onItemDragOver}
onDragLeave={this.onItemDragLeave}
onDrop={this.onItemDragDrop}
onMouseDown={this.onItemMouseDown}
onContextMenu={this.onItemContextMenu}
>
<td className={`pl10 ${this.state.isDragTipShow ? 'tr-drag-effect' : ''}`}>
<input type="checkbox" className="vam" onChange={this.onItemSelected} checked={dirent.isSelected}/>
</td>
@@ -539,7 +509,7 @@ class DirentListItem extends React.Component {
<li className="operation-group-item">
<i className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.onItemDownload}></i>
</li>
{showShare &&
{this.props.showShareBtn &&
<li className="operation-group-item">
<i className="op-icon sf2-icon-share" title={gettext('Share')} onClick={this.onItemShare}></i>
</li>
@@ -555,29 +525,11 @@ class DirentListItem extends React.Component {
isRepoOwner={this.props.isRepoOwner}
onFreezedItem={this.props.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
appMenuType={this.props.appMenuType}
/>
</li>
</ul>
</div>
}
{this.state.isItemContextMenuShow && this.state.contextmenuItemIndex === this.props.itemIndex && this.props.appMenuType === 'item_contextmenu' &&
<DirentRightMenu
dirent={this.state.contextmenuItemData}
mousePosition={this.state.itemMousePosition}
isRepoOwner={this.props.isRepoOwner}
currentRepoInfo={this.props.currentRepoInfo}
onMenuItemClick={this.onMenuItemClick}
onItemDownload={this.onItemDownload}
onItemShare={this.onItemShare}
onItemDelete={this.onItemDelete}
itemRegisterHandlers={this.itemRegisterHandlers}
itemUnregisterHandlers={this.itemUnregisterHandlers}
closeRightMenu={this.closeRightMenu}
onUnfreezedItem={this.onUnfreezedItem}
showShare={showShare}
/>
}
</td>
<td className="file-size">{dirent.size && dirent.size}</td>
<td className="last-update">{dirent.mtime_relative}</td>

View File

@@ -1,15 +1,22 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { siteRoot, gettext, thumbnailSizeForOriginal, username } from '../../utils/constants';
import { siteRoot, gettext, thumbnailSizeForOriginal, username, isPro, enableFileComment, fileAuditEnabled, folderPermEnabled } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import TextTranslation from '../../utils/text-translation';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import Loading from '../loading';
import DirentListItem from './dirent-list-item';
import ModalPortal from '../modal-portal';
import CreateFile from '../../components/dialog/create-file-dialog';
import ImageDialog from '../../components/dialog/image-dialog';
import '../../css/tip-for-new-md.css';
import toaster from '../toast';
import ModalPortal from '../modal-portal';
import CreateFile from '../dialog/create-file-dialog';
import CreateFolder from '../dialog/create-folder-dialog';
import ImageDialog from '../dialog/image-dialog';
import ZipDownloadDialog from '../dialog/zip-download-dialog';
import MoveDirentDialog from '../dialog/move-dirent-dialog';
import CopyDirentDialog from '../dialog/copy-dirent-dialog';
import DirentListItem from './dirent-list-item';
import ContextMenu from '../context-menu/context-menu';
import { hideMenu, showMenu } from '../context-menu/actions';
const propTypes = {
path: PropTypes.string.isRequired,
@@ -18,10 +25,12 @@ const propTypes = {
isAllItemSelected: PropTypes.bool.isRequired,
isDirentListLoading: PropTypes.bool.isRequired,
direntList: PropTypes.array.isRequired,
showShareBtn: PropTypes.bool.isRequired,
sortBy: PropTypes.string.isRequired,
sortOrder: PropTypes.string.isRequired,
sortItems: PropTypes.func.isRequired,
onAddFile: PropTypes.func.isRequired,
onAddFolder: PropTypes.func.isRequired,
onItemDelete: PropTypes.func.isRequired,
onAllItemSelected: PropTypes.func.isRequired,
onItemSelected: PropTypes.func.isRequired,
@@ -31,8 +40,10 @@ const propTypes = {
onItemCopy: PropTypes.func.isRequired,
onDirentClick: PropTypes.func.isRequired,
updateDirent: PropTypes.func.isRequired,
switchAnotherMenuToShow: PropTypes.func,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
selectedDirentList: PropTypes.array.isRequired,
onItemsMove: PropTypes.func.isRequired,
onItemsCopy: PropTypes.func.isRequired,
onItemsDelete: PropTypes.func.isRequired,
};
class DirentListView extends React.Component {
@@ -41,35 +52,28 @@ class DirentListView extends React.Component {
super(props);
this.state = {
isItemFreezed: false,
isImagePopupOpen: false,
imageItems: [],
imageIndex: 0,
isCreateFileDialogShow: false,
fileType: '',
isCreateFileDialogShow: false,
isCreateFolderDialogShow: false,
isMoveDialogShow: false,
isCopyDialogShow: false,
isProgressDialogShow: false,
progress: 0,
isMutipleOperation: true,
};
this.isRepoOwner = props.currentRepoInfo.owner_email === username;
this.isAdmin = props.currentRepoInfo.is_admin;
this.repoEncrypted = props.currentRepoInfo.encrypted;
}
componentWillReceiveProps(nextProps) {
if (nextProps.appMenuType === 'item_op_menu' || nextProps.appMenuType === 'tree_contextmenu') {
this.setState({isItemFreezed: false});
} else {
this.setState({isItemFreezed: true});
}
}
this.clickedDirent = null;
this.direntItems = [];
this.currentItemRef = null;
componentDidUpdate() {
let thead = document.querySelector('thead');
if (thead) {
thead.addEventListener('contextmenu', (e) => {
e.stopPropagation();
});
}
this.zipToken = null;
}
onFreezedItem = () => {
@@ -97,23 +101,8 @@ class DirentListView extends React.Component {
this.onFreezedItem();
}
onCreateFileToggle = () => {
this.setState({
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
fileType: ''
});
}
onCreateNewFile = (suffix) => {
this.setState({
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
fileType: suffix
});
}
onAddFile = (filePath, isDraft) => {
this.setState({isCreateFileDialogShow: false});
this.props.onAddFile(filePath, isDraft);
onItemDetails = (dirent) => {
this.props.onItemDetails(dirent);
}
sortByName = (e) => {
@@ -190,6 +179,24 @@ class DirentListView extends React.Component {
});
}
onCreateFileToggle = () => {
this.setState({isCreateFileDialogShow: !this.state.isCreateFileDialogShow});
}
onCreateFolderToggle = () => {
this.setState({isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow});
}
onAddFile = (filePath, isDraft) => {
this.setState({isCreateFileDialogShow: false});
this.props.onAddFile(filePath, isDraft);
}
onAddFolder = (dirPath) => {
this.setState({isCreateFolderDialogShow: false});
this.props.onAddFolder(dirPath);
}
checkDuplicatedName = (newName) => {
let direntList = this.props.direntList;
let isDuplicated = direntList.some(object => {
@@ -198,6 +205,289 @@ class DirentListView extends React.Component {
return isDuplicated;
}
onMoveToggle = () => {
this.setState({isMoveDialogShow: !this.state.isMoveDialogShow});
}
onCopyToggle = () => {
this.setState({isCopyDialogShow: !this.state.isCopyDialogShow});
}
onItemsDownload = () => {
let { path, repoID, selectedDirentList } = this.props;
if (selectedDirentList.length) {
if (selectedDirentList.length === 1 && !selectedDirentList[0].isDir()) {
let direntPath = Utils.joinPath(path, selectedDirentList[0].name);
let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: direntPath});
location.href= url;
return;
}
let selectedDirentNames = selectedDirentList.map(dirent => {
return dirent.name;
});
this.setState({isProgressDialogShow: true, progress: 0});
seafileAPI.zipDownload(repoID, path, selectedDirentNames).then(res => {
this.zipToken = res.data['zip_token'];
this.addDownloadAnimation();
this.interval = setInterval(this.addDownloadAnimation, 1000);
});
}
}
addDownloadAnimation = () => {
let _this = this;
let token = this.zipToken;
seafileAPI.queryZipProgress(token).then(res => {
let data = res.data;
let progress = data.total === 0 ? 100 : (data.zipped / data.total * 100).toFixed(0);
this.setState({progress: parseInt(progress)});
if (data['total'] === data['zipped']) {
this.setState({
progress: 100
});
clearInterval(this.interval);
location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token});
setTimeout(function() {
_this.setState({isProgressDialogShow: false});
}, 500);
}
});
}
onCancelDownload = () => {
seafileAPI.cancelZipTask(this.zipToken).then(() => {
this.setState({isProgressDialogShow: false});
});
}
// common contextmenu handle
onMouseDown = (event) => {
event.stopPropagation();
if (event.button === 2) {
return;
}
}
handleContextClick = (event, id, menuList, currentObject = null) => {
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 showMenuConfig = {
id: id,
position: { x, y },
target: event.target,
currentObject: currentObject,
menuList: menuList,
};
showMenu(showMenuConfig);
}
// table-container contextmenu handle
onContainerMouseDown = (event) => {
this.onMouseDown(event);
}
onContainerContextMenu = (event) => {
if (this.props.selectedDirentList.length === 0) {
let id = "dirent-container-menu"
let menuList = [TextTranslation.NEW_FOLDER, TextTranslation.NEW_FILE];
this.handleContextClick(event, id, menuList);
} else {
if (this.props.selectedDirentList.length === 1) {
let dirent = this.props.selectedDirentList[0];
let id = 'dirent-item-menu';
let menuList = this.getDirentItemMenuList(dirent, true);
this.handleContextClick(event, id, menuList, dirent);
} else {
let id = 'dirents-menu';
let menuList = [TextTranslation.MOVE, TextTranslation.COPY, TextTranslation.DOWNLOAD, TextTranslation.DELETE];
this.handleContextClick(event, id, menuList);
}
}
}
onContainerMenuItemClick = (operation) => {
switch(operation) {
case 'New Folder':
this.onCreateFolderToggle();
break;
case 'New File':
this.onCreateFileToggle();
break;
default:
break;
}
hideMenu();
}
onDirentsMenuItemClick = (operation) => {
switch(operation) {
case 'Move':
this.onMoveToggle();
break;
case 'Copy':
this.onCopyToggle();
break;
case 'Download':
this.onItemsDownload();
break;
case 'Delete':
this.props.onItemsDelete();
break;
default:
break;
}
hideMenu();
}
// table-thread contextmenu handle -- Shield event
onThreadMouseDown = (event) => {
this.onMouseDown(event);
}
onThreadContextMenu = (event) => {
event.stopPropagation();
}
// table-dirent-item contextmenu handle
onItemMouseDown = (event) => {
this.onMouseDown(event);
}
onItemContextMenu = (event, dirent) => {
if (this.props.selectedDirentList.length === 0) {
let id = 'dirent-item-menu';
let menuList = this.getDirentItemMenuList(dirent, true);
this.handleContextClick(event, id, menuList, dirent);
}
}
setDirentItemRef = (index) => item => {
this.direntItems[index] = item;
}
onMenuItemClick = (operation, currentObject, event) => {
let index = this.getDirentIndex(currentObject);
this.direntItems[index].onMenuItemClick(operation, event);
hideMenu();
}
onShowMenu = (e) => {
this.onFreezedItem();
}
onHideMenu = (e) => {
if (this.props.selectedDirentList.length === 0) {
this.onUnfreezedItem();
}
}
// contextmenu utils
getDirentIndex = (dirent) => {
let direntList = this.props.direntList;
let index = 0;
for (let i = 0; i < direntList.length; i++) {
if (direntList[i].name === dirent.name) {
index = i;
break;
}
}
return index;
}
getDirentItemMenuList = (dirent, isContextmenu) => {
let isRepoOwner = this.isRepoOwner;
let currentRepoInfo = this.props.currentRepoInfo;
let can_set_folder_perm = folderPermEnabled && ((isRepoOwner && currentRepoInfo.has_been_shared_out) || currentRepoInfo.is_admin);
let type = dirent.type;
let permission = dirent.permission;
let menuList = [];
let contextmenuList = [];
if (isContextmenu) {
let { SHARE, DOWNLOAD, DELETE } = TextTranslation;
contextmenuList = this.props.showShareBtn ? [SHARE, DOWNLOAD, DELETE, 'Divider'] : [DOWNLOAD, DELETE, 'Divider'];
}
let { RENAME, MOVE, COPY, PERMISSION, DETAILS, OPEN_VIA_CLIENT, LOCK, UNLOCK, COMMENT, HISTORY, ACCESS_LOG } = TextTranslation;
if (type === 'dir' && permission === 'rw') {
if (can_set_folder_perm) {
menuList = [...contextmenuList, RENAME, MOVE, COPY, 'Divider', PERMISSION, DETAILS, 'Divider', OPEN_VIA_CLIENT];
} else {
menuList = [...contextmenuList, RENAME, MOVE, COPY, 'Divider', DETAILS, 'Divider', OPEN_VIA_CLIENT];
}
return menuList;
}
if (type === 'dir' && permission === 'r') {
menuList = currentRepoInfo.encrypted ? [...contextmenuList, COPY, DETAILS] : [DETAILS];
return menuList;
}
if (type === 'file' && permission === 'rw') {
menuList = [...contextmenuList];
if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) {
menuList.push(RENAME);
menuList.push(MOVE);
}
menuList.push(COPY);
if (isPro) {
if (dirent.is_locked) {
if (dirent.locked_by_me || (dirent.lock_owner === 'OnlineOffice' && permission === 'rw')) {
menuList.push(UNLOCK);
}
} else {
menuList.push(LOCK);
}
}
menuList.push('Divider');
if (enableFileComment) {
menuList.push(COMMENT);
}
menuList.push(HISTORY);
if (fileAuditEnabled) {
menuList.push(ACCESS_LOG);
}
menuList.push(DETAILS);
menuList.push('Divider');
menuList.push(OPEN_VIA_CLIENT);
return menuList;
}
if (type === 'file' && permission === 'r') {
menuList = [...contextmenuList];
if (!currentRepoInfo.encrypted) {
menuList.push(COPY);
}
if (enableFileComment) {
menuList.push(COMMENT);
}
menuList.push(HISTORY);
menuList.push(DETAILS);
return menuList;
}
}
render() {
const { direntList, sortBy, sortOrder } = this.props;
@@ -205,51 +495,15 @@ class DirentListView extends React.Component {
return (<Loading />);
}
if (this.props.path == '/' && !direntList.length) {
return (
<Fragment>
<div className="tip-for-new-file">
<p className="text-center">{gettext('This folder has no content at this time.')}</p>
<p className="text-center">{gettext('You can create files quickly')}{' +'}</p>
<div className="big-new-file-button-group">
<div className="d-flex justify-content-center">
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.md')}>
{'+ Markdown'}</button>
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.ppt')}>
{'+ PPT'}</button>
</div>
<div className="d-flex justify-content-center">
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.doc')}>
{'+ Word'}</button>
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.xls')}>
{'+ Excel'}</button>
</div>
</div>
</div>
{this.state.isCreateFileDialogShow && (
<ModalPortal>
<CreateFile
parentPath={this.props.path}
fileType={this.state.fileType}
onAddFile={this.onAddFile}
checkDuplicatedName={this.checkDuplicatedName}
addFileCancel={this.onCreateFileToggle}
/>
</ModalPortal>
)}
</Fragment>
);
}
// sort
const sortByName = sortBy == 'name';
const sortByTime = sortBy == 'time';
const sortIcon = sortOrder == 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
return (
<Fragment>
<div className="table-container" onMouseDown={this.onContainerMouseDown} onContextMenu={this.onContainerContextMenu}>
<table>
<thead>
<thead onMouseDown={this.onThreadMouseDown} onContextMenu={this.onThreadContextMenu}>
<tr>
<th width="3%" className="pl10">
<input type="checkbox" className="vam" onChange={this.props.onAllItemSelected} checked={this.props.isAllItemSelected}/>
@@ -264,56 +518,116 @@ class DirentListView extends React.Component {
</tr>
</thead>
<tbody>
{
direntList.length !== 0 && direntList.map((dirent, index) => {
return (
<DirentListItem
key={index}
dirent={dirent}
path={this.props.path}
repoID={this.props.repoID}
currentRepoInfo={this.props.currentRepoInfo}
isAdmin={this.isAdmin}
isRepoOwner={this.isRepoOwner}
repoEncrypted={this.repoEncrypted}
enableDirPrivateShare={this.props.enableDirPrivateShare}
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
direntList={this.props.direntList}
onItemClick={this.props.onItemClick}
onItemRenameToggle={this.onItemRenameToggle}
onItemSelected={this.props.onItemSelected}
onItemDelete={this.props.onItemDelete}
onItemRename={this.onItemRename}
onItemMove={this.props.onItemMove}
onItemCopy={this.props.onItemCopy}
updateDirent={this.props.updateDirent}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
onDirentClick={this.props.onDirentClick}
showImagePopup={this.showImagePopup}
switchAnotherMenuToShow={this.props.switchAnotherMenuToShow}
appMenuType={this.props.appMenuType}
itemIndex={index}
/>
);
})
}
{direntList.map((dirent, index) => {
return (
<DirentListItem
ref={this.setDirentItemRef(index)}
key={index}
dirent={dirent}
path={this.props.path}
repoID={this.props.repoID}
currentRepoInfo={this.props.currentRepoInfo}
isAdmin={this.isAdmin}
isRepoOwner={this.isRepoOwner}
repoEncrypted={this.repoEncrypted}
enableDirPrivateShare={this.props.enableDirPrivateShare}
isGroupOwnedRepo={this.props.isGroupOwnedRepo}
showShareBtn={this.props.showShareBtn}
onItemClick={this.props.onItemClick}
onItemRenameToggle={this.onItemRenameToggle}
onItemSelected={this.props.onItemSelected}
onItemDelete={this.props.onItemDelete}
onItemRename={this.onItemRename}
onItemMove={this.props.onItemMove}
onItemCopy={this.props.onItemCopy}
updateDirent={this.props.updateDirent}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
onDirentClick={this.props.onDirentClick}
onItemDetails={this.onItemDetails}
showImagePopup={this.showImagePopup}
onItemMouseDown={this.onItemMouseDown}
onItemContextMenu={this.onItemContextMenu}
/>
);
})}
</tbody>
</table>
{this.state.isImagePopupOpen && (
<ModalPortal>
<ImageDialog
imageItems={this.state.imageItems}
imageIndex={this.state.imageIndex}
closeImagePopup={this.closeImagePopup}
moveToPrevImage={this.moveToPrevImage}
moveToNextImage={this.moveToNextImage}
<Fragment>
<ContextMenu
id={'dirent-container-menu'}
onMenuItemClick={this.onContainerMenuItemClick}
/>
<ContextMenu
id={'dirent-item-menu'}
onMenuItemClick={this.onMenuItemClick}
onShowMenu={this.onShowMenu}
onHideMenu={this.onHideMenu}
/>
<ContextMenu
id={'dirents-menu'}
onMenuItemClick={this.onDirentsMenuItemClick}
/>
{this.state.isImagePopupOpen && (
<ModalPortal>
<ImageDialog
imageItems={this.state.imageItems}
imageIndex={this.state.imageIndex}
closeImagePopup={this.closeImagePopup}
moveToPrevImage={this.moveToPrevImage}
moveToNextImage={this.moveToNextImage}
/>
</ModalPortal>
)}
{this.state.isCreateFolderDialogShow && (
<ModalPortal>
<CreateFolder
parentPath={this.props.path}
onAddFolder={this.onAddFolder}
checkDuplicatedName={this.checkDuplicatedName}
addFolderCancel={this.onCreateFolderToggle}
/>
</ModalPortal>
)}
{this.state.isCreateFileDialogShow && (
<ModalPortal>
<CreateFile
parentPath={this.props.path}
fileType={this.state.fileType}
onAddFile={this.onAddFile}
checkDuplicatedName={this.checkDuplicatedName}
addFileCancel={this.onCreateFileToggle}
/>
</ModalPortal>
)}
{this.state.isMoveDialogShow &&
<MoveDirentDialog
path={this.props.path}
repoID={this.props.repoID}
repoEncrypted={this.props.currentRepoInfo.encrypted}
isMutipleOperation={this.state.isMutipleOperation}
selectedDirentList={this.props.selectedDirentList}
onItemsMove={this.props.onItemsMove}
onCancelMove={this.onMoveToggle}
/>
</ModalPortal>
)}
</Fragment>
}
{this.state.isCopyDialogShow &&
<CopyDirentDialog
path={this.props.path}
repoID={this.props.repoID}
repoEncrypted={this.props.currentRepoInfo.encrypted}
selectedDirentList={this.props.selectedDirentList}
isMutipleOperation={this.state.isMutipleOperation}
onItemsCopy={this.props.onItemsCopy}
onCancelCopy={this.onCopyToggle}
/>
}
{this.state.isProgressDialogShow &&
<ZipDownloadDialog progress={this.state.progress} onCancelDownload={this.onCancelDownload} />
}
</Fragment>
</div>
);
}
}

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, isPro, enableFileComment, fileAuditEnabled, folderPermEnabled } from '../../utils/constants';
@@ -10,7 +11,6 @@ const propTypes = {
onMenuItemClick: PropTypes.func.isRequired,
onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired,
appMenuType: PropTypes.oneOf(['list_view_contextmenu', 'item_contextmenu', 'tree_contextmenu', 'item_op_menu']),
};
class DirentMenu extends React.Component {
@@ -25,11 +25,23 @@ class DirentMenu extends React.Component {
componentDidMount() {
this.menuList = this.calculateMenuList();
this.listenerId = listener.register(this.onShowMenu, this.onHideMenu);
}
componentWillReceiveProps(nextProp) {
if (nextProp.appMenuType === 'list_view_contextmenu' || nextProp.appMenuType === 'item_contextmenu') {
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

@@ -0,0 +1,92 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import Loading from '../loading';
import ModalPortal from '../modal-portal';
import CreateFile from '../../components/dialog/create-file-dialog';
import '../../css/tip-for-new-md.css';
const propTypes = {
path: PropTypes.string.isRequired,
isDirentListLoading: PropTypes.bool.isRequired,
onAddFile: PropTypes.func.isRequired,
};
class DirentNodeView extends React.Component {
constructor(props) {
super(props);
this.state = {
fileType: '',
isCreateFileDialogShow: false,
};
}
onCreateNewFile = (type) => {
this.setState({
fileType: type,
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
});
}
onCreateFileToggle = () => {
this.setState({
fileType: '',
isCreateFileDialogShow: !this.state.isCreateFileDialogShow,
});
}
checkDuplicatedName = () => {
return false; // current repo is null, and unnecessary to check duplicated name
}
onAddFile = (filePath, isDraft) => {
this.setState({isCreateFileDialogShow: false});
this.props.onAddFile(filePath, isDraft);
}
render() {
if (this.props.isDirentListLoading) {
return (<Loading />);
}
return (
<Fragment>
<div className="tip-for-new-file">
<p className="text-center">{gettext('This folder has no content at this time.')}</p>
<p className="text-center">{gettext('You can create files quickly')}{' +'}</p>
<div className="big-new-file-button-group">
<div className="d-flex justify-content-center">
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.md')}>
{'+ Markdown'}</button>
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.ppt')}>
{'+ PPT'}</button>
</div>
<div className="d-flex justify-content-center">
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.doc')}>
{'+ Word'}</button>
<button className="big-new-file-button" onClick={this.onCreateNewFile.bind(this, '.xls')}>
{'+ Excel'}</button>
</div>
</div>
</div>
{this.state.isCreateFileDialogShow && (
<ModalPortal>
<CreateFile
parentPath={this.props.path}
fileType={this.state.fileType}
onAddFile={this.onAddFile}
addFileCancel={this.onCreateFileToggle}
checkDuplicatedName={this.checkDuplicatedName}
/>
</ModalPortal>
)}
</Fragment>
);
}
}
DirentNodeView.propTypes = propTypes;
export default DirentNodeView;

View File

@@ -1,292 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext, isPro, enableFileComment, fileAuditEnabled, folderPermEnabled } from '../../utils/constants';
import '../../css/tree-view-contextmenu.css'
const propTypes = {
dirent: PropTypes.object,
mousePosition: PropTypes.object,
isRepoOwner: PropTypes.bool,
currentRepoInfo: PropTypes.object,
onMenuItemClick: PropTypes.func,
onItemDownload: PropTypes.func,
onItemShare: PropTypes.func,
onItemDelete: PropTypes.func,
itemRegisterHandlers: PropTypes.func,
itemUnregisterHandlers: PropTypes.func,
closeRightMenu: PropTypes.func,
onCreateFolderToggle: PropTypes.func,
onCreateFileToggle: PropTypes.func,
};
class DirentRightMenu 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() {
if (!this.props.dirent) {
let menuList = ['New Folder', 'New File'];
return menuList;
}
let { currentRepoInfo, dirent, isRepoOwner, showShare } = this.props;
let type = dirent.type ? dirent.type : '';
let permission = dirent.permission ? dirent.permission : '';
let can_set_folder_perm = folderPermEnabled && ((isRepoOwner && currentRepoInfo.has_been_shared_out) || currentRepoInfo.is_admin);
if (type === 'dir' && permission === 'rw') {
let subscriptList = showShare ? ['Share', 'Download', 'Delete', 'Divider'] : ['Download', 'Delete', 'Divider'];
let menuList = [];
if (can_set_folder_perm) {
menuList = [...subscriptList, 'Rename', 'Move', 'Copy', 'Divider', 'Permission', 'Divider', 'Open via Client'];
} else {
menuList = [...subscriptList, 'Rename', 'Move', 'Copy', 'Divider', 'Open via Client'];
}
return menuList;
}
if (type === 'dir' && permission === 'r') {
let menuList = showShare ? ['Share', 'Download','Delete', 'Divider', 'Copy'] : ['Download', 'Delete'];
return menuList;
}
if (type === 'file' && permission === 'rw') {
let menuList = showShare ? ['Share', 'Download', 'Delete', 'Divider'] : ['Download', 'Delete', 'Divider'];
if (!dirent.is_locked || (dirent.is_locked && dirent.locked_by_me)) {
menuList.push('Rename');
menuList.push('Move');
}
menuList.push('Copy');
if (isPro) {
if (dirent.is_locked) {
if (dirent.locked_by_me || (dirent.lock_owner === 'OnlineOffice' && permission === 'rw')) {
menuList.push('Unlock');
}
} else {
menuList.push('Lock');
}
}
menuList.push('Divider');
if (enableFileComment) {
menuList.push('Comment');
}
menuList.push('History');
if (fileAuditEnabled) {
menuList.push('Access Log');
}
menuList.push('Divider');
menuList.push('Open via Client');
return menuList;
}
if (type === 'file' && permission === 'r') {
let menuList = showShare ? ['Share', 'Download', 'Delete', 'Divider'] : ['Download', 'Delete', 'Divider'];
if (!currentRepoInfo.encrypted) {
menuList.push('Copy');
}
if (enableFileComment) {
menuList.push('Comment');
}
menuList.push('History');
return menuList;
}
}
calculateMenuDistance = () => {
let { mousePosition } = this.props;
let rightTreeMenu = document.querySelector('.right-tree-menu');
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
let rightTreeMenuWidth = rightTreeMenu.offsetWidth;
if (mousePosition.clientY + rightTreeMenuHeight > document.body.clientHeight) {
rightTreeMenu.style.top = mousePosition.clientY - rightTreeMenuHeight + 'px';
} else {
rightTreeMenu.style.top = mousePosition.clientY + 'px';
}
if (mousePosition.clientX + rightTreeMenuWidth > document.body.clientWidth) {
rightTreeMenu.style.left = mousePosition.clientX - rightTreeMenuWidth + 'px';
} else {
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 'Share':
translateResult = gettext('Share');
break;
case 'Download':
translateResult = gettext('Download');
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 'Unlock':
translateResult = gettext('Unlock');
break;
case 'Lock':
translateResult = gettext('Lock');
break;
case 'History':
translateResult = gettext('History');
break;
case 'Open via Client':
translateResult = gettext('Open via Client');
break;
default:
break;
}
return translateResult;
}
handleContextMenu = (e) => {
let { mousePosition } = this.props;
let rightTreeMenu = document.querySelector('.right-tree-menu');
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
let rightTreeMenuWidth = rightTreeMenu.offsetWidth;
rightTreeMenu.addEventListener('contextmenu', (e) => {
e.stopPropagation();
})
if (mousePosition.clientY + rightTreeMenuHeight > document.body.clientHeight) {
if (mousePosition.clientX + rightTreeMenuWidth > document.body.clientWidth) {
if ((e.clientX >= (mousePosition.clientX - rightTreeMenuWidth)) && (e.clientX <= mousePosition.clientX) && (e.clientY <= mousePosition.clientY) && (e.clientY >= (mousePosition.clientY - rightTreeMenuHeight))) {
this.props.itemUnregisterHandlers();
} else {
this.props.itemRegisterHandlers();
}
} else {
if ((e.clientX >= mousePosition.clientX) && (e.clientX <= (mousePosition.clientX + rightTreeMenuWidth)) && (e.clientY <= mousePosition.clientY) && (e.clientY >= (mousePosition.clientY - rightTreeMenuHeight))) {
this.props.itemUnregisterHandlers();
} else {
this.props.itemRegisterHandlers();
}
}
} else {
if (mousePosition.clientX + rightTreeMenuWidth > document.body.clientWidth) {
if ((e.clientX >= (mousePosition.clientX - rightTreeMenuWidth)) && (e.clientX <= mousePosition.clientX) && (e.clientY >= mousePosition.clientY) && (e.clientY <= (mousePosition.clientY + rightTreeMenuHeight))) {
this.props.itemUnregisterHandlers();
} else {
this.props.itemRegisterHandlers();
}
} else {
if ((e.clientX >= mousePosition.clientX) && (e.clientX <= (mousePosition.clientX + rightTreeMenuWidth)) && (e.clientY >= mousePosition.clientY) && (e.clientY <= (mousePosition.clientY + rightTreeMenuHeight))) {
this.props.itemUnregisterHandlers();
} else {
this.props.itemRegisterHandlers();
}
}
}
}
listenClick = (e) => {
let { mousePosition } = this.props;
let rightTreeMenu = document.querySelector('.right-tree-menu');
let rightTreeMenuHeight = rightTreeMenu.offsetHeight;
let rightTreeMenuWidth = rightTreeMenu.offsetWidth;
if (mousePosition.clientX + rightTreeMenuWidth > document.body.clientWidth) {
if (e.clientX <= (mousePosition.clientX - rightTreeMenuWidth) || e.clientX >= mousePosition.clientX) {
this.props.closeRightMenu();
}
} else {
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;
if (operation === 'Share') {
this.props.onItemShare(event);
} else if (operation === 'Delete') {
this.props.onItemDelete(event);
} else if (operation === 'Download') {
this.props.onItemDownload(event);
} else if (operation === 'New Folder') {
this.props.onCreateFolderToggle()
} else if (operation === 'New File') {
this.props.onCreateFileToggle();
} else {
this.props.onMenuItemClick(operation);
}
this.props.closeRightMenu();
if(this.props.onUnfreezedItem){
this.props.onUnfreezedItem();
}
}
render() {
return (
<div className='right-tree-menu'>
{this.state.menuList.map((menuItem, index) => {
if (menuItem === 'Divider') {
return <div className="right-tree-divider"></div>
} else {
return (
<button className='right-tree-item' key={index} data-toggle={menuItem} onClick={this.onMenuItemClick}>{this.translateMenuItem(menuItem)}</button>
);
}
})}
</div>
);
}
}
DirentRightMenu.propTypes = propTypes;
export default DirentRightMenu;

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>
);
}