feat(resizable-side-panels): Make the left and right side panels (optionally) resizable. (#4672)
This commit is contained in:
parent
a518854a4c
commit
d90a4cfb16
@ -7,47 +7,58 @@ export type SidePanelWithServicesProps = {
|
||||
side: 'left' | 'right';
|
||||
className?: string;
|
||||
activeTabIndex: number;
|
||||
tabs: any;
|
||||
tabs?: any;
|
||||
expandedWidth?: number;
|
||||
onClose: () => void;
|
||||
onOpen: () => void;
|
||||
isExpanded: boolean;
|
||||
collapsedWidth?: number;
|
||||
expandedInsideBorderSize?: number;
|
||||
collapsedInsideBorderSize?: number;
|
||||
collapsedOutsideBorderSize?: number;
|
||||
};
|
||||
|
||||
const SidePanelWithServices = ({
|
||||
servicesManager,
|
||||
side,
|
||||
activeTabIndex: activeTabIndexProp,
|
||||
isExpanded,
|
||||
tabs: tabsProp,
|
||||
expandedWidth,
|
||||
onOpen,
|
||||
onClose,
|
||||
...props
|
||||
}: SidePanelWithServicesProps) => {
|
||||
const 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 [sidePanelOpen, setSidePanelOpen] = useState(activeTabIndexProp !== null);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
|
||||
const [sidePanelExpanded, setSidePanelExpanded] = useState(isExpanded);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp ?? 0);
|
||||
const [closedManually, setClosedManually] = useState(false);
|
||||
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
|
||||
|
||||
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
|
||||
setActiveTabIndex(activeTabIndex);
|
||||
setSidePanelOpen(activeTabIndex !== null);
|
||||
}, []);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setSidePanelOpen(true);
|
||||
// If panel is being opened but no tab is active, set first tab as active
|
||||
if (activeTabIndex === null && tabs.length > 0) {
|
||||
setActiveTabIndex(0);
|
||||
}
|
||||
}, [activeTabIndex, tabs]);
|
||||
setSidePanelExpanded(true);
|
||||
onOpen?.();
|
||||
}, [onOpen]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setSidePanelOpen(false);
|
||||
setActiveTabIndex(null);
|
||||
}, []);
|
||||
setSidePanelExpanded(false);
|
||||
setClosedManually(true);
|
||||
onClose?.();
|
||||
}, [onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
setSidePanelExpanded(isExpanded);
|
||||
}, [isExpanded]);
|
||||
|
||||
/** update the active tab index from outside */
|
||||
useEffect(() => {
|
||||
setActiveTabIndex(activeTabIndexProp);
|
||||
setActiveTabIndex(activeTabIndexProp ?? 0);
|
||||
}, [activeTabIndexProp]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -71,9 +82,12 @@ const SidePanelWithServices = ({
|
||||
const activatePanelSubscription = panelService.subscribe(
|
||||
panelService.EVENTS.ACTIVATE_PANEL,
|
||||
(activatePanelEvent: Types.ActivatePanelEvent) => {
|
||||
if (sidePanelOpen || activatePanelEvent.forceActive) {
|
||||
if (sidePanelExpanded || activatePanelEvent.forceActive) {
|
||||
const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId);
|
||||
if (tabIndex !== -1) {
|
||||
if (!closedManually) {
|
||||
setSidePanelExpanded(true);
|
||||
}
|
||||
setActiveTabIndex(tabIndex);
|
||||
}
|
||||
}
|
||||
@ -83,7 +97,7 @@ const SidePanelWithServices = ({
|
||||
return () => {
|
||||
activatePanelSubscription.unsubscribe();
|
||||
};
|
||||
}, [tabs, sidePanelOpen, panelService]);
|
||||
}, [tabs, sidePanelExpanded, panelService, closedManually]);
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
@ -91,10 +105,10 @@ const SidePanelWithServices = ({
|
||||
side={side}
|
||||
tabs={tabs}
|
||||
activeTabIndex={activeTabIndex}
|
||||
isExpanded={sidePanelExpanded}
|
||||
onOpen={handleOpen}
|
||||
onClose={handleClose}
|
||||
onActiveTabIndexChange={handleActiveTabIndexChange}
|
||||
expandedWidth={expandedWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
325
extensions/default/src/ViewerLayout/ResizablePanelsHook.tsx
Normal file
325
extensions/default/src/ViewerLayout/ResizablePanelsHook.tsx
Normal file
@ -0,0 +1,325 @@
|
||||
import { useState, useCallback, useLayoutEffect, useRef } from 'react';
|
||||
import { getPanelElement, getPanelGroupElement } from 'react-resizable-panels';
|
||||
|
||||
// Id needed to grab the panel group for converting pixels to percentages
|
||||
const viewerLayoutResizablePanelGroupId = 'viewerLayoutResizablePanelGroup';
|
||||
const viewerLayoutResizableLeftPanelId = 'viewerLayoutResizableLeftPanel';
|
||||
const viewerLayoutResizableRightPanelId = 'viewerLayoutResizableRightPanel';
|
||||
|
||||
const sidePanelExpandedDefaultWidth = 280;
|
||||
const sidePanelExpandedInsideBorderSize = 4;
|
||||
const sidePanelExpandedDefaultOffsetWidth =
|
||||
sidePanelExpandedDefaultWidth + sidePanelExpandedInsideBorderSize;
|
||||
const sidePanelCollapsedInsideBorderSize = 4;
|
||||
const sidePanelCollapsedOutsideBorderSize = 8;
|
||||
const sidePanelCollapsedWidth = 25;
|
||||
const sidePanelCollapsedOffsetWidth =
|
||||
sidePanelCollapsedWidth +
|
||||
sidePanelCollapsedInsideBorderSize +
|
||||
sidePanelCollapsedOutsideBorderSize;
|
||||
|
||||
/**
|
||||
* Set the minimum and maximum css style width attributes for the given element.
|
||||
* The two style attributes are cleared whenever the width
|
||||
* arguments is undefined.
|
||||
* <p>
|
||||
* This utility is used as part of a HACK throughout the ViewerLayout component as
|
||||
* the means of restricting the side panel widths during the resizing of the
|
||||
* browser window. In general, the widths are always set unless the resize
|
||||
* handle for either side panel is being dragged (i.e. a side panel is being resized).
|
||||
*
|
||||
* @param elem the element
|
||||
* @param width the max and min width to set on the element
|
||||
*/
|
||||
const setMinMaxWidth = (elem, width?) => {
|
||||
elem.style.minWidth = width === undefined ? '' : `${width}px`;
|
||||
elem.style.maxWidth = elem.style.minWidth;
|
||||
};
|
||||
|
||||
const useResizablePanels = (
|
||||
leftPanelClosed,
|
||||
setLeftPanelClosed,
|
||||
rightPanelClosed,
|
||||
setRightPanelClosed
|
||||
) => {
|
||||
const [leftPanelExpandedWidth, setLeftPanelExpandedWidth] = useState(
|
||||
sidePanelExpandedDefaultWidth
|
||||
);
|
||||
const [rightPanelExpandedWidth, setRightPanelExpandedWidth] = useState(
|
||||
sidePanelExpandedDefaultWidth
|
||||
);
|
||||
|
||||
// Percentage sizes.
|
||||
const [resizablePanelCollapsedSize, setResizablePanelCollapsedSize] = useState(0);
|
||||
const [resizablePanelDefaultSize, setResizablePanelDefaultSize] = useState(0);
|
||||
|
||||
const resizablePanelGroupElemRef = useRef(null);
|
||||
const resizableLeftPanelElemRef = useRef(null);
|
||||
const resizableRightPanelElemRef = useRef(null);
|
||||
const resizableLeftPanelAPIRef = useRef(null);
|
||||
const resizableRightPanelAPIRef = useRef(null);
|
||||
const isResizableHandleDraggingRef = useRef(false);
|
||||
|
||||
// This useLayoutEffect is used to...
|
||||
// - Grab a reference to the various resizable panel elements needed for
|
||||
// converting between percentages and pixels in various callbacks.
|
||||
// - Expand those panels that are initially expanded.
|
||||
useLayoutEffect(() => {
|
||||
const panelGroupElem = getPanelGroupElement(viewerLayoutResizablePanelGroupId);
|
||||
|
||||
resizablePanelGroupElemRef.current = panelGroupElem;
|
||||
const { width: panelGroupWidth } = panelGroupElem.getBoundingClientRect();
|
||||
|
||||
const leftPanelElem = getPanelElement(viewerLayoutResizableLeftPanelId);
|
||||
resizableLeftPanelElemRef.current = leftPanelElem;
|
||||
|
||||
const rightPanelElem = getPanelElement(viewerLayoutResizableRightPanelId);
|
||||
resizableRightPanelElemRef.current = rightPanelElem;
|
||||
|
||||
const resizablePanelExpandedSize =
|
||||
(sidePanelExpandedDefaultOffsetWidth / panelGroupWidth) * 100;
|
||||
|
||||
// Since both resizable panels are collapsed by default (i.e. their default size is zero),
|
||||
// on the very first render check if either/both side panels should be expanded.
|
||||
if (!leftPanelClosed) {
|
||||
resizableLeftPanelAPIRef?.current?.expand(resizablePanelExpandedSize);
|
||||
setMinMaxWidth(leftPanelElem, sidePanelExpandedDefaultOffsetWidth);
|
||||
}
|
||||
|
||||
if (!rightPanelClosed) {
|
||||
resizableRightPanelAPIRef?.current?.expand(resizablePanelExpandedSize);
|
||||
setMinMaxWidth(rightPanelElem, sidePanelExpandedDefaultOffsetWidth);
|
||||
}
|
||||
}, []); // no dependencies because this useLayoutEffect is only needed on the very first render
|
||||
|
||||
// This useLayoutEffect follows the pattern prescribed by the react-resizable-panels
|
||||
// readme for converting between pixel values and percentages. An example of
|
||||
// the pattern can be found here:
|
||||
// https://github.com/bvaughn/react-resizable-panels/issues/46#issuecomment-1368108416
|
||||
// This useLayoutEffect is used to...
|
||||
// - Ensure that the percentage size is up-to-date with the pixel sizes
|
||||
// - Add a resize observer to the resizable panel group to reset various state
|
||||
// values whenever the resizable panel group is resized (e.g. whenever the
|
||||
// browser window is resized).
|
||||
useLayoutEffect(() => {
|
||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||
|
||||
// Ensure the side panels' percentage size is in synch with the pixel width of the
|
||||
// expanded side panels. In general the two get out-of-sync during a browser
|
||||
// window resize. Note that this code is here and NOT in the ResizeObserver
|
||||
// because it has to be done AFTER the minimum percentage size for a panel is
|
||||
// updated which occurs only AFTER the render following a resize. And by virtue
|
||||
// of the dependency on the `resizablePanelDefaultSize` state, this code
|
||||
// is executed on the render following an update of the minimum percentage size
|
||||
// for a panel.
|
||||
if (!resizableLeftPanelAPIRef.current.isCollapsed()) {
|
||||
const leftSize =
|
||||
((leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize) / panelGroupWidth) * 100;
|
||||
resizableLeftPanelAPIRef.current.resize(leftSize);
|
||||
}
|
||||
|
||||
if (!resizableRightPanelAPIRef.current.isCollapsed()) {
|
||||
const rightSize =
|
||||
((rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize) / panelGroupWidth) * 100;
|
||||
resizableRightPanelAPIRef.current.resize(rightSize);
|
||||
}
|
||||
|
||||
// This observer kicks in when the ViewportLayout resizable panel group
|
||||
// component is resized. This typically occurs when the browser window resizes.
|
||||
const observer = new ResizeObserver(() => {
|
||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||
const defaultSize = (sidePanelExpandedDefaultOffsetWidth / panelGroupWidth) * 100;
|
||||
|
||||
// Set the new default and collapsed resizable panel sizes.
|
||||
setResizablePanelDefaultSize(Math.min(50, defaultSize));
|
||||
setResizablePanelCollapsedSize((sidePanelCollapsedOffsetWidth / panelGroupWidth) * 100);
|
||||
|
||||
if (
|
||||
resizableLeftPanelAPIRef.current.isCollapsed() &&
|
||||
resizableRightPanelAPIRef.current.isCollapsed()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The code that follows is to handle cases when the group panel is resized to be
|
||||
// too small to display either side panel at its current width.
|
||||
|
||||
// Determine the current widths of the two side panels.
|
||||
let leftPanelOffsetWidth = resizableLeftPanelAPIRef.current.isCollapsed()
|
||||
? sidePanelCollapsedOffsetWidth
|
||||
: leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize;
|
||||
|
||||
let rightPanelOffsetWidth = resizableRightPanelAPIRef.current.isCollapsed()
|
||||
? sidePanelCollapsedOffsetWidth
|
||||
: rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize;
|
||||
|
||||
if (
|
||||
!resizableLeftPanelAPIRef.current.isCollapsed() &&
|
||||
leftPanelOffsetWidth + rightPanelOffsetWidth > panelGroupWidth
|
||||
) {
|
||||
// There is not enough space to show both panels at their pre-resize widths.
|
||||
// Note that at this point, the viewport grid component is zero width.
|
||||
// Reduce the left panel width so that both panels might fit.
|
||||
leftPanelOffsetWidth = Math.max(
|
||||
panelGroupWidth - rightPanelOffsetWidth,
|
||||
sidePanelExpandedDefaultOffsetWidth
|
||||
);
|
||||
setLeftPanelExpandedWidth(leftPanelOffsetWidth - sidePanelExpandedInsideBorderSize);
|
||||
setMinMaxWidth(resizableLeftPanelElemRef.current, leftPanelOffsetWidth);
|
||||
}
|
||||
|
||||
if (
|
||||
!resizableRightPanelAPIRef.current.isCollapsed() &&
|
||||
rightPanelOffsetWidth + leftPanelOffsetWidth > panelGroupWidth
|
||||
) {
|
||||
// There is not enough space to show both panels at their pre-resize widths.
|
||||
// Note that at this point, the viewport grid component is zero width.
|
||||
// Reduce the right panel width so that both panels might fit.
|
||||
rightPanelOffsetWidth = Math.max(
|
||||
panelGroupWidth - leftPanelOffsetWidth,
|
||||
sidePanelExpandedDefaultOffsetWidth
|
||||
);
|
||||
setRightPanelExpandedWidth(rightPanelOffsetWidth - sidePanelExpandedInsideBorderSize);
|
||||
setMinMaxWidth(resizableRightPanelElemRef.current, rightPanelOffsetWidth);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(resizablePanelGroupElemRef.current);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [leftPanelExpandedWidth, resizablePanelDefaultSize, rightPanelExpandedWidth]);
|
||||
|
||||
/**
|
||||
* Handles dragging of either side panel resize handle.
|
||||
*/
|
||||
const onHandleDragging = useCallback(
|
||||
isStartDrag => {
|
||||
if (isStartDrag) {
|
||||
isResizableHandleDraggingRef.current = true;
|
||||
|
||||
setMinMaxWidth(resizableLeftPanelElemRef.current);
|
||||
setMinMaxWidth(resizableRightPanelElemRef.current);
|
||||
} else {
|
||||
isResizableHandleDraggingRef.current = false;
|
||||
|
||||
if (resizableLeftPanelAPIRef?.current?.isExpanded()) {
|
||||
setMinMaxWidth(
|
||||
resizableLeftPanelElemRef.current,
|
||||
leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
||||
);
|
||||
}
|
||||
|
||||
if (resizableRightPanelAPIRef?.current?.isExpanded()) {
|
||||
setMinMaxWidth(
|
||||
resizableRightPanelElemRef.current,
|
||||
rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[leftPanelExpandedWidth, rightPanelExpandedWidth]
|
||||
);
|
||||
|
||||
const onLeftPanelClose = useCallback(() => {
|
||||
setLeftPanelClosed(true);
|
||||
setMinMaxWidth(resizableLeftPanelElemRef.current);
|
||||
resizableLeftPanelAPIRef?.current?.collapse();
|
||||
}, [setLeftPanelClosed]);
|
||||
|
||||
const onLeftPanelOpen = useCallback(() => {
|
||||
resizableLeftPanelAPIRef?.current?.expand();
|
||||
if (!isResizableHandleDraggingRef.current) {
|
||||
setMinMaxWidth(
|
||||
resizableLeftPanelElemRef.current,
|
||||
leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
||||
);
|
||||
}
|
||||
setLeftPanelClosed(false);
|
||||
}, [leftPanelExpandedWidth, setLeftPanelClosed]);
|
||||
|
||||
const onLeftPanelResize = useCallback(size => {
|
||||
if (resizableLeftPanelAPIRef.current.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||
setLeftPanelExpandedWidth((size / 100) * panelGroupWidth - sidePanelExpandedInsideBorderSize);
|
||||
}, []);
|
||||
|
||||
const onRightPanelClose = useCallback(() => {
|
||||
setRightPanelClosed(true);
|
||||
setMinMaxWidth(resizableRightPanelElemRef.current);
|
||||
resizableRightPanelAPIRef?.current?.collapse();
|
||||
}, [setRightPanelClosed]);
|
||||
|
||||
const onRightPanelOpen = useCallback(() => {
|
||||
resizableRightPanelAPIRef?.current?.expand();
|
||||
if (!isResizableHandleDraggingRef.current) {
|
||||
setMinMaxWidth(
|
||||
resizableRightPanelElemRef.current,
|
||||
rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
||||
);
|
||||
}
|
||||
setRightPanelClosed(false);
|
||||
}, [rightPanelExpandedWidth, setRightPanelClosed]);
|
||||
|
||||
const onRightPanelResize = useCallback(size => {
|
||||
if (resizableRightPanelAPIRef?.current?.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||
setRightPanelExpandedWidth((size / 100) * panelGroupWidth - sidePanelExpandedInsideBorderSize);
|
||||
}, []);
|
||||
|
||||
return [
|
||||
{
|
||||
expandedWidth: leftPanelExpandedWidth,
|
||||
collapsedWidth: sidePanelCollapsedWidth,
|
||||
collapsedInsideBorderSize: sidePanelCollapsedInsideBorderSize,
|
||||
collapsedOutsideBorderSize: sidePanelCollapsedOutsideBorderSize,
|
||||
expandedInsideBorderSize: sidePanelExpandedInsideBorderSize,
|
||||
onClose: onLeftPanelClose,
|
||||
onOpen: onLeftPanelOpen,
|
||||
},
|
||||
{
|
||||
expandedWidth: rightPanelExpandedWidth,
|
||||
collapsedWidth: sidePanelCollapsedWidth,
|
||||
collapsedInsideBorderSize: sidePanelCollapsedInsideBorderSize,
|
||||
collapsedOutsideBorderSize: sidePanelCollapsedOutsideBorderSize,
|
||||
expandedInsideBorderSize: sidePanelExpandedInsideBorderSize,
|
||||
onClose: onRightPanelClose,
|
||||
onOpen: onRightPanelOpen,
|
||||
},
|
||||
{ direction: 'horizontal', id: viewerLayoutResizablePanelGroupId },
|
||||
{
|
||||
defaultSize: resizablePanelDefaultSize,
|
||||
minSize: resizablePanelDefaultSize,
|
||||
onResize: onLeftPanelResize,
|
||||
collapsible: true,
|
||||
collapsedSize: resizablePanelCollapsedSize,
|
||||
onCollapse: () => setLeftPanelClosed(true),
|
||||
onExpand: () => setLeftPanelClosed(false),
|
||||
ref: resizableLeftPanelAPIRef,
|
||||
order: 0,
|
||||
id: viewerLayoutResizableLeftPanelId,
|
||||
},
|
||||
{ order: 1, id: 'viewerLayoutResizableViewportGridPanel' },
|
||||
{
|
||||
defaultSize: resizablePanelDefaultSize,
|
||||
minSize: resizablePanelDefaultSize,
|
||||
onResize: onRightPanelResize,
|
||||
collapsible: true,
|
||||
collapsedSize: resizablePanelCollapsedSize,
|
||||
onCollapse: () => setRightPanelClosed(true),
|
||||
onExpand: () => setRightPanelClosed(false),
|
||||
ref: resizableRightPanelAPIRef,
|
||||
order: 2,
|
||||
id: viewerLayoutResizableRightPanelId,
|
||||
},
|
||||
onHandleDragging,
|
||||
];
|
||||
};
|
||||
|
||||
export default useResizablePanels;
|
||||
@ -6,7 +6,8 @@ import { HangingProtocolService, CommandsManager } from '@ohif/core';
|
||||
import { useAppConfig } from '@state';
|
||||
import ViewerHeader from './ViewerHeader';
|
||||
import SidePanelWithServices from '../Components/SidePanelWithServices';
|
||||
import { Onboarding } from '@ohif/ui-next';
|
||||
import { Onboarding, ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@ohif/ui-next';
|
||||
import useResizablePanels from './ResizablePanelsHook';
|
||||
|
||||
function ViewerLayout({
|
||||
// From Extension Module Params
|
||||
@ -19,6 +20,8 @@ function ViewerLayout({
|
||||
ViewportGridComp,
|
||||
leftPanelClosed = false,
|
||||
rightPanelClosed = false,
|
||||
leftPanelResizable = false,
|
||||
rightPanelResizable = false,
|
||||
}: withAppTypes): React.FunctionComponent {
|
||||
const [appConfig] = useAppConfig();
|
||||
|
||||
@ -35,6 +38,21 @@ function ViewerLayout({
|
||||
const [leftPanelClosedState, setLeftPanelClosed] = useState(leftPanelClosed);
|
||||
const [rightPanelClosedState, setRightPanelClosed] = useState(rightPanelClosed);
|
||||
|
||||
const [
|
||||
leftPanelProps,
|
||||
rightPanelProps,
|
||||
resizablePanelGroupProps,
|
||||
resizableLeftPanelProps,
|
||||
resizableViewportGridPanelProps,
|
||||
resizableRightPanelProps,
|
||||
onHandleDragging,
|
||||
] = useResizablePanels(
|
||||
leftPanelClosed,
|
||||
setLeftPanelClosed,
|
||||
rightPanelClosed,
|
||||
setRightPanelClosed
|
||||
);
|
||||
|
||||
/**
|
||||
* Set body classes (tailwindcss) that don't allow vertical
|
||||
* or horizontal overflow (no scrolling). Also guarantee window
|
||||
@ -123,31 +141,56 @@ function ViewerLayout({
|
||||
>
|
||||
<React.Fragment>
|
||||
{showLoadingIndicator && <LoadingIndicatorProgress className="h-full w-full bg-black" />}
|
||||
{/* LEFT SIDEPANELS */}
|
||||
{hasLeftPanels ? (
|
||||
<SidePanelWithServices
|
||||
side="left"
|
||||
activeTabIndex={leftPanelClosedState ? null : 0}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
) : null}
|
||||
{/* TOOLBAR + GRID */}
|
||||
<div className="flex h-full flex-1 flex-col">
|
||||
<div className="relative flex h-full flex-1 items-center justify-center overflow-hidden bg-black">
|
||||
<ViewportGridComp
|
||||
servicesManager={servicesManager}
|
||||
viewportComponents={viewportComponents}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{hasRightPanels ? (
|
||||
<SidePanelWithServices
|
||||
side="right"
|
||||
activeTabIndex={rightPanelClosedState ? null : 0}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
) : null}
|
||||
<ResizablePanelGroup {...resizablePanelGroupProps}>
|
||||
{/* LEFT SIDEPANELS */}
|
||||
|
||||
{hasLeftPanels ? (
|
||||
<>
|
||||
<ResizablePanel {...resizableLeftPanelProps}>
|
||||
<SidePanelWithServices
|
||||
side="left"
|
||||
isExpanded={!leftPanelClosedState}
|
||||
servicesManager={servicesManager}
|
||||
{...leftPanelProps}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle
|
||||
onDragging={onHandleDragging}
|
||||
disabled={!leftPanelResizable}
|
||||
className="!w-0"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{/* TOOLBAR + GRID */}
|
||||
<ResizablePanel {...resizableViewportGridPanelProps}>
|
||||
<div className="flex h-full flex-1 flex-col">
|
||||
<div className="relative flex h-full flex-1 items-center justify-center overflow-hidden bg-black">
|
||||
<ViewportGridComp
|
||||
servicesManager={servicesManager}
|
||||
viewportComponents={viewportComponents}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
{hasRightPanels ? (
|
||||
<>
|
||||
<ResizableHandle
|
||||
onDragging={onHandleDragging}
|
||||
disabled={!rightPanelResizable}
|
||||
className="!w-0"
|
||||
/>
|
||||
<ResizablePanel {...resizableRightPanelProps}>
|
||||
<SidePanelWithServices
|
||||
side="right"
|
||||
isExpanded={!rightPanelClosedState}
|
||||
servicesManager={servicesManager}
|
||||
{...rightPanelProps}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</>
|
||||
) : null}
|
||||
</ResizablePanelGroup>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
<Onboarding />
|
||||
|
||||
@ -117,21 +117,30 @@ function modeFactory({ modeConfiguration }) {
|
||||
// // ActivatePanel event trigger for when a segmentation or measurement is added.
|
||||
// // Do not force activation so as to respect the state the user may have left the UI in.
|
||||
_activatePanelTriggersSubscriptions = [
|
||||
...panelService.addActivatePanelTriggers(cornerstone.segmentation, [
|
||||
{
|
||||
sourcePubSubService: segmentationService,
|
||||
sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
|
||||
},
|
||||
]),
|
||||
...panelService.addActivatePanelTriggers(tracked.measurements, [
|
||||
{
|
||||
sourcePubSubService: measurementService,
|
||||
sourceEvents: [
|
||||
measurementService.EVENTS.MEASUREMENT_ADDED,
|
||||
measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
|
||||
],
|
||||
},
|
||||
]),
|
||||
...panelService.addActivatePanelTriggers(
|
||||
cornerstone.segmentation,
|
||||
[
|
||||
{
|
||||
sourcePubSubService: segmentationService,
|
||||
sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
|
||||
},
|
||||
],
|
||||
true
|
||||
),
|
||||
...panelService.addActivatePanelTriggers(
|
||||
tracked.measurements,
|
||||
[
|
||||
{
|
||||
sourcePubSubService: measurementService,
|
||||
sourceEvents: [
|
||||
measurementService.EVENTS.MEASUREMENT_ADDED,
|
||||
measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
|
||||
],
|
||||
},
|
||||
],
|
||||
true
|
||||
),
|
||||
true,
|
||||
];
|
||||
},
|
||||
onModeExit: ({ servicesManager }: withAppTypes) => {
|
||||
@ -181,8 +190,10 @@ function modeFactory({ modeConfiguration }) {
|
||||
id: ohif.layout,
|
||||
props: {
|
||||
leftPanels: [tracked.thumbnailList],
|
||||
leftPanelResizable: true,
|
||||
rightPanels: [cornerstone.segmentation, tracked.measurements],
|
||||
rightPanelClosed: true,
|
||||
rightPanelResizable: true,
|
||||
viewports: [
|
||||
{
|
||||
namespace: tracked.viewport,
|
||||
|
||||
@ -15,7 +15,7 @@ describe('OHIF Measurement Panel', function () {
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.exist');
|
||||
|
||||
cy.get('@RightCollapseBtn').click();
|
||||
cy.get('@RightCollapseBtn').click({ force: true });
|
||||
|
||||
// segmentation panel should be visible
|
||||
cy.get('@segmentationPanel').should('be.visible');
|
||||
|
||||
@ -151,13 +151,7 @@ export default class PanelService extends PubSubService {
|
||||
panelsIds.forEach(panelId => this.addPanel(position, panelId, options));
|
||||
}
|
||||
|
||||
public setPanels(
|
||||
panels: { [key in PanelPosition]: string[] },
|
||||
options: {
|
||||
rightPanelClosed?: boolean;
|
||||
leftPanelClosed?: boolean;
|
||||
}
|
||||
): void {
|
||||
public setPanels(panels: { [key in PanelPosition]: string[] }, options): void {
|
||||
this.reset();
|
||||
|
||||
Object.keys(panels).forEach((position: PanelPosition) => {
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-shepherd": "6.1.1",
|
||||
"shepherd.js": "13.0.3",
|
||||
"sonner": "^1.5.0",
|
||||
|
||||
43
platform/ui-next/src/components/Resizable/Resizable.tsx
Normal file
43
platform/ui-next/src/components/Resizable/Resizable.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
|
||||
import { GripVertical } from 'lucide-react';
|
||||
import * as ResizablePrimitive from 'react-resizable-panels';
|
||||
|
||||
import cn from 'classnames';
|
||||
|
||||
const ResizablePanelGroup = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
className={cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const ResizablePanel = ResizablePrimitive.Panel;
|
||||
|
||||
const ResizableHandle = ({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean;
|
||||
}) => (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
className={cn(
|
||||
'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border">
|
||||
<GripVertical className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
);
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
|
||||
3
platform/ui-next/src/components/Resizable/index.ts
Normal file
3
platform/ui-next/src/components/Resizable/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from './Resizable';
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
|
||||
@ -1,39 +1,64 @@
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Icons } from '../Icons';
|
||||
import { TooltipTrigger, TooltipContent, TooltipProvider, Tooltip } from '../Tooltip';
|
||||
import { TooltipTrigger, TooltipContent, Tooltip } from '../Tooltip';
|
||||
import { Separator } from '../Separator';
|
||||
|
||||
/**
|
||||
* SidePanel component properties.
|
||||
* Note that the component monitors changes to the various widths and border sizes and will resize dynamically
|
||||
* @property {boolean} isExpanded - boolean indicating if the side panel is expanded/open or collapsed
|
||||
* @property {number} expandedWidth - the width of this side panel when expanded not including any borders or margins
|
||||
* @property {number} collapsedWidth - the width of this side panel when collapsed not including any borders or margins
|
||||
* @property {number} expandedInsideBorderSize - the width of the space between the expanded side panel content and viewport grid
|
||||
* @property {number} collapsedInsideBorderSize - the width of the space between the collapsed side panel content and the viewport grid
|
||||
* @property {number} collapsedOutsideBorderSize - the width of the space between the collapsed side panel content and the edge of the browser window
|
||||
*/
|
||||
type SidePanelProps = {
|
||||
side: 'left' | 'right';
|
||||
className: string;
|
||||
activeTabIndex: number;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
onActiveTabIndexChange: () => void;
|
||||
isExpanded: boolean;
|
||||
expandedWidth: number;
|
||||
collapsedWidth: number;
|
||||
expandedInsideBorderSize: number;
|
||||
collapsedInsideBorderSize: number;
|
||||
collapsedOutsideBorderSize: number;
|
||||
tabs: any;
|
||||
};
|
||||
|
||||
type StyleMap = {
|
||||
open: {
|
||||
left: { marginLeft: string };
|
||||
right: { marginRight: string };
|
||||
left: {
|
||||
marginLeft: string; // the space between the expanded/open left side panel and the browser window left edge
|
||||
marginRight: string; // the space between the expanded/open left side panel and the viewport grid
|
||||
};
|
||||
right: {
|
||||
marginLeft: string; // the space between the expanded/open right side panel and the viewport grid
|
||||
marginRight: string; // the space between the expanded/open right side panel and the browser window right edge
|
||||
};
|
||||
};
|
||||
closed: {
|
||||
left: { marginLeft: string };
|
||||
right: { marginRight: string };
|
||||
left: {
|
||||
marginLeft: string; // the space between the collapsed/closed left panel and the browser window left edge
|
||||
marginRight: string; // the space between the collapsed/closed left panel and the viewport grid
|
||||
alignItems: 'flex-end'; // the flexbox layout align-items property
|
||||
};
|
||||
right: {
|
||||
marginLeft: string; // the space between the collapsed/closed right panel and the viewport grid
|
||||
marginRight: string; // the space between the collapsed/closed right panel and the browser window right edge
|
||||
alignItems: 'flex-start'; // the flexbox layout align-items property
|
||||
};
|
||||
};
|
||||
};
|
||||
const borderSize = 4;
|
||||
const collapsedWidth = 25;
|
||||
const closeIconWidth = 30;
|
||||
const gridHorizontalPadding = 10;
|
||||
const tabSpacerWidth = 2;
|
||||
|
||||
const baseClasses =
|
||||
'transition-all duration-300 ease-in-out 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 baseClasses = 'bg-black border-black justify-start box-content flex flex-col';
|
||||
|
||||
const openStateIconName = {
|
||||
left: 'SidePanelCloseLeft',
|
||||
@ -106,19 +131,29 @@ const getTabIconClassNames = (numTabs: number, isActiveTab: boolean) => {
|
||||
};
|
||||
const createStyleMap = (
|
||||
expandedWidth: number,
|
||||
borderSize: number,
|
||||
collapsedWidth: number
|
||||
expandedInsideBorderSize: number,
|
||||
collapsedWidth: number,
|
||||
collapsedInsideBorderSize: number,
|
||||
collapsedOutsideBorderSize: number
|
||||
): StyleMap => {
|
||||
const collapsedHideWidth = expandedWidth - collapsedWidth - borderSize;
|
||||
const collapsedHideWidth = expandedWidth - collapsedWidth - collapsedInsideBorderSize;
|
||||
|
||||
return {
|
||||
open: {
|
||||
left: { marginLeft: '0px' },
|
||||
right: { marginRight: '0px' },
|
||||
left: { marginLeft: '0px', marginRight: `${expandedInsideBorderSize}px` },
|
||||
right: { marginLeft: `${expandedInsideBorderSize}px`, marginRight: '0px' },
|
||||
},
|
||||
closed: {
|
||||
left: { marginLeft: `-${collapsedHideWidth}px` },
|
||||
right: { marginRight: `-${collapsedHideWidth}px` },
|
||||
left: {
|
||||
marginLeft: `-${collapsedHideWidth}px`,
|
||||
marginRight: `${collapsedOutsideBorderSize}px`,
|
||||
alignItems: `flex-end`,
|
||||
},
|
||||
right: {
|
||||
marginLeft: `${collapsedOutsideBorderSize}px`,
|
||||
marginRight: `-${collapsedHideWidth}px`,
|
||||
alignItems: `flex-start`,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -143,47 +178,67 @@ const createBaseStyle = (expandedWidth: number) => {
|
||||
height: '99.8%',
|
||||
};
|
||||
};
|
||||
|
||||
const SidePanel = ({
|
||||
side,
|
||||
className,
|
||||
activeTabIndex: activeTabIndexProp = null,
|
||||
activeTabIndex: activeTabIndexProp,
|
||||
isExpanded,
|
||||
tabs,
|
||||
onOpen,
|
||||
onClose,
|
||||
expandedWidth = 280,
|
||||
onActiveTabIndexChange,
|
||||
}) => {
|
||||
const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
||||
expandedWidth = 280,
|
||||
collapsedWidth = 25,
|
||||
expandedInsideBorderSize = 4,
|
||||
collapsedInsideBorderSize = 8,
|
||||
collapsedOutsideBorderSize = 4,
|
||||
}: SidePanelProps) => {
|
||||
const [panelOpen, setPanelOpen] = useState(isExpanded);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp ?? 0);
|
||||
|
||||
const styleMap = createStyleMap(expandedWidth, borderSize, collapsedWidth);
|
||||
const baseStyle = createBaseStyle(expandedWidth);
|
||||
const gridAvailableWidth = expandedWidth - closeIconWidth - gridHorizontalPadding;
|
||||
const gridWidth = getGridWidth(tabs.length, gridAvailableWidth);
|
||||
const [styleMap, setStyleMap] = useState(
|
||||
createStyleMap(
|
||||
expandedWidth,
|
||||
expandedInsideBorderSize,
|
||||
collapsedWidth,
|
||||
collapsedInsideBorderSize,
|
||||
collapsedOutsideBorderSize
|
||||
)
|
||||
);
|
||||
|
||||
const [baseStyle, setBaseStyle] = useState(createBaseStyle(expandedWidth));
|
||||
|
||||
const [gridAvailableWidth, setGridAvailableWidth] = useState(
|
||||
expandedWidth - closeIconWidth - gridHorizontalPadding
|
||||
);
|
||||
|
||||
const [gridWidth, setGridWidth] = useState(getGridWidth(tabs.length, gridAvailableWidth));
|
||||
const openStatus = panelOpen ? 'open' : 'closed';
|
||||
const style = Object.assign({}, styleMap[openStatus][side], baseStyle);
|
||||
|
||||
const updatePanelOpen = useCallback(
|
||||
(panelOpen: boolean) => {
|
||||
setPanelOpen(panelOpen);
|
||||
if (panelOpen && onOpen) {
|
||||
onOpen();
|
||||
} else if (onClose && !panelOpen) {
|
||||
onClose();
|
||||
(isOpen: boolean) => {
|
||||
setPanelOpen(isOpen);
|
||||
if (isOpen !== panelOpen) {
|
||||
// only fire events for changes
|
||||
if (isOpen && onOpen) {
|
||||
onOpen();
|
||||
} else if (onClose && !isOpen) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
},
|
||||
[onOpen, onClose]
|
||||
[panelOpen, onOpen, onClose]
|
||||
);
|
||||
|
||||
const updateActiveTabIndex = useCallback(
|
||||
(activeTabIndex: number) => {
|
||||
if (activeTabIndex === null) {
|
||||
updatePanelOpen(false);
|
||||
return;
|
||||
(activeTabIndex: number, forceOpen: boolean = false) => {
|
||||
if (forceOpen) {
|
||||
updatePanelOpen(true);
|
||||
}
|
||||
|
||||
setActiveTabIndex(activeTabIndex);
|
||||
updatePanelOpen(true);
|
||||
|
||||
if (onActiveTabIndexChange) {
|
||||
onActiveTabIndexChange({ activeTabIndex });
|
||||
@ -193,7 +248,35 @@ const SidePanel = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateActiveTabIndex(activeTabIndexProp);
|
||||
updatePanelOpen(isExpanded);
|
||||
}, [isExpanded, updatePanelOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
setStyleMap(
|
||||
createStyleMap(
|
||||
expandedWidth,
|
||||
expandedInsideBorderSize,
|
||||
collapsedWidth,
|
||||
collapsedInsideBorderSize,
|
||||
collapsedOutsideBorderSize
|
||||
)
|
||||
);
|
||||
setBaseStyle(createBaseStyle(expandedWidth));
|
||||
|
||||
const gridAvailableWidth = expandedWidth - closeIconWidth - gridHorizontalPadding;
|
||||
setGridAvailableWidth(gridAvailableWidth);
|
||||
setGridWidth(getGridWidth(tabs.length, gridAvailableWidth));
|
||||
}, [
|
||||
collapsedInsideBorderSize,
|
||||
collapsedWidth,
|
||||
expandedWidth,
|
||||
expandedInsideBorderSize,
|
||||
tabs.length,
|
||||
collapsedOutsideBorderSize,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
updateActiveTabIndex(activeTabIndexProp ?? 0);
|
||||
}, [activeTabIndexProp, updateActiveTabIndex]);
|
||||
|
||||
const getCloseStateComponent = () => {
|
||||
@ -223,7 +306,7 @@ const SidePanel = ({
|
||||
data-cy={`${childComponent.name}-btn`}
|
||||
className="text-primary-active hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
return childComponent.disabled ? null : updateActiveTabIndex(index);
|
||||
return childComponent.disabled ? null : updateActiveTabIndex(index, true);
|
||||
}}
|
||||
>
|
||||
{React.createElement(Icons[childComponent.iconName] || Icons.MissingIcon, {
|
||||
@ -374,7 +457,7 @@ const SidePanel = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, baseClasses, classesMap[openStatus][side])}
|
||||
className={classnames(className, baseClasses)}
|
||||
style={style}
|
||||
>
|
||||
{panelOpen ? (
|
||||
@ -394,14 +477,4 @@ const SidePanel = ({
|
||||
);
|
||||
};
|
||||
|
||||
SidePanel.propTypes = {
|
||||
side: PropTypes.oneOf(['left', 'right']).isRequired,
|
||||
className: PropTypes.string,
|
||||
activeTabIndex: PropTypes.number,
|
||||
onOpen: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
onActiveTabIndexChange: PropTypes.func,
|
||||
expandedWidth: PropTypes.number,
|
||||
};
|
||||
|
||||
export { SidePanel };
|
||||
|
||||
@ -27,6 +27,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '.
|
||||
import { Clipboard } from './Clipboard';
|
||||
import { Combobox } from './Combobox';
|
||||
import { Popover, PopoverContent, PopoverTrigger, PopoverAnchor } from './Popover';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from './Resizable';
|
||||
import { Calendar } from './Calendar';
|
||||
import { DatePickerWithRange } from './DateRange';
|
||||
import { Separator } from './Separator';
|
||||
@ -121,6 +122,9 @@ export {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
PopoverAnchor,
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
Calendar,
|
||||
DatePickerWithRange,
|
||||
Input,
|
||||
|
||||
@ -44,6 +44,9 @@ import {
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
ResizablePanelGroup,
|
||||
ResizablePanel,
|
||||
ResizableHandle,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
@ -150,6 +153,9 @@ export {
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
ResizablePanelGroup,
|
||||
ResizablePanel,
|
||||
ResizableHandle,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
|
||||
163
yarn.lock
163
yarn.lock
@ -1394,9 +1394,9 @@
|
||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||
|
||||
"@cornerstonejs/adapters@^2.15.3":
|
||||
version "2.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.15.3.tgz#0b29519748fd1a51522f5a0c362317d51af98a7f"
|
||||
integrity sha512-TeYSM+qUh+x1XrPR4Tqowp3+T6X+g+H9i2siF33gOw+cIDi4EQ14WNcvWSB2tHmqHvg/Rmta9Kvgha0dSoWTzA==
|
||||
version "2.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.15.5.tgz#2fce5e7ad5a3e7b5f229c5a1f4e07d6c91f9836c"
|
||||
integrity sha512-ohdp8waX52ZpPhnrJJnM64Dlc2+X+DCL8HvYB/SjvE/H1YsHk1Pa5trd5taEKq1GJks3LX19XIuWTpKj/wXTUA==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
buffer "^6.0.3"
|
||||
@ -1430,18 +1430,18 @@
|
||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||
|
||||
"@cornerstonejs/core@^2.15.3":
|
||||
version "2.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.15.3.tgz#62b82ac8ebc6a5cf30f96e358bf7654f4eee71b4"
|
||||
integrity sha512-qQDvDUtONz7ydI6yf2RPI+p7/846IxnSPgCPgqLaVohjWjHeZkyhV5oY6lN50ZxgHkWmBvinw6l2wjvh4x1bKw==
|
||||
version "2.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.15.5.tgz#2c81bf28a1e15dfbe25215a7df6ae29b70e7d621"
|
||||
integrity sha512-P+Dzsdgp9uSqcIBRECHI0Kj1G2//2UacstRmsRc3uxXgJ0lJ7AaLS31F4xVNOf+5mHMsB8BUsxdDg6iujX9lQA==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "32.9.0"
|
||||
comlink "^4.4.1"
|
||||
gl-matrix "^3.4.3"
|
||||
|
||||
"@cornerstonejs/dicom-image-loader@^2.15.3":
|
||||
version "2.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.15.3.tgz#1ab90d03fab2674cbdfd6a56f4d3da585f69abf6"
|
||||
integrity sha512-/njbngdKBV3DEcD5UrPD9B3QC/eXS8gx9U3hfAvABFihaSQFd7/rEgKte1EbShQ9Qdtx1FPiaX+uMmhGPxVsQA==
|
||||
version "2.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.15.5.tgz#867636c95321dc03d5eb960266af300609f28d54"
|
||||
integrity sha512-XbWHlfNe6/+W6xFYSSBcOQNTJfCg0FRduh+AXfMxES96xTSdK/1LObeOOtVq5QCslUU90ERHuUYSY8wDNiys7w==
|
||||
dependencies:
|
||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||
@ -1453,9 +1453,9 @@
|
||||
uuid "^9.0.0"
|
||||
|
||||
"@cornerstonejs/tools@^2.15.3":
|
||||
version "2.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.15.3.tgz#b778f67f13faf62d625c6ce8bbe8eed656f29ba0"
|
||||
integrity sha512-NzEeII/xTc4kFjvCZ0HV3LNOtlkFvPobxwHePuMq9wa8/2M89+blDrbrm1IOkO0xKrKMNnTqt1wTzBjcy+TKGA==
|
||||
version "2.15.5"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.15.5.tgz#e38ff0e5a7158f073f361068ff0bbc2fe337fe7b"
|
||||
integrity sha512-uOphCv2ulmRijy72emPUkXYw1JXYYygTPKWNmaEA6PX17fE6NlQfYdJc4GHVkXiqj2YJCDzN1PwukI1dWfL9Vg==
|
||||
dependencies:
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
@ -3731,10 +3731,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
|
||||
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
|
||||
|
||||
"@remix-run/router@1.21.0":
|
||||
version "1.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5"
|
||||
integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==
|
||||
"@remix-run/router@1.19.2":
|
||||
version "1.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273"
|
||||
integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.1"
|
||||
@ -5068,9 +5068,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0":
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.4.tgz#88c29e3052cec3536d64b6ce5015a30dfcbefca7"
|
||||
integrity sha512-5kz9ScmzBdzTgB/3susoCgfqNDzBjvLL4taparufgSvlwjdLy6UyUy9T/tCpYd2GIdIilCatC4iSQS0QSYHt0w==
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz#91f06cda1049e8f17eeab364798ed79c97488a1c"
|
||||
integrity sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/qs" "*"
|
||||
@ -5480,9 +5480,9 @@
|
||||
integrity sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==
|
||||
|
||||
"@types/ws@^8.2.2":
|
||||
version "8.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20"
|
||||
integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==
|
||||
version "8.5.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e"
|
||||
integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@ -10099,44 +10099,7 @@ exponential-backoff@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
|
||||
|
||||
express@^4.17.1:
|
||||
version "4.21.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32"
|
||||
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
|
||||
dependencies:
|
||||
accepts "~1.3.8"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.20.3"
|
||||
content-disposition "0.5.4"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.7.1"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
encodeurl "~2.0.0"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "1.3.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "2.0.0"
|
||||
merge-descriptors "1.0.3"
|
||||
methods "~1.1.2"
|
||||
on-finished "2.4.1"
|
||||
parseurl "~1.3.3"
|
||||
path-to-regexp "0.1.12"
|
||||
proxy-addr "~2.0.7"
|
||||
qs "6.13.0"
|
||||
range-parser "~1.2.1"
|
||||
safe-buffer "5.2.1"
|
||||
send "0.19.0"
|
||||
serve-static "1.16.2"
|
||||
setprototypeof "1.2.0"
|
||||
statuses "2.0.1"
|
||||
type-is "~1.6.18"
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
express@^4.17.3:
|
||||
express@^4.17.1, express@^4.17.3:
|
||||
version "4.21.1"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281"
|
||||
integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==
|
||||
@ -10531,12 +10494,7 @@ flow-parser@0.*:
|
||||
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.247.1.tgz#4a512c882cd08126825fe00b1ee452637ef44371"
|
||||
integrity sha512-DHwcm06fWbn2Z6uFD3NaBZ5lMOoABIQ4asrVA80IWvYjjT5WdbghkUOL1wIcbLcagnFTdCZYOlSNnKNp/xnRZQ==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.15.9"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
|
||||
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
|
||||
|
||||
follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6:
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6:
|
||||
version "1.15.6"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||
@ -11468,9 +11426,9 @@ http-errors@~1.6.2:
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
http-parser-js@>=0.5.1:
|
||||
version "0.5.9"
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.9.tgz#b817b3ca0edea6236225000d795378707c169cec"
|
||||
integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==
|
||||
version "0.5.8"
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
|
||||
integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
|
||||
|
||||
http-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
@ -15701,11 +15659,6 @@ path-to-regexp@0.1.10:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
|
||||
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
|
||||
|
||||
path-to-regexp@0.1.12:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
|
||||
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
|
||||
|
||||
path-to-regexp@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
|
||||
@ -17160,6 +17113,11 @@ react-remove-scroll@^2.6.1:
|
||||
use-callback-ref "^1.3.3"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-resizable-panels@^2.1.7:
|
||||
version "2.1.7"
|
||||
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz#afd29d8a3d708786a9f95183a38803c89f13c2e7"
|
||||
integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==
|
||||
|
||||
react-resize-detector@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-10.0.1.tgz#ae9a8c5b6b93c4c11e03b3eb87e57fd7b62f1020"
|
||||
@ -17168,19 +17126,19 @@ react-resize-detector@^10.0.1:
|
||||
lodash "^4.17.21"
|
||||
|
||||
react-router-dom@^6.8.1:
|
||||
version "6.28.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.1.tgz#b78fe452d2cd31919b80e57047a896bfa1509f8c"
|
||||
integrity sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==
|
||||
version "6.26.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.26.2.tgz#a6e3b0cbd6bfd508e42b9342099d015a0ac59680"
|
||||
integrity sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.21.0"
|
||||
react-router "6.28.1"
|
||||
"@remix-run/router" "1.19.2"
|
||||
react-router "6.26.2"
|
||||
|
||||
react-router@6.28.1, react-router@^6.23.1:
|
||||
version "6.28.1"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.1.tgz#f82317ab24eee67d7beb7b304c0378b2b48fa178"
|
||||
integrity sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==
|
||||
react-router@6.26.2, react-router@^6.23.1:
|
||||
version "6.26.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.26.2.tgz#2f0a68999168954431cdc29dd36cec3b6fa44a7e"
|
||||
integrity sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.21.0"
|
||||
"@remix-run/router" "1.19.2"
|
||||
|
||||
react-select@5.7.4:
|
||||
version "5.7.4"
|
||||
@ -17406,9 +17364,9 @@ readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09"
|
||||
integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91"
|
||||
integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==
|
||||
dependencies:
|
||||
abort-controller "^3.0.0"
|
||||
buffer "^6.0.3"
|
||||
@ -18699,7 +18657,7 @@ string-natural-compare@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -18717,15 +18675,6 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
@ -18828,7 +18777,7 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -18849,13 +18798,6 @@ strip-ansi@^4.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.0, strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@ -20687,7 +20629,7 @@ worker-loader@3.0.8, worker-loader@^3.0.8:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -20713,15 +20655,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user