diff --git a/frontend/src/metadata/metadata-view/components/context-menu/context-menu.css b/frontend/src/metadata/metadata-view/components/context-menu/context-menu.css
new file mode 100644
index 00000000000..0b4aec9148c
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/context-menu/context-menu.css
@@ -0,0 +1,39 @@
+.context-menu {
+ position: fixed;
+ min-width: 12rem;
+ padding: 0.5rem 0;
+ margin: 0.125rem 0 0;
+ font-size: 0.875rem;
+ color: #212529;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0, 40, 100, 0.12);
+ border-radius: 3px;
+ box-shadow: 0 0 5px #ccc;
+ z-index: 1000;
+}
+
+.context-menu .context-menu-item {
+ line-height: 1.5;
+ cursor: pointer;
+ min-height: 28px;
+ font-weight: 400;
+ color: #373a3c;
+ text-align: inherit;
+ white-space: nowrap;
+ background: 0 0;
+ border: 0;
+ display: block;
+ width: 100%;
+ padding: 0.25rem 1.5rem;
+ clear: both;
+}
+
+.context-menu .context-menu-item:hover {
+ color: #fff;
+ background-color: #20a0ff;
+ border-color: #20a0ff;
+ text-decoration: none;
+}
diff --git a/frontend/src/metadata/metadata-view/components/context-menu/context-menu.jsx b/frontend/src/metadata/metadata-view/components/context-menu/context-menu.jsx
new file mode 100644
index 00000000000..ed4f9522444
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/context-menu/context-menu.jsx
@@ -0,0 +1,63 @@
+import React, { useRef, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import './context-menu.css';
+
+const ContextMenu = ({ position, options, onOptionClick, visible, onCloseContextMenu }) => {
+ const menuRef = useRef(null);
+
+ useEffect(() => {
+ const handleCloseContextMenu = (event) => {
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
+ onCloseContextMenu();
+ }
+ };
+
+ if (visible) {
+ document.addEventListener('mousedown', handleCloseContextMenu);
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleCloseContextMenu);
+ };
+ }, [visible, onCloseContextMenu]);
+
+ if (!visible) return null;
+
+ return (
+
+ {options.map((option, index) => (
+ - onOptionClick(event, option)}
+ >
+ {option.label}
+
+ ))}
+
+ );
+};
+
+ContextMenu.propTypes = {
+ position: PropTypes.shape({
+ x: PropTypes.number.isRequired,
+ y: PropTypes.number.isRequired,
+ }).isRequired,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ })
+ ).isRequired,
+ onOptionClick: PropTypes.func.isRequired,
+ visible: PropTypes.bool.isRequired,
+};
+
+export default ContextMenu;
diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js
index 13e28159678..c468d6976cd 100644
--- a/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js
+++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js
@@ -409,7 +409,7 @@ class RecordsBody extends Component {
onCellMouseMove: this.onCellMouseMove,
onDragEnter: this.handleDragEnter,
modifyRecord: this.props.modifyRecord,
- onContextMenu: this.props.onFileNameContextMenu
+ onContextMenu: this.props.onContextMenu
};
return this.cellMetaData;
};
@@ -601,7 +601,7 @@ RecordsBody.propTypes = {
openDownloadFilesDialog: PropTypes.func,
cacheDownloadFilesProps: PropTypes.func,
onRowExpand: PropTypes.func,
- onFileNameContextMenu: PropTypes.func,
+ onContextMenu: PropTypes.func,
};
export default RecordsBody;
diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js
index 9962147d6c0..ea1a8e3be19 100644
--- a/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js
+++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js
@@ -15,13 +15,10 @@ import RecordMetrics from '../../../../utils/record-metrics';
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
import { getVisibleBoundaries } from '../../../../utils/viewport';
import { getColOverScanEndIdx, getColOverScanStartIdx } from '../../../../utils/grid';
-import { hideMenu, showMenu } from '../../../../../../components/context-menu/actions';
-import ContextMenu from '../../../../../../components/context-menu/context-menu';
import TextTranslation from '../../../../../../utils/text-translation';
import { Utils } from '../../../../../../utils/utils';
import { siteRoot } from '../../../../../../utils/constants';
-
-const METADATA_RECORD_CONTEXT_MENU = 'metadata-record-context-menu';
+import ContextMenu from '../../../context-menu/context-menu';
class Records extends Component {
@@ -46,11 +43,17 @@ class Records extends Component {
bottomRight: this.initPosition,
},
selectedPosition: this.initPosition,
+ contextMenuPosition: { x: 0, y: 0 },
+ isContextMenuVisible: false,
...initHorizontalScrollState,
};
this.isWindows = isWindowsBrowser();
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() {
@@ -603,18 +606,14 @@ class Records extends Component {
const record = this.props.recordGetter(rowIdx);
const repoID = window.sfMetadataStore.repoId;
+ let url;
if (record._is_dir) {
- let url;
- if (record._parent_dir === '/') {
- url = this.baseURI + record._parent_dir + record._name;
- } else {
- url = this.baseURI + record._parent_dir + '/' + record._name;
- }
- window.open(url, '_blank');
+ url = `${this.baseURI}${record._parent_dir === '/' ? '' : record._parent_dir + '/'}${record._name}`;
} else {
- const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(record._parent_dir + '/' + record._name);
- window.open(url, '_blank');
+ url = `${siteRoot}lib/${repoID}/file${Utils.encodePath(record._parent_dir + '/' + record._name)}`;
}
+
+ window.open(url, '_blank');
};
onOpenParentFolder = (event) => {
@@ -628,73 +627,46 @@ class Records extends Component {
location.href = url;
};
- onMenuItemClick = (operation, obj, event) => {
- hideMenu();
- switch (operation) {
- case 'Open file in new tab':
- this.onOpenInNewTab(event);
- return;
- case 'Open parent folder':
- this.onOpenParentFolder(event);
- return;
- default:
- return;
- }
- };
-
- getMenuList = () => {
- const { OPEN_FILE_IN_NEW_TAB, OPEN_PARENT_FOLDER } = TextTranslation;
- return [OPEN_FILE_IN_NEW_TAB, OPEN_PARENT_FOLDER];
- };
-
- handleContextMenu = (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();
+ onOptionClick = (event, option) => {
+ this.setState({
+ isContextMenuVisible: false,
+ });
- this.setState({ activeDirent: currentObject });
+ const handlers = {
+ openFileInNewTab: this.onOpenInNewTab.bind(this),
+ openParentFolder: this.onOpenParentFolder.bind(this),
+ };
- if (menuList.length === 0) {
- return;
+ const handler = handlers[option.value];
+ if (handler) {
+ handler(event);
}
+ };
- showMenu({
- id: id,
- position: { x, y },
- target: event.target,
- currentObject: currentObject,
- menuList: menuList,
- });
+ onCloseContextMenu = () => {
+ this.setState({ isContextMenuVisible: false });
};
- onFileNameContextMenu = (event, cell) => {
+ onContextMenu = (event, cell) => {
const record = this.props.recordGetter(cell.rowIdx);
if (record._is_dir) {
return;
}
- this.baseURI = event.target.baseURI;
- this.setState({ selectedPosition: cell });
- this.handleContextMenu(event, METADATA_RECORD_CONTEXT_MENU, this.getMenuList());
- };
+ const { clientX, clientY, touches, target } = event;
- getMenuContainerSize = () => {
- if (this.resultContainerRef) {
- const { offsetWidth: width, offsetHeight: height } = this.resultContainerRef;
- return { width, height };
- }
- return { width: 0, height: 0 };
+ // Calculate x and y coordinates
+ const x = (clientX || touches?.[0]?.pageX) - this.resultContainerRef.getBoundingClientRect().left - (this.props.posX || 0);
+ const y = (clientY || touches?.[0]?.pageY) - this.resultContainerRef.getBoundingClientRect().top - (this.props.posY || 0);
+
+ const position = { x, y };
+
+ this.baseURI = target.baseURI;
+ this.setState({
+ selectedPosition: cell,
+ isContextMenuVisible: true,
+ contextMenuPosition: position
+ });
};
renderRecordsBody = ({ containerWidth }) => {
@@ -716,7 +688,7 @@ class Records extends Component {
setRecordsScrollLeft: this.setScrollLeft,
hasSelectedCell: this.hasSelectedCell,
cacheScrollTop: this.storeScrollTop,
- onFileNameContextMenu: this.onFileNameContextMenu,
+ onContextMenu: this.onContextMenu,
};
if (this.props.isGroupView) {
return (
@@ -779,9 +751,11 @@ class Records extends Component {
{this.renderRecordsBody({ containerWidth })}
{this.isWindows && this.isWebkit && (
diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js
index 688826cdfd4..88ca9e5c488 100644
--- a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js
+++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js
@@ -103,6 +103,8 @@ const Cell = React.memo(({
}, []);
const onContextMenu = useCallback((event) => {
+ event.preventDefault();
+ event.stopPropagation();
if (column.idx !== 0) return;
const cell = { idx: column.idx, groupRecordIndex, rowIdx: recordIndex };