feat: 处理很多小bug

This commit is contained in:
mozzie 2024-09-14 14:29:35 +08:00
parent 0e7d05d9a4
commit 2897d02c22
11 changed files with 481 additions and 273 deletions

View File

@ -29,7 +29,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
let win: BrowserWindow | null;
let tray: Tray | null = null;
const theme: "dark" | "light" = "light";
const theme: "dark" | "light" = "dark";
const themeTitleBarStyles = {
dark: { color: "rgb(32,32,32)", symbolColor: "#fff" },

View File

@ -0,0 +1,44 @@
import { Card } from "@/components/ui/card";
import { PatientInfo } from "./type";
interface PatientCardProps {
patient: PatientInfo;
isSelected: boolean;
onSelect: (id: string) => void;
}
export const PatientCard = ({
patient,
isSelected,
onSelect,
}: PatientCardProps) => {
return (
<Card
key={patient.ID}
onClick={() => onSelect(patient.ID)}
className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
isSelected ? "bg-accent" : ""
}`}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{patient.MainDicomTags.PatientName}
</div>
<span className="flex h-2 w-2 rounded-full bg-blue-600"></span>
</div>
<div className="ml-auto text-xs text-foreground">
{patient.MainDicomTags.PatientSex}
</div>
</div>
<div className="text-xs font-medium">
{patient.MainDicomTags.PatientBirthDate}
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
: {patient.LastUpdate}
</div>
</Card>
);
};

View File

@ -0,0 +1,30 @@
import { ScrollArea } from "@/components/ui/scroll-area";
import { PatientCard } from "./PatientCard";
import { PatientInfo } from "./type";
interface PatientListProps {
patients: PatientInfo[];
selectedPatientId: string | null;
onSelectPatient: (id: string) => void;
}
export const PatientList = ({
patients,
selectedPatientId,
onSelectPatient,
}: PatientListProps) => {
return (
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{patients.map((patient) => (
<PatientCard
key={patient.ID}
patient={patient}
isSelected={patient.ID === selectedPatientId}
onSelect={onSelectPatient}
/>
))}
</div>
</ScrollArea>
);
};

View File

@ -0,0 +1,94 @@
import { Card } from "@/components/ui/card";
import {
EllipsisIcon,
FileDown,
Rotate3DIcon,
Sparkles,
Torus,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { SeriesInfo } from "./type";
interface SeriesCardProps {
series: SeriesInfo;
onAction: (action: string, series: SeriesInfo) => void;
}
export const SeriesCard = ({ series, onAction }: SeriesCardProps) => {
return (
<Card
key={series.MainDicomTags.SeriesInstanceUID}
className="flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent"
>
<div className="flex flex-col gap-1 w-full">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{series.MainDicomTags.SeriesNumber}
</div>
</div>
<div className="ml-auto text-xs text-foreground">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<EllipsisIcon className="w-4 h-4" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-36">
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => onAction("viewMpr", series)}>
MPR
<DropdownMenuShortcut>
<Rotate3DIcon className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onAction("view3D", series)}>
3D
<DropdownMenuShortcut>
<Torus className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onAction("exportMeasurement", series)}
>
<DropdownMenuShortcut>
<FileDown className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onAction("viewReport", series)}
>
<DropdownMenuShortcut>
<Sparkles className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
<span>{series.MainDicomTags.SeriesDescription}</span>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground flex gap-x-2">
<div className="flex items-center gap-2">
<Badge variant="secondary">{series.Instances.length}</Badge>
{series.MainDicomTags.BodyPartExamined && (
<Badge variant="secondary">
{series.MainDicomTags.BodyPartExamined}
</Badge>
)}
</div>
</div>
</Card>
);
};

View File

@ -0,0 +1,24 @@
import { ScrollArea } from "@/components/ui/scroll-area";
import { SeriesCard } from "./SeriesCard";
import { SeriesInfo } from "./type";
interface SeriesListProps {
seriesList: SeriesInfo[];
onAction: (action: string, series: SeriesInfo) => void;
}
export const SeriesList = ({ seriesList, onAction }: SeriesListProps) => {
return (
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{seriesList.map((series) => (
<SeriesCard
key={series.MainDicomTags.SeriesInstanceUID}
series={series}
onAction={onAction}
/>
))}
</div>
</ScrollArea>
);
};

View File

@ -0,0 +1,37 @@
// StudyCard.tsx
import { Card } from "@/components/ui/card";
import { StudyInfo } from "./type";
interface StudyCardProps {
study: StudyInfo;
isSelected: boolean;
onSelect: (id: string) => void;
}
export const StudyCard = ({ study, isSelected, onSelect }: StudyCardProps) => {
return (
<Card
key={study.ID}
onClick={() => onSelect(study.ID)}
className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
isSelected ? "bg-accent" : ""
}`}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{study.MainDicomTags.StudyID ?? "无"}
</div>
</div>
<div className="ml-auto text-xs text-foreground">
{study.MainDicomTags.StudyDate}
</div>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{study.MainDicomTags.InstitutionName}
</div>
</Card>
);
};

View File

@ -0,0 +1,30 @@
import { ScrollArea } from "@/components/ui/scroll-area";
import { StudyCard } from "./StudyCard";
import { StudyInfo } from "./type";
interface StudyListProps {
studies: StudyInfo[];
selectedStudyId: string | null;
onSelectStudy: (id: string) => void;
}
export const StudyList = ({
studies,
selectedStudyId,
onSelectStudy,
}: StudyListProps) => {
return (
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{studies.map((study) => (
<StudyCard
key={study.ID}
study={study}
isSelected={study.ID === selectedStudyId}
onSelect={onSelectStudy}
/>
))}
</div>
</ScrollArea>
);
};

View File

@ -1,41 +1,26 @@
import { useEffect, useRef, useState } from "react";
// Datasource.tsx
import { useEffect, useRef, useState, useMemo } from "react";
import { motion } from "framer-motion";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Card } from "@/components/ui/card";
import { PatientInfo, SeriesInfo, StudyInfo } from "./type";
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react";
import {
EllipsisIcon,
FileDown,
Rotate3DIcon,
Sparkles,
Torus,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useNavigate } from "react-router-dom";
interface SeriesClickItem {
action: "viewMpr" | "view3D" | "exportMeaurement" | "viewReport";
series: SeriesInfo;
}
import { PatientInfo, SeriesInfo } from "./type";
import { PatientList } from "./PatientList";
import { StudyList } from "./StudyList";
import { SeriesList } from "./SeriesList";
export const Datasource = () => {
const rawPatientsRef = useRef<PatientInfo[]>([]);
const [patients, setPatients] = useState<PatientInfo[]>([]);
const [selectedPatientId, setSelectedPatientId] = useState<string | null>(
null
);
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
@ -44,43 +29,47 @@ export const Datasource = () => {
.then((patients: PatientInfo[]) => {
console.log(patients);
rawPatientsRef.current = patients;
setPatients(
patients.map((p) => ({
...p,
active: false,
children: p.children.map((s) => ({ ...s, active: false })),
}))
);
setPatients(patients);
});
}, []);
const onClickItem = (p: SeriesClickItem) => {
const activeStudy = patients
.find((p) => p.active)
?.children.find((s) => s.active);
const StudyInstanceUID = activeStudy?.MainDicomTags.StudyInstanceUID;
const SeriesInstanceUID = p.series.MainDicomTags.SeriesInstanceUID;
const handlePatientSearch = (filterValue: string) => {
const raw = rawPatientsRef.current;
const filteredPatients = raw.filter((p) =>
p.MainDicomTags.PatientName.toUpperCase().includes(
filterValue.toUpperCase()
)
);
setPatients(filteredPatients);
if (!filteredPatients.find((p) => p.ID === selectedPatientId)) {
setSelectedPatientId(null);
setSelectedStudyId(null);
}
};
const selectedPatient = useMemo(() => {
return patients.find((p) => p.ID === selectedPatientId) || null;
}, [patients, selectedPatientId]);
const selectedStudy = useMemo(() => {
return (
selectedPatient?.children.find((s) => s.ID === selectedStudyId) || null
);
}, [selectedPatient, selectedStudyId]);
const handleSeriesAction = (action: string, series: SeriesInfo) => {
const StudyInstanceUID = selectedStudy?.MainDicomTags.StudyInstanceUID;
const SeriesInstanceUID = series.MainDicomTags.SeriesInstanceUID;
const query = `StudyInstanceUID=${StudyInstanceUID}&SeriesInstanceUID=${SeriesInstanceUID}`;
switch (p.action) {
switch (action) {
case "viewMpr":
navigate(`/viewer?${query}`, { replace: true });
break;
// 其他操作逻辑
default:
break;
}
console.log(StudyInstanceUID, p.action, p.series);
};
const handlePatientSearch = (filterValue: string) => {
const raw = rawPatientsRef.current;
setPatients(
raw.filter((p) =>
p.MainDicomTags.PatientName.toUpperCase().includes(
filterValue.toUpperCase()
)
)
);
console.log(StudyInstanceUID, action, series);
};
return (
@ -93,7 +82,7 @@ export const Datasource = () => {
>
<div className="p-4 h-full flex flex-col">
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
<ResizablePanel defaultSize={24}>
<ResizablePanel defaultSize={100 / 3}>
<div className="flex flex-col h-full pt-2">
<div className="px-4 pb-2">
<div className="relative">
@ -105,178 +94,37 @@ export const Datasource = () => {
/>
</div>
</div>
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{patients.map((patient) => (
<Card
key={patient.ID}
onClick={() =>
setPatients((p) =>
p.map((i) => ({ ...i, active: i.ID === patient.ID }))
)
}
className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
patient.active ? "bg-accent" : ""
}`}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{patient.MainDicomTags.PatientName}
</div>
<span className="flex h-2 w-2 rounded-full bg-blue-600"></span>
</div>
<div className="ml-auto text-xs text-foreground">
{patient.MainDicomTags.PatientSex}
</div>
</div>
<div className="text-xs font-medium">
{patient.MainDicomTags.PatientBirthDate}
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
: {patient.LastUpdate}
</div>
</Card>
))}
</div>
</ScrollArea>
<PatientList
patients={patients}
selectedPatientId={selectedPatientId}
onSelectPatient={(id) => {
setSelectedPatientId(id);
setSelectedStudyId(null);
}}
/>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={24}>
<ResizablePanel defaultSize={100 / 3}>
<div className="flex flex-col h-full">
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{patients.find((p) => p.active) &&
patients
.find((p) => p.active)
?.children.map((study) => (
<Card
key={study.ID}
onClick={() =>
setPatients((p) =>
p.map((i) => ({
...i,
children: i.children.map((s) => ({
...s,
active: s.ID === study.ID,
})),
}))
)
}
className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent hover:cursor-pointer ${
study.active ? "bg-accent" : ""
}`}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{study.MainDicomTags.StudyID ?? "无"}
</div>
</div>
<div className="ml-auto text-xs text-foreground">
{study.MainDicomTags.StudyDate}
</div>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{study.MainDicomTags.InstitutionName}
</div>
</Card>
))}
</div>
</ScrollArea>
{selectedPatient && (
<StudyList
studies={selectedPatient.children}
selectedStudyId={selectedStudyId}
onSelectStudy={(id) => setSelectedStudyId(id)}
/>
)}
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={52}>
<ResizablePanel defaultSize={100 / 3}>
<div className="flex flex-col h-full">
<ScrollArea className="flex-grow w-full h-full px-4 pb-2">
<div className="flex flex-col gap-y-2">
{patients.find((p) => p.active) &&
patients
.find((p) => p.active)
?.children.find((study) => study.active)
?.children.map((series) => (
<Card
key={series.MainDicomTags.SeriesInstanceUID}
className={`flex shadow-none flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent`}
>
<div className="flex flex-col gap-1 w-full">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">
{series.MainDicomTags.SeriesNumber}
</div>
</div>
<div className="ml-auto text-xs text-foreground">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<EllipsisIcon className="w-4 h-4" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-36">
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() =>
onClickItem({
series,
action: "viewMpr",
})
}
>
MPR
<DropdownMenuShortcut>
<Rotate3DIcon className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
3D
<DropdownMenuShortcut>
<Torus className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuShortcut>
<FileDown className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<DropdownMenuShortcut>
<Sparkles className="w-4 h-4" />
</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
<span>
{series.MainDicomTags.SeriesDescription}
</span>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground flex gap-x-2">
<div className="flex items-center gap-2">
<Badge variant="secondary">
{series.Instances.length}
</Badge>
{series.MainDicomTags.BodyPartExamined && (
<Badge variant="secondary">
{series.MainDicomTags.BodyPartExamined}
</Badge>
)}
</div>
</div>
</Card>
))}
</div>
</ScrollArea>
{selectedStudy && (
<SeriesList
seriesList={selectedStudy.children}
onAction={handleSeriesAction}
/>
)}
</div>
</ResizablePanel>
</ResizablePanelGroup>

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import * as cornerstoneTools from "@cornerstonejs/tools";
import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
import { createImageIdsAndCacheMetaData } from "./CornerstoneDicomLoader/createImageIdsAndCacheMetaData";
@ -20,17 +20,12 @@ import {
ViewportId,
} from "./Crosshair.config";
import GridLayout from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { DraftingCompassIcon, MoveIcon } from "lucide-react";
import { Card } from "@/components/ui/card";
const {
ToolGroupManager,
CrosshairsTool,
StackScrollMouseWheelTool,
WindowLevelTool,
ZoomTool,
Enums: csToolsEnums,
} = cornerstoneTools;
@ -72,14 +67,22 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
const viewportRef_SAGITTAL = useRef<HTMLDivElement | null>(null);
const viewportRef_CORONAL = useRef<HTMLDivElement | null>(null);
const renderingEngine = useRef<RenderingEngine>();
const toolGroupIdRef = useRef<string>("");
const toolGroupRef = useRef<cornerstoneTools.Types.IToolGroup | undefined>(
undefined
);
const imageIds = useRef<string[]>();
const ts = "-" + Date.now();
useEffect(() => {
cornerstoneTools.addTool(StackScrollMouseWheelTool);
cornerstoneTools.addTool(CrosshairsTool);
cornerstoneTools.addTool(WindowLevelTool);
cornerstoneTools.addTool(ZoomTool);
const toolGroupId = props.SeriesInstanceUID + ts;
toolGroupIdRef.current = toolGroupId;
toolGroupRef.current = ToolGroupManager.createToolGroup(toolGroupId);
const run = async () => {
if (
@ -92,9 +95,6 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
renderingEngine.current = new RenderingEngine(
props.SeriesInstanceUID + ts
);
const toolGroup = ToolGroupManager.createToolGroup(
props.SeriesInstanceUID + ts
);
imageIds.current = await createImageIdsAndCacheMetaData({
StudyInstanceUID: props.StudyInstanceUID,
@ -124,7 +124,7 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
element: viewportRef_AXIAL.current,
defaultOptions: {
orientation: CoreEnums.OrientationAxis.AXIAL,
background: [0, 0, 0],
background: [0, 0, 0],
},
},
{
@ -169,38 +169,59 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
[viewportId1, viewportId2, viewportId3]
);
if (toolGroup) {
if (toolGroupRef.current) {
// For the crosshairs to operate, the viewports must currently be
// added ahead of setting the tool active. This will be improved in the future.
toolGroup.addViewport(viewportId1, props.SeriesInstanceUID + ts);
toolGroup.addViewport(viewportId2, props.SeriesInstanceUID + ts);
toolGroup.addViewport(viewportId3, props.SeriesInstanceUID + ts);
toolGroupRef.current.addViewport(
viewportId1,
props.SeriesInstanceUID + ts
);
toolGroupRef.current.addViewport(
viewportId2,
props.SeriesInstanceUID + ts
);
toolGroupRef.current.addViewport(
viewportId3,
props.SeriesInstanceUID + ts
);
/**
* zoom影像
*/
toolGroupRef.current.addTool(ZoomTool.toolName);
toolGroupRef.current.setToolActive(ZoomTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary, // 鼠标中键
},
],
});
// Manipulation Tools
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
toolGroupRef.current.addTool(StackScrollMouseWheelTool.toolName);
// Add Crosshairs tool and configure it to link the three viewports
// These viewports could use different tool groups. See the PET-CT example
// for a more complicated used case.
toolGroup.addTool(CrosshairsTool.toolName, {
toolGroupRef.current.addTool(CrosshairsTool.toolName, {
getReferenceLineColor,
getReferenceLineControllable,
getReferenceLineDraggableRotatable,
getReferenceLineSlabThicknessControlsOn,
});
toolGroup.setToolActive(CrosshairsTool.toolName, {
toolGroupRef.current.setToolActive(CrosshairsTool.toolName, {
bindings: [{ mouseButton: 1 }],
});
// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
// hook instead of mouse buttons, it does not need to assign any mouse button.
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
toolGroupRef.current.setToolActive(StackScrollMouseWheelTool.toolName);
toolGroup.addTool(WindowLevelTool.toolName);
toolGroup.setToolActive(WindowLevelTool.toolName, {
toolGroupRef.current.addTool(WindowLevelTool.toolName);
toolGroupRef.current.setToolActive(WindowLevelTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Secondary,
mouseButton: MouseBindings.Auxiliary,
},
],
});
@ -218,37 +239,50 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
}
return () => {
// 禁用视口
renderingEngine.current?.disableElement(viewportId1);
renderingEngine.current?.disableElement(viewportId2);
renderingEngine.current?.disableElement(viewportId3);
// 销毁渲染引擎
renderingEngine.current?.destroy();
// 从 ToolGroupManager 中移除工具组
ToolGroupManager.destroyToolGroup(toolGroupIdRef.current);
// 移出工具注册
cornerstoneTools.removeTool(StackScrollMouseWheelTool);
cornerstoneTools.removeTool(CrosshairsTool);
cornerstoneTools.removeTool(WindowLevelTool);
renderingEngine.current?.destroy();
ToolGroupManager.destroyToolGroup(props.SeriesInstanceUID + ts);
cornerstoneTools.removeTool(ZoomTool);
};
}, [props, ts]);
/**
* mpr resize
*/
useEffect(() => {
const resize = () => renderingEngine.current?.resize()
window.addEventListener("resize", resize);
const container = containerRef.current;
if (!container) return;
let resizeTimeout: NodeJS.Timeout | null = null;
const resizeObserver = new ResizeObserver(() => {
console.log("mpr resize");
if (resizeTimeout) clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => renderingEngine.current?.resize(), 100);
});
resizeObserver.observe(container);
return () => {
window.removeEventListener("resize", resize);
if (resizeTimeout) clearTimeout(resizeTimeout);
resizeObserver.unobserve(container);
resizeObserver.disconnect();
};
}, []);
const onDBClickViewport = (viewportRef) => {
console.log("dblcik", viewportRef);
};
return (
<div ref={containerRef} className="w-full h-full flex flex-col">
<Card className="rounded-none h-1/3">
<div className="w-full h-full" onDoubleClick={() => onDBClickViewport(viewportRef_AXIAL)} ref={viewportRef_AXIAL}></div>
</Card>
<Card className="rounded-none h-1/3">
<div className="w-full h-full" ref={viewportRef_SAGITTAL}></div>
</Card>
<Card className="rounded-none h-1/3">
<div className="w-full h-full" ref={viewportRef_CORONAL}></div>
</Card>
<div className="w-full h-1/3" ref={viewportRef_AXIAL} />
<div className="w-full h-1/3" ref={viewportRef_SAGITTAL} />
<div className="w-full h-1/3" ref={viewportRef_CORONAL} />
</div>
);
};

View File

@ -0,0 +1,39 @@
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { MoonIcon, PersonStanding } from "lucide-react";
export const ToolBarMenu = () => {
return (
<div className="flex gap-x-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<MoonIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>/</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<PersonStanding className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>MIP</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
};

View File

@ -2,7 +2,12 @@ import { useLocation } from "react-router-dom";
import { initCornerstone } from "./MprViewer/CornerstoneDicomLoader/init";
import { CrosshairMpr } from "./MprViewer/Crosshair";
import { useEffect, useState } from "react";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import { ToolBarMenu } from "./MprViewer/ToolBarMenu";
export interface CurrentDicom {
SeriesInstanceUID: string | null;
@ -27,7 +32,7 @@ export const Viewer = () => {
}, [currentDicom]);
useEffect(() => {
console.log(window.location.href)
console.log(window.location.href);
initCornerstone(() => {
setCornerstoneLoaded(true);
});
@ -37,19 +42,42 @@ export const Viewer = () => {
console.log(cornerstoneLoaded);
}, [cornerstoneLoaded]);
return (
<div className="w-full h-full flex">
<div className="w-2/3 h-full">vr</div>
<div className="w-1/3 h-full">
{cornerstoneLoaded &&
currentDicom.StudyInstanceUID &&
currentDicom.SeriesInstanceUID && (
<CrosshairMpr
StudyInstanceUID={currentDicom.StudyInstanceUID}
SeriesInstanceUID={currentDicom.SeriesInstanceUID}
/>
)}</div>
<div className="w-full h-full flex flex-col">
<div className="flex-shrink-0 border-b border-secondary">
<ToolBarMenu />
</div>
<div className="flex-grow">
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
<ResizablePanel defaultSize={61.8}>
<div className="h-full">
<ResizablePanelGroup
direction="vertical"
className="w-full h-full"
>
<ResizablePanel defaultSize={50}>
<div>top</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<div>bototm</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={38.2}>
{cornerstoneLoaded &&
currentDicom.StudyInstanceUID &&
currentDicom.SeriesInstanceUID && (
<CrosshairMpr
StudyInstanceUID={currentDicom.StudyInstanceUID}
SeriesInstanceUID={currentDicom.SeriesInstanceUID}
/>
)}
</ResizablePanel>
</ResizablePanelGroup>
</div>
</div>
);
};