Add stripWadouriPrefix function and refactor image ID handling for improved URL normalization
This commit is contained in:
+127
-37
@@ -166,6 +166,12 @@ function getToolIcon(toolName?: string) {
|
|||||||
return found?.icon || '🔖';
|
return found?.icon || '🔖';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripWadouriPrefix(imageId?: string): string {
|
||||||
|
if (!imageId) return "";
|
||||||
|
if (!imageId.startsWith("wadouri:")) return imageId;
|
||||||
|
return imageId.replace(/^wadouri:+/, "").replace(/^:+/, "");
|
||||||
|
}
|
||||||
|
|
||||||
// Mouse button labels
|
// Mouse button labels
|
||||||
const MOUSE_BUTTONS = [
|
const MOUSE_BUTTONS = [
|
||||||
{ label: "Left", value: "Primary" },
|
{ label: "Left", value: "Primary" },
|
||||||
@@ -601,7 +607,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
try {
|
try {
|
||||||
const first = imageIds[0];
|
const first = imageIds[0];
|
||||||
if (typeof fetch === 'function' && typeof (window as any).dicomParser !== 'undefined' && first.startsWith('wadouri:')) {
|
if (typeof fetch === 'function' && typeof (window as any).dicomParser !== 'undefined' && first.startsWith('wadouri:')) {
|
||||||
const url = first.slice(7);
|
const url = stripWadouriPrefix(first);
|
||||||
const resp = await fetch(url);
|
const resp = await fetch(url);
|
||||||
const buffer = await resp.arrayBuffer();
|
const buffer = await resp.arrayBuffer();
|
||||||
const ds = (window as any).dicomParser.parseDicom(new Uint8Array(buffer));
|
const ds = (window as any).dicomParser.parseDicom(new Uint8Array(buffer));
|
||||||
@@ -908,7 +914,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
for (const imageId of imageIds) {
|
for (const imageId of imageIds) {
|
||||||
if (imageId.startsWith("wadouri:")) {
|
if (imageId.startsWith("wadouri:")) {
|
||||||
try {
|
try {
|
||||||
const url = imageId.slice(8);
|
const url = stripWadouriPrefix(imageId);
|
||||||
if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) {
|
if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const result = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.load(url);
|
const result = cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.load(url);
|
||||||
@@ -1018,11 +1024,11 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
() => Array(MAX_VIEWPORTS).fill(null).map(() => [])
|
() => Array(MAX_VIEWPORTS).fill(null).map(() => [])
|
||||||
);
|
);
|
||||||
const viewportStackSignaturesRef = useRef<string[]>(Array(MAX_VIEWPORTS).fill(""));
|
const viewportStackSignaturesRef = useRef<string[]>(Array(MAX_VIEWPORTS).fill(""));
|
||||||
|
const defaultVoiRangeByViewportRef = useRef<Array<{ lower: number; upper: number } | null>>(Array(MAX_VIEWPORTS).fill(null));
|
||||||
|
|
||||||
const [viewportModes, setViewportModes] = useState<Array<'stack' | 'volume'>>(
|
const [viewportModes, setViewportModes] = useState<Array<'stack' | 'volume'>>(
|
||||||
() => Array(MAX_VIEWPORTS).fill('stack')
|
() => Array(MAX_VIEWPORTS).fill('stack')
|
||||||
);
|
);
|
||||||
|
|
||||||
// State for preset menu open/close per viewport
|
// State for preset menu open/close per viewport
|
||||||
const [presetsOpenArr, setPresetsOpenArr] = useState<boolean[]>(
|
const [presetsOpenArr, setPresetsOpenArr] = useState<boolean[]>(
|
||||||
() => Array(viewportGrid.rows * viewportGrid.cols).fill(false)
|
() => Array(viewportGrid.rows * viewportGrid.cols).fill(false)
|
||||||
@@ -1675,7 +1681,27 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const currentImageId = imageIds[currentIndex];
|
const currentImageId = imageIds[currentIndex];
|
||||||
if (currentImageId) {
|
if (currentImageId) {
|
||||||
const seriesMeta = metaData.get("generalSeriesModule", currentImageId) as any;
|
const seriesMeta = metaData.get("generalSeriesModule", currentImageId) as any;
|
||||||
|
const planeMeta = metaData.get("imagePlaneModule", currentImageId) as any;
|
||||||
|
const pixelMeta = metaData.get("imagePixelModule", currentImageId) as any;
|
||||||
modality = seriesMeta?.modality || "";
|
modality = seriesMeta?.modality || "";
|
||||||
|
if (pixelMeta?.rows && pixelMeta?.columns) {
|
||||||
|
resolution = `${pixelMeta.columns} x ${pixelMeta.rows}`;
|
||||||
|
}
|
||||||
|
const spacingParts = [
|
||||||
|
planeMeta?.columnPixelSpacing,
|
||||||
|
planeMeta?.rowPixelSpacing,
|
||||||
|
planeMeta?.sliceThickness,
|
||||||
|
].filter((value) => value !== undefined && value !== null && value !== "");
|
||||||
|
if (spacingParts.length > 0) {
|
||||||
|
spacing = spacingParts.join(" x ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof viewport.getCamera === "function") {
|
||||||
|
const camera = viewport.getCamera();
|
||||||
|
if (camera?.focalPoint && Array.isArray(camera.focalPoint)) {
|
||||||
|
focalPoint = camera.focalPoint.map((value: number) => value.toFixed(1)).join(", ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentIndex >= 0) {
|
if (currentIndex >= 0) {
|
||||||
@@ -1697,6 +1723,9 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const { lower, upper } = props.voiRange;
|
const { lower, upper } = props.voiRange;
|
||||||
windowCenter = ((upper + lower) / 2).toFixed(0);
|
windowCenter = ((upper + lower) / 2).toFixed(0);
|
||||||
windowWidth = (upper - lower).toFixed(0);
|
windowWidth = (upper - lower).toFixed(0);
|
||||||
|
if (defaultVoiRangeByViewportRef.current[viewportIdx] === null) {
|
||||||
|
defaultVoiRangeByViewportRef.current[viewportIdx] = { lower, upper };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (viewportModes[viewportIdx] === "volume") {
|
} else if (viewportModes[viewportIdx] === "volume") {
|
||||||
@@ -1721,6 +1750,9 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const { lower, upper } = props.voiRange;
|
const { lower, upper } = props.voiRange;
|
||||||
windowCenter = ((upper + lower) / 2).toFixed(0);
|
windowCenter = ((upper + lower) / 2).toFixed(0);
|
||||||
windowWidth = (upper - lower).toFixed(0);
|
windowWidth = (upper - lower).toFixed(0);
|
||||||
|
if (defaultVoiRangeByViewportRef.current[viewportIdx] === null) {
|
||||||
|
defaultVoiRangeByViewportRef.current[viewportIdx] = { lower, upper };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try to get slice position in mm (distance along viewPlaneNormal from origin)
|
// Try to get slice position in mm (distance along viewPlaneNormal from origin)
|
||||||
@@ -1787,6 +1819,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const signature = `${ids.length}:${ids[0] || ""}:${ids[ids.length - 1] || ""}`;
|
const signature = `${ids.length}:${ids[0] || ""}:${ids[ids.length - 1] || ""}`;
|
||||||
if (viewportStackSignaturesRef.current[i] !== signature) {
|
if (viewportStackSignaturesRef.current[i] !== signature) {
|
||||||
viewportStackSignaturesRef.current[i] = signature;
|
viewportStackSignaturesRef.current[i] = signature;
|
||||||
|
defaultVoiRangeByViewportRef.current[i] = null;
|
||||||
next[i] = [];
|
next[i] = [];
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -2290,6 +2323,52 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
}
|
}
|
||||||
}, [updateOverlay]);
|
}, [updateOverlay]);
|
||||||
|
|
||||||
|
const applyDefaultWindowLevel = useCallback((viewportIdx: number, resetCamera = false) => {
|
||||||
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
|
try {
|
||||||
|
const renderingEngine = renderingEngineRef.current;
|
||||||
|
const viewport = renderingEngine?.getViewport(viewportId);
|
||||||
|
if (!viewport) return false;
|
||||||
|
|
||||||
|
const ids = typeof viewport.getImageIds === "function"
|
||||||
|
? viewport.getImageIds()
|
||||||
|
: (viewportImageIds[viewportIdx] || []);
|
||||||
|
const currentIndex = typeof viewport.getCurrentImageIdIndex === "function"
|
||||||
|
? viewport.getCurrentImageIdIndex()
|
||||||
|
: 0;
|
||||||
|
const imageId = ids[currentIndex] || ids[0];
|
||||||
|
const defaultVoi = defaultVoiRangeByViewportRef.current[viewportIdx];
|
||||||
|
const voiMeta = imageId ? (metaData.get("voiLutModule", imageId) || {}) as any : {};
|
||||||
|
const centerMeta = Array.isArray(voiMeta.windowCenter) ? voiMeta.windowCenter[0] : voiMeta.windowCenter;
|
||||||
|
const widthMeta = Array.isArray(voiMeta.windowWidth) ? voiMeta.windowWidth[0] : voiMeta.windowWidth;
|
||||||
|
|
||||||
|
if (defaultVoi) {
|
||||||
|
viewport.setProperties({ voiRange: defaultVoi });
|
||||||
|
} else if (Number.isFinite(Number(centerMeta)) && Number.isFinite(Number(widthMeta)) && Number(widthMeta) > 0) {
|
||||||
|
const center = Number(centerMeta);
|
||||||
|
const width = Number(widthMeta);
|
||||||
|
viewport.setProperties({
|
||||||
|
voiRange: {
|
||||||
|
lower: center - width / 2,
|
||||||
|
upper: center + width / 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (typeof viewport.resetProperties === "function") {
|
||||||
|
viewport.resetProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetCamera && typeof viewport.resetCamera === "function") {
|
||||||
|
viewport.resetCamera();
|
||||||
|
}
|
||||||
|
viewport.render();
|
||||||
|
updateOverlay(viewportIdx, viewport);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Failed to apply default window level", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [updateOverlay, viewportImageIds]);
|
||||||
|
|
||||||
const handleAutoLevel = useCallback((viewportIdx: number) => {
|
const handleAutoLevel = useCallback((viewportIdx: number) => {
|
||||||
const viewportId = `CT_${viewportIdx}`;
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
try {
|
try {
|
||||||
@@ -2307,39 +2386,51 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const imageId = ids[currentIndex] || ids[0];
|
const imageId = ids[currentIndex] || ids[0];
|
||||||
|
|
||||||
if (!imageId) {
|
if (!imageId) {
|
||||||
if (typeof viewport.resetProperties === "function") viewport.resetProperties();
|
applyDefaultWindowLevel(viewportIdx);
|
||||||
viewport.render();
|
|
||||||
updateOverlay(viewportIdx, viewport);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixelMeta = (metaData.get("imagePixelModule", imageId) || {}) as any;
|
const pixelMeta = (metaData.get("imagePixelModule", imageId) || {}) as any;
|
||||||
const lutMeta = (metaData.get("modalityLutModule", imageId) || {}) as any;
|
const lutMeta = (metaData.get("modalityLutModule", imageId) || {}) as any;
|
||||||
|
const voiMeta = (metaData.get("voiLutModule", imageId) || {}) as any;
|
||||||
|
const defaultVoi = defaultVoiRangeByViewportRef.current[viewportIdx];
|
||||||
|
|
||||||
const minRaw = Number(pixelMeta.smallestPixelValue ?? pixelMeta.smallestImagePixelValue);
|
const minRaw = Number(pixelMeta.smallestPixelValue ?? pixelMeta.smallestImagePixelValue);
|
||||||
const maxRaw = Number(pixelMeta.largestPixelValue ?? pixelMeta.largestImagePixelValue);
|
const maxRaw = Number(pixelMeta.largestPixelValue ?? pixelMeta.largestImagePixelValue);
|
||||||
const slope = Number(lutMeta.rescaleSlope ?? 1);
|
const slope = Number(lutMeta.rescaleSlope ?? 1);
|
||||||
const intercept = Number(lutMeta.rescaleIntercept ?? 0);
|
const intercept = Number(lutMeta.rescaleIntercept ?? 0);
|
||||||
|
const centerMeta = Array.isArray(voiMeta.windowCenter) ? voiMeta.windowCenter[0] : voiMeta.windowCenter;
|
||||||
|
const widthMeta = Array.isArray(voiMeta.windowWidth) ? voiMeta.windowWidth[0] : voiMeta.windowWidth;
|
||||||
|
|
||||||
if (!Number.isFinite(minRaw) || !Number.isFinite(maxRaw) || maxRaw <= minRaw) {
|
if (Number.isFinite(minRaw) && Number.isFinite(maxRaw) && maxRaw > minRaw) {
|
||||||
if (typeof viewport.resetProperties === "function") viewport.resetProperties();
|
viewport.setProperties({
|
||||||
viewport.render();
|
voiRange: {
|
||||||
updateOverlay(viewportIdx, viewport);
|
lower: minRaw * slope + intercept,
|
||||||
|
upper: maxRaw * slope + intercept,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (Number.isFinite(Number(centerMeta)) && Number.isFinite(Number(widthMeta)) && Number(widthMeta) > 0) {
|
||||||
|
const center = Number(centerMeta);
|
||||||
|
const width = Number(widthMeta);
|
||||||
|
viewport.setProperties({
|
||||||
|
voiRange: {
|
||||||
|
lower: center - width / 2,
|
||||||
|
upper: center + width / 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (defaultVoi) {
|
||||||
|
viewport.setProperties({ voiRange: defaultVoi });
|
||||||
|
} else {
|
||||||
|
applyDefaultWindowLevel(viewportIdx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport.setProperties({
|
|
||||||
voiRange: {
|
|
||||||
lower: minRaw * slope + intercept,
|
|
||||||
upper: maxRaw * slope + intercept,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
viewport.render();
|
viewport.render();
|
||||||
updateOverlay(viewportIdx, viewport);
|
updateOverlay(viewportIdx, viewport);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore failures and keep existing VOI.
|
applyDefaultWindowLevel(viewportIdx);
|
||||||
}
|
}
|
||||||
}, [updateOverlay, viewportImageIds]);
|
}, [applyDefaultWindowLevel, updateOverlay, viewportImageIds]);
|
||||||
|
|
||||||
const toggleViewerFullscreen = useCallback(() => {
|
const toggleViewerFullscreen = useCallback(() => {
|
||||||
const root = appRootRef.current;
|
const root = appRootRef.current;
|
||||||
@@ -2461,7 +2552,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
try {
|
try {
|
||||||
const firstId = wadouriImageIds[0];
|
const firstId = wadouriImageIds[0];
|
||||||
if (firstId && firstId.startsWith("wadouri:")) {
|
if (firstId && firstId.startsWith("wadouri:")) {
|
||||||
const url = firstId.slice(8);
|
const url = stripWadouriPrefix(firstId);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
||||||
@@ -2686,8 +2777,8 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
if (!viewport || typeof viewport.getImageIds !== "function") return;
|
if (!viewport || typeof viewport.getImageIds !== "function") return;
|
||||||
const imageIds = viewport.getImageIds();
|
const imageIds = viewport.getImageIds();
|
||||||
// Some stacks may have imageIds with or without "wadouri:" prefix, so normalize for comparison
|
// Some stacks may have imageIds with or without "wadouri:" prefix, so normalize for comparison
|
||||||
const normImageIds = imageIds.map(id => id.replace(/^wadouri:/, ""));
|
const normImageIds = imageIds.map(id => stripWadouriPrefix(id));
|
||||||
const normTarget = imageId.replace(/^wadouri:/, "");
|
const normTarget = stripWadouriPrefix(imageId);
|
||||||
const idx = normImageIds.indexOf(normTarget);
|
const idx = normImageIds.indexOf(normTarget);
|
||||||
if (idx !== -1 && typeof viewport.setImageIdIndex === "function") {
|
if (idx !== -1 && typeof viewport.setImageIdIndex === "function") {
|
||||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
|
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
|
||||||
@@ -2720,7 +2811,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
let uid = studyInstanceUID;
|
let uid = studyInstanceUID;
|
||||||
if (!uid && imageIds[0]?.startsWith("wadouri:")) {
|
if (!uid && imageIds[0]?.startsWith("wadouri:")) {
|
||||||
try {
|
try {
|
||||||
const url = imageIds[0].slice(8);
|
const url = stripWadouriPrefix(imageIds[0]);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
||||||
@@ -3790,6 +3881,14 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
<div>
|
<div>
|
||||||
WC: {imageInfos[i]?.windowCenter} WW: {imageInfos[i]?.windowWidth}
|
WC: {imageInfos[i]?.windowCenter} WW: {imageInfos[i]?.windowWidth}
|
||||||
</div>
|
</div>
|
||||||
|
{volumeDiagnosticsEnabled && (
|
||||||
|
<>
|
||||||
|
<div>Ori: {imageInfos[i]?.orientation || "N/A"}</div>
|
||||||
|
{imageInfos[i]?.resolution && <div>Res: {imageInfos[i]?.resolution}</div>}
|
||||||
|
{imageInfos[i]?.spacing && <div>Spacing: {imageInfos[i]?.spacing}</div>}
|
||||||
|
{imageInfos[i]?.focalPoint && <div>Pos: {imageInfos[i]?.focalPoint}</div>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
@@ -3907,19 +4006,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation(); // <-- prevent bubbling to viewport
|
e.stopPropagation(); // <-- prevent bubbling to viewport
|
||||||
if (preset.action === "reset") {
|
if (preset.action === "reset") {
|
||||||
try {
|
applyDefaultWindowLevel(i, true);
|
||||||
const viewportId = `CT_${i}`;
|
|
||||||
const renderingEngine = renderingEngineRef.current;
|
|
||||||
const viewport = renderingEngine?.getViewport(viewportId);
|
|
||||||
if (viewport) {
|
|
||||||
if (typeof viewport.resetProperties === "function") viewport.resetProperties();
|
|
||||||
if (typeof viewport.resetCamera === "function") viewport.resetCamera();
|
|
||||||
if (typeof viewport.render === "function") viewport.render();
|
|
||||||
updateOverlay(i, viewport);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("Failed to reset viewport properties", err);
|
|
||||||
}
|
|
||||||
} else if (preset.action === "autolevel") {
|
} else if (preset.action === "autolevel") {
|
||||||
handleAutoLevel(i);
|
handleAutoLevel(i);
|
||||||
} else {
|
} else {
|
||||||
@@ -4722,6 +4809,9 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
width: "auto",
|
width: "auto",
|
||||||
pointerEvents: "auto",
|
pointerEvents: "auto",
|
||||||
transition: "top 180ms ease",
|
transition: "top 180ms ease",
|
||||||
|
paddingTop: 28,
|
||||||
|
marginTop: -28,
|
||||||
|
paddingBottom: 12,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => setTopControlsPeek(true)}
|
onMouseEnter={() => setTopControlsPeek(true)}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
@@ -4752,7 +4842,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: 16, lineHeight: 1 }}>⛶</span>
|
<span style={{ fontSize: 16, lineHeight: 1 }}>⛶</span>
|
||||||
<span>Viewer</span>
|
<span>Fullscreen</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div style={{ position: "relative", display: "flex", flexDirection: "column", alignItems: "stretch" }}>
|
<div style={{ position: "relative", display: "flex", flexDirection: "column", alignItems: "stretch" }}>
|
||||||
@@ -4993,7 +5083,7 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
|||||||
const midIndex = Math.floor(imageIds.length / 2);
|
const midIndex = Math.floor(imageIds.length / 2);
|
||||||
const chosenImage = imageIds[midIndex] || imageIds[0];
|
const chosenImage = imageIds[midIndex] || imageIds[0];
|
||||||
// Remove the wadouri: scheme if present. Use replace to avoid off-by-one slice errors.
|
// Remove the wadouri: scheme if present. Use replace to avoid off-by-one slice errors.
|
||||||
const thumbnailUrl = chosenImage ? chosenImage.replace(/^wadouri:/, '') : null;
|
const thumbnailUrl = chosenImage ? stripWadouriPrefix(chosenImage) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user