increase error containment
parent
5e52d3c6dd
commit
c2d80bd55c
|
@ -0,0 +1,14 @@
|
|||
jest.mock("../session", () => ({ Session: { clear: jest.fn() } }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { Apology } from "../apology";
|
||||
import { Session } from "../session";
|
||||
|
||||
describe("<Apology />", () => {
|
||||
it("clears session", () => {
|
||||
const wrapper = mount(<Apology />);
|
||||
wrapper.find("a").first().simulate("click");
|
||||
expect(Session.clear).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,20 +1,29 @@
|
|||
import * as React from "react";
|
||||
import { Session } from "./session";
|
||||
|
||||
const STYLE: React.CSSProperties = {
|
||||
border: "2px solid #434343",
|
||||
background: "#a4c2f4",
|
||||
fontSize: "18px",
|
||||
const OUTER_STYLE: React.CSSProperties = {
|
||||
borderRadius: "10px",
|
||||
background: "repeating-linear-gradient(-45deg," +
|
||||
"#ffff55, #ffff55 20px," +
|
||||
"#ff5555 20px, #ff5555 40px)",
|
||||
fontSize: "100%",
|
||||
color: "black",
|
||||
display: "block",
|
||||
overflow: "auto",
|
||||
padding: "1rem",
|
||||
margin: "1rem",
|
||||
};
|
||||
|
||||
const INNER_STYLE: React.CSSProperties = {
|
||||
borderRadius: "10px",
|
||||
background: "#ffffffdd",
|
||||
padding: "1rem",
|
||||
};
|
||||
|
||||
export function Apology(_: {}) {
|
||||
return <div style={STYLE}>
|
||||
<div>
|
||||
<h1>Page Error</h1>
|
||||
return <div style={OUTER_STYLE}>
|
||||
<div style={INNER_STYLE}>
|
||||
<h1 style={{ fontSize: "175%" }}>Page Error</h1>
|
||||
<span>
|
||||
We can't render this part of the page due to an unrecoverable error.
|
||||
Here are some things you can try:
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ColWidth } from "../farmbot_os_settings";
|
|||
import { FarmbotOsRowProps } from "./interfaces";
|
||||
import { FbosDetails } from "./fbos_details";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../../../error_boundary";
|
||||
|
||||
const getVersionString =
|
||||
(fbosVersion: string | undefined, onBeta: boolean | undefined): string => {
|
||||
|
@ -30,14 +31,16 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
|||
<p>
|
||||
{t("Version {{ version }}", { version })}
|
||||
</p>
|
||||
<FbosDetails
|
||||
botInfoSettings={bot.hardware.informational_settings}
|
||||
dispatch={dispatch}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||
timeSettings={props.timeSettings}
|
||||
deviceAccount={props.deviceAccount} />
|
||||
<ErrorBoundary>
|
||||
<FbosDetails
|
||||
botInfoSettings={bot.hardware.informational_settings}
|
||||
dispatch={dispatch}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||
timeSettings={props.timeSettings}
|
||||
deviceAccount={props.deviceAccount} />
|
||||
</ErrorBoundary>
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { catchErrors } from "./util";
|
|||
import { Apology } from "./apology";
|
||||
|
||||
interface State { hasError?: boolean; }
|
||||
interface Props { }
|
||||
interface Props { fallback?: React.ReactElement }
|
||||
|
||||
export class ErrorBoundary extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
|
@ -16,7 +16,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
|
|||
this.setState({ hasError: true });
|
||||
}
|
||||
|
||||
no = () => <Apology />;
|
||||
no = () => this.props.fallback || <Apology />;
|
||||
|
||||
ok = () => this.props.children || <div />;
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { DesignerPanel, DesignerPanelHeader } from "../../designer_panel";
|
||||
import { DesignerPanel, DesignerPanelHeader } from "../designer_panel";
|
||||
|
||||
describe("<DesignerPanel />", () => {
|
||||
it("renders default panel", () => {
|
||||
const wrapper = mount(<DesignerPanel panelName={"test-panel"} />);
|
||||
expect(wrapper.find("div").hasClass("gray-panel")).toBeTruthy();
|
||||
expect(wrapper.find("div").first().hasClass("gray-panel")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("<DesignerPanelHeader />", () => {
|
||||
it("renders default panel header", () => {
|
||||
const wrapper = mount(<DesignerPanelHeader panelName={"test-panel"} />);
|
||||
expect(wrapper.find("div").hasClass("gray-panel")).toBeTruthy();
|
||||
expect(wrapper.find("div").first().hasClass("gray-panel")).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@ import { last, trim } from "lodash";
|
|||
import { Link } from "../link";
|
||||
import { Panel, TAB_COLOR, PanelColor } from "./panel_header";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface DesignerPanelProps {
|
||||
panelName: string;
|
||||
|
@ -19,7 +20,9 @@ export const DesignerPanel = (props: DesignerPanelProps) => {
|
|||
"panel-container",
|
||||
`${color || PanelColor.gray}-panel`,
|
||||
`${props.panelName}-panel`].join(" ")}>
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -86,7 +89,9 @@ export const DesignerPanelTop = (props: DesignerPanelTopProps) => {
|
|||
<div className="text-input-wrapper">
|
||||
{!props.noIcon &&
|
||||
<i className="fa fa-search"></i>}
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
{props.linkTo &&
|
||||
|
@ -105,8 +110,11 @@ interface DesignerPanelContentProps {
|
|||
}
|
||||
|
||||
export const DesignerPanelContent = (props: DesignerPanelContentProps) =>
|
||||
<div className={
|
||||
`panel-content ${props.panelName}-panel-content ${props.className || ""}`
|
||||
}>
|
||||
{props.children}
|
||||
<div className={[
|
||||
"panel-content",
|
||||
`${props.panelName}-panel-content`,
|
||||
props.className || ""].join(" ")}>
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
|
|
|
@ -47,8 +47,7 @@ export class RawPlantInfo extends React.Component<EditPlantInfoProps, {}> {
|
|||
panel={Panel.Plants}
|
||||
title={`${t("Edit")} ${info.name}`}
|
||||
backTo={"/app/designer/plants"}
|
||||
onBack={unselectPlant(this.props.dispatch)}>
|
||||
</DesignerPanelHeader>
|
||||
onBack={unselectPlant(this.props.dispatch)} />
|
||||
<PlantPanel
|
||||
info={info}
|
||||
onDestroy={this.destroy}
|
||||
|
|
|
@ -76,8 +76,7 @@ export class GroupDetailActive
|
|||
panelName={Panel.Groups}
|
||||
panel={Panel.Groups}
|
||||
title={t("Edit Group")}
|
||||
backTo={"/app/designer/groups"}>
|
||||
</DesignerPanelHeader>
|
||||
backTo={"/app/designer/groups"} />
|
||||
<DesignerPanelContent
|
||||
panelName={"groups"}>
|
||||
<label>{t("GROUP NAME")}{this.saved ? "" : "*"}</label>
|
||||
|
|
|
@ -49,8 +49,7 @@ export class RawEditPoint extends React.Component<EditPointProps, {}> {
|
|||
backTo={this.backTo}
|
||||
onBack={() => this.props.dispatch({
|
||||
type: Actions.TOGGLE_HOVERED_POINT, payload: undefined
|
||||
})}>
|
||||
</DesignerPanelHeader>
|
||||
})} />
|
||||
<DesignerPanelContent panelName={this.panelName}>
|
||||
<EditPointProperties point={point}
|
||||
updatePoint={updatePoint(point, this.props.dispatch)} />
|
||||
|
|
|
@ -49,8 +49,7 @@ export class RawEditWeed extends React.Component<EditWeedProps, {}> {
|
|||
backTo={this.backTo}
|
||||
onBack={() => this.props.dispatch({
|
||||
type: Actions.TOGGLE_HOVERED_POINT, payload: undefined
|
||||
})}>
|
||||
</DesignerPanelHeader>
|
||||
})} />
|
||||
<DesignerPanelContent panelName={this.panelName}>
|
||||
<EditPointProperties point={point}
|
||||
updatePoint={updatePoint(point, this.props.dispatch)} />
|
||||
|
|
|
@ -37,8 +37,7 @@ export class RawEditZone extends React.Component<EditZoneProps, {}> {
|
|||
panelName={"zone-info"}
|
||||
panel={Panel.Zones}
|
||||
title={`${t("Edit")} zone`}
|
||||
backTo={"/app/designer/zones"}>
|
||||
</DesignerPanelHeader>
|
||||
backTo={"/app/designer/zones"} />
|
||||
<DesignerPanelContent panelName={"zone-info"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
|
|
|
@ -12,6 +12,7 @@ import { fakeDevice } from "../../__test_support__/resource_index_builder";
|
|||
import { maybeSetTimezone } from "../../devices/timezones/guess_timezone";
|
||||
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
|
||||
import { fakePings } from "../../__test_support__/fake_state/pings";
|
||||
import { Link } from "../../link";
|
||||
|
||||
describe("NavBar", () => {
|
||||
const fakeProps = (): NavBarProps => ({
|
||||
|
@ -35,8 +36,8 @@ describe("NavBar", () => {
|
|||
});
|
||||
|
||||
it("closes nav menu", () => {
|
||||
const wrapper = shallow<NavBar>(<NavBar {...fakeProps()} />);
|
||||
const link = wrapper.find("Link").first();
|
||||
const wrapper = mount<NavBar>(<NavBar {...fakeProps()} />);
|
||||
const link = wrapper.find(Link).first();
|
||||
link.simulate("click");
|
||||
expect(wrapper.instance().state.mobileMenuOpen).toBeFalsy();
|
||||
link.simulate("click");
|
||||
|
|
|
@ -22,7 +22,6 @@ import { BooleanSetting } from "../session_keys";
|
|||
import { ReadOnlyIcon } from "../read_only_mode";
|
||||
|
||||
export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
|
||||
|
||||
state: NavBarState = {
|
||||
mobileMenuOpen: false,
|
||||
tickerListOpen: false,
|
||||
|
@ -42,41 +41,87 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
|
|||
close = (name: keyof NavBarState) => () =>
|
||||
this.setState({ [name]: false });
|
||||
|
||||
syncButton = () => {
|
||||
return <SyncButton
|
||||
ReadOnlyStatus = () =>
|
||||
<ReadOnlyIcon locked={!!this.props.getConfigValue(
|
||||
BooleanSetting.user_interface_read_only_mode)} />
|
||||
|
||||
SyncButton = () =>
|
||||
<SyncButton
|
||||
bot={this.props.bot}
|
||||
dispatch={this.props.dispatch}
|
||||
autoSync={this.props.autoSync}
|
||||
consistent={this.props.consistent} />;
|
||||
consistent={this.props.consistent} />
|
||||
|
||||
EstopButton = () =>
|
||||
<EStopButton
|
||||
bot={this.props.bot}
|
||||
forceUnlock={!!this.props.getConfigValue(
|
||||
BooleanSetting.disable_emergency_unlock_confirmation)} />
|
||||
|
||||
AccountMenu = () => {
|
||||
const hasName = this.props.user && this.props.user.body.name;
|
||||
const firstName = hasName ?
|
||||
`${hasName.split(" ")[0].slice(0, 9)} ▾` : `${t("Menu")} ▾`;
|
||||
return <div className="menu-popover">
|
||||
<Popover
|
||||
portalClassName={"nav-right"}
|
||||
popoverClassName={"menu-popover"}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
isOpen={this.state.accountMenuOpen}
|
||||
onClose={this.close("accountMenuOpen")}>
|
||||
<div className="nav-name" data-title={firstName}
|
||||
onClick={this.toggle("accountMenuOpen")}>
|
||||
{firstName}
|
||||
</div>
|
||||
{AdditionalMenu({ logout: this.logout, close: this.close })}
|
||||
</Popover>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get connectivityData() {
|
||||
return connectivityData({
|
||||
ConnectionStatus = () => {
|
||||
const data = connectivityData({
|
||||
bot: this.props.bot,
|
||||
device: this.props.device
|
||||
});
|
||||
return <div className="connection-status-popover">
|
||||
<Popover position={Position.BOTTOM_RIGHT}
|
||||
portalClassName={"connectivity-popover-portal"}
|
||||
popoverClassName="connectivity-popover">
|
||||
<DiagnosisSaucer {...data.flags} />
|
||||
<ErrorBoundary>
|
||||
<Connectivity
|
||||
bot={this.props.bot}
|
||||
rowData={data.rowData}
|
||||
flags={data.flags}
|
||||
pings={this.props.pings} />
|
||||
</ErrorBoundary>
|
||||
</Popover>
|
||||
</div>;
|
||||
}
|
||||
|
||||
AppNavLinks = () => {
|
||||
const { close } = this;
|
||||
const { mobileMenuOpen } = this.state;
|
||||
const { alertCount } = this.props;
|
||||
return <div>
|
||||
<i className={"fa fa-bars mobile-menu-icon"}
|
||||
onClick={this.toggle("mobileMenuOpen")} />
|
||||
<span className="mobile-menu-container">
|
||||
{MobileMenu({ close, mobileMenuOpen, alertCount })}
|
||||
</span>
|
||||
<span className="top-menu-container">
|
||||
{NavLinks({ close, alertCount })}
|
||||
</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const isLocked = this.props.getConfigValue("user_interface_read_only_mode");
|
||||
const hasName = this.props.user && this.props.user.body.name;
|
||||
|
||||
const firstName = hasName ?
|
||||
`${hasName.split(" ")[0].slice(0, 9)} ▾` : `${t("Menu")} ▾`;
|
||||
|
||||
const menuIconClassNames: string[] = [
|
||||
"fa", "fa-bars", "mobile-menu-icon"
|
||||
];
|
||||
|
||||
/** The way our app is laid out, we'll pretty much always want this bit. */
|
||||
const pageName = getPathArray()[2] || "";
|
||||
|
||||
/** Change document meta title on every route change. */
|
||||
updatePageInfo(pageName);
|
||||
updatePageInfo(getPathArray()[2] || "");
|
||||
|
||||
const { toggle, close } = this;
|
||||
const { mobileMenuOpen, tickerListOpen, accountMenuOpen } = this.state;
|
||||
const { logs, timeSettings, getConfigValue, alertCount } = this.props;
|
||||
const { toggle } = this;
|
||||
const { tickerListOpen } = this.state;
|
||||
const { logs, timeSettings, getConfigValue } = this.props;
|
||||
const tickerListProps = {
|
||||
logs, tickerListOpen, toggle, timeSettings, getConfigValue
|
||||
};
|
||||
|
@ -89,51 +134,17 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
|
|||
<TickerList {...tickerListProps} />
|
||||
<div className="nav-group">
|
||||
<div className="nav-left">
|
||||
<i
|
||||
className={menuIconClassNames.join(" ")}
|
||||
onClick={this.toggle("mobileMenuOpen")} />
|
||||
<span className="mobile-menu-container">
|
||||
{MobileMenu({ close, mobileMenuOpen, alertCount })}
|
||||
</span>
|
||||
<span className="top-menu-container">
|
||||
{NavLinks({ close, alertCount })}
|
||||
</span>
|
||||
<this.AppNavLinks />
|
||||
</div>
|
||||
<div className="nav-right">
|
||||
<ReadOnlyIcon locked={!!isLocked} />
|
||||
|
||||
<div className="menu-popover">
|
||||
<Popover
|
||||
portalClassName={"nav-right"}
|
||||
popoverClassName={"menu-popover"}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
isOpen={accountMenuOpen}
|
||||
onClose={this.close("accountMenuOpen")}>
|
||||
<div className="nav-name" data-title={firstName}
|
||||
onClick={this.toggle("accountMenuOpen")}>
|
||||
{firstName}
|
||||
</div>
|
||||
{AdditionalMenu({ logout: this.logout, close })}
|
||||
</Popover>
|
||||
</div>
|
||||
<EStopButton
|
||||
bot={this.props.bot}
|
||||
forceUnlock={!!this.props.getConfigValue(
|
||||
BooleanSetting.disable_emergency_unlock_confirmation)} />
|
||||
{this.syncButton()}
|
||||
<div className="connection-status-popover">
|
||||
<Popover position={Position.BOTTOM_RIGHT}
|
||||
portalClassName={"connectivity-popover-portal"}
|
||||
popoverClassName="connectivity-popover">
|
||||
<DiagnosisSaucer {...this.connectivityData.flags} />
|
||||
<Connectivity
|
||||
bot={this.props.bot}
|
||||
rowData={this.connectivityData.rowData}
|
||||
flags={this.connectivityData.flags}
|
||||
pings={this.props.pings} />
|
||||
</Popover>
|
||||
</div>
|
||||
<RunTour currentTour={this.props.tour} />
|
||||
<ErrorBoundary>
|
||||
<this.ReadOnlyStatus />
|
||||
<this.AccountMenu />
|
||||
<this.EstopButton />
|
||||
<this.SyncButton />
|
||||
<this.ConnectionStatus />
|
||||
<RunTour currentTour={this.props.tour} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Actions } from "../../constants";
|
|||
import { reduceVariables } from "../../sequences/locals_list/variable_support";
|
||||
import { determineDropdown, withPrefix } from "../../resources/sequence_meta";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { ErrorBoundary } from "../../error_boundary";
|
||||
|
||||
/**
|
||||
* The bottom half of the regimen editor panel (when there's something to
|
||||
|
@ -57,14 +58,18 @@ export class ActiveEditor
|
|||
<div id="regimen-editor-tools" className="regimen-editor-tools">
|
||||
<RegimenButtonGroup {...this.regimenProps} />
|
||||
<RegimenNameInput {...this.regimenProps} />
|
||||
<this.LocalsList />
|
||||
<ErrorBoundary>
|
||||
<this.LocalsList />
|
||||
</ErrorBoundary>
|
||||
<hr />
|
||||
</div>
|
||||
<OpenSchedulerButton dispatch={this.props.dispatch} />
|
||||
<RegimenRows {...this.regimenProps}
|
||||
calendar={this.props.calendar}
|
||||
varsCollapsed={this.state.variablesCollapsed}
|
||||
resources={this.props.resources} />
|
||||
<ErrorBoundary>
|
||||
<RegimenRows {...this.regimenProps}
|
||||
calendar={this.props.calendar}
|
||||
varsCollapsed={this.state.variablesCollapsed}
|
||||
resources={this.props.resources} />
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import { getStepTag } from "../resources/sequence_tagging";
|
|||
import { HardwareFlags, FarmwareInfo } from "./interfaces";
|
||||
import { ShouldDisplay } from "../devices/interfaces";
|
||||
import { AddCommandButton } from "./sequence_editor_middle_active";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
import { TileUnknown } from "./step_tiles/tile_unknown";
|
||||
|
||||
export interface AllStepsProps {
|
||||
sequence: TaggedSequence;
|
||||
|
@ -34,6 +36,19 @@ export class AllSteps extends React.Component<AllStepsProps, {}> {
|
|||
* is guaranteed to be unique no matter where the step gets moved and
|
||||
* allows React to diff the list correctly. */
|
||||
const readThatCommentAbove = getStepTag(currentStep);
|
||||
const stepProps = {
|
||||
currentStep,
|
||||
index,
|
||||
dispatch,
|
||||
currentSequence: sequence,
|
||||
resources: this.props.resources,
|
||||
hardwareFlags: this.props.hardwareFlags,
|
||||
farmwareInfo: this.props.farmwareInfo,
|
||||
shouldDisplay: this.props.shouldDisplay,
|
||||
confirmStepDeletion: this.props.confirmStepDeletion,
|
||||
showPins: this.props.showPins,
|
||||
expandStepOptions: this.props.expandStepOptions,
|
||||
};
|
||||
return <div className="sequence-steps"
|
||||
key={readThatCommentAbove}>
|
||||
<AddCommandButton dispatch={dispatch} index={index} />
|
||||
|
@ -44,19 +59,9 @@ export class AllSteps extends React.Component<AllStepsProps, {}> {
|
|||
intent="step_move"
|
||||
draggerId={index}>
|
||||
<div className="sequence-step">
|
||||
{renderCeleryNode({
|
||||
currentStep,
|
||||
index,
|
||||
dispatch,
|
||||
currentSequence: sequence,
|
||||
resources: this.props.resources,
|
||||
hardwareFlags: this.props.hardwareFlags,
|
||||
farmwareInfo: this.props.farmwareInfo,
|
||||
shouldDisplay: this.props.shouldDisplay,
|
||||
confirmStepDeletion: this.props.confirmStepDeletion,
|
||||
showPins: this.props.showPins,
|
||||
expandStepOptions: this.props.expandStepOptions,
|
||||
})}
|
||||
<ErrorBoundary fallback={<TileUnknown {...stepProps} />}>
|
||||
{renderCeleryNode(stepProps)}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</StepDragger>
|
||||
</div>;
|
||||
|
|
|
@ -30,6 +30,7 @@ import { BooleanSetting } from "../session_keys";
|
|||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||
import { isUndefined } from "lodash";
|
||||
import { NO_GROUPS } from "./locals_list/default_value_form";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
export const onDrop =
|
||||
(dispatch1: Function, sequence: TaggedSequence) =>
|
||||
|
@ -201,19 +202,21 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
|
|||
getWebAppConfigValue={props.getWebAppConfigValue}
|
||||
menuOpen={props.menuOpen} />
|
||||
<SequenceNameAndColor {...sequenceAndDispatch} />
|
||||
<LocalsList
|
||||
variableData={variableData}
|
||||
sequenceUuid={sequence.uuid}
|
||||
resources={props.resources}
|
||||
onChange={localListCallback(props)(declarations)}
|
||||
locationDropdownKey={JSON.stringify(sequence)}
|
||||
allowedVariableNodes={AllowedVariableNodes.parameter}
|
||||
collapsible={true}
|
||||
collapsed={props.variablesCollapsed}
|
||||
toggleVarShow={props.toggleVarShow}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
hideGroups={true}
|
||||
customFilterRule={NO_GROUPS} />
|
||||
<ErrorBoundary>
|
||||
<LocalsList
|
||||
variableData={variableData}
|
||||
sequenceUuid={sequence.uuid}
|
||||
resources={props.resources}
|
||||
onChange={localListCallback(props)(declarations)}
|
||||
locationDropdownKey={JSON.stringify(sequence)}
|
||||
allowedVariableNodes={AllowedVariableNodes.parameter}
|
||||
collapsible={true}
|
||||
collapsed={props.variablesCollapsed}
|
||||
toggleVarShow={props.toggleVarShow}
|
||||
shouldDisplay={props.shouldDisplay}
|
||||
hideGroups={true}
|
||||
customFilterRule={NO_GROUPS} />
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -271,7 +274,9 @@ export class SequenceEditorMiddleActive extends
|
|||
<hr />
|
||||
<div className="sequence" id="sequenceDiv"
|
||||
style={{ height: this.stepSectionHeight }}>
|
||||
<AllSteps {...this.stepProps} />
|
||||
<ErrorBoundary>
|
||||
<AllSteps {...this.stepProps} />
|
||||
</ErrorBoundary>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<DropArea isLocked={true}
|
||||
|
|
|
@ -13,12 +13,12 @@ import { conflictsString } from "../step_warning";
|
|||
describe("<StepWrapper />", () => {
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<StepWrapper />);
|
||||
expect(wrapper.find("div").hasClass("step-wrapper")).toBeTruthy();
|
||||
expect(wrapper.find("div").first().hasClass("step-wrapper")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders with extra className", () => {
|
||||
const wrapper = mount(<StepWrapper className={"step-class"} />);
|
||||
expect(wrapper.find("div").hasClass("step-class")).toBeTruthy();
|
||||
expect(wrapper.find("div").first().hasClass("step-class")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -50,7 +50,7 @@ describe("<StepHeader />", () => {
|
|||
describe("<StepContent />", () => {
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<StepContent className={"step-class"} />);
|
||||
const div = wrapper.find("div").last();
|
||||
const div = wrapper.find("div").at(2);
|
||||
expect(div.hasClass("step-content")).toBeTruthy();
|
||||
expect(div.hasClass("step-class")).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../ui/index";
|
||||
import { ErrorBoundary } from "../../error_boundary";
|
||||
|
||||
interface StepContentProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -11,7 +12,9 @@ export function StepContent(props: StepContentProps) {
|
|||
return <Row>
|
||||
<Col sm={12}>
|
||||
<div className={`step-content ${className}`}>
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { ErrorBoundary } from "../../error_boundary";
|
||||
|
||||
interface StepWrapperProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -8,6 +9,8 @@ interface StepWrapperProps {
|
|||
export function StepWrapper(props: StepWrapperProps) {
|
||||
const { className } = props;
|
||||
return <div className={`step-wrapper ${className ? className : ""}`}>
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Col, ToolTip, DocSlug } from ".";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface CenterProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -20,9 +21,10 @@ export function CenterPanel(props: CenterProps) {
|
|||
<i>{t(props.title)}</i>
|
||||
</h3>
|
||||
{props.helpText &&
|
||||
<ToolTip helpText={t(props.helpText)} docPage={props.docPage} />
|
||||
}
|
||||
{props.children}
|
||||
<ToolTip helpText={t(props.helpText)} docPage={props.docPage} />}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Col>;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
export enum EmptyStateGraphic {
|
||||
plants = "plants",
|
||||
|
@ -28,7 +29,11 @@ interface EmptyStateWrapperProps {
|
|||
|
||||
export const EmptyStateWrapper = (props: EmptyStateWrapperProps) =>
|
||||
!!props.notEmpty
|
||||
? <div className="non-empty-state">{props.children}</div>
|
||||
? <div className="non-empty-state">
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
: <div className={`empty-state ${props.colorScheme || ""}`}>
|
||||
<img
|
||||
className="empty-state-graphic"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Col, ToolTip } from ".";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface LeftPanelProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -17,7 +18,9 @@ export function LeftPanel(props: LeftPanelProps) {
|
|||
<i>{t(props.title)}</i>
|
||||
</h3>
|
||||
{props.helpText && <ToolTip helpText={props.helpText} />}
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Col>;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Col, ToolTip, DocSlug } from ".";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface RightPanelProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -22,7 +23,9 @@ export function RightPanel(props: RightPanelProps) {
|
|||
<i>{t(props.title)}</i>
|
||||
</h3>
|
||||
<ToolTip helpText={props.helpText} docPage={props.docPage} />
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>}
|
||||
</Col>;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface WidgetProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -9,6 +10,8 @@ export function Widget(props: WidgetProps) {
|
|||
let className = `widget-wrapper `;
|
||||
if (props.className) { className += props.className; }
|
||||
return <div className={className}>
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface WidgetBodyProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -6,6 +7,8 @@ interface WidgetBodyProps {
|
|||
|
||||
export function WidgetBody(props: WidgetBodyProps) {
|
||||
return <div className="widget-body">
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from "react";
|
|||
import { DocSlug } from "./doc_link";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { ToolTip } from "./tooltip";
|
||||
import { ErrorBoundary } from "../error_boundary";
|
||||
|
||||
interface WidgetHeaderProps {
|
||||
children?: React.ReactNode;
|
||||
|
@ -12,7 +13,9 @@ interface WidgetHeaderProps {
|
|||
|
||||
export function WidgetHeader(props: WidgetHeaderProps) {
|
||||
return <div className="widget-header">
|
||||
{props.children}
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
<h5>{t(props.title)}</h5>
|
||||
{props.helpText &&
|
||||
<ToolTip helpText={props.helpText} docPage={props.docPage} />}
|
||||
|
|
Loading…
Reference in New Issue