From b857a42c9aa88b3ef97f881f302db7cedbb0bcea Mon Sep 17 00:00:00 2001 From: llj Date: Mon, 10 Mar 2025 10:21:14 +0800 Subject: [PATCH] [library content view] fixed the position of the operation menu for folder/file items displayed in the bottom of the page (#7565) --- .../dropdown-menu/item-dropdown-menu.js | 11 +- .../metadata-item-dropdown-menu.js | 296 ++++++++++++++++++ .../src/metadata/metadata-tree-view/folder.js | 2 +- .../src/metadata/metadata-tree-view/view.js | 2 +- 4 files changed, 300 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/dropdown-menu/metadata-item-dropdown-menu.js diff --git a/frontend/src/components/dropdown-menu/item-dropdown-menu.js b/frontend/src/components/dropdown-menu/item-dropdown-menu.js index 35d55031a4..9303e98fa8 100644 --- a/frontend/src/components/dropdown-menu/item-dropdown-menu.js +++ b/frontend/src/components/dropdown-menu/item-dropdown-menu.js @@ -200,7 +200,7 @@ class ItemDropdownMenu extends React.Component { } return ( - + {menuList.map((menuItem, index) => { if (menuItem === 'Divider') { @@ -241,11 +238,7 @@ class ItemDropdownMenu extends React.Component { {menuItem.value} - + {menuItem.subOpListHeader && {menuItem.subOpListHeader}} {menuItem.subOpList.map((item, index) => { if (item == 'Divider') { diff --git a/frontend/src/components/dropdown-menu/metadata-item-dropdown-menu.js b/frontend/src/components/dropdown-menu/metadata-item-dropdown-menu.js new file mode 100644 index 0000000000..35d55031a4 --- /dev/null +++ b/frontend/src/components/dropdown-menu/metadata-item-dropdown-menu.js @@ -0,0 +1,296 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; +import listener from '../context-menu/globalEventListener'; +import { gettext } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; +import ModalPortal from '../modal-portal'; + +import '../../css/item-dropdown-menu.css'; + +const propTypes = { + tagName: PropTypes.string, + item: PropTypes.object.isRequired, + toggleClass: PropTypes.string, + toggleChildren: PropTypes.object, + isHandleContextMenuEvent: PropTypes.bool, + getMenuList: PropTypes.func.isRequired, + onMenuItemClick: PropTypes.func.isRequired, + freezeItem: PropTypes.func, + unfreezeItem: PropTypes.func, + menuStyle: PropTypes.object, + isDisplayFiles: PropTypes.bool, +}; + +class ItemDropdownMenu extends React.Component { + + static defaultProps = { + isHandleContextMenuEvent: true, + menuStyle: {}, + toggleClass: 'sf3-font-more sf3-font' + }; + + constructor(props) { + super(props); + this.state = { + menuList: [], + isItemMenuShow: false, + isSubMenuShown: false, + currentItem: '' + }; + this.dropdownRef = React.createRef(); + this.subMenuDirection = 'right'; + } + + componentDidMount() { + if (this.props.isHandleContextMenuEvent) { + this.listenerId = listener.register(this.onShowMenu, this.onHideMenu); + } + this.setState({ + menuList: this.removeUselessDivider(this.props.getMenuList(this.props.item)) + }); + setTimeout(() => { + if (this.dropdownRef.current) { + this.subMenuDirection = (window.innerWidth - this.dropdownRef.current.getBoundingClientRect().right < 400) ? 'left' : 'right'; + } + }, 1); + } + + UNSAFE_componentWillReceiveProps(nextProps) { // for toolbar item operation + const nextMenuList = this.removeUselessDivider(nextProps.getMenuList(nextProps.item)); + if (nextProps.item.name !== this.props.item.name || this.state.menuList !== nextMenuList) { + this.setState({ menuList: nextMenuList }); + } + } + + componentWillUnmount() { + if (this.props.isHandleContextMenuEvent && this.listenerId) { + listener.unregister(this.listenerId); + } + } + + removeUselessDivider = (menuList) => { + while (menuList && menuList[0] === 'Divider') { + menuList.shift(); + } + return menuList; + }; + + onShowMenu = () => { + // nothing todo + }; + + onHideMenu = () => { + if (this.state.isItemMenuShow) { + this.setState({ isItemMenuShow: false }); + if (typeof(this.props.unfreezeItem) === 'function') { + this.props.unfreezeItem(); + } + } + }; + + onDropdownToggleKeyDown = (e) => { + if (e.key == 'Enter' || e.key == 'Space') { + this.onDropdownToggleClick(e); + } + }; + + onDropdownToggleClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + + this.toggleOperationMenu(); + }; + + toggleOperationMenu = () => { + this.setState( + { isItemMenuShow: !this.state.isItemMenuShow }, + () => { + if (this.state.isItemMenuShow && typeof(this.props.freezeItem) === 'function') { + this.props.freezeItem(); + } else if (!this.state.isItemMenuShow && typeof(this.props.unfreezeItem) === 'function') { + this.props.unfreezeItem(); + } + } + ); + }; + + onMenuItemKeyDown = (e) => { + if (e.key == 'Enter' || e.key == 'Space') { + this.onMenuItemClick(e); + } + }; + + onMenuItemClick = (event) => { + let operation = Utils.getEventData(event, 'toggle') ?? event.currentTarget.getAttribute('data-toggle'); + let item = this.props.item; + if (typeof(this.props.unfreezeItem) === 'function') { + this.props.unfreezeItem(); + } + this.props.onMenuItemClick(operation, event, item); + this.setState({ isItemMenuShow: false }); + }; + + onDropDownMouseMove = () => { + if (this.state.isSubMenuShown) { + this.setState({ + isSubMenuShown: false + }); + } + }; + + toggleSubMenu = (e) => { + e.stopPropagation(); + this.setState({ + isSubMenuShown: !this.state.isSubMenuShown + }); + }; + + toggleSubMenuShown = (item) => { + this.setState({ + isSubMenuShown: true, + currentItem: item.key + }); + }; + + render() { + let menuList = this.state.menuList; + let { toggleClass, toggleChildren, tagName, menuStyle } = this.props; + toggleClass = 'sf-dropdown-toggle ' + toggleClass; + + if (!menuList.length) { + return ''; + } + + if (tagName && tagName === 'button') { + return ( + + + {toggleChildren} + + + {menuList.map((menuItem, index) => { + if (menuItem === 'Divider') { + return ; + } else { + return ( + + {menuItem.value} + + ); + } + })} + + + ); + } + + return ( + + + + + {menuList.map((menuItem, index) => { + if (menuItem === 'Divider') { + return ; + } else if (menuItem.subOpList) { + return ( + {e.stopPropagation();}} + > + + {menuItem.value} + + + + {menuItem.subOpListHeader && {menuItem.subOpListHeader}} + {menuItem.subOpList.map((item, index) => { + if (item == 'Divider') { + return ; + } else { + return ( + + {item.icon_dom || null} + {item.value} + + ); + } + })} + + + ); + } else { + return ( + + {menuItem.key === 'Display files' && this.props.isDisplayFiles && ( + + )} + {menuItem.icon_dom || null} + {menuItem.value} + + ); + } + })} + + + + ); + } +} + +ItemDropdownMenu.propTypes = propTypes; + +export default ItemDropdownMenu; diff --git a/frontend/src/metadata/metadata-tree-view/folder.js b/frontend/src/metadata/metadata-tree-view/folder.js index d9450b9777..c5b4d376d2 100644 --- a/frontend/src/metadata/metadata-tree-view/folder.js +++ b/frontend/src/metadata/metadata-tree-view/folder.js @@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import ViewItem from './view'; -import ItemDropdownMenu from '../../components/dropdown-menu/item-dropdown-menu'; +import ItemDropdownMenu from '../../components/dropdown-menu/metadata-item-dropdown-menu'; import toaster from '../../components/toast'; import NewView from './new-view'; import InlineNameEditor from './inline-name-editor'; diff --git a/frontend/src/metadata/metadata-tree-view/view.js b/frontend/src/metadata/metadata-tree-view/view.js index efc91ef440..3420c129b9 100644 --- a/frontend/src/metadata/metadata-tree-view/view.js +++ b/frontend/src/metadata/metadata-tree-view/view.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { gettext } from '../../utils/constants'; import Icon from '../../components/icon'; -import ItemDropdownMenu from '../../components/dropdown-menu/item-dropdown-menu'; +import ItemDropdownMenu from '../../components/dropdown-menu/metadata-item-dropdown-menu'; import toaster from '../../components/toast'; import InlineNameEditor from './inline-name-editor'; import { Utils, isMobile } from '../../utils/utils';