commit
e205214ba4
|
@ -1315,20 +1315,46 @@ ul {
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-resource-step {
|
.update-resource-step {
|
||||||
.custom-meta-field {
|
.update-resource-step-resource {
|
||||||
position: relative;
|
margin-bottom: 1rem;
|
||||||
.fa-undo {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.65rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
color: $medium_light_gray;
|
|
||||||
&:hover {
|
|
||||||
color: $dark_gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.update-resource-pair {
|
.update-resource-pair {
|
||||||
margin-top: 1rem;
|
margin-top: 0;
|
||||||
|
margin-right: -2rem;
|
||||||
|
div[class*=col-] {
|
||||||
|
padding: 0;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.custom-meta-field {
|
||||||
|
position: relative;
|
||||||
|
input {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
.fa-undo {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.65rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
color: $medium_light_gray;
|
||||||
|
&:hover {
|
||||||
|
color: $dark_gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.custom-field-warning {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
i,
|
||||||
|
p {
|
||||||
|
display: inline;
|
||||||
|
cursor: default !important;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
color: $darkest_red;
|
||||||
|
}
|
||||||
|
.did-you-mean {
|
||||||
|
cursor: pointer !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
jest.mock("../../redux/store", () => ({ store: jest.fn() }));
|
||||||
|
|
||||||
import { botReducer, initialState } from "../reducer";
|
import { botReducer, initialState } from "../reducer";
|
||||||
import { Actions } from "../../constants";
|
import { Actions } from "../../constants";
|
||||||
import { ControlPanelState, BotState } from "../interfaces";
|
import { ControlPanelState, BotState } from "../interfaces";
|
||||||
|
|
|
@ -43,16 +43,6 @@ describe("<BoardType/>", () => {
|
||||||
expect(wrapper.text()).toContain("Farmduino");
|
expect(wrapper.text()).toContain("Farmduino");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets sending status", () => {
|
|
||||||
const wrapper = mount<BoardType>(<BoardType {...fakeProps()} />);
|
|
||||||
expect(wrapper.state().sending).toBeFalsy();
|
|
||||||
const p = fakeProps();
|
|
||||||
p.sourceFbosConfig = () => ({ value: true, consistent: false });
|
|
||||||
wrapper.setProps(p);
|
|
||||||
wrapper.mount();
|
|
||||||
expect(wrapper.state().sending).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls updateConfig", () => {
|
it("calls updateConfig", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = mount<BoardType>(<BoardType {...p} />);
|
const wrapper = mount<BoardType>(<BoardType {...p} />);
|
||||||
|
|
|
@ -13,17 +13,7 @@ import { Highlight } from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
import { DevSettings } from "../../../account/dev/dev_support";
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
interface BoardTypeState { sending: boolean }
|
export class BoardType extends React.Component<BoardTypeProps, {}> {
|
||||||
|
|
||||||
export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
|
||||||
state = {
|
|
||||||
sending: this.sending
|
|
||||||
};
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps() {
|
|
||||||
this.setState({ sending: this.sending });
|
|
||||||
}
|
|
||||||
|
|
||||||
get sending() {
|
get sending() {
|
||||||
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
||||||
}
|
}
|
||||||
|
@ -39,15 +29,14 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||||
if (selectedItem && isFwHardwareValue(firmware_hardware)) {
|
if (selectedItem && isFwHardwareValue(firmware_hardware)) {
|
||||||
info(t("Sending firmware configuration..."), t("Sending"));
|
info(t("Sending firmware configuration..."), t("Sending"));
|
||||||
this.props.dispatch(updateConfig({ firmware_hardware }));
|
this.props.dispatch(updateConfig({ firmware_hardware }));
|
||||||
this.setState({ sending: true });
|
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FirmwareSelection = () =>
|
FirmwareSelection = () =>
|
||||||
<FBSelect
|
<FBSelect
|
||||||
key={this.props.firmwareHardware}
|
key={this.props.firmwareHardware + "" + this.sending}
|
||||||
extraClass={this.state.sending ? "dim" : ""}
|
extraClass={this.sending ? "dim" : ""}
|
||||||
list={getFirmwareChoices()}
|
list={getFirmwareChoices()}
|
||||||
selectedItem={this.selectedBoard}
|
selectedItem={this.selectedBoard}
|
||||||
onChange={this.sendOffConfig} />
|
onChange={this.sendOffConfig} />
|
||||||
|
|
|
@ -545,7 +545,10 @@ describe("<RepeatForm />", () => {
|
||||||
const fakeProps = (): RepeatFormProps => ({
|
const fakeProps = (): RepeatFormProps => ({
|
||||||
isRegimen: false,
|
isRegimen: false,
|
||||||
fieldGet: jest.fn(key =>
|
fieldGet: jest.fn(key =>
|
||||||
"" + ({ endDate: "2017-07-26" } as FarmEventViewModel)[key]),
|
"" + ({
|
||||||
|
endDate: "2017-07-26", endTime: "08:57",
|
||||||
|
startDate: "2017-07-25", startTime: "08:57"
|
||||||
|
} as FarmEventViewModel)[key]),
|
||||||
fieldSet: jest.fn(),
|
fieldSet: jest.fn(),
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
import * as React from "react";
|
jest.mock("../../../../../api/crud", () => ({
|
||||||
import { ImageFilterMenu, ImageFilterMenuProps } from "../image_filter_menu";
|
edit: jest.fn(),
|
||||||
import { shallow, mount } from "enzyme";
|
save: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fakeWebAppConfig,
|
fakeWebAppConfig,
|
||||||
} from "../../../../../__test_support__/fake_state/resources";
|
} from "../../../../../__test_support__/fake_state/resources";
|
||||||
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
|
||||||
import { setWebAppConfigValue } from "../../../../../config_storage/actions";
|
|
||||||
import {
|
|
||||||
fakeTimeSettings,
|
|
||||||
} from "../../../../../__test_support__/fake_time_settings";
|
|
||||||
|
|
||||||
const mockConfig = fakeWebAppConfig();
|
const mockConfig = fakeWebAppConfig();
|
||||||
jest.mock("../../../../../resources/selectors", () => ({
|
jest.mock("../../../../../resources/selectors", () => ({
|
||||||
getWebAppConfig: () => mockConfig,
|
getWebAppConfig: () => mockConfig,
|
||||||
assertUuid: jest.fn(),
|
assertUuid: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../../../config_storage/actions", () => ({
|
import * as React from "react";
|
||||||
setWebAppConfigValue: jest.fn(),
|
import { ImageFilterMenu, ImageFilterMenuProps } from "../image_filter_menu";
|
||||||
}));
|
import { shallow, mount } from "enzyme";
|
||||||
|
|
||||||
|
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
|
import {
|
||||||
|
fakeTimeSettings,
|
||||||
|
} from "../../../../../__test_support__/fake_time_settings";
|
||||||
|
import { edit, save } from "../../../../../api/crud";
|
||||||
|
import { fakeState } from "../../../../../__test_support__/fake_state";
|
||||||
|
import {
|
||||||
|
buildResourceIndex,
|
||||||
|
} from "../../../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
describe("<ImageFilterMenu />", () => {
|
describe("<ImageFilterMenu />", () => {
|
||||||
mockConfig.body.photo_filter_begin = "";
|
mockConfig.body.photo_filter_begin = "";
|
||||||
|
@ -45,13 +51,19 @@ describe("<ImageFilterMenu />", () => {
|
||||||
["endDate", "photo_filter_end", 2],
|
["endDate", "photo_filter_end", 2],
|
||||||
])("sets filter: %s", (filter, key, i) => {
|
])("sets filter: %s", (filter, key, i) => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
const config = fakeWebAppConfig();
|
||||||
|
state.resources = buildResourceIndex([config]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||||
currentTarget: { value: "2001-01-03" }
|
currentTarget: { value: "2001-01-03" }
|
||||||
});
|
});
|
||||||
expect(wrapper.instance().state[filter]).toEqual("2001-01-03");
|
expect(wrapper.instance().state[filter]).toEqual("2001-01-03");
|
||||||
expect(setWebAppConfigValue)
|
expect(edit).toHaveBeenCalledWith(config, {
|
||||||
.toHaveBeenCalledWith(key, "2001-01-03T00:00:00.000Z");
|
[key]: "2001-01-03T00:00:00.000Z"
|
||||||
|
});
|
||||||
|
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each<[
|
it.each<[
|
||||||
|
@ -61,14 +73,64 @@ describe("<ImageFilterMenu />", () => {
|
||||||
["endTime", "photo_filter_end", 3],
|
["endTime", "photo_filter_end", 3],
|
||||||
])("sets filter: %s", (filter, key, i) => {
|
])("sets filter: %s", (filter, key, i) => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
const config = fakeWebAppConfig();
|
||||||
|
state.resources = buildResourceIndex([config]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||||
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||||
currentTarget: { value: "05:00" }
|
currentTarget: { value: "05:00" }
|
||||||
});
|
});
|
||||||
expect(wrapper.instance().state[filter]).toEqual("05:00");
|
expect(wrapper.instance().state[filter]).toEqual("05:00");
|
||||||
expect(setWebAppConfigValue)
|
expect(edit).toHaveBeenCalledWith(config, {
|
||||||
.toHaveBeenCalledWith(key, "2001-01-03T05:00:00.000Z");
|
[key]: "2001-01-03T05:00:00.000Z"
|
||||||
|
});
|
||||||
|
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each<[
|
||||||
|
"beginDate" | "endDate",
|
||||||
|
"photo_filter_begin" | "photo_filter_end",
|
||||||
|
number
|
||||||
|
]>([
|
||||||
|
["beginDate", "photo_filter_begin", 0],
|
||||||
|
["endDate", "photo_filter_end", 2],
|
||||||
|
])("unsets filter: %s", (filter, key, i) => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
const config = fakeWebAppConfig();
|
||||||
|
state.resources = buildResourceIndex([config]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
|
wrapper.setState({ beginDate: "2001-01-03", endDate: "2001-01-03" });
|
||||||
|
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||||
|
currentTarget: { value: "" }
|
||||||
|
});
|
||||||
|
expect(wrapper.instance().state[filter]).toEqual(undefined);
|
||||||
|
// tslint:disable-next-line:no-null-keyword
|
||||||
|
expect(edit).toHaveBeenCalledWith(config, { [key]: null });
|
||||||
|
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each<[
|
||||||
|
"beginTime" | "endTime", number
|
||||||
|
]>([
|
||||||
|
["beginTime", 1],
|
||||||
|
["endTime", 3],
|
||||||
|
])("doesn't set filter: %s", (filter, i) => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
const config = fakeWebAppConfig();
|
||||||
|
state.resources = buildResourceIndex([config]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
|
wrapper.find("BlurableInput").at(i).simulate("commit", {
|
||||||
|
currentTarget: { value: "05:00" }
|
||||||
|
});
|
||||||
|
expect(wrapper.instance().state[filter]).toEqual("05:00");
|
||||||
|
expect(edit).not.toHaveBeenCalled();
|
||||||
|
expect(save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("loads values from config", () => {
|
it("loads values from config", () => {
|
||||||
|
@ -83,14 +145,34 @@ describe("<ImageFilterMenu />", () => {
|
||||||
|
|
||||||
it("changes slider", () => {
|
it("changes slider", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
const config = fakeWebAppConfig();
|
||||||
|
state.resources = buildResourceIndex([config]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
|
p.getConfigValue = () => undefined;
|
||||||
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
||||||
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
wrapper.instance().sliderChange(1);
|
wrapper.instance().sliderChange(1);
|
||||||
expect(wrapper.instance().state.slider).toEqual(1);
|
expect(wrapper.instance().state.slider).toEqual(1);
|
||||||
expect(setWebAppConfigValue)
|
expect(edit).toHaveBeenCalledWith(config, {
|
||||||
.toHaveBeenCalledWith("photo_filter_begin", "2001-01-02T00:00:00.000Z");
|
photo_filter_begin: "2001-01-02T00:00:00.000Z",
|
||||||
expect(setWebAppConfigValue)
|
photo_filter_end: "2001-01-03T00:00:00.000Z",
|
||||||
.toHaveBeenCalledWith("photo_filter_end", "2001-01-03T00:00:00.000Z");
|
});
|
||||||
|
expect(save).toHaveBeenCalledWith(config.uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't update config", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const state = fakeState();
|
||||||
|
state.resources = buildResourceIndex([]);
|
||||||
|
p.dispatch = jest.fn(x => x(jest.fn(), () => state));
|
||||||
|
p.getConfigValue = () => 1;
|
||||||
|
p.imageAgeInfo.newestDate = "2001-01-03T05:00:00.000Z";
|
||||||
|
const wrapper = shallow<ImageFilterMenu>(<ImageFilterMenu {...p} />);
|
||||||
|
wrapper.instance().sliderChange(1);
|
||||||
|
expect(wrapper.instance().state.slider).toEqual(1);
|
||||||
|
expect(edit).not.toHaveBeenCalled();
|
||||||
|
expect(save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays slider labels", () => {
|
it("displays slider labels", () => {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BlurableInput } from "../../../../ui/index";
|
import { BlurableInput } from "../../../../ui/index";
|
||||||
import { offsetTime } from "../../../farm_events/edit_fe_form";
|
import { offsetTime } from "../../../farm_events/edit_fe_form";
|
||||||
import {
|
import { GetWebAppConfigValue } from "../../../../config_storage/actions";
|
||||||
setWebAppConfigValue, GetWebAppConfigValue,
|
|
||||||
} from "../../../../config_storage/actions";
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {
|
import {
|
||||||
formatDate, formatTime,
|
formatDate, formatTime,
|
||||||
|
@ -11,8 +9,13 @@ import {
|
||||||
import { Slider } from "@blueprintjs/core";
|
import { Slider } from "@blueprintjs/core";
|
||||||
import { t } from "../../../../i18next_wrapper";
|
import { t } from "../../../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../../../interfaces";
|
import { TimeSettings } from "../../../../interfaces";
|
||||||
|
import { StringConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
|
import { GetState } from "../../../../redux/interfaces";
|
||||||
|
import { getWebAppConfig } from "../../../../resources/getters";
|
||||||
|
import { edit, save } from "../../../../api/crud";
|
||||||
|
import { isString, isUndefined } from "lodash";
|
||||||
|
|
||||||
interface ImageFilterMenuState {
|
interface FullImageFilterMenuState {
|
||||||
beginDate: string | undefined;
|
beginDate: string | undefined;
|
||||||
beginTime: string | undefined;
|
beginTime: string | undefined;
|
||||||
endDate: string | undefined;
|
endDate: string | undefined;
|
||||||
|
@ -20,6 +23,8 @@ interface ImageFilterMenuState {
|
||||||
slider: number;
|
slider: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageFilterMenuState = Partial<FullImageFilterMenuState>;
|
||||||
|
|
||||||
export interface ImageFilterMenuProps {
|
export interface ImageFilterMenuProps {
|
||||||
timeSettings: TimeSettings;
|
timeSettings: TimeSettings;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
@ -28,26 +33,48 @@ export interface ImageFilterMenuProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImageFilterMenu
|
export class ImageFilterMenu
|
||||||
extends React.Component<ImageFilterMenuProps, Partial<ImageFilterMenuState>> {
|
extends React.Component<ImageFilterMenuProps, ImageFilterMenuState> {
|
||||||
constructor(props: ImageFilterMenuProps) {
|
state: ImageFilterMenuState = {};
|
||||||
super(props);
|
|
||||||
this.state = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
componentDidMount() {
|
||||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
|
||||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||||
this.setState({
|
if (isString(beginDatetime) || isUndefined(beginDatetime)) {
|
||||||
slider: toOldest + 1 - (beginDatetime
|
this.updateSliderState(beginDatetime);
|
||||||
? Math.abs(moment(beginDatetime.toString())
|
}
|
||||||
.diff(moment(newestDate).clone(), "days")) : 0)
|
|
||||||
});
|
|
||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps() {
|
updateSliderState = (begin: string | undefined) => {
|
||||||
this.updateState();
|
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||||
}
|
const offset = begin ? Math.abs(moment(begin.toString())
|
||||||
|
.diff(moment(newestDate).clone(), "days")) : 0;
|
||||||
|
this.setState({ slider: toOldest + 1 - offset });
|
||||||
|
};
|
||||||
|
|
||||||
|
setValues = (update: StringValueUpdate) => {
|
||||||
|
Object.entries(update).map(([key, value]) => {
|
||||||
|
switch (key) {
|
||||||
|
case "photo_filter_begin":
|
||||||
|
this.updateSliderState(value);
|
||||||
|
value
|
||||||
|
? this.setState({
|
||||||
|
beginDate: formatDate(value.toString(), this.props.timeSettings),
|
||||||
|
beginTime: formatTime(value.toString(), this.props.timeSettings),
|
||||||
|
})
|
||||||
|
: this.setState({ beginDate: undefined, beginTime: undefined });
|
||||||
|
break;
|
||||||
|
case "photo_filter_end":
|
||||||
|
value
|
||||||
|
? this.setState({
|
||||||
|
endDate: formatDate(value.toString(), this.props.timeSettings),
|
||||||
|
endTime: formatTime(value.toString(), this.props.timeSettings),
|
||||||
|
})
|
||||||
|
: this.setState({ endDate: undefined, endTime: undefined });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.props.dispatch(setWebAppConfigValues(update));
|
||||||
|
};
|
||||||
|
|
||||||
updateState = () => {
|
updateState = () => {
|
||||||
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
const beginDatetime = this.props.getConfigValue("photo_filter_begin");
|
||||||
|
@ -70,27 +97,27 @@ export class ImageFilterMenu
|
||||||
const input = e.currentTarget.value;
|
const input = e.currentTarget.value;
|
||||||
this.setState({ [datetime]: input });
|
this.setState({ [datetime]: input });
|
||||||
const { beginDate, beginTime, endDate, endTime } = this.state;
|
const { beginDate, beginTime, endDate, endTime } = this.state;
|
||||||
const { dispatch, timeSettings } = this.props;
|
const { timeSettings } = this.props;
|
||||||
let value = undefined;
|
let value = undefined;
|
||||||
switch (datetime) {
|
switch (datetime) {
|
||||||
case "beginDate":
|
case "beginDate":
|
||||||
value = offsetTime(input, beginTime || "00:00", timeSettings);
|
value = offsetTime(input, beginTime || "00:00", timeSettings);
|
||||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
this.setValues({ photo_filter_begin: value });
|
||||||
break;
|
break;
|
||||||
case "beginTime":
|
case "beginTime":
|
||||||
if (beginDate) {
|
if (beginDate) {
|
||||||
value = offsetTime(beginDate, input, timeSettings);
|
value = offsetTime(beginDate, input, timeSettings);
|
||||||
dispatch(setWebAppConfigValue("photo_filter_begin", value));
|
this.setValues({ photo_filter_begin: value });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "endDate":
|
case "endDate":
|
||||||
value = offsetTime(input, endTime || "00:00", timeSettings);
|
value = offsetTime(input, endTime || "00:00", timeSettings);
|
||||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
this.setValues({ photo_filter_end: value });
|
||||||
break;
|
break;
|
||||||
case "endTime":
|
case "endTime":
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
value = offsetTime(endDate, input, timeSettings);
|
value = offsetTime(endDate, input, timeSettings);
|
||||||
dispatch(setWebAppConfigValue("photo_filter_end", value));
|
this.setValues({ photo_filter_end: value });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -100,13 +127,12 @@ export class ImageFilterMenu
|
||||||
sliderChange = (slider: number) => {
|
sliderChange = (slider: number) => {
|
||||||
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
const { newestDate, toOldest } = this.props.imageAgeInfo;
|
||||||
this.setState({ slider });
|
this.setState({ slider });
|
||||||
const { dispatch, timeSettings } = this.props;
|
const { timeSettings } = this.props;
|
||||||
const calcDate = (day: number) =>
|
const calcDate = (day: number) =>
|
||||||
moment(newestDate).subtract(toOldest - day, "days").toISOString();
|
moment(newestDate).subtract(toOldest - day, "days").toISOString();
|
||||||
const begin = offsetTime(calcDate(slider - 1), "00:00", timeSettings);
|
const begin = offsetTime(calcDate(slider - 1), "00:00", timeSettings);
|
||||||
const end = offsetTime(calcDate(slider), "00:00", timeSettings);
|
const end = offsetTime(calcDate(slider), "00:00", timeSettings);
|
||||||
dispatch(setWebAppConfigValue("photo_filter_begin", begin));
|
this.setValues({ photo_filter_begin: begin, photo_filter_end: end });
|
||||||
dispatch(setWebAppConfigValue("photo_filter_end", end));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLabel = (day: number) => {
|
renderLabel = (day: number) => {
|
||||||
|
@ -191,3 +217,14 @@ export class ImageFilterMenu
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StringValueUpdate = Partial<Record<StringConfigKey, string | undefined>>;
|
||||||
|
|
||||||
|
const setWebAppConfigValues = (update: StringValueUpdate) =>
|
||||||
|
(dispatch: Function, getState: GetState) => {
|
||||||
|
const webAppConfig = getWebAppConfig(getState().resources.index);
|
||||||
|
if (webAppConfig) {
|
||||||
|
dispatch(edit(webAppConfig, update));
|
||||||
|
dispatch(save(webAppConfig.uuid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -73,7 +73,7 @@ export class SpreadCircle extends
|
||||||
React.Component<SpreadCircleProps, SpreadCircleState> {
|
React.Component<SpreadCircleProps, SpreadCircleState> {
|
||||||
state: SpreadCircleState = { spread: undefined };
|
state: SpreadCircleState = { spread: undefined };
|
||||||
|
|
||||||
UNSAFE_componentWillMount = () => {
|
componentDidMount = () => {
|
||||||
cachedCrop(this.props.plant.body.openfarm_slug)
|
cachedCrop(this.props.plant.body.openfarm_slug)
|
||||||
.then(({ spread }) => this.setState({ spread }));
|
.then(({ spread }) => this.setState({ spread }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,12 +78,12 @@ describe("<CreatePoints />", () => {
|
||||||
it("updates specific fields", () => {
|
it("updates specific fields", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.drawnPoint = FAKE_POINT;
|
p.drawnPoint = FAKE_POINT;
|
||||||
const i = new CreatePoints(p);
|
const wrapper = mount<CreatePoints>(<CreatePoints {...p} />);
|
||||||
i.updateValue("color")(inputEvent("cheerful hue"));
|
wrapper.instance().updateValue("color")(inputEvent("cheerful hue"));
|
||||||
expect(i.props.drawnPoint).toBeTruthy();
|
expect(wrapper.instance().props.drawnPoint).toBeTruthy();
|
||||||
const expected = cloneDeep(FAKE_POINT);
|
const expected = cloneDeep(FAKE_POINT);
|
||||||
expected.color = "cheerful hue";
|
expected.color = "cheerful hue";
|
||||||
expect(i.props.dispatch).toHaveBeenCalledWith({
|
expect(wrapper.instance().props.dispatch).toHaveBeenCalledWith({
|
||||||
type: "SET_DRAWN_POINT_DATA",
|
type: "SET_DRAWN_POINT_DATA",
|
||||||
payload: expected,
|
payload: expected,
|
||||||
});
|
});
|
||||||
|
|
|
@ -122,7 +122,7 @@ export class RawCreatePoints
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
componentDidMount() {
|
||||||
this.loadDefaultPoint();
|
this.loadDefaultPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,11 +73,11 @@ export const AdditionalWeedProperties = (props: AdditionalWeedPropertiesProps) =
|
||||||
case "type": return <div key={key}
|
case "type": return <div key={key}
|
||||||
className={`meta-${key}-not-displayed`} />;
|
className={`meta-${key}-not-displayed`} />;
|
||||||
case "created_by":
|
case "created_by":
|
||||||
return <ListItem name={t("Source")}>
|
return <ListItem name={t("Source")} key={key}>
|
||||||
{SOURCE_LOOKUP()[value || ""] || t("unknown")}
|
{SOURCE_LOOKUP()[value || ""] || t("unknown")}
|
||||||
</ListItem>;
|
</ListItem>;
|
||||||
case "removal_method":
|
case "removal_method":
|
||||||
return <ListItem name={t("Removal method")}>
|
return <ListItem name={t("Removal method")} key={key}>
|
||||||
<div className="weed-removal-method-section">
|
<div className="weed-removal-method-section">
|
||||||
{REMOVAL_METHODS.map(method =>
|
{REMOVAL_METHODS.map(method =>
|
||||||
<div className={"weed-removal-method"} key={method}>
|
<div className={"weed-removal-method"} key={method}>
|
||||||
|
@ -93,7 +93,7 @@ export const AdditionalWeedProperties = (props: AdditionalWeedPropertiesProps) =
|
||||||
</div>
|
</div>
|
||||||
</ListItem>;
|
</ListItem>;
|
||||||
default:
|
default:
|
||||||
return <ListItem name={key}>
|
return <ListItem name={key} key={key}>
|
||||||
{value || ""}
|
{value || ""}
|
||||||
</ListItem>;
|
</ListItem>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ export class RawFarmwarePage extends React.Component<FarmwareProps, {}> {
|
||||||
return isBotOnline(this.props.syncStatus, this.props.botToMqttStatus);
|
return isBotOnline(this.props.syncStatus, this.props.botToMqttStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
componentDidMount() {
|
||||||
if (window.innerWidth > 450) {
|
if (window.innerWidth > 450) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: Actions.SELECT_FARMWARE,
|
type: Actions.SELECT_FARMWARE,
|
||||||
|
|
|
@ -26,6 +26,8 @@ jest.mock("../../api", () => ({
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock("../laptop_splash", () => ({ LaptopSplash: () => <div /> }));
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { FrontPage, setField, PartialFormEvent } from "../front_page";
|
import { FrontPage, setField, PartialFormEvent } from "../front_page";
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import { LaptopSplash } from "../laptop_splash";
|
||||||
|
|
||||||
|
describe("<LaptopSplash />", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const wrapper = shallow(<LaptopSplash className={""} />);
|
||||||
|
expect(wrapper.find("video").length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -28,7 +28,7 @@ export const RegimenBackButton = (props: RegimenBackButtonProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RawRegimens extends React.Component<Props, {}> {
|
export class RawRegimens extends React.Component<Props, {}> {
|
||||||
UNSAFE_componentWillMount() {
|
componentDidMount() {
|
||||||
if (!this.props.current) { setActiveRegimenByName(); }
|
if (!this.props.current) { setActiveRegimenByName(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const SequenceBackButton = (props: SequenceBackButtonProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class RawSequences extends React.Component<Props, {}> {
|
export class RawSequences extends React.Component<Props, {}> {
|
||||||
UNSAFE_componentWillMount() {
|
componentDidMount() {
|
||||||
if (!this.props.sequence) { setActiveSequenceByName(); }
|
if (!this.props.sequence) { setActiveSequenceByName(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export function ThenElse(props: ThenElseParams) {
|
||||||
onChange, selectedItem, calledSequenceVariableData, assignVariable
|
onChange, selectedItem, calledSequenceVariableData, assignVariable
|
||||||
} = IfBlockDropDownHandler(props);
|
} = IfBlockDropDownHandler(props);
|
||||||
const { body } = props.currentStep.args[props.thenElseKey];
|
const { body } = props.currentStep.args[props.thenElseKey];
|
||||||
return <Col xs={6}>
|
return <Col xs={12} lg={6}>
|
||||||
<div className="execute-row">
|
<div className="execute-row">
|
||||||
<label>{props.thenElseKey === "_then" ? t("Then Execute") : t("Else Execute")}
|
<label>{props.thenElseKey === "_then" ? t("Then Execute") : t("Else Execute")}
|
||||||
</label>
|
</label>
|
||||||
|
@ -22,7 +22,7 @@ export function ThenElse(props: ThenElseParams) {
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
selectedItem={selectedItem()} />
|
selectedItem={selectedItem()} />
|
||||||
{!!calledSequenceVariableData &&
|
{!!calledSequenceVariableData &&
|
||||||
<Col xs={6}>
|
<Col xs={12}>
|
||||||
<LocalsList
|
<LocalsList
|
||||||
bodyVariables={body}
|
bodyVariables={body}
|
||||||
variableData={calledSequenceVariableData}
|
variableData={calledSequenceVariableData}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { FieldSelection, isCustomMetaField } from "../field_selection";
|
import {
|
||||||
|
FieldSelection, isCustomMetaField, UPDATE_RESOURCE_DDIS,
|
||||||
|
} from "../field_selection";
|
||||||
import { FieldSelectionProps } from "../interfaces";
|
import { FieldSelectionProps } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
buildResourceIndex,
|
buildResourceIndex,
|
||||||
} from "../../../../__test_support__/resource_index_builder";
|
} from "../../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
|
|
||||||
describe("<FieldSelection />", () => {
|
describe("<FieldSelection />", () => {
|
||||||
const fakeProps = (): FieldSelectionProps => ({
|
const fakeProps = (): FieldSelectionProps => ({
|
||||||
resource: { kind: "nothing", args: {} },
|
resource: { kind: "nothing", args: {} },
|
||||||
|
@ -35,8 +39,8 @@ describe("<FieldSelection />", () => {
|
||||||
const wrapper = mount(<FieldSelection {...p} />);
|
const wrapper = mount(<FieldSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Plant stage", value: "plant_stage" },
|
DDI.PLANT_STAGE,
|
||||||
{ label: "Custom Meta Field", value: "" },
|
DDI.CUSTOM_META_FIELD,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("field");
|
expect(wrapper.text()).toContain("field");
|
||||||
expect(wrapper.text()).toContain("Select one");
|
expect(wrapper.text()).toContain("Select one");
|
||||||
|
@ -80,8 +84,8 @@ describe("<FieldSelection />", () => {
|
||||||
const wrapper = mount(<FieldSelection {...p} />);
|
const wrapper = mount(<FieldSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Status", value: "plant_stage" },
|
DDI.STATUS,
|
||||||
{ label: "Custom Meta Field", value: "" },
|
DDI.CUSTOM_META_FIELD,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("field");
|
expect(wrapper.text()).toContain("field");
|
||||||
expect(wrapper.text()).toContain("Status");
|
expect(wrapper.text()).toContain("Status");
|
||||||
|
@ -98,8 +102,8 @@ describe("<FieldSelection />", () => {
|
||||||
const wrapper = mount(<FieldSelection {...p} />);
|
const wrapper = mount(<FieldSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Weed status", value: "plant_stage" },
|
DDI.WEED_STATUS,
|
||||||
{ label: "Custom Meta Field", value: "" },
|
DDI.CUSTOM_META_FIELD,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("field");
|
expect(wrapper.text()).toContain("field");
|
||||||
expect(wrapper.text()).toContain("Weed status");
|
expect(wrapper.text()).toContain("Weed status");
|
||||||
|
@ -116,11 +120,10 @@ describe("<FieldSelection />", () => {
|
||||||
const wrapper = mount(<FieldSelection {...p} />);
|
const wrapper = mount(<FieldSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Status", value: "plant_stage" },
|
DDI.CUSTOM_META_FIELD,
|
||||||
{ label: "Custom Meta Field", value: "" },
|
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("field");
|
expect(wrapper.text()).toContain("field");
|
||||||
expect(wrapper.text()).toContain("Status");
|
expect(wrapper.text()).toContain("Point status");
|
||||||
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
expect(wrapper.find(".reset-custom-field").length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -149,8 +152,8 @@ describe("<FieldSelection />", () => {
|
||||||
const wrapper = mount(<FieldSelection {...p} />);
|
const wrapper = mount(<FieldSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Mounted Tool", value: "mounted_tool_id" },
|
DDI.MOUNTED_TOOL,
|
||||||
{ label: "Custom Meta Field", value: "" },
|
DDI.CUSTOM_META_FIELD,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("field");
|
expect(wrapper.text()).toContain("field");
|
||||||
expect(wrapper.text()).toContain("Mounted Tool");
|
expect(wrapper.text()).toContain("Mounted Tool");
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import { CustomFieldWarning } from "../field_warning";
|
||||||
|
import { CustomFieldWarningProps } from "../interfaces";
|
||||||
|
|
||||||
|
describe("<CustomFieldWarning />", () => {
|
||||||
|
const fakeProps = (): CustomFieldWarningProps => ({
|
||||||
|
resource: { kind: "nothing", args: {} },
|
||||||
|
field: "",
|
||||||
|
update: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't display warning", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.field = "";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("invalid field");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays warning", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.field = "nope";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("meta");
|
||||||
|
wrapper.find(".did-you-mean").simulate("click");
|
||||||
|
expect(p.update).toHaveBeenCalledWith({ field: "meta.nope" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays warning: Device resource", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.resource = {
|
||||||
|
kind: "resource", args: { resource_type: "Device", resource_id: 1 }
|
||||||
|
};
|
||||||
|
p.field = "x";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("meta");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays warning: GenericPointer resource", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.resource = {
|
||||||
|
kind: "resource", args: { resource_type: "GenericPointer", resource_id: 1 }
|
||||||
|
};
|
||||||
|
p.field = "openfarm_slug";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("meta");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't display warning: Plant resource", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.resource = {
|
||||||
|
kind: "resource", args: { resource_type: "Plant", resource_id: 1 }
|
||||||
|
};
|
||||||
|
p.field = "openfarm_slug";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("meta");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays warning: Weed resource", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.resource = {
|
||||||
|
kind: "resource", args: { resource_type: "Weed", resource_id: 1 }
|
||||||
|
};
|
||||||
|
p.field = "openfarm_slug";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("meta");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays warning: identifier", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.resource = { kind: "identifier", args: { label: "var" } };
|
||||||
|
p.field = "mounted_tool_id";
|
||||||
|
const wrapper = mount(<CustomFieldWarning {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("invalid field");
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("meta");
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,6 +15,9 @@ import {
|
||||||
} from "../../../../farm_designer/plants/edit_plant_status";
|
} from "../../../../farm_designer/plants/edit_plant_status";
|
||||||
import { fakeTool } from "../../../../__test_support__/fake_state/resources";
|
import { fakeTool } from "../../../../__test_support__/fake_state/resources";
|
||||||
import { resource_type, Resource } from "farmbot";
|
import { resource_type, Resource } from "farmbot";
|
||||||
|
import { UPDATE_RESOURCE_DDIS } from "../field_selection";
|
||||||
|
|
||||||
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
|
|
||||||
describe("<ValueSelection />", () => {
|
describe("<ValueSelection />", () => {
|
||||||
const fakeProps = (): ValueSelectionProps => ({
|
const fakeProps = (): ValueSelectionProps => ({
|
||||||
|
@ -119,7 +122,7 @@ describe("<ValueSelection />", () => {
|
||||||
const wrapper = mount(<ValueSelection {...p} />);
|
const wrapper = mount(<ValueSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Removed", value: "removed" },
|
DDI.REMOVED,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("as");
|
expect(wrapper.text()).toContain("as");
|
||||||
expect(wrapper.text()).toContain("Removed");
|
expect(wrapper.text()).toContain("Removed");
|
||||||
|
@ -153,7 +156,7 @@ describe("<ValueSelection />", () => {
|
||||||
const wrapper = mount(<ValueSelection {...p} />);
|
const wrapper = mount(<ValueSelection {...p} />);
|
||||||
expect(wrapper.find("FBSelect").length).toEqual(1);
|
expect(wrapper.find("FBSelect").length).toEqual(1);
|
||||||
expect(wrapper.find("FBSelect").props().list).toEqual([
|
expect(wrapper.find("FBSelect").props().list).toEqual([
|
||||||
{ label: "Removed", value: "removed" },
|
DDI.REMOVED,
|
||||||
]);
|
]);
|
||||||
expect(wrapper.text()).toContain("as");
|
expect(wrapper.text()).toContain("as");
|
||||||
expect(wrapper.text()).toContain("Removed");
|
expect(wrapper.text()).toContain("Removed");
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { FieldSelection } from "./field_selection";
|
||||||
import { ValueSelection } from "./value_selection";
|
import { ValueSelection } from "./value_selection";
|
||||||
import { isUndefined } from "lodash";
|
import { isUndefined } from "lodash";
|
||||||
import { NOTHING_SELECTED } from "../../locals_list/handle_select";
|
import { NOTHING_SELECTED } from "../../locals_list/handle_select";
|
||||||
|
import { CustomFieldWarning } from "./field_warning";
|
||||||
|
|
||||||
export class MarkAs extends React.Component<MarkAsProps, MarkAsState> {
|
export class MarkAs extends React.Component<MarkAsProps, MarkAsState> {
|
||||||
state: MarkAsState = {
|
state: MarkAsState = {
|
||||||
|
@ -89,30 +90,35 @@ export class MarkAs extends React.Component<MarkAsProps, MarkAsState> {
|
||||||
confirmStepDeletion={this.props.confirmStepDeletion} />
|
confirmStepDeletion={this.props.confirmStepDeletion} />
|
||||||
<StepContent className={className}>
|
<StepContent className={className}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12}>
|
<Col xs={12} lg={6}>
|
||||||
<ResourceSelection {...commonProps}
|
<ResourceSelection {...commonProps}
|
||||||
sequenceUuid={this.props.currentSequence.uuid}
|
sequenceUuid={this.props.currentSequence.uuid}
|
||||||
updateResource={this.updateResource} />
|
updateResource={this.updateResource} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
{this.state.fieldsAndValues.map((fieldAndValue, index) =>
|
||||||
{this.state.fieldsAndValues.map((fieldAndValue, index) =>
|
<Col xs={12} lg={6} key={index}>
|
||||||
<div className={"update-resource-pair"} key={index}>
|
<div className={"update-resource-pair"}>
|
||||||
<Row>
|
<Col xs={6}>
|
||||||
<Col xs={6}>
|
<FieldSelection {...commonProps}
|
||||||
<FieldSelection {...commonProps}
|
field={fieldAndValue.field}
|
||||||
|
update={this.updateFieldOrValue(index)} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={6}>
|
||||||
|
<ValueSelection {...commonProps}
|
||||||
|
field={fieldAndValue.field}
|
||||||
|
value={fieldAndValue.value}
|
||||||
|
update={this.updateFieldOrValue(index)}
|
||||||
|
add={this.updateFieldOrValue(
|
||||||
|
this.state.fieldsAndValues.length)}
|
||||||
|
commitSelection={this.commitSelection} />
|
||||||
|
</Col>
|
||||||
|
<CustomFieldWarning
|
||||||
|
resource={this.state.resource}
|
||||||
field={fieldAndValue.field}
|
field={fieldAndValue.field}
|
||||||
update={this.updateFieldOrValue(index)} />
|
update={this.updateFieldOrValue(index)} />
|
||||||
</Col>
|
</div>
|
||||||
<Col xs={6}>
|
</Col>)}
|
||||||
<ValueSelection {...commonProps}
|
</Row>
|
||||||
field={fieldAndValue.field}
|
|
||||||
value={fieldAndValue.value}
|
|
||||||
update={this.updateFieldOrValue(index)}
|
|
||||||
add={this.updateFieldOrValue(this.state.fieldsAndValues.length)}
|
|
||||||
commitSelection={this.commitSelection} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>)}
|
|
||||||
</StepContent>
|
</StepContent>
|
||||||
</StepWrapper>;
|
</StepWrapper>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ const KnownFieldSelection = (props: FieldSelectionProps) =>
|
||||||
list={props.resource.kind == "nothing"
|
list={props.resource.kind == "nothing"
|
||||||
? []
|
? []
|
||||||
: fieldList(props.resource)
|
: fieldList(props.resource)
|
||||||
.concat([{ label: t("Custom Meta Field"), value: "" }])}
|
.concat([UPDATE_RESOURCE_DDIS().CUSTOM_META_FIELD])}
|
||||||
onChange={ddi => props.update({
|
onChange={ddi => props.update({
|
||||||
field: "" + ddi.value,
|
field: "" + ddi.value,
|
||||||
value: undefined
|
value: undefined
|
||||||
|
@ -58,18 +58,13 @@ export const isCustomMetaField = (field: string | undefined): boolean =>
|
||||||
!(isUndefined(field) || knownField(field));
|
!(isUndefined(field) || knownField(field));
|
||||||
|
|
||||||
const fieldList = (resource: Resource | Identifier) => {
|
const fieldList = (resource: Resource | Identifier) => {
|
||||||
if (resource.kind == "identifier") {
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
return [{ label: t("Status"), value: "plant_stage" }];
|
if (resource.kind == "identifier") { return [DDI.STATUS]; }
|
||||||
}
|
|
||||||
switch (resource.args.resource_type) {
|
switch (resource.args.resource_type) {
|
||||||
case "Device":
|
case "Device": return [DDI.MOUNTED_TOOL];
|
||||||
return [{ label: t("Mounted Tool"), value: "mounted_tool_id" }];
|
case "Weed": return [DDI.WEED_STATUS];
|
||||||
case "Weed":
|
case "GenericPointer": return [];
|
||||||
return [{ label: t("Weed status"), value: "plant_stage" }];
|
default: return [DDI.PLANT_STAGE];
|
||||||
case "GenericPointer":
|
|
||||||
return [{ label: t("Status"), value: "plant_stage" }];
|
|
||||||
default:
|
|
||||||
return [{ label: t("Plant stage"), value: "plant_stage" }];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,23 +72,27 @@ const getSelectedField = (
|
||||||
resource: Resource | Identifier | Nothing,
|
resource: Resource | Identifier | Nothing,
|
||||||
field: KnownField | undefined,
|
field: KnownField | undefined,
|
||||||
): DropDownItem => {
|
): DropDownItem => {
|
||||||
if (isUndefined(field) || resource.kind == "nothing") {
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
return { label: t("Select one"), value: "" };
|
if (isUndefined(field) || resource.kind == "nothing") { return DDI.SELECT_ONE; }
|
||||||
}
|
if (resource.kind == "identifier") { return DDI.STATUS; }
|
||||||
if (resource.kind == "identifier") {
|
|
||||||
return { label: t("Status"), value: "plant_stage" };
|
|
||||||
}
|
|
||||||
const resourceType = resource.args.resource_type;
|
const resourceType = resource.args.resource_type;
|
||||||
switch (field) {
|
switch (field) {
|
||||||
case KnownField.mounted_tool_id:
|
case KnownField.mounted_tool_id: return DDI.MOUNTED_TOOL;
|
||||||
return { label: t("Mounted Tool"), value: "tool" };
|
|
||||||
case KnownField.plant_stage:
|
case KnownField.plant_stage:
|
||||||
if (resourceType == "Weed") {
|
if (resourceType == "Weed") { return DDI.WEED_STATUS; }
|
||||||
return { label: t("Weed status"), value: "plant_stage" };
|
if (resourceType == "GenericPointer") { return DDI.POINT_STATUS; }
|
||||||
}
|
return DDI.PLANT_STAGE;
|
||||||
if (resourceType == "GenericPointer") {
|
|
||||||
return { label: t("Status"), value: "plant_stage" };
|
|
||||||
}
|
|
||||||
return { label: t("Plant stage"), value: "plant_stage" };
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UPDATE_RESOURCE_DDIS = (): Record<string, DropDownItem> => ({
|
||||||
|
SELECT_ONE: { label: t("Select one"), value: "" },
|
||||||
|
CUSTOM_META_FIELD: { label: t("Custom field"), value: "" },
|
||||||
|
STATUS: { label: t("Status"), value: "plant_stage" },
|
||||||
|
MOUNTED_TOOL: { label: t("Mounted Tool"), value: "mounted_tool_id" },
|
||||||
|
WEED_STATUS: { label: t("Weed status"), value: "plant_stage" },
|
||||||
|
POINT_STATUS: { label: t("Point status"), value: "plant_stage" },
|
||||||
|
PLANT_STAGE: { label: t("Plant stage"), value: "plant_stage" },
|
||||||
|
NONE: { label: t("None"), value: 0 },
|
||||||
|
REMOVED: { label: t("Removed"), value: "removed" },
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Resource, Identifier, Nothing } from "farmbot";
|
||||||
|
import {
|
||||||
|
PlantPointer, ToolSlotPointer, WeedPointer, GenericPointer,
|
||||||
|
DeviceAccountSettings, Point,
|
||||||
|
} from "farmbot/dist/resources/api_resources";
|
||||||
|
import { CustomFieldWarningProps } from "./interfaces";
|
||||||
|
|
||||||
|
export const CustomFieldWarning = (props: CustomFieldWarningProps) =>
|
||||||
|
props.field && !validFields(props.resource).includes(props.field)
|
||||||
|
&& !props.field.includes("meta.")
|
||||||
|
? <div className="custom-field-warning">
|
||||||
|
<i className="fa fa-exclamation-triangle" />
|
||||||
|
<p>
|
||||||
|
{t("Invalid field for resource.")}
|
||||||
|
</p>
|
||||||
|
{!(props.resource.kind == "resource" &&
|
||||||
|
props.resource.args.resource_type == "Device") &&
|
||||||
|
<p className={"did-you-mean"}
|
||||||
|
onClick={() => props.update({
|
||||||
|
field: "meta." + props.field,
|
||||||
|
value: undefined
|
||||||
|
})}>
|
||||||
|
{t("Did you mean meta.{{field}}?", { field: props.field })}
|
||||||
|
</p>}
|
||||||
|
</div>
|
||||||
|
: <div className="custom-field-warning" />;
|
||||||
|
|
||||||
|
const validFields = (resource: Resource | Identifier | Nothing): string[] => {
|
||||||
|
if (resource.kind == "identifier" || resource.kind == "nothing") {
|
||||||
|
return POINT_FIELDS;
|
||||||
|
}
|
||||||
|
switch (resource.args.resource_type) {
|
||||||
|
case "Device": return DEVICE_FIELDS;
|
||||||
|
case "Weed": return WEED_FIELDS;
|
||||||
|
case "GenericPointer": return GENERIC_POINTER_FIELDS;
|
||||||
|
default: return PLANT_FIELDS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type BaseFields = (keyof Point)[];
|
||||||
|
type PlantFields = (keyof PlantPointer)[];
|
||||||
|
type ToolSlotFields = (keyof ToolSlotPointer)[];
|
||||||
|
type GenericPointerFields = (keyof GenericPointer)[];
|
||||||
|
type WeedFields = (keyof WeedPointer)[];
|
||||||
|
type PointFields = (
|
||||||
|
keyof PlantPointer
|
||||||
|
| keyof ToolSlotPointer
|
||||||
|
| keyof GenericPointer
|
||||||
|
| keyof WeedPointer
|
||||||
|
)[];
|
||||||
|
|
||||||
|
const BASE_FIELDS: BaseFields =
|
||||||
|
["name", "pointer_type", "x", "y", "z", "meta"];
|
||||||
|
const PLANT_FIELDS: PlantFields = (BASE_FIELDS as PlantFields)
|
||||||
|
.concat(["openfarm_slug", "plant_stage", "planted_at", "radius"]);
|
||||||
|
const TOOL_SLOT_FIELDS: ToolSlotFields = (BASE_FIELDS as ToolSlotFields)
|
||||||
|
.concat(["tool_id", "pullout_direction", "gantry_mounted"]);
|
||||||
|
const GENERIC_POINTER_FIELDS: GenericPointerFields =
|
||||||
|
(BASE_FIELDS as GenericPointerFields).concat(["radius"]);
|
||||||
|
const WEED_FIELDS: WeedFields = (BASE_FIELDS as PlantFields)
|
||||||
|
.concat(["plant_stage", "radius"]) as WeedFields;
|
||||||
|
const POINT_FIELDS: PointFields = (BASE_FIELDS as PointFields)
|
||||||
|
.concat(PLANT_FIELDS)
|
||||||
|
.concat(TOOL_SLOT_FIELDS)
|
||||||
|
.concat(GENERIC_POINTER_FIELDS)
|
||||||
|
.concat(WEED_FIELDS);
|
||||||
|
const DEVICE_FIELDS: (keyof DeviceAccountSettings)[] =
|
||||||
|
["name", "mounted_tool_id", "ota_hour", "timezone"];
|
|
@ -55,6 +55,12 @@ export interface CustomFieldSelectionProps extends SelectionPropsBase {
|
||||||
update: UpdateFieldOrValue;
|
update: UpdateFieldOrValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomFieldWarningProps {
|
||||||
|
resource: Resource | Identifier | Nothing;
|
||||||
|
field: string | undefined;
|
||||||
|
update: UpdateFieldOrValue;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ValueSelectionProps extends SelectionPropsBase {
|
export interface ValueSelectionProps extends SelectionPropsBase {
|
||||||
field: string | undefined;
|
field: string | undefined;
|
||||||
value: UpdateResourceValue | undefined;
|
value: UpdateResourceValue | undefined;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { formatPoint } from "../../locals_list/location_form_list";
|
||||||
import {
|
import {
|
||||||
maybeFindVariable, SequenceMeta,
|
maybeFindVariable, SequenceMeta,
|
||||||
} from "../../../resources/sequence_meta";
|
} from "../../../resources/sequence_meta";
|
||||||
|
import { UPDATE_RESOURCE_DDIS } from "./field_selection";
|
||||||
|
|
||||||
export const ResourceSelection = (props: ResourceSelectionProps) =>
|
export const ResourceSelection = (props: ResourceSelectionProps) =>
|
||||||
<div className={"update-resource-step-resource"}>
|
<div className={"update-resource-step-resource"}>
|
||||||
|
@ -83,7 +84,7 @@ const getSelectedResource = (
|
||||||
label: resourceVariableLabel(variable),
|
label: resourceVariableLabel(variable),
|
||||||
value: resource.args.label,
|
value: resource.args.label,
|
||||||
};
|
};
|
||||||
case "nothing": return { label: t("Select one"), value: "" };
|
case "nothing": return UPDATE_RESOURCE_DDIS().SELECT_ONE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,9 @@ import { selectAllTools, maybeFindToolById } from "../../../resources/selectors"
|
||||||
import {
|
import {
|
||||||
PLANT_STAGE_LIST, PLANT_STAGE_DDI_LOOKUP,
|
PLANT_STAGE_LIST, PLANT_STAGE_DDI_LOOKUP,
|
||||||
} from "../../../farm_designer/plants/edit_plant_status";
|
} from "../../../farm_designer/plants/edit_plant_status";
|
||||||
import { isCustomMetaField, KnownField, knownField } from "./field_selection";
|
import {
|
||||||
|
isCustomMetaField, KnownField, knownField, UPDATE_RESOURCE_DDIS,
|
||||||
|
} from "./field_selection";
|
||||||
import { DevSettings } from "../../../account/dev/dev_support";
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export const ValueSelection = (props: ValueSelectionProps) =>
|
export const ValueSelection = (props: ValueSelectionProps) =>
|
||||||
|
@ -43,6 +45,7 @@ const KnownValue = (props: ValueSelectionProps) =>
|
||||||
const CustomMetaValue = (props: ValueSelectionProps) =>
|
const CustomMetaValue = (props: ValueSelectionProps) =>
|
||||||
<div className="custom-meta-field">
|
<div className="custom-meta-field">
|
||||||
<BlurableInput type="text" name="value"
|
<BlurableInput type="text" name="value"
|
||||||
|
allowEmpty={true}
|
||||||
value={isUndefined(props.value) ? "" : "" + props.value}
|
value={isUndefined(props.value) ? "" : "" + props.value}
|
||||||
onCommit={e => {
|
onCommit={e => {
|
||||||
props.update({ value: e.currentTarget.value },
|
props.update({ value: e.currentTarget.value },
|
||||||
|
@ -53,33 +56,33 @@ const CustomMetaValue = (props: ValueSelectionProps) =>
|
||||||
const valuesList = (
|
const valuesList = (
|
||||||
resource: Resource | Identifier,
|
resource: Resource | Identifier,
|
||||||
resources: ResourceIndex): DropDownItem[] => {
|
resources: ResourceIndex): DropDownItem[] => {
|
||||||
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
const stepResourceType =
|
const stepResourceType =
|
||||||
resource.kind == "identifier" ? undefined : resource.args.resource_type;
|
resource.kind == "identifier" ? undefined : resource.args.resource_type;
|
||||||
switch (stepResourceType) {
|
switch (stepResourceType) {
|
||||||
case "Device": return [
|
case "Device": return [
|
||||||
{ label: t("None"), value: 0 },
|
DDI.NONE,
|
||||||
...selectAllTools(resources).filter(x => !!x.body.id)
|
...selectAllTools(resources).filter(x => !!x.body.id)
|
||||||
.map(x => ({ toolName: x.body.name, toolId: x.body.id }))
|
.map(x => ({ toolName: x.body.name, toolId: x.body.id }))
|
||||||
.map(({ toolName, toolId }:
|
.map(({ toolName, toolId }:
|
||||||
{ toolName: string | undefined, toolId: number }) =>
|
{ toolName: string | undefined, toolId: number }) =>
|
||||||
({ label: toolName || t("Untitled tool"), value: toolId })),
|
({ label: toolName || t("Untitled tool"), value: toolId })),
|
||||||
];
|
];
|
||||||
case "GenericPointer": return [{ label: t("Removed"), value: "removed" }];
|
case "GenericPointer": return [DDI.REMOVED];
|
||||||
case "Weed": return [{ label: t("Removed"), value: "removed" }];
|
case "Weed": return [DDI.REMOVED];
|
||||||
case "Plant":
|
case "Plant":
|
||||||
default: return PLANT_STAGE_LIST();
|
default: return PLANT_STAGE_LIST();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelectedValue = (props: GetSelectedValueProps): DropDownItem => {
|
const getSelectedValue = (props: GetSelectedValueProps): DropDownItem => {
|
||||||
|
const DDI = UPDATE_RESOURCE_DDIS();
|
||||||
if (isUndefined(props.field) || isUndefined(props.value)
|
if (isUndefined(props.field) || isUndefined(props.value)
|
||||||
|| props.resource.kind == "nothing") {
|
|| props.resource.kind == "nothing") { return DDI.SELECT_ONE; }
|
||||||
return { label: t("Select one"), value: "" };
|
|
||||||
}
|
|
||||||
switch (props.field) {
|
switch (props.field) {
|
||||||
case KnownField.mounted_tool_id:
|
case KnownField.mounted_tool_id:
|
||||||
const toolId = parseInt("" + props.value);
|
const toolId = parseInt("" + props.value);
|
||||||
if (toolId == 0) { return { label: t("None"), value: 0 }; }
|
if (toolId == 0) { return DDI.NONE; }
|
||||||
const tool = maybeFindToolById(props.resourceIndex, toolId);
|
const tool = maybeFindToolById(props.resourceIndex, toolId);
|
||||||
if (!tool) { return { label: t("Unknown tool"), value: toolId }; }
|
if (!tool) { return { label: t("Unknown tool"), value: toolId }; }
|
||||||
return {
|
return {
|
||||||
|
|
22
package.json
22
package.json
|
@ -24,16 +24,16 @@
|
||||||
"author": "farmbot.io",
|
"author": "farmbot.io",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.9.0",
|
"@babel/core": "7.9.6",
|
||||||
"@blueprintjs/core": "3.26.0",
|
"@blueprintjs/core": "3.26.1",
|
||||||
"@blueprintjs/datetime": "3.16.1",
|
"@blueprintjs/datetime": "3.17.0",
|
||||||
"@blueprintjs/select": "3.12.2",
|
"@blueprintjs/select": "3.12.3",
|
||||||
"@types/enzyme": "3.10.5",
|
"@types/enzyme": "3.10.5",
|
||||||
"@types/jest": "25.2.1",
|
"@types/jest": "25.2.1",
|
||||||
"@types/lodash": "4.14.150",
|
"@types/lodash": "4.14.150",
|
||||||
"@types/markdown-it": "10.0.1",
|
"@types/markdown-it": "10.0.1",
|
||||||
"@types/moxios": "0.4.9",
|
"@types/moxios": "0.4.9",
|
||||||
"@types/node": "13.13.4",
|
"@types/node": "13.13.5",
|
||||||
"@types/promise-timeout": "1.3.0",
|
"@types/promise-timeout": "1.3.0",
|
||||||
"@types/react": "16.9.34",
|
"@types/react": "16.9.34",
|
||||||
"@types/react-color": "3.0.1",
|
"@types/react-color": "3.0.1",
|
||||||
|
@ -51,21 +51,21 @@
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"markdown-it": "10.0.0",
|
"markdown-it": "10.0.0",
|
||||||
"markdown-it-emoji": "1.4.0",
|
"markdown-it-emoji": "1.4.0",
|
||||||
"moment": "2.24.0",
|
"moment": "2.25.3",
|
||||||
"moxios": "0.4.0",
|
"moxios": "0.4.0",
|
||||||
"mqtt": "4.0.0",
|
"mqtt": "4.0.0",
|
||||||
"npm": "6.14.4",
|
"npm": "6.14.5",
|
||||||
"parcel-bundler": "1.12.4",
|
"parcel-bundler": "1.12.4",
|
||||||
"promise-timeout": "1.3.0",
|
"promise-timeout": "1.3.0",
|
||||||
"raf": "3.4.1",
|
"raf": "3.4.1",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-addons-test-utils": "15.6.2",
|
"react-addons-test-utils": "15.6.2",
|
||||||
"react-color": "2.18.0",
|
"react-color": "2.18.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-joyride": "2.2.1",
|
"react-joyride": "2.2.1",
|
||||||
"react-redux": "7.2.0",
|
"react-redux": "7.2.0",
|
||||||
"react-test-renderer": "16.13.1",
|
"react-test-renderer": "16.13.1",
|
||||||
"react-transition-group": "4.3.0",
|
"react-transition-group": "4.4.1",
|
||||||
"redux": "4.0.5",
|
"redux": "4.0.5",
|
||||||
"redux-immutable-state-invariant": "2.1.0",
|
"redux-immutable-state-invariant": "2.1.0",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.3.0",
|
||||||
|
@ -78,8 +78,8 @@
|
||||||
"which": "2.0.2"
|
"which": "2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "25.4.0",
|
"jest": "25.5.4",
|
||||||
"jest-cli": "25.4.0",
|
"jest-cli": "25.5.4",
|
||||||
"jest-junit": "10.0.0",
|
"jest-junit": "10.0.0",
|
||||||
"jest-skipped-reporter": "0.0.5",
|
"jest-skipped-reporter": "0.0.5",
|
||||||
"jshint": "2.11.0",
|
"jshint": "2.11.0",
|
||||||
|
|
Loading…
Reference in New Issue