Параллельные шаги (#3)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/3
This commit was merged in pull request #3.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
@import '../../../../../styles/design-tokens';
|
||||
|
||||
// Поверх всего, включая SPFx-хром вебпарта — иначе падение внутри вложенного
|
||||
// блока схлопывает только его, а не видно, что вообще произошло.
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 999999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $sp-6;
|
||||
background: rgba($navy-950, .78);
|
||||
font-family: $font-sans;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 780px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $sp-6;
|
||||
border-radius: $r-lg;
|
||||
background: $white;
|
||||
box-shadow: $sh-modal;
|
||||
border-top: 4px solid $red-500;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-form-title;
|
||||
font-weight: 600;
|
||||
color: $navy-950;
|
||||
}
|
||||
|
||||
.source {
|
||||
margin-top: $sp-1;
|
||||
font-size: $fs-caption;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
.stack {
|
||||
box-sizing: border-box;
|
||||
margin: $sp-4 0 0;
|
||||
padding: $sp-3;
|
||||
overflow: auto;
|
||||
background: $gray-50;
|
||||
border: 1px solid $gray-200;
|
||||
border-radius: $r-sm;
|
||||
font-family: 'Consolas', 'Menlo', monospace;
|
||||
font-size: $fs-caption;
|
||||
color: $gray-900;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: $sp-2;
|
||||
margin-top: $sp-4;
|
||||
}
|
||||
|
||||
.copyBtn {
|
||||
@include btn-accent;
|
||||
padding: 9px 16px;
|
||||
border-radius: $r-sm;
|
||||
font-size: $fs-sm;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reloadBtn {
|
||||
padding: 9px 16px;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
background: $white;
|
||||
color: $navy-800;
|
||||
font-size: $fs-sm;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: $gold-400;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import * as React from 'react';
|
||||
import styles from './ErrorOverlay.module.scss';
|
||||
|
||||
interface ICaughtError {
|
||||
message: string;
|
||||
stack: string;
|
||||
source: string; // откуда поймана: React-рендер, window.onerror, unhandledrejection
|
||||
}
|
||||
|
||||
interface IErrorOverlayState {
|
||||
error: ICaughtError | undefined;
|
||||
}
|
||||
|
||||
function formatError(err: unknown, source: string): ICaughtError {
|
||||
const e = err instanceof Error ? err : new Error(typeof err === 'string' ? err : JSON.stringify(err));
|
||||
return { message: e.message, stack: e.stack || '(нет стека)', source };
|
||||
}
|
||||
|
||||
// Копия message+stack в буфер обмена, чтобы кинуть разработчику. Ошибка
|
||||
// самого копирования не должна маскировать исходную — просто игнорируем.
|
||||
function copyToClipboard(text: string): void {
|
||||
navigator.clipboard.writeText(text).catch(() => undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ловит падения, которые иначе схлопывают вебпарт в SPFx workbench:
|
||||
* 1) React-рендер (componentDidCatch);
|
||||
* 2) синхронные ошибки вне React (window.onerror);
|
||||
* 3) необработанные rejected promise (unhandledrejection).
|
||||
* Вместо пустого места рисует поверх всего оверлей с текстом ошибки и кнопкой
|
||||
* «Скопировать», чтобы можно было переслать стек трейс.
|
||||
*/
|
||||
export class ErrorOverlay extends React.Component<React.PropsWithChildren<unknown>, IErrorOverlayState> {
|
||||
public state: IErrorOverlayState = { error: undefined };
|
||||
|
||||
private onWindowError = (event: ErrorEvent): void => {
|
||||
this.setState({ error: formatError(event.error || event.message, 'window.onerror') });
|
||||
};
|
||||
|
||||
private onUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
||||
this.setState({ error: formatError(event.reason, 'unhandledrejection') });
|
||||
};
|
||||
|
||||
public componentDidMount(): void {
|
||||
window.addEventListener('error', this.onWindowError);
|
||||
window.addEventListener('unhandledrejection', this.onUnhandledRejection);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.removeEventListener('error', this.onWindowError);
|
||||
window.removeEventListener('unhandledrejection', this.onUnhandledRejection);
|
||||
}
|
||||
|
||||
public static getDerivedStateFromError(err: unknown): IErrorOverlayState {
|
||||
return { error: formatError(err, 'React render') };
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const { error } = this.state;
|
||||
if (!error) return this.props.children;
|
||||
|
||||
const fullText = `[${error.source}] ${error.message}\n\n${error.stack}`;
|
||||
|
||||
return (
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.title}>Необработанное исключение</div>
|
||||
<div className={styles.source}>{error.source}</div>
|
||||
<pre className={styles.stack}>{fullText}</pre>
|
||||
<div className={styles.actions}>
|
||||
<button type="button" className={styles.copyBtn} onClick={() => copyToClipboard(fullText)}>
|
||||
Скопировать
|
||||
</button>
|
||||
<button type="button" className={styles.reloadBtn} onClick={() => window.location.reload()}>
|
||||
Перезагрузить страницу
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export { PeopleField } from './PeopleField/PeopleField';
|
||||
export { PeopleFieldMulti } from './PeopleField/PeopleFieldMulti';
|
||||
export { FormModeProvider, useFormReadOnly } from './FormMode';
|
||||
export { Icon } from './Icon/Icon';
|
||||
export { ErrorOverlay } from './ErrorOverlay/ErrorOverlay';
|
||||
export { SubmitActions } from './SubmitActions/SubmitActions';
|
||||
export { RequestNumber } from './RequestNumber/RequestNumber';
|
||||
export { PrintButton } from './PrintButton/PrintButton';
|
||||
|
||||
Reference in New Issue
Block a user