diff --git a/dicom-viewer/index.html b/dicom-viewer/index.html index 01942b1..23fb46c 100644 --- a/dicom-viewer/index.html +++ b/dicom-viewer/index.html @@ -7,8 +7,10 @@ Vite + React + TS -
-
+
+
diff --git a/dicom-viewer/src/App.tsx b/dicom-viewer/src/App.tsx index c2ac9bf..413ef58 100644 --- a/dicom-viewer/src/App.tsx +++ b/dicom-viewer/src/App.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useCallback, useState } from "react" -import { createImageIds, availableImageIds } from "./lib/createImageIds.ts" import { RenderingEngine, Enums, @@ -65,7 +64,6 @@ const { MouseBindings, KeyboardBindings } = csToolsEnums; const { IMAGE_RENDERED } = Enums.Events; -const MAIN_TOOL_GROUP_ID = 'MAIN_TOOL_GROUP_ID'; // Common CT presets (add more as needed) const WINDOW_LEVEL_PRESETS = [ @@ -115,9 +113,14 @@ const ALL_MOUSE_BINDINGS = [ { label: "Mouse Wheel + Left", value: "Wheel_Primary", mask: 524289 }, ]; +type AppProps = { + container_id?: string; + imageStacks: () => Promise; + // ...other props if needed +}; // App definintion -function App() { +function App({ container_id, imageStacks, ...props }: AppProps) { const elementRef = useRef(null) const running = useRef(false) @@ -130,7 +133,8 @@ function App() { const [openDropdownIdx, setOpenDropdownIdx] = useState(null); const MAX_VIEWPORTS = 9; - const renderingEngineId = "mainRenderingEngine"; + const renderingEngineId = "renderingEngine_" + container_id; + const MAIN_TOOL_GROUP_ID = "TOOL_GROUP_" + container_id; const renderingEngineRef = useRef(null); const restoringStateRef = useRef(false); @@ -148,7 +152,7 @@ function App() { // Load available stacks on mount useEffect(() => { - availableImageIds().then(async stacks => { + imageStacks().then(async stacks => { // Try to extract StudyInstanceUID for each stack const stackEntries: StackEntry[] = await Promise.all( stacks.map(async imageIds => { @@ -663,7 +667,7 @@ function App() { // --- In your setup function, use the correct tool group per viewport --- let mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID); if (!mainToolGroup) { - mainToolGroup = setupTools().mainToolGroup; + mainToolGroup = setupTools(MAIN_TOOL_GROUP_ID).mainToolGroup; } @@ -1065,8 +1069,9 @@ function App() { } } useEffect(() => { + const apiKey = container_id || "default"; // Export the current viewer state as JSON - window.exportViewerState = () => { + window[`exportViewerState_${apiKey}`] = () => { const numActive = viewportGrid.rows * viewportGrid.cols; const modes = viewportModes.slice(0, numActive); const stackIdx = selectedStackIdx.slice(0, numActive); @@ -1117,7 +1122,7 @@ function App() { }; // Import and restore a viewer state from JSON (using stack indices) - window.importViewerState = (json: string) => { + window[`importViewerState_${apiKey}`] = (json: string) => { try { restoringStateRef.current = true; const state = typeof json === "string" ? JSON.parse(json) : json; @@ -1189,7 +1194,7 @@ function App() { }; // Export all annotations using cornerstoneTools.annotation.state.getAllAnnotations() - window.exportAnnotations = () => { + window[`exportAnnotations_${apiKey}`] = () => { try { let allAnnotations = cornerstoneTools.annotation.state.getAllAnnotations(); console.log("Exporting annotations:", allAnnotations); @@ -1221,7 +1226,7 @@ function App() { }; // Import and restore all annotations using cornerstoneTools.annotation.state.restoreAnnotations() - window.importAnnotations = (json: string) => { + window[`importAnnotations_${apiKey}`] = (json: string) => { try { const annotationData = typeof json === "string" ? JSON.parse(json) : json; // Remove all existing annotations if needed @@ -1246,10 +1251,10 @@ function App() { // Cleanup return () => { - window.exportViewerState = undefined; - window.importViewerState = undefined; - window.exportAnnotations = undefined; - window.importAnnotations = undefined; + window[`exportViewerState_${apiKey}`] = undefined; + window[`importViewerState_${apiKey}`] = undefined; + window[`exportAnnotations_${apiKey}`] = undefined; + window[`importAnnotations_${apiKey}`] = undefined; }; }, [ viewportGrid, @@ -1271,8 +1276,10 @@ function App() { const referenceStackObj = availableStacks[referenceStackIdx]; const referenceUID = referenceStackObj?.studyInstanceUID; + const numVisible = viewportGrid.rows * viewportGrid.cols; const viewports = []; - for (let i = 0; i < MAX_VIEWPORTS; i++) { + //for (let i = 0; i < MAX_VIEWPORTS; i++) { +for (let i = 0; i < numVisible; i++) { // Determine if this viewport should be visible in the current grid const isVisible = i < viewportGrid.rows * viewportGrid.cols; @@ -1282,7 +1289,7 @@ function App() { ref={el => (viewportRefs.current[i] = el)} style={{ flex: 1, - border: "1px solid #222", + border: isVisible ? "1px solid #222" : "none", // Only show border if visible background: "#000", minWidth: 0, minHeight: 0, @@ -1292,8 +1299,8 @@ function App() { gridRow: Math.floor(i / viewportGrid.cols) + 1, gridColumn: (i % viewportGrid.cols) + 1, height: "100%", - visibility: isVisible ? "visible" : "hidden", - pointerEvents: isVisible ? "auto" : "none", + //visibility: isVisible ? "visible" : "hidden", + //pointerEvents: isVisible ? "auto" : "none", // Remove outline, use only boxShadow for indicator boxShadow: activeViewport === i ? "0 0 0 4px #1976d2, 0 0 16px 4px #1976d2cc" @@ -1676,20 +1683,23 @@ function App() { )} +{isVisible && (
{/* Custom Stack Dropdown */} @@ -1787,6 +1797,7 @@ function App() { const thisUID = stackObj.studyInstanceUID; const sameStudy = referenceUID && thisUID && referenceUID === thisUID; const dotColor = sameStudy ? "#4caf50" : "#888"; + const isEmpty = stackObj.imageIds.length === 0; return (
{ e.preventDefault(); - handleLoadStack(i, idx); - setOpenDropdownIdx(null); + if (!isEmpty) { + handleLoadStack(i, idx); + setOpenDropdownIdx(null); + } }} > - Stack {idx + 1} ({stackObj.imageIds.length} images) + Stack {idx + 1} ({stackObj.imageIds.length} images + {isEmpty && (empty)})
); })} @@ -1825,6 +1840,7 @@ function App() { )}
+)} ); } @@ -1833,16 +1849,17 @@ function App() { return ( -
+ // App root +
{/* Side menu toggle button */}