feat(SidePanel): new side panel tab look-and-feel (#3657)
This commit is contained in:
parent
48bbd6281a
commit
85c899b399
@ -45,10 +45,10 @@ module.exports = (env, argv) => {
|
|||||||
new webpack.optimize.LimitChunkCountPlugin({
|
new webpack.optimize.LimitChunkCountPlugin({
|
||||||
maxChunks: 1,
|
maxChunks: 1,
|
||||||
}),
|
}),
|
||||||
// new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
// filename: `./dist/${outputName}.css`,
|
filename: `./dist/${outputName}.css`,
|
||||||
// chunkFilename: `./dist/${outputName}.css`,
|
chunkFilename: `./dist/${outputName}.css`,
|
||||||
// }),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
60
extensions/default/src/Components/SidePanelWithServices.tsx
Normal file
60
extensions/default/src/Components/SidePanelWithServices.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { SidePanel } from '@ohif/ui';
|
||||||
|
import { PanelService, ServicesManager } from '@ohif/core';
|
||||||
|
|
||||||
|
export type SidePanelWithServicesProps = {
|
||||||
|
servicesManager: ServicesManager;
|
||||||
|
side: 'left' | 'right';
|
||||||
|
className: string;
|
||||||
|
activeTabIndex: number;
|
||||||
|
tabs: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SidePanelWithServices = ({
|
||||||
|
servicesManager,
|
||||||
|
side,
|
||||||
|
className,
|
||||||
|
activeTabIndex: activeTabIndexProp,
|
||||||
|
tabs,
|
||||||
|
}) => {
|
||||||
|
const panelService: PanelService = servicesManager?.services?.panelService;
|
||||||
|
|
||||||
|
// 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(false);
|
||||||
|
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
setActiveTabIndex(tabIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
activatePanelSubscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [tabs, hasBeenOpened, panelService]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidePanel
|
||||||
|
side={side}
|
||||||
|
className={className}
|
||||||
|
activeTabIndex={activeTabIndex}
|
||||||
|
tabs={tabs}
|
||||||
|
onOpen={() => {
|
||||||
|
setHasBeenOpened(true);
|
||||||
|
}}
|
||||||
|
></SidePanel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidePanelWithServices;
|
||||||
@ -5,6 +5,7 @@ import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui';
|
|||||||
import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core';
|
import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core';
|
||||||
import { useAppConfig } from '@state';
|
import { useAppConfig } from '@state';
|
||||||
import ViewerHeader from './ViewerHeader';
|
import ViewerHeader from './ViewerHeader';
|
||||||
|
import SidePanelWithServices from '../Components/SidePanelWithServices';
|
||||||
|
|
||||||
function ViewerLayout({
|
function ViewerLayout({
|
||||||
// From Extension Module Params
|
// From Extension Module Params
|
||||||
@ -119,7 +120,7 @@ function ViewerLayout({
|
|||||||
{/* LEFT SIDEPANELS */}
|
{/* LEFT SIDEPANELS */}
|
||||||
{leftPanelComponents.length ? (
|
{leftPanelComponents.length ? (
|
||||||
<ErrorBoundary context="Left Panel">
|
<ErrorBoundary context="Left Panel">
|
||||||
<SidePanel
|
<SidePanelWithServices
|
||||||
side="left"
|
side="left"
|
||||||
activeTabIndex={leftPanelDefaultClosed ? null : 0}
|
activeTabIndex={leftPanelDefaultClosed ? null : 0}
|
||||||
tabs={leftPanelComponents}
|
tabs={leftPanelComponents}
|
||||||
@ -141,7 +142,7 @@ function ViewerLayout({
|
|||||||
</div>
|
</div>
|
||||||
{rightPanelComponents.length ? (
|
{rightPanelComponents.length ? (
|
||||||
<ErrorBoundary context="Right Panel">
|
<ErrorBoundary context="Right Panel">
|
||||||
<SidePanel
|
<SidePanelWithServices
|
||||||
side="right"
|
side="right"
|
||||||
activeTabIndex={rightPanelDefaultClosed ? null : 0}
|
activeTabIndex={rightPanelDefaultClosed ? null : 0}
|
||||||
tabs={rightPanelComponents}
|
tabs={rightPanelComponents}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'seriesList',
|
name: 'seriesList',
|
||||||
iconName: 'group-layers',
|
iconName: 'tab-studies',
|
||||||
iconLabel: 'Studies',
|
iconLabel: 'Studies',
|
||||||
label: 'Studies',
|
label: 'Studies',
|
||||||
component: WrappedPanelStudyBrowser.bind(null, {
|
component: WrappedPanelStudyBrowser.bind(null, {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'seriesList',
|
name: 'seriesList',
|
||||||
iconName: 'group-layers',
|
iconName: 'tab-studies',
|
||||||
iconLabel: 'Studies',
|
iconLabel: 'Studies',
|
||||||
label: 'Studies',
|
label: 'Studies',
|
||||||
component: PanelStudyBrowserTracking.bind(null, {
|
component: PanelStudyBrowserTracking.bind(null, {
|
||||||
|
|||||||
@ -13,8 +13,12 @@ describe('OHIF Multi Study', () => {
|
|||||||
it('Should display 2 comparison up', () => {
|
it('Should display 2 comparison up', () => {
|
||||||
beforeSetup();
|
beforeSetup();
|
||||||
|
|
||||||
cy.get('[data-cy="viewport-pane"]').its('length').should('be.eq', 4);
|
cy.get('[data-cy="viewport-pane"]').as('viewportPane');
|
||||||
cy.get('[data-cy="studyDate"]').should(studyDate => {
|
cy.get('@viewportPane').its('length').should('be.eq', 4);
|
||||||
|
|
||||||
|
cy.get('[data-cy="studyDate"]').as('studyDate');
|
||||||
|
|
||||||
|
cy.get('@studyDate').should(studyDate => {
|
||||||
expect(studyDate.length).to.be.eq(4);
|
expect(studyDate.length).to.be.eq(4);
|
||||||
expect(studyDate.text()).to.contain('2014').contain('2001');
|
expect(studyDate.text()).to.contain('2014').contain('2001');
|
||||||
expect(studyDate.text().indexOf('2014')).to.be.lessThan(studyDate.text().indexOf('2001'));
|
expect(studyDate.text().indexOf('2014')).to.be.lessThan(studyDate.text().indexOf('2001'));
|
||||||
|
|||||||
@ -23,26 +23,28 @@ describe('OHIF Measurement Panel', function () {
|
|||||||
it('checks if measurement item can be Relabeled under Measurements panel', function () {
|
it('checks if measurement item can be Relabeled under Measurements panel', function () {
|
||||||
// Add length measurement
|
// Add length measurement
|
||||||
cy.addLengthMeasurement();
|
cy.addLengthMeasurement();
|
||||||
cy.get('[data-cy="viewport-notification"]').should('exist');
|
cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('exist');
|
||||||
cy.get('[data-cy="viewport-notification"]').should('be.visible');
|
cy.get('[data-cy="viewport-notification"]').as('viewportNotification').should('be.visible');
|
||||||
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
|
|
||||||
cy.get('[data-cy="measurement-item"]').click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="measurement-item"]').find('svg').click();
|
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('promptBeginTrackingYesBtn').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="measurement-item"]').as('measurementItem').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="measurement-item"]').find('svg').as('measurementItemSvg').click();
|
||||||
|
|
||||||
// enter Bone label
|
// enter Bone label
|
||||||
cy.get('[data-cy="input-annotation"]').should('exist');
|
cy.get('[data-cy="input-annotation"]').should('exist');
|
||||||
cy.get('[data-cy="input-annotation"]').should('be.visible');
|
cy.get('[data-cy="input-annotation"]').should('be.visible');
|
||||||
cy.get('[data-cy="input-annotation"]').type('Bone{enter}');
|
cy.get('[data-cy="input-annotation"]').type('Bone{enter}');
|
||||||
|
|
||||||
// Verify if 'Bone' label was added
|
cy.get('[data-cy="measurement-item"]').as('measurementItem').should('contain.text', 'Bone');
|
||||||
cy.get('[data-cy="measurement-item"]').should('contain.text', 'Bone');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if image would jump when clicked on a measurement item', function () {
|
it('checks if image would jump when clicked on a measurement item', function () {
|
||||||
// Add length measurement
|
// Add length measurement
|
||||||
cy.addLengthMeasurement();
|
cy.addLengthMeasurement();
|
||||||
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
|
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').as('promptBeginTrackingYesBtn');
|
||||||
|
cy.get('@promptBeginTrackingYesBtn').click();
|
||||||
|
|
||||||
cy.scrollToIndex(13);
|
cy.scrollToIndex(13);
|
||||||
|
|
||||||
|
|||||||
@ -144,19 +144,22 @@ Cypress.Commands.add('drag', { prevSubject: 'element' }, (...args) =>
|
|||||||
* @param {number[]} secondClick - Click position [x, y]
|
* @param {number[]} secondClick - Click position [x, y]
|
||||||
*/
|
*/
|
||||||
Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||||
|
const performClick = (alias, x, y) => {
|
||||||
|
cy.get(alias).click(x, y, { force: true, multiple: true }).wait(250);
|
||||||
|
};
|
||||||
|
|
||||||
cy.get(viewport).as('viewportAlias');
|
cy.get(viewport).as('viewportAlias');
|
||||||
const [x1, y1] = firstClick;
|
const [x1, y1] = firstClick;
|
||||||
const [x2, y2] = secondClick;
|
const [x2, y2] = secondClick;
|
||||||
|
|
||||||
// First click
|
// First click
|
||||||
cy.get('@viewportAlias').click(x1, y1, { force: true, multiple: true }).wait(250);
|
performClick('@viewportAlias', x1, y1);
|
||||||
|
|
||||||
// Move the mouse and then click again
|
// Move the mouse
|
||||||
cy.get('@viewportAlias')
|
cy.get('@viewportAlias').trigger('mousemove', { clientX: x2, clientY: y2 }).wait(250);
|
||||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
|
||||||
.get('@viewportAlias')
|
// Second click
|
||||||
.click(x2, y2, { force: true, multiple: true })
|
performClick('@viewportAlias', x2, y2);
|
||||||
.wait(250);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
14
platform/ui/src/assets/icons/side-panel-close-left.svg
Normal file
14
platform/ui/src/assets/icons/side-panel-close-left.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon-panel-close-left</title>
|
||||||
|
<g id="Artboards-Updated" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Segmentation---assets" transform="translate(-370, -60)">
|
||||||
|
<g id="icon-panel-close-left" transform="translate(380, 70) scale(-1, 1) translate(-380, -70)translate(370, 60)">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="20" height="20"></rect>
|
||||||
|
<line x1="12.8336364" y1="10.4061473" x2="3" y2="10.4061473" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(7.9168, 10.4061) scale(-1, 1) translate(-7.9168, -10.4061)"></line>
|
||||||
|
<polyline id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(10.7991, 10.4061) scale(-1, 1) translate(-10.7991, -10.4061)" points="12.8336364 6.33705636 8.76454545 10.4061473 12.8336364 14.4752382"></polyline>
|
||||||
|
<line x1="16.2418182" y1="14.4752382" x2="16.2418182" y2="6.33705636" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(16.2418, 10.4061) scale(-1, 1) translate(-16.2418, -10.4061)"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
14
platform/ui/src/assets/icons/side-panel-close-right.svg
Normal file
14
platform/ui/src/assets/icons/side-panel-close-right.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon-panel-close-right</title>
|
||||||
|
<g id="Artboards-Updated" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Segmentation---assets" transform="translate(-418, -60)">
|
||||||
|
<g id="icon-panel-close-right" transform="translate(418, 60)">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="20" height="20"></rect>
|
||||||
|
<line x1="12.8336364" y1="10.4061473" x2="3" y2="10.4061473" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(7.9168, 10.4061) scale(-1, 1) translate(-7.9168, -10.4061)"></line>
|
||||||
|
<polyline id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(10.7991, 10.4061) scale(-1, 1) translate(-10.7991, -10.4061)" points="12.8336364 6.33705636 8.76454545 10.4061473 12.8336364 14.4752382"></polyline>
|
||||||
|
<line x1="16.2418182" y1="14.4752382" x2="16.2418182" y2="6.33705636" id="Path" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" transform="translate(16.2418, 10.4061) scale(-1, 1) translate(-16.2418, -10.4061)"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
13
platform/ui/src/assets/icons/tab-studies.svg
Normal file
13
platform/ui/src/assets/icons/tab-studies.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="22px" height="23px" viewBox="0 0 22 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>tab-studies</title>
|
||||||
|
<g id="Artboards-Updated" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Segmentation---assets" transform="translate(-61, -242)">
|
||||||
|
<g id="tab-studies" transform="translate(61, 242.5)">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="22" height="22"></rect>
|
||||||
|
<path d="M6.93478261,15.0652174 L3.23913043,15.0652174 C2.83091997,15.0652174 2.5,14.7342974 2.5,14.326087 L2.5,3.23913043 C2.5,2.83091997 2.83091997,2.5 3.23913043,2.5 L14.326087,2.5 C14.7342974,2.5 15.0652174,2.83091997 15.0652174,3.23913043 L15.0652174,6.93478261" id="Path" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<rect id="Rectangle" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" x="6.93478261" y="6.93478261" width="12.5652174" height="12.5652174" rx="1"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -39,6 +39,8 @@ import profile from './../../assets/icons/profile.svg';
|
|||||||
import pushLeft from './../../assets/icons/push-left.svg';
|
import pushLeft from './../../assets/icons/push-left.svg';
|
||||||
import pushRight from './../../assets/icons/push-right.svg';
|
import pushRight from './../../assets/icons/push-right.svg';
|
||||||
import settings from './../../assets/icons/settings.svg';
|
import settings from './../../assets/icons/settings.svg';
|
||||||
|
import sidePanelCloseLeft from './../../assets/icons/side-panel-close-left.svg';
|
||||||
|
import sidePanelCloseRight from './../../assets/icons/side-panel-close-right.svg';
|
||||||
import sorting from './../../assets/icons/sorting.svg';
|
import sorting from './../../assets/icons/sorting.svg';
|
||||||
import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
||||||
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
||||||
@ -71,6 +73,7 @@ import tabLinear from './../../assets/icons/tab-linear.svg';
|
|||||||
import tabPatientInfo from './../../assets/icons/tab-patient-info.svg';
|
import tabPatientInfo from './../../assets/icons/tab-patient-info.svg';
|
||||||
import tabROIThreshold from './../../assets/icons/tab-roi-threshold.svg';
|
import tabROIThreshold from './../../assets/icons/tab-roi-threshold.svg';
|
||||||
import tabSegmentation from './../../assets/icons/tab-segmentation.svg';
|
import tabSegmentation from './../../assets/icons/tab-segmentation.svg';
|
||||||
|
import tabStudies from './../../assets/icons/tab-studies.svg';
|
||||||
import uiArrowDown from './../../assets/icons/ui-arrow-down.svg';
|
import uiArrowDown from './../../assets/icons/ui-arrow-down.svg';
|
||||||
import uiArrowUp from './../../assets/icons/ui-arrow-up.svg';
|
import uiArrowUp from './../../assets/icons/ui-arrow-up.svg';
|
||||||
import uiArrowLeft from './../../assets/icons/ui-arrow-left.svg';
|
import uiArrowLeft from './../../assets/icons/ui-arrow-left.svg';
|
||||||
@ -189,6 +192,8 @@ const ICONS = {
|
|||||||
'push-left': pushLeft,
|
'push-left': pushLeft,
|
||||||
'push-right': pushRight,
|
'push-right': pushRight,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
|
'side-panel-close-left': sidePanelCloseLeft,
|
||||||
|
'side-panel-close-right': sidePanelCloseRight,
|
||||||
'sorting-active-down': sortingActiveDown,
|
'sorting-active-down': sortingActiveDown,
|
||||||
'sorting-active-up': sortingActiveUp,
|
'sorting-active-up': sortingActiveUp,
|
||||||
'status-alert': statusAlert,
|
'status-alert': statusAlert,
|
||||||
@ -259,6 +264,7 @@ const ICONS = {
|
|||||||
'tab-patient-info': tabPatientInfo,
|
'tab-patient-info': tabPatientInfo,
|
||||||
'tab-roi-threshold': tabROIThreshold,
|
'tab-roi-threshold': tabROIThreshold,
|
||||||
'tab-segmentation': tabSegmentation,
|
'tab-segmentation': tabSegmentation,
|
||||||
|
'tab-studies': tabStudies,
|
||||||
'ui-arrow-down': uiArrowDown,
|
'ui-arrow-down': uiArrowDown,
|
||||||
'ui-arrow-up': uiArrowUp,
|
'ui-arrow-up': uiArrowUp,
|
||||||
'ui-arrow-left': uiArrowLeft,
|
'ui-arrow-left': uiArrowLeft,
|
||||||
|
|||||||
376
platform/ui/src/components/LegacySidePanel/LegacySidePanel.tsx
Normal file
376
platform/ui/src/components/LegacySidePanel/LegacySidePanel.tsx
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
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 LegacyButton from '../LegacyButton';
|
||||||
|
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 LegacySidePanel = ({
|
||||||
|
servicesManager,
|
||||||
|
side,
|
||||||
|
className,
|
||||||
|
activeTabIndex: activeTabIndexProp,
|
||||||
|
tabs,
|
||||||
|
}) => {
|
||||||
|
const panelService: PanelService = servicesManager?.services?.panelService;
|
||||||
|
|
||||||
|
const { t } = useTranslation('LegacySidePanel');
|
||||||
|
|
||||||
|
// Tracks whether this LegacySidePanel has been opened at least once since this LegacySidePanel was inserted into the DOM.
|
||||||
|
// Thus going to the Study List page and back to the viewer resets this flag for a LegacySidePanel.
|
||||||
|
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<any>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'bg-secondary-dark flex h-[28px] w-full cursor-pointer items-center rounded-md',
|
||||||
|
side === 'left' ? 'justify-end pr-2' : 'justify-start pl-2'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
updatePanelOpen(prev => !prev);
|
||||||
|
}}
|
||||||
|
data-cy={`side-panel-header-${side}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={'navigation-panel-right-reveal'}
|
||||||
|
className={classnames('text-primary-active', side === 'left' && 'rotate-180 transform')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={classnames('mt-3 flex flex-col space-y-3')}>
|
||||||
|
{_childComponents.map((childComponent, index) => (
|
||||||
|
<Tooltip
|
||||||
|
position={side === 'left' ? 'right' : 'left'}
|
||||||
|
key={index}
|
||||||
|
content={`${childComponent.label}`}
|
||||||
|
className={classnames(
|
||||||
|
'flex items-center',
|
||||||
|
side === 'left' ? 'justify-end ' : 'justify-start '
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
id={`${childComponent.name}-btn`}
|
||||||
|
variant="text"
|
||||||
|
color="inherit"
|
||||||
|
size="initial"
|
||||||
|
className="text-primary-active"
|
||||||
|
onClick={() => {
|
||||||
|
updateActiveTabIndex(index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={childComponent.iconName}
|
||||||
|
className="text-primary-active"
|
||||||
|
style={{
|
||||||
|
width: '22px',
|
||||||
|
height: '22px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames(className, baseClasses, classesMap[openStatus][side])}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{panelOpen ? (
|
||||||
|
<React.Fragment>
|
||||||
|
{/** Panel Header with Arrow and Close Actions */}
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex-static bg-primary-dark flex h-9 cursor-pointer px-[10px]',
|
||||||
|
tabs.length === 1 && 'mb-1'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
updatePanelOpen(prev => !prev);
|
||||||
|
// slideToActivePanel();
|
||||||
|
}}
|
||||||
|
data-cy={`side-panel-header-${side}`}
|
||||||
|
>
|
||||||
|
{/* TODO This should be redesigned to not be a button. */}
|
||||||
|
<LegacyButton
|
||||||
|
variant="text"
|
||||||
|
color="inherit"
|
||||||
|
border="none"
|
||||||
|
rounded="none"
|
||||||
|
className="flex-static relative flex w-full flex-row items-center px-3"
|
||||||
|
name={tabs.length === 1 ? `${tabs[activeTabIndex].name}` : ''}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={openStateIconName[side]}
|
||||||
|
className={classnames(
|
||||||
|
'text-primary-active absolute',
|
||||||
|
side === 'left' && 'order-last'
|
||||||
|
)}
|
||||||
|
style={{ ...position[side] }}
|
||||||
|
/>
|
||||||
|
{/* Todo: ass secondary label here */}
|
||||||
|
<span className="text-primary-active">
|
||||||
|
{tabs.length === 1 && (t(tabs[activeTabIndex].label) as string)}
|
||||||
|
</span>
|
||||||
|
</LegacyButton>
|
||||||
|
</div>
|
||||||
|
{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 && (
|
||||||
|
<div className="text-primary-active bg-primary-dark flex w-full justify-end gap-2 py-1 px-2">
|
||||||
|
<button
|
||||||
|
ref={prevRef}
|
||||||
|
className="swiper-button-prev-custom"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={'icon-prev'}
|
||||||
|
className={classnames('text-primary-active')}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
ref={nextRef}
|
||||||
|
className="swiper-button-next-custom"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={'icon-next'}
|
||||||
|
className={classnames('text-primary-active')}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ActiveComponent />
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
<React.Fragment>{getCloseStateComponent()}</React.Fragment>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacySidePanel.defaultProps = {
|
||||||
|
defaultComponentOpen: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacySidePanel.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<any>,
|
||||||
|
prevRef: React.MutableRefObject<undefined>,
|
||||||
|
nextRef: React.MutableRefObject<undefined>,
|
||||||
|
tabs: any,
|
||||||
|
activeTabIndex: any,
|
||||||
|
updateActiveTabIndex
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex-static collapse-sidebar relative"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#06081f',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="w-full">
|
||||||
|
<Swiper
|
||||||
|
onInit={(core: SwiperCore) => {
|
||||||
|
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) => (
|
||||||
|
<SwiperSlide key={index}>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
index === activeTabIndex ? 'bg-secondary-main text-white' : 'text-aqua-pale',
|
||||||
|
'flex cursor-pointer flex-col items-center justify-center rounded-[4px] px-4 py-1 text-center hover:text-white'
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
onClick={() => {
|
||||||
|
updateActiveTabIndex(index);
|
||||||
|
}}
|
||||||
|
data-cy={`${obj.name}-btn`}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Icon
|
||||||
|
name={obj.iconName}
|
||||||
|
className={classnames(
|
||||||
|
index === activeTabIndex ? 'text-white' : 'text-primary-active'
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width: '22px',
|
||||||
|
height: '22px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="mt-[5px] select-none whitespace-nowrap text-[10px] font-medium">
|
||||||
|
{obj.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LegacySidePanel;
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
import LegacySidePanel from '../LegacySidePanel';
|
||||||
|
|
||||||
|
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||||
|
import { createComponentTemplate } from '../../../storybook/functions/create-component-story';
|
||||||
|
|
||||||
|
export const argTypes = {
|
||||||
|
component: LegacySidePanel,
|
||||||
|
title: 'Components/LegacySidePanel',
|
||||||
|
};
|
||||||
|
|
||||||
|
<Meta
|
||||||
|
title="Components/LegacySidePanel"
|
||||||
|
component={LegacySidePanel}
|
||||||
|
/>
|
||||||
|
|
||||||
|
export const LegacySidePanelTemplate = args => (
|
||||||
|
<div className="w-96">
|
||||||
|
<LegacySidePanel {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
title="LegacySidePanel"
|
||||||
|
componentRelativePath="LegacySidePanel/LegacySidePanel.tsx"
|
||||||
|
/>
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Props](#props)
|
||||||
|
- [Contribute](#contribute)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
LegacySidePanel is a component that renders the LegacySidePanels.
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Overview"
|
||||||
|
args={{
|
||||||
|
side: 'left',
|
||||||
|
defaultComponentOpen: 'Example',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'tab1',
|
||||||
|
name: 'tab1',
|
||||||
|
label: 'Tab 1',
|
||||||
|
iconName: 'group-layers',
|
||||||
|
iconLabel: 'group-layers',
|
||||||
|
content: () => <div class="text-primary-light p-3">Hello</div>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
childComponents: [
|
||||||
|
{
|
||||||
|
iconName: 'home',
|
||||||
|
iconLabel: 'launch-info',
|
||||||
|
name: 'Example',
|
||||||
|
label: 'home',
|
||||||
|
content: () => <div>Hello world</div>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{LegacySidePanelTemplate.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
<ArgsTable of={LegacySidePanel} />
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
<Footer componentRelativePath="LegacySidePanel/__stories__/legacySidePanel.stories.mdx" />
|
||||||
2
platform/ui/src/components/LegacySidePanel/index.js
Normal file
2
platform/ui/src/components/LegacySidePanel/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import LegacySidePanel from './LegacySidePanel';
|
||||||
|
export default LegacySidePanel;
|
||||||
@ -1,28 +1,27 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 LegacyButton from '../LegacyButton';
|
|
||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
import IconButton from '../IconButton';
|
|
||||||
import Tooltip from '../Tooltip';
|
import Tooltip from '../Tooltip';
|
||||||
|
|
||||||
import 'swiper/css';
|
|
||||||
import 'swiper/css/navigation';
|
|
||||||
import './style.css';
|
|
||||||
|
|
||||||
const borderSize = 4;
|
const borderSize = 4;
|
||||||
const expandedWidth = 248;
|
const expandedWidth = 248;
|
||||||
const collapsedWidth = 25;
|
const collapsedWidth = 25;
|
||||||
|
const closeIconWidth = 30;
|
||||||
|
const gridHorizontalPadding = 10;
|
||||||
|
const tabSpacerWidth = 2;
|
||||||
|
const gridAvailableWidth = expandedWidth - closeIconWidth - gridHorizontalPadding;
|
||||||
|
|
||||||
const baseStyle = {
|
const baseStyle = {
|
||||||
maxWidth: `${expandedWidth}px`,
|
maxWidth: `${expandedWidth}px`,
|
||||||
width: `${expandedWidth}px`,
|
width: `${expandedWidth}px`,
|
||||||
|
// To align the top of the side panel with the top of the viewport grid, use position relative and offset the
|
||||||
|
// top by the same top offset as the viewport grid. Also adjust the height so that there is no overflow.
|
||||||
|
position: 'relative',
|
||||||
|
top: '0.2%',
|
||||||
|
height: '99.8%',
|
||||||
};
|
};
|
||||||
|
|
||||||
const collapsedHideWidth = expandedWidth - collapsedWidth - borderSize;
|
const collapsedHideWidth = expandedWidth - collapsedWidth - borderSize;
|
||||||
@ -38,7 +37,7 @@ const styleMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const baseClasses =
|
const baseClasses =
|
||||||
'transition-all duration-300 ease-in-out h-100 bg-black border-black justify-start box-content flex flex-col';
|
'transition-all duration-300 ease-in-out bg-black border-black justify-start box-content flex flex-col';
|
||||||
|
|
||||||
const classesMap = {
|
const classesMap = {
|
||||||
open: {
|
open: {
|
||||||
@ -52,70 +51,110 @@ const classesMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openStateIconName = {
|
const openStateIconName = {
|
||||||
left: 'push-left',
|
left: 'side-panel-close-left',
|
||||||
right: 'push-right',
|
right: 'side-panel-close-right',
|
||||||
};
|
};
|
||||||
|
|
||||||
const position = {
|
const getTabWidth = (numTabs: number) => {
|
||||||
left: {
|
if (numTabs < 3) {
|
||||||
right: 5,
|
return 68;
|
||||||
},
|
} else {
|
||||||
right: {
|
return 40;
|
||||||
left: 5,
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidePanel = ({
|
const getGridWidth = (numTabs: number) => {
|
||||||
servicesManager,
|
const spacersWidth = (numTabs - 1) * tabSpacerWidth;
|
||||||
side,
|
const tabsWidth = getTabWidth(numTabs) * numTabs;
|
||||||
className,
|
|
||||||
activeTabIndex: activeTabIndexProp,
|
|
||||||
tabs,
|
|
||||||
}) => {
|
|
||||||
const panelService: PanelService = servicesManager?.services?.panelService;
|
|
||||||
|
|
||||||
|
if (gridAvailableWidth > tabsWidth + spacersWidth) {
|
||||||
|
return tabsWidth + spacersWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gridAvailableWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNumGridColumns = (numTabs: number) => {
|
||||||
|
if (numTabs === 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start by calculating the number of tabs assuming each tab was accompanied by a spacer.
|
||||||
|
const tabWidth = getTabWidth(numTabs);
|
||||||
|
const gridWidth = getGridWidth(numTabs);
|
||||||
|
const numTabsWithOneSpacerEach = Math.floor(gridWidth / (tabWidth + tabSpacerWidth));
|
||||||
|
|
||||||
|
// But there is always one less spacer than tabs, so now check if an extra tab with one less spacer fits.
|
||||||
|
if (
|
||||||
|
(numTabsWithOneSpacerEach + 1) * tabWidth + numTabsWithOneSpacerEach * tabSpacerWidth <=
|
||||||
|
gridWidth
|
||||||
|
) {
|
||||||
|
return numTabsWithOneSpacerEach + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return numTabsWithOneSpacerEach;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGridStyle = (side: string, numTabs: number = 0): CSSProperties => {
|
||||||
|
const gridWidth = getGridWidth(numTabs);
|
||||||
|
const relativePosition = Math.max(0, Math.floor(expandedWidth - gridWidth) / 2 - closeIconWidth);
|
||||||
|
return {
|
||||||
|
position: 'relative',
|
||||||
|
...(side === 'left' ? { right: `${relativePosition}px` } : { left: `${relativePosition}px` }),
|
||||||
|
width: `${gridWidth}px`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabClassNames = (
|
||||||
|
numColumns: number,
|
||||||
|
numTabs: number,
|
||||||
|
tabIndex: number,
|
||||||
|
isActiveTab: boolean
|
||||||
|
) =>
|
||||||
|
classnames('h-[28px] mb-[2px] cursor-pointer text-white bg-black', {
|
||||||
|
'hover:text-primary-active': !isActiveTab,
|
||||||
|
'rounded-l': tabIndex % numColumns === 0,
|
||||||
|
'rounded-r': (tabIndex + 1) % numColumns === 0 || tabIndex === numTabs - 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getTabStyle = (numTabs: number) => {
|
||||||
|
return {
|
||||||
|
width: `${getTabWidth(numTabs)}px`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabIconClassNames = (numTabs: number, isActiveTab: boolean) => {
|
||||||
|
return classnames('h-full w-full flex items-center justify-center', {
|
||||||
|
'bg-customblue-40': isActiveTab,
|
||||||
|
rounded: isActiveTab,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const SidePanel = ({ side, className, activeTabIndex: activeTabIndexProp, tabs, onOpen }) => {
|
||||||
const { t } = useTranslation('SidePanel');
|
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 [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
|
||||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp ?? 0);
|
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
||||||
const swiperRef = useRef() as any;
|
|
||||||
const [swiper, setSwiper] = useState<any>();
|
|
||||||
|
|
||||||
const prevRef = React.useRef();
|
|
||||||
const nextRef = React.useRef();
|
|
||||||
|
|
||||||
const openStatus = panelOpen ? 'open' : 'closed';
|
const openStatus = panelOpen ? 'open' : 'closed';
|
||||||
const style = Object.assign({}, styleMap[openStatus][side], baseStyle);
|
const style = Object.assign({}, styleMap[openStatus][side], baseStyle);
|
||||||
|
|
||||||
const ActiveComponent = tabs[activeTabIndex].content;
|
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) => {
|
const updatePanelOpen = useCallback((panelOpen: boolean) => {
|
||||||
setPanelOpen(panelOpen);
|
setPanelOpen(panelOpen);
|
||||||
if (panelOpen) {
|
if (panelOpen) {
|
||||||
setHasBeenOpened(true);
|
onOpen?.();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateActiveTabIndex = useCallback(
|
const updateActiveTabIndex = useCallback(
|
||||||
(activeTabIndex: number) => {
|
(activeTabIndex: number) => {
|
||||||
|
if (activeTabIndex === null) {
|
||||||
|
updatePanelOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setActiveTabIndex(activeTabIndex);
|
setActiveTabIndex(activeTabIndex);
|
||||||
updatePanelOpen(true);
|
updatePanelOpen(true);
|
||||||
},
|
},
|
||||||
@ -123,24 +162,8 @@ const SidePanel = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (panelService) {
|
updateActiveTabIndex(activeTabIndexProp);
|
||||||
const activatePanelSubscription = panelService.subscribe(
|
}, [activeTabIndexProp, updateActiveTabIndex]);
|
||||||
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 getCloseStateComponent = () => {
|
||||||
const _childComponents = Array.isArray(tabs) ? tabs : [tabs];
|
const _childComponents = Array.isArray(tabs) ? tabs : [tabs];
|
||||||
@ -172,12 +195,10 @@ const SidePanel = ({
|
|||||||
side === 'left' ? 'justify-end ' : 'justify-start '
|
side === 'left' ? 'justify-end ' : 'justify-start '
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<IconButton
|
<div
|
||||||
id={`${childComponent.name}-btn`}
|
id={`${childComponent.name}-btn`}
|
||||||
variant="text"
|
data-cy={`${childComponent.name}-btn`}
|
||||||
color="inherit"
|
className="text-primary-active hover:cursor-pointer"
|
||||||
size="initial"
|
|
||||||
className="text-primary-active"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateActiveTabIndex(index);
|
updateActiveTabIndex(index);
|
||||||
}}
|
}}
|
||||||
@ -190,7 +211,7 @@ const SidePanel = ({
|
|||||||
height: '22px',
|
height: '22px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -198,81 +219,116 @@ const SidePanel = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCloseIcon = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex h-[28px] cursor-pointer items-center justify-center',
|
||||||
|
side === 'left' ? 'order-last' : 'order-first'
|
||||||
|
)}
|
||||||
|
style={{ width: `${closeIconWidth}px` }}
|
||||||
|
onClick={() => {
|
||||||
|
updatePanelOpen(prev => !prev);
|
||||||
|
}}
|
||||||
|
data-cy={`side-panel-header-${side}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={openStateIconName[side]}
|
||||||
|
className="text-primary-active"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTabGridComponent = () => {
|
||||||
|
const numCols = getNumGridColumns(tabs.length);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames('flex grow ', side === 'right' ? 'justify-start' : 'justify-end')}>
|
||||||
|
<div
|
||||||
|
className={classnames('bg-primary-dark text-primary-active flex flex-wrap')}
|
||||||
|
style={getGridStyle(side, tabs.length)}
|
||||||
|
>
|
||||||
|
{tabs.map((tab, tabIndex) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={tabIndex}>
|
||||||
|
{tabIndex % numCols !== 0 && (
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex h-[28px] w-[2px] items-center bg-black',
|
||||||
|
tabSpacerWidth
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="bg-primary-dark h-[20px] w-full"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Tooltip
|
||||||
|
position={'bottom'}
|
||||||
|
key={tabIndex}
|
||||||
|
content={`${tab.label}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={getTabClassNames(
|
||||||
|
numCols,
|
||||||
|
tabs.length,
|
||||||
|
tabIndex,
|
||||||
|
tabIndex === activeTabIndex
|
||||||
|
)}
|
||||||
|
style={getTabStyle(tabs.length)}
|
||||||
|
onClick={() => updateActiveTabIndex(tabIndex)}
|
||||||
|
data-cy={`${tab.name}-btn`}
|
||||||
|
>
|
||||||
|
<div className={getTabIconClassNames(tabs.length, tabIndex === activeTabIndex)}>
|
||||||
|
<Icon name={tab.iconName}></Icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOneTabComponent = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'text-primary-active flex grow cursor-pointer justify-center self-center text-[13px]'
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
...(side === 'left'
|
||||||
|
? { marginLeft: `${closeIconWidth}px` }
|
||||||
|
: { marginRight: `${closeIconWidth}px` }),
|
||||||
|
}}
|
||||||
|
data-cy={`${tabs[0].name}-btn`}
|
||||||
|
onClick={() => updatePanelOpen(prev => !prev)}
|
||||||
|
>
|
||||||
|
<span>{tabs[0].label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOpenStateComponent = () => {
|
||||||
|
return (
|
||||||
|
<div className="bg-primary-dark flex rounded-t pt-1.5 pb-[2px]">
|
||||||
|
{getCloseIcon()}
|
||||||
|
{tabs.length === 1 ? getOneTabComponent() : getTabGridComponent()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(className, baseClasses, classesMap[openStatus][side])}
|
className={classnames(className, baseClasses, classesMap[openStatus][side])}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{panelOpen ? (
|
{panelOpen ? (
|
||||||
<React.Fragment>
|
<>
|
||||||
{/** Panel Header with Arrow and Close Actions */}
|
{getOpenStateComponent()}
|
||||||
<div
|
|
||||||
className={classnames('flex-static bg-primary-dark flex h-9 cursor-pointer px-[10px]')}
|
|
||||||
onClick={() => {
|
|
||||||
updatePanelOpen(prev => !prev);
|
|
||||||
// slideToActivePanel();
|
|
||||||
}}
|
|
||||||
data-cy={`side-panel-header-${side}`}
|
|
||||||
>
|
|
||||||
{/* TODO This should be redesigned to not be a button. */}
|
|
||||||
<LegacyButton
|
|
||||||
variant="text"
|
|
||||||
color="inherit"
|
|
||||||
border="none"
|
|
||||||
rounded="none"
|
|
||||||
className="flex-static relative flex w-full flex-row items-center px-3"
|
|
||||||
name={tabs.length === 1 ? `${tabs[activeTabIndex].name}` : ''}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name={openStateIconName[side]}
|
|
||||||
className={classnames(
|
|
||||||
'text-primary-active absolute',
|
|
||||||
side === 'left' && 'order-last'
|
|
||||||
)}
|
|
||||||
style={{ ...position[side] }}
|
|
||||||
/>
|
|
||||||
{/* Todo: ass secondary label here */}
|
|
||||||
<span className="text-primary-active">
|
|
||||||
{tabs.length === 1 && (t(tabs[activeTabIndex].label) as string)}
|
|
||||||
</span>
|
|
||||||
</LegacyButton>
|
|
||||||
</div>
|
|
||||||
{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 && (
|
|
||||||
<div className="text-primary-active bg-primary-dark flex w-full justify-end gap-2 py-1 px-2">
|
|
||||||
<button
|
|
||||||
ref={prevRef}
|
|
||||||
className="swiper-button-prev-custom"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name={'icon-prev'}
|
|
||||||
className={classnames('text-primary-active')}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ref={nextRef}
|
|
||||||
className="swiper-button-next-custom"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name={'icon-next'}
|
|
||||||
className={classnames('text-primary-active')}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ActiveComponent />
|
<ActiveComponent />
|
||||||
</React.Fragment>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>{getCloseStateComponent()}</React.Fragment>
|
<React.Fragment>{getCloseStateComponent()}</React.Fragment>
|
||||||
)}
|
)}
|
||||||
@ -282,10 +338,10 @@ const SidePanel = ({
|
|||||||
|
|
||||||
SidePanel.defaultProps = {
|
SidePanel.defaultProps = {
|
||||||
defaultComponentOpen: null,
|
defaultComponentOpen: null,
|
||||||
|
activeTabIndex: null, // the default is to close the side panel
|
||||||
};
|
};
|
||||||
|
|
||||||
SidePanel.propTypes = {
|
SidePanel.propTypes = {
|
||||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
|
||||||
side: PropTypes.oneOf(['left', 'right']).isRequired,
|
side: PropTypes.oneOf(['left', 'right']).isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
activeTabIndex: PropTypes.number,
|
activeTabIndex: PropTypes.number,
|
||||||
@ -300,74 +356,7 @@ SidePanel.propTypes = {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
onOpen: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
function _getMoreThanOneTabLayout(
|
|
||||||
swiperRef: any,
|
|
||||||
setSwiper: React.Dispatch<any>,
|
|
||||||
prevRef: React.MutableRefObject<undefined>,
|
|
||||||
nextRef: React.MutableRefObject<undefined>,
|
|
||||||
tabs: any,
|
|
||||||
activeTabIndex: any,
|
|
||||||
updateActiveTabIndex
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex-static collapse-sidebar relative"
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#06081f',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="w-full">
|
|
||||||
<Swiper
|
|
||||||
onInit={(core: SwiperCore) => {
|
|
||||||
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) => (
|
|
||||||
<SwiperSlide key={index}>
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
index === activeTabIndex ? 'bg-secondary-main text-white' : 'text-aqua-pale',
|
|
||||||
'flex cursor-pointer flex-col items-center justify-center rounded-[4px] px-4 py-1 text-center hover:text-white'
|
|
||||||
)}
|
|
||||||
key={index}
|
|
||||||
onClick={() => {
|
|
||||||
updateActiveTabIndex(index);
|
|
||||||
}}
|
|
||||||
data-cy={`${obj.name}-btn`}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<Icon
|
|
||||||
name={obj.iconName}
|
|
||||||
className={classnames(
|
|
||||||
index === activeTabIndex ? 'text-white' : 'text-primary-active'
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
width: '22px',
|
|
||||||
height: '22px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="mt-[5px] select-none whitespace-nowrap text-[10px] font-medium">
|
|
||||||
{obj.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SidePanel;
|
export default SidePanel;
|
||||||
|
|||||||
@ -38,6 +38,7 @@ SidePanel is a component that renders the SidePanels.
|
|||||||
args={{
|
args={{
|
||||||
side: 'left',
|
side: 'left',
|
||||||
defaultComponentOpen: 'Example',
|
defaultComponentOpen: 'Example',
|
||||||
|
activeTabIndex: 0,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
id: 'tab1',
|
id: 'tab1',
|
||||||
|
|||||||
@ -74,6 +74,7 @@ import LoadingIndicatorProgress from './LoadingIndicatorProgress';
|
|||||||
import LoadingIndicatorTotalPercent from './LoadingIndicatorTotalPercent';
|
import LoadingIndicatorTotalPercent from './LoadingIndicatorTotalPercent';
|
||||||
import ViewportActionBar from './ViewportActionBar';
|
import ViewportActionBar from './ViewportActionBar';
|
||||||
import ProgressLoadingBar from './ProgressLoadingBar';
|
import ProgressLoadingBar from './ProgressLoadingBar';
|
||||||
|
import LegacySidePanel from './LegacySidePanel';
|
||||||
import PanelSection from './PanelSection';
|
import PanelSection from './PanelSection';
|
||||||
import AdvancedToolbox from './AdvancedToolbox';
|
import AdvancedToolbox from './AdvancedToolbox';
|
||||||
import InputDoubleRange from './InputDoubleRange';
|
import InputDoubleRange from './InputDoubleRange';
|
||||||
@ -117,6 +118,7 @@ export {
|
|||||||
LegacyButton,
|
LegacyButton,
|
||||||
LegacyButtonGroup,
|
LegacyButtonGroup,
|
||||||
LegacyCinePlayer,
|
LegacyCinePlayer,
|
||||||
|
LegacySidePanel,
|
||||||
LegacyViewportActionBar,
|
LegacyViewportActionBar,
|
||||||
LoadingIndicatorProgress,
|
LoadingIndicatorProgress,
|
||||||
LoadingIndicatorTotalPercent,
|
LoadingIndicatorTotalPercent,
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export {
|
|||||||
LegacyButton,
|
LegacyButton,
|
||||||
LegacyButtonGroup,
|
LegacyButtonGroup,
|
||||||
LegacyCinePlayer,
|
LegacyCinePlayer,
|
||||||
|
LegacySidePanel,
|
||||||
LegacyViewportActionBar,
|
LegacyViewportActionBar,
|
||||||
LoadingIndicatorProgress,
|
LoadingIndicatorProgress,
|
||||||
LoadingIndicatorTotalPercent,
|
LoadingIndicatorTotalPercent,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SidePanel, StudyBrowser } from '../../components';
|
import { LegacySidePanel, StudyBrowser } from '../../components';
|
||||||
import { DragAndDropProvider } from '../../contextProviders';
|
import { DragAndDropProvider } from '../../contextProviders';
|
||||||
|
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
@ -13,7 +13,7 @@ const Viewer = () => {
|
|||||||
className="flex w-full flex-1 flex-row flex-nowrap items-stretch overflow-hidden"
|
className="flex w-full flex-1 flex-row flex-nowrap items-stretch overflow-hidden"
|
||||||
style={{ height: 'calc(100vh - 52px' }}
|
style={{ height: 'calc(100vh - 52px' }}
|
||||||
>
|
>
|
||||||
<SidePanel
|
<LegacySidePanel
|
||||||
side="left"
|
side="left"
|
||||||
iconName="group-layers"
|
iconName="group-layers"
|
||||||
iconLabel="Studies"
|
iconLabel="Studies"
|
||||||
@ -21,12 +21,12 @@ const Viewer = () => {
|
|||||||
defaultIsOpen={true}
|
defaultIsOpen={true}
|
||||||
>
|
>
|
||||||
<StudyBrowser />
|
<StudyBrowser />
|
||||||
</SidePanel>
|
</LegacySidePanel>
|
||||||
<div className="h-100 bg-primary-main flex flex-1 items-center justify-center overflow-hidden text-white">
|
<div className="h-100 bg-primary-main flex flex-1 items-center justify-center overflow-hidden text-white">
|
||||||
{/* <ViewportToolbar /> */}
|
{/* <ViewportToolbar /> */}
|
||||||
<div>CONTENT</div>
|
<div>CONTENT</div>
|
||||||
</div>
|
</div>
|
||||||
<SidePanel
|
<LegacySidePanel
|
||||||
side="right"
|
side="right"
|
||||||
iconName="list-bullets"
|
iconName="list-bullets"
|
||||||
iconLabel="Measure"
|
iconLabel="Measure"
|
||||||
@ -34,7 +34,7 @@ const Viewer = () => {
|
|||||||
defaultIsOpen={false}
|
defaultIsOpen={false}
|
||||||
>
|
>
|
||||||
<div className="flex justify-center p-2 text-white">panel placeholder</div>
|
<div className="flex justify-center p-2 text-white">panel placeholder</div>
|
||||||
</SidePanel>
|
</LegacySidePanel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragAndDropProvider>
|
</DragAndDropProvider>
|
||||||
|
|||||||
19
yarn.lock
19
yarn.lock
@ -1523,6 +1523,16 @@
|
|||||||
gl-matrix "^3.4.3"
|
gl-matrix "^3.4.3"
|
||||||
lodash.clonedeep "4.5.0"
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
|
"@cornerstonejs/core@^1.16.6":
|
||||||
|
version "1.16.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-1.16.6.tgz#a216fc655f3c4b110196e63dcf129e02839d0890"
|
||||||
|
integrity sha512-ysd+t2N2KVk88TLYg0Nx174uYd47wQRBdFQZ4ApHhI/IHq5lY/qrP8prrtLR/DEtyLyUERIxDyGFRr+lEBZpxg==
|
||||||
|
dependencies:
|
||||||
|
"@kitware/vtk.js" "27.3.1"
|
||||||
|
detect-gpu "^5.0.22"
|
||||||
|
gl-matrix "^3.4.3"
|
||||||
|
lodash.clonedeep "4.5.0"
|
||||||
|
|
||||||
"@cornerstonejs/dicom-image-loader@^1.16.5":
|
"@cornerstonejs/dicom-image-loader@^1.16.5":
|
||||||
version "1.16.5"
|
version "1.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.16.5.tgz#23ca3e27e29b88cd05840c777578b37de60e5b87"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-1.16.5.tgz#23ca3e27e29b88cd05840c777578b37de60e5b87"
|
||||||
@ -1544,6 +1554,15 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@cornerstonejs/core" "^1.16.5"
|
"@cornerstonejs/core" "^1.16.5"
|
||||||
|
|
||||||
|
"@cornerstonejs/tools@^1.16.4":
|
||||||
|
version "1.16.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.16.6.tgz#0debc17fb9830288bff263bbf3a34cb940b726c2"
|
||||||
|
integrity sha512-qLjtEC0OGW+bpvvKXQPQs8TEE/ze+NIiBbg1qBSXo2vm7O3YO1DRWbWthJMVL7tIWYuVxZx5OsAI2WOkydQUOA==
|
||||||
|
dependencies:
|
||||||
|
"@cornerstonejs/core" "^1.16.6"
|
||||||
|
lodash.clonedeep "4.5.0"
|
||||||
|
lodash.get "^4.4.2"
|
||||||
|
|
||||||
"@cornerstonejs/tools@^1.16.5":
|
"@cornerstonejs/tools@^1.16.5":
|
||||||
version "1.16.5"
|
version "1.16.5"
|
||||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.16.5.tgz#79948216521c591a02f395e2130d72e0b9347eca"
|
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-1.16.5.tgz#79948216521c591a02f395e2130d72e0b9347eca"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user