import classnames from 'classnames'; import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import SwiperCore, { A11y, Controller, Navigation, Pagination, Scrollbar, } from 'swiper'; import { Swiper, SwiperSlide } from 'swiper/react'; import { PanelService, ServicesManager, Types } from '@ohif/core'; import Button from '../Button'; import Icon from '../Icon'; import IconButton from '../IconButton'; import Tooltip from '../Tooltip'; import 'swiper/css'; import 'swiper/css/navigation'; import './style.css'; const borderSize = 4; const expandedWidth = 248; const collapsedWidth = 25; const baseStyle = { maxWidth: `${expandedWidth}px`, width: `${expandedWidth}px`, }; const collapsedHideWidth = expandedWidth - collapsedWidth - borderSize; const styleMap = { open: { left: { marginLeft: '0px' }, right: { marginRight: '0px' }, }, closed: { left: { marginLeft: `-${collapsedHideWidth}px` }, right: { marginRight: `-${collapsedHideWidth}px` }, }, }; const baseClasses = 'transition-all duration-300 ease-in-out h-100 bg-black border-black justify-start box-content flex flex-col'; const classesMap = { open: { left: `mr-1`, right: `ml-1`, }, closed: { left: `mr-2 items-end`, right: `ml-2 items-start`, }, }; const openStateIconName = { left: 'push-left', right: 'push-right', }; const position = { left: { right: 5, }, right: { left: 5, }, }; const SidePanel = ({ servicesManager, side, className, activeTabIndex: activeTabIndexProp, tabs, }) => { const panelService: PanelService = servicesManager?.services?.panelService; const { t } = useTranslation('SidePanel'); // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. const [hasBeenOpened, setHasBeenOpened] = useState( activeTabIndexProp !== null ); const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null); const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp ?? 0); const swiperRef = useRef() as any; const [swiper, setSwiper] = useState(); const prevRef = React.useRef(); const nextRef = React.useRef(); const openStatus = panelOpen ? 'open' : 'closed'; const style = Object.assign({}, styleMap[openStatus][side], baseStyle); const ActiveComponent = tabs[activeTabIndex].content; useEffect(() => { if (panelOpen && swiper) { swiper.slideTo(activeTabIndex, 500); } }, [panelOpen, swiper]); useEffect(() => { if (swiper) { swiper.params.navigation.prevEl = prevRef.current; swiper.params.navigation.nextEl = nextRef.current; swiper.navigation.init(); swiper.navigation.update(); } }, [swiper]); const updatePanelOpen = useCallback((panelOpen: boolean) => { setPanelOpen(panelOpen); if (panelOpen) { setHasBeenOpened(true); } }, []); const updateActiveTabIndex = useCallback( (activeTabIndex: number) => { setActiveTabIndex(activeTabIndex); updatePanelOpen(true); }, [updatePanelOpen] ); useEffect(() => { if (panelService) { const activatePanelSubscription = panelService.subscribe( panelService.EVENTS.ACTIVATE_PANEL, (activatePanelEvent: Types.ActivatePanelEvent) => { if (!hasBeenOpened || activatePanelEvent.forceActive) { const tabIndex = tabs.findIndex( tab => tab.id === activatePanelEvent.panelId ); if (tabIndex !== -1) { updateActiveTabIndex(tabIndex); } } } ); return () => { activatePanelSubscription.unsubscribe(); }; } }, [tabs, hasBeenOpened, panelService, updateActiveTabIndex]); const getCloseStateComponent = () => { const _childComponents = Array.isArray(tabs) ? tabs : [tabs]; return ( <>
{ updatePanelOpen(prev => !prev); }} data-cy={`side-panel-header-${side}`} >
{_childComponents.map((childComponent, index) => ( { updateActiveTabIndex(index); }} > ))}
); }; return (
{panelOpen ? ( {/** Panel Header with Arrow and Close Actions */}
{ updatePanelOpen(prev => !prev); // slideToActivePanel(); }} data-cy={`side-panel-header-${side}`} >
{tabs.length > 1 && _getMoreThanOneTabLayout( swiperRef, setSwiper, prevRef, nextRef, tabs, activeTabIndex, updateActiveTabIndex )} {/** carousel navigation with the arrows */} {/** only show carousel nav if tabs are more than 3 tabs */} {tabs.length > 3 && (
)}
) : ( {getCloseStateComponent()} )}
); }; SidePanel.defaultProps = { defaultComponentOpen: null, }; SidePanel.propTypes = { servicesManager: PropTypes.instanceOf(ServicesManager), side: PropTypes.oneOf(['left', 'right']).isRequired, className: PropTypes.string, activeTabIndex: PropTypes.number, tabs: PropTypes.oneOfType([ PropTypes.arrayOf( PropTypes.shape({ iconName: PropTypes.string.isRequired, iconLabel: PropTypes.string.isRequired, name: PropTypes.string.isRequired, label: PropTypes.string.isRequired, content: PropTypes.func, // TODO: Should be node, but it keeps complaining? }) ), ]), }; function _getMoreThanOneTabLayout( swiperRef: any, setSwiper: React.Dispatch, prevRef: React.MutableRefObject, nextRef: React.MutableRefObject, tabs: any, activeTabIndex: any, updateActiveTabIndex ) { return (
{ swiperRef.current = core.el; }} simulateTouch={false} modules={[Navigation, Pagination, Scrollbar, A11y, Controller]} slidesPerView={3} spaceBetween={5} onSwiper={swiper => setSwiper(swiper)} navigation={{ prevEl: prevRef?.current, nextEl: nextRef?.current, }} > {tabs.map((obj, index) => (
{ updateActiveTabIndex(index); }} data-cy={`${obj.name}-btn`} > {obj.label}
))}
); } export default SidePanel;