refactor using new ts features

pull/1652/head
gabrielburnworth 2020-01-03 12:04:45 -08:00
parent a32d9f025b
commit a38e7b6b91
52 changed files with 77 additions and 107 deletions

View File

@ -7,11 +7,11 @@ export function clickButton(
position: number, position: number,
text: string, text: string,
options?: { partial_match?: boolean, button_tag?: string }) { options?: { partial_match?: boolean, button_tag?: string }) {
const btnTag = options && options.button_tag ? options.button_tag : "button"; const btnTag = options?.button_tag ? options.button_tag : "button";
const button = wrapper.find(btnTag).at(position); const button = wrapper.find(btnTag).at(position);
const expectedText = text.toLowerCase(); const expectedText = text.toLowerCase();
const actualText = button.text().toLowerCase(); const actualText = button.text().toLowerCase();
options && options.partial_match options?.partial_match
? expect(actualText).toContain(expectedText) ? expect(actualText).toContain(expectedText)
: expect(actualText).toEqual(expectedText); : expect(actualText).toEqual(expectedText);
button.simulate("click"); button.simulate("click");

View File

@ -32,7 +32,7 @@ test("buildResourceIndex - add a FarmEvent", () => {
const key = Object.keys(db.index.byKind.FarmEvent)[0]; const key = Object.keys(db.index.byKind.FarmEvent)[0];
const fe = db.index.references[key]; const fe = db.index.references[key];
expect(fe).toBeTruthy(); expect(fe).toBeTruthy();
if (fe && fe.kind === "FarmEvent") { if (fe?.kind === "FarmEvent") {
const { body } = fe; const { body } = fe;
expect(body).toEqual(STUB_RESOURCE.body); expect(body).toEqual(STUB_RESOURCE.body);
} else { } else {

View File

@ -1,12 +1,11 @@
import * as React from "react"; import * as React from "react";
import { AxisInputBoxProps } from "./interfaces"; import { AxisInputBoxProps } from "./interfaces";
import { Col, BlurableInput } from "../ui/index"; import { Col, BlurableInput } from "../ui/index";
import { isUndefined } from "lodash";
export const AxisInputBox = ({ onChange, value, axis }: AxisInputBoxProps) => { export const AxisInputBox = ({ onChange, value, axis }: AxisInputBoxProps) => {
return <Col xs={3}> return <Col xs={3}>
<BlurableInput <BlurableInput
value={(isUndefined(value) ? "" : value)} value={value ?? ""}
type="number" type="number"
allowEmpty={true} allowEmpty={true}
onCommit={e => { onCommit={e => {

View File

@ -36,8 +36,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col> </Col>
<Col xs={2} className={"centered-button-div"}> <Col xs={2} className={"centered-button-div"}>
<ToggleButton <ToggleButton
grayscale={grayscale && grayscale.x} grayscale={grayscale?.x}
disabled={disable && disable.x} disabled={disable?.x}
dim={!xParam.consistent} dim={!xParam.consistent}
toggleValue={xParam.value} toggleValue={xParam.value}
toggleAction={() => toggleAction={() =>
@ -45,8 +45,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col> </Col>
<Col xs={2} className={"centered-button-div"}> <Col xs={2} className={"centered-button-div"}>
<ToggleButton <ToggleButton
grayscale={grayscale && grayscale.y} grayscale={grayscale?.y}
disabled={disable && disable.y} disabled={disable?.y}
dim={!yParam.consistent} dim={!yParam.consistent}
toggleValue={yParam.value} toggleValue={yParam.value}
toggleAction={() => toggleAction={() =>
@ -54,8 +54,8 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
</Col> </Col>
<Col xs={2} className={"centered-button-div"}> <Col xs={2} className={"centered-button-div"}>
<ToggleButton <ToggleButton
grayscale={grayscale && grayscale.z} grayscale={grayscale?.z}
disabled={disable && disable.z} disabled={disable?.z}
dim={!zParam.consistent} dim={!zParam.consistent}
toggleValue={zParam.value} toggleValue={zParam.value}
toggleAction={() => toggleAction={() =>

View File

@ -115,7 +115,7 @@ describe("<PinBindingInputGroup/>", () => {
const p = fakeProps(); const p = fakeProps();
const key = Object.keys(p.resources.byKind.Sequence)[0]; const key = Object.keys(p.resources.byKind.Sequence)[0];
const s = p.resources.references[key]; const s = p.resources.references[key];
const id = s && s.body.id; const id = s?.body.id;
const wrapper = mount<PinBindingInputGroup>(<PinBindingInputGroup {...p} />); const wrapper = mount<PinBindingInputGroup>(<PinBindingInputGroup {...p} />);
expect(wrapper.instance().state.sequenceIdInput).toEqual(undefined); expect(wrapper.instance().state.sequenceIdInput).toEqual(undefined);
wrapper.instance().setSequenceIdInput({ label: "label", value: "" + id }); wrapper.instance().setSequenceIdInput({ label: "label", value: "" + id });

View File

@ -79,8 +79,8 @@ export const mcuParamValidator =
(ok: () => void, no?: (message: string) => void): void => { (ok: () => void, no?: (message: string) => void): void => {
const validator = edgeCases[key]; const validator = edgeCases[key];
const result = validator && validator(key, val, state); const result = validator && validator(key, val, state);
if (result && result.outcome === "NO") { if (result?.outcome === "NO") {
return (no && no(result.errorMessage)); return (no?.(result.errorMessage));
} else { } else {
return ok(); return ok();
} }

View File

@ -26,7 +26,7 @@ describe("draggableReducer", () => {
const dt = nextState.dataTransfer; const dt = nextState.dataTransfer;
expect(Object.keys(dt)).toContain(payload.uuid); expect(Object.keys(dt)).toContain(payload.uuid);
const entry = dt[payload.uuid]; const entry = dt[payload.uuid];
expect(entry && entry.uuid).toEqual(payload.uuid); expect(entry?.uuid).toEqual(payload.uuid);
}); });
it("drops a step", () => { it("drops a step", () => {

View File

@ -56,7 +56,7 @@ export const DesignerPanelHeader = (props: DesignerPanelHeaderProps) => {
title={t("go back") + backToText(props.backTo)} title={t("go back") + backToText(props.backTo)}
onClick={() => { onClick={() => {
props.backTo ? routeHistory.push(props.backTo) : history.back(); props.backTo ? routeHistory.push(props.backTo) : history.back();
props.onBack && props.onBack(); props.onBack?.();
}} /> }} />
{props.title && {props.title &&
<span className={`title ${textColor}-text`}> <span className={`title ${textColor}-text`}>

View File

@ -16,10 +16,10 @@ export function occurrence(
CalendarOccurrence { CalendarOccurrence {
const normalHeading = fe.executable.name || fe.executable_type; const normalHeading = fe.executable.name || fe.executable_type;
const heading = () => { const heading = () => {
if (modifiers && modifiers.empty) { if (modifiers?.empty) {
return "*Empty*"; return "*Empty*";
} }
if (modifiers && modifiers.numHidden) { if (modifiers?.numHidden) {
return `+ ${modifiers.numHidden} more: ` + normalHeading; return `+ ${modifiers.numHidden} more: ` + normalHeading;
} }
return normalHeading; return normalHeading;

View File

@ -36,7 +36,7 @@ const addOrRemoveFromGroup =
const group = fetchGroupFromUrl(resources); const group = fetchGroupFromUrl(resources);
const point = const point =
resources.references[clickedPlantUuid] as TaggedPoint | undefined; resources.references[clickedPlantUuid] as TaggedPoint | undefined;
if (group && point && point.body.id) { if (group && point?.body.id) {
type Body = (typeof group)["body"]; type Body = (typeof group)["body"];
const nextGroup: Body = ({ const nextGroup: Body = ({
...group.body, ...group.body,
@ -54,7 +54,7 @@ const addOrRemoveFromSelection =
(clickedPlantUuid: UUID, selectedPlants: UUID[] | undefined) => { (clickedPlantUuid: UUID, selectedPlants: UUID[] | undefined) => {
const nextSelected = const nextSelected =
(selectedPlants || []).filter(uuid => uuid !== clickedPlantUuid); (selectedPlants || []).filter(uuid => uuid !== clickedPlantUuid);
if (!(selectedPlants && selectedPlants.includes(clickedPlantUuid))) { if (!(selectedPlants?.includes(clickedPlantUuid))) {
nextSelected.push(clickedPlantUuid); nextSelected.push(clickedPlantUuid);
} }
return selectPlant(nextSelected); return selectPlant(nextSelected);

View File

@ -21,7 +21,7 @@ function getNewTrailArray(update: TrailRecord, watering: boolean): TrailRecord[]
const arr: TrailRecord[] = JSON.parse(get(sessionStorage, key, "[]")); const arr: TrailRecord[] = JSON.parse(get(sessionStorage, key, "[]"));
if (arr.length > (trailLength - 1)) { arr.shift(); } // max length reached if (arr.length > (trailLength - 1)) { arr.shift(); } // max length reached
const last = arr[arr.length - 1]; // most recent item in array const last = arr[arr.length - 1]; // most recent item in array
if (update && update.coord && if (update?.coord &&
(!last || !isEqual(last.coord, update.coord))) { // coordinate comparison (!last || !isEqual(last.coord, update.coord))) { // coordinate comparison
arr.push(update); // unique addition arr.push(update); // unique addition
} else { // nothing new to add, increase water circle size if watering } else { // nothing new to add, increase water circle size if watering

View File

@ -7,7 +7,7 @@ import { TaggedPlant } from "../map/interfaces";
import { DesignerPanel, DesignerPanelHeader } from "../designer_panel"; import { DesignerPanel, DesignerPanelHeader } from "../designer_panel";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
import { EditPlantInfoProps, PlantOptions } from "../interfaces"; import { EditPlantInfoProps, PlantOptions } from "../interfaces";
import { isString, isUndefined } from "lodash"; import { isString } from "lodash";
import { history, getPathArray } from "../../history"; import { history, getPathArray } from "../../history";
import { destroy, edit, save } from "../../api/crud"; import { destroy, edit, save } from "../../api/crud";
import { BooleanSetting } from "../../session_keys"; import { BooleanSetting } from "../../session_keys";
@ -20,7 +20,7 @@ export class RawPlantInfo extends React.Component<EditPlantInfoProps, {}> {
get confirmDelete() { get confirmDelete() {
const confirmSetting = this.props.getConfigValue( const confirmSetting = this.props.getConfigValue(
BooleanSetting.confirm_plant_deletion); BooleanSetting.confirm_plant_deletion);
return isUndefined(confirmSetting) ? true : confirmSetting; return confirmSetting ?? true;
} }
destroy = (plantUUID: string) => { destroy = (plantUUID: string) => {

View File

@ -51,7 +51,7 @@ describe("", () => {
const sort = (sortType: PointGroupSortType): string[] => { const sort = (sortType: PointGroupSortType): string[] => {
const array = SORT_OPTIONS[sortType](plants as TaggedPlant[]); const array = SORT_OPTIONS[sortType](plants as TaggedPlant[]);
return array.map(x => x && x.body && (x.body.name || "NA")); return array.map(x => x?.body?.name || "NA");
}; };
it("sorts randomly", () => { it("sorts randomly", () => {

View File

@ -66,7 +66,7 @@ export const mapStateToProps = (props: Everything): EditGardenProps => {
const savedGarden = findSavedGardenByUrl(props.resources.index); const savedGarden = findSavedGardenByUrl(props.resources.index);
return { return {
savedGarden, savedGarden,
gardenIsOpen: !!(savedGarden && savedGarden.uuid === openedSavedGarden), gardenIsOpen: !!(savedGarden?.uuid === openedSavedGarden),
dispatch: props.dispatch, dispatch: props.dispatch,
plantPointerCount: selectAllPlantPointers(props.resources.index).length, plantPointerCount: selectAllPlantPointers(props.resources.index).length,
}; };

View File

@ -71,7 +71,7 @@ const Setting = (props: SettingProps) => {
toggleValue={props.invert ? !value : value} toggleValue={props.invert ? !value : value}
toggleAction={() => { toggleAction={() => {
props.dispatch(setWebAppConfigValue(setting, !value)); props.dispatch(setWebAppConfigValue(setting, !value));
callback && callback(); callback?.();
}} }}
title={`${t("toggle")} ${title}`} title={`${t("toggle")} ${title}`}
customText={{ textFalse: t("off"), textTrue: t("on") }} />} customText={{ textFalse: t("off"), textTrue: t("on") }} />}

View File

@ -100,7 +100,7 @@ export class RawAddToolSlot
: "initializing"} : "initializing"}
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} /> <SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
</DesignerPanelContent> </DesignerPanelContent>
</DesignerPanel >; </DesignerPanel>;
} }
} }

View File

@ -24,7 +24,7 @@ export function FarmwareConfigMenu(props: FarmwareConfigMenuProps) {
className="fb-button gray fa fa-download" className="fb-button gray fa fa-download"
onClick={() => { onClick={() => {
const p = getDevice().installFirstPartyFarmware(); const p = getDevice().installFirstPartyFarmware();
p && p.catch(commandErr("Farmware installation")); p?.catch(commandErr("Farmware installation"));
}} }}
disabled={props.firstPartyFwsInstalled} /> disabled={props.firstPartyFwsInstalled} />
</fieldset> </fieldset>

View File

@ -108,6 +108,6 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element {
/** Determine if a Farmware has requested inputs. */ /** Determine if a Farmware has requested inputs. */
export function needsFarmwareForm(farmware: FarmwareManifestInfo): Boolean { export function needsFarmwareForm(farmware: FarmwareManifestInfo): Boolean {
const needsWidget = farmware.config && farmware.config.length > 0; const needsWidget = farmware.config?.length > 0;
return needsWidget; return needsWidget;
} }

View File

@ -57,7 +57,7 @@ const PendingInstallNameError =
}): JSX.Element => { }): JSX.Element => {
const installation: TaggedFarmwareInstallation | undefined = const installation: TaggedFarmwareInstallation | undefined =
installations.filter(x => x.body.url === url)[0]; installations.filter(x => x.body.url === url)[0];
const packageError = installation && installation.body.package_error; const packageError = installation?.body.package_error;
return (url && installation && packageError) return (url && installation && packageError)
? <div className="error-with-button"> ? <div className="error-with-button">
<label>{t("Could not fetch package name")}</label> <label>{t("Could not fetch package name")}</label>

View File

@ -116,7 +116,7 @@ export class Photos extends React.Component<PhotosProps, {}> {
deletePhoto = () => { deletePhoto = () => {
const img = this.props.currentImage || this.props.images[0]; const img = this.props.currentImage || this.props.images[0];
if (img && img.uuid) { if (img?.uuid) {
this.props.dispatch(destroy(img.uuid)) this.props.dispatch(destroy(img.uuid))
.then(() => success(t("Image Deleted."))) .then(() => success(t("Image Deleted.")))
.catch(() => error(t("Could not delete image."))); .catch(() => error(t("Could not delete image.")));

View File

@ -61,7 +61,7 @@ export class ImageWorkspace extends React.Component<ImageWorkspaceProps, {}> {
maybeProcessPhoto = () => { maybeProcessPhoto = () => {
const img = this.props.currentImage || this.props.images[0]; const img = this.props.currentImage || this.props.images[0];
if (img && img.body.id) { if (img?.body.id) {
this.props.onProcessPhoto(img.body.id); this.props.onProcessPhoto(img.body.id);
} }
}; };

View File

@ -80,7 +80,7 @@ export const FolderListItem = (props: FolderItemProps) => {
onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} /> onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} />
</div> </div>
</li> </li>
</StepDragger >; </StepDragger>;
}; };
const ToggleFolderBtn = (props: ToggleFolderBtnProps) => { const ToggleFolderBtn = (props: ToggleFolderBtnProps) => {

View File

@ -1,5 +1,4 @@
import { TaggedResource, TaggedSequence } from "farmbot"; import { TaggedResource, TaggedSequence } from "farmbot";
import { RootFolderNode, FolderUnion } from "./constants"; import { RootFolderNode, FolderUnion } from "./constants";
export interface FolderSearchProps { export interface FolderSearchProps {

View File

@ -18,7 +18,7 @@ export const clickHandler =
/** BEGIN LEGACY SHIMS */ /** BEGIN LEGACY SHIMS */
const { onClick, to } = props; const { onClick, to } = props;
navigate(maybeStripLegacyUrl(to)); navigate(maybeStripLegacyUrl(to));
onClick && onClick(e); onClick?.(e);
}; };
export class Link extends React.Component<LinkProps, {}> { export class Link extends React.Component<LinkProps, {}> {

View File

@ -180,7 +180,7 @@ const FirmwareChoiceTable = () =>
export const changeFirmwareHardware = (dispatch: Function | undefined) => export const changeFirmwareHardware = (dispatch: Function | undefined) =>
(ddi: DropDownItem) => { (ddi: DropDownItem) => {
if (isFwHardwareValue(ddi.value)) { if (isFwHardwareValue(ddi.value)) {
dispatch && dispatch(updateConfig({ firmware_hardware: ddi.value })); dispatch?.(updateConfig({ firmware_hardware: ddi.value }));
} }
}; };

View File

@ -11,7 +11,7 @@ export const computeEditorUrlFromState =
: resources.consumers.regimens.currentRegimen; : resources.consumers.regimens.currentRegimen;
const r = resources.index.references[current || ""]; const r = resources.index.references[current || ""];
const base = `/app/${resource === "Sequence" ? "sequences" : "regimens"}/`; const base = `/app/${resource === "Sequence" ? "sequences" : "regimens"}/`;
if (r && r.kind == resource) { if (r?.kind == resource) {
return base + urlFriendly(r.body.name); return base + urlFriendly(r.body.name);
} else { } else {
return base; return base;

View File

@ -59,7 +59,7 @@ export class NavBar extends React.Component<NavBarProps, Partial<NavBarState>> {
BooleanSetting.disable_emergency_unlock_confirmation)} /> BooleanSetting.disable_emergency_unlock_confirmation)} />
AccountMenu = () => { AccountMenu = () => {
const hasName = this.props.user && this.props.user.body.name; const hasName = this.props.user?.body.name;
const firstName = hasName ? const firstName = hasName ?
`${hasName.split(" ")[0].slice(0, 9)}` : `${t("Menu")}`; `${hasName.split(" ")[0].slice(0, 9)}` : `${t("Menu")}`;
return <div className="menu-popover"> return <div className="menu-popover">

View File

@ -43,10 +43,7 @@ const promiseCache: Dictionary<Promise<Readonly<OFCropAttrs>>> = {};
const cacheTheIcon = (slug: string) => const cacheTheIcon = (slug: string) =>
(resp: AxiosResponse<OFCropResponse>): OFIcon => { (resp: AxiosResponse<OFCropResponse>): OFIcon => {
if (resp if (resp?.data?.data?.attributes) {
&& resp.data
&& resp.data.data
&& resp.data.data.attributes) {
const icon = { const icon = {
slug: resp.data.data.attributes.slug, slug: resp.data.data.attributes.slug,
spread: resp.data.data.attributes.spread, spread: resp.data.data.attributes.spread,

View File

@ -35,7 +35,7 @@ export function getMiddleware(env: EnvName) {
.map((mwc) => mwc.fn); .map((mwc) => mwc.fn);
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
const wow = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; const wow = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
const dtCompose = wow && wow({ const dtCompose = wow?.({
actionsBlacklist: [ actionsBlacklist: [
Actions.NETWORK_EDGE_CHANGE, Actions.NETWORK_EDGE_CHANGE,
Actions.PING_NO, Actions.PING_NO,

View File

@ -12,10 +12,8 @@ const WEB_APP_CONFIG: ResourceName = "WebAppConfig";
* resources, downloading the filtered log list as required from the API. */ * resources, downloading the filtered log list as required from the API. */
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
export const fn: Middleware = () => (dispatch) => (action: any) => { export const fn: Middleware = () => (dispatch) => (action: any) => {
const needsRefresh = action const needsRefresh = action?.payload?.kind === WEB_APP_CONFIG
&& action.payload && action.type === Actions.SAVE_RESOURCE_OK;
&& action.type === Actions.SAVE_RESOURCE_OK
&& action.payload.kind === WEB_APP_CONFIG;
needsRefresh && throttledLogRefresh(dispatch); needsRefresh && throttledLogRefresh(dispatch);

View File

@ -23,16 +23,11 @@ const WEB_APP_CONFIG: ResourceName = "WebAppConfig";
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
const fn: Middleware = () => (dispatch) => (action: any) => { const fn: Middleware = () => (dispatch) => (action: any) => {
const x: DeepPartial<SyncResponse<TaggedWebAppConfig>> = action; const x: DeepPartial<SyncResponse<TaggedWebAppConfig>> = action;
if (x if (x?.type === Actions.RESOURCE_READY
&& x.type === Actions.RESOURCE_READY && x.payload?.body
&& x.payload
&& x.payload.body
&& x.payload.kind === WEB_APP_CONFIG) { && x.payload.kind === WEB_APP_CONFIG) {
const conf = arrayUnwrap(x.payload.body); const conf = arrayUnwrap(x.payload.body);
conf conf?.body?.disable_i18n && revertToEnglish();
&& conf.body
&& conf.body.disable_i18n
&& revertToEnglish();
} }
return dispatch(action); return dispatch(action);

View File

@ -12,10 +12,9 @@ export function dontStopThem() { }
const shouldStop = const shouldStop =
(allResources: TaggedResource[], config: TaggedWebAppConfig | undefined) => { (allResources: TaggedResource[], config: TaggedWebAppConfig | undefined) => {
const loggedIn = !!localStorage.getItem("session"); const loggedIn = !!localStorage.getItem("session");
const discardUnsaved = config && config.body.discard_unsaved; const discardUnsaved = config?.body.discard_unsaved;
const sequenceResources = allResources.filter(r => r.kind === "Sequence"); const sequenceResources = allResources.filter(r => r.kind === "Sequence");
const discardUnsavedSequences = const discardUnsavedSequences = config?.body.discard_unsaved_sequences;
config && config.body.discard_unsaved_sequences;
/** /**
* For the unsaved notification to show, a user must: * For the unsaved notification to show, a user must:
@ -59,6 +58,6 @@ export function registerSubscribers(store: Store) {
subscriptions.forEach(function (s) { subscriptions.forEach(function (s) {
ENV_LIST.includes && ENV_LIST.includes &&
ENV_LIST.includes(s.env) && ENV_LIST.includes(s.env) &&
store.subscribe(() => s.fn && s.fn(store.getState())); store.subscribe(() => s.fn?.(store.getState()));
}); });
} }

View File

@ -23,7 +23,7 @@ const fn: MW =
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
(action: any) => { (action: any) => {
const fbos = getVersionFromState(store.getState()); const fbos = getVersionFromState(store.getState());
window.Rollbar && window.Rollbar.configure({ payload: { fbos } }); window.Rollbar?.configure({ payload: { fbos } });
return dispatch(action); return dispatch(action);
}; };

View File

@ -17,7 +17,7 @@ const BAD_UUID = "WARNING: Not a sequence UUID.";
export class BulkScheduler extends React.Component<BulkEditorProps, {}> { export class BulkScheduler extends React.Component<BulkEditorProps, {}> {
selected = (): DropDownItem => { selected = (): DropDownItem => {
const s = this.props.selectedSequence; const s = this.props.selectedSequence;
return (s && s.body.id) return (s?.body.id)
? { label: s.body.name, value: s.uuid } ? { label: s.body.name, value: s.uuid }
: NULL_CHOICE; : NULL_CHOICE;
}; };
@ -66,7 +66,7 @@ export class BulkScheduler extends React.Component<BulkEditorProps, {}> {
render() { render() {
const { dispatch, weeks, sequences } = this.props; const { dispatch, weeks, sequences } = this.props;
const active = !!(sequences && sequences.length); const active = !!(sequences?.length);
return <div className="bulk-scheduler-content"> return <div className="bulk-scheduler-content">
<AddButton <AddButton
active={active} active={active}

View File

@ -66,7 +66,7 @@ describe("<RegimenEditor />", () => {
p.dispatch = jest.fn(() => Promise.resolve()); p.dispatch = jest.fn(() => Promise.resolve());
const wrapper = mount(<RegimenEditor {...p} />); const wrapper = mount(<RegimenEditor {...p} />);
clickButton(wrapper, 2, "delete"); clickButton(wrapper, 2, "delete");
const expectedUuid = p.current && p.current.uuid; const expectedUuid = p.current?.uuid;
expect(destroy).toHaveBeenCalledWith(expectedUuid); expect(destroy).toHaveBeenCalledWith(expectedUuid);
}); });
@ -74,7 +74,7 @@ describe("<RegimenEditor />", () => {
const p = fakeProps(); const p = fakeProps();
const wrapper = mount(<RegimenEditor {...p} />); const wrapper = mount(<RegimenEditor {...p} />);
clickButton(wrapper, 0, "save", { partial_match: true }); clickButton(wrapper, 0, "save", { partial_match: true });
const expectedUuid = p.current && p.current.uuid; const expectedUuid = p.current?.uuid;
expect(save).toHaveBeenCalledWith(expectedUuid); expect(save).toHaveBeenCalledWith(expectedUuid);
}); });
}); });

View File

@ -177,7 +177,7 @@ describe("maybeGetSequence", () => {
const i = buildResourceIndex([s]); const i = buildResourceIndex([s]);
const result = Selector.maybeGetSequence(i.index, s.uuid); const result = Selector.maybeGetSequence(i.index, s.uuid);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
result && expect(result.uuid).toBe(s.uuid); expect(result?.uuid).toBe(s.uuid);
}); });
}); });

View File

@ -188,7 +188,7 @@ const reindexAllSequences = (i: ResourceIndex) => {
const mapper = reindexSequences(i); const mapper = reindexSequences(i);
betterCompact(Object.keys(i.byKind["Sequence"]).map(uuid => { betterCompact(Object.keys(i.byKind["Sequence"]).map(uuid => {
const resource = i.references[uuid]; const resource = i.references[uuid];
return (resource && resource.kind == "Sequence") ? resource : undefined; return (resource?.kind == "Sequence") ? resource : undefined;
})).map(mapper); })).map(mapper);
}; };
@ -283,7 +283,7 @@ const AFTER_HOOKS: IndexerHook = {
FbosConfig: (i) => { FbosConfig: (i) => {
const conf = getFbosConfig(i); const conf = getFbosConfig(i);
if (conf && conf.body.boot_sequence_id) { if (conf?.body.boot_sequence_id) {
const { boot_sequence_id } = conf.body; const { boot_sequence_id } = conf.body;
const tracker = i.inUse["Sequence.FbosConfig"]; const tracker = i.inUse["Sequence.FbosConfig"];
const uuid = i.byKindAndId[joinKindAndId("Sequence", boot_sequence_id)]; const uuid = i.byKindAndId[joinKindAndId("Sequence", boot_sequence_id)];
@ -345,7 +345,7 @@ export const indexUpsert: IndexUpsert = (db, resources, strategy) => {
const { kind } = arrayUnwrap(resources); const { kind } = arrayUnwrap(resources);
// Clean up indexes (if needed) // Clean up indexes (if needed)
const before = BEFORE_HOOKS[kind]; const before = BEFORE_HOOKS[kind];
before && before(db, strategy); before?.(db, strategy);
// Run indexers // Run indexers
ups.map(callback => { ups.map(callback => {
@ -354,14 +354,14 @@ export const indexUpsert: IndexUpsert = (db, resources, strategy) => {
// Finalize indexing (if needed) // Finalize indexing (if needed)
const after = AFTER_HOOKS[kind]; const after = AFTER_HOOKS[kind];
after && after(db, strategy); after?.(db, strategy);
}; };
export function indexRemove(db: ResourceIndex, resource: TaggedResource) { export function indexRemove(db: ResourceIndex, resource: TaggedResource) {
downs.map(callback => arrayWrap(resource).map(r => callback(r, db))); downs.map(callback => arrayWrap(resource).map(r => callback(r, db)));
// Finalize indexing (if needed) // Finalize indexing (if needed)
const after = AFTER_HOOKS[resource.kind]; const after = AFTER_HOOKS[resource.kind];
after && after(db, "ongoing"); after?.(db, "ongoing");
} }
export const beforeEach = (state: RestResources, export const beforeEach = (state: RestResources,

View File

@ -77,7 +77,7 @@ export function findPointerByTypeAndId(index: ResourceIndex,
const uuid = "" + index.byKindAndId[pni]; const uuid = "" + index.byKindAndId[pni];
const resource = index.references[uuid]; const resource = index.references[uuid];
if (resource && resource.kind === "Point") { if (resource?.kind === "Point") {
return resource; return resource;
} else { } else {
// We might have a sequence dependency leak if this exception is ever // We might have a sequence dependency leak if this exception is ever
@ -204,7 +204,7 @@ export function maybeGetTimeSettings(index: ResourceIndex): TimeSettings {
export function maybeGetDevice(index: ResourceIndex): TaggedDevice | undefined { export function maybeGetDevice(index: ResourceIndex): TaggedDevice | undefined {
const dev = index.references[Object.keys(index.byKind.Device)[0] || "nope"]; const dev = index.references[Object.keys(index.byKind.Device)[0] || "nope"];
return (dev && dev.kind === "Device") ? return (dev?.kind === "Device") ?
dev : undefined; dev : undefined;
} }
@ -227,7 +227,7 @@ export function maybeFetchUser(index: ResourceIndex):
if (user && sanityCheck(user) && list.length > 1) { if (user && sanityCheck(user) && list.length > 1) {
throw new Error("PROBLEM: Expected 1 user. Got: " + list.length); throw new Error("PROBLEM: Expected 1 user. Got: " + list.length);
} }
if ((list.length === 1) && user && user.kind === "User") { if ((list.length === 1) && user?.kind === "User") {
return user; return user;
} else { } else {
return undefined; return undefined;

View File

@ -50,7 +50,7 @@ export const determineVector =
return ts ? ts.body : undefined; return ts ? ts.body : undefined;
case "identifier": case "identifier":
const variable = maybeFindVariable(node.args.label, resources, uuid); const variable = maybeFindVariable(node.args.label, resources, uuid);
return variable && variable.vector; return variable?.vector;
} }
return undefined; return undefined;
}; };

View File

@ -127,7 +127,7 @@ describe("<SequenceEditorMiddleActive/>", () => {
p.dispatch = dispatch; p.dispatch = dispatch;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />); const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
const props = wrapper.find("DropArea").props() as DropAreaProps; const props = wrapper.find("DropArea").props() as DropAreaProps;
props.callback && props.callback("key"); props.callback?.("key");
dispatch.mock.calls[0][0](() => dispatch.mock.calls[0][0](() =>
({ value: 1, intent: "step_splice", draggerId: 2 })); ({ value: 1, intent: "step_splice", draggerId: 2 }));
expect(splice).toHaveBeenCalledWith(expect.objectContaining({ expect(splice).toHaveBeenCalledWith(expect.objectContaining({

View File

@ -148,7 +148,7 @@ const SequenceBtnGroup = ({
onClick={() => { onClick={() => {
const confirm = getWebAppConfigValue( const confirm = getWebAppConfigValue(
BooleanSetting.confirm_sequence_deletion); BooleanSetting.confirm_sequence_deletion);
const force = isUndefined(confirm) ? false : !confirm; const force = !(confirm ?? true);
dispatch(destroy(sequence.uuid, force)) dispatch(destroy(sequence.uuid, force))
.then(() => push("/app/sequences/")); .then(() => push("/app/sequences/"));
}}> }}>

View File

@ -19,7 +19,7 @@ import {
export function mapStateToProps(props: Everything): Props { export function mapStateToProps(props: Everything): Props {
const uuid = props.resources.consumers.sequences.current; const uuid = props.resources.consumers.sequences.current;
const sequence = uuid ? findSequence(props.resources.index, uuid) : undefined; const sequence = uuid ? findSequence(props.resources.index, uuid) : undefined;
sequence && (sequence.body.body || []).map(x => getStepTag(x)); (sequence?.body.body || []).map(x => getStepTag(x));
const fwConfig = validFwConfig(getFirmwareConfig(props.resources.index)); const fwConfig = validFwConfig(getFirmwareConfig(props.resources.index));
const { mcu_params } = props.bot.hardware; const { mcu_params } = props.bot.hardware;

View File

@ -83,7 +83,7 @@ describe("<TileExecuteScript/>", () => {
it("shows special 1st-party Farmware name", () => { it("shows special 1st-party Farmware name", () => {
const p = fakeProps(); const p = fakeProps();
(p.currentStep as ExecuteScript).args.label = "plant-detection"; (p.currentStep as ExecuteScript).args.label = "plant-detection";
p.farmwareData && p.farmwareData.farmwareNames.push("plant-detection"); p.farmwareData?.farmwareNames.push("plant-detection");
const wrapper = mount(<TileExecuteScript {...p} />); const wrapper = mount(<TileExecuteScript {...p} />);
expect(wrapper.find("label").length).toEqual(1); expect(wrapper.find("label").length).toEqual(1);
expect(wrapper.text()).toContain("Weed Detector"); expect(wrapper.text()).toContain("Weed Detector");

View File

@ -54,7 +54,7 @@ export function TileExecuteScript(props: StepParams) {
/** Configs (inputs) from Farmware manifest for <FarmwareInputs />. */ /** Configs (inputs) from Farmware manifest for <FarmwareInputs />. */
const currentFarmwareConfigDefaults = (fwName: string): FarmwareConfig[] => { const currentFarmwareConfigDefaults = (fwName: string): FarmwareConfig[] => {
return farmwareData && farmwareData.farmwareConfigs[fwName] return farmwareData?.farmwareConfigs[fwName]
? farmwareData.farmwareConfigs[fwName] ? farmwareData.farmwareConfigs[fwName]
: []; : [];
}; };

View File

@ -24,8 +24,8 @@ import {
const fakeResourceIndex = buildResourceIndex(FAKE_RESOURCES).index; const fakeResourceIndex = buildResourceIndex(FAKE_RESOURCES).index;
const fakeTaggedSequence = fakeResourceIndex const fakeTaggedSequence = fakeResourceIndex
.references[Object.keys(fakeResourceIndex.byKind.Sequence)[0]] as TaggedSequence; .references[Object.keys(fakeResourceIndex.byKind.Sequence)[0]] as TaggedSequence;
const fakeId = fakeTaggedSequence && fakeTaggedSequence.body.id || 0; const fakeId = fakeTaggedSequence.body.id || 0;
const fakeName = fakeTaggedSequence && fakeTaggedSequence.body.name || ""; const fakeName = fakeTaggedSequence.body.name || "";
const expectedItem = { label: fakeName, value: fakeId }; const expectedItem = { label: fakeName, value: fakeId };
function fakeProps(): IfParams { function fakeProps(): IfParams {

View File

@ -30,7 +30,7 @@ export const newTaggedResource = <T extends TR>(kind: T["kind"],
return { return {
kind: kind as TaggedResource["kind"], kind: kind as TaggedResource["kind"],
body: body as TaggedResource["body"], body: body as TaggedResource["body"],
uuid: generateUuid(body && body.id ? body.id : undefined, kind), uuid: generateUuid(body?.id, kind),
specialStatus specialStatus
} as T; } as T;
}); });

View File

@ -11,7 +11,7 @@ export class ToolBayList extends React.Component<ToolBayListProps, {}> {
ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => { ToolSlotListItem = (slot: TaggedToolSlotPointer, index: number) => {
const { getToolByToolSlotUUID } = this.props; const { getToolByToolSlotUUID } = this.props;
const tool = getToolByToolSlotUUID(slot.uuid); const tool = getToolByToolSlotUUID(slot.uuid);
const name = (tool && tool.body.name) || t("None"); const name = (tool?.body.name) || t("None");
return <Row key={slot.uuid}> return <Row key={slot.uuid}>
<Col xs={1}><label>{index + 1}</label></Col> <Col xs={1}><label>{index + 1}</label></Col>
<Col xs={2}>{slot.body.gantry_mounted ? t("Gantry") : slot.body.x}</Col> <Col xs={2}>{slot.body.gantry_mounted ? t("Gantry") : slot.body.x}</Col>

View File

@ -6,7 +6,7 @@ interface BackArrowProps {
export function BackArrow(props: BackArrowProps) { export function BackArrow(props: BackArrowProps) {
const onClick = () => { const onClick = () => {
history.back(); history.back();
props.onClick && props.onClick(); props.onClick?.();
}; };
return <a onClick={onClick} className="back-arrow"> return <a onClick={onClick} className="back-arrow">

View File

@ -47,7 +47,7 @@ export class BlurableInput extends React.Component<BIProps, Partial<BIState>> {
withinLimits = (options?: { toasts?: boolean }): boolean => { withinLimits = (options?: { toasts?: boolean }): boolean => {
const onError = (msg: string) => { const onError = (msg: string) => {
this.setState({ error: msg }); this.setState({ error: msg });
options && options.toasts && error(msg); options?.toasts && error(msg);
}; };
if (this.props.type === "number") { if (this.props.type === "number") {

View File

@ -131,18 +131,6 @@ describe("util", () => {
}); });
}); });
describe("isUndefined()", () => {
it("undefined", () => {
const result = Util.isUndefined(undefined);
expect(result).toBeTruthy();
});
it("defined", () => {
const result = Util.isUndefined({});
expect(result).toBeFalsy();
});
});
describe("randomColor()", () => { describe("randomColor()", () => {
it("only picks valid colors", () => { it("only picks valid colors", () => {
times(Util.colors.length * 1.5, () => times(Util.colors.length * 1.5, () =>

View File

@ -28,7 +28,7 @@ export function prettyPrintApiErrors(err: AxiosErrorResponse) {
function safelyFetchErrors(err: AxiosErrorResponse): Dictionary<string> { function safelyFetchErrors(err: AxiosErrorResponse): Dictionary<string> {
// In case the interpreter gives us an oddball error message. // In case the interpreter gives us an oddball error message.
if (err && err.response && err.response.data) { if (err.response?.data) {
return err.response.data; return err.response.data;
} else { } else {
return { return {
@ -42,7 +42,7 @@ export function bail(message: string): never {
} }
export const catchErrors = (error: Error) => { export const catchErrors = (error: Error) => {
if (window.Rollbar && window.Rollbar.error) { if (window.Rollbar?.error) {
window.Rollbar.error(error); window.Rollbar.error(error);
} else { } else {
throw error; throw error;

View File

@ -14,7 +14,6 @@ import {
sortBy, sortBy,
merge, merge,
isNumber, isNumber,
isUndefined as lodashIsUndefined
} from "lodash"; } from "lodash";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";
@ -88,10 +87,6 @@ export type CowardlyDictionary<T> = Dictionary<T | undefined>;
*/ */
export const NOT_SAVED = -1; export const NOT_SAVED = -1;
export function isUndefined(x: object | undefined): x is undefined {
return lodashIsUndefined(x);
}
/** Better than Array.proto.filter and compact() because the type checker /** Better than Array.proto.filter and compact() because the type checker
* knows what's going on. * knows what's going on.
*/ */
@ -190,7 +185,7 @@ export function validBotLocationData(
*/ */
export function validFwConfig(config: TaggedFirmwareConfig | undefined): export function validFwConfig(config: TaggedFirmwareConfig | undefined):
TaggedFirmwareConfig["body"] | undefined { TaggedFirmwareConfig["body"] | undefined {
return (config && config.body.api_migrated) return (config?.body.api_migrated)
? config.body ? config.body
: undefined; : undefined;
} }
@ -200,7 +195,7 @@ export function validFwConfig(config: TaggedFirmwareConfig | undefined):
*/ */
export function validFbosConfig( export function validFbosConfig(
config: TaggedFbosConfig | undefined): TaggedFbosConfig["body"] | undefined { config: TaggedFbosConfig | undefined): TaggedFbosConfig["body"] | undefined {
return (config && config.body.api_migrated) return (config?.body.api_migrated)
? config.body ? config.body
: undefined; : undefined;
} }
@ -232,4 +227,4 @@ export const parseIntInput = (input: string): number => {
export const timeFormatString = export const timeFormatString =
(timeSettings: TimeSettings | undefined): string => (timeSettings: TimeSettings | undefined): string =>
(timeSettings && timeSettings.hour24) ? "H:mm" : "h:mma"; (timeSettings?.hour24) ? "H:mm" : "h:mma";