From 3622729e5993e52c68e9cf40f9886dd143ca7c28 Mon Sep 17 00:00:00 2001 From: zhouwenxuan Date: Wed, 11 Dec 2024 18:02:53 +0800 Subject: [PATCH] optimize code --- .../context-menu/index.css | 0 .../context-menu/index.js | 64 +++---- frontend/src/metadata/utils/row/core.js | 31 +++- frontend/src/metadata/utils/row/index.js | 2 + frontend/src/metadata/views/gallery/main.js | 41 ++++- .../views/kanban/boards/board/card/index.js | 6 +- .../views/kanban/boards/board/index.js | 6 +- .../src/metadata/views/kanban/boards/index.js | 134 ++++++++------- .../views/kanban/context-menu/index.css | 6 - .../views/kanban/context-menu/index.js | 160 ------------------ frontend/src/metadata/views/kanban/index.js | 26 +-- .../src/metadata/views/kanban/rename/index.js | 3 +- .../views/table/context-menu/index.js | 36 +--- 13 files changed, 189 insertions(+), 326 deletions(-) rename frontend/src/metadata/{views/gallery => components}/context-menu/index.css (100%) rename frontend/src/metadata/{views/gallery => components}/context-menu/index.js (69%) delete mode 100644 frontend/src/metadata/views/kanban/context-menu/index.css delete mode 100644 frontend/src/metadata/views/kanban/context-menu/index.js diff --git a/frontend/src/metadata/views/gallery/context-menu/index.css b/frontend/src/metadata/components/context-menu/index.css similarity index 100% rename from frontend/src/metadata/views/gallery/context-menu/index.css rename to frontend/src/metadata/components/context-menu/index.css diff --git a/frontend/src/metadata/views/gallery/context-menu/index.js b/frontend/src/metadata/components/context-menu/index.js similarity index 69% rename from frontend/src/metadata/views/gallery/context-menu/index.js rename to frontend/src/metadata/components/context-menu/index.js index 6660ca64484..d53621bc2bc 100644 --- a/frontend/src/metadata/views/gallery/context-menu/index.js +++ b/frontend/src/metadata/components/context-menu/index.js @@ -1,50 +1,18 @@ -import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { gettext } from '../../../../utils/constants'; import './index.css'; -const OPERATION = { - DOWNLOAD: 'download', - DELETE: 'delete', -}; - -const ContextMenu = ({ getContentRect, getContainerRect, onDownload, onDelete }) => { +const ContextMenu = ({ options, getContainerRect, getContentRect, onOptionClick, validTargets }) => { const menuRef = useRef(null); const [visible, setVisible] = useState(false); const [position, setPosition] = useState({ top: 0, left: 0 }); - const options = useMemo(() => { - if (!visible) return []; - return [ - { value: OPERATION.DOWNLOAD, label: gettext('Download') }, - { value: OPERATION.DELETE, label: gettext('Delete') } - ]; - }, [visible]); - const handleHide = useCallback((event) => { if (menuRef.current && !menuRef.current.contains(event.target)) { setVisible(false); } }, [menuRef]); - const handleOptionClick = useCallback((event, option) => { - event.stopPropagation(); - switch (option.value) { - case OPERATION.DOWNLOAD: { - onDownload && onDownload(); - break; - } - case OPERATION.DELETE: { - onDelete && onDelete(); - break; - } - default: { - break; - } - } - setVisible(false); - }, [onDownload, onDelete]); - const getMenuPosition = useCallback((x = 0, y = 0) => { let menuStyles = { top: y, @@ -72,17 +40,22 @@ const ContextMenu = ({ getContentRect, getContainerRect, onDownload, onDelete }) return menuStyles; }, [getContentRect, getContainerRect]); + const handleOptionClick = useCallback((event, option) => { + event.stopPropagation(); + onOptionClick(option); + setVisible(false); + }, [onOptionClick]); + useEffect(() => { const handleShow = (event) => { event.preventDefault(); if (menuRef.current && menuRef.current.contains(event.target)) return; - if (event.target.tagName.toLowerCase() !== 'img') { + if (validTargets && !validTargets.some(target => event.target.closest(target))) { return; } setVisible(true); - const position = getMenuPosition(event.clientX, event.clientY); setPosition(position); }; @@ -92,8 +65,7 @@ const ContextMenu = ({ getContentRect, getContainerRect, onDownload, onDelete }) return () => { document.removeEventListener('contextmenu', handleShow); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [getMenuPosition, validTargets]); useEffect(() => { if (visible) { @@ -113,14 +85,14 @@ const ContextMenu = ({ getContentRect, getContainerRect, onDownload, onDelete }) return (
{options.map((option, index) => ( @@ -130,10 +102,14 @@ const ContextMenu = ({ getContentRect, getContainerRect, onDownload, onDelete }) }; ContextMenu.propTypes = { - getContentRect: PropTypes.func.isRequired, + options: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + })).isRequired, getContainerRect: PropTypes.func.isRequired, - onDownload: PropTypes.func, - onDelete: PropTypes.func, + getContentRect: PropTypes.func.isRequired, + onOptionClick: PropTypes.func.isRequired, + validTargets: PropTypes.array, }; export default ContextMenu; diff --git a/frontend/src/metadata/utils/row/core.js b/frontend/src/metadata/utils/row/core.js index f69e806ee6d..83aa11a2042 100644 --- a/frontend/src/metadata/utils/row/core.js +++ b/frontend/src/metadata/utils/row/core.js @@ -1,4 +1,7 @@ +import { siteRoot } from '../../../utils/constants'; +import { Utils } from '../../../utils/utils'; import { PRIVATE_COLUMN_KEY } from '../../constants'; +import { getFileNameFromRecord, getParentDirFromRecord } from '../cell'; import { getTableById } from '../table'; /** @@ -26,7 +29,7 @@ const updateTableRowsWithRowsData = (tables, tableId, recordsData = []) => { }); }; -export const checkIsDir = (record) => { +const checkIsDir = (record) => { if (!record) return false; const isDir = record[PRIVATE_COLUMN_KEY.IS_DIR]; if (typeof isDir === 'string') { @@ -35,7 +38,33 @@ export const checkIsDir = (record) => { return isDir; }; +const openInNewTab = (record) => { + const repoID = window.sfMetadataStore.repoId; + const isDir = checkIsDir(record); + const parentDir = getParentDirFromRecord(record); + const name = getFileNameFromRecord(record); + const url = isDir + ? window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, name)) + : `${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, name))}`; + + window.open(url, '_blank'); +}; + +const openParentFolder = (record) => { + let parentDir = getParentDirFromRecord(record); + + if (window.location.pathname.endsWith('/')) { + parentDir = parentDir.slice(1); + } + + const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir); + window.open(url, '_blank'); +}; + export { isTableRows, updateTableRowsWithRowsData, + checkIsDir, + openInNewTab, + openParentFolder, }; diff --git a/frontend/src/metadata/utils/row/index.js b/frontend/src/metadata/utils/row/index.js index af843e8cd58..34ada026680 100644 --- a/frontend/src/metadata/utils/row/index.js +++ b/frontend/src/metadata/utils/row/index.js @@ -2,4 +2,6 @@ export { checkIsDir, isTableRows, updateTableRowsWithRowsData, + openInNewTab, + openParentFolder, } from './core'; diff --git a/frontend/src/metadata/views/gallery/main.js b/frontend/src/metadata/views/gallery/main.js index 747b4ea9def..a98d5598b7c 100644 --- a/frontend/src/metadata/views/gallery/main.js +++ b/frontend/src/metadata/views/gallery/main.js @@ -5,17 +5,17 @@ import metadataAPI from '../../api'; import URLDecorator from '../../../utils/url-decorator'; import toaster from '../../../components/toast'; import Content from './content'; -import ContextMenu from './context-menu'; import ImageDialog from '../../../components/dialog/image-dialog'; import ZipDownloadDialog from '../../../components/dialog/zip-download-dialog'; import ModalPortal from '../../../components/modal-portal'; import { useMetadataView } from '../../hooks/metadata-view'; import { Utils } from '../../../utils/utils'; import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord } from '../../utils/cell'; -import { siteRoot, fileServerRoot, useGoFileserver, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants'; +import { siteRoot, fileServerRoot, useGoFileserver, thumbnailSizeForGrid, thumbnailSizeForOriginal, gettext } from '../../../utils/constants'; import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants'; import { getRowById } from '../../utils/table'; import { getEventClassName } from '../../utils/common'; +import ContextMenu from '../../components/context-menu'; import './index.css'; @@ -340,6 +340,34 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore }) => { } }, [handleImageSelection, updateSelectedImage]); + const options = useMemo(() => { + return [ + { value: 'download', label: gettext('Download') }, + { value: 'delete', label: gettext('Delete') } + ]; + }, []); + + const handleOptionClick = useCallback(option => { + switch (option.value) { + case 'download': + handleDownload(); + break; + case 'delete': + handleDelete(); + break; + default: + break; + } + }, [handleDownload, handleDelete]); + + const getContainerRect = useCallback(() => { + return containerRef.current.getBoundingClientRect(); + }, []); + + const getContentRect = useCallback(() => { + return containerRef.current.getBoundingClientRect(); + }, []); + return ( <>
{ )}
containerRef.current.getBoundingClientRect()} - getContainerRect={() => containerRef.current.getBoundingClientRect()} - onDownload={handleDownload} - onDelete={handleDelete} + options={options} + onOptionClick={handleOptionClick} + getContainerRect={getContainerRect} + getContentRect={getContentRect} + validTargets={['.metadata-gallery-image-item', '.metadata-gallery-grid-image']} /> {isImagePopupOpen && ( diff --git a/frontend/src/metadata/views/kanban/boards/board/card/index.js b/frontend/src/metadata/views/kanban/boards/board/card/index.js index d6035546e9f..f6bf966557e 100644 --- a/frontend/src/metadata/views/kanban/boards/board/card/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/card/index.js @@ -17,7 +17,7 @@ const Card = ({ displayColumns, onOpenFile, onSelectCard, - onRightClick, + onContextMenu, }) => { const titleValue = getCellValueByColumn(record, titleColumn); @@ -42,7 +42,7 @@ const Card = ({ data-id={record._id} className={classnames('sf-metadata-kanban-card', { 'selected': isSelected })} onClick={handleClickCard} - onContextMenu={onRightClick} + onContextMenu={onContextMenu} > {titleColumn && (
@@ -84,7 +84,7 @@ Card.propTypes = { displayColumns: PropTypes.array, onOpenFile: PropTypes.func.isRequired, onSelectCard: PropTypes.func.isRequired, - onRightClick: PropTypes.func.isRequired, + onContextMenu: PropTypes.func.isRequired, }; export default Card; diff --git a/frontend/src/metadata/views/kanban/boards/board/index.js b/frontend/src/metadata/views/kanban/boards/board/index.js index 77e8a687e24..f5253a4f484 100644 --- a/frontend/src/metadata/views/kanban/boards/board/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/index.js @@ -28,7 +28,7 @@ const Board = ({ onOpenFile, onSelectCard, updateDragging, - onRightClick, + onContextMenu, }) => { const [isDraggingOver, setDraggingOver] = useState(false); const boardName = useMemo(() => `sf_metadata_kanban_board_${board.key}`, [board]); @@ -94,7 +94,7 @@ const Board = ({ displayColumns={displayColumns} onOpenFile={onOpenFile} onSelectCard={onSelectCard} - onRightClick={(e) => onRightClick(e, recordId)} + onContextMenu={(e) => onContextMenu(e, recordId)} /> ); if (readonly) return CardElement; @@ -127,7 +127,7 @@ Board.propTypes = { onOpenFile: PropTypes.func.isRequired, onSelectCard: PropTypes.func.isRequired, updateDragging: PropTypes.func.isRequired, - onRightClick: PropTypes.func, + onContextMenu: PropTypes.func, }; export default Board; diff --git a/frontend/src/metadata/views/kanban/boards/index.js b/frontend/src/metadata/views/kanban/boards/index.js index 916b5befadb..386cb3263fe 100644 --- a/frontend/src/metadata/views/kanban/boards/index.js +++ b/frontend/src/metadata/views/kanban/boards/index.js @@ -6,22 +6,23 @@ import { useMetadataView } from '../../../hooks/metadata-view'; import { useCollaborators } from '../../../hooks'; import { CellType, KANBAN_SETTINGS_KEYS, PRIVATE_COLUMN_KEY, UNCATEGORIZED } from '../../../constants'; import { COLUMN_DATA_OPERATION_TYPE } from '../../../store/operations'; -import { gettext, siteRoot } from '../../../../utils/constants'; +import { gettext } from '../../../../utils/constants'; import { checkIsPredefinedOption, getCellValueByColumn, isValidCellValue, getRecordIdFromRecord, getFileNameFromRecord, getParentDirFromRecord } from '../../../utils/cell'; -import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../../../utils/column'; +import { getColumnOptions, getColumnOriginName } from '../../../utils/column'; +import { openFile } from '../../../utils/open-file'; +import { checkIsDir, openInNewTab, openParentFolder } from '../../../utils/row'; +import URLDecorator from '../../../../utils/url-decorator'; +import { Utils, validateName } from '../../../../utils/utils'; +import { getRowById } from '../../../utils/table'; import AddBoard from '../add-board'; import EmptyTip from '../../../../components/empty-tip'; import Board from './board'; import ImagePreviewer from '../../../components/cell-formatter/image-previewer'; -import { openFile } from '../../../utils/open-file'; -import { checkIsDir } from '../../../utils/row'; -import ContextMenu from '../context-menu'; -import URLDecorator from '../../../../utils/url-decorator'; -import { Utils, validateName } from '../../../../utils/utils'; import toaster from '../../../../components/toast'; import Rename from '../rename'; +import ContextMenu from '../../../components/context-menu'; import './index.css'; @@ -226,47 +227,15 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { setDragging(isDragging); }, []); - const handleRightClick = useCallback((event, recordId) => { + const onContextMenu = useCallback((event, recordId) => { event.preventDefault(); setSelectedCard(recordId); }, []); - const handleOpenInNewTab = useCallback(() => { - if (!selectedCard) return; - - const record = metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard); - if (!record) return; - - const isDir = checkIsDir(record); - const parentDir = getParentDirFromRecord(record); - const name = getFileNameFromRecord(record); - const url = isDir - ? window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, name)) - : `${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, name))}`; - - window.open(url, '_blank'); - }, [selectedCard, metadata.rows, repoID]); - - const handleOpenParentFolder = useCallback(() => { - if (!selectedCard) return; - - const record = metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard); - if (!record) return; - - let parentDir = getParentDirFromRecord(record); - if (!parentDir) return; - if (window.location.pathname.endsWith('/')) { - parentDir = parentDir.slice(1); - } - - const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir); - window.open(url, '_blank'); - }, [selectedCard, metadata.rows]); - const handleDownload = useCallback(() => { if (!selectedCard) return; - const record = metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard); + const record = getRowById(metadata, selectedCard); if (!record) return; const path = getParentDirFromRecord(record); @@ -275,12 +244,12 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { const url = URLDecorator.getUrl({ type: 'download_file_url', repoID: repoID, path: direntPath }); location.href = url; return; - }, [selectedCard, metadata.rows, repoID]); + }, [selectedCard, metadata, repoID]); const handleDelete = useCallback(() => { if (!selectedCard) return; - const record = metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard); + const record = getRowById(metadata, selectedCard); if (!record) return; const parentDir = getParentDirFromRecord(record); @@ -302,7 +271,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { toaster.success(msg.replace('{name}', fileNames[0])); }, }); - }, [store, metadata.rows, selectedCard, deleteFilesCallback]); + }, [store, metadata, selectedCard, deleteFilesCallback]); const handleRename = useCallback(() => { setIsRenameDialogShow(true); @@ -311,7 +280,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { const handleSubmitRename = useCallback((newName) => { if (!selectedCard) return; - const record = metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard); + const record = getRowById(metadata, selectedCard); if (!record) return; const { isValid, errMessage } = validateName(newName); @@ -327,16 +296,14 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { return; } - const column = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.FILE_NAME); const rowId = selectedCard; const oldName = getFileNameFromRecord(record); const path = Utils.joinPath(parentDir, oldName); const newPath = Utils.joinPath(parentDir, newName); const rowIds = [rowId]; - const updates = { [column.key]: newName }; - const oldValue = getCellValueByColumn(record, column); - const oldRowData = { [column.key]: oldValue }; + const updates = { [PRIVATE_COLUMN_KEY.FILE_NAME]: newName }; + const oldRowData = { [PRIVATE_COLUMN_KEY.FILE_NAME]: oldName }; store.modifyRecords(rowIds, { [rowId]: updates }, { [rowId]: updates }, { [rowId]: oldRowData }, { [rowId]: oldRowData }, false, true, { fail_callback: (error) => { @@ -348,7 +315,7 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { setIsRenameDialogShow(false); }, }); - }, [store, metadata.columns, metadata.rows, selectedCard, renameFileCallback]); + }, [store, metadata, selectedCard, renameFileCallback]); useEffect(() => { if (!isDirentDetailShow) { @@ -356,11 +323,62 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { } }, [isDirentDetailShow]); - const selectedRecord = useMemo(() => metadata.rows.find(row => getRecordIdFromRecord(row) === selectedCard), [metadata.rows, selectedCard]); + const selectedRecord = useMemo(() => getRowById(metadata, selectedCard), [metadata, selectedCard]); const isDir = useMemo(() => selectedRecord && checkIsDir(selectedRecord), [selectedRecord]); const oldName = useMemo(() => selectedRecord && getFileNameFromRecord(selectedRecord), [selectedRecord]); const isEmpty = boards.length === 0; + const options = useMemo(() => { + return [ + { value: 'open-in-new-tab', label: isDir ? gettext('Open folder in new tab') : gettext('Open file in new tab') }, + { value: 'open-parent-folder', label: gettext('Open parent folder') }, + { value: 'download', label: gettext('Download') }, + { value: 'delete', label: gettext('Delete') }, + { value: 'rename', label: gettext('Rename') }, + ]; + }, [isDir]); + + const handleOptionClick = useCallback((option) => { + if (!selectedCard) return; + + const record = getRowById(metadata, selectedCard); + if (!record) return; + + switch (option.value) { + case 'open-in-new-tab': { + openInNewTab(record); + break; + } + case 'open-parent-folder': { + openParentFolder(record); + break; + } + case 'download': { + handleDownload(); + break; + } + case 'delete': { + handleDelete(); + break; + } + case 'rename': { + handleRename(); + break; + } + default: { + break; + } + } + }, [metadata, selectedCard, handleDownload, handleDelete, handleRename]); + + const getContainerRect = useCallback(() => { + return containerRef.current.getBoundingClientRect(); + }, []); + + const getContentRect = useCallback(() => { + return containerRef.current.getBoundingClientRect(); + }, []); + return ( <>
{ onOpenFile={onOpenFile} onSelectCard={onSelectCard} updateDragging={updateDragging} - onRightClick={handleRightClick} + onContextMenu={onContextMenu} /> ); })} @@ -415,13 +433,11 @@ const Boards = ({ modifyRecord, modifyColumnData, onCloseSettings }) => { /> )} containerRef.current.getBoundingClientRect()} - onOpenInNewTab={handleOpenInNewTab} - onOpenParentFolder={handleOpenParentFolder} - onDownload={handleDownload} - onDelete={handleDelete} - onRename={handleRename} + options={options} + getContainerRect={getContainerRect} + getContentRect={getContentRect} + onOptionClick={handleOptionClick} + validTargets={['.sf-metadata-kanban-card']} /> {isRenameDialogShow && ( diff --git a/frontend/src/metadata/views/kanban/context-menu/index.css b/frontend/src/metadata/views/kanban/context-menu/index.css deleted file mode 100644 index 1159fae5c2c..00000000000 --- a/frontend/src/metadata/views/kanban/context-menu/index.css +++ /dev/null @@ -1,6 +0,0 @@ -.sf-metadata-contextmenu { - display: block; - opacity: 1; - box-shadow: 0 0 5px #ccc; - position: fixed; -} diff --git a/frontend/src/metadata/views/kanban/context-menu/index.js b/frontend/src/metadata/views/kanban/context-menu/index.js deleted file mode 100644 index d9f66f8bc56..00000000000 --- a/frontend/src/metadata/views/kanban/context-menu/index.js +++ /dev/null @@ -1,160 +0,0 @@ -import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../../../utils/constants'; -import './index.css'; - -const OPERATION = { - OPEN_IN_NEW_TAB: 'open-in-new-tab', - OPEN_PARENT_FOLDER: 'open-parent-folder', - DOWNLOAD: 'download', - DELETE: 'delete', - RENAME: 'rename', -}; - -const ContextMenu = ({ isDir, getContainerRect, onOpenInNewTab, onOpenParentFolder, onDownload, onDelete, onRename }) => { - const menuRef = useRef(null); - const [visible, setVisible] = useState(false); - const [position, setPosition] = useState({ top: 0, left: 0 }); - - const options = useMemo(() => { - if (!visible) return []; - return [ - { value: OPERATION.OPEN_IN_NEW_TAB, label: isDir ? gettext('Open folder in new tab') : gettext('Open file in new tab') }, - { value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder') }, - { value: OPERATION.DOWNLOAD, label: gettext('Download') }, - { value: OPERATION.DELETE, label: gettext('Delete') }, - { value: OPERATION.RENAME, label: gettext('Rename') }, - ]; - }, [visible, isDir]); - - const handleHide = useCallback((event) => { - if (menuRef.current && !menuRef.current.contains(event.target)) { - setVisible(false); - } - }, [menuRef]); - - const handleOptionClick = useCallback((event, option) => { - event.stopPropagation(); - switch (option.value) { - case OPERATION.OPEN_IN_NEW_TAB: { - onOpenInNewTab && onOpenInNewTab(); - break; - } - case OPERATION.OPEN_PARENT_FOLDER: { - onOpenParentFolder && onOpenParentFolder(); - break; - } - case OPERATION.DOWNLOAD: { - onDownload && onDownload(); - break; - } - case OPERATION.DELETE: { - onDelete && onDelete(); - break; - } - case OPERATION.RENAME: { - onRename && onRename(); - break; - } - default: { - break; - } - } - setVisible(false); - }, [onDownload, onDelete, onRename]); - - const getMenuPosition = useCallback((x = 0, y = 0) => { - let menuStyles = { - top: y, - left: x - }; - if (!menuRef.current) return menuStyles; - const rect = menuRef.current.getBoundingClientRect(); - const { right: innerWidth, bottom: innerHeight } = getContainerRect(); - - if (y + rect.height > innerHeight - 10) { - menuStyles.top -= rect.height; - } - if (x + rect.width > innerWidth) { - menuStyles.left -= rect.width; - } - if (menuStyles.top < 0) { - menuStyles.top = rect.bottom > innerHeight ? (innerHeight - 10 - rect.height) / 2 : 0; - } - if (menuStyles.left < 0) { - menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0; - } - return menuStyles; - }, [getContainerRect]); - - useEffect(() => { - const handleShow = (event) => { - event.preventDefault(); - - if (menuRef.current && menuRef.current.contains(event.target)) return; - - let target = event.target; - while (target && target.tagName.toLowerCase() !== 'article') { - target = target.parentElement; - } - - if (!target) return; - - setVisible(true); - - const position = getMenuPosition(event.clientX, event.clientY); - setPosition(position); - }; - - document.addEventListener('contextmenu', handleShow); - - return () => { - document.removeEventListener('contextmenu', handleShow); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - 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 ( -
- {options.map((option, index) => ( - - ))} -
- ); -}; - -ContextMenu.propTypes = { - isDir: PropTypes.bool.isRequired, - getContainerRect: PropTypes.func.isRequired, - onOpenInNewTab: PropTypes.func.isRequired, - onOpenParentFolder: PropTypes.func.isRequired, - onDownload: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, -}; - -export default ContextMenu; diff --git a/frontend/src/metadata/views/kanban/index.js b/frontend/src/metadata/views/kanban/index.js index b91e921f96b..362e8820723 100644 --- a/frontend/src/metadata/views/kanban/index.js +++ b/frontend/src/metadata/views/kanban/index.js @@ -54,18 +54,20 @@ const Kanban = () => { }, [isShowSettings]); return ( -
- -
- {isShowSettings && ( - - )} +
+
+ +
+ {isShowSettings && ( + + )} +
); diff --git a/frontend/src/metadata/views/kanban/rename/index.js b/frontend/src/metadata/views/kanban/rename/index.js index 9cf96caac43..3faaf8f297b 100644 --- a/frontend/src/metadata/views/kanban/rename/index.js +++ b/frontend/src/metadata/views/kanban/rename/index.js @@ -3,6 +3,7 @@ import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Alert } from import PropTypes from 'prop-types'; import { gettext } from '../../../../utils/constants'; import { validateName } from '../../../../utils/utils'; +import { isEnter } from '../../../utils/hotkey'; const Rename = ({ isDir, oldName, onSubmit, onCancel }) => { const [newName, setNewName] = useState(''); @@ -27,7 +28,7 @@ const Rename = ({ isDir, oldName, onSubmit, onCancel }) => { }; const handleKeyDown = (e) => { - if (e.key === 'Enter') { + if (isEnter(e)) { handleSubmit(); } }; diff --git a/frontend/src/metadata/views/table/context-menu/index.js b/frontend/src/metadata/views/table/context-menu/index.js index 34bbfd70f99..f88e33d9a0d 100644 --- a/frontend/src/metadata/views/table/context-menu/index.js +++ b/frontend/src/metadata/views/table/context-menu/index.js @@ -1,11 +1,11 @@ import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import toaster from '../../../../components/toast'; -import { gettext, siteRoot } from '../../../../utils/constants'; +import { gettext } from '../../../../utils/constants'; import { Utils } from '../../../../utils/utils'; import { useMetadataView } from '../../../hooks/metadata-view'; import { getColumnByKey, isNameColumn } from '../../../utils/column'; -import { checkIsDir } from '../../../utils/row'; +import { checkIsDir, openInNewTab, openParentFolder } from '../../../utils/row'; import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants'; import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord, getRecordIdFromRecord, @@ -170,32 +170,6 @@ const ContextMenu = (props) => { } }, [menuRef, visible]); - const onOpenFileInNewTab = useCallback((record) => { - const repoID = window.sfMetadataStore.repoId; - const isFolder = checkIsDir(record); - const parentDir = getParentDirFromRecord(record); - const fileName = getFileNameFromRecord(record); - - const url = isFolder ? - window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName)) : - `${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, fileName))}`; - - window.open(url, '_blank'); - }, []); - - const onOpenParentFolder = useCallback((event, record) => { - event.preventDefault(); - event.stopPropagation(); - let parentDir = getParentDirFromRecord(record); - - if (window.location.pathname.endsWith('/')) { - parentDir = parentDir.slice(1); - } - - const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir); - window.open(url, '_blank'); - }, []); - const generateDescription = useCallback((record) => { const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION; let path = ''; @@ -296,13 +270,13 @@ const ContextMenu = (props) => { case OPERATION.OPEN_IN_NEW_TAB: { const { record } = option; if (!record) break; - onOpenFileInNewTab(record); + openInNewTab(record); break; } case OPERATION.OPEN_PARENT_FOLDER: { const { record } = option; if (!record) break; - onOpenParentFolder(event, record); + openParentFolder(record); break; } case OPERATION.COPY_SELECTED: { @@ -374,7 +348,7 @@ const ContextMenu = (props) => { } } setVisible(false); - }, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord]); + }, [onCopySelected, onClearSelected, generateDescription, imageCaption, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord]); const getMenuPosition = useCallback((x = 0, y = 0) => { let menuStyles = {