murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
177 lines (176 loc) • 7.35 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Component } from 'react';
export class ErrorBoundary extends Component {
constructor() {
super(...arguments);
this.resetTimeoutId = null;
this.state = {
hasError: false,
error: null,
errorInfo: null
};
this.resetErrorBoundary = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
this.handleReload = () => {
this.resetErrorBoundary();
// If still erroring after reset, reload the page
this.resetTimeoutId = window.setTimeout(() => {
window.location.reload();
}, 100);
};
this.handleKeyDown = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this.handleReload();
}
};
}
static getDerivedStateFromError(error) {
return {
hasError: true,
error
};
}
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
// Call custom error handler if provided
this.props.onError?.(error, errorInfo);
// Log error for debugging
console.error('🚨 Murmuraba ErrorBoundary caught an error:', error, errorInfo);
}
componentDidUpdate(prevProps) {
const { resetOnPropsChange } = this.props;
const { hasError } = this.state;
// Reset error state if props change and resetOnPropsChange is enabled
if (resetOnPropsChange && hasError && prevProps.children !== this.props.children) {
this.resetErrorBoundary();
}
}
componentWillUnmount() {
if (this.resetTimeoutId) {
window.clearTimeout(this.resetTimeoutId);
}
}
render() {
const { hasError, error, errorInfo } = this.state;
const { children, fallback, className = '', 'aria-label': ariaLabel } = this.props;
if (hasError && error) {
// Use custom fallback if provided
if (fallback) {
return fallback(error, errorInfo);
}
// Default error UI
return (_jsx("div", { className: `murmuraba-error-boundary ${className}`, role: "alert", "aria-label": ariaLabel || 'Application error occurred', style: defaultStyles.container, children: _jsxs("div", { style: defaultStyles.card, children: [_jsx("div", { style: defaultStyles.icon, "aria-hidden": "true", children: "\uD83C\uDF3E" }), _jsx("h1", { style: defaultStyles.title, children: "Oops! Something went wrong" }), _jsx("p", { style: defaultStyles.message, children: "The audio processing application encountered an unexpected error. This might be due to browser compatibility or a temporary issue." }), _jsxs("button", { onClick: this.handleReload, onKeyDown: this.handleKeyDown, style: defaultStyles.button, "aria-label": "Reload application to recover from error", children: [_jsx("span", { style: defaultStyles.buttonIcon, "aria-hidden": "true", children: "\uD83D\uDD04" }), _jsx("span", { children: "Reload Application" })] }), process.env.NODE_ENV === 'development' && error && (_jsxs("details", { style: defaultStyles.details, children: [_jsx("summary", { style: defaultStyles.summary, children: "\uD83D\uDD0D Error Details (Development)" }), _jsxs("div", { style: defaultStyles.errorContent, children: [_jsx("h4", { style: defaultStyles.errorTitle, children: "Error:" }), _jsx("pre", { style: defaultStyles.errorText, children: error.toString() }), error.stack && (_jsxs(_Fragment, { children: [_jsx("h4", { style: defaultStyles.errorTitle, children: "Stack Trace:" }), _jsx("pre", { style: defaultStyles.errorText, children: error.stack })] })), errorInfo?.componentStack && (_jsxs(_Fragment, { children: [_jsx("h4", { style: defaultStyles.errorTitle, children: "Component Stack:" }), _jsx("pre", { style: defaultStyles.errorText, children: errorInfo.componentStack })] }))] })] }))] }) }));
}
return children;
}
}
// Default styles for the error boundary
const defaultStyles = {
container: {
minHeight: '400px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem',
backgroundColor: 'var(--dark-bg-primary, #0A0B0E)',
fontFamily: 'system-ui, -apple-system, sans-serif',
},
card: {
maxWidth: '500px',
width: '100%',
padding: '3rem 2rem',
backgroundColor: 'var(--dark-surface, #1F2028)',
borderRadius: '12px',
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.1)',
textAlign: 'center',
border: '1px solid var(--neutral-400, #4E5165)',
},
icon: {
fontSize: '4rem',
marginBottom: '1.5rem',
display: 'block',
},
title: {
fontSize: '1.75rem',
fontWeight: 'bold',
color: 'var(--dark-text-primary, #CACBDA)',
marginBottom: '1rem',
margin: '0 0 1rem 0',
},
message: {
fontSize: '1rem',
color: 'var(--dark-text-secondary, #A0A1B3)',
lineHeight: '1.6',
marginBottom: '2rem',
margin: '0 0 2rem 0',
},
button: {
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.75rem 1.5rem',
backgroundColor: 'var(--accent-green, #52A32F)',
color: 'var(--dark-text-primary, #CACBDA)',
border: 'none',
borderRadius: '6px',
fontSize: '1rem',
fontWeight: '500',
cursor: 'pointer',
transition: 'all 0.2s ease',
':hover': {
backgroundColor: 'var(--accent-green-light, #6BC748)',
},
},
buttonIcon: {
fontSize: '1rem',
},
details: {
marginTop: '2rem',
textAlign: 'left',
backgroundColor: 'var(--dark-bg-tertiary, #1A1B23)',
borderRadius: '6px',
padding: '1rem',
},
summary: {
cursor: 'pointer',
fontWeight: '500',
color: 'var(--dark-text-secondary, #A0A1B3)',
marginBottom: '1rem',
fontSize: '0.9rem',
},
errorContent: {
marginTop: '1rem',
},
errorTitle: {
fontSize: '0.875rem',
fontWeight: '600',
color: 'var(--dark-text-primary, #CACBDA)',
marginBottom: '0.5rem',
margin: '1rem 0 0.5rem 0',
},
errorText: {
fontSize: '0.75rem',
fontFamily: 'Monaco, Consolas, "Courier New", monospace',
backgroundColor: '#edf2f7',
padding: '0.75rem',
borderRadius: '4px',
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: 'var(--dark-text-primary, #CACBDA)',
border: '1px solid #cbd5e0',
margin: '0 0 0.5rem 0',
},
};
// Export a HOC version for easier usage
export function withErrorBoundary(Component, errorBoundaryProps) {
const WrappedComponent = (props) => (_jsx(ErrorBoundary, { ...errorBoundaryProps, children: _jsx(Component, { ...props }) }));
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
return WrappedComponent;
}