diff --git a/src/components/map-projects/Controls.jsx b/src/components/map-projects/Controls.jsx index 8956212..8560c7b 100644 --- a/src/components/map-projects/Controls.jsx +++ b/src/components/map-projects/Controls.jsx @@ -10,6 +10,8 @@ import Menu from '@mui/material/Menu'; import Divider from '@mui/material/Divider'; import ListItemIcon from '@mui/material/ListItemIcon'; import CircularProgress from '@mui/material/CircularProgress' +import DoubleArrowIcon from '@mui/icons-material/DoubleArrow'; +import AutoMatchIcon from '@mui/icons-material/MotionPhotosAutoOutlined'; import DownloadIcon from '@mui/icons-material/Download'; import TimelineIcon from '@mui/icons-material/Timeline'; import SettingsIcon from '@mui/icons-material/Settings'; @@ -20,7 +22,9 @@ import JSONIcon from '@mui/icons-material/DataObject'; import MoreVertIcon from '@mui/icons-material/MoreVert'; import { copyToClipboard } from '../../common/utils' import RepoIcon from '../repos/RepoIcon' -import { splitSecondaryActionsByVisibility } from './controlsLayout' +import { getToolbarLayout } from './controlsLayout' + +const SHRINKABLE_MIN_WIDTH = 48 const IkonButton = ({title, icon, onClick, color, disabled, id}) => { return ( @@ -42,7 +46,7 @@ const IkonButton = ({title, icon, onClick, color, disabled, id}) => { ) } -const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, onImport, importResponse, onDownloadImportReport, onProjectLogsClick, isProjectsLogOpen, configure, setConfigure, isCoreUser, loadingMatches, onCopyClick}) => { +const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, onImport, importResponse, onDownloadImportReport, onProjectLogsClick, isProjectsLogOpen, configure, setConfigure, isCoreUser, loadingMatches, onCopyClick, autoMatch}) => { const { t } = useTranslation(); const toolbarRef = React.useRef(null) const overflowButtonRef = React.useRef(null) @@ -56,20 +60,44 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, const hasPersistentOverflowActions = Boolean(project?.id) React.useEffect(() => { - const node = toolbarRef.current + const node = toolbarRef.current?.parentElement if (!node) return undefined + const sibling = toolbarRef.current.previousElementSibling + + const getOuterWidth = element => { + const style = window.getComputedStyle(element) + return element.getBoundingClientRect().width + (parseFloat(style.marginLeft) || 0) + (parseFloat(style.marginRight) || 0) + } const updateToolbarWidth = () => { - const nextWidth = node.getBoundingClientRect?.().width - setToolbarWidth(nextWidth || Number.POSITIVE_INFINITY) + const headerWidth = node.getBoundingClientRect?.().width + if (!headerWidth) { + setToolbarWidth(Number.POSITIVE_INFINITY) + return + } + const reservedWidth = sibling ? + Array.from(sibling.children).reduce((sum, child) => sum + (child.dataset.shrinkable ? SHRINKABLE_MIN_WIDTH : getOuterWidth(child)), 0) : + 0 + setToolbarWidth(headerWidth - reservedWidth) } updateToolbarWidth() if (typeof ResizeObserver !== 'undefined') { - const observer = new ResizeObserver(updateToolbarWidth) - observer.observe(node) - return () => observer.disconnect() + const resizeObserver = new ResizeObserver(updateToolbarWidth) + const observeChildren = () => { + resizeObserver.disconnect() + resizeObserver.observe(node) + Array.from(sibling?.children || []).forEach(child => resizeObserver.observe(child)) + } + observeChildren() + const mutationObserver = typeof MutationObserver !== 'undefined' && sibling ? + new MutationObserver(() => { observeChildren(); updateToolbarWidth() }) : null + mutationObserver?.observe(sibling, {childList: true}) + return () => { + resizeObserver.disconnect() + mutationObserver?.disconnect() + } } window.addEventListener('resize', updateToolbarWidth) @@ -110,14 +138,24 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, } } - const { overflowActionKeys, visibleActionKeys } = splitSecondaryActionsByVisibility({ - toolbarWidth, - hasOverflowItems: hasPersistentOverflowActions - }) + const hasActions = Boolean(file?.name) + const { autoMatch: autoMatchMode, saveInMenu, overflowActionKeys, visibleActionKeys } = hasActions ? + getToolbarLayout({ + toolbarWidth, + hasOverflowItems: hasPersistentOverflowActions, + hasAutoMatch: Boolean(autoMatch) + }) : + {autoMatch: 'full', saveInMenu: false, overflowActionKeys: [], visibleActionKeys: []} const visibleSecondaryActions = visibleActionKeys.map(key => actionsByKey[key]) const overflowSecondaryActions = overflowActionKeys.map(key => actionsByKey[key]) - const hasOverflowActions = hasPersistentOverflowActions || overflowSecondaryActions.length > 0 + const autoMatchInMenu = Boolean(autoMatch) && autoMatchMode === 'menu' + const hasOverflowActions = hasActions && (hasPersistentOverflowActions || overflowSecondaryActions.length > 0 || saveInMenu || autoMatchInMenu) + const overflowMenuActions = [ + ...(autoMatchInMenu ? [{key: 'autoMatch', icon: , title: autoMatch.label, disabled: autoMatch.disabled || autoMatch.loading, onClick: autoMatch.onClick}] : []), + ...overflowSecondaryActions, + ...(saveInMenu ? [actionsByKey.save] : []) + ] const closeDownloadMenu = () => setDownloadAnchorEl(null) const closeOverflowMenu = () => setOverflowAnchorEl(null) @@ -134,8 +172,43 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, } return ( - -
+ +
+ { + autoMatch && !autoMatchInMenu && (autoMatchMode === 'compact' ? + + + + {autoMatch.loading ? : } + + + : + + ) + } + { + autoMatch && !autoMatchInMenu && hasActions && + + } { visibleSecondaryActions.map(action => ( 0 && } - + { + hasActions && !saveInMenu && + + } { hasOverflowActions && <> - + {!saveInMenu && } { (lastSavedText || isSaving) && -
+
{ isSaving ? t('map_project.saving') : t('map_project.last_saved', {time: lastSavedText}) @@ -266,7 +342,7 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, transformOrigin={{ vertical: 'top', horizontal: 'right' }} > { - overflowSecondaryActions.map(action => ( + overflowMenuActions.map(action => ( {action.icon} {action.title} @@ -274,7 +350,7 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, )) } { - overflowSecondaryActions.length > 0 && project?.id && + overflowMenuActions.length > 0 && project?.id && } { diff --git a/src/components/map-projects/MapProject.jsx b/src/components/map-projects/MapProject.jsx index 3f92f9c..dea59d1 100644 --- a/src/components/map-projects/MapProject.jsx +++ b/src/components/map-projects/MapProject.jsx @@ -30,10 +30,8 @@ import Snackbar from '@mui/material/Snackbar'; import { DataGrid, GRID_CHECKBOX_SELECTION_COL_DEF } from '@mui/x-data-grid'; -import DoubleArrowIcon from '@mui/icons-material/DoubleArrow'; import DoneIcon from '@mui/icons-material/Done'; import CloseIcon from '@mui/icons-material/Close'; -import AutoMatchIcon from '@mui/icons-material/MotionPhotosAutoOutlined'; import ClearIcon from '@mui/icons-material/Clear'; import AssistantIcon from '@mui/icons-material/Assistant'; import PendingIcon from '@mui/icons-material/HourglassBottom'; @@ -6345,30 +6343,17 @@ const MapProject = () => {
{ !isConfigFormInline && - + { name && - + {name} } - { scispacyCandidatesStartedAt &&