mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-22 11:57:34 +00:00
refactor: metadata contextmenu (#6587)
* refactor: metadata contextmenu * feat: update code --------- Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
@@ -108,7 +108,7 @@ const FileNameEditor = ({ column, record, table, onCommitCancel }) => {
|
|||||||
if (!fileType || fileType === 'sdoc') {
|
if (!fileType || fileType === 'sdoc') {
|
||||||
window.open(url);
|
window.open(url);
|
||||||
} else {
|
} else {
|
||||||
window.open(window.location.href + Utils.encodePath(Utils.joinPath(parentDir, fileName)));
|
window.open(window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName)));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@@ -1,118 +0,0 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import './context-menu.css';
|
|
||||||
|
|
||||||
const ContextMenu = ({ options, onOptionClick }) => {
|
|
||||||
const menuRef = useRef(null);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
||||||
|
|
||||||
const handleHide = useCallback((event) => {
|
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
||||||
setVisible(false);
|
|
||||||
}
|
|
||||||
}, [menuRef]);
|
|
||||||
|
|
||||||
const handleOptionClick = (event, option) => {
|
|
||||||
onOptionClick(event, option);
|
|
||||||
setVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMenuPosition = (x = 0, y = 0) => {
|
|
||||||
let menuStyles = {
|
|
||||||
top: y,
|
|
||||||
left: x
|
|
||||||
};
|
|
||||||
if (!menuRef.current) return menuStyles;
|
|
||||||
|
|
||||||
const { innerWidth, innerHeight } = window;
|
|
||||||
const rect = menuRef.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
// Calculate the offset of the parent components
|
|
||||||
const parentRect = menuRef.current.parentElement.getBoundingClientRect();
|
|
||||||
const offsetX = parentRect.left;
|
|
||||||
const offsetY = parentRect.top;
|
|
||||||
|
|
||||||
// Adjust the position based on the offset
|
|
||||||
menuStyles.top = y - offsetY;
|
|
||||||
menuStyles.left = x - offsetX;
|
|
||||||
|
|
||||||
const metadataResultFooterHeight = 32;
|
|
||||||
const contentHeight = innerHeight - metadataResultFooterHeight;
|
|
||||||
if (y + rect.height > contentHeight) {
|
|
||||||
menuStyles.top -= rect.height;
|
|
||||||
}
|
|
||||||
if (x + rect.width > innerWidth) {
|
|
||||||
menuStyles.left -= rect.width;
|
|
||||||
}
|
|
||||||
if (menuStyles.top < 0) {
|
|
||||||
menuStyles.top = rect.height < contentHeight ? (contentHeight - rect.height) / 2 : 0;
|
|
||||||
}
|
|
||||||
if (menuStyles.left < 0) {
|
|
||||||
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
|
|
||||||
}
|
|
||||||
return menuStyles;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleShow = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (menuRef.current && menuRef.current.contains(event.target)) return;
|
|
||||||
|
|
||||||
setVisible(true);
|
|
||||||
|
|
||||||
const position = getMenuPosition(event.clientX, event.clientY);
|
|
||||||
setPosition(position);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('contextmenu', handleShow);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('contextmenu', handleShow);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
document.addEventListener('mousedown', handleHide);
|
|
||||||
} else {
|
|
||||||
document.removeEventListener('mousedown', handleHide);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleHide);
|
|
||||||
};
|
|
||||||
}, [visible, handleHide]);
|
|
||||||
|
|
||||||
if (!visible) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={menuRef}
|
|
||||||
className='dropdown-menu sf-metadata-contextmenu'
|
|
||||||
style={position}
|
|
||||||
>
|
|
||||||
{options.map((option, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
className='dropdown-item sf-metadata-contextmenu-item'
|
|
||||||
onClick={(event) => handleOptionClick(event, option)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ContextMenu.propTypes = {
|
|
||||||
options: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
onOptionClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContextMenu;
|
|
@@ -0,0 +1,214 @@
|
|||||||
|
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { PRIVATE_COLUMN_KEY } from '../../_basic';
|
||||||
|
import { gettext } from '../../utils';
|
||||||
|
import { siteRoot } from '../../../../utils/constants';
|
||||||
|
import { Utils } from '../../../../utils/utils';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const OPERATION = {
|
||||||
|
CLEAR_SELECTED: 'clear-selected',
|
||||||
|
COPY_SELECTED: 'copy-selected',
|
||||||
|
OPEN_PARENT_FOLDER: 'open-parent-folder',
|
||||||
|
OPEN_IN_NEW_TAB: 'open-new-tab',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContextMenu = ({
|
||||||
|
isGroupView,
|
||||||
|
selectedRange,
|
||||||
|
selectedPosition,
|
||||||
|
recordMetrics,
|
||||||
|
recordGetterByIndex,
|
||||||
|
onClearSelected,
|
||||||
|
onCopySelected,
|
||||||
|
}) => {
|
||||||
|
const menuRef = useRef(null);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
const permission = window.sfMetadataContext.getPermission();
|
||||||
|
const isReadonly = permission === 'r';
|
||||||
|
let list = [];
|
||||||
|
|
||||||
|
if (selectedRange) {
|
||||||
|
!isReadonly && list.push({ value: OPERATION.CLEAR_SELECTED, label: gettext('Clear selected') });
|
||||||
|
list.push({ value: OPERATION.COPY_SELECTED, label: gettext('Copy selected') });
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(recordMetrics.idSelectedRecordMap).length > 1) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedPosition) return list;
|
||||||
|
const { groupRecordIndex, rowIdx: recordIndex } = selectedPosition;
|
||||||
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex });
|
||||||
|
if (!record) return list;
|
||||||
|
const isFolder = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
||||||
|
list.push({ value: OPERATION.OPEN_IN_NEW_TAB, label: isFolder ? gettext('Open folder in new tab') : gettext('Open file in new tab') });
|
||||||
|
list.push({ value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder') });
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}, [isGroupView, selectedPosition, recordMetrics, selectedRange, recordGetterByIndex]);
|
||||||
|
|
||||||
|
const handleHide = useCallback((event) => {
|
||||||
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
}, [menuRef]);
|
||||||
|
|
||||||
|
const onOpenFileInNewTab = useCallback(() => {
|
||||||
|
const { groupRecordIndex, rowIdx } = selectedPosition;
|
||||||
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||||
|
if (!record) return;
|
||||||
|
const repoID = window.sfMetadataStore.repoId;
|
||||||
|
const isFolder = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
||||||
|
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
||||||
|
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
||||||
|
|
||||||
|
let url;
|
||||||
|
if (isFolder) {
|
||||||
|
url = window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
||||||
|
} else {
|
||||||
|
url = `${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, fileName))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}, [isGroupView, recordGetterByIndex, selectedPosition]);
|
||||||
|
|
||||||
|
const onOpenParentFolder = useCallback((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const { groupRecordIndex, rowIdx } = selectedPosition;
|
||||||
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||||
|
if (!record) return;
|
||||||
|
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
||||||
|
const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}, [isGroupView, recordGetterByIndex, selectedPosition]);
|
||||||
|
|
||||||
|
const handleOptionClick = useCallback((event, option) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
switch (option.value) {
|
||||||
|
case OPERATION.OPEN_IN_NEW_TAB: {
|
||||||
|
onOpenFileInNewTab();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.OPEN_PARENT_FOLDER: {
|
||||||
|
onOpenParentFolder(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.COPY_SELECTED: {
|
||||||
|
onCopySelected && onCopySelected();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.CLEAR_SELECTED: {
|
||||||
|
onClearSelected && onClearSelected();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setVisible(false);
|
||||||
|
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected]);
|
||||||
|
|
||||||
|
const getMenuPosition = (x = 0, y = 0) => {
|
||||||
|
let menuStyles = {
|
||||||
|
top: y,
|
||||||
|
left: x
|
||||||
|
};
|
||||||
|
if (!menuRef.current) return menuStyles;
|
||||||
|
|
||||||
|
const { innerWidth, innerHeight } = window;
|
||||||
|
const rect = menuRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Calculate the offset of the parent components
|
||||||
|
const parentRect = menuRef.current.parentElement.getBoundingClientRect();
|
||||||
|
const offsetX = parentRect.left;
|
||||||
|
const offsetY = parentRect.top;
|
||||||
|
|
||||||
|
// Adjust the position based on the offset
|
||||||
|
menuStyles.top = y - offsetY;
|
||||||
|
menuStyles.left = x - offsetX;
|
||||||
|
|
||||||
|
const metadataResultFooterHeight = 32;
|
||||||
|
const contentHeight = innerHeight - metadataResultFooterHeight;
|
||||||
|
if (y + rect.height > contentHeight) {
|
||||||
|
menuStyles.top -= rect.height;
|
||||||
|
}
|
||||||
|
if (x + rect.width > innerWidth) {
|
||||||
|
menuStyles.left -= rect.width;
|
||||||
|
}
|
||||||
|
if (menuStyles.top < 0) {
|
||||||
|
menuStyles.top = rect.height < contentHeight ? (contentHeight - rect.height) / 2 : 0;
|
||||||
|
}
|
||||||
|
if (menuStyles.left < 0) {
|
||||||
|
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
|
||||||
|
}
|
||||||
|
return menuStyles;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleShow = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (menuRef.current && menuRef.current.contains(event.target)) return;
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
|
||||||
|
const position = getMenuPosition(event.clientX, event.clientY);
|
||||||
|
setPosition(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('contextmenu', handleShow);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('contextmenu', handleShow);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
document.addEventListener('mousedown', handleHide);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('mousedown', handleHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleHide);
|
||||||
|
};
|
||||||
|
}, [visible, handleHide]);
|
||||||
|
|
||||||
|
if (!visible) return null;
|
||||||
|
if (options.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={menuRef}
|
||||||
|
className='dropdown-menu sf-metadata-contextmenu'
|
||||||
|
style={position}
|
||||||
|
>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className='dropdown-item sf-metadata-contextmenu-item'
|
||||||
|
onClick={(event) => handleOptionClick(event, option)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ContextMenu.propTypes = {
|
||||||
|
isGroupView: PropTypes.bool,
|
||||||
|
selectedRange: PropTypes.object,
|
||||||
|
selectedPosition: PropTypes.object,
|
||||||
|
recordMetrics: PropTypes.object,
|
||||||
|
recordGetterByIndex: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContextMenu;
|
@@ -28,10 +28,6 @@ const Container = () => {
|
|||||||
return validGroupbys.length > 0;
|
return validGroupbys.length > 0;
|
||||||
}, [metadata]);
|
}, [metadata]);
|
||||||
|
|
||||||
const onSelectCell = useCallback(() => {
|
|
||||||
// todo
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
if (!metadata.hasMore) return;
|
if (!metadata.hasMore) return;
|
||||||
setLoadingMore(true);
|
setLoadingMore(true);
|
||||||
@@ -158,14 +154,12 @@ const Container = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('keydown', onKeyDown);
|
document.addEventListener('keydown', onKeyDown);
|
||||||
const unsubscribeSelectCell = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, onSelectCell);
|
|
||||||
const unsubscribeModifyFilters = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_FILTERS, modifyFilters);
|
const unsubscribeModifyFilters = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_FILTERS, modifyFilters);
|
||||||
const unsubscribeModifySorts = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SORTS, modifySorts);
|
const unsubscribeModifySorts = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SORTS, modifySorts);
|
||||||
const unsubscribeModifyGroupbys = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_GROUPBYS, modifyGroupbys);
|
const unsubscribeModifyGroupbys = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_GROUPBYS, modifyGroupbys);
|
||||||
const unsubscribeModifyHiddenColumns = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns);
|
const unsubscribeModifyHiddenColumns = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', onKeyDown);
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
unsubscribeSelectCell();
|
|
||||||
unsubscribeModifyFilters();
|
unsubscribeModifyFilters();
|
||||||
unsubscribeModifySorts();
|
unsubscribeModifySorts();
|
||||||
unsubscribeModifyGroupbys();
|
unsubscribeModifyGroupbys();
|
||||||
|
@@ -331,8 +331,8 @@ class RecordsBody extends Component {
|
|||||||
this.props.onCellRangeSelectionUpdated(selectedRange);
|
this.props.onCellRangeSelectionUpdated(selectedRange);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCellContextMenu = (event, cell) => {
|
onCellContextMenu = (cellPosition) => {
|
||||||
this.props.onCellContextMenu(event, cell);
|
this.props.onCellContextMenu(cellPosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -510,6 +510,7 @@ class RecordsBody extends Component {
|
|||||||
<div id="canvas" className="sf-metadata-result-table-content" ref={this.setResultContentRef} onScroll={this.onScroll}>
|
<div id="canvas" className="sf-metadata-result-table-content" ref={this.setResultContentRef} onScroll={this.onScroll}>
|
||||||
<InteractionMasks
|
<InteractionMasks
|
||||||
ref={this.setInteractionMaskRef}
|
ref={this.setInteractionMaskRef}
|
||||||
|
contextMenu={this.props.contextMenu}
|
||||||
canAddRow={this.props.canAddRow}
|
canAddRow={this.props.canAddRow}
|
||||||
table={this.props.table}
|
table={this.props.table}
|
||||||
columns={this.props.columns}
|
columns={this.props.columns}
|
||||||
@@ -559,6 +560,7 @@ class RecordsBody extends Component {
|
|||||||
|
|
||||||
RecordsBody.propTypes = {
|
RecordsBody.propTypes = {
|
||||||
onRef: PropTypes.func,
|
onRef: PropTypes.func,
|
||||||
|
contextMenu: PropTypes.oneOfType([PropTypes.node, PropTypes.element]),
|
||||||
canAddRow: PropTypes.bool,
|
canAddRow: PropTypes.bool,
|
||||||
gridUtils: PropTypes.object,
|
gridUtils: PropTypes.object,
|
||||||
table: PropTypes.object,
|
table: PropTypes.object,
|
||||||
|
@@ -446,8 +446,8 @@ class GroupBody extends Component {
|
|||||||
this.selectUpdate(cellPosition, false, this.updateViewableArea);
|
this.selectUpdate(cellPosition, false, this.updateViewableArea);
|
||||||
};
|
};
|
||||||
|
|
||||||
onCellContextMenu = (event, cell) => {
|
onCellContextMenu = (cellPosition) => {
|
||||||
this.props.onCellContextMenu(event, cell);
|
this.props.onCellContextMenu(cellPosition);
|
||||||
};
|
};
|
||||||
|
|
||||||
onWindowMouseUp = (event) => {
|
onWindowMouseUp = (event) => {
|
||||||
|
@@ -15,10 +15,7 @@ import RecordMetrics from '../../../../utils/record-metrics';
|
|||||||
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
|
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
|
||||||
import { getVisibleBoundaries } from '../../../../utils/viewport';
|
import { getVisibleBoundaries } from '../../../../utils/viewport';
|
||||||
import { getColOverScanEndIdx, getColOverScanStartIdx } from '../../../../utils/grid';
|
import { getColOverScanEndIdx, getColOverScanStartIdx } from '../../../../utils/grid';
|
||||||
import TextTranslation from '../../../../../../utils/text-translation';
|
import ContextMenu from '../../../context-menu';
|
||||||
import { Utils } from '../../../../../../utils/utils';
|
|
||||||
import { siteRoot } from '../../../../../../utils/constants';
|
|
||||||
import ContextMenu from '../../../context-menu/context-menu';
|
|
||||||
|
|
||||||
class Records extends Component {
|
class Records extends Component {
|
||||||
|
|
||||||
@@ -47,11 +44,6 @@ class Records extends Component {
|
|||||||
};
|
};
|
||||||
this.isWindows = isWindowsBrowser();
|
this.isWindows = isWindowsBrowser();
|
||||||
this.isWebkit = isWebkitBrowser();
|
this.isWebkit = isWebkitBrowser();
|
||||||
this.baseURI = '';
|
|
||||||
this.contextMenuOptions = [
|
|
||||||
{ label: TextTranslation.OPEN_FILE_IN_NEW_TAB.value, value: 'openFileInNewTab' },
|
|
||||||
{ label: TextTranslation.OPEN_PARENT_FOLDER.value, value: 'openParentFolder' },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -64,6 +56,7 @@ class Records extends Component {
|
|||||||
document.addEventListener('mousedown', this.onMouseDown);
|
document.addEventListener('mousedown', this.onMouseDown);
|
||||||
}
|
}
|
||||||
this.unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
|
this.unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
|
||||||
|
this.unsubscribeSelectCell = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.selectCell);
|
||||||
this.getScrollPosition();
|
this.getScrollPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +92,8 @@ class Records extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.clearSetAbsoluteTimer();
|
this.clearSetAbsoluteTimer();
|
||||||
|
this.unsubscribeSelectNone();
|
||||||
|
this.unsubscribeSelectCell();
|
||||||
this.setState = (state, callback) => {
|
this.setState = (state, callback) => {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -334,10 +329,9 @@ class Records extends Component {
|
|||||||
|
|
||||||
onCellClick = (cell) => {
|
onCellClick = (cell) => {
|
||||||
if (cell) {
|
if (cell) {
|
||||||
const currentPosition = { ...cell };
|
|
||||||
this.updateSelectedRange({
|
this.updateSelectedRange({
|
||||||
topLeft: currentPosition,
|
topLeft: this.initPosition,
|
||||||
bottomRight: currentPosition,
|
bottomRight: this.initPosition,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.onDeselectAllRecords();
|
this.onDeselectAllRecords();
|
||||||
@@ -401,6 +395,10 @@ class Records extends Component {
|
|||||||
this.onDeselectAllRecords();
|
this.onDeselectAllRecords();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectCell = (cellPosition) => {
|
||||||
|
this.setState({ selectedPosition: cellPosition });
|
||||||
|
};
|
||||||
|
|
||||||
onSelectRecord = ({ groupRecordIndex, recordIndex }, e) => {
|
onSelectRecord = ({ groupRecordIndex, recordIndex }, e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (isShiftKeyDown(e)) {
|
if (isShiftKeyDown(e)) {
|
||||||
@@ -597,65 +595,43 @@ class Records extends Component {
|
|||||||
this.setState(scrollState);
|
this.setState(scrollState);
|
||||||
};
|
};
|
||||||
|
|
||||||
onOpenFileInNewTab = () => {
|
isOutSelectedRange = ({ recordIndex, idx }) => {
|
||||||
|
const { selectedRange } = this.state;
|
||||||
|
const { topLeft, bottomRight } = selectedRange;
|
||||||
|
const { idx: minIdx, rowIdx: minRowIdx } = topLeft;
|
||||||
|
const { idx: maxIdx, rowIdx: maxRowIdx } = bottomRight;
|
||||||
|
return idx < minIdx || idx > maxIdx || recordIndex < minRowIdx || recordIndex > maxRowIdx;
|
||||||
|
};
|
||||||
|
|
||||||
|
onCellContextMenu = (cell) => {
|
||||||
|
const { rowIdx: recordIndex, idx, groupRecordIndex } = cell;
|
||||||
const { isGroupView, recordGetterByIndex } = this.props;
|
const { isGroupView, recordGetterByIndex } = this.props;
|
||||||
const { groupRecordIndex, rowIdx } = this.state.selectedPosition;
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex });
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
|
||||||
const repoID = window.sfMetadataStore.repoId;
|
if (!record) return;
|
||||||
let url;
|
const { recordMetrics } = this.state;
|
||||||
if (record._is_dir) {
|
const recordId = record._id;
|
||||||
url = `${this.baseURI}${record._parent_dir === '/' ? '' : record._parent_dir}/${record._name}`;
|
if (!RecordMetrics.isRecordSelected(recordId, recordMetrics)) {
|
||||||
} else {
|
this.setState({ recordMetrics: this.createRowMetrics() });
|
||||||
url = `${siteRoot}lib/${repoID}/file${Utils.encodePath(record._parent_dir + '/' + record._name)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(url, '_blank');
|
// select cell when click out of selectRange
|
||||||
};
|
if (this.isOutSelectedRange({ recordIndex, idx })) {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_CELL, cell, false);
|
||||||
onOpenParentFolder = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const { isGroupView, recordGetterByIndex } = this.props;
|
|
||||||
const { groupRecordIndex, rowIdx } = this.state.selectedPosition;
|
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
|
||||||
const url = `${this.baseURI}${record._parent_dir}`;
|
|
||||||
|
|
||||||
window.open(url, '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
onOptionClick = (event, option) => {
|
|
||||||
|
|
||||||
const handlers = {
|
|
||||||
openFileInNewTab: this.onOpenFileInNewTab.bind(this),
|
|
||||||
openParentFolder: this.onOpenParentFolder.bind(this),
|
|
||||||
};
|
|
||||||
|
|
||||||
const handler = handlers[option.value];
|
|
||||||
if (handler) {
|
|
||||||
handler(event);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onCellContextMenu = (event, cell) => {
|
|
||||||
const url = new URL(event.target.baseURI);
|
|
||||||
url.search = '';
|
|
||||||
this.baseURI = url.toString();
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
selectedPosition: cell,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
renderRecordsBody = ({ containerWidth }) => {
|
renderRecordsBody = ({ containerWidth }) => {
|
||||||
|
const { isGroupView, recordGetterByIndex } = this.props;
|
||||||
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
||||||
const {
|
const {
|
||||||
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
||||||
} = columnMetrics;
|
} = columnMetrics;
|
||||||
|
const contextMenu = (<ContextMenu isGroupView={isGroupView} recordGetterByIndex={recordGetterByIndex} />);
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
...this.props,
|
...this.props,
|
||||||
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
||||||
recordMetrics, colOverScanStartIdx, colOverScanEndIdx,
|
recordMetrics, colOverScanStartIdx, colOverScanEndIdx, contextMenu,
|
||||||
hasSelectedRecord: this.hasSelectedRecord(),
|
hasSelectedRecord: this.hasSelectedRecord(),
|
||||||
getScrollLeft: this.getScrollLeft,
|
getScrollLeft: this.getScrollLeft,
|
||||||
getScrollTop: this.getScrollTop,
|
getScrollTop: this.getScrollTop,
|
||||||
@@ -728,10 +704,6 @@ class Records extends Component {
|
|||||||
/>
|
/>
|
||||||
{this.renderRecordsBody({ containerWidth })}
|
{this.renderRecordsBody({ containerWidth })}
|
||||||
</div>
|
</div>
|
||||||
<ContextMenu
|
|
||||||
options={this.contextMenuOptions}
|
|
||||||
onOptionClick={this.onOptionClick}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{this.isWindows && this.isWebkit && (
|
{this.isWindows && this.isWebkit && (
|
||||||
<HorizontalScrollbar
|
<HorizontalScrollbar
|
||||||
|
@@ -107,8 +107,7 @@ const Cell = React.memo(({
|
|||||||
|
|
||||||
const cell = { idx: column.idx, groupRecordIndex, rowIdx: recordIndex };
|
const cell = { idx: column.idx, groupRecordIndex, rowIdx: recordIndex };
|
||||||
if (!isFunction(cellMetaData.onCellContextMenu)) return;
|
if (!isFunction(cellMetaData.onCellContextMenu)) return;
|
||||||
cellMetaData.onCellContextMenu(event, cell,);
|
cellMetaData.onCellContextMenu(cell);
|
||||||
cellMetaData.onCellClick(cell, event);
|
|
||||||
}, [cellMetaData, column, groupRecordIndex, recordIndex]);
|
}, [cellMetaData, column, groupRecordIndex, recordIndex]);
|
||||||
|
|
||||||
const getEvents = useCallback(() => {
|
const getEvents = useCallback(() => {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { isValidElement, cloneElement } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import deepCopy from 'deep-copy';
|
import deepCopy from 'deep-copy';
|
||||||
import toaster from '../../../../../../components/toast';
|
import toaster from '../../../../../../components/toast';
|
||||||
@@ -547,6 +547,10 @@ class InteractionMasks extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onCopySelected = () => {
|
||||||
|
this.onCopyCells();
|
||||||
|
};
|
||||||
|
|
||||||
onCopy = (e) => {
|
onCopy = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -1014,7 +1018,7 @@ class InteractionMasks extends React.Component {
|
|||||||
const showDragHandle = (isDragEnabled && canEdit);
|
const showDragHandle = (isDragEnabled && canEdit);
|
||||||
const column = getSelectedColumn({ selectedPosition, columns });
|
const column = getSelectedColumn({ selectedPosition, columns });
|
||||||
const { type: columnType } = column || {};
|
const { type: columnType } = column || {};
|
||||||
if (isEditorEnabled && columnType !== CellType.RATE && columnType !== CellType.CHECKBOX) return null;
|
if (isEditorEnabled && columnType !== CellType.RATE && columnType !== CellType.CHECKBOX && columnType !== CellType.FILE_NAME) return null;
|
||||||
if (!this.isGridSelected()) return null;
|
if (!this.isGridSelected()) return null;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1068,7 +1072,7 @@ class InteractionMasks extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectedRange, isEditorEnabled, draggedRange, selectedPosition, firstEditorKeyDown, openEditorMode, editorPosition } = this.state;
|
const { selectedRange, isEditorEnabled, draggedRange, selectedPosition, firstEditorKeyDown, openEditorMode, editorPosition } = this.state;
|
||||||
const { table, columns, isGroupView, recordGetterByIndex, scrollTop, getScrollLeft, editorPortalTarget } = this.props;
|
const { table, columns, isGroupView, recordGetterByIndex, scrollTop, getScrollLeft, editorPortalTarget, contextMenu, recordMetrics } = this.props;
|
||||||
const isSelectedSingleCell = selectedRangeIsSingleCell(selectedRange);
|
const isSelectedSingleCell = selectedRangeIsSingleCell(selectedRange);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -1113,12 +1117,21 @@ class InteractionMasks extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</EditorPortal>
|
</EditorPortal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isValidElement(contextMenu) && cloneElement(contextMenu, {
|
||||||
|
recordMetrics: recordMetrics,
|
||||||
|
selectedPosition: isSelectedSingleCell ? selectedPosition : null,
|
||||||
|
selectedRange: !isSelectedSingleCell ? selectedRange : null,
|
||||||
|
onClearSelected: this.handleSelectCellsDelete,
|
||||||
|
onCopySelected: this.onCopySelected,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractionMasks.propTypes = {
|
InteractionMasks.propTypes = {
|
||||||
|
contextmenu: PropTypes.element,
|
||||||
table: PropTypes.object,
|
table: PropTypes.object,
|
||||||
columns: PropTypes.array,
|
columns: PropTypes.array,
|
||||||
canAddRow: PropTypes.bool,
|
canAddRow: PropTypes.bool,
|
||||||
|
@@ -19,8 +19,6 @@ const TextTranslation = {
|
|||||||
'PERMISSION': { key: 'Permission', value: gettext('Permission') },
|
'PERMISSION': { key: 'Permission', value: gettext('Permission') },
|
||||||
'DETAILS': { key: 'Details', value: gettext('Details') },
|
'DETAILS': { key: 'Details', value: gettext('Details') },
|
||||||
'OPEN_VIA_CLIENT': { key: 'Open via Client', value: gettext('Open via Client') },
|
'OPEN_VIA_CLIENT': { key: 'Open via Client', value: gettext('Open via Client') },
|
||||||
'OPEN_FILE_IN_NEW_TAB': { key: 'Open file in new tab', value: gettext('Open file in new tab') },
|
|
||||||
'OPEN_PARENT_FOLDER': { key: 'Open parent folder', value: gettext('Open parent folder') },
|
|
||||||
'LOCK': { key: 'Lock', value: gettext('Lock') },
|
'LOCK': { key: 'Lock', value: gettext('Lock') },
|
||||||
'UNLOCK': { key: 'Unlock', value: gettext('Unlock') },
|
'UNLOCK': { key: 'Unlock', value: gettext('Unlock') },
|
||||||
'FREEZE_DOCUMENT': { key: 'Freeze Document', value: gettext('Freeze Document') },
|
'FREEZE_DOCUMENT': { key: 'Freeze Document', value: gettext('Freeze Document') },
|
||||||
|
Reference in New Issue
Block a user