04c75cd725
Guard TipTap getHTML under StrictMode, wrap secondary window roots in WindowErrorBoundary, and add stability regression tests. Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
2.2 KiB
TypeScript
81 lines
2.2 KiB
TypeScript
import React from 'react';
|
||
|
||
type WindowErrorBoundaryProps = {
|
||
title?: string;
|
||
children: React.ReactNode;
|
||
};
|
||
|
||
type WindowErrorBoundaryState = {
|
||
error: Error | null;
|
||
};
|
||
|
||
/**
|
||
* Ловит падения React в дочерних окнах (НПС, материалы, редактор сцены…),
|
||
* чтобы вместо чёрного экрана показать сообщение и кнопку перезагрузки.
|
||
*/
|
||
export class WindowErrorBoundary extends React.Component<
|
||
WindowErrorBoundaryProps,
|
||
WindowErrorBoundaryState
|
||
> {
|
||
state: WindowErrorBoundaryState = { error: null };
|
||
|
||
static getDerivedStateFromError(error: Error): WindowErrorBoundaryState {
|
||
return { error };
|
||
}
|
||
|
||
override componentDidCatch(error: Error, info: React.ErrorInfo): void {
|
||
console.error('[WindowErrorBoundary]', error, info.componentStack);
|
||
}
|
||
|
||
private handleReload = (): void => {
|
||
this.setState({ error: null });
|
||
window.location.reload();
|
||
};
|
||
|
||
override render(): React.ReactNode {
|
||
if (!this.state.error) return this.props.children;
|
||
|
||
const title = this.props.title ?? 'Ошибка окна';
|
||
const message = this.state.error.message || String(this.state.error);
|
||
|
||
return (
|
||
<div
|
||
role="alert"
|
||
style={{
|
||
height: '100vh',
|
||
boxSizing: 'border-box',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
gap: 12,
|
||
padding: 24,
|
||
background: '#09090b',
|
||
color: '#e4e4e7',
|
||
fontFamily: 'system-ui, sans-serif',
|
||
textAlign: 'center',
|
||
}}
|
||
>
|
||
<div style={{ fontWeight: 800, fontSize: 16 }}>{title}</div>
|
||
<div style={{ opacity: 0.85, fontSize: 13, maxWidth: 480, lineHeight: 1.45 }}>{message}</div>
|
||
<button
|
||
type="button"
|
||
onClick={this.handleReload}
|
||
style={{
|
||
marginTop: 8,
|
||
height: 34,
|
||
padding: '0 14px',
|
||
borderRadius: 8,
|
||
border: '1px solid rgba(255,255,255,0.16)',
|
||
background: '#27272a',
|
||
color: '#fafafa',
|
||
cursor: 'pointer',
|
||
}}
|
||
>
|
||
Перезагрузить
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
}
|