feat(panels): responsive thumbnails based on panel size (#4723)
This commit is contained in:
parent
9d07705a1f
commit
d9abc3da8d
@ -1,22 +1,6 @@
|
|||||||
import { useState, useCallback, useLayoutEffect, useRef } from 'react';
|
import { useState, useCallback, useLayoutEffect, useRef } from 'react';
|
||||||
import { getPanelElement, getPanelGroupElement } from 'react-resizable-panels';
|
import { getPanelElement, getPanelGroupElement } from 'react-resizable-panels';
|
||||||
|
import { panelGroupDefinition } from './constants/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.
|
* Set the minimum and maximum css style width attributes for the given element.
|
||||||
@ -43,15 +27,15 @@ const useResizablePanels = (
|
|||||||
setRightPanelClosed
|
setRightPanelClosed
|
||||||
) => {
|
) => {
|
||||||
const [leftPanelExpandedWidth, setLeftPanelExpandedWidth] = useState(
|
const [leftPanelExpandedWidth, setLeftPanelExpandedWidth] = useState(
|
||||||
sidePanelExpandedDefaultWidth
|
panelGroupDefinition.left.initialExpandedWidth
|
||||||
);
|
);
|
||||||
const [rightPanelExpandedWidth, setRightPanelExpandedWidth] = useState(
|
const [rightPanelExpandedWidth, setRightPanelExpandedWidth] = useState(
|
||||||
sidePanelExpandedDefaultWidth
|
panelGroupDefinition.right.initialExpandedWidth
|
||||||
);
|
);
|
||||||
|
const [leftResizablePanelMinimumSize, setLeftResizablePanelMinimumSize] = useState(0);
|
||||||
// Percentage sizes.
|
const [rightResizablePanelMinimumSize, setRightResizablePanelMinimumSize] = useState(0);
|
||||||
const [resizablePanelCollapsedSize, setResizablePanelCollapsedSize] = useState(0);
|
const [leftResizeablePanelCollapsedSize, setLeftResizePanelCollapsedSize] = useState(0);
|
||||||
const [resizablePanelDefaultSize, setResizablePanelDefaultSize] = useState(0);
|
const [rightResizePanelCollapsedSize, setRightResizePanelCollapsedSize] = useState(0);
|
||||||
|
|
||||||
const resizablePanelGroupElemRef = useRef(null);
|
const resizablePanelGroupElemRef = useRef(null);
|
||||||
const resizableLeftPanelElemRef = useRef(null);
|
const resizableLeftPanelElemRef = useRef(null);
|
||||||
@ -65,30 +49,33 @@ const useResizablePanels = (
|
|||||||
// converting between percentages and pixels in various callbacks.
|
// converting between percentages and pixels in various callbacks.
|
||||||
// - Expand those panels that are initially expanded.
|
// - Expand those panels that are initially expanded.
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const panelGroupElem = getPanelGroupElement(viewerLayoutResizablePanelGroupId);
|
const panelGroupElem = getPanelGroupElement(panelGroupDefinition.groupId);
|
||||||
|
|
||||||
resizablePanelGroupElemRef.current = panelGroupElem;
|
resizablePanelGroupElemRef.current = panelGroupElem;
|
||||||
const { width: panelGroupWidth } = panelGroupElem.getBoundingClientRect();
|
const { width: panelGroupWidth } = panelGroupElem.getBoundingClientRect();
|
||||||
|
|
||||||
const leftPanelElem = getPanelElement(viewerLayoutResizableLeftPanelId);
|
const leftPanelElem = getPanelElement(panelGroupDefinition.left.panelId);
|
||||||
resizableLeftPanelElemRef.current = leftPanelElem;
|
resizableLeftPanelElemRef.current = leftPanelElem;
|
||||||
|
|
||||||
const rightPanelElem = getPanelElement(viewerLayoutResizableRightPanelId);
|
const rightPanelElem = getPanelElement(panelGroupDefinition.right.panelId);
|
||||||
resizableRightPanelElemRef.current = rightPanelElem;
|
resizableRightPanelElemRef.current = rightPanelElem;
|
||||||
|
|
||||||
const resizablePanelExpandedSize =
|
|
||||||
(sidePanelExpandedDefaultOffsetWidth / panelGroupWidth) * 100;
|
|
||||||
|
|
||||||
// Since both resizable panels are collapsed by default (i.e. their default size is zero),
|
// 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.
|
// on the very first render check if either/both side panels should be expanded.
|
||||||
|
// we use the initialExpandedOffsetWidth on the first render incase the panel has min width but we want the initial state to be larger than that
|
||||||
|
|
||||||
if (!leftPanelClosed) {
|
if (!leftPanelClosed) {
|
||||||
resizableLeftPanelAPIRef?.current?.expand(resizablePanelExpandedSize);
|
const leftResizablePanelExpandedSize =
|
||||||
setMinMaxWidth(leftPanelElem, sidePanelExpandedDefaultOffsetWidth);
|
(panelGroupDefinition.left.initialExpandedOffsetWidth / panelGroupWidth) * 100;
|
||||||
|
resizableLeftPanelAPIRef?.current?.expand(leftResizablePanelExpandedSize);
|
||||||
|
setMinMaxWidth(leftPanelElem, panelGroupDefinition.left.initialExpandedOffsetWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rightPanelClosed) {
|
if (!rightPanelClosed) {
|
||||||
resizableRightPanelAPIRef?.current?.expand(resizablePanelExpandedSize);
|
const rightResizablePanelExpandedSize =
|
||||||
setMinMaxWidth(rightPanelElem, sidePanelExpandedDefaultOffsetWidth);
|
(panelGroupDefinition.right.initialExpandedOffsetWidth / panelGroupWidth) * 100;
|
||||||
|
resizableRightPanelAPIRef?.current?.expand(rightResizablePanelExpandedSize);
|
||||||
|
setMinMaxWidth(rightPanelElem, panelGroupDefinition.right.initialExpandedOffsetWidth);
|
||||||
}
|
}
|
||||||
}, []); // no dependencies because this useLayoutEffect is only needed on the very first render
|
}, []); // no dependencies because this useLayoutEffect is only needed on the very first render
|
||||||
|
|
||||||
@ -114,13 +101,17 @@ const useResizablePanels = (
|
|||||||
// for a panel.
|
// for a panel.
|
||||||
if (!resizableLeftPanelAPIRef.current.isCollapsed()) {
|
if (!resizableLeftPanelAPIRef.current.isCollapsed()) {
|
||||||
const leftSize =
|
const leftSize =
|
||||||
((leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize) / panelGroupWidth) * 100;
|
((leftPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize) /
|
||||||
|
panelGroupWidth) *
|
||||||
|
100;
|
||||||
resizableLeftPanelAPIRef.current.resize(leftSize);
|
resizableLeftPanelAPIRef.current.resize(leftSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resizableRightPanelAPIRef.current.isCollapsed()) {
|
if (!resizableRightPanelAPIRef.current.isCollapsed()) {
|
||||||
const rightSize =
|
const rightSize =
|
||||||
((rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize) / panelGroupWidth) * 100;
|
((rightPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize) /
|
||||||
|
panelGroupWidth) *
|
||||||
|
100;
|
||||||
resizableRightPanelAPIRef.current.resize(rightSize);
|
resizableRightPanelAPIRef.current.resize(rightSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,11 +119,20 @@ const useResizablePanels = (
|
|||||||
// component is resized. This typically occurs when the browser window resizes.
|
// component is resized. This typically occurs when the browser window resizes.
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||||
const defaultSize = (sidePanelExpandedDefaultOffsetWidth / panelGroupWidth) * 100;
|
const minimumLeftSize =
|
||||||
|
(panelGroupDefinition.left.minimumExpandedOffsetWidth / panelGroupWidth) * 100;
|
||||||
|
const minimumRightSize =
|
||||||
|
(panelGroupDefinition.right.minimumExpandedOffsetWidth / panelGroupWidth) * 100;
|
||||||
|
|
||||||
// Set the new default and collapsed resizable panel sizes.
|
// Set the new default and collapsed resizable panel sizes.
|
||||||
setResizablePanelDefaultSize(Math.min(50, defaultSize));
|
setLeftResizablePanelMinimumSize(Math.min(50, minimumLeftSize));
|
||||||
setResizablePanelCollapsedSize((sidePanelCollapsedOffsetWidth / panelGroupWidth) * 100);
|
setRightResizablePanelMinimumSize(Math.min(50, minimumRightSize));
|
||||||
|
setLeftResizePanelCollapsedSize(
|
||||||
|
(panelGroupDefinition.left.collapsedOffsetWidth / panelGroupWidth) * 100
|
||||||
|
);
|
||||||
|
setRightResizePanelCollapsedSize(
|
||||||
|
(panelGroupDefinition.right.collapsedOffsetWidth / panelGroupWidth) * 100
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
resizableLeftPanelAPIRef.current.isCollapsed() &&
|
resizableLeftPanelAPIRef.current.isCollapsed() &&
|
||||||
@ -146,12 +146,12 @@ const useResizablePanels = (
|
|||||||
|
|
||||||
// Determine the current widths of the two side panels.
|
// Determine the current widths of the two side panels.
|
||||||
let leftPanelOffsetWidth = resizableLeftPanelAPIRef.current.isCollapsed()
|
let leftPanelOffsetWidth = resizableLeftPanelAPIRef.current.isCollapsed()
|
||||||
? sidePanelCollapsedOffsetWidth
|
? panelGroupDefinition.left.collapsedOffsetWidth
|
||||||
: leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize;
|
: leftPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize;
|
||||||
|
|
||||||
let rightPanelOffsetWidth = resizableRightPanelAPIRef.current.isCollapsed()
|
let rightPanelOffsetWidth = resizableRightPanelAPIRef.current.isCollapsed()
|
||||||
? sidePanelCollapsedOffsetWidth
|
? panelGroupDefinition.right.collapsedOffsetWidth
|
||||||
: rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize;
|
: rightPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resizableLeftPanelAPIRef.current.isCollapsed() &&
|
!resizableLeftPanelAPIRef.current.isCollapsed() &&
|
||||||
@ -162,9 +162,11 @@ const useResizablePanels = (
|
|||||||
// Reduce the left panel width so that both panels might fit.
|
// Reduce the left panel width so that both panels might fit.
|
||||||
leftPanelOffsetWidth = Math.max(
|
leftPanelOffsetWidth = Math.max(
|
||||||
panelGroupWidth - rightPanelOffsetWidth,
|
panelGroupWidth - rightPanelOffsetWidth,
|
||||||
sidePanelExpandedDefaultOffsetWidth
|
panelGroupDefinition.left.minimumExpandedOffsetWidth
|
||||||
|
);
|
||||||
|
setLeftPanelExpandedWidth(
|
||||||
|
leftPanelOffsetWidth - panelGroupDefinition.shared.expandedInsideBorderSize
|
||||||
);
|
);
|
||||||
setLeftPanelExpandedWidth(leftPanelOffsetWidth - sidePanelExpandedInsideBorderSize);
|
|
||||||
setMinMaxWidth(resizableLeftPanelElemRef.current, leftPanelOffsetWidth);
|
setMinMaxWidth(resizableLeftPanelElemRef.current, leftPanelOffsetWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,9 +179,11 @@ const useResizablePanels = (
|
|||||||
// Reduce the right panel width so that both panels might fit.
|
// Reduce the right panel width so that both panels might fit.
|
||||||
rightPanelOffsetWidth = Math.max(
|
rightPanelOffsetWidth = Math.max(
|
||||||
panelGroupWidth - leftPanelOffsetWidth,
|
panelGroupWidth - leftPanelOffsetWidth,
|
||||||
sidePanelExpandedDefaultOffsetWidth
|
panelGroupDefinition.right.minimumExpandedOffsetWidth
|
||||||
|
);
|
||||||
|
setRightPanelExpandedWidth(
|
||||||
|
rightPanelOffsetWidth - panelGroupDefinition.shared.expandedInsideBorderSize
|
||||||
);
|
);
|
||||||
setRightPanelExpandedWidth(rightPanelOffsetWidth - sidePanelExpandedInsideBorderSize);
|
|
||||||
setMinMaxWidth(resizableRightPanelElemRef.current, rightPanelOffsetWidth);
|
setMinMaxWidth(resizableRightPanelElemRef.current, rightPanelOffsetWidth);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -189,7 +193,12 @@ const useResizablePanels = (
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [leftPanelExpandedWidth, resizablePanelDefaultSize, rightPanelExpandedWidth]);
|
}, [
|
||||||
|
leftPanelExpandedWidth,
|
||||||
|
rightPanelExpandedWidth,
|
||||||
|
leftResizablePanelMinimumSize,
|
||||||
|
rightResizablePanelMinimumSize,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles dragging of either side panel resize handle.
|
* Handles dragging of either side panel resize handle.
|
||||||
@ -207,14 +216,14 @@ const useResizablePanels = (
|
|||||||
if (resizableLeftPanelAPIRef?.current?.isExpanded()) {
|
if (resizableLeftPanelAPIRef?.current?.isExpanded()) {
|
||||||
setMinMaxWidth(
|
setMinMaxWidth(
|
||||||
resizableLeftPanelElemRef.current,
|
resizableLeftPanelElemRef.current,
|
||||||
leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
leftPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resizableRightPanelAPIRef?.current?.isExpanded()) {
|
if (resizableRightPanelAPIRef?.current?.isExpanded()) {
|
||||||
setMinMaxWidth(
|
setMinMaxWidth(
|
||||||
resizableRightPanelElemRef.current,
|
resizableRightPanelElemRef.current,
|
||||||
rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
rightPanelExpandedWidth + panelGroupDefinition.shared.expandedInsideBorderSize
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,15 +238,12 @@ const useResizablePanels = (
|
|||||||
}, [setLeftPanelClosed]);
|
}, [setLeftPanelClosed]);
|
||||||
|
|
||||||
const onLeftPanelOpen = useCallback(() => {
|
const onLeftPanelOpen = useCallback(() => {
|
||||||
resizableLeftPanelAPIRef?.current?.expand();
|
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||||
if (!isResizableHandleDraggingRef.current) {
|
resizableLeftPanelAPIRef?.current?.expand(
|
||||||
setMinMaxWidth(
|
(panelGroupDefinition.left.initialExpandedOffsetWidth / panelGroupWidth) * 100
|
||||||
resizableLeftPanelElemRef.current,
|
);
|
||||||
leftPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setLeftPanelClosed(false);
|
setLeftPanelClosed(false);
|
||||||
}, [leftPanelExpandedWidth, setLeftPanelClosed]);
|
}, [setLeftPanelClosed]);
|
||||||
|
|
||||||
const onLeftPanelResize = useCallback(size => {
|
const onLeftPanelResize = useCallback(size => {
|
||||||
if (resizableLeftPanelAPIRef.current.isCollapsed()) {
|
if (resizableLeftPanelAPIRef.current.isCollapsed()) {
|
||||||
@ -245,7 +251,10 @@ const useResizablePanels = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||||
setLeftPanelExpandedWidth((size / 100) * panelGroupWidth - sidePanelExpandedInsideBorderSize);
|
const newExpandedWidth =
|
||||||
|
(size / 100) * panelGroupWidth - panelGroupDefinition.shared.expandedInsideBorderSize;
|
||||||
|
|
||||||
|
setLeftPanelExpandedWidth(newExpandedWidth);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onRightPanelClose = useCallback(() => {
|
const onRightPanelClose = useCallback(() => {
|
||||||
@ -255,68 +264,69 @@ const useResizablePanels = (
|
|||||||
}, [setRightPanelClosed]);
|
}, [setRightPanelClosed]);
|
||||||
|
|
||||||
const onRightPanelOpen = useCallback(() => {
|
const onRightPanelOpen = useCallback(() => {
|
||||||
resizableRightPanelAPIRef?.current?.expand();
|
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||||
if (!isResizableHandleDraggingRef.current) {
|
resizableRightPanelAPIRef?.current?.expand(
|
||||||
setMinMaxWidth(
|
(panelGroupDefinition.right.initialExpandedOffsetWidth / panelGroupWidth) * 100
|
||||||
resizableRightPanelElemRef.current,
|
);
|
||||||
rightPanelExpandedWidth + sidePanelExpandedInsideBorderSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setRightPanelClosed(false);
|
setRightPanelClosed(false);
|
||||||
}, [rightPanelExpandedWidth, setRightPanelClosed]);
|
}, [setRightPanelClosed]);
|
||||||
|
|
||||||
const onRightPanelResize = useCallback(size => {
|
const onRightPanelResize = useCallback(size => {
|
||||||
if (resizableRightPanelAPIRef?.current?.isCollapsed()) {
|
if (resizableRightPanelAPIRef?.current?.isCollapsed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
const { width: panelGroupWidth } = resizablePanelGroupElemRef.current.getBoundingClientRect();
|
||||||
setRightPanelExpandedWidth((size / 100) * panelGroupWidth - sidePanelExpandedInsideBorderSize);
|
const newExpandedWidth =
|
||||||
|
(size / 100) * panelGroupWidth - panelGroupDefinition.shared.expandedInsideBorderSize;
|
||||||
|
|
||||||
|
setRightPanelExpandedWidth(newExpandedWidth);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
expandedWidth: leftPanelExpandedWidth,
|
expandedWidth: leftPanelExpandedWidth,
|
||||||
collapsedWidth: sidePanelCollapsedWidth,
|
collapsedWidth: panelGroupDefinition.shared.collapsedWidth,
|
||||||
collapsedInsideBorderSize: sidePanelCollapsedInsideBorderSize,
|
collapsedInsideBorderSize: panelGroupDefinition.shared.collapsedInsideBorderSize,
|
||||||
collapsedOutsideBorderSize: sidePanelCollapsedOutsideBorderSize,
|
collapsedOutsideBorderSize: panelGroupDefinition.shared.collapsedOutsideBorderSize,
|
||||||
expandedInsideBorderSize: sidePanelExpandedInsideBorderSize,
|
expandedInsideBorderSize: panelGroupDefinition.shared.expandedInsideBorderSize,
|
||||||
onClose: onLeftPanelClose,
|
onClose: onLeftPanelClose,
|
||||||
onOpen: onLeftPanelOpen,
|
onOpen: onLeftPanelOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expandedWidth: rightPanelExpandedWidth,
|
expandedWidth: rightPanelExpandedWidth,
|
||||||
collapsedWidth: sidePanelCollapsedWidth,
|
collapsedWidth: panelGroupDefinition.shared.collapsedWidth,
|
||||||
collapsedInsideBorderSize: sidePanelCollapsedInsideBorderSize,
|
collapsedInsideBorderSize: panelGroupDefinition.shared.collapsedInsideBorderSize,
|
||||||
collapsedOutsideBorderSize: sidePanelCollapsedOutsideBorderSize,
|
collapsedOutsideBorderSize: panelGroupDefinition.shared.collapsedOutsideBorderSize,
|
||||||
expandedInsideBorderSize: sidePanelExpandedInsideBorderSize,
|
expandedInsideBorderSize: panelGroupDefinition.shared.expandedInsideBorderSize,
|
||||||
onClose: onRightPanelClose,
|
onClose: onRightPanelClose,
|
||||||
onOpen: onRightPanelOpen,
|
onOpen: onRightPanelOpen,
|
||||||
},
|
},
|
||||||
{ direction: 'horizontal', id: viewerLayoutResizablePanelGroupId },
|
{ direction: 'horizontal', id: panelGroupDefinition.groupId },
|
||||||
{
|
{
|
||||||
defaultSize: resizablePanelDefaultSize,
|
defaultSize: leftResizablePanelMinimumSize,
|
||||||
minSize: resizablePanelDefaultSize,
|
minSize: leftResizablePanelMinimumSize,
|
||||||
onResize: onLeftPanelResize,
|
onResize: onLeftPanelResize,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
collapsedSize: resizablePanelCollapsedSize,
|
collapsedSize: leftResizeablePanelCollapsedSize,
|
||||||
onCollapse: () => setLeftPanelClosed(true),
|
onCollapse: () => setLeftPanelClosed(true),
|
||||||
onExpand: () => setLeftPanelClosed(false),
|
onExpand: () => setLeftPanelClosed(false),
|
||||||
ref: resizableLeftPanelAPIRef,
|
ref: resizableLeftPanelAPIRef,
|
||||||
order: 0,
|
order: 0,
|
||||||
id: viewerLayoutResizableLeftPanelId,
|
id: panelGroupDefinition.left.panelId,
|
||||||
},
|
},
|
||||||
{ order: 1, id: 'viewerLayoutResizableViewportGridPanel' },
|
{ order: 1, id: 'viewerLayoutResizableViewportGridPanel' },
|
||||||
{
|
{
|
||||||
defaultSize: resizablePanelDefaultSize,
|
defaultSize: rightResizablePanelMinimumSize,
|
||||||
minSize: resizablePanelDefaultSize,
|
minSize: rightResizablePanelMinimumSize,
|
||||||
onResize: onRightPanelResize,
|
onResize: onRightPanelResize,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
collapsedSize: resizablePanelCollapsedSize,
|
collapsedSize: rightResizePanelCollapsedSize,
|
||||||
onCollapse: () => setRightPanelClosed(true),
|
onCollapse: () => setRightPanelClosed(true),
|
||||||
onExpand: () => setRightPanelClosed(false),
|
onExpand: () => setRightPanelClosed(false),
|
||||||
ref: resizableRightPanelAPIRef,
|
ref: resizableRightPanelAPIRef,
|
||||||
order: 2,
|
order: 2,
|
||||||
id: viewerLayoutResizableRightPanelId,
|
id: panelGroupDefinition.right.panelId,
|
||||||
},
|
},
|
||||||
onHandleDragging,
|
onHandleDragging,
|
||||||
];
|
];
|
||||||
|
|||||||
38
extensions/default/src/ViewerLayout/constants/panels.ts
Normal file
38
extensions/default/src/ViewerLayout/constants/panels.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const expandedInsideBorderSize = 4;
|
||||||
|
const collapsedInsideBorderSize = 4;
|
||||||
|
const collapsedOutsideBorderSize = 8;
|
||||||
|
const collapsedWidth = 25;
|
||||||
|
|
||||||
|
const rightPanelInitialExpandedWidth = 280;
|
||||||
|
const leftPanelInitialExpandedWidth = 282;
|
||||||
|
|
||||||
|
const panelGroupDefinition = {
|
||||||
|
groupId: 'viewerLayoutResizablePanelGroup',
|
||||||
|
shared: {
|
||||||
|
expandedInsideBorderSize,
|
||||||
|
collapsedInsideBorderSize,
|
||||||
|
collapsedOutsideBorderSize,
|
||||||
|
collapsedWidth,
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
// id
|
||||||
|
panelId: 'viewerLayoutResizableLeftPanel',
|
||||||
|
// expanded width
|
||||||
|
initialExpandedWidth: leftPanelInitialExpandedWidth,
|
||||||
|
// expanded width + expanded inside border
|
||||||
|
minimumExpandedOffsetWidth: 145 + expandedInsideBorderSize,
|
||||||
|
// initial expanded width
|
||||||
|
initialExpandedOffsetWidth: leftPanelInitialExpandedWidth + expandedInsideBorderSize,
|
||||||
|
// collapsed width + collapsed inside border + collapsed outside border
|
||||||
|
collapsedOffsetWidth: collapsedWidth + collapsedInsideBorderSize + collapsedOutsideBorderSize,
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
panelId: 'viewerLayoutResizableRightPanel',
|
||||||
|
initialExpandedWidth: rightPanelInitialExpandedWidth,
|
||||||
|
minimumExpandedOffsetWidth: rightPanelInitialExpandedWidth + expandedInsideBorderSize,
|
||||||
|
initialExpandedOffsetWidth: rightPanelInitialExpandedWidth + expandedInsideBorderSize,
|
||||||
|
collapsedOffsetWidth: collapsedWidth + collapsedInsideBorderSize + collapsedOutsideBorderSize,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { panelGroupDefinition };
|
||||||
@ -141,7 +141,9 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
props: {
|
props: {
|
||||||
// TODO: Should be optional, or required to pass empty array for slots?
|
// TODO: Should be optional, or required to pass empty array for slots?
|
||||||
leftPanels: [ohif.thumbnailList],
|
leftPanels: [ohif.thumbnailList],
|
||||||
|
leftPanelResizable: true,
|
||||||
rightPanels: [ohif.measurements],
|
rightPanels: [ohif.measurements],
|
||||||
|
rightPanelResizable: true,
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
namespace: cs3d.viewport,
|
namespace: cs3d.viewport,
|
||||||
|
|||||||
@ -150,8 +150,10 @@ function modeFactory() {
|
|||||||
// leftPanels: [ohif.thumbnailList],
|
// leftPanels: [ohif.thumbnailList],
|
||||||
// rightPanels: [dicomSeg.panel, ohif.measurements],
|
// rightPanels: [dicomSeg.panel, ohif.measurements],
|
||||||
leftPanels: [tracked.thumbnailList],
|
leftPanels: [tracked.thumbnailList],
|
||||||
|
leftPanelResizable: true,
|
||||||
// Can use cornerstone.measurements for all measurements
|
// Can use cornerstone.measurements for all measurements
|
||||||
rightPanels: [cornerstone.panel, tracked.measurements, cornerstone.measurements],
|
rightPanels: [cornerstone.panel, tracked.measurements, cornerstone.measurements],
|
||||||
|
rightPanelResizable: true,
|
||||||
// rightPanelClosed: true, // optional prop to start with collapse panels
|
// rightPanelClosed: true, // optional prop to start with collapse panels
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -87,9 +87,11 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
id: ohif.layout,
|
id: ohif.layout,
|
||||||
props: {
|
props: {
|
||||||
leftPanels: [ohif.leftPanel],
|
leftPanels: [ohif.leftPanel],
|
||||||
|
leftPanelResizable: true,
|
||||||
leftPanelClosed: true, // we have problem with rendering thumbnails for microscopy images
|
leftPanelClosed: true, // we have problem with rendering thumbnails for microscopy images
|
||||||
rightPanelClosed: true, // we do not have the save microscopy measurements yet
|
rightPanelClosed: true, // we do not have the save microscopy measurements yet
|
||||||
rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'],
|
rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'],
|
||||||
|
rightPanelResizable: true,
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
namespace: '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom',
|
namespace: '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom',
|
||||||
|
|||||||
@ -158,7 +158,9 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
id: ohif.layout,
|
id: ohif.layout,
|
||||||
props: {
|
props: {
|
||||||
leftPanels: [[dynamicVolume.leftPanel, cornerstone.activeViewportWindowLevel]],
|
leftPanels: [[dynamicVolume.leftPanel, cornerstone.activeViewportWindowLevel]],
|
||||||
|
leftPanelResizable: true,
|
||||||
rightPanels: [],
|
rightPanels: [],
|
||||||
|
rightPanelResizable: true,
|
||||||
rightPanelClosed: true,
|
rightPanelClosed: true,
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -132,7 +132,9 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
id: ohif.layout,
|
id: ohif.layout,
|
||||||
props: {
|
props: {
|
||||||
leftPanels: [ohif.leftPanel],
|
leftPanels: [ohif.leftPanel],
|
||||||
|
leftPanelResizable: true,
|
||||||
rightPanels: [cornerstone.panelTool],
|
rightPanels: [cornerstone.panelTool],
|
||||||
|
rightPanelResizable: true,
|
||||||
// leftPanelClosed: true,
|
// leftPanelClosed: true,
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -215,8 +215,10 @@ function modeFactory({ modeConfiguration }) {
|
|||||||
id: ohif.layout,
|
id: ohif.layout,
|
||||||
props: {
|
props: {
|
||||||
leftPanels: [ohif.thumbnailList],
|
leftPanels: [ohif.thumbnailList],
|
||||||
|
leftPanelResizable: true,
|
||||||
leftPanelClosed: true,
|
leftPanelClosed: true,
|
||||||
rightPanels: [tmtv.tmtv, tmtv.petSUV],
|
rightPanels: [tmtv.tmtv, tmtv.petSUV],
|
||||||
|
rightPanelResizable: true,
|
||||||
viewports: [
|
viewports: [
|
||||||
{
|
{
|
||||||
namespace: cs3d.viewport,
|
namespace: cs3d.viewport,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
describe('OHIF Video Display', function () {
|
describe('OHIF Video Display', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
Cypress.on('uncaught:exception', () => false);
|
||||||
cy.openStudyInViewer('2.25.96975534054447904995905761963464388233');
|
cy.openStudyInViewer('2.25.96975534054447904995905761963464388233');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,7 @@ const StudyBrowser = ({
|
|||||||
className="ohif-scrollbar invisible-scrollbar bg-bkg-low flex flex-1 flex-col gap-[4px] overflow-auto"
|
className="ohif-scrollbar invisible-scrollbar bg-bkg-low flex flex-1 flex-col gap-[4px] overflow-auto"
|
||||||
data-cy={'studyBrowser-panel'}
|
data-cy={'studyBrowser-panel'}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="flex flex-col gap-[4px]">
|
||||||
{showSettings && (
|
{showSettings && (
|
||||||
<div className="w-100 bg-bkg-low flex h-[48px] items-center justify-center gap-[10px] px-[8px] py-[10px]">
|
<div className="w-100 bg-bkg-low flex h-[48px] items-center justify-center gap-[10px] px-[8px] py-[10px]">
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
} from '../DropdownMenu/DropdownMenu';
|
} from '../DropdownMenu/DropdownMenu';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||||
|
|
||||||
export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
||||||
// Todo: this should not be here, no servicesManager should be in ui-next, only
|
// Todo: this should not be here, no servicesManager should be in ui-next, only
|
||||||
@ -50,11 +51,16 @@ export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
|||||||
}, [displaySetService, selectedSort, sortDirection]);
|
}, [displaySetService, selectedSort, sortDirection]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex w-[50%] items-center gap-1">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-[125px] items-center justify-start rounded border bg-black p-2 text-base text-white">
|
<Tooltip>
|
||||||
{selectedSort.label}
|
<TooltipTrigger className="w-full overflow-hidden">
|
||||||
</DropdownMenuTrigger>
|
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-full items-center justify-start overflow-hidden whitespace-nowrap rounded border bg-black p-2 text-base text-white">
|
||||||
|
{selectedSort.label}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{selectedSort.label}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<DropdownMenuContent className="bg-black">
|
<DropdownMenuContent className="bg-black">
|
||||||
{sortFunctions.map(sort => (
|
{sortFunctions.map(sort => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@ -67,16 +73,21 @@ export function StudyBrowserSort({ servicesManager }: withAppTypes) {
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<button
|
<Tooltip>
|
||||||
onClick={toggleSortDirection}
|
<TooltipTrigger>
|
||||||
className="flex h-[26px] items-center justify-center bg-black"
|
<button
|
||||||
>
|
onClick={toggleSortDirection}
|
||||||
{sortDirection === 'ascending' ? (
|
className="flex h-[26px] items-center justify-center bg-black"
|
||||||
<Icons.SortingAscending className="text-primary-main w-2" />
|
>
|
||||||
) : (
|
{sortDirection === 'ascending' ? (
|
||||||
<Icons.SortingDescending className="text-primary-main w-2" />
|
<Icons.SortingAscending className="text-primary-main w-2" />
|
||||||
)}
|
) : (
|
||||||
</button>
|
<Icons.SortingDescending className="text-primary-main w-2" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Sort direction</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
} from '../DropdownMenu/DropdownMenu';
|
} from '../DropdownMenu/DropdownMenu';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||||
|
|
||||||
export function StudyBrowserViewOptions({ tabs, onSelectTab, activeTabName }: withAppTypes) {
|
export function StudyBrowserViewOptions({ tabs, onSelectTab, activeTabName }: withAppTypes) {
|
||||||
const handleTabChange = (tabName: string) => {
|
const handleTabChange = (tabName: string) => {
|
||||||
@ -15,9 +16,14 @@ export function StudyBrowserViewOptions({ tabs, onSelectTab, activeTabName }: wi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-[125px] items-center justify-start rounded border bg-black p-2 text-base text-white">
|
<Tooltip>
|
||||||
{activeTab?.label}
|
<TooltipTrigger className="w-full w-[50%] overflow-hidden">
|
||||||
</DropdownMenuTrigger>
|
<DropdownMenuTrigger className="border-inputfield-main focus:border-inputfield-main flex h-[26px] w-full items-center justify-start rounded border bg-black p-2 text-base text-white">
|
||||||
|
{activeTab?.label}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{activeTab?.label}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
<DropdownMenuContent className="bg-black">
|
<DropdownMenuContent className="bg-black">
|
||||||
{tabs.map(tab => {
|
{tabs.map(tab => {
|
||||||
const { name, label, studies } = tab;
|
const { name, label, studies } = tab;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import classnames from 'classnames';
|
|||||||
import { ThumbnailList } from '../ThumbnailList';
|
import { ThumbnailList } from '../ThumbnailList';
|
||||||
|
|
||||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../Accordion';
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../Accordion';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../Tooltip';
|
||||||
|
|
||||||
const StudyItem = ({
|
const StudyItem = ({
|
||||||
date,
|
date,
|
||||||
@ -34,16 +35,28 @@ const StudyItem = ({
|
|||||||
defaultValue={isActive ? 'study-item' : undefined}
|
defaultValue={isActive ? 'study-item' : undefined}
|
||||||
>
|
>
|
||||||
<AccordionItem value="study-item">
|
<AccordionItem value="study-item">
|
||||||
<AccordionTrigger className={classnames('hover:bg-accent bg-popover group rounded')}>
|
<AccordionTrigger className={classnames('hover:bg-accent bg-popover group w-full rounded')}>
|
||||||
<div className="flex h-[40px] flex-1 flex-row">
|
<div className="flex h-[40px] w-full flex-row overflow-hidden">
|
||||||
<div className="flex w-full flex-row items-center">
|
<div className="flex w-full flex-row items-center justify-between">
|
||||||
<div className="flex flex-col items-start text-[13px]">
|
<div className="flex min-w-0 flex-col items-start text-[13px]">
|
||||||
<div className="text-white">{date}</div>
|
<Tooltip>
|
||||||
<div className="text-muted-foreground h-[18px] max-w-[160px] overflow-hidden truncate whitespace-nowrap">
|
<TooltipContent>{date}</TooltipContent>
|
||||||
{description}
|
<TooltipTrigger className="w-full">
|
||||||
</div>
|
<div className="h-[18px] w-full max-w-[160px] overflow-hidden truncate whitespace-nowrap text-left text-white">
|
||||||
|
{date}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipContent>{description}</TooltipContent>
|
||||||
|
<TooltipTrigger className="w-full">
|
||||||
|
<div className="text-muted-foreground h-[18px] w-full overflow-hidden truncate whitespace-nowrap text-left">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground ml-auto flex flex-col items-end text-[12px]">
|
<div className="text-muted-foreground flex flex-col items-end pl-[10px] text-[12px]">
|
||||||
<div className="max-w-[150px] overflow-hidden text-ellipsis">{modalities}</div>
|
<div className="max-w-[150px] overflow-hidden text-ellipsis">{modalities}</div>
|
||||||
<div>{numInstances}</div>
|
<div>{numInstances}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -20,17 +20,17 @@ const Thumbnail = ({
|
|||||||
loadingProgress,
|
loadingProgress,
|
||||||
countIcon,
|
countIcon,
|
||||||
messages,
|
messages,
|
||||||
dragData = {},
|
|
||||||
isActive,
|
isActive,
|
||||||
onClick,
|
onClick,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
viewPreset = 'thumbnails',
|
thumbnailType,
|
||||||
modality,
|
modality,
|
||||||
|
viewPreset = 'thumbnails',
|
||||||
isHydratedForDerivedDisplaySet = false,
|
isHydratedForDerivedDisplaySet = false,
|
||||||
isTracked = false,
|
isTracked = false,
|
||||||
canReject = false,
|
canReject = false,
|
||||||
|
dragData = {},
|
||||||
onReject = () => {},
|
onReject = () => {},
|
||||||
thumbnailType = 'thumbnail',
|
|
||||||
onClickUntrack = () => {},
|
onClickUntrack = () => {},
|
||||||
ThumbnailMenuItems = () => {},
|
ThumbnailMenuItems = () => {},
|
||||||
}: withAppTypes): React.ReactNode => {
|
}: withAppTypes): React.ReactNode => {
|
||||||
@ -135,10 +135,15 @@ const Thumbnail = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex h-[52px] w-[128px] flex-col">
|
<div className="flex h-[52px] w-[128px] flex-col justify-end">
|
||||||
<div className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis pb-0.5 pl-1 text-[12px] font-normal leading-4 text-white">
|
<Tooltip>
|
||||||
{description}
|
<TooltipContent>{description}</TooltipContent>
|
||||||
</div>
|
<TooltipTrigger>
|
||||||
|
<div className="min-h-[18px] w-[128px] overflow-hidden text-ellipsis whitespace-nowrap pb-0.5 pl-1 text-left text-[12px] font-normal leading-4 text-white">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Tooltip>
|
||||||
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
||||||
<div className="text-muted-foreground pl-1 text-[11px]"> S:{seriesNumber}</div>
|
<div className="text-muted-foreground pl-1 text-[11px]"> S:{seriesNumber}</div>
|
||||||
<div className="text-muted-foreground text-[11px]">
|
<div className="text-muted-foreground text-[11px]">
|
||||||
@ -165,21 +170,25 @@ const Thumbnail = ({
|
|||||||
isActive && 'bg-popover'
|
isActive && 'bg-popover'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative flex h-[32px] items-center gap-[8px]">
|
<div className="relative flex h-[32px] w-full items-center gap-[8px] overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'h-[32px] w-[4px] rounded-[2px]',
|
'h-[32px] w-[4px] min-w-[4px] rounded-[2px]',
|
||||||
isActive || isHydratedForDerivedDisplaySet ? 'bg-highlight' : 'bg-primary/65',
|
isActive || isHydratedForDerivedDisplaySet ? 'bg-highlight' : 'bg-primary/65',
|
||||||
loadingProgress && loadingProgress < 1 && 'bg-primary/25'
|
loadingProgress && loadingProgress < 1 && 'bg-primary/25'
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full w-[calc(100%-12px)] flex-col">
|
||||||
<div className="flex items-center gap-[7px]">
|
<div className="flex items-center gap-[7px]">
|
||||||
<div className="text-[13px] font-semibold text-white">{modality}</div>
|
<div className="text-[13px] font-semibold text-white">{modality}</div>
|
||||||
|
<Tooltip>
|
||||||
<div className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-[13px] font-normal text-white">
|
<TooltipContent>{description}</TooltipContent>
|
||||||
{description}
|
<TooltipTrigger className="w-full overflow-hidden">
|
||||||
</div>
|
<div className="max-w-[160px] overflow-hidden overflow-ellipsis whitespace-nowrap text-left text-[13px] font-normal text-white">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
<div className="flex h-[12px] items-center gap-[7px] overflow-hidden">
|
||||||
@ -246,7 +255,7 @@ const Thumbnail = ({
|
|||||||
className,
|
className,
|
||||||
'bg-muted hover:bg-primary/30 group flex cursor-pointer select-none flex-col rounded outline-none',
|
'bg-muted hover:bg-primary/30 group flex cursor-pointer select-none flex-col rounded outline-none',
|
||||||
viewPreset === 'thumbnails' && 'h-[170px] w-[135px]',
|
viewPreset === 'thumbnails' && 'h-[170px] w-[135px]',
|
||||||
viewPreset === 'list' && 'col-span-2 h-[40px] w-[275px]'
|
viewPreset === 'list' && 'h-[40px] w-full'
|
||||||
)}
|
)}
|
||||||
id={`thumbnail-${displaySetInstanceUID}`}
|
id={`thumbnail-${displaySetInstanceUID}`}
|
||||||
data-cy={
|
data-cy={
|
||||||
|
|||||||
@ -11,68 +11,73 @@ const ThumbnailList = ({
|
|||||||
activeDisplaySetInstanceUIDs = [],
|
activeDisplaySetInstanceUIDs = [],
|
||||||
viewPreset,
|
viewPreset,
|
||||||
ThumbnailMenuItems,
|
ThumbnailMenuItems,
|
||||||
}: withAppTypes) => {
|
}) => {
|
||||||
|
// Filter thumbnails into list items and thumbnail items
|
||||||
|
const listItems = thumbnails?.filter(
|
||||||
|
({ componentType }) => componentType === 'thumbnailNoImage' || viewPreset === 'list'
|
||||||
|
);
|
||||||
|
|
||||||
|
const thumbnailItems = thumbnails?.filter(
|
||||||
|
({ componentType }) => componentType !== 'thumbnailNoImage' && viewPreset === 'thumbnails'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex flex-col gap-[4px] pt-[4px] pr-[2.5px] pl-[5px] pb-[4px]">
|
||||||
className="min-h-[350px]"
|
{/* Thumbnail Items */}
|
||||||
style={{
|
{thumbnailItems.length > 0 && (
|
||||||
'--radix-accordion-content-height': '350px',
|
<div
|
||||||
}}
|
id="ohif-thumbnail-list"
|
||||||
>
|
className="ohif-scrollbar bg-bkg-low grid grid-cols-[repeat(auto-fit,_minmax(0,135px))] place-items-start gap-[4px] overflow-y-hidden"
|
||||||
<div
|
>
|
||||||
id="ohif-thumbnail-list"
|
{thumbnailItems.map(item => {
|
||||||
className={`ohif-scrollbar bg-bkg-low grid place-items-center overflow-y-hidden pt-[4px] pr-[2.5px] pl-[2.5px] ${viewPreset === 'thumbnails' ? 'grid-cols-2 gap-[4px] pb-[12px]' : 'grid-cols-1 gap-[2px]'}`}
|
const { displaySetInstanceUID, componentType, numInstances, ...rest } = item;
|
||||||
>
|
|
||||||
{thumbnails.map(
|
|
||||||
({
|
|
||||||
displaySetInstanceUID,
|
|
||||||
description,
|
|
||||||
dragData,
|
|
||||||
seriesNumber,
|
|
||||||
numInstances,
|
|
||||||
loadingProgress,
|
|
||||||
modality,
|
|
||||||
componentType,
|
|
||||||
countIcon,
|
|
||||||
canReject,
|
|
||||||
onReject,
|
|
||||||
isTracked,
|
|
||||||
imageSrc,
|
|
||||||
messages,
|
|
||||||
imageAltText,
|
|
||||||
isHydratedForDerivedDisplaySet,
|
|
||||||
}) => {
|
|
||||||
const isActive = activeDisplaySetInstanceUIDs.includes(displaySetInstanceUID);
|
const isActive = activeDisplaySetInstanceUIDs.includes(displaySetInstanceUID);
|
||||||
return (
|
return (
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
key={displaySetInstanceUID}
|
key={displaySetInstanceUID}
|
||||||
|
{...rest}
|
||||||
displaySetInstanceUID={displaySetInstanceUID}
|
displaySetInstanceUID={displaySetInstanceUID}
|
||||||
dragData={dragData}
|
|
||||||
description={description}
|
|
||||||
seriesNumber={seriesNumber}
|
|
||||||
numInstances={numInstances || 1}
|
numInstances={numInstances || 1}
|
||||||
countIcon={countIcon}
|
|
||||||
imageSrc={imageSrc}
|
|
||||||
imageAltText={imageAltText}
|
|
||||||
messages={messages}
|
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
canReject={canReject}
|
|
||||||
onReject={onReject}
|
|
||||||
modality={modality}
|
|
||||||
viewPreset={componentType === 'thumbnailNoImage' ? 'list' : viewPreset}
|
|
||||||
thumbnailType={componentType}
|
thumbnailType={componentType}
|
||||||
onClick={() => onThumbnailClick(displaySetInstanceUID)}
|
viewPreset="thumbnails"
|
||||||
onDoubleClick={() => onThumbnailDoubleClick(displaySetInstanceUID)}
|
onClick={onThumbnailClick.bind(null, displaySetInstanceUID)}
|
||||||
isTracked={isTracked}
|
onDoubleClick={onThumbnailDoubleClick.bind(null, displaySetInstanceUID)}
|
||||||
loadingProgress={loadingProgress}
|
onClickUntrack={onClickUntrack.bind(null, displaySetInstanceUID)}
|
||||||
onClickUntrack={() => onClickUntrack(displaySetInstanceUID)}
|
|
||||||
isHydratedForDerivedDisplaySet={isHydratedForDerivedDisplaySet}
|
|
||||||
ThumbnailMenuItems={ThumbnailMenuItems}
|
ThumbnailMenuItems={ThumbnailMenuItems}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{/* List Items */}
|
||||||
|
{listItems.length > 0 && (
|
||||||
|
<div
|
||||||
|
id="ohif-thumbnail-list"
|
||||||
|
className="ohif-scrollbar bg-bkg-low grid grid-cols-[repeat(auto-fit,_minmax(0,275px))] place-items-start gap-[2px] overflow-y-hidden"
|
||||||
|
>
|
||||||
|
{listItems.map(item => {
|
||||||
|
const { displaySetInstanceUID, componentType, numInstances, ...rest } = item;
|
||||||
|
const isActive = activeDisplaySetInstanceUIDs.includes(displaySetInstanceUID);
|
||||||
|
return (
|
||||||
|
<Thumbnail
|
||||||
|
key={displaySetInstanceUID}
|
||||||
|
{...rest}
|
||||||
|
displaySetInstanceUID={displaySetInstanceUID}
|
||||||
|
numInstances={numInstances || 1}
|
||||||
|
isActive={isActive}
|
||||||
|
thumbnailType={componentType}
|
||||||
|
viewPreset="list"
|
||||||
|
onClick={onThumbnailClick.bind(null, displaySetInstanceUID)}
|
||||||
|
onDoubleClick={onThumbnailDoubleClick.bind(null, displaySetInstanceUID)}
|
||||||
|
onClickUntrack={onClickUntrack.bind(null, displaySetInstanceUID)}
|
||||||
|
ThumbnailMenuItems={ThumbnailMenuItems}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -107,6 +112,7 @@ ThumbnailList.propTypes = {
|
|||||||
onThumbnailDoubleClick: PropTypes.func.isRequired,
|
onThumbnailDoubleClick: PropTypes.func.isRequired,
|
||||||
onClickUntrack: PropTypes.func.isRequired,
|
onClickUntrack: PropTypes.func.isRequired,
|
||||||
viewPreset: PropTypes.string,
|
viewPreset: PropTypes.string,
|
||||||
|
ThumbnailMenuItems: PropTypes.any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ThumbnailList };
|
export { ThumbnailList };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user