Skip to content

Commit

Permalink
Add errorBoundaries on all main components to prevent crashing the wh…
Browse files Browse the repository at this point in the history
…ole app (#5889)

Do not show in changelog
  • Loading branch information
ClementPasteau authored Nov 10, 2023
1 parent d39cfd1 commit 7229406
Show file tree
Hide file tree
Showing 38 changed files with 769 additions and 258 deletions.
19 changes: 16 additions & 3 deletions newIDE/app/src/AssetStore/ExtensionStore/ExtensionsSearchDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { useResponsiveWindowWidth } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
import Download from '../../UI/CustomSvgIcons/Download';
import Add from '../../UI/CustomSvgIcons/Add';
import ErrorBoundary from '../../UI/ErrorBoundary';

type Props = {|
project: gdProject,
Expand All @@ -31,13 +32,13 @@ type Props = {|
/**
* Allows to browse and install events based extensions.
*/
export default function ExtensionsSearchDialog({
const ExtensionsSearchDialog = ({
project,
onClose,
onInstallExtension,
onExtensionInstalled,
onCreateNew,
}: Props) {
}: Props) => {
const windowWidth = useResponsiveWindowWidth();
const isMobileScreen = windowWidth === 'small';
const [isInstalling, setIsInstalling] = React.useState(false);
Expand Down Expand Up @@ -171,4 +172,16 @@ export default function ExtensionsSearchDialog({
)}
</I18n>
);
}
};

const ExtensionsSearchDialogWithErrorBoundary = (props: Props) => (
<ErrorBoundary
componentTitle={<Trans>Extensions search</Trans>}
scope="extensions-search-dialog"
onClose={props.onClose}
>
<ExtensionsSearchDialog {...props} />
</ErrorBoundary>
);

export default ExtensionsSearchDialogWithErrorBoundary;
15 changes: 14 additions & 1 deletion newIDE/app/src/AssetStore/NewObjectDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { enumerateAssetStoreIds } from './EnumerateAssetStoreIds';
import PromisePool from '@supercharge/promise-pool';
import NewObjectFromScratch from './NewObjectFromScratch';
import { getAssetShortHeadersToDisplay } from './AssetsList';
import ErrorBoundary from '../UI/ErrorBoundary';

const isDev = Window.isDev();

Expand Down Expand Up @@ -104,7 +105,7 @@ type Props = {|
canInstallPrivateAsset: () => boolean,
|};

export default function NewObjectDialog({
function NewObjectDialog({
project,
layout,
objectsContainer,
Expand Down Expand Up @@ -501,3 +502,15 @@ export default function NewObjectDialog({
</I18n>
);
}

const NewObjectDialogWithErrorBoundary = (props: Props) => (
<ErrorBoundary
componentTitle={<Trans>New Object dialog</Trans>}
scope="new-object-dialog"
onClose={props.onClose}
>
<NewObjectDialog {...props} />
</ErrorBoundary>
);

export default NewObjectDialogWithErrorBoundary;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import ExtensionsSearchDialog from '../../AssetStore/ExtensionStore/ExtensionsSearchDialog';
import { sendBehaviorAdded } from '../../Utils/Analytics/EventSender';
import { useShouldAutofocusInput } from '../../UI/Reponsive/ScreenTypeMeasurer';
import ErrorBoundary from '../../UI/ErrorBoundary';

const styles = {
fullHeightSelector: {
Expand Down Expand Up @@ -88,7 +89,7 @@ const getInitialTab = (
* A responsive instruction editor in a dialog, showing InstructionParametersEditor
* at the end.
*/
export default function InstructionEditorDialog({
const InstructionEditorDialog = ({
project,
globalObjectsContainer,
objectsContainer,
Expand All @@ -101,7 +102,7 @@ export default function InstructionEditorDialog({
onSubmit,
resourceManagementProps,
openInstructionOrExpression,
}: Props) {
}: Props) => {
const forceUpdate = useForceUpdate();
const [
instructionEditorState,
Expand Down Expand Up @@ -438,4 +439,16 @@ export default function InstructionEditorDialog({
)}
</>
);
}
};

const InstructionEditorDialogWithErrorBoundary = (props: Props) => (
<ErrorBoundary
componentTitle={<Trans>Instruction editor</Trans>}
scope="scene-events-instruction-editor"
onClose={props.onCancel}
>
<InstructionEditorDialog {...props} />
</ErrorBoundary>
);

export default InstructionEditorDialogWithErrorBoundary;
57 changes: 32 additions & 25 deletions newIDE/app/src/EventsSheet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import { TutorialContext } from '../Tutorial/TutorialContext';
import { type Tutorial } from '../Utils/GDevelopServices/Tutorial';
import AlertMessage from '../UI/AlertMessage';
import { Column, Line } from '../UI/Grid';
import ErrorBoundary from '../UI/ErrorBoundary';

const gd: libGDevelop = global.gd;

Expand Down Expand Up @@ -1866,31 +1867,37 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
tutorials={tutorials}
/>
{this.state.showSearchPanel && (
<SearchPanel
ref={searchPanel => (this._searchPanel = searchPanel)}
onSearchInEvents={inputs =>
this._searchInEvents(searchInEvents, inputs)
}
onReplaceInEvents={inputs => {
this._replaceInEvents(replaceInEvents, inputs);
}}
resultsCount={
eventsSearchResultEvents
? eventsSearchResultEvents.length
: null
}
hasEventSelected={hasEventSelected(this.state.selection)}
onGoToPreviousSearchResult={() =>
this._ensureEventUnfolded(goToPreviousSearchResult)
}
onCloseSearchPanel={() => {
this._closeSearchPanel();
}}
onGoToNextSearchResult={() =>
this._ensureEventUnfolded(goToNextSearchResult)
}
searchFocusOffset={searchFocusOffset}
/>
<ErrorBoundary
componentTitle={<Trans>Search panel</Trans>}
scope="scene-events-search"
onClose={() => this._closeSearchPanel()}
>
<SearchPanel
ref={searchPanel => (this._searchPanel = searchPanel)}
onSearchInEvents={inputs =>
this._searchInEvents(searchInEvents, inputs)
}
onReplaceInEvents={inputs => {
this._replaceInEvents(replaceInEvents, inputs);
}}
resultsCount={
eventsSearchResultEvents
? eventsSearchResultEvents.length
: null
}
hasEventSelected={hasEventSelected(this.state.selection)}
onGoToPreviousSearchResult={() =>
this._ensureEventUnfolded(goToPreviousSearchResult)
}
onCloseSearchPanel={() => {
this._closeSearchPanel();
}}
onGoToNextSearchResult={() =>
this._ensureEventUnfolded(goToNextSearchResult)
}
searchFocusOffset={searchFocusOffset}
/>
</ErrorBoundary>
)}
<InlineParameterEditor
open={this.state.inlineEditing}
Expand Down
13 changes: 12 additions & 1 deletion newIDE/app/src/ExportAndShare/ShareDialog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import useAlertDialog from '../../UI/Alert/useAlertDialog';
import PreferencesContext from '../../MainFrame/Preferences/PreferencesContext';
import { type FileMetadata, type StorageProvider } from '../../ProjectsStorage';
import { useOnlineStatus } from '../../Utils/OnlineStatus';
import ErrorBoundary from '../../UI/ErrorBoundary';

export type ShareTab = 'invite' | 'publish';
export type ExporterSection = 'browser' | 'desktop' | 'mobile';
Expand Down Expand Up @@ -360,4 +361,14 @@ const ShareDialog = ({
);
};

export default ShareDialog;
const ShareDialogWithErrorBoundary = (props: Props) => (
<ErrorBoundary
componentTitle={<Trans>Share dialog</Trans>}
scope="export-and-share"
onClose={props.onClose}
>
<ShareDialog {...props} />
</ErrorBoundary>
);

export default ShareDialogWithErrorBoundary;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useDebounce } from '../Utils/UseDebounce';
import Rectangle from '../Utils/Rectangle';
import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
import { useIsMounted } from '../Utils/UseIsMounted';
import ErrorBoundary from '../UI/ErrorBoundary';
import { Trans } from '@lingui/macro';

const SCROLLBAR_DETECTION_WIDTH = 50;
// Those scrollbar dimensions should be the same as in the CSS file Scrollbar.css
Expand Down Expand Up @@ -378,38 +380,45 @@ const FullSizeInstancesEditorWithScrollbars = (props: Props) => {
{({ width, height }) => (
<div style={styles.container}>
{width !== undefined && height !== undefined && (
<InstancesEditor
onViewPositionChanged={
screenType !== 'touch' ? handleViewPositionChange : noop
}
ref={(editor: ?InstancesEditor) => {
wrappedEditorRef && wrappedEditorRef(editor);
editorRef.current = editor;
}}
width={width}
height={height}
screenType={screenType}
onMouseMove={onMouseMoveOverInstanceEditor}
onMouseLeave={(event: MouseEvent) => {
const { relatedTarget } = event;
if (!isDragging.current && relatedTarget) {
if (
// Flow says className is not present in ElementTarget but this piece
// of code cannot break.
// $FlowFixMe
relatedTarget.className &&
typeof relatedTarget.className === 'string' &&
// Hide only if the mouse is not leaving to go on one of the scrollbars' thumb.
// $FlowFixMe
!relatedTarget.className.includes('canvas-scrollbar-thumb')
) {
hideScrollbarsAfterDelay();
}
<ErrorBoundary
componentTitle={<Trans>Instances editor</Trans>}
scope="scene-editor-canvas"
>
<InstancesEditor
onViewPositionChanged={
screenType !== 'touch' ? handleViewPositionChange : noop
}
}}
showObjectInstancesIn3D={values.use3DEditor}
{...otherProps}
/>
ref={(editor: ?InstancesEditor) => {
wrappedEditorRef && wrappedEditorRef(editor);
editorRef.current = editor;
}}
width={width}
height={height}
screenType={screenType}
onMouseMove={onMouseMoveOverInstanceEditor}
onMouseLeave={(event: MouseEvent) => {
const { relatedTarget } = event;
if (!isDragging.current && relatedTarget) {
if (
// Flow says className is not present in ElementTarget but this piece
// of code cannot break.
// $FlowFixMe
relatedTarget.className &&
typeof relatedTarget.className === 'string' &&
// Hide only if the mouse is not leaving to go on one of the scrollbars' thumb.
// $FlowFixMe
!relatedTarget.className.includes(
'canvas-scrollbar-thumb'
)
) {
hideScrollbarsAfterDelay();
}
}
}}
showObjectInstancesIn3D={values.use3DEditor}
{...otherProps}
/>
</ErrorBoundary>
)}
{screenType !== 'touch' && (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ const InstancePropertiesEditor = ({

return (
<ErrorBoundary
title="An error occurred when displaying instance properties"
scope="instance-properties-editor"
componentTitle={<Trans>Instance properties</Trans>}
scope="scene-editor-instance-properties"
>
<ScrollView
autoHideScrollbar
Expand Down
53 changes: 40 additions & 13 deletions newIDE/app/src/InstancesEditor/InstancesList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import RemoveCircle from '../../UI/CustomSvgIcons/RemoveCircle';
import Lock from '../../UI/CustomSvgIcons/Lock';
import LockOpen from '../../UI/CustomSvgIcons/LockOpen';
import { toFixedWithoutTrailingZeros } from '../../Utils/Mathematics';
import ErrorBoundary from '../../UI/ErrorBoundary';
import useForceUpdate from '../../Utils/UseForceUpdate';
const gd = global.gd;

const minimumWidths = {
Expand All @@ -25,18 +27,6 @@ const minimumWidths = {
layerName: 50,
};

type State = {|
searchText: string,
sortBy: string,
sortDirection: SortDirection,
|};

type Props = {|
instances: gdInitialInstancesContainer,
selectedInstances: Array<gdInitialInstance>,
onSelectInstances: (Array<gdInitialInstance>, boolean) => void,
|};

type RenderedRowInfo = {
instance: gdInitialInstance,
name: string,
Expand Down Expand Up @@ -72,7 +62,23 @@ const compareStrings = (x: string, y: string, direction: number): number => {
return 0;
};

export default class InstancesList extends Component<Props, State> {
export type InstancesListInterface = {|
forceUpdate: () => void,
|};

type State = {|
searchText: string,
sortBy: string,
sortDirection: SortDirection,
|};

type Props = {|
instances: gdInitialInstancesContainer,
selectedInstances: Array<gdInitialInstance>,
onSelectInstances: (Array<gdInitialInstance>, boolean) => void,
|};

class InstancesList extends Component<Props, State> {
state = {
searchText: '',
sortBy: '',
Expand Down Expand Up @@ -344,3 +350,24 @@ export default class InstancesList extends Component<Props, State> {
);
}
}

const InstancesListWithErrorBoundary = React.forwardRef<
Props,
InstancesListInterface
>((props, ref) => {
const forceUpdate = useForceUpdate();
React.useImperativeHandle(ref, () => ({
forceUpdate,
}));

return (
<ErrorBoundary
componentTitle={<Trans>Instances list</Trans>}
scope="scene-editor-instances-list"
>
<InstancesList {...props} />
</ErrorBoundary>
);
});

export default InstancesListWithErrorBoundary;
Loading

0 comments on commit 7229406

Please sign in to comment.