Implement resizable stack panel with min/max width constraints and update side menu button positioning
This commit is contained in:
@@ -187,6 +187,19 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
// Right-side stack panel open state (toggled by button)
|
// Right-side stack panel open state (toggled by button)
|
||||||
const [stackPanelOpen, setStackPanelOpen] = useState(false);
|
const [stackPanelOpen, setStackPanelOpen] = useState(false);
|
||||||
const [showOpenButtonHover, setShowOpenButtonHover] = useState(false);
|
const [showOpenButtonHover, setShowOpenButtonHover] = useState(false);
|
||||||
|
const [stackPanelWidth, setStackPanelWidth] = useState(320);
|
||||||
|
const stackPanelResizeRef = useRef<{ resizing: boolean; startX: number; startWidth: number }>({
|
||||||
|
resizing: false,
|
||||||
|
startX: 0,
|
||||||
|
startWidth: 320,
|
||||||
|
});
|
||||||
|
|
||||||
|
const STACK_PANEL_MIN_WIDTH = 240;
|
||||||
|
const STACK_PANEL_MAX_WIDTH = 720;
|
||||||
|
|
||||||
|
const SIDE_MENU_WIDTH = 220;
|
||||||
|
const SIDE_MENU_BUTTON_SIZE = 40;
|
||||||
|
const SIDE_MENU_BUTTON_PEEK = 14;
|
||||||
|
|
||||||
const [fullscreenHoverIdx, setFullscreenHoverIdx] = useState<number | null>(null);
|
const [fullscreenHoverIdx, setFullscreenHoverIdx] = useState<number | null>(null);
|
||||||
const [openDropdownIdx, setOpenDropdownIdx] = useState<number | null>(null);
|
const [openDropdownIdx, setOpenDropdownIdx] = useState<number | null>(null);
|
||||||
@@ -1296,6 +1309,52 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const [sideMenuOpen, setSideMenuOpen] = useState(false);
|
const [sideMenuOpen, setSideMenuOpen] = useState(false);
|
||||||
const [folderLoadMode, setFolderLoadMode] = useState<'add' | 'replace'>('add');
|
const [folderLoadMode, setFolderLoadMode] = useState<'add' | 'replace'>('add');
|
||||||
const folderInputRef = useRef<HTMLInputElement | null>(null);
|
const folderInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
const beginStackPanelResize = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (!stackPanelOpen) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
stackPanelResizeRef.current = {
|
||||||
|
resizing: true,
|
||||||
|
startX: event.clientX,
|
||||||
|
startWidth: stackPanelWidth,
|
||||||
|
};
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
}, [stackPanelOpen, stackPanelWidth]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onMouseMove = (event: MouseEvent) => {
|
||||||
|
if (!stackPanelResizeRef.current.resizing) return;
|
||||||
|
const delta = stackPanelResizeRef.current.startX - event.clientX;
|
||||||
|
const nextWidth = Math.max(
|
||||||
|
STACK_PANEL_MIN_WIDTH,
|
||||||
|
Math.min(STACK_PANEL_MAX_WIDTH, stackPanelResizeRef.current.startWidth + delta)
|
||||||
|
);
|
||||||
|
setStackPanelWidth(nextWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseUp = () => {
|
||||||
|
if (!stackPanelResizeRef.current.resizing) return;
|
||||||
|
stackPanelResizeRef.current.resizing = false;
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', onMouseMove);
|
||||||
|
window.addEventListener('mouseup', onMouseUp);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sideMenuButtonLeft = sideMenuOpen
|
||||||
|
? SIDE_MENU_WIDTH - (SIDE_MENU_BUTTON_SIZE / 2)
|
||||||
|
: -SIDE_MENU_BUTTON_SIZE + SIDE_MENU_BUTTON_PEEK;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Some browsers don't accept non-standard props via JSX typings; set attributes directly.
|
// Some browsers don't accept non-standard props via JSX typings; set attributes directly.
|
||||||
try {
|
try {
|
||||||
@@ -3947,10 +4006,10 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
transform: "translateY(-50%)",
|
transform: "translateY(-50%)",
|
||||||
left: sideMenuOpen ? 250 : -20, // Slide right when open
|
left: sideMenuButtonLeft,
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
width: 40,
|
width: SIDE_MENU_BUTTON_SIZE,
|
||||||
height: 40,
|
height: SIDE_MENU_BUTTON_SIZE,
|
||||||
background: "#222",
|
background: "#222",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
border: "none",
|
border: "none",
|
||||||
@@ -3974,7 +4033,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: 220,
|
width: SIDE_MENU_WIDTH,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
background: "#222",
|
background: "#222",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
@@ -4364,7 +4423,23 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right-side panel occupies layout space when visible; when closed width is 0 and it doesn't catch pointer events */}
|
{/* Right-side panel occupies layout space when visible; when closed width is 0 and it doesn't catch pointer events */}
|
||||||
<div style={{ width: stackPanelOpen ? 320 : 0, transition: 'width 220ms ease', display: 'flex', flexDirection: 'column', pointerEvents: stackPanelOpen ? 'auto' : 'none', overflow: 'hidden' }}>
|
<div style={{ width: stackPanelOpen ? stackPanelWidth : 0, transition: 'width 220ms ease', display: 'flex', flexDirection: 'column', pointerEvents: stackPanelOpen ? 'auto' : 'none', overflow: 'hidden', position: 'relative' }}>
|
||||||
|
{stackPanelOpen && (
|
||||||
|
<div
|
||||||
|
onMouseDown={beginStackPanelResize}
|
||||||
|
title="Resize stacks panel"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: 8,
|
||||||
|
height: '100%',
|
||||||
|
cursor: 'col-resize',
|
||||||
|
zIndex: 2800,
|
||||||
|
background: 'linear-gradient(to right, rgba(100,181,246,0.25), rgba(100,181,246,0))',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{/* Reuse the existing right-side panel markup but ensure pointerEvents are enabled inside */}
|
{/* Reuse the existing right-side panel markup but ensure pointerEvents are enabled inside */}
|
||||||
<div style={{ pointerEvents: 'auto', width: '100%', height: '100%' }}>
|
<div style={{ pointerEvents: 'auto', width: '100%', height: '100%' }}>
|
||||||
{/* Panel (same content as before) */}
|
{/* Panel (same content as before) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user