NeoScan_Physician/app/shared/components/DicomViewer.tsx
2025-08-14 20:16:03 +05:30

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.
*/