Part I of syntheticimport issues :-\

pull/1103/head
Rick Carlino 2019-02-04 08:32:26 -06:00
parent cf6cf0e2b8
commit 86fe6fba78
72 changed files with 244 additions and 252 deletions

View File

@ -7,11 +7,11 @@ import {
TaggedSequence, TaggedSequence,
TaggedRegimen, TaggedRegimen,
} from "farmbot"; } from "farmbot";
import * as _ from "lodash";
import { resourceReducer, emptyState } from "../resources/reducer"; import { resourceReducer, emptyState } from "../resources/reducer";
import { resourceReady } from "../sync/actions"; import { resourceReady } from "../sync/actions";
import { threeWayComparison as c3 } from "../util/move"; import { threeWayComparison as c3 } from "../util/move";
import { defensiveClone } from "../util/util"; import { defensiveClone } from "../util/util";
import { chain } from "lodash";
export function fakeDevice(): TaggedDevice { export function fakeDevice(): TaggedDevice {
return { return {
"kind": "Device", "kind": "Device",
@ -374,7 +374,7 @@ export function buildResourceIndex(resources: TaggedResource[] = FAKE_RESOURCES,
const sortedResources = repairBrokeReferences(resources) const sortedResources = repairBrokeReferences(resources)
.sort((l, r) => c3(KIND_PRIORITY[l.kind], KIND_PRIORITY[r.kind])); .sort((l, r) => c3(KIND_PRIORITY[l.kind], KIND_PRIORITY[r.kind]));
type K = keyof typeof KIND_PRIORITY; type K = keyof typeof KIND_PRIORITY;
return _.chain(sortedResources) return chain(sortedResources)
.groupBy(KIND) .groupBy(KIND)
.toPairs() .toPairs()
.sort((l, r) => c3(KIND_PRIORITY[l[0] as K || 4], KIND_PRIORITY[r[0] as K || 4])) .sort((l, r) => c3(KIND_PRIORITY[l[0] as K || 4], KIND_PRIORITY[r[0] as K || 4]))
@ -420,7 +420,7 @@ const blankReg: TaggedRegimen = {
* number of failed tests. To circumvent this, we "repair" faulty foreign keys * number of failed tests. To circumvent this, we "repair" faulty foreign keys
* in TaggedResources. This applies to many legacy tests. - RC*/ * in TaggedResources. This applies to many legacy tests. - RC*/
function repairBrokeReferences(resources: TaggedResource[]): TaggedResource[] { function repairBrokeReferences(resources: TaggedResource[]): TaggedResource[] {
const table = _.chain(resources).groupBy(x => x.kind).value(); const table = chain(resources).groupBy(x => x.kind).value();
resources.map(resource => { resources.map(resource => {
if (resource.kind === "FarmEvent") { // Find FarmEvents if (resource.kind === "FarmEvent") { // Find FarmEvents
const { executable_type, executable_id } = resource.body; const { executable_type, executable_id } = resource.body;

View File

@ -23,7 +23,7 @@ import { maybeStartTracking } from "../maybe_start_tracking";
import { API } from "../api"; import { API } from "../api";
import { betterCompact } from "../../util"; import { betterCompact } from "../../util";
import { SpecialStatus, TaggedUser } from "farmbot"; import { SpecialStatus, TaggedUser } from "farmbot";
import * as _ from "lodash"; import { uniq } from "lodash";
describe("AJAX data tracking", () => { describe("AJAX data tracking", () => {
API.setBaseUrl("http://blah.whatever.party"); API.setBaseUrl("http://blah.whatever.party");
@ -54,7 +54,7 @@ describe("AJAX data tracking", () => {
expect(maybeStartTracking).toHaveBeenCalled(); expect(maybeStartTracking).toHaveBeenCalled();
const list = (maybeStartTracking as jest.Mock).mock.calls; const list = (maybeStartTracking as jest.Mock).mock.calls;
const uuids: string[] = const uuids: string[] =
_.uniq(list.map((x: string[]) => x[0])); uniq(list.map((x: string[]) => x[0]));
expect(uuids.length).toEqual(r.length); expect(uuids.length).toEqual(r.length);
}); });

View File

@ -15,13 +15,13 @@ import { defensiveClone, unpackUUID } from "../util";
import { EditResourceParams } from "./interfaces"; import { EditResourceParams } from "./interfaces";
import { ResourceIndex } from "../resources/interfaces"; import { ResourceIndex } from "../resources/interfaces";
import { SequenceBodyItem } from "farmbot/dist"; import { SequenceBodyItem } from "farmbot/dist";
import _ from "lodash";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { maybeStartTracking } from "./maybe_start_tracking"; import { maybeStartTracking } from "./maybe_start_tracking";
import { t } from "i18next"; import { t } from "i18next";
import { newTaggedResource } from "../sync/actions"; import { newTaggedResource } from "../sync/actions";
import { arrayUnwrap } from "../resources/util"; import { arrayUnwrap } from "../resources/util";
import { findByUuid } from "../resources/reducer_support"; import { findByUuid } from "../resources/reducer_support";
import { assign, noop } from "lodash";
export function edit(tr: TaggedResource, changes: Partial<typeof tr.body>): export function edit(tr: TaggedResource, changes: Partial<typeof tr.body>):
ReduxAction<EditResourceParams> { ReduxAction<EditResourceParams> {
@ -135,7 +135,7 @@ export function refresh(resource: TaggedResource, urlNeedsId = false) {
.then(resp => { .then(resp => {
const r1 = defensiveClone(resource); const r1 = defensiveClone(resource);
const r2 = { body: defensiveClone(resp.data) }; const r2 = { body: defensiveClone(resp.data) };
const newTR = _.assign({}, r1, r2); const newTR = assign({}, r1, r2);
if (isTaggedResource(newTR)) { if (isTaggedResource(newTR)) {
dispatch(refreshOK(newTR)); dispatch(refreshOK(newTR));
} else { } else {
@ -223,8 +223,8 @@ export function destroyAll(resourceName: ResourceName, force = false) {
} }
export function saveAll(input: TaggedResource[], export function saveAll(input: TaggedResource[],
callback: () => void = _.noop, callback: () => void = noop,
errBack: (err: UnsafeError) => void = _.noop) { errBack: (err: UnsafeError) => void = noop) {
return function (dispatch: Function) { return function (dispatch: Function) {
const p = input const p = input
.filter(x => x.specialStatus === SpecialStatus.DIRTY) .filter(x => x.specialStatus === SpecialStatus.DIRTY)
@ -293,7 +293,7 @@ export function updateViaAjax(payl: AjaxUpdatePayload) {
.then(function (resp) { .then(function (resp) {
const r1 = defensiveClone(resource); const r1 = defensiveClone(resource);
const r2 = { body: defensiveClone(resp.data) }; const r2 = { body: defensiveClone(resp.data) };
const newTR = _.assign({}, r1, r2); const newTR = assign({}, r1, r2);
if (isTaggedResource(newTR)) { if (isTaggedResource(newTR)) {
dispatch(saveOK(newTR)); dispatch(saveOK(newTR));
} else { } else {

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import { t } from "i18next"; import { t } from "i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import _ from "lodash";
import { init, error } from "farmbot-toastr"; import { init, error } from "farmbot-toastr";
import { NavBar } from "./nav"; import { NavBar } from "./nav";
import { Everything } from "./interfaces"; import { Everything } from "./interfaces";
@ -24,6 +23,7 @@ import { getWebAppConfigValue, GetWebAppConfigValue } from "./config_storage/act
import { takeSortedLogs } from "./logs/state_to_props"; import { takeSortedLogs } from "./logs/state_to_props";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
import { getFirmwareConfig } from "./resources/getters"; import { getFirmwareConfig } from "./resources/getters";
import { intersection } from "lodash";
/** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */ /** Remove 300ms delay on touch devices - https://github.com/ftlabs/fastclick */
// const fastClick = require("fastclick"); // const fastClick = require("fastclick");
@ -89,7 +89,7 @@ const MUST_LOAD: ResourceName[] = [
export class App extends React.Component<AppProps, {}> { export class App extends React.Component<AppProps, {}> {
private get isLoaded() { private get isLoaded() {
return (MUST_LOAD.length === return (MUST_LOAD.length ===
_.intersection(this.props.loaded, MUST_LOAD).length); intersection(this.props.loaded, MUST_LOAD).length);
} }
/** /**

View File

@ -8,8 +8,8 @@ import {
import { GetState } from "../redux/interfaces"; import { GetState } from "../redux/interfaces";
import { dispatchNetworkDown } from "."; import { dispatchNetworkDown } from ".";
import { Log } from "farmbot/dist/resources/api_resources"; import { Log } from "farmbot/dist/resources/api_resources";
import _ from "lodash";
import { globalQueue } from "./batch_queue"; import { globalQueue } from "./batch_queue";
import { isUndefined, get } from "lodash";
const LEGACY_META_KEY_NAMES: (keyof Log)[] = [ const LEGACY_META_KEY_NAMES: (keyof Log)[] = [
"type", "type",
@ -25,7 +25,7 @@ function legacyKeyTransformation(log: Log,
key: keyof Log) { key: keyof Log) {
const before = log[key]; const before = log[key];
// You don't want to use || here, trust me. -RC // You don't want to use || here, trust me. -RC
log[key] = !_.isUndefined(before) ? before : _.get(log, ["meta", key], undefined); log[key] = !isUndefined(before) ? before : get(log, ["meta", key], undefined);
} }
export const onLogs = export const onLogs =

View File

@ -7,7 +7,7 @@ import {
AxisInputBoxGroupState, AxisInputBoxGroupState,
Vector Vector
} from "./interfaces"; } from "./interfaces";
import _ from "lodash"; import { isNumber } from "lodash";
/** Coordinate input and GO button for Move widget. */ /** Coordinate input and GO button for Move widget. */
export class AxisInputBoxGroup extends export class AxisInputBoxGroup extends
@ -29,9 +29,9 @@ export class AxisInputBoxGroup extends
z2 = p.z; z2 = p.z;
return { return {
x: _.isNumber(x) ? x : (x2 || 0), x: isNumber(x) ? x : (x2 || 0),
y: _.isNumber(y) ? y : (y2 || 0), y: isNumber(y) ? y : (y2 || 0),
z: _.isNumber(z) ? z : (z2 || 0) z: isNumber(z) ? z : (z2 || 0)
}; };
} }

View File

@ -1,10 +1,10 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { Xyz, LocationName, Dictionary } from "farmbot"; import { Xyz, LocationName, Dictionary } from "farmbot";
import moment from "moment"; import moment from "moment";
import { BotLocationData, BotPosition } from "../../devices/interfaces"; import { BotLocationData, BotPosition } from "../../devices/interfaces";
import { trim } from "../../util"; import { trim } from "../../util";
import { t } from "i18next"; import { t } from "i18next";
import { cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range } from "lodash";
const HEIGHT = 50; const HEIGHT = 50;
const HISTORY_LENGTH_SECONDS = 120; const HISTORY_LENGTH_SECONDS = 120;
@ -32,9 +32,9 @@ type Entry = {
type Paths = Record<LocationName, Record<Xyz, string>>; type Paths = Record<LocationName, Record<Xyz, string>>;
const getArray = (): Entry[] => const getArray = (): Entry[] =>
JSON.parse(_.get(sessionStorage, MotorPositionHistory.array, "[]")); JSON.parse(get(sessionStorage, MotorPositionHistory.array, "[]"));
const getReversedArray = (): Entry[] => _.cloneDeep(getArray()).reverse(); const getReversedArray = (): Entry[] => cloneDeep(getArray()).reverse();
const getLastEntry = (): Entry | undefined => { const getLastEntry = (): Entry | undefined => {
const array = getArray(); const array = getArray();
@ -43,21 +43,21 @@ const getLastEntry = (): Entry | undefined => {
const findYLimit = (): number => { const findYLimit = (): number => {
const array = getArray(); const array = getArray();
const arrayAbsMax = _.max(array.map(entry => const arrayAbsMax = max(array.map(entry =>
_.max(["position", "scaled_encoders"].map((name: LocationName) => max(["position", "scaled_encoders"].map((name: LocationName) =>
_.max(["x", "y", "z"].map((axis: Xyz) => max(["x", "y", "z"].map((axis: Xyz) =>
Math.abs(entry.locationData[name][axis] || 0) + 1)))))); Math.abs(entry.locationData[name][axis] || 0) + 1))))));
return Math.max(_.ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX); return Math.max(ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX);
}; };
const updateArray = (update: Entry): Entry[] => { const updateArray = (update: Entry): Entry[] => {
const arr = getArray(); const arr = getArray();
const last = getLastEntry(); const last = getLastEntry();
if (update && _.isNumber(update.locationData.position.x) && if (update && isNumber(update.locationData.position.x) &&
(!last || !_.isEqual(last.timestamp, update.timestamp))) { (!last || !isEqual(last.timestamp, update.timestamp))) {
arr.push(update); arr.push(update);
} }
const newArray = _.takeRight(arr, 100) const newArray = takeRight(arr, 100)
.filter(x => { .filter(x => {
const entryAge = (last ? last.timestamp : moment().unix()) - x.timestamp; const entryAge = (last ? last.timestamp : moment().unix()) - x.timestamp;
return entryAge <= HISTORY_LENGTH_SECONDS; return entryAge <= HISTORY_LENGTH_SECONDS;
@ -82,8 +82,8 @@ const getPaths = (): Paths => {
["x", "y", "z"].map((axis: Xyz) => { ["x", "y", "z"].map((axis: Xyz) => {
const lastPos = last.locationData[name][axis]; const lastPos = last.locationData[name][axis];
const pos = entry.locationData[name][axis]; const pos = entry.locationData[name][axis];
if (_.isNumber(lastPos) && _.isFinite(lastPos) if (isNumber(lastPos) && isFinite(lastPos)
&& _.isNumber(maxY) && _.isNumber(pos)) { && isNumber(maxY) && isNumber(pos)) {
if (!paths[name][axis].startsWith("M")) { if (!paths[name][axis].startsWith("M")) {
const yStart = -lastPos / maxY * HEIGHT / 2; const yStart = -lastPos / maxY * HEIGHT / 2;
paths[name][axis] = `M ${MAX_X},${yStart} `; paths[name][axis] = `M ${MAX_X},${yStart} `;
@ -135,7 +135,7 @@ const XAxisLabels = () =>
fontStyle={"italic"}> fontStyle={"italic"}>
{t("seconds ago")} {t("seconds ago")}
</text> </text>
{_.range(0, HISTORY_LENGTH_SECONDS + 1, 20).map(secondsAgo => {range(0, HISTORY_LENGTH_SECONDS + 1, 20).map(secondsAgo =>
<text key={"x_axis_label_" + secondsAgo} <text key={"x_axis_label_" + secondsAgo}
x={MAX_X - secondsAgo} y={HEIGHT / 2 + BORDER_WIDTH / 3}> x={MAX_X - secondsAgo} y={HEIGHT / 2 + BORDER_WIDTH / 3}>
{secondsAgo} {secondsAgo}

View File

@ -1,16 +1,16 @@
import * as React from "react"; import * as React from "react";
import { Component } from "react"; import { Component } from "react";
import { StepSizeSelectorProps } from "./interfaces"; import { StepSizeSelectorProps } from "./interfaces";
import _ from "lodash"; import { first, last } from "lodash";
export class StepSizeSelector extends Component<StepSizeSelectorProps, {}> { export class StepSizeSelector extends Component<StepSizeSelectorProps, {}> {
cssForIndex(num: number) { cssForIndex(num: number) {
const choices = this.props.choices; const choices = this.props.choices;
let css = "move-amount no-radius fb-button "; let css = "move-amount no-radius fb-button ";
if (num === _.first(choices)) { if (num === first(choices)) {
css += "leftmost "; css += "leftmost ";
} }
if (num === _.last(choices)) { if (num === last(choices)) {
css += "rightmost "; css += "rightmost ";
} }
if (num === this.props.selected) { if (num === this.props.selected) {

View File

@ -9,16 +9,16 @@ import {
} from "../resources/selectors"; } from "../resources/selectors";
import { Props } from "./interfaces"; import { Props } from "./interfaces";
import { maybeFetchUser } from "../resources/selectors"; import { maybeFetchUser } from "../resources/selectors";
import _ from "lodash";
import { import {
validFwConfig, shouldDisplay, determineInstalledOsVersion validFwConfig, shouldDisplay, determineInstalledOsVersion
} from "../util"; } from "../util";
import { getWebAppConfigValue } from "../config_storage/actions"; import { getWebAppConfigValue } from "../config_storage/actions";
import { getFirmwareConfig } from "../resources/getters"; import { getFirmwareConfig } from "../resources/getters";
import { uniq } from "lodash";
export function mapStateToProps(props: Everything): Props { export function mapStateToProps(props: Everything): Props {
const peripherals = _.uniq(selectAllPeripherals(props.resources.index)); const peripherals = uniq(selectAllPeripherals(props.resources.index));
const sensors = _.uniq(selectAllSensors(props.resources.index)); const sensors = uniq(selectAllSensors(props.resources.index));
const resources = props.resources; const resources = props.resources;
const bot2mqtt = props.bot.connectivity["bot.mqtt"]; const bot2mqtt = props.bot.connectivity["bot.mqtt"];
const botToMqttStatus = bot2mqtt ? bot2mqtt.state : "down"; const botToMqttStatus = bot2mqtt ? bot2mqtt.state : "down";

View File

@ -5,10 +5,10 @@ import { ToolTips } from "../../constants";
import { WebcamPanelProps } from "./interfaces"; import { WebcamPanelProps } from "./interfaces";
import { KeyValEditRow } from "../key_val_edit_row"; import { KeyValEditRow } from "../key_val_edit_row";
import { SpecialStatus, TaggedWebcamFeed } from "farmbot"; import { SpecialStatus, TaggedWebcamFeed } from "farmbot";
import _ from "lodash"; import { sortBy } from "lodash";
export function sortedFeeds(feeds: TaggedWebcamFeed[]): TaggedWebcamFeed[] { export function sortedFeeds(feeds: TaggedWebcamFeed[]): TaggedWebcamFeed[] {
return _.sortBy(feeds, (f) => { return f.body.id || Infinity; }); return sortBy(feeds, (f) => { return f.body.id || Infinity; });
} }
export function Edit(props: WebcamPanelProps) { export function Edit(props: WebcamPanelProps) {

View File

@ -1,12 +1,12 @@
import { botReducer, initialState } from "../reducer"; import { botReducer, initialState } from "../reducer";
import { Actions } from "../../constants"; import { Actions } from "../../constants";
import { ControlPanelState } from "../interfaces"; import { ControlPanelState } from "../interfaces";
import * as _ from "lodash";
import { defensiveClone } from "../../util"; import { defensiveClone } from "../../util";
import { networkUp, networkDown } from "../../connectivity/actions"; import { networkUp, networkDown } from "../../connectivity/actions";
import { stash } from "../../connectivity/data_consistency"; import { stash } from "../../connectivity/data_consistency";
import { incomingStatus } from "../../connectivity/connect_device"; import { incomingStatus } from "../../connectivity/connect_device";
import { Vector3 } from "farmbot"; import { Vector3 } from "farmbot";
import { values, omit } from "lodash";
describe("botReducer", () => { describe("botReducer", () => {
it("Starts / stops an update", () => { it("Starts / stops an update", () => {
@ -49,8 +49,8 @@ describe("botReducer", () => {
}); });
const bulkToggable = const bulkToggable =
_.omit(state.controlPanelState, "power_and_reset", "diagnostic_dumps"); omit(state.controlPanelState, "power_and_reset", "diagnostic_dumps");
_.values(bulkToggable).map(value => { values(bulkToggable).map(value => {
expect(value).toBeTruthy(); expect(value).toBeTruthy();
}); });
}); });

View File

@ -1,6 +1,5 @@
import { t } from "i18next"; import { t } from "i18next";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
import { success, warning, info, error } from "farmbot-toastr"; import { success, warning, info, error } from "farmbot-toastr";
import { getDevice } from "../device"; import { getDevice } from "../device";
import { Everything } from "../interfaces"; import { Everything } from "../interfaces";
@ -22,6 +21,7 @@ import { Log } from "farmbot/dist/resources/api_resources";
import { FbosConfig } from "farmbot/dist/resources/configs/fbos"; import { FbosConfig } from "farmbot/dist/resources/configs/fbos";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
import { getFirmwareConfig, getFbosConfig } from "../resources/getters"; import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
import { isObject, isString, get, noop } from "lodash";
const ON = 1, OFF = 0; const ON = 1, OFF = 0;
export type ConfigKey = keyof McuParams; export type ConfigKey = keyof McuParams;
@ -37,7 +37,7 @@ const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"];
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
export function isLog(x: any): x is Log { export function isLog(x: any): x is Log {
const yup = _.isObject(x) && _.isString(_.get(x, "message" as keyof Log)); const yup = isObject(x) && isString(get(x, "message" as keyof Log));
if (yup) { if (yup) {
if (oneOf(BAD_WORDS, x.message.toUpperCase())) {// SECURITY CRITICAL CODE. if (oneOf(BAD_WORDS, x.message.toUpperCase())) {// SECURITY CRITICAL CODE.
throw new Error("Refusing to display log: " + JSON.stringify(x)); throw new Error("Refusing to display log: " + JSON.stringify(x));
@ -219,7 +219,7 @@ export const fetchReleases =
* @param x axios response data * @param x axios response data
*/ */
function validMinOsFeatureLookup(x: MinOsFeatureLookup): boolean { function validMinOsFeatureLookup(x: MinOsFeatureLookup): boolean {
return _.isObject(x) && return isObject(x) &&
Object.entries(x).every(([key, val]) => Object.entries(x).every(([key, val]) =>
typeof key === "string" && // feature name typeof key === "string" && // feature name
typeof val === "string" && // version string typeof val === "string" && // version string
@ -296,7 +296,7 @@ export function settingToggle(
} else { } else {
return getDevice() return getDevice()
.updateMcu(update) .updateMcu(update)
.then(_.noop, commandErr(noun)); .then(noop, commandErr(noun));
} }
}; };
} }
@ -304,28 +304,28 @@ export function settingToggle(
export function moveRelative(props: MoveRelProps) { export function moveRelative(props: MoveRelProps) {
return getDevice() return getDevice()
.moveRelative(props) .moveRelative(props)
.then(_.noop, commandErr("Relative movement")); .then(noop, commandErr("Relative movement"));
} }
export function moveAbs(props: MoveRelProps) { export function moveAbs(props: MoveRelProps) {
const noun = "Absolute movement"; const noun = "Absolute movement";
return getDevice() return getDevice()
.moveAbsolute(props) .moveAbsolute(props)
.then(_.noop, commandErr(noun)); .then(noop, commandErr(noun));
} }
export function pinToggle(pin_number: number) { export function pinToggle(pin_number: number) {
const noun = "Setting toggle"; const noun = "Setting toggle";
return getDevice() return getDevice()
.togglePin({ pin_number }) .togglePin({ pin_number })
.then(_.noop, commandErr(noun)); .then(noop, commandErr(noun));
} }
export function readPin(pin_number: number, label: string, pin_mode: number) { export function readPin(pin_number: number, label: string, pin_mode: number) {
const noun = "Read pin"; const noun = "Read pin";
return getDevice() return getDevice()
.readPin({ pin_number, label, pin_mode }) .readPin({ pin_number, label, pin_mode })
.then(_.noop, commandErr(noun)); .then(noop, commandErr(noun));
} }
export function homeAll(speed: number) { export function homeAll(speed: number) {
@ -405,7 +405,7 @@ export function updateConfig(config: Configuration) {
} else { } else {
getDevice() getDevice()
.updateConfig(config) .updateConfig(config)
.then(_.noop, commandErr(noun)); .then(noop, commandErr(noun));
} }
}; };
} }

View File

@ -1,10 +1,10 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { BlurableInput } from "../../ui/index"; import { BlurableInput } from "../../ui/index";
import { SourceFbosConfig } from "../interfaces"; import { SourceFbosConfig } from "../interfaces";
import { ConfigurationName } from "farmbot/dist"; import { ConfigurationName } from "farmbot/dist";
import { updateConfig } from "../actions"; import { updateConfig } from "../actions";
import { parseIntInput } from "../../util"; import { parseIntInput } from "../../util";
import { isNumber, isBoolean, isNaN } from "lodash";
export interface BotConfigInputBoxProps { export interface BotConfigInputBoxProps {
setting: ConfigurationName; setting: ConfigurationName;
@ -27,7 +27,7 @@ export class BotConfigInputBox
return (event: React.FormEvent<HTMLInputElement>) => { return (event: React.FormEvent<HTMLInputElement>) => {
const next = parseIntInput(event.currentTarget.value); const next = parseIntInput(event.currentTarget.value);
const current = this.config.value; const current = this.config.value;
if (!_.isNaN(next) && (next !== current)) { if (!isNaN(next) && (next !== current)) {
dispatch(updateConfig({ [key]: next })); dispatch(updateConfig({ [key]: next }));
} }
}; };
@ -35,7 +35,7 @@ export class BotConfigInputBox
render() { render() {
const current = this.config.value; const current = this.config.value;
const boxValue = (_.isNumber(current) || _.isBoolean(current)) const boxValue = (isNumber(current) || isBoolean(current))
? current.toString() : ""; ? current.toString() : "";
return <BlurableInput return <BlurableInput

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { warning } from "farmbot-toastr"; import { warning } from "farmbot-toastr";
import { McuInputBoxProps } from "../interfaces"; import { McuInputBoxProps } from "../interfaces";
import { updateMCU } from "../actions"; import { updateMCU } from "../actions";
@ -8,6 +7,7 @@ import {
clampUnsignedInteger, IntegerSize, getMaxInputFromIntSize clampUnsignedInteger, IntegerSize, getMaxInputFromIntSize
} from "../../util"; } from "../../util";
import { t } from "i18next"; import { t } from "i18next";
import { isUndefined } from "lodash";
export class McuInputBox extends React.Component<McuInputBoxProps, {}> { export class McuInputBox extends React.Component<McuInputBoxProps, {}> {
@ -20,7 +20,7 @@ export class McuInputBox extends React.Component<McuInputBoxProps, {}> {
get value() { get value() {
const v = this.config.value; const v = this.config.value;
const { filter } = this.props; const { filter } = this.props;
const goodValue = !_.isUndefined(v) && !(filter && v > filter); const goodValue = !isUndefined(v) && !(filter && v > filter);
return goodValue ? (v || 0).toString() : ""; return goodValue ? (v || 0).toString() : "";
} }

View File

@ -2,9 +2,9 @@ import * as React from "react";
import { render, mount } from "enzyme"; import { render, mount } from "enzyme";
import { ConnectivityPanel } from "../index"; import { ConnectivityPanel } from "../index";
import { StatusRowProps } from "../connectivity_row"; import { StatusRowProps } from "../connectivity_row";
import * as _ from "lodash";
import { SpecialStatus } from "farmbot"; import { SpecialStatus } from "farmbot";
import { bot } from "../../../__test_support__/fake_state/bot"; import { bot } from "../../../__test_support__/fake_state/bot";
import { fill } from "lodash";
describe("<ConnectivityPanel/>", () => { describe("<ConnectivityPanel/>", () => {
function test() { function test() {
@ -16,7 +16,7 @@ describe("<ConnectivityPanel/>", () => {
connectionStatus: false, connectionStatus: false,
children: "Can't do things with stuff." children: "Can't do things with stuff."
}; };
const rowData: StatusRowProps[] = _.fill(Array(5), statusRow); const rowData: StatusRowProps[] = fill(Array(5), statusRow);
return { return {
component: <ConnectivityPanel component: <ConnectivityPanel

View File

@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { Color } from "../../ui/colors"; import { Color } from "../../ui/colors";
import _ from "lodash";
import { reservedPiGPIO } from "./list_and_label_support"; import { reservedPiGPIO } from "./list_and_label_support";
import { range, isNumber, includes, noop } from "lodash";
export interface RpiGpioDiagramProps { export interface RpiGpioDiagramProps {
boundPins: number[] | undefined; boundPins: number[] | undefined;
@ -55,7 +55,7 @@ export class RpiGpioDiagram
<circle fill={Color.gray} strokeWidth={1.5} stroke={"yellow"} <circle fill={Color.gray} strokeWidth={1.5} stroke={"yellow"}
cx={5} cy={4} r={2} /> cx={5} cy={4} r={2} />
{[3, 5.5].map((x, xi) => { {[3, 5.5].map((x, xi) => {
return _.range(8, 56, 2.5).map((y, yi) => { return range(8, 56, 2.5).map((y, yi) => {
const pin = gpio[yi][xi]; const pin = gpio[yi][xi];
const normalColor = () => { const normalColor = () => {
switch (pin) { switch (pin) {
@ -72,10 +72,10 @@ export class RpiGpioDiagram
return Color.green; return Color.green;
} }
}; };
const color = _.isNumber(pin) && reservedPiGPIO.includes(pin) const color = isNumber(pin) && reservedPiGPIO.includes(pin)
? Color.magenta ? Color.magenta
: normalColor(); : normalColor();
const pinColor = _.includes(this.props.boundPins, pin) const pinColor = includes(this.props.boundPins, pin)
? Color.darkGray ? Color.darkGray
: color; : color;
return <rect strokeWidth={0.5} key={`gpio_${pin}_${xi}_${yi}`} return <rect strokeWidth={0.5} key={`gpio_${pin}_${xi}_${yi}`}
@ -84,7 +84,7 @@ export class RpiGpioDiagram
onMouseEnter={this.hover(pin)} onMouseEnter={this.hover(pin)}
onMouseLeave={this.hover(undefined)} onMouseLeave={this.hover(undefined)}
onClick={() => onClick={() =>
_.isNumber(pin) ? this.props.setSelectedPin(pin) : _.noop} />; isNumber(pin) ? this.props.setSelectedPin(pin) : noop} />;
}); });
})} })}
<rect fill={Color.white} <rect fill={Color.white}

View File

@ -1,6 +1,6 @@
import { t } from "i18next"; import { t } from "i18next";
import _ from "lodash";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { isString } from "lodash";
/** Used for every new account the first time the Device page is loaded. */ /** Used for every new account the first time the Device page is loaded. */
const ONLY_ONCE = { const ONLY_ONCE = {
@ -33,7 +33,7 @@ export function inferTimezone(current: string | undefined): string {
export function timezoneMismatch(botTime: string | undefined, export function timezoneMismatch(botTime: string | undefined,
userTime: string | undefined = maybeResolveTZ()): boolean { userTime: string | undefined = maybeResolveTZ()): boolean {
if (_.isString(botTime) && _.isString(userTime)) { if (isString(botTime) && isString(userTime)) {
return botTime.toUpperCase() !== userTime.toUpperCase(); return botTime.toUpperCase() !== userTime.toUpperCase();
} else { } else {
// Don't show warnings if TZ data is unavailable. // Don't show warnings if TZ data is unavailable.

View File

@ -2,7 +2,7 @@ import * as React from "react";
import { FBSelect, DropDownItem } from "../../ui/index"; import { FBSelect, DropDownItem } from "../../ui/index";
import { list } from "./tz_list"; import { list } from "./tz_list";
import { inferTimezone } from "./guess_timezone"; import { inferTimezone } from "./guess_timezone";
import _ from "lodash"; import { isString } from "lodash";
const CHOICES: DropDownItem[] = list.map(x => ({ label: x, value: x })); const CHOICES: DropDownItem[] = list.map(x => ({ label: x, value: x }));
@ -30,7 +30,7 @@ export class TimezoneSelector extends React.Component<TZSelectorProps, {}> {
} }
itemSelected = (d: DropDownItem): void => { itemSelected = (d: DropDownItem): void => {
if (_.isString(d.value)) { if (isString(d.value)) {
this.props.onUpdate(d.value); this.props.onUpdate(d.value);
} }
} }

View File

@ -1,20 +1,20 @@
import { MovePlantProps, DraggableEvent } from "./interfaces"; import { MovePlantProps, DraggableEvent } from "./interfaces";
import { defensiveClone } from "../util"; import { defensiveClone } from "../util";
import { edit } from "../api/crud"; import { edit } from "../api/crud";
import _ from "lodash";
import { history, getPathArray } from "../history"; import { history, getPathArray } from "../history";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { svgToUrl, DEFAULT_ICON } from "../open_farm/icons"; import { svgToUrl, DEFAULT_ICON } from "../open_farm/icons";
import { getMode } from "./map/util"; import { getMode } from "./map/util";
import { Mode } from "./map/interfaces"; import { Mode } from "./map/interfaces";
import { clamp } from "lodash";
export function movePlant(payload: MovePlantProps) { export function movePlant(payload: MovePlantProps) {
const tr = payload.plant; const tr = payload.plant;
const update = defensiveClone(payload.plant).body; const update = defensiveClone(payload.plant).body;
update.x += payload.deltaX; update.x += payload.deltaX;
update.y += payload.deltaY; update.y += payload.deltaY;
update.x = _.clamp(update.x, 0, payload.gridSize.x); update.x = clamp(update.x, 0, payload.gridSize.x);
update.y = _.clamp(update.y, 0, payload.gridSize.y); update.y = clamp(update.y, 0, payload.gridSize.y);
return edit(tr, update); return edit(tr, update);
} }

View File

@ -1,7 +1,7 @@
import { Dictionary } from "farmbot/dist"; import { Dictionary } from "farmbot/dist";
import { CalendarOccurrence, CalendarDay } from "../../interfaces"; import { CalendarOccurrence, CalendarDay } from "../../interfaces";
import moment from "moment"; import moment from "moment";
import _ from "lodash"; import { chain, sortBy } from "lodash";
export class Calendar { export class Calendar {
/** We sort by this attribute. Left as const so that the compiler can catch /** We sort by this attribute. Left as const so that the compiler can catch
@ -44,10 +44,10 @@ export class Calendar {
year: parseInt(item.mmddyy.slice(4, 6)), year: parseInt(item.mmddyy.slice(4, 6)),
month: Calendar.MONTHS[item.mmddyy.slice(0, 2)] || "???", month: Calendar.MONTHS[item.mmddyy.slice(0, 2)] || "???",
day: parseInt(item.mmddyy.slice(2, 4)), day: parseInt(item.mmddyy.slice(2, 4)),
items: _.sortBy(items, Calendar.SORT_KEY) items: sortBy(items, Calendar.SORT_KEY)
}; };
}); });
return _.chain(all).sortBy(Calendar.SORT_KEY).value(); return chain(all).sortBy(Calendar.SORT_KEY).value();
} }
findByDate(m: moment.Moment): CalendarOccurrence[] { findByDate(m: moment.Moment): CalendarOccurrence[] {

View File

@ -6,13 +6,13 @@ import { mapStateToProps } from "./map_state_to_props";
import { import {
FarmEventProps, CalendarOccurrence, FarmEventState FarmEventProps, CalendarOccurrence, FarmEventState
} from "../interfaces"; } from "../interfaces";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { DesignerNavTabs } from "../panel_header"; import { DesignerNavTabs } from "../panel_header";
import { Link } from "../../link"; import { Link } from "../../link";
import { DesignerPanel, DesignerPanelContent } from "../plants/designer_panel"; import { DesignerPanel, DesignerPanelContent } from "../plants/designer_panel";
import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper"; import { EmptyStateWrapper, EmptyStateGraphic } from "../../ui/empty_state_wrapper";
import { chain, some, uniq, map } from "lodash";
const filterSearch = (term: string) => (item: CalendarOccurrence) => const filterSearch = (term: string) => (item: CalendarOccurrence) =>
item.heading.toLowerCase().includes(term) item.heading.toLowerCase().includes(term)
@ -31,7 +31,7 @@ export class PureFarmEvents
innerRows = (items: CalendarOccurrence[]) => { innerRows = (items: CalendarOccurrence[]) => {
return _.chain(items) return chain(items)
.sortBy(x => x.sortKey) .sortBy(x => x.sortKey)
.value() .value()
.filter(filterSearch(this.searchTerm)) .filter(filterSearch(this.searchTerm))
@ -67,7 +67,7 @@ export class PureFarmEvents
return day.year == year; return day.year == year;
}) })
.filter(item => !this.searchTerm || .filter(item => !this.searchTerm ||
_.some(item.items.map(filterSearch(this.searchTerm)))) some(item.items.map(filterSearch(this.searchTerm))))
.map(item => { .map(item => {
return <div className="farm-event" key={item.sortKey}> return <div className="farm-event" key={item.sortKey}>
<div className="farm-event-date"> <div className="farm-event-date">
@ -86,7 +86,7 @@ export class PureFarmEvents
} }
renderCalendarRows() { renderCalendarRows() {
const years = _.uniq(_.map(this.props.calendarRows, "year")); const years = uniq(map(this.props.calendarRows, "year"));
return years.map(year => { return years.map(year => {
return <div key={moment(year, "YY").unix()}> return <div key={moment(year, "YY").unix()}>
<div className="farm-event-year"> <div className="farm-event-year">

View File

@ -4,7 +4,7 @@ import { transformXY, round } from "../util";
import { MapTransformProps, TaggedPlant } from "../interfaces"; import { MapTransformProps, TaggedPlant } from "../interfaces";
import { SpreadCircle } from "../layers/spread/spread_layer"; import { SpreadCircle } from "../layers/spread/spread_layer";
import { Circle } from "../layers/plants/circle"; import { Circle } from "../layers/plants/circle";
import _ from "lodash"; import { noop } from "lodash";
/** /**
* For showing the map plant hovered in the plant panel. * For showing the map plant hovered in the plant panel.
@ -73,7 +73,7 @@ export class HoveredPlant extends
<image <image
visibility={hovered ? "visible" : "hidden"} visibility={hovered ? "visible" : "hidden"}
style={isEditing ? {} : { pointerEvents: "none" }} style={isEditing ? {} : { pointerEvents: "none" }}
onClick={_.noop} onClick={noop}
className="hovered-plant-copy" className="hovered-plant-copy"
opacity={alpha} opacity={alpha}
x={qx - scaledRadius} x={qx - scaledRadius}

View File

@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react";
import { GridProps } from "../interfaces"; import { GridProps } from "../interfaces";
import { transformXY, transformForQuadrant } from "../util"; import { transformXY, transformForQuadrant } from "../util";
import _ from "lodash";
import { Color } from "../../../ui/index"; import { Color } from "../../../ui/index";
import { range } from "lodash";
export function Grid(props: GridProps) { export function Grid(props: GridProps) {
const { mapTransformProps } = props; const { mapTransformProps } = props;
@ -59,12 +59,12 @@ export function Grid(props: GridProps) {
<g id="axis-values" fontFamily="Arial" fontSize="10" <g id="axis-values" fontFamily="Arial" fontSize="10"
textAnchor="middle" dominantBaseline="central" fill="rgba(0, 0, 0, 0.3)"> textAnchor="middle" dominantBaseline="central" fill="rgba(0, 0, 0, 0.3)">
{_.range(100, gridSize.x, 100).map((i) => { {range(100, gridSize.x, 100).map((i) => {
const location = transformXY(i, -10, mapTransformProps); const location = transformXY(i, -10, mapTransformProps);
return <text key={"x-label-" + i} return <text key={"x-label-" + i}
x={location.qx} y={location.qy}>{i}</text>; x={location.qx} y={location.qy}>{i}</text>;
})} })}
{_.range(100, gridSize.y, 100).map((i) => { {range(100, gridSize.y, 100).map((i) => {
const location = transformXY(-15, i, mapTransformProps); const location = transformXY(-15, i, mapTransformProps);
return <text key={"y-label-" + i} return <text key={"y-label-" + i}
x={location.qx} y={location.qy}>{i}</text>; x={location.qx} y={location.qy}>{i}</text>;

View File

@ -2,10 +2,10 @@ import * as React from "react";
import { AxisNumberProperty, MapTransformProps } from "../../interfaces"; import { AxisNumberProperty, MapTransformProps } from "../../interfaces";
import { getMapSize, transformXY } from "../../util"; import { getMapSize, transformXY } from "../../util";
import { BotPosition } from "../../../../devices/interfaces"; import { BotPosition } from "../../../../devices/interfaces";
import _ from "lodash";
import { trim } from "../../../../util"; import { trim } from "../../../../util";
import { GetWebAppConfigValue } from "../../../../config_storage/actions"; import { GetWebAppConfigValue } from "../../../../config_storage/actions";
import { BooleanSetting } from "../../../../session_keys"; import { BooleanSetting } from "../../../../session_keys";
import { range } from "lodash";
export interface BotPeripheralsProps { export interface BotPeripheralsProps {
position: BotPosition; position: BotPosition;
@ -73,11 +73,11 @@ function waterFigure(
</g> </g>
</defs> </defs>
{_.range(0, copies).map(s => { {range(0, copies).map(s => {
return <g return <g
className={`water-spray delay-${s} ${animateClass}`} key={`spray-${s}`}> className={`water-spray delay-${s} ${animateClass}`} key={`spray-${s}`}>
<use xlinkHref="#water-circle" /> <use xlinkHref="#water-circle" />
{_.range(0, 360, 15).map(rotation => { {range(0, 360, 15).map(rotation => {
return <use xlinkHref="#water-line" key={`spray-line-${rotation}`} return <use xlinkHref="#water-line" key={`spray-line-${rotation}`}
transform={`rotate(${rotation}, ${cx}, ${cy})`} />; transform={`rotate(${rotation}, ${cx}, ${cy})`} />;
})} })}
@ -110,7 +110,7 @@ function vacuumFigure(
</g> </g>
</defs> </defs>
{_.range(0, copies).map(s => { {range(0, copies).map(s => {
return <g return <g
className={`vacuum delay-${s} ${animateClass}`} key={`vacuum-${s}`}> className={`vacuum delay-${s} ${animateClass}`} key={`vacuum-${s}`}>
<use xlinkHref="#vacuum-wave" /> <use xlinkHref="#vacuum-wave" />

View File

@ -1,9 +1,9 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { MapTransformProps } from "../../interfaces"; import { MapTransformProps } from "../../interfaces";
import { transformXY } from "../../util"; import { transformXY } from "../../util";
import { BotPosition } from "../../../../devices/interfaces"; import { BotPosition } from "../../../../devices/interfaces";
import { Color } from "../../../../ui"; import { Color } from "../../../../ui";
import { get, isNumber, takeRight, isEqual, round, first } from "lodash";
type TrailRecord = { type TrailRecord = {
coord: Record<"x" | "y", number | undefined>, coord: Record<"x" | "y", number | undefined>,
@ -17,18 +17,18 @@ export enum VirtualTrail {
function getNewTrailArray(update: TrailRecord, watering: boolean): TrailRecord[] { function getNewTrailArray(update: TrailRecord, watering: boolean): TrailRecord[] {
const key = VirtualTrail.records; // sessionStorage location const key = VirtualTrail.records; // sessionStorage location
const trailLength = _.get(sessionStorage, VirtualTrail.length, 100); const trailLength = get(sessionStorage, VirtualTrail.length, 100);
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 && 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
if (watering && last && _.isNumber(last.water)) { last.water += 1; } if (watering && last && isNumber(last.water)) { last.water += 1; }
} }
sessionStorage.setItem(key, JSON.stringify(arr)); // save array sessionStorage.setItem(key, JSON.stringify(arr)); // save array
return _.takeRight(arr, trailLength); return takeRight(arr, trailLength);
} }
export interface BotTrailProps { export interface BotTrailProps {
@ -42,7 +42,7 @@ export function BotTrail(props: BotTrailProps) {
transformXY(ox, oy, props.mapTransformProps); transformXY(ox, oy, props.mapTransformProps);
const { x, y } = props.position; const { x, y } = props.position;
const watering = !!_.first(props.peripherals const watering = !!first(props.peripherals
.filter(p => p.label.toLowerCase().includes("water")) .filter(p => p.label.toLowerCase().includes("water"))
.map(p => p.value)); .map(p => p.value));
@ -51,10 +51,10 @@ export function BotTrail(props: BotTrailProps) {
return <g className="virtual-bot-trail"> return <g className="virtual-bot-trail">
{array.map((cur: TrailRecord, i: number) => { {array.map((cur: TrailRecord, i: number) => {
const prev = (array[i - 1] || { coord: undefined }).coord; // prev coord const prev = (array[i - 1] || { coord: undefined }).coord; // prev coord
const opacity = _.round(Math.max(0.25, i / (array.length - 1)), 2); const opacity = round(Math.max(0.25, i / (array.length - 1)), 2);
if (i > 0 && cur && prev && _.isNumber(prev.x) && _.isNumber(prev.y) if (i > 0 && cur && prev && isNumber(prev.x) && isNumber(prev.y)
&& _.isNumber(cur.coord.x) && _.isNumber(cur.coord.y) && isNumber(cur.coord.x) && isNumber(cur.coord.y)
&& _.isNumber(cur.water)) { && isNumber(cur.water)) {
const p1 = toQ(cur.coord.x, cur.coord.y); const p1 = toQ(cur.coord.x, cur.coord.y);
const p2 = toQ(prev.x, prev.y); const p2 = toQ(prev.x, prev.y);
return <g key={i}> return <g key={i}>

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { GardenPlant } from "./garden_plant"; import { GardenPlant } from "./garden_plant";
import { PlantLayerProps } from "../../interfaces"; import { PlantLayerProps } from "../../interfaces";
import { unpackUUID } from "../../../../util"; import { unpackUUID } from "../../../../util";

View File

@ -2,9 +2,9 @@ import * as React from "react";
import { SlotWithTool } from "../../../../resources/interfaces"; import { SlotWithTool } from "../../../../resources/interfaces";
import { transformXY } from "../../util"; import { transformXY } from "../../util";
import { MapTransformProps } from "../../interfaces"; import { MapTransformProps } from "../../interfaces";
import _ from "lodash";
import { ToolbaySlot, ToolNames, Tool } from "./tool_graphics"; import { ToolbaySlot, ToolNames, Tool } from "./tool_graphics";
import { ToolLabel } from "./tool_label"; import { ToolLabel } from "./tool_label";
import { includes } from "lodash";
export interface TSPProps { export interface TSPProps {
slot: SlotWithTool; slot: SlotWithTool;
@ -28,8 +28,8 @@ export class ToolSlotPoint extends
reduceToolName = (raw: string | undefined) => { reduceToolName = (raw: string | undefined) => {
const lower = (raw || "").toLowerCase(); const lower = (raw || "").toLowerCase();
if (_.includes(lower, "seed bin")) { return ToolNames.seedBin; } if (includes(lower, "seed bin")) { return ToolNames.seedBin; }
if (_.includes(lower, "seed tray")) { return ToolNames.seedTray; } if (includes(lower, "seed tray")) { return ToolNames.seedTray; }
return ToolNames.tool; return ToolNames.tool;
} }

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
import { t } from "i18next"; import { t } from "i18next";
import _ from "lodash";
import { svgToUrl } from "../../open_farm/icons"; import { svgToUrl } from "../../open_farm/icons";
import { import {
CropInfoProps, CropLiveSearchResult, OpenfarmSearch CropInfoProps, CropLiveSearchResult, OpenfarmSearch
@ -23,6 +22,7 @@ import { Actions } from "../../constants";
import { import {
EmptyStateWrapper, EmptyStateGraphic EmptyStateWrapper, EmptyStateGraphic
} from "../../ui/empty_state_wrapper"; } from "../../ui/empty_state_wrapper";
import { startCase, isArray, chain, isNumber } from "lodash";
interface InfoFieldProps { interface InfoFieldProps {
title: string; title: string;
@ -40,7 +40,7 @@ interface SummaryItemProps {
const InfoField = (props: InfoFieldProps) => const InfoField = (props: InfoFieldProps) =>
<li> <li>
<p> <p>
{t(_.startCase(props.title))} {t(startCase(props.title))}
</p> </p>
<div> <div>
{props.children} {props.children}
@ -87,7 +87,7 @@ const CmProperty = ({ i, field, value }: SummaryItemProps) =>
/** Comma-separated list of crop common names. */ /** Comma-separated list of crop common names. */
const CommonNames = ({ i, field, value }: SummaryItemProps) => const CommonNames = ({ i, field, value }: SummaryItemProps) =>
<InfoField key={i} title={field}> <InfoField key={i} title={field}>
{(_.isArray(value) {(isArray(value)
? value.join(", ") ? value.join(", ")
: value) || NO_VALUE} : value) || NO_VALUE}
</InfoField>; </InfoField>;
@ -119,7 +119,7 @@ const handleDisplay = ([field, value]: string[], i: number) => {
const CropInfoList = (crop: OpenFarm.OFCrop) => { const CropInfoList = (crop: OpenFarm.OFCrop) => {
return <div className="object-list"> return <div className="object-list">
<ul> <ul>
{_(crop) {chain(crop)
.omit(OMITTED_PROPERTIES) .omit(OMITTED_PROPERTIES)
.toPairs() .toPairs()
.map(handleDisplay) .map(handleDisplay)
@ -138,7 +138,7 @@ const AddPlantHereButton = (props: {
}) => { }) => {
const { botPosition, openedSavedGarden, cropName, slug, dispatch } = props; const { botPosition, openedSavedGarden, cropName, slug, dispatch } = props;
const { x, y } = botPosition; const { x, y } = botPosition;
const botXY = _.isNumber(x) && _.isNumber(y) ? const botXY = isNumber(x) && isNumber(y) ?
{ x: round(x), y: round(y) } : undefined; { x: round(x), y: round(y) } : undefined;
const botXYLabel = botXY ? `(${botXY.x}, ${botXY.y})` : "(unknown)"; const botXYLabel = botXY ? `(${botXY.x}, ${botXY.y})` : "(unknown)";
const click = () => botXY const click = () => botXY

View File

@ -6,8 +6,8 @@ import {
} from "../../resources/selectors"; } from "../../resources/selectors";
import { history } from "../../history"; import { history } from "../../history";
import { PlantStage } from "farmbot"; import { PlantStage } from "farmbot";
import _ from "lodash";
import { TaggedPlant } from "../map/interfaces"; import { TaggedPlant } from "../map/interfaces";
import { isNumber, get } from "lodash";
export function mapStateToProps(props: Everything): EditPlantInfoProps { export function mapStateToProps(props: Everything): EditPlantInfoProps {
const openedSavedGarden = const openedSavedGarden =
@ -15,7 +15,7 @@ export function mapStateToProps(props: Everything): EditPlantInfoProps {
const gardenOpen = !!openedSavedGarden; const gardenOpen = !!openedSavedGarden;
const findPlant = (id: string | undefined) => { const findPlant = (id: string | undefined) => {
const num = parseInt(id || "NOPE", 10); const num = parseInt(id || "NOPE", 10);
if (_.isNumber(num) && !_.isNaN(num)) { if (isNumber(num) && !isNaN(num)) {
return gardenOpen return gardenOpen
? maybeFindPlantTemplateById(props.resources.index, num) ? maybeFindPlantTemplateById(props.resources.index, num)
: maybeFindPlantById(props.resources.index, num); : maybeFindPlantById(props.resources.index, num);
@ -49,9 +49,9 @@ export interface FormattedPlantInfo {
export function formatPlantInfo(rsrc: TaggedPlant): FormattedPlantInfo { export function formatPlantInfo(rsrc: TaggedPlant): FormattedPlantInfo {
const p = rsrc.body; const p = rsrc.body;
const plantedAt = _.get(p, "planted_at", moment()) const plantedAt = get(p, "planted_at", moment())
? moment(_.get(p, "planted_at", moment())) ? moment(get(p, "planted_at", moment()))
: moment(_.get(p, "created_at", moment())); : moment(get(p, "created_at", moment()));
const currentDay = moment(); const currentDay = moment();
const daysOld = currentDay.diff(plantedAt, "days") + 1; const daysOld = currentDay.diff(plantedAt, "days") + 1;
return { return {
@ -63,6 +63,6 @@ export function formatPlantInfo(rsrc: TaggedPlant): FormattedPlantInfo {
y: p.y, y: p.y,
uuid: rsrc.uuid, uuid: rsrc.uuid,
plantedAt, plantedAt,
plantStatus: _.get(p, "plant_stage", "planned"), plantStatus: get(p, "plant_stage", "planned"),
}; };
} }

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { t } from "i18next"; import { t } from "i18next";
import { FormattedPlantInfo } from "./map_state_to_props"; import { FormattedPlantInfo } from "./map_state_to_props";
import { round } from "../map/util"; import { round } from "../map/util";
@ -12,6 +11,7 @@ import { Actions } from "../../constants";
import { Link } from "../../link"; import { Link } from "../../link";
import { DesignerPanelContent } from "./designer_panel"; import { DesignerPanelContent } from "./designer_panel";
import { parseIntInput } from "../../util"; import { parseIntInput } from "../../util";
import { startCase, isNumber } from "lodash";
export interface PlantPanelProps { export interface PlantPanelProps {
info: FormattedPlantInfo; info: FormattedPlantInfo;
@ -149,17 +149,17 @@ export function PlantPanel(props: PlantPanelProps) {
</label> </label>
<ul> <ul>
<ListItem name={t("Full Name")}> <ListItem name={t("Full Name")}>
{_.startCase(name)} {startCase(name)}
</ListItem> </ListItem>
<ListItem name={t("Plant Type")}> <ListItem name={t("Plant Type")}>
<Link <Link
title={t("View crop info")} title={t("View crop info")}
to={`/app/designer/plants/crop_search/` + slug}> to={`/app/designer/plants/crop_search/` + slug}>
{_.startCase(slug)} {startCase(slug)}
</Link> </Link>
</ListItem> </ListItem>
<ListItem name={t("Started")}> <ListItem name={t("Started")}>
{(updatePlant && _.isNumber(props.timeOffset) && !inSavedGarden) {(updatePlant && isNumber(props.timeOffset) && !inSavedGarden)
? <EditDatePlanted ? <EditDatePlanted
uuid={uuid} uuid={uuid}
datePlanted={plantedAt} datePlanted={plantedAt}
@ -183,7 +183,7 @@ export function PlantPanel(props: PlantPanelProps) {
uuid={uuid} uuid={uuid}
plantStatus={plantStatus} plantStatus={plantStatus}
updatePlant={updatePlant} /> updatePlant={updatePlant} />
: t(_.startCase(plantStatus))} : t(startCase(plantStatus))}
</ListItem> </ListItem>
</ul> </ul>
<MoveToPlant x={x} y={y} dispatch={dispatch} isEditing={isEditing} /> <MoveToPlant x={x} y={y} dispatch={dispatch} isEditing={isEditing} />

View File

@ -1,12 +1,11 @@
import _ from "lodash";
import { CropLiveSearchResult } from "./interfaces"; import { CropLiveSearchResult } from "./interfaces";
import { t } from "i18next"; import { t } from "i18next";
import { DEFAULT_ICON } from "../open_farm/icons"; import { DEFAULT_ICON } from "../open_farm/icons";
import { startCase } from "lodash"; import { startCase, chain } from "lodash";
export function findBySlug( export function findBySlug(
crops: CropLiveSearchResult[], slug?: string): CropLiveSearchResult { crops: CropLiveSearchResult[], slug?: string): CropLiveSearchResult {
const crop = _.chain(crops).find((result) => result.crop.slug === slug).value(); const crop = chain(crops).find((result) => result.crop.slug === slug).value();
return crop || { return crop || {
crop: { crop: {
name: startCase((slug || t("Name")).split("-").join(" ")), name: startCase((slug || t("Name")).split("-").join(" ")),

View File

@ -12,7 +12,6 @@ import {
selectAllSensors, selectAllSensors,
maybeGetDevice maybeGetDevice
} from "../resources/selectors"; } from "../resources/selectors";
import _ from "lodash";
import { import {
validBotLocationData, validFwConfig, unpackUUID, validBotLocationData, validFwConfig, unpackUUID,
shouldDisplay as shouldDisplayFunc, shouldDisplay as shouldDisplayFunc,
@ -22,7 +21,7 @@ import { getWebAppConfigValue } from "../config_storage/actions";
import { Props } from "./interfaces"; import { Props } from "./interfaces";
import { TaggedPlant } from "./map/interfaces"; import { TaggedPlant } from "./map/interfaces";
import { RestResources } from "../resources/interfaces"; import { RestResources } from "../resources/interfaces";
import { isString } from "lodash"; import { isString, uniq, chain } from "lodash";
import { BooleanSetting } from "../session_keys"; import { BooleanSetting } from "../session_keys";
import { Feature } from "../devices/interfaces"; import { Feature } from "../devices/interfaces";
import { reduceFarmwareEnv } from "../farmware/state_to_props"; import { reduceFarmwareEnv } from "../farmware/state_to_props";
@ -66,7 +65,7 @@ export function mapStateToProps(props: Everything): Props {
const { movement_step_per_mm_x, movement_step_per_mm_y } = firmwareSettings; const { movement_step_per_mm_x, movement_step_per_mm_y } = firmwareSettings;
const peripherals = _.uniq(selectAllPeripherals(props.resources.index)) const peripherals = uniq(selectAllPeripherals(props.resources.index))
.map(x => { .map(x => {
const label = x.body.label; const label = x.body.label;
const pinStatus = x.body.pin const pinStatus = x.body.pin
@ -76,7 +75,7 @@ export function mapStateToProps(props: Everything): Props {
return { label, value }; return { label, value };
}); });
const latestImages = _.chain(selectAllImages(props.resources.index)) const latestImages = chain(selectAllImages(props.resources.index))
.sortBy(x => x.body.id) .sortBy(x => x.body.id)
.reverse() .reverse()
.value(); .value();
@ -100,7 +99,7 @@ export function mapStateToProps(props: Everything): Props {
calibrationZ: env["CAMERA_CALIBRATION_camera_z"], calibrationZ: env["CAMERA_CALIBRATION_camera_z"],
}; };
const sensorReadings = _.chain(selectAllSensorReadings(props.resources.index)) const sensorReadings = chain(selectAllSensorReadings(props.resources.index))
.sortBy(x => x.body.created_at) .sortBy(x => x.body.created_at)
.reverse() .reverse()
.take(500) .take(500)

View File

@ -1,9 +1,9 @@
import axios, { AxiosPromise } from "axios"; import axios, { AxiosPromise } from "axios";
import _ from "lodash";
import { OpenFarm, CropSearchResult } from "./openfarm"; import { OpenFarm, CropSearchResult } from "./openfarm";
import { DEFAULT_ICON } from "../open_farm/icons"; import { DEFAULT_ICON } from "../open_farm/icons";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { ExecutableType } from "farmbot/dist/resources/api_resources"; import { ExecutableType } from "farmbot/dist/resources/api_resources";
import { get } from "lodash";
const url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`; const url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`;
const openFarmSearchQuery = (q: string): AxiosPromise<CropSearchResult> => const openFarmSearchQuery = (q: string): AxiosPromise<CropSearchResult> =>
@ -21,14 +21,14 @@ export let OFSearch = (searchTerm: string) =>
openFarmSearchQuery(searchTerm) openFarmSearchQuery(searchTerm)
.then(resp => { .then(resp => {
const images: { [key: string]: string } = {}; const images: { [key: string]: string } = {};
_.get(resp, "data.included", FALLBACK) get(resp, "data.included", FALLBACK)
.map((item: OpenFarm.Included) => { .map((item: OpenFarm.Included) => {
return { id: item.id, url: item.attributes.thumbnail_url }; return { id: item.id, url: item.attributes.thumbnail_url };
}) })
.map((val: IdURL) => images[val.id] = val.url); .map((val: IdURL) => images[val.id] = val.url);
const payload = resp.data.data.map(datum => { const payload = resp.data.data.map(datum => {
const crop = datum.attributes; const crop = datum.attributes;
const id = _.get(datum, "relationships.pictures.data[0].id", ""); const id = get(datum, "relationships.pictures.data[0].id", "");
return { crop, image: (images[id] || DEFAULT_ICON) }; return { crop, image: (images[id] || DEFAULT_ICON) };
}); });
dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload }); dispatch({ type: Actions.OF_SEARCH_RESULTS_OK, payload });

View File

@ -3,10 +3,10 @@ import { Col, BlurableInput } from "../ui/index";
import { t } from "i18next"; import { t } from "i18next";
import { FarmwareManifest, Pair, FarmwareConfig } from "farmbot"; import { FarmwareManifest, Pair, FarmwareConfig } from "farmbot";
import { getDevice } from "../device"; import { getDevice } from "../device";
import _ from "lodash";
import { import {
ShouldDisplay, Feature, SaveFarmwareEnv, UserEnv ShouldDisplay, Feature, SaveFarmwareEnv, UserEnv
} from "../devices/interfaces"; } from "../devices/interfaces";
import { kebabCase, toString, snakeCase } from "lodash";
export interface FarmwareFormProps { export interface FarmwareFormProps {
farmware: FarmwareManifest; farmware: FarmwareManifest;
@ -18,7 +18,7 @@ export interface FarmwareFormProps {
/** Namespace a Farmware config with the Farmware name. */ /** Namespace a Farmware config with the Farmware name. */
export function getConfigEnvName(farmwareName: string, configName: string) { export function getConfigEnvName(farmwareName: string, configName: string) {
return `${_.snakeCase(farmwareName)}_${configName}`; return `${snakeCase(farmwareName)}_${configName}`;
} }
/** Farmware description and version info for help text contents. */ /** Farmware description and version info for help text contents. */
@ -71,7 +71,7 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element {
/** Get a Farmware input value from FBOS. */ /** Get a Farmware input value from FBOS. */
function getValue(farmwareName: string, currentConfig: FarmwareConfig) { function getValue(farmwareName: string, currentConfig: FarmwareConfig) {
return (user_env[getConfigEnvName(farmwareName, currentConfig.name)] return (user_env[getConfigEnvName(farmwareName, currentConfig.name)]
|| _.toString(currentConfig.value)); || toString(currentConfig.value));
} }
/** Execute a Farmware using the provided inputs. */ /** Execute a Farmware using the provided inputs. */
@ -86,7 +86,7 @@ export function FarmwareForm(props: FarmwareFormProps): JSX.Element {
const { farmware, user_env } = props; const { farmware, user_env } = props;
return <Col key={farmware.name}> return <Col key={farmware.name}>
<div className={_.kebabCase(farmware.name)}> <div className={kebabCase(farmware.name)}>
<button <button
className="fb-button green farmware-button" className="fb-button green farmware-button"
onClick={() => run(farmware.name, farmware.config)}> onClick={() => run(farmware.name, farmware.config)}>

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { t } from "i18next"; import { t } from "i18next";
import { success, error } from "farmbot-toastr"; import { success, error } from "farmbot-toastr";
@ -14,6 +13,7 @@ import {
downloadProgress downloadProgress
} from "../../devices/components/fbos_settings/os_update_button"; } from "../../devices/components/fbos_settings/os_update_button";
import { JobProgress, TaggedImage } from "farmbot"; import { JobProgress, TaggedImage } from "farmbot";
import { startCase } from "lodash";
interface MetaInfoProps { interface MetaInfoProps {
/** Default conversion is `attr_name ==> Attr Name`. /** Default conversion is `attr_name ==> Attr Name`.
@ -26,7 +26,7 @@ interface MetaInfoProps {
} }
function MetaInfo({ obj, attr, label }: MetaInfoProps) { function MetaInfo({ obj, attr, label }: MetaInfoProps) {
const top = label || _.startCase(attr.split("_").join()); const top = label || startCase(attr.split("_").join());
const bottom = safeStringFetch(obj, attr); const bottom = safeStringFetch(obj, attr);
return <div> return <div>
<label>{top}:</label> <label>{top}:</label>

View File

@ -6,7 +6,6 @@ import {
FarmwareProps, Feature, SaveFarmwareEnv, UserEnv FarmwareProps, Feature, SaveFarmwareEnv, UserEnv
} from "../devices/interfaces"; } from "../devices/interfaces";
import { prepopulateEnv } from "./weed_detector/remote_env/selectors"; import { prepopulateEnv } from "./weed_detector/remote_env/selectors";
import _ from "lodash";
import { import {
selectAllFarmwareEnvs, selectAllFarmwareInstallations selectAllFarmwareEnvs, selectAllFarmwareInstallations
} from "../resources/selectors_by_kind"; } from "../resources/selectors_by_kind";
@ -20,6 +19,7 @@ import { TaggedFarmwareEnv, FarmwareManifest, JobProgress } from "farmbot";
import { save, edit, initSave } from "../api/crud"; import { save, edit, initSave } from "../api/crud";
import { t } from "i18next"; import { t } from "i18next";
import { getWebAppConfig } from "../resources/getters"; import { getWebAppConfig } from "../resources/getters";
import { chain, cloneDeep } from "lodash";
/** Edit an existing Farmware env variable or add a new one. */ /** Edit an existing Farmware env variable or add a new one. */
export const saveOrEditFarmwareEnv = (ri: ResourceIndex): SaveFarmwareEnv => export const saveOrEditFarmwareEnv = (ri: ResourceIndex): SaveFarmwareEnv =>
@ -48,7 +48,7 @@ export const reduceFarmwareEnv =
}; };
export function mapStateToProps(props: Everything): FarmwareProps { export function mapStateToProps(props: Everything): FarmwareProps {
const images = _.chain(selectAllImages(props.resources.index)) const images = chain(selectAllImages(props.resources.index))
.sortBy(x => x.body.id) .sortBy(x => x.body.id)
.reverse() .reverse()
.value(); .value();
@ -56,7 +56,7 @@ export function mapStateToProps(props: Everything): FarmwareProps {
const currentImage = images const currentImage = images
.filter(i => i.uuid === props.resources.consumers.farmware.currentImage)[0] .filter(i => i.uuid === props.resources.consumers.farmware.currentImage)[0]
|| firstImage; || firstImage;
const { farmwares } = _.cloneDeep(props.bot.hardware.process_info); const { farmwares } = cloneDeep(props.bot.hardware.process_info);
const conf = getWebAppConfig(props.resources.index); const conf = getWebAppConfig(props.resources.index);
const { currentFarmware, firstPartyFarmwareNames } = const { currentFarmware, firstPartyFarmwareNames } =
props.resources.consumers.farmware; props.resources.consumers.farmware;
@ -107,7 +107,7 @@ export function mapStateToProps(props: Everything): FarmwareProps {
const jobs = props.bot.hardware.jobs || {}; const jobs = props.bot.hardware.jobs || {};
const imageJobNames = Object.keys(jobs).filter(x => x != "FBOS_OTA"); const imageJobNames = Object.keys(jobs).filter(x => x != "FBOS_OTA");
const imageJobs: JobProgress[] = const imageJobs: JobProgress[] =
_.chain(betterCompact(imageJobNames.map(x => jobs[x]))) chain(betterCompact(imageJobNames.map(x => jobs[x])))
.sortBy("time") .sortBy("time")
.reverse() .reverse()
.value(); .value();

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import axios from "axios"; import axios from "axios";
import { t } from "i18next"; import { t } from "i18next";
import { success, error } from "farmbot-toastr"; import { success, error } from "farmbot-toastr";
@ -6,7 +5,7 @@ import { Thunk } from "../../redux/interfaces";
import { API } from "../../api"; import { API } from "../../api";
import { Progress, ProgressCallback, trim } from "../../util"; import { Progress, ProgressCallback, trim } from "../../util";
import { getDevice } from "../../device"; import { getDevice } from "../../device";
import { noop } from "lodash"; import { noop, chunk } from "lodash";
import { GenericPointer } from "farmbot/dist/resources/api_resources"; import { GenericPointer } from "farmbot/dist/resources/api_resources";
import { Actions } from "../../constants"; import { Actions } from "../../constants";
@ -21,12 +20,12 @@ export function deletePoints(
const ids = resp.data.map(x => x.id); const ids = resp.data.map(x => x.id);
// If you delete too many points, you will violate the URL length // If you delete too many points, you will violate the URL length
// limitation of 2,083. Chunking helps fix that. // limitation of 2,083. Chunking helps fix that.
const chunks = _.chunk(ids, 179 /* Prime numbers, why not? */); const chunks = chunk(ids, 179 /* Prime numbers, why not? */);
const prog = new Progress(chunks.length, cb || noop); const prog = new Progress(chunks.length, cb || noop);
prog.inc(); prog.inc();
const promises = chunks.map(function (chunk) { const promises = chunks.map(function (c) {
return axios return axios
.delete(API.current.pointsPath + chunk.join(",")) .delete(API.current.pointsPath + c.join(","))
.then(function (x) { .then(function (x) {
prog.inc(); prog.inc();
return x; return x;

View File

@ -6,13 +6,13 @@ import {
FBSelect, NULL_CHOICE, DropDownItem FBSelect, NULL_CHOICE, DropDownItem
} from "../../ui/index"; } from "../../ui/index";
import { SettingsMenuProps } from "./interfaces"; import { SettingsMenuProps } from "./interfaces";
import _ from "lodash";
import { import {
SPECIAL_VALUE_DDI, CALIBRATION_DROPDOWNS, ORIGIN_DROPDOWNS SPECIAL_VALUE_DDI, CALIBRATION_DROPDOWNS, ORIGIN_DROPDOWNS
} from "./constants"; } from "./constants";
import { WD_ENV } from "./remote_env/interfaces"; import { WD_ENV } from "./remote_env/interfaces";
import { envGet } from "./remote_env/selectors"; import { envGet } from "./remote_env/selectors";
import { SPECIAL_VALUES } from "./remote_env/constants"; import { SPECIAL_VALUES } from "./remote_env/constants";
import { isNumber } from "lodash";
export class WeedDetectorConfig extends React.Component<SettingsMenuProps, {}> { export class WeedDetectorConfig extends React.Component<SettingsMenuProps, {}> {
NumberBox = ({ conf, label }: { NumberBox = ({ conf, label }: {
@ -32,7 +32,7 @@ export class WeedDetectorConfig extends React.Component<SettingsMenuProps, {}> {
}; };
setDDI = (k: keyof WD_ENV) => (d: DropDownItem) => { setDDI = (k: keyof WD_ENV) => (d: DropDownItem) => {
if (_.isNumber(d.value)) { if (isNumber(d.value)) {
this.props.onChange(k, d.value); this.props.onChange(k, d.value);
} else { } else {
throw new Error("Weed detector got a non-numeric value"); throw new Error("Weed detector got a non-numeric value");

View File

@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react";
import { Hue, Saturation } from "react-color/lib/components/common"; import { Hue, Saturation } from "react-color/lib/components/common";
import { FarmbotPickerProps } from "./interfaces"; import { FarmbotPickerProps } from "./interfaces";
import _ from "lodash";
import { Color } from "../../ui/index"; import { Color } from "../../ui/index";
import { noop } from "lodash";
/** Wrapper class around `react-color`'s `<Saturation />` and `<Hue />`. /** Wrapper class around `react-color`'s `<Saturation />` and `<Hue />`.
* Add an extra white box feature for showing user weed detection settings. * Add an extra white box feature for showing user weed detection settings.
@ -108,7 +108,7 @@ export class FarmbotColorPicker extends React.Component<FarmbotPickerProps, {}>
<Hue <Hue
{...dontTouchThis} {...dontTouchThis}
pointer={this.customPointer} pointer={this.customPointer}
onChange={_.noop} /> onChange={noop} />
{getHueBoxes(this.props.h, !!this.props.invertHue) {getHueBoxes(this.props.h, !!this.props.invertHue)
.map((box, i) => <div key={i} style={box} />)} .map((box, i) => <div key={i} style={box} />)}
</div> </div>
@ -117,7 +117,7 @@ export class FarmbotColorPicker extends React.Component<FarmbotPickerProps, {}>
<Saturation <Saturation
{...dontTouchThis} {...dontTouchThis}
pointer={this.customPointer} pointer={this.customPointer}
onChange={_.noop} /> onChange={noop} />
<div style={this.saturationboxCSS()} /> <div style={this.saturationboxCSS()} />
</div> </div>
</div>; </div>;

View File

@ -1,6 +1,6 @@
import { box } from "boxed_value"; import { box } from "boxed_value";
import _ from "lodash";
import { WDENVKey, Translation, FormatTranslationMap } from "./interfaces"; import { WDENVKey, Translation, FormatTranslationMap } from "./interfaces";
import { snakeCase, get, isUndefined } from "lodash";
/** I would rather not deal with all the weird edge cases that come with /** I would rather not deal with all the weird edge cases that come with
* supporting strings and numbers right now. It adds too many edge cases for the * supporting strings and numbers right now. It adds too many edge cases for the
@ -97,10 +97,10 @@ export const TRANSLATORS: FormatTranslationMap = {};
export function getSpecialValue(key: string | number): export function getSpecialValue(key: string | number):
SPECIAL_VALUES { SPECIAL_VALUES {
const k = _.snakeCase(("" + key).toUpperCase()).toUpperCase(); const k = snakeCase(("" + key).toUpperCase()).toUpperCase();
const v = _.get(SPECIAL_VALUES, k, NaN); const v = get(SPECIAL_VALUES, k, NaN);
if (_.isUndefined(v) || _.isNaN(v)) { if (isUndefined(v) || isNaN(v)) {
throw new Error("Not a SPECIAL_VALUE: " + k); throw new Error("Not a SPECIAL_VALUE: " + k);
} else { } else {
return v; return v;

View File

@ -2,9 +2,8 @@
import { WDENVKey, WD_ENV } from "./interfaces"; import { WDENVKey, WD_ENV } from "./interfaces";
import { WD_KEY_DEFAULTS, EVERY_WD_KEY } from "./constants"; import { WD_KEY_DEFAULTS, EVERY_WD_KEY } from "./constants";
import { defensiveClone, betterParseNum } from "../../../util"; import { defensiveClone, betterParseNum } from "../../../util";
import _ from "lodash";
import { parseEnvKey } from "./translators"; import { parseEnvKey } from "./translators";
import { isNumber } from "lodash"; import { isNumber, isString } from "lodash";
import { UserEnv } from "../../../devices/interfaces"; import { UserEnv } from "../../../devices/interfaces";
/** Given a half formed set of weed detector environment variables, creates a /** Given a half formed set of weed detector environment variables, creates a
@ -15,7 +14,7 @@ export function prepopulateEnv(env: UserEnv): WD_ENV {
EVERY_WD_KEY.map(key => { EVERY_WD_KEY.map(key => {
const initial = env[key]; const initial = env[key];
let val: string; let val: string;
if (_.isString(initial)) { if (isString(initial)) {
val = initial; val = initial;
} else { } else {
val = "" + WD_KEY_DEFAULTS[key]; val = "" + WD_KEY_DEFAULTS[key];

View File

@ -1,7 +1,6 @@
import * as React from "react"; import * as React from "react";
import axios from "axios"; import axios from "axios";
import { t } from "i18next"; import { t } from "i18next";
import _ from "lodash";
import { error as log, success, init as logInit } from "farmbot-toastr"; import { error as log, success, init as logInit } from "farmbot-toastr";
import { AuthState } from "../auth/interfaces"; import { AuthState } from "../auth/interfaces";
import { prettyPrintApiErrors, attachToRoot } from "../util"; import { prettyPrintApiErrors, attachToRoot } from "../util";
@ -16,6 +15,7 @@ import { CreateAccount } from "./create_account";
import { Content } from "../constants"; import { Content } from "../constants";
import { LaptopSplash } from "./laptop_splash"; import { LaptopSplash } from "./laptop_splash";
import { TermsCheckbox } from "./terms_checkbox"; import { TermsCheckbox } from "./terms_checkbox";
import { get } from "lodash";
export const attachFrontPage = export const attachFrontPage =
() => attachToRoot(FrontPage, {}); () => attachToRoot(FrontPage, {});
@ -85,7 +85,7 @@ export class FrontPage extends React.Component<{}, Partial<FrontPageState>> {
Session.replaceToken(resp.data); Session.replaceToken(resp.data);
window.location.href = "/app/controls"; window.location.href = "/app/controls";
}).catch((error: Error) => { }).catch((error: Error) => {
switch (_.get(error, "response.status")) { switch (get(error, "response.status")) {
case 451: // TOS was updated; User must agree to terms. case 451: // TOS was updated; User must agree to terms.
window.location.assign("/tos_update"); window.location.assign("/tos_update");
break; break;

View File

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash";
import { t } from "i18next"; import { t } from "i18next";
import { links } from "./nav/nav_links"; import { links } from "./nav/nav_links";
import { sync } from "./devices/actions"; import { sync } from "./devices/actions";
@ -13,6 +12,7 @@ import {
Overlay, Overlay,
Classes Classes
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import { findIndex } from "lodash";
interface Props { interface Props {
dispatch: Function; dispatch: Function;
@ -65,7 +65,7 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
this.setState({ [property]: !this.state[property] }); this.setState({ [property]: !this.state[property] });
private hotkeys(dispatch: Function, slug: string) { private hotkeys(dispatch: Function, slug: string) {
const idx = _.findIndex(links, { slug }); const idx = findIndex(links, { slug });
const right = "/app/" + (links[idx + 1] || links[0]).slug; const right = "/app/" + (links[idx + 1] || links[0]).slug;
const left = "/app/" + (links[idx - 1] || links[links.length - 1]).slug; const left = "/app/" + (links[idx - 1] || links[links.length - 1]).slug;
const hotkeyMap: IHotkeyProps[] = [ const hotkeyMap: IHotkeyProps[] = [

View File

@ -1,6 +1,6 @@
import { DataChangeType, Dictionary } from "farmbot/dist"; import { DataChangeType, Dictionary } from "farmbot/dist";
import { box } from "boxed_value"; import { box } from "boxed_value";
import _ from "lodash"; import { isNumber, isNaN } from "lodash";
export let METHOD_MAP: Dictionary<DataChangeType> = { export let METHOD_MAP: Dictionary<DataChangeType> = {
"post": "add", "post": "add",
@ -18,9 +18,9 @@ export function inferUpdateId(url: string) {
.split("/") .split("/")
.filter(x => !x.includes(",")) // Don't allow batch endpoints to participate. .filter(x => !x.includes(",")) // Don't allow batch endpoints to participate.
.map(x => parseInt(x, 10)) .map(x => parseInt(x, 10))
.filter(x => !_.isNaN(x)); .filter(x => !isNaN(x));
const id: number | undefined = ids[0]; const id: number | undefined = ids[0];
const isNum = _.isNumber(id); const isNum = isNumber(id);
const onlyOne = ids.length === 1; const onlyOne = ids.length === 1;
return (isNum && onlyOne) ? ("" + id) : "*"; return (isNum && onlyOne) ? ("" + id) : "*";
} catch (error) { // Don't crash - just keep moving along. This is a temp patch. } catch (error) { // Don't crash - just keep moving along. This is a temp patch.

View File

@ -6,25 +6,25 @@ import {
} from "./interceptor_support"; } from "./interceptor_support";
import { API } from "./api/index"; import { API } from "./api/index";
import { AuthState } from "./auth/interfaces"; import { AuthState } from "./auth/interfaces";
import _ from "lodash";
import { AxiosRequestConfig, AxiosResponse } from "axios"; import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Content } from "./constants"; import { Content } from "./constants";
import { dispatchNetworkUp, dispatchNetworkDown } from "./connectivity/index"; import { dispatchNetworkUp, dispatchNetworkDown } from "./connectivity/index";
import { Dictionary } from "farmbot"; import { Dictionary } from "farmbot";
import { outstandingRequests } from "./connectivity/data_consistency"; import { outstandingRequests } from "./connectivity/data_consistency";
import { Session } from "./session"; import { Session } from "./session";
import { get } from "lodash";
export function responseFulfilled(input: AxiosResponse): AxiosResponse { export function responseFulfilled(input: AxiosResponse): AxiosResponse {
dispatchNetworkUp("user.api", undefined, "responseFulfilled()"); dispatchNetworkUp("user.api", undefined, "responseFulfilled()");
return input; return input;
} }
/** These will raise type errors if our _.get usage ever requires changing. */ /** These will raise type errors if our get usage ever requires changing. */
const request: keyof SafeError = "request"; const request: keyof SafeError = "request";
const responseUrl: keyof SafeError["request"] = "responseURL"; const responseUrl: keyof SafeError["request"] = "responseURL";
export const isLocalRequest = (x: SafeError) => export const isLocalRequest = (x: SafeError) =>
_.get(x, [request, responseUrl], "").includes(API.current.baseUrl); get(x, [request, responseUrl], "").includes(API.current.baseUrl);
let ONLY_ONCE = true; let ONLY_ONCE = true;
export function responseRejected(x: SafeError | undefined) { export function responseRejected(x: SafeError | undefined) {
@ -33,7 +33,7 @@ export function responseRejected(x: SafeError | undefined) {
const a = ![451, 401, 422].includes(x.response.status); const a = ![451, 401, 422].includes(x.response.status);
const b = x.response.status > 399; const b = x.response.status > 399;
// Openfarm API was sending too many 404's. // Openfarm API was sending too many 404's.
const c = !_.get(x, "response.config.url", "").includes("openfarm.cc/"); const c = !get(x, "response.config.url", "").includes("openfarm.cc/");
if (a && b && c) { if (a && b && c) {
setTimeout(() => { setTimeout(() => {
// Explicitly throw error so error reporting tool will save it. // Explicitly throw error so error reporting tool will save it.

View File

@ -1,9 +1,9 @@
import * as React from "react"; import * as React from "react";
import { LogsFilterMenuProps } from "../interfaces"; import { LogsFilterMenuProps } from "../interfaces";
import _ from "lodash";
import { Slider } from "@blueprintjs/core"; import { Slider } from "@blueprintjs/core";
import { t } from "i18next"; import { t } from "i18next";
import { Filters } from "../interfaces"; import { Filters } from "../interfaces";
import { startCase } from "lodash";
export const LogsFilterMenu = (props: LogsFilterMenuProps) => { export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
/** Filter level 0: logs hidden. */ /** Filter level 0: logs hidden. */
@ -32,7 +32,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
return <fieldset key={logType}> return <fieldset key={logType}>
<label> <label>
<div className={`saucer ${logType}`} /> <div className={`saucer ${logType}`} />
{t(_.startCase(logType))} {t(startCase(logType))}
</label> </label>
<button <button
className={"fb-button fb-toggle-button " + btnColor(logType)} className={"fb-button fb-toggle-button " + btnColor(logType)}

View File

@ -1,6 +1,5 @@
import { Everything } from "../interfaces"; import { Everything } from "../interfaces";
import { selectAllLogs, maybeGetTimeOffset } from "../resources/selectors"; import { selectAllLogs, maybeGetTimeOffset } from "../resources/selectors";
import _ from "lodash";
import { LogsProps } from "./interfaces"; import { LogsProps } from "./interfaces";
import { import {
sourceFbosConfigValue sourceFbosConfigValue
@ -10,11 +9,12 @@ import { ResourceIndex } from "../resources/interfaces";
import { TaggedLog } from "farmbot"; import { TaggedLog } from "farmbot";
import { getWebAppConfigValue } from "../config_storage/actions"; import { getWebAppConfigValue } from "../config_storage/actions";
import { getFbosConfig } from "../resources/getters"; import { getFbosConfig } from "../resources/getters";
import { chain } from "lodash";
/** Take the specified number of logs after sorting by time created. */ /** Take the specified number of logs after sorting by time created. */
export function takeSortedLogs( export function takeSortedLogs(
numberOfLogs: number, ri: ResourceIndex): TaggedLog[] { numberOfLogs: number, ri: ResourceIndex): TaggedLog[] {
return _.chain(selectAllLogs(ri)) return chain(selectAllLogs(ri))
.sortBy("body.created_at") .sortBy("body.created_at")
.reverse() .reverse()
.take(numberOfLogs) .take(numberOfLogs)

View File

@ -1,4 +1,3 @@
import "../css/_index.scss";
import { detectLanguage } from "../i18n"; import { detectLanguage } from "../i18n";
import * as I18n from "i18next"; import * as I18n from "i18next";
import { onInit } from "./on_init"; import { onInit } from "./on_init";

View File

@ -1,27 +1,27 @@
import { configureStore } from "../store"; import { configureStore } from "../store";
import * as _ from "lodash"; import { get, set } from "lodash";
describe("configureStore", () => { describe("configureStore", () => {
it("calls the appropriate ENV", () => { it("calls the appropriate ENV", () => {
const result1 = configureStore(); const result1 = configureStore();
const old = _.get(process.env, "NODE_ENV", "development"); const old = get(process.env, "NODE_ENV", "development");
_.set(process.env, "NODE_ENV", "production"); set(process.env, "NODE_ENV", "production");
const result2 = configureStore(); const result2 = configureStore();
_.set(process.env, "NODE_ENV", old); set(process.env, "NODE_ENV", old);
expect(result1).not.toBe(result2); expect(result1).not.toBe(result2);
}); });
it("Does not crash if process.env.NODE_ENV is undefined", () => { it("Does not crash if process.env.NODE_ENV is undefined", () => {
const old = _.get(process.env, "NODE_ENV", "development"); const old = get(process.env, "NODE_ENV", "development");
_.set(process.env, "NODE_ENV", ""); set(process.env, "NODE_ENV", "");
const result1 = configureStore(); const result1 = configureStore();
expect(result1) expect(result1)
.toEqual(expect.objectContaining({ getState: expect.anything() })); .toEqual(expect.objectContaining({ getState: expect.anything() }));
_.set(process.env, "NODE_ENV", old); set(process.env, "NODE_ENV", old);
}); });
it("does not crash on malformed states", () => { it("does not crash on malformed states", () => {

View File

@ -1,11 +1,11 @@
import moment from "moment"; import moment from "moment";
import _ from "lodash"; import { isNumber, padStart } from "lodash";
export function msToTime(ms: number) { export function msToTime(ms: number) {
if (_.isNumber(ms)) { if (isNumber(ms)) {
const d = moment.duration(ms); const d = moment.duration(ms);
const h = _.padStart(d.hours().toString(), 2, "0"); const h = padStart(d.hours().toString(), 2, "0");
const m = _.padStart(d.minutes().toString(), 2, "0"); const m = padStart(d.minutes().toString(), 2, "0");
return `${h}:${m}`; return `${h}:${m}`;
} else { } else {
return "00:01"; return "00:01";

View File

@ -1,4 +1,4 @@
import _ from "lodash"; import { times } from "lodash";
import { Dictionary, TaggedResource } from "farmbot"; import { Dictionary, TaggedResource } from "farmbot";
import { Week, DAYS } from "./bulk_scheduler/interfaces"; import { Week, DAYS } from "./bulk_scheduler/interfaces";
import { generateReducer } from "../redux/generate_reducer"; import { generateReducer } from "../redux/generate_reducer";
@ -28,7 +28,7 @@ function newWeek() {
function newState(): RegimenState { function newState(): RegimenState {
return { return {
dailyOffsetMs: 300000, dailyOffsetMs: 300000,
weeks: _.times(10, newWeek), weeks: times(10, newWeek),
selectedSequenceUUID: undefined, selectedSequenceUUID: undefined,
currentRegimen: undefined currentRegimen: undefined
}; };

View File

@ -20,8 +20,8 @@ import {
randomColor, determineInstalledOsVersion, randomColor, determineInstalledOsVersion,
shouldDisplay as shouldDisplayFunc shouldDisplay as shouldDisplayFunc
} from "../util"; } from "../util";
import _ from "lodash";
import { resourceUsageList } from "../resources/in_use"; import { resourceUsageList } from "../resources/in_use";
import { groupBy, chain } from "lodash";
export function mapStateToProps(props: Everything): Props { export function mapStateToProps(props: Everything): Props {
const { resources, dispatch, bot } = props; const { resources, dispatch, bot } = props;
@ -81,9 +81,9 @@ function generateCalendar(regimen: TaggedRegimen,
dispatch: Function): CalendarRow[] { dispatch: Function): CalendarRow[] {
const mapper = createRows(index, dispatch, regimen); const mapper = createRows(index, dispatch, regimen);
const rows = regimen.body.regimen_items.map(mapper); const rows = regimen.body.regimen_items.map(mapper);
const dict = _.groupBy(rows, "day"); const dict = groupBy(rows, "day");
const makeRows = (day: string): CalendarRow => ({ day: day, items: dict[day] }); const makeRows = (day: string): CalendarRow => ({ day: day, items: dict[day] });
const days = _.chain(dict) const days = chain(dict)
.keys() .keys()
.map(x => parseInt(x)) .map(x => parseInt(x))
.sort((a, b) => a - b) .sort((a, b) => a - b)
@ -92,7 +92,7 @@ function generateCalendar(regimen: TaggedRegimen,
return days return days
.map(makeRows) .map(makeRows)
.map((x) => { .map((x) => {
x.items = _.chain(x.items).sortBy(SORT_KEY).value(); x.items = chain(x.items).sortBy(SORT_KEY).value();
return x; return x;
}); });
} }

View File

@ -13,10 +13,10 @@ import {
fakeSequence, fakeSequence,
fakePlant fakePlant
} from "../../__test_support__/fake_state/resources"; } from "../../__test_support__/fake_state/resources";
import * as _ from "lodash";
import { resourceReducer } from "../reducer"; import { resourceReducer } from "../reducer";
import { emptyState } from "../reducer"; import { emptyState } from "../reducer";
import { resourceReady, newTaggedResource } from "../../sync/actions"; import { resourceReady, newTaggedResource } from "../../sync/actions";
import { chain } from "lodash";
// import { Actions } from "../../constants"; // import { Actions } from "../../constants";
const TOOL_ID = 99; const TOOL_ID = 99;
@ -77,7 +77,7 @@ describe("selectAllLogs", () => {
it("stays truthful to its name by finding all logs", () => { it("stays truthful to its name by finding all logs", () => {
const results = Selector.selectAllLogs(fakeIndex); const results = Selector.selectAllLogs(fakeIndex);
expect(results.length).toBeGreaterThan(0); expect(results.length).toBeGreaterThan(0);
const kinds = _.chain(results).map("kind").uniq().value(); const kinds = chain(results).map("kind").uniq().value();
expect(kinds.length).toEqual(1); expect(kinds.length).toEqual(1);
expect(kinds[0]).toEqual("Log"); expect(kinds[0]).toEqual("Log");
}); });

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import { ResourceIndex } from "./interfaces"; import { ResourceIndex } from "./interfaces";
import { import {
ResourceName, ResourceName,
@ -26,6 +25,7 @@ import { findAllById } from "./selectors_by_id";
import { findPoints, selectAllPoints, selectAllActivePoints } from "./selectors_by_kind"; import { findPoints, selectAllPoints, selectAllActivePoints } from "./selectors_by_kind";
import { assertUuid } from "./util"; import { assertUuid } from "./util";
import { joinKindAndId } from "./reducer_support"; import { joinKindAndId } from "./reducer_support";
import { chain } from "lodash";
export * from "./selectors_by_id"; export * from "./selectors_by_id";
export * from "./selectors_by_kind"; export * from "./selectors_by_kind";
@ -57,7 +57,7 @@ export let findId = (index: ResourceIndex, kind: ResourceName, id: number) => {
export let isKind = (name: ResourceName) => (tr: TaggedResource) => tr.kind === name; export let isKind = (name: ResourceName) => (tr: TaggedResource) => tr.kind === name;
export function groupPointsByType(index: ResourceIndex) { export function groupPointsByType(index: ResourceIndex) {
return _.chain(selectAllActivePoints(index)) return chain(selectAllActivePoints(index))
// If this fails to compile.... // If this fails to compile....
.tap(x => x[0].body.pointer_type) .tap(x => x[0].body.pointer_type)
// ... this line must be updated: // ... this line must be updated:

View File

@ -16,8 +16,7 @@ import {
TaggedToolSlotPointer, TaggedToolSlotPointer,
} from "farmbot"; } from "farmbot";
import { ResourceIndex } from "./interfaces"; import { ResourceIndex } from "./interfaces";
import { isNumber } from "lodash"; import { isNumber, find } from "lodash";
import _ from "lodash";
import { joinKindAndId } from "./reducer_support"; import { joinKindAndId } from "./reducer_support";
import { findAll } from "./find_all"; import { findAll } from "./find_all";
@ -84,7 +83,7 @@ export let findSlotByToolId = (index: ResourceIndex, tool_id: number) => {
const every = Object const every = Object
.keys(index.references) .keys(index.references)
.map(x => index.references[x]); .map(x => index.references[x]);
const tts = _.find(every, query); const tts = find(every, query);
if (tts && !isNumber(tts) && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) { if (tts && !isNumber(tts) && isTaggedToolSlotPointer(tts) && sanityCheck(tts)) {
return tts; return tts;
} else { } else {

View File

@ -1,7 +1,7 @@
import { ResourceName } from "farmbot"; import { ResourceName } from "farmbot";
import { Dictionary } from "farmbot/dist"; import { Dictionary } from "farmbot/dist";
import { betterCompact } from "../util"; import { betterCompact } from "../util";
import _ from "lodash"; import { isArray } from "lodash";
import { ResourceIndex } from "./interfaces"; import { ResourceIndex } from "./interfaces";
import { joinKindAndId } from "./reducer_support"; import { joinKindAndId } from "./reducer_support";
@ -11,12 +11,12 @@ export function generateUuid(id: number | undefined, kind: ResourceName) {
} }
export function arrayWrap<T>(input: T | (T[])): T[] { export function arrayWrap<T>(input: T | (T[])): T[] {
return _.isArray(input) ? input : [input]; return isArray(input) ? input : [input];
} }
/** For when you have an array that is guaranteed to have a length of 1 */ /** For when you have an array that is guaranteed to have a length of 1 */
export function arrayUnwrap<T>(input: T | T[]): T { export function arrayUnwrap<T>(input: T | T[]): T {
return _.isArray(input) ? input[0] : input; return isArray(input) ? input[0] : input;
} }
export function entries<T>(input: Dictionary<T | undefined>): T[] { export function entries<T>(input: Dictionary<T | undefined>): T[] {

View File

@ -7,9 +7,8 @@ import { betterCompact } from "../../util";
import { TaggedTool, TaggedPoint } from "farmbot"; import { TaggedTool, TaggedPoint } from "farmbot";
import { DropDownItem } from "../../ui"; import { DropDownItem } from "../../ui";
import { Vector3 } from "farmbot/dist"; import { Vector3 } from "farmbot/dist";
import _ from "lodash";
import { t } from "i18next"; import { t } from "i18next";
import { capitalize } from "lodash"; import { capitalize, chain } from "lodash";
import { joinKindAndId } from "../../resources/reducer_support"; import { joinKindAndId } from "../../resources/reducer_support";
import { Point } from "farmbot/dist/resources/api_resources"; import { Point } from "farmbot/dist/resources/api_resources";
@ -71,7 +70,7 @@ export function locationFormList(resources: ResourceIndex,
.map(({ tool, location }) => formatTools(tool, location)) .map(({ tool, location }) => formatTools(tool, location))
.filter(x => parseInt("" + x.value) > 0); .filter(x => parseInt("" + x.value) > 0);
const group = maybeGroup(!!displayGroups); const group = maybeGroup(!!displayGroups);
return _.chain(heading("Tool")) return chain(heading("Tool"))
.concat(toolDDI) .concat(toolDDI)
.concat(group(everyPointDDI("Tool"))) .concat(group(everyPointDDI("Tool")))
.concat(group(everyPointDDI("ToolSlot"))) .concat(group(everyPointDDI("ToolSlot")))

View File

@ -20,7 +20,6 @@ import {
celery2DropDown, celery2DropDown,
BoxLed, BoxLed,
} from "../pin_and_peripheral_support"; } from "../pin_and_peripheral_support";
import * as _ from "lodash";
import { import {
fakePeripheral, fakePeripheral,
fakeSensor, fakeSensor,
@ -32,6 +31,7 @@ import {
} from "farmbot"; } from "farmbot";
import { StepParams } from "../../interfaces"; import { StepParams } from "../../interfaces";
import { Actions } from "../../../constants"; import { Actions } from "../../../constants";
import { chain } from "lodash";
describe("Pin and Peripheral support files", () => { describe("Pin and Peripheral support files", () => {
const newIndex = () => { const newIndex = () => {
@ -61,7 +61,7 @@ describe("Pin and Peripheral support files", () => {
.toBe(PIN_RANGE.length + 1); // 54 pins plus the header. .toBe(PIN_RANGE.length + 1); // 54 pins plus the header.
expect(pinDropdowns(n => n)[0]).toBe(PIN_HEADING); expect(pinDropdowns(n => n)[0]).toBe(PIN_HEADING);
// Grab all uniq heading IDs- we expect only 1. // Grab all uniq heading IDs- we expect only 1.
const values = _.chain(pinDropdowns(n => n)) const values = chain(pinDropdowns(n => n))
.tail() .tail()
.map((x: DropDownItem) => x.headingId) .map((x: DropDownItem) => x.headingId)
.uniq() .uniq()

View File

@ -16,13 +16,13 @@ import { TileSendMessage } from "./tile_send_message";
import { TileWritePin } from "./tile_write_pin"; import { TileWritePin } from "./tile_write_pin";
import { TileExecuteScript } from "./tile_execute_script"; import { TileExecuteScript } from "./tile_execute_script";
import { TileTakePhoto } from "./tile_take_photo"; import { TileTakePhoto } from "./tile_take_photo";
import _ from "lodash";
import { overwrite } from "../../api/crud"; import { overwrite } from "../../api/crud";
import { TileFindHome } from "./tile_find_home"; import { TileFindHome } from "./tile_find_home";
import { t } from "i18next"; import { t } from "i18next";
import { MarkAs } from "./mark_as"; import { MarkAs } from "./mark_as";
import { TileUnknown } from "./tile_unknown"; import { TileUnknown } from "./tile_unknown";
import { forceSetStepTag } from "../../resources/sequence_tagging"; import { forceSetStepTag } from "../../resources/sequence_tagging";
import { compact, assign } from "lodash";
interface MoveParams { interface MoveParams {
step: Step; step: Step;
@ -41,7 +41,7 @@ export function move({ step, sequence, to, from }: MoveParams) {
} else { } else {
seq.body.splice(to, 0, defensiveClone(copy)); seq.body.splice(to, 0, defensiveClone(copy));
delete seq.body[from]; delete seq.body[from];
seq.body = _.compact(seq.body); seq.body = compact(seq.body);
} }
return overwrite(sequence, next.body); return overwrite(sequence, next.body);
} }
@ -76,7 +76,7 @@ export function remove(props: RemoveParams) {
const update = defensiveClone(original); const update = defensiveClone(original);
update.body.body = (update.body.body || []); update.body.body = (update.body.body || []);
delete update.body.body[index]; delete update.body.body[index];
update.body.body = _.compact(update.body.body); update.body.body = compact(update.body.body);
dispatch(overwrite(original, update.body)); dispatch(overwrite(original, update.body));
} }
} }
@ -93,7 +93,7 @@ export function updateStep(props: StepInputProps) {
if (isNumeric) { if (isNumeric) {
numericNonsense(val, stepCopy, field); numericNonsense(val, stepCopy, field);
} else { } else {
_.assign(stepCopy.args, { [field]: val }); assign(stepCopy.args, { [field]: val });
} }
seqCopy.body[index] = stepCopy; seqCopy.body[index] = stepCopy;
@ -122,7 +122,7 @@ function numericNonsense(val: string, copy: CeleryNode, field: LegalArgString) {
const parsedNumber = FLOAT_NUMERIC_FIELDS.includes(field) const parsedNumber = FLOAT_NUMERIC_FIELDS.includes(field)
? parseFloat(val) ? parseFloat(val)
: parseInt(val, 10); : parseInt(val, 10);
return _.assign(copy.args, { [field]: parsedNumber }); return assign(copy.args, { [field]: parsedNumber });
} }
export function renderCeleryNode(props: StepParams) { export function renderCeleryNode(props: StepParams) {

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import * as React from "react"; import * as React from "react";
import { StepParams } from "../interfaces"; import { StepParams } from "../interfaces";
import { t } from "i18next"; import { t } from "i18next";
@ -17,6 +16,7 @@ import {
addOrEditVarDeclaration, declarationList addOrEditVarDeclaration, declarationList
} from "../locals_list/declaration_support"; } from "../locals_list/declaration_support";
import { AllowedDeclaration } from "../locals_list/locals_list_support"; import { AllowedDeclaration } from "../locals_list/locals_list_support";
import { isNumber } from "lodash";
/** Replaces the execute step body with a new array of declarations. */ /** Replaces the execute step body with a new array of declarations. */
const assignVariable = (props: ExecBlockParams) => const assignVariable = (props: ExecBlockParams) =>
@ -72,7 +72,7 @@ export class RefactoredExecuteBlock
step: currentStep, step: currentStep,
index: index, index: index,
executor: (step: Execute) => { executor: (step: Execute) => {
if (_.isNumber(input.value)) { if (isNumber(input.value)) {
step.args.sequence_id = input.value; step.args.sequence_id = input.value;
const sequenceUuid = findSequenceById(resources, input.value).uuid; const sequenceUuid = findSequenceById(resources, input.value).uuid;
step.body = declarationList(resources.sequenceMetas[sequenceUuid]); step.body = declarationList(resources.sequenceMetas[sequenceUuid]);

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import * as React from "react"; import * as React from "react";
import { IfParams, LHSOptions, operatorOptions } from "./index"; import { IfParams, LHSOptions, operatorOptions } from "./index";
import { t } from "i18next"; import { t } from "i18next";
@ -14,6 +13,7 @@ import {
import { ALLOWED_OPS } from "farmbot/dist"; import { ALLOWED_OPS } from "farmbot/dist";
import { updateLhs } from "./update_lhs"; import { updateLhs } from "./update_lhs";
import { displayLhs } from "./display_lhs"; import { displayLhs } from "./display_lhs";
import { isString } from "lodash";
const IS_UNDEFINED: ALLOWED_OPS = "is_undefined"; const IS_UNDEFINED: ALLOWED_OPS = "is_undefined";
const label_ops: Record<ALLOWED_OPS, string> = { const label_ops: Record<ALLOWED_OPS, string> = {
@ -42,7 +42,7 @@ export function If_(props: IfParams) {
const seqCopy = defensiveClone(sequence).body; const seqCopy = defensiveClone(sequence).body;
const val = e.value; const val = e.value;
seqCopy.body = seqCopy.body || []; seqCopy.body = seqCopy.body || [];
if (_.isString(val)) { if (isString(val)) {
stepCopy.args[field] = val; stepCopy.args[field] = val;
} }
seqCopy.body[index] = stepCopy; seqCopy.body[index] = stepCopy;

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import * as React from "react"; import * as React from "react";
import { t } from "i18next"; import { t } from "i18next";
import { DropDownItem, NULL_CHOICE } from "../../../ui/index"; import { DropDownItem, NULL_CHOICE } from "../../../ui/index";
@ -18,6 +17,7 @@ import {
sensorsAsDropDowns, peripheralsAsDropDowns, pinDropdowns sensorsAsDropDowns, peripheralsAsDropDowns, pinDropdowns
} from "../pin_and_peripheral_support"; } from "../pin_and_peripheral_support";
import { ShouldDisplay, Feature } from "../../../devices/interfaces"; import { ShouldDisplay, Feature } from "../../../devices/interfaces";
import { isNumber, isString } from "lodash";
export interface IfParams { export interface IfParams {
currentSequence: TaggedSequence; currentSequence: TaggedSequence;
@ -60,7 +60,7 @@ export function seqDropDown(i: ResourceIndex) {
selectAllSequences(i) selectAllSequences(i)
.map(function (x) { .map(function (x) {
const { body } = x; const { body } = x;
if (_.isNumber(body.id)) { if (isNumber(body.id)) {
results.push({ label: body.name, value: body.id }); results.push({ label: body.name, value: body.id });
} }
}); });
@ -72,7 +72,7 @@ export function initialValue(input: Execute | Nothing, index: ResourceIndex) {
case "execute": case "execute":
const id = input.args.sequence_id; const id = input.args.sequence_id;
const seq = findSequenceById(index, id).body; const seq = findSequenceById(index, id).body;
if (_.isNumber(seq.id)) { if (isNumber(seq.id)) {
return { label: seq.name, value: seq.id }; return { label: seq.name, value: seq.id };
} else { } else {
throw new Error("Failed seq id type assertion."); throw new Error("Failed seq id type assertion.");
@ -134,7 +134,7 @@ export let IfBlockDropDownHandler = (props: IfParams,
} else { } else {
const value = (block.kind === "execute") && block.args.sequence_id; const value = (block.kind === "execute") && block.args.sequence_id;
const label = value && findSequenceById(props.resources, value).body.name; const label = value && findSequenceById(props.resources, value).body.name;
if (_.isNumber(value) && _.isString(label)) { if (isNumber(value) && isString(label)) {
return { label, value }; return { label, value };
} else { } else {
throw new Error("Failed type assertion"); throw new Error("Failed type assertion");
@ -151,7 +151,7 @@ export let IfBlockDropDownHandler = (props: IfParams,
} }
function onChange(e: DropDownItem) { function onChange(e: DropDownItem) {
if (e.value && _.isNumber(e.value)) { if (e.value && isNumber(e.value)) {
const v = e.value; const v = e.value;
overwriteStep({ kind: "execute", args: { sequence_id: v } }); overwriteStep({ kind: "execute", args: { sequence_id: v } });
} else { } else {

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import * as React from "react"; import * as React from "react";
import { t } from "i18next"; import { t } from "i18next";
import { Component } from "react"; import { Component } from "react";
@ -41,6 +40,7 @@ import {
} from "../../resources/sequence_meta"; } from "../../resources/sequence_meta";
import { LocationForm } from "../locals_list/location_form"; import { LocationForm } from "../locals_list/location_form";
import { AllowedDeclaration } from "../locals_list/locals_list_support"; import { AllowedDeclaration } from "../locals_list/locals_list_support";
import { merge, some } from "lodash";
/** Union of all types found in a move_abs "args" attribute. */ /** Union of all types found in a move_abs "args" attribute. */
export type LocationData = MoveAbsolute["args"]["location"]; export type LocationData = MoveAbsolute["args"]["location"];
@ -114,7 +114,7 @@ export class TileMoveAbsolute extends Component<StepParams, MoveAbsState> {
(e: React.SyntheticEvent<HTMLInputElement>) => { (e: React.SyntheticEvent<HTMLInputElement>) => {
const num = parseFloat(e.currentTarget.value); const num = parseFloat(e.currentTarget.value);
const update = { [place]: { args: { [axis]: num } } }; const update = { [place]: { args: { [axis]: num } } };
this.updateArgs(_.merge({}, this.args, update)); this.updateArgs(merge({}, this.args, update));
} }
/** Determine if location conflicts with bot settings. */ /** Determine if location conflicts with bot settings. */
@ -231,7 +231,7 @@ export class TileMoveAbsolute extends Component<StepParams, MoveAbsState> {
dispatch={dispatch} dispatch={dispatch}
index={index} index={index}
confirmStepDeletion={this.props.confirmStepDeletion}> confirmStepDeletion={this.props.confirmStepDeletion}>
{_.some(this.settingConflicts) && {some(this.settingConflicts) &&
<StepWarning <StepWarning
warning={this.settingConflictWarning} warning={this.settingConflictWarning}
conflicts={this.settingConflicts} />} conflicts={this.settingConflicts} />}

View File

@ -1,9 +1,9 @@
import { t } from "i18next"; import { t } from "i18next";
import { editStep } from "../../api/crud"; import { editStep } from "../../api/crud";
import _ from "lodash";
import { WritePin, SequenceBodyItem } from "farmbot"; import { WritePin, SequenceBodyItem } from "farmbot";
import { DropDownItem } from "../../ui/index"; import { DropDownItem } from "../../ui/index";
import { StepParams } from "../interfaces"; import { StepParams } from "../interfaces";
import { isNumber } from "lodash";
export const PIN_MODES = [ export const PIN_MODES = [
{ value: 1, label: t("Analog") }, { value: 1, label: t("Analog") },
@ -42,7 +42,7 @@ export function setPinMode(
step: currentStep, step: currentStep,
index: index, index: index,
executor: (step: WritePin) => { executor: (step: WritePin) => {
if (_.isNumber(x.value)) { if (isNumber(x.value)) {
step.args.pin_mode = x.value; step.args.pin_mode = x.value;
} else { } else {
throw new Error("Numbers only in pin_mode."); throw new Error("Numbers only in pin_mode.");
@ -58,7 +58,7 @@ export function setPinValue(
step: currentStep, step: currentStep,
index: index, index: index,
executor: (step: WritePin) => { executor: (step: WritePin) => {
if (_.isNumber(x.value)) { if (isNumber(x.value)) {
step.args.pin_value = x.value; step.args.pin_value = x.value;
} else { } else {
throw new Error("Numbers only in pin_value."); throw new Error("Numbers only in pin_value.");

View File

@ -3,7 +3,6 @@ import { FBSelect, DropDownItem, Row, Col } from "../../ui/index";
import { t } from "i18next"; import { t } from "i18next";
import { StepInputBox } from "../inputs/step_input_box"; import { StepInputBox } from "../inputs/step_input_box";
import { SendMessage, TaggedSequence } from "farmbot"; import { SendMessage, TaggedSequence } from "farmbot";
import _ from "lodash";
import { StepParams, ChannelName } from "../interfaces"; import { StepParams, ChannelName } from "../interfaces";
import { ResourceIndex } from "../../resources/interfaces"; import { ResourceIndex } from "../../resources/interfaces";
import { editStep } from "../../api/crud"; import { editStep } from "../../api/crud";
@ -15,6 +14,7 @@ import {
MESSAGE_STATUSES_DDI MESSAGE_STATUSES_DDI
} from "./tile_send_message_support"; } from "./tile_send_message_support";
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index"; import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
import { isString } from "lodash";
export function TileSendMessage(props: StepParams) { export function TileSendMessage(props: StepParams) {
if (props.currentStep.kind === "send_message") { if (props.currentStep.kind === "send_message") {
@ -84,7 +84,7 @@ export class RefactoredSendMessage
step: this.step, step: this.step,
index: this.index, index: this.index,
executor: (step: SendMessage) => { executor: (step: SendMessage) => {
if (_.isString(x.value)) { if (isString(x.value)) {
step.args.message_type = x.value; step.args.message_type = x.value;
} else { } else {
throw new Error("Strings only in send_message."); throw new Error("Strings only in send_message.");

View File

@ -36,10 +36,8 @@ export const newTaggedResource = <T extends TR>(kind: T["kind"],
}); });
}; };
console.log("Yes, HMR works on refresh");
function fail(e: Error) { function fail(e: Error) {
console.log("WEE OOO"); console.error("DATA SYNC ERROR!!");
Session.clear(); Session.clear();
throw e; throw e;
} }

View File

@ -1,6 +1,5 @@
import { Everything } from "../interfaces"; import { Everything } from "../interfaces";
import { Props } from "./interfaces"; import { Props } from "./interfaces";
import _ from "lodash";
import { import {
selectAllToolSlotPointers, selectAllToolSlotPointers,
selectAllTools, selectAllTools,
@ -13,6 +12,7 @@ import { edit } from "../api/crud";
import { DropDownItem, NULL_CHOICE } from "../ui"; import { DropDownItem, NULL_CHOICE } from "../ui";
import { validBotLocationData } from "../util"; import { validBotLocationData } from "../util";
import { TaggedTool, TaggedToolSlotPointer } from "farmbot"; import { TaggedTool, TaggedToolSlotPointer } from "farmbot";
import { chain, isNumber, noop } from "lodash";
export function mapStateToProps(props: Everything): Props { export function mapStateToProps(props: Everything): Props {
const toolSlots = selectAllToolSlotPointers(props.resources.index); const toolSlots = selectAllToolSlotPointers(props.resources.index);
@ -23,17 +23,17 @@ export function mapStateToProps(props: Everything): Props {
/** Returns all tools in an <FBSelect /> compatible format. */ /** Returns all tools in an <FBSelect /> compatible format. */
const getToolOptions = () => { const getToolOptions = () => {
return _.chain(tools) return chain(tools)
.map(tool => ({ .map(tool => ({
label: tool.body.name || "untitled", label: tool.body.name || "untitled",
value: (tool.body.id as number) value: (tool.body.id as number)
})) }))
.filter(ddi => _.isNumber(ddi.value) && ddi.value > 0) .filter(ddi => isNumber(ddi.value) && ddi.value > 0)
.compact() .compact()
.value(); .value();
}; };
const activeTools = _.chain(toolSlots).map(x => x.body.tool_id).compact().value(); const activeTools = chain(toolSlots).map(x => x.body.tool_id).compact().value();
const isActive = const isActive =
(t: TaggedTool) => !!(t.body.id && activeTools.includes(t.body.id)); (t: TaggedTool) => !!(t.body.id && activeTools.includes(t.body.id));
@ -72,7 +72,7 @@ export function mapStateToProps(props: Everything): Props {
getToolByToolSlotUUID, getToolByToolSlotUUID,
changeToolSlot, changeToolSlot,
isActive, isActive,
dispatch: _.noop, dispatch: noop,
botPosition, botPosition,
}; };

View File

@ -1,7 +1,7 @@
import { Dictionary } from "farmbot"; import { Dictionary } from "farmbot";
import { t } from "i18next"; import { t } from "i18next";
import _ from "lodash";
import { Content } from "../constants"; import { Content } from "../constants";
import { capitalize, map } from "lodash";
export interface AxiosErrorResponse { export interface AxiosErrorResponse {
response?: { response?: {
@ -13,7 +13,7 @@ export interface AxiosErrorResponse {
const mapper = (v: string, k: string) => { const mapper = (v: string, k: string) => {
// "Reason: Explanation lorem ipsum dolor ipsum." // "Reason: Explanation lorem ipsum dolor ipsum."
const reason = _.capitalize(("" + k).split("_").join(" ")); const reason = capitalize(("" + k).split("_").join(" "));
const explanation = v.toString(); const explanation = v.toString();
return t(`${reason}: ${explanation}`); return t(`${reason}: ${explanation}`);
@ -23,7 +23,7 @@ const mapper = (v: string, k: string) => {
* pairs returned by the /api/xyz endpoint. */ * pairs returned by the /api/xyz endpoint. */
export function prettyPrintApiErrors(err: AxiosErrorResponse) { export function prettyPrintApiErrors(err: AxiosErrorResponse) {
const errors = safelyFetchErrors(err); const errors = safelyFetchErrors(err);
return _.map(errors, mapper).join(" "); return map(errors, mapper).join(" ");
} }
function safelyFetchErrors(err: AxiosErrorResponse): Dictionary<string> { function safelyFetchErrors(err: AxiosErrorResponse): Dictionary<string> {

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import { parseIntInput } from "./util"; import { parseIntInput } from "./util";
/** The firmware will have an integer overflow if you don't check this one. */ /** The firmware will have an integer overflow if you don't check this one. */
@ -31,7 +30,7 @@ export function clampUnsignedInteger(
const result = parseIntInput(input); const result = parseIntInput(input);
// Clamp to prevent overflow. // Clamp to prevent overflow.
if (_.isNaN(result)) { return { outcome: "malformed", result: undefined }; } if (isNaN(result)) { return { outcome: "malformed", result: undefined }; }
const max = getMaxInputFromIntSize(size); const max = getMaxInputFromIntSize(size);
if (result > max) { return { outcome: "high", result: max }; } if (result > max) { return { outcome: "high", result: max }; }
if (result < MIN_INPUT) { return { outcome: "low", result: MIN_INPUT }; } if (result < MIN_INPUT) { return { outcome: "low", result: MIN_INPUT }; }

View File

@ -1,5 +1,4 @@
import { t } from "i18next"; import { t } from "i18next";
import _ from "lodash";
import { ResourceColor } from "../interfaces"; import { ResourceColor } from "../interfaces";
import { box } from "boxed_value"; import { box } from "boxed_value";
import { import {
@ -10,6 +9,13 @@ import {
ResourceName, ResourceName,
} from "farmbot"; } from "farmbot";
import { BotLocationData } from "../devices/interfaces"; import { BotLocationData } from "../devices/interfaces";
import {
sample,
padStart,
sortBy,
merge,
isNumber
} from "lodash";
export let colors: Array<ResourceColor> = [ export let colors: Array<ResourceColor> = [
"blue", "blue",
@ -24,7 +30,7 @@ export let colors: Array<ResourceColor> = [
/** Picks a color that is compliant with sequence / regimen color codes */ /** Picks a color that is compliant with sequence / regimen color codes */
export function randomColor(): ResourceColor { export function randomColor(): ResourceColor {
return _.sample(colors) as typeof colors[0]; return sample(colors) as typeof colors[0];
} }
export function defensiveClone<T>(target: T): T { export function defensiveClone<T>(target: T): T {
@ -62,7 +68,7 @@ export function fancyDebug<T extends {}>(d: T): T {
.keys(d) .keys(d)
.map(key => [key, (d as Dictionary<string>)[key]]) .map(key => [key, (d as Dictionary<string>)[key]])
.map((x) => { .map((x) => {
const key = _.padStart(x[0], 20, " "); const key = padStart(x[0], 20, " ");
const val = (JSON.stringify(x[1]) || "Nothing").slice(0, 52); const val = (JSON.stringify(x[1]) || "Nothing").slice(0, 52);
return `${key} => ${val}`; return `${key} => ${val}`;
@ -82,10 +88,10 @@ 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 { export function isUndefined(x: object | undefined): x is undefined {
return _.isUndefined(x); return isUndefined(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.
*/ */
export function betterCompact<T>(input: (T | undefined)[]): T[] { export function betterCompact<T>(input: (T | undefined)[]): T[] {
@ -96,11 +102,11 @@ export function betterCompact<T>(input: (T | undefined)[]): T[] {
/** Sorts a list of tagged resources. Unsaved resource get put on the end. */ /** Sorts a list of tagged resources. Unsaved resource get put on the end. */
export function sortResourcesById<T extends TaggedResource>(input: T[]): T[] { export function sortResourcesById<T extends TaggedResource>(input: T[]): T[] {
return _.sortBy(input, (x) => x.body.id || Infinity); return sortBy(input, (x) => x.body.id || Infinity);
} }
/** /**
* Light wrapper around _.merge() to prevent common type errors / mistakes. * Light wrapper around merge() to prevent common type errors / mistakes.
* *
* NOTE: If you rely solely on `betterMerge()` to combine array-bearing * NOTE: If you rely solely on `betterMerge()` to combine array-bearing
* CeleryScript nodes, the API will reject them because they contain * CeleryScript nodes, the API will reject them because they contain
@ -108,7 +114,7 @@ export function sortResourcesById<T extends TaggedResource>(input: T[]): T[] {
* safety reasons. * safety reasons.
*/ */
export function betterMerge<T, U>(target: T, update: U): T & U { export function betterMerge<T, U>(target: T, update: U): T & U {
return _.merge({}, target, update); return merge({}, target, update);
} }
/** Like parseFloat, but allows you to control fallback value instead of /** Like parseFloat, but allows you to control fallback value instead of
@ -117,7 +123,7 @@ export function betterParseNum(num: string | undefined,
fallback: number): number { fallback: number): number {
try { try {
const maybe = JSON.parse("" + num); const maybe = JSON.parse("" + num);
if (_.isNumber(maybe) && !_.isNaN(maybe)) { if (isNumber(maybe) && !isNaN(maybe)) {
return maybe; return maybe;
} }
} catch (_) { } catch (_) {

View File

@ -29,7 +29,7 @@
"@types/enzyme": "3.1.15", "@types/enzyme": "3.1.15",
"@types/fastclick": "1.0.28", "@types/fastclick": "1.0.28",
"@types/jest": "23.3.12", "@types/jest": "23.3.12",
"@types/lodash": "4.14.119", "@types/lodash": "4.14.120",
"@types/markdown-it": "0.0.7", "@types/markdown-it": "0.0.7",
"@types/moxios": "0.4.8", "@types/moxios": "0.4.8",
"@types/node": "10.12.18", "@types/node": "10.12.18",
@ -101,9 +101,6 @@
"SHORT_REVISION": "--------" "SHORT_REVISION": "--------"
} }
}, },
"moduleNameMapper": {
"^.*\\.scss$": "<rootDir>/frontend/__test_support__/stub.ts"
},
"setupFiles": [ "setupFiles": [
"./frontend/__test_support__/setup_enzyme.js", "./frontend/__test_support__/setup_enzyme.js",
"./frontend/__test_support__/localstorage.js", "./frontend/__test_support__/localstorage.js",

View File

@ -6,8 +6,8 @@
"es2017.object" "es2017.object"
], ],
"allowJs": true, "allowJs": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false, "allowUnreachableCode": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"jsx": "react", "jsx": "react",
"module": "esnext", "module": "esnext",