321 lines
8.5 KiB
TypeScript
321 lines
8.5 KiB
TypeScript
/*
|
|
* File: DicomViewer.tsx
|
|
* Description: DICOM viewer component using WebView for medical imaging
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React, { useRef, useEffect, useState } from 'react';
|
|
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
|
import { Platform, View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
|
|
// Interface for component props
|
|
interface DicomViewerProps {
|
|
dicomUrl: string;
|
|
onError?: (error: string) => void;
|
|
onLoad?: () => void;
|
|
debugMode?: boolean;
|
|
}
|
|
|
|
// Interface for WebView reference
|
|
interface WebViewRef {
|
|
postMessage: (message: string) => void;
|
|
reload: () => void;
|
|
}
|
|
|
|
export default function DicomViewer({ dicomUrl, onError, onLoad, debugMode = false }: DicomViewerProps): React.ReactElement {
|
|
const webViewRef = useRef<WebViewRef>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [hasError, setHasError] = useState(false);
|
|
const [debugInfo, setDebugInfo] = useState<string[]>([]);
|
|
const [webViewReady, setWebViewReady] = useState(false);
|
|
|
|
// Debug logging function
|
|
const debugLog = (message: string) => {
|
|
if (debugMode) {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const logMessage = `[${timestamp}] ${message}`;
|
|
console.log(logMessage);
|
|
setDebugInfo(prev => [...prev.slice(-9), logMessage]); // Keep last 10 messages
|
|
}
|
|
};
|
|
|
|
// Handle WebView load events
|
|
const handleLoadStart = () => {
|
|
debugLog('WebView load started');
|
|
setIsLoading(true);
|
|
setHasError(false);
|
|
};
|
|
|
|
const handleLoadEnd = () => {
|
|
debugLog('WebView load ended');
|
|
setIsLoading(false);
|
|
setWebViewReady(true);
|
|
onLoad?.();
|
|
};
|
|
|
|
const handleError = (error: any) => {
|
|
debugLog(`WebView error: ${JSON.stringify(error)}`);
|
|
setIsLoading(false);
|
|
setHasError(true);
|
|
onError?.(error?.nativeEvent?.description || 'Failed to load DICOM viewer');
|
|
};
|
|
|
|
const handleMessage = (event: WebViewMessageEvent) => {
|
|
try {
|
|
const message = event.nativeEvent.data;
|
|
debugLog(`Message from WebView: ${message}`);
|
|
|
|
// Try to parse JSON message
|
|
if (typeof message === 'string') {
|
|
try {
|
|
const parsedMessage = JSON.parse(message);
|
|
debugLog(`Parsed message: ${JSON.stringify(parsedMessage)}`);
|
|
|
|
if (parsedMessage.type === 'error') {
|
|
setHasError(true);
|
|
onError?.(parsedMessage.message);
|
|
} else if (parsedMessage.type === 'success') {
|
|
setHasError(false);
|
|
}
|
|
} catch (parseError) {
|
|
debugLog(`Failed to parse message as JSON: ${parseError}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
debugLog(`Error handling WebView message: ${error}`);
|
|
}
|
|
};
|
|
|
|
// Send DICOM URL to WebView when component mounts or URL changes
|
|
useEffect(() => {
|
|
if (webViewRef.current && dicomUrl && webViewReady) {
|
|
debugLog(`Sending DICOM URL to WebView: ${dicomUrl}`);
|
|
|
|
// Wait a bit for WebView to be ready
|
|
const timer = setTimeout(() => {
|
|
if (webViewRef.current) {
|
|
try {
|
|
// Send the URL directly as a string message
|
|
webViewRef.current.postMessage(dicomUrl);
|
|
debugLog('DICOM URL sent successfully');
|
|
|
|
// Also try sending as a structured message
|
|
setTimeout(() => {
|
|
if (webViewRef.current) {
|
|
const structuredMessage = JSON.stringify({
|
|
type: 'loadDicom',
|
|
data: dicomUrl
|
|
});
|
|
webViewRef.current.postMessage(structuredMessage);
|
|
debugLog('Structured DICOM message sent');
|
|
}
|
|
}, 500);
|
|
|
|
} catch (error) {
|
|
debugLog(`Failed to send DICOM URL: ${error}`);
|
|
}
|
|
}
|
|
}, 1000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [dicomUrl, webViewReady]);
|
|
|
|
// Reload WebView if there's an error
|
|
const handleRetry = () => {
|
|
debugLog('Retrying WebView load');
|
|
if (webViewRef.current) {
|
|
setHasError(false);
|
|
setIsLoading(true);
|
|
setWebViewReady(false);
|
|
webViewRef.current.reload();
|
|
}
|
|
};
|
|
|
|
// Clear debug info
|
|
const clearDebugInfo = () => {
|
|
setDebugInfo([]);
|
|
};
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<WebView
|
|
ref={webViewRef as any}
|
|
source={require('../../assets/dicom/dicom-viewer.html')}
|
|
originWhitelist={['*']}
|
|
javaScriptEnabled
|
|
domStorageEnabled
|
|
allowFileAccess
|
|
allowUniversalAccessFromFileURLs
|
|
allowFileAccessFromFileURLs
|
|
onLoadStart={handleLoadStart}
|
|
onLoadEnd={handleLoadEnd}
|
|
onError={handleError}
|
|
onMessage={handleMessage}
|
|
style={styles.webview}
|
|
startInLoadingState
|
|
renderLoading={() => (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color="#2196F3" />
|
|
<Text style={styles.loadingText}>Loading DICOM Viewer...</Text>
|
|
</View>
|
|
)}
|
|
/>
|
|
|
|
{hasError && (
|
|
<View style={styles.errorContainer}>
|
|
<Text style={styles.errorText}>Failed to load DICOM viewer</Text>
|
|
<Text style={styles.errorDetails}>
|
|
URL: {dicomUrl}
|
|
</Text>
|
|
<TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
|
|
<Text style={styles.retryButtonText}>Tap to retry</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
)}
|
|
|
|
{debugMode && (
|
|
<View style={styles.debugContainer}>
|
|
<View style={styles.debugHeader}>
|
|
<Text style={styles.debugTitle}>Debug Info</Text>
|
|
<TouchableOpacity onPress={clearDebugInfo}>
|
|
<Text style={styles.clearButton}>Clear</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
<View style={styles.debugContent}>
|
|
{debugInfo.map((info, index) => (
|
|
<Text key={index} style={styles.debugText}>{info}</Text>
|
|
))}
|
|
</View>
|
|
<View style={styles.debugStatus}>
|
|
<Text style={styles.debugStatusText}>
|
|
WebView Ready: {webViewReady ? 'Yes' : 'No'}
|
|
</Text>
|
|
<Text style={styles.debugStatusText}>
|
|
Loading: {isLoading ? 'Yes' : 'No'}
|
|
</Text>
|
|
<Text style={styles.debugStatusText}>
|
|
Error: {hasError ? 'Yes' : 'No'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#000',
|
|
},
|
|
webview: {
|
|
flex: 1,
|
|
backgroundColor: '#000',
|
|
},
|
|
loadingContainer: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#000',
|
|
},
|
|
loadingText: {
|
|
color: '#FFF',
|
|
marginTop: 16,
|
|
fontSize: 16,
|
|
},
|
|
errorContainer: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
backgroundColor: '#000',
|
|
padding: 20,
|
|
},
|
|
errorText: {
|
|
color: '#F44336',
|
|
fontSize: 18,
|
|
textAlign: 'center',
|
|
marginBottom: 16,
|
|
fontWeight: '600',
|
|
},
|
|
errorDetails: {
|
|
color: '#FF9800',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
marginBottom: 20,
|
|
fontFamily: 'monospace',
|
|
},
|
|
retryButton: {
|
|
backgroundColor: '#2196F3',
|
|
paddingHorizontal: 24,
|
|
paddingVertical: 12,
|
|
borderRadius: 8,
|
|
},
|
|
retryButtonText: {
|
|
color: '#FFFFFF',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
debugContainer: {
|
|
position: 'absolute',
|
|
top: 10,
|
|
right: 10,
|
|
backgroundColor: 'rgba(0,0,0,0.9)',
|
|
borderRadius: 8,
|
|
padding: 10,
|
|
maxWidth: 300,
|
|
maxHeight: 400,
|
|
},
|
|
debugHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
debugTitle: {
|
|
color: '#FFFFFF',
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
clearButton: {
|
|
color: '#2196F3',
|
|
fontSize: 12,
|
|
textDecorationLine: 'underline',
|
|
},
|
|
debugContent: {
|
|
maxHeight: 200,
|
|
},
|
|
debugText: {
|
|
color: '#FFFFFF',
|
|
fontSize: 10,
|
|
fontFamily: 'monospace',
|
|
marginBottom: 2,
|
|
},
|
|
debugStatus: {
|
|
marginTop: 8,
|
|
paddingTop: 8,
|
|
borderTopColor: '#333',
|
|
borderTopWidth: 1,
|
|
},
|
|
debugStatusText: {
|
|
color: '#CCC',
|
|
fontSize: 10,
|
|
marginBottom: 2,
|
|
},
|
|
});
|
|
|
|
/*
|
|
* End of File: DicomViewer.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|