misc updates
parent
49fdced812
commit
9bd98aca1e
|
@ -10,6 +10,7 @@ import { Move } from "./move/move";
|
||||||
import { BooleanSetting } from "../session_keys";
|
import { BooleanSetting } from "../session_keys";
|
||||||
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
||||||
import { isBotOnline } from "../devices/must_be_online";
|
import { isBotOnline } from "../devices/must_be_online";
|
||||||
|
import { hasSensors } from "../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
/** Controls page. */
|
/** Controls page. */
|
||||||
export class RawControls extends React.Component<Props, {}> {
|
export class RawControls extends React.Component<Props, {}> {
|
||||||
|
@ -24,7 +25,8 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get hideSensors() {
|
get hideSensors() {
|
||||||
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors);
|
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors)
|
||||||
|
|| !hasSensors(this.props.firmwareHardware);
|
||||||
}
|
}
|
||||||
|
|
||||||
move = () => <Move
|
move = () => <Move
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { AxisInputBoxGroup } from "../axis_input_box_group";
|
||||||
import { GetWebAppBool } from "./interfaces";
|
import { GetWebAppBool } from "./interfaces";
|
||||||
import { BooleanSetting } from "../../session_keys";
|
import { BooleanSetting } from "../../session_keys";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
|
import { hasEncoders } from "../../devices/components/firmware_hardware_support";
|
||||||
import { FirmwareHardware } from "farmbot";
|
import { FirmwareHardware } from "farmbot";
|
||||||
|
|
||||||
export interface BotPositionRowsProps {
|
export interface BotPositionRowsProps {
|
||||||
|
@ -34,12 +34,12 @@ export const BotPositionRows = (props: BotPositionRowsProps) => {
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.position}
|
position={locationData.position}
|
||||||
label={t("Motor Coordinates (mm)")} />
|
label={t("Motor Coordinates (mm)")} />
|
||||||
{!isExpressBoard(props.firmwareHardware) &&
|
{hasEncoders(props.firmwareHardware) &&
|
||||||
getValue(BooleanSetting.scaled_encoders) &&
|
getValue(BooleanSetting.scaled_encoders) &&
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.scaled_encoders}
|
position={locationData.scaled_encoders}
|
||||||
label={t("Scaled Encoder (mm)")} />}
|
label={t("Scaled Encoder (mm)")} />}
|
||||||
{!isExpressBoard(props.firmwareHardware) &&
|
{hasEncoders(props.firmwareHardware) &&
|
||||||
getValue(BooleanSetting.raw_encoders) &&
|
getValue(BooleanSetting.raw_encoders) &&
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.raw_encoders}
|
position={locationData.raw_encoders}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
import { DevSettings } from "../../account/dev/dev_support";
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { FirmwareHardware } from "farmbot";
|
import { FirmwareHardware } from "farmbot";
|
||||||
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
|
import { hasEncoders } from "../../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
export const moveWidgetSetting =
|
export const moveWidgetSetting =
|
||||||
(toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
|
(toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
|
||||||
|
@ -36,7 +36,7 @@ export const MoveWidgetSettingsMenu = (
|
||||||
<Setting label={t("Y Axis")} setting={BooleanSetting.y_axis_inverted} />
|
<Setting label={t("Y Axis")} setting={BooleanSetting.y_axis_inverted} />
|
||||||
<Setting label={t("Z Axis")} setting={BooleanSetting.z_axis_inverted} />
|
<Setting label={t("Z Axis")} setting={BooleanSetting.z_axis_inverted} />
|
||||||
|
|
||||||
{!isExpressBoard(firmwareHardware) &&
|
{hasEncoders(firmwareHardware) &&
|
||||||
<div className="display-encoder-data">
|
<div className="display-encoder-data">
|
||||||
<p>{t("Display Encoder Data")}</p>
|
<p>{t("Display Encoder Data")}</p>
|
||||||
<Setting
|
<Setting
|
||||||
|
|
|
@ -88,4 +88,14 @@ describe("<Peripherals />", () => {
|
||||||
clickButton(wrapper, 3, "stock");
|
clickButton(wrapper, 3, "stock");
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds);
|
expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hides stock button", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "none";
|
||||||
|
const wrapper = mount(<Peripherals {...p} />);
|
||||||
|
wrapper.setState({ isEditing: true });
|
||||||
|
const btn = wrapper.find("button").at(3);
|
||||||
|
expect(btn.text().toLowerCase()).toContain("stock");
|
||||||
|
expect(btn.props().hidden).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -108,7 +108,7 @@ export class Peripherals
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
hidden={!isEditing}
|
hidden={!isEditing || this.props.firmwareHardware == "none"}
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.stockPeripherals.map(p =>
|
onClick={() => this.stockPeripherals.map(p =>
|
||||||
|
|
|
@ -72,6 +72,7 @@ describe("<Sensors />", () => {
|
||||||
expect(wrapper.text().toLowerCase()).toContain("stock sensors");
|
expect(wrapper.text().toLowerCase()).toContain("stock sensors");
|
||||||
wrapper.setState({ isEditing: true });
|
wrapper.setState({ isEditing: true });
|
||||||
clickButton(wrapper, 3, "stock sensors");
|
clickButton(wrapper, 3, "stock sensors");
|
||||||
|
expect(wrapper.find("button").at(3).props().hidden).toBeFalsy();
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(2);
|
expect(p.dispatch).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,6 +80,18 @@ describe("<Sensors />", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.firmwareHardware = "express_k10";
|
p.firmwareHardware = "express_k10";
|
||||||
const wrapper = mount(<Sensors {...p} />);
|
const wrapper = mount(<Sensors {...p} />);
|
||||||
expect(wrapper.text().toLowerCase()).not.toContain("stock sensors");
|
const btn = wrapper.find("button").at(3);
|
||||||
|
expect(btn.text().toLowerCase()).toContain("stock");
|
||||||
|
expect(btn.props().hidden).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides stock button", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "none";
|
||||||
|
const wrapper = mount(<Sensors {...p} />);
|
||||||
|
wrapper.setState({ isEditing: true });
|
||||||
|
const btn = wrapper.find("button").at(3);
|
||||||
|
expect(btn.text().toLowerCase()).toContain("stock");
|
||||||
|
expect(btn.props().hidden).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { saveAll, init } from "../../api/crud";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { uniq } from "lodash";
|
import { uniq } from "lodash";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
|
|
||||||
|
|
||||||
export class Sensors extends React.Component<SensorsProps, SensorState> {
|
export class Sensors extends React.Component<SensorsProps, SensorState> {
|
||||||
constructor(props: SensorsProps) {
|
constructor(props: SensorsProps) {
|
||||||
|
@ -80,15 +79,14 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
|
||||||
onClick={() => this.newSensor()}>
|
onClick={() => this.newSensor()}>
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
</button>
|
</button>
|
||||||
{!isExpressBoard(this.props.firmwareHardware) &&
|
<button
|
||||||
<button
|
hidden={!isEditing || this.props.firmwareHardware == "none"}
|
||||||
hidden={!isEditing}
|
className="fb-button green"
|
||||||
className="fb-button green"
|
type="button"
|
||||||
type="button"
|
onClick={this.stockSensors}>
|
||||||
onClick={this.stockSensors}>
|
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
{t("Stock sensors")}
|
||||||
{t("Stock sensors")}
|
</button>
|
||||||
</button>}
|
|
||||||
</WidgetHeader>
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
{this.showPins()}
|
{this.showPins()}
|
||||||
|
|
|
@ -958,3 +958,10 @@
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
&:hover { color: $white; }
|
&:hover { color: $white; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desktop-hide {
|
||||||
|
display: none !important;
|
||||||
|
@media screen and (max-width: 1075px) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -407,6 +407,18 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.load-progress-bar-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 3.2rem;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
.load-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.firmware-setting-export-menu {
|
.firmware-setting-export-menu {
|
||||||
button {
|
button {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
@ -1654,3 +1666,9 @@ textarea:focus {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.read-only-icon {
|
||||||
|
margin: 9px 0px 0px 9px;
|
||||||
|
float: right;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
|
@ -322,6 +322,9 @@
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
&.active {
|
&.active {
|
||||||
border-left: 4px solid $dark_gray;
|
border-left: 4px solid $dark_gray;
|
||||||
|
p {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fa-chevron-down, .fa-chevron-right {
|
.fa-chevron-down, .fa-chevron-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -330,11 +333,11 @@
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
.folder-settings-icon,
|
.folder-settings-icon,
|
||||||
.fa-bars {
|
.fa-arrows-v {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
.fa-bars, .fa-ellipsis-v {
|
.fa-arrows-v, .fa-ellipsis-v {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.fa-ellipsis-v {
|
.fa-ellipsis-v {
|
||||||
|
@ -342,8 +345,14 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 450px) {
|
||||||
|
.fa-arrows-v, .fa-ellipsis-v {
|
||||||
|
display: block;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
.fa-bars, .fa-ellipsis-v {
|
.fa-arrows-v, .fa-ellipsis-v {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,7 +376,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: bold;
|
font-weight: normal;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { clickButton } from "../../../__test_support__/helpers";
|
||||||
import {
|
import {
|
||||||
buildResourceIndex
|
buildResourceIndex
|
||||||
} from "../../../__test_support__/resource_index_builder";
|
} from "../../../__test_support__/resource_index_builder";
|
||||||
|
import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
|
import { Color } from "../../../ui";
|
||||||
|
|
||||||
describe("<HardwareSettings />", () => {
|
describe("<HardwareSettings />", () => {
|
||||||
const fakeProps = (): HardwareSettingsProps => ({
|
const fakeProps = (): HardwareSettingsProps => ({
|
||||||
|
@ -68,4 +70,41 @@ describe("<HardwareSettings />", () => {
|
||||||
const wrapper = shallow(<HardwareSettings {...p} />);
|
const wrapper = shallow(<HardwareSettings {...p} />);
|
||||||
expect(wrapper.html()).toContain("fa-download");
|
expect(wrapper.html()).toContain("fa-download");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows setting load progress", () => {
|
||||||
|
type ConsistencyLookup = Record<keyof FirmwareConfig, boolean>;
|
||||||
|
const consistent: Partial<ConsistencyLookup> =
|
||||||
|
({ id: false, encoder_invert_x: true, encoder_enabled_y: false });
|
||||||
|
const consistencyLookup = consistent as ConsistencyLookup;
|
||||||
|
const p = fakeProps();
|
||||||
|
const fakeConfig: Partial<FirmwareConfig> =
|
||||||
|
({ id: 0, encoder_invert_x: 1, encoder_enabled_y: 0 });
|
||||||
|
p.firmwareConfig = fakeConfig as FirmwareConfig;
|
||||||
|
p.sourceFwConfig = x =>
|
||||||
|
({ value: p.firmwareConfig?.[x], consistent: consistencyLookup[x] });
|
||||||
|
const wrapper = mount(<HardwareSettings {...p} />);
|
||||||
|
const barStyle = wrapper.find(".load-progress-bar").props().style;
|
||||||
|
expect(barStyle?.background).toEqual(Color.white);
|
||||||
|
expect(barStyle?.width).toEqual("50%");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows setting load progress: 0%", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareConfig = fakeFirmwareConfig().body;
|
||||||
|
p.sourceFwConfig = () => ({ value: 0, consistent: false });
|
||||||
|
const wrapper = mount(<HardwareSettings {...p} />);
|
||||||
|
const barStyle = wrapper.find(".load-progress-bar").props().style;
|
||||||
|
expect(barStyle?.width).toEqual("0%");
|
||||||
|
expect(barStyle?.background).toEqual(Color.darkGray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows setting load progress: 100%", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareConfig = fakeFirmwareConfig().body;
|
||||||
|
p.sourceFwConfig = () => ({ value: 0, consistent: true });
|
||||||
|
const wrapper = mount(<HardwareSettings {...p} />);
|
||||||
|
const barStyle = wrapper.find(".load-progress-bar").props().style;
|
||||||
|
expect(barStyle?.width).toEqual("100%");
|
||||||
|
expect(barStyle?.background).toEqual(Color.darkGray);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,15 +16,31 @@ export const getFwHardwareValue =
|
||||||
return isFwHardwareValue(value) ? value : undefined;
|
return isFwHardwareValue(value) ? value : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TMC_BOARDS = ["express_k10", "farmduino_k15"];
|
const NO_BUTTONS = ["arduino", "farmduino", "none"];
|
||||||
const EXPRESS_BOARDS = ["express_k10"];
|
const EXPRESS_BOARDS = ["express_k10"];
|
||||||
|
const NO_SENSORS = [...EXPRESS_BOARDS];
|
||||||
|
const NO_ENCODERS = [...EXPRESS_BOARDS];
|
||||||
|
const NO_TOOLS = [...EXPRESS_BOARDS];
|
||||||
|
const NO_TMC = ["arduino", "farmduino", "farmduino_k14"];
|
||||||
|
|
||||||
export const isTMCBoard = (firmwareHardware: FirmwareHardware | undefined) =>
|
export const isTMCBoard = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
!!(firmwareHardware && TMC_BOARDS.includes(firmwareHardware));
|
!firmwareHardware || !NO_TMC.includes(firmwareHardware);
|
||||||
|
|
||||||
export const isExpressBoard = (firmwareHardware: FirmwareHardware | undefined) =>
|
export const isExpressBoard = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
!!(firmwareHardware && EXPRESS_BOARDS.includes(firmwareHardware));
|
!!(firmwareHardware && EXPRESS_BOARDS.includes(firmwareHardware));
|
||||||
|
|
||||||
|
export const hasButtons = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
|
!firmwareHardware || !NO_BUTTONS.includes(firmwareHardware);
|
||||||
|
|
||||||
|
export const hasEncoders = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
|
!firmwareHardware || !NO_ENCODERS.includes(firmwareHardware);
|
||||||
|
|
||||||
|
export const hasSensors = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
|
!firmwareHardware || !NO_SENSORS.includes(firmwareHardware);
|
||||||
|
|
||||||
|
export const hasUTM = (firmwareHardware: FirmwareHardware | undefined) =>
|
||||||
|
!firmwareHardware || !NO_TOOLS.includes(firmwareHardware);
|
||||||
|
|
||||||
export const getBoardIdentifier =
|
export const getBoardIdentifier =
|
||||||
(firmwareVersion: string | undefined): string =>
|
(firmwareVersion: string | undefined): string =>
|
||||||
firmwareVersion ? firmwareVersion.split(".")[3] : "undefined";
|
firmwareVersion ? firmwareVersion.split(".")[3] : "undefined";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { MCUFactoryReset, bulkToggleControlPanel } from "../actions";
|
import { MCUFactoryReset, bulkToggleControlPanel } from "../actions";
|
||||||
import { Widget, WidgetHeader, WidgetBody } from "../../ui/index";
|
import { Widget, WidgetHeader, WidgetBody, Color } from "../../ui/index";
|
||||||
import { HardwareSettingsProps } from "../interfaces";
|
import { HardwareSettingsProps, SourceFwConfig } from "../interfaces";
|
||||||
import { isBotOnline } from "../must_be_online";
|
import { isBotOnline } from "../must_be_online";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { DangerZone } from "./hardware_settings/danger_zone";
|
import { DangerZone } from "./hardware_settings/danger_zone";
|
||||||
|
@ -19,6 +19,8 @@ import { t } from "../../i18next_wrapper";
|
||||||
import { PinBindings } from "./hardware_settings/pin_bindings";
|
import { PinBindings } from "./hardware_settings/pin_bindings";
|
||||||
import { ErrorHandling } from "./hardware_settings/error_handling";
|
import { ErrorHandling } from "./hardware_settings/error_handling";
|
||||||
import { maybeOpenPanel } from "./maybe_highlight";
|
import { maybeOpenPanel } from "./maybe_highlight";
|
||||||
|
import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
|
import type { McuParamName } from "farmbot";
|
||||||
|
|
||||||
export class HardwareSettings extends
|
export class HardwareSettings extends
|
||||||
React.Component<HardwareSettingsProps, {}> {
|
React.Component<HardwareSettingsProps, {}> {
|
||||||
|
@ -36,7 +38,10 @@ export class HardwareSettings extends
|
||||||
const botDisconnected = !isBotOnline(sync_status, botToMqttStatus);
|
const botDisconnected = !isBotOnline(sync_status, botToMqttStatus);
|
||||||
const commonProps = { dispatch, controlPanelState };
|
const commonProps = { dispatch, controlPanelState };
|
||||||
return <Widget className="hardware-widget">
|
return <Widget className="hardware-widget">
|
||||||
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS} />
|
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS}>
|
||||||
|
<SettingLoadProgress firmwareConfig={firmwareConfig}
|
||||||
|
sourceFwConfig={sourceFwConfig} />
|
||||||
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<button
|
<button
|
||||||
className={"fb-button gray no-float"}
|
className={"fb-button gray no-float"}
|
||||||
|
@ -78,8 +83,33 @@ export class HardwareSettings extends
|
||||||
onReset={MCUFactoryReset}
|
onReset={MCUFactoryReset}
|
||||||
botDisconnected={botDisconnected} />
|
botDisconnected={botDisconnected} />
|
||||||
<PinBindings {...commonProps}
|
<PinBindings {...commonProps}
|
||||||
resources={resources} />
|
resources={resources}
|
||||||
|
firmwareHardware={firmwareHardware} />
|
||||||
</WidgetBody>
|
</WidgetBody>
|
||||||
</Widget>;
|
</Widget>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SettingLoadProgressProps {
|
||||||
|
sourceFwConfig: SourceFwConfig;
|
||||||
|
firmwareConfig: FirmwareConfig | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNTRACKED_KEYS: (keyof FirmwareConfig)[] = [
|
||||||
|
"id", "created_at", "updated_at", "device_id", "api_migrated",
|
||||||
|
"param_config_ok", "param_test", "param_use_eeprom", "param_version",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Track firmware configuration adoption by FarmBot OS. */
|
||||||
|
const SettingLoadProgress = (props: SettingLoadProgressProps) => {
|
||||||
|
const keys = Object.keys(props.firmwareConfig || {})
|
||||||
|
.filter((k: keyof FirmwareConfig) => !UNTRACKED_KEYS.includes(k));
|
||||||
|
const loadedKeys = keys.filter((key: McuParamName) =>
|
||||||
|
props.sourceFwConfig(key).consistent);
|
||||||
|
const progress = loadedKeys.length / keys.length * 100;
|
||||||
|
const color = [0, 100].includes(progress) ? Color.darkGray : Color.white;
|
||||||
|
return <div className={"load-progress-bar-wrapper"}>
|
||||||
|
<div className={"load-progress-bar"}
|
||||||
|
style={{ width: `${progress}%`, background: color }} />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe("<PinBindings />", () => {
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
controlPanelState: panelState(),
|
controlPanelState: panelState(),
|
||||||
resources: buildResourceIndex([]).index,
|
resources: buildResourceIndex([]).index,
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows pin binding labels", () => {
|
it("shows pin binding labels", () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||||
import { EncodersProps } from "../interfaces";
|
import { EncodersProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { isExpressBoard } from "../firmware_hardware_support";
|
import { hasEncoders } from "../firmware_hardware_support";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function Encoders(props: EncodersProps) {
|
export function Encoders(props: EncodersProps) {
|
||||||
|
@ -18,23 +18,23 @@ export function Encoders(props: EncodersProps) {
|
||||||
y: !sourceFwConfig("encoder_enabled_y").value,
|
y: !sourceFwConfig("encoder_enabled_y").value,
|
||||||
z: !sourceFwConfig("encoder_enabled_z").value
|
z: !sourceFwConfig("encoder_enabled_z").value
|
||||||
};
|
};
|
||||||
const isExpress = isExpressBoard(firmwareHardware);
|
const showEncoders = hasEncoders(firmwareHardware);
|
||||||
|
|
||||||
return <Highlight className={"section"}
|
return <Highlight className={"section"}
|
||||||
settingName={DeviceSetting.encoders}>
|
settingName={DeviceSetting.encoders}>
|
||||||
<Header
|
<Header
|
||||||
expanded={encoders}
|
expanded={encoders}
|
||||||
title={isExpress
|
title={!showEncoders
|
||||||
? DeviceSetting.stallDetection
|
? DeviceSetting.stallDetection
|
||||||
: DeviceSetting.encoders}
|
: DeviceSetting.encoders}
|
||||||
panel={"encoders"}
|
panel={"encoders"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!encoders}>
|
<Collapse isOpen={!!encoders}>
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={isExpress
|
label={!showEncoders
|
||||||
? DeviceSetting.enableStallDetection
|
? DeviceSetting.enableStallDetection
|
||||||
: DeviceSetting.enableEncoders}
|
: DeviceSetting.enableEncoders}
|
||||||
tooltip={isExpress
|
tooltip={!showEncoders
|
||||||
? ToolTips.ENABLE_STALL_DETECTION
|
? ToolTips.ENABLE_STALL_DETECTION
|
||||||
: ToolTips.ENABLE_ENCODERS}
|
: ToolTips.ENABLE_ENCODERS}
|
||||||
x={"encoder_enabled_x"}
|
x={"encoder_enabled_x"}
|
||||||
|
@ -42,7 +42,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
z={"encoder_enabled_z"}
|
z={"encoder_enabled_z"}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
{isExpress &&
|
{!showEncoders &&
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
label={DeviceSetting.stallSensitivity}
|
label={DeviceSetting.stallSensitivity}
|
||||||
tooltip={ToolTips.STALL_SENSITIVITY}
|
tooltip={ToolTips.STALL_SENSITIVITY}
|
||||||
|
@ -52,7 +52,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
gray={encodersDisabled}
|
gray={encodersDisabled}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
{!isExpress &&
|
{showEncoders &&
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={DeviceSetting.useEncodersForPositioning}
|
label={DeviceSetting.useEncodersForPositioning}
|
||||||
tooltip={ToolTips.ENCODER_POSITIONING}
|
tooltip={ToolTips.ENCODER_POSITIONING}
|
||||||
|
@ -62,7 +62,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
grayscale={encodersDisabled}
|
grayscale={encodersDisabled}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
{!isExpress &&
|
{showEncoders &&
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={DeviceSetting.invertEncoders}
|
label={DeviceSetting.invertEncoders}
|
||||||
tooltip={ToolTips.INVERT_ENCODERS}
|
tooltip={ToolTips.INVERT_ENCODERS}
|
||||||
|
@ -74,7 +74,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
label={DeviceSetting.maxMissedSteps}
|
label={DeviceSetting.maxMissedSteps}
|
||||||
tooltip={isExpress
|
tooltip={!showEncoders
|
||||||
? ToolTips.MAX_MISSED_STEPS_STALL_DETECTION
|
? ToolTips.MAX_MISSED_STEPS_STALL_DETECTION
|
||||||
: ToolTips.MAX_MISSED_STEPS_ENCODERS}
|
: ToolTips.MAX_MISSED_STEPS_ENCODERS}
|
||||||
x={"encoder_missed_steps_max_x"}
|
x={"encoder_missed_steps_max_x"}
|
||||||
|
@ -92,7 +92,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
gray={encodersDisabled}
|
gray={encodersDisabled}
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
{!isExpress &&
|
{showEncoders &&
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
label={DeviceSetting.encoderScaling}
|
label={DeviceSetting.encoderScaling}
|
||||||
tooltip={ToolTips.ENCODER_SCALING}
|
tooltip={ToolTips.ENCODER_SCALING}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { calculateScale } from "./motors";
|
import { calculateScale } from "./motors";
|
||||||
import { isExpressBoard } from "../firmware_hardware_support";
|
import { hasEncoders } from "../firmware_hardware_support";
|
||||||
import { getDevice } from "../../../device";
|
import { getDevice } from "../../../device";
|
||||||
import { commandErr } from "../../actions";
|
import { commandErr } from "../../actions";
|
||||||
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
||||||
|
@ -44,7 +44,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
type={"find_home"}
|
type={"find_home"}
|
||||||
title={DeviceSetting.homing}
|
title={DeviceSetting.homing}
|
||||||
axisTitle={t("FIND HOME")}
|
axisTitle={t("FIND HOME")}
|
||||||
toolTip={isExpressBoard(firmwareHardware)
|
toolTip={!hasEncoders(firmwareHardware)
|
||||||
? ToolTips.HOMING_STALL_DETECTION
|
? ToolTips.HOMING_STALL_DETECTION
|
||||||
: ToolTips.HOMING_ENCODERS}
|
: ToolTips.HOMING_ENCODERS}
|
||||||
action={axis => getDevice()
|
action={axis => getDevice()
|
||||||
|
@ -56,7 +56,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
type={"calibrate"}
|
type={"calibrate"}
|
||||||
title={DeviceSetting.calibration}
|
title={DeviceSetting.calibration}
|
||||||
axisTitle={t("CALIBRATE")}
|
axisTitle={t("CALIBRATE")}
|
||||||
toolTip={isExpressBoard(firmwareHardware)
|
toolTip={!hasEncoders(firmwareHardware)
|
||||||
? ToolTips.CALIBRATION_STALL_DETECTION
|
? ToolTips.CALIBRATION_STALL_DETECTION
|
||||||
: ToolTips.CALIBRATION_ENCODERS}
|
: ToolTips.CALIBRATION_ENCODERS}
|
||||||
action={axis => getDevice().calibrate({ axis })
|
action={axis => getDevice().calibrate({ axis })
|
||||||
|
@ -74,7 +74,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
botDisconnected={botDisconnected} />
|
botDisconnected={botDisconnected} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={DeviceSetting.findHomeOnBoot}
|
label={DeviceSetting.findHomeOnBoot}
|
||||||
tooltip={isExpressBoard(firmwareHardware)
|
tooltip={!hasEncoders(firmwareHardware)
|
||||||
? ToolTips.FIND_HOME_ON_BOOT_STALL_DETECTION
|
? ToolTips.FIND_HOME_ON_BOOT_STALL_DETECTION
|
||||||
: ToolTips.FIND_HOME_ON_BOOT_ENCODERS}
|
: ToolTips.FIND_HOME_ON_BOOT_ENCODERS}
|
||||||
disable={disabled}
|
disable={disabled}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Highlight } from "../maybe_highlight";
|
||||||
export function PinBindings(props: PinBindingsProps) {
|
export function PinBindings(props: PinBindingsProps) {
|
||||||
|
|
||||||
const { pin_bindings } = props.controlPanelState;
|
const { pin_bindings } = props.controlPanelState;
|
||||||
const { dispatch, resources } = props;
|
const { dispatch, resources, firmwareHardware } = props;
|
||||||
|
|
||||||
return <Highlight className={"section"}
|
return <Highlight className={"section"}
|
||||||
settingName={DeviceSetting.pinBindings}>
|
settingName={DeviceSetting.pinBindings}>
|
||||||
|
@ -19,7 +19,8 @@ export function PinBindings(props: PinBindingsProps) {
|
||||||
panel={"pin_bindings"}
|
panel={"pin_bindings"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!pin_bindings}>
|
<Collapse isOpen={!!pin_bindings}>
|
||||||
<PinBindingsContent dispatch={dispatch} resources={resources} />
|
<PinBindingsContent dispatch={dispatch} resources={resources}
|
||||||
|
firmwareHardware={firmwareHardware} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Highlight>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ export interface PinBindingsProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
controlPanelState: ControlPanelState;
|
controlPanelState: ControlPanelState;
|
||||||
resources: ResourceIndex;
|
resources: ResourceIndex;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DangerZoneProps {
|
export interface DangerZoneProps {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
|
||||||
import {
|
import {
|
||||||
ConnectivityDiagram,
|
ConnectivityDiagram,
|
||||||
ConnectivityDiagramProps,
|
ConnectivityDiagramProps,
|
||||||
|
@ -83,9 +82,9 @@ describe("getTextPosition()", () => {
|
||||||
|
|
||||||
describe("nodeLabel()", () => {
|
describe("nodeLabel()", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const label = mount(nodeLabel("Top Node", "top" as DiagramNodes));
|
const label = svgMount(nodeLabel("Top Node", "top" as DiagramNodes));
|
||||||
expect(label.text()).toEqual("Top Node");
|
expect(label.find("text").text()).toEqual("Top Node");
|
||||||
expect(label.props())
|
expect(label.find("text").props())
|
||||||
.toEqual({ children: "Top Node", textAnchor: "middle", x: 0, y: -75 });
|
.toEqual({ children: "Top Node", textAnchor: "middle", x: 0, y: -75 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,6 +46,7 @@ describe("<PinBindingsContent/>", () => {
|
||||||
return {
|
return {
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
resources: resources,
|
resources: resources,
|
||||||
|
firmwareHardware: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,37 @@
|
||||||
jest.mock("../../../api/crud", () => ({
|
jest.mock("../../../api/crud", () => ({ initSave: jest.fn() }));
|
||||||
initSave: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { StockPinBindingsButton } from "../tagged_pin_binding_init";
|
import {
|
||||||
|
StockPinBindingsButton, StockPinBindingsButtonProps
|
||||||
|
} from "../tagged_pin_binding_init";
|
||||||
import { initSave } from "../../../api/crud";
|
import { initSave } from "../../../api/crud";
|
||||||
import { stockPinBindings } from "../list_and_label_support";
|
import { stockPinBindings } from "../list_and_label_support";
|
||||||
|
|
||||||
describe("<StockPinBindingsButton />", () => {
|
describe("<StockPinBindingsButton />", () => {
|
||||||
const fakeProps = () => ({
|
const fakeProps = (): StockPinBindingsButtonProps => ({
|
||||||
shouldDisplay: () => false,
|
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds bindings", () => {
|
it("adds bindings", () => {
|
||||||
const p = fakeProps();
|
const wrapper = mount(<StockPinBindingsButton {...fakeProps()} />);
|
||||||
p.shouldDisplay = () => true;
|
|
||||||
const wrapper = mount(<StockPinBindingsButton {...p} />);
|
|
||||||
wrapper.find("button").simulate("click");
|
wrapper.find("button").simulate("click");
|
||||||
stockPinBindings.map(body =>
|
stockPinBindings.map(body =>
|
||||||
expect(initSave).toHaveBeenCalledWith("PinBinding", body));
|
expect(initSave).toHaveBeenCalledWith("PinBinding", body));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("is hidden", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "arduino";
|
||||||
|
const wrapper = mount(<StockPinBindingsButton {...p} />);
|
||||||
|
expect(wrapper.find("button").props().hidden).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is not hidden", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "farmduino_k14";
|
||||||
|
const wrapper = mount(<StockPinBindingsButton {...p} />);
|
||||||
|
expect(wrapper.find("button").props().hidden).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,10 +3,12 @@ import {
|
||||||
PinBindingType,
|
PinBindingType,
|
||||||
PinBindingSpecialAction
|
PinBindingSpecialAction
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
|
import { FirmwareHardware } from "farmbot";
|
||||||
|
|
||||||
export interface PinBindingsContentProps {
|
export interface PinBindingsContentProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
resources: ResourceIndex;
|
resources: ResourceIndex;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PinBindingListItems {
|
export interface PinBindingListItems {
|
||||||
|
|
|
@ -68,12 +68,13 @@ const PinBindingsListHeader = () =>
|
||||||
</Row>;
|
</Row>;
|
||||||
|
|
||||||
export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
||||||
const { dispatch, resources } = props;
|
const { dispatch, resources, firmwareHardware } = props;
|
||||||
const pinBindings = apiPinBindings(resources);
|
const pinBindings = apiPinBindings(resources);
|
||||||
|
|
||||||
return <div className="pin-bindings">
|
return <div className="pin-bindings">
|
||||||
<Row>
|
<Row>
|
||||||
<StockPinBindingsButton dispatch={dispatch} />
|
<StockPinBindingsButton
|
||||||
|
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
||||||
<Popover
|
<Popover
|
||||||
position={Position.RIGHT_TOP}
|
position={Position.RIGHT_TOP}
|
||||||
interactionKind={PopoverInteractionKind.HOVER}
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { PinBindingListItems } from "./interfaces";
|
||||||
import { stockPinBindings } from "./list_and_label_support";
|
import { stockPinBindings } from "./list_and_label_support";
|
||||||
import { initSave } from "../../api/crud";
|
import { initSave } from "../../api/crud";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { FirmwareHardware } from "farmbot";
|
||||||
|
import { hasButtons } from "../components/firmware_hardware_support";
|
||||||
|
|
||||||
/** Return the correct Pin Binding resource according to binding type. */
|
/** Return the correct Pin Binding resource according to binding type. */
|
||||||
export const pinBindingBody =
|
export const pinBindingBody =
|
||||||
|
@ -34,15 +36,21 @@ export const pinBindingBody =
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface StockPinBindingsButtonProps {
|
||||||
|
dispatch: Function;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** Add default pin bindings. */
|
/** Add default pin bindings. */
|
||||||
export const StockPinBindingsButton = ({ dispatch }: { dispatch: Function }) =>
|
export const StockPinBindingsButton = (props: StockPinBindingsButtonProps) =>
|
||||||
<div className="stock-pin-bindings-button">
|
<div className="stock-pin-bindings-button">
|
||||||
<button
|
<button
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
|
hidden={!hasButtons(props.firmwareHardware)}
|
||||||
onClick={() => stockPinBindings.map(binding =>
|
onClick={() => stockPinBindings.map(binding =>
|
||||||
dispatch(initSave("PinBinding", pinBindingBody(binding))))}>
|
props.dispatch(initSave("PinBinding", pinBindingBody(binding))))}>
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
{t("v1.4 Stock Bindings")}
|
{t("Stock Bindings")}
|
||||||
</button>
|
</button>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Plant } from "../plant";
|
||||||
|
|
||||||
|
describe("Plant()", () => {
|
||||||
|
it("returns defaults", () => {
|
||||||
|
expect(Plant({})).toEqual({
|
||||||
|
created_at: "",
|
||||||
|
id: undefined,
|
||||||
|
meta: {},
|
||||||
|
name: "Untitled Plant",
|
||||||
|
openfarm_slug: "not-set",
|
||||||
|
plant_stage: "planned",
|
||||||
|
pointer_type: "Plant",
|
||||||
|
radius: 25,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -94,6 +94,19 @@ describe("designer reducer", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses current point color", () => {
|
||||||
|
const action: ReduxAction<CurrentPointPayl> = {
|
||||||
|
type: Actions.SET_CURRENT_POINT_DATA,
|
||||||
|
payload: { cx: 10, cy: 20, r: 30 }
|
||||||
|
};
|
||||||
|
const state = oldState();
|
||||||
|
state.currentPoint = { cx: 0, cy: 0, r: 0, color: "red" };
|
||||||
|
const newState = designer(state, action);
|
||||||
|
expect(newState.currentPoint).toEqual({
|
||||||
|
cx: 10, cy: 20, r: 30, color: "red"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("sets opened saved garden", () => {
|
it("sets opened saved garden", () => {
|
||||||
const payload = "savedGardenUuid";
|
const payload = "savedGardenUuid";
|
||||||
const action: ReduxAction<string | undefined> = {
|
const action: ReduxAction<string | undefined> = {
|
||||||
|
|
|
@ -90,7 +90,7 @@ export const DesignerPanelTop = (props: DesignerPanelTopProps) => {
|
||||||
<div className="thin-search-wrapper">
|
<div className="thin-search-wrapper">
|
||||||
<div className="text-input-wrapper">
|
<div className="text-input-wrapper">
|
||||||
{!props.noIcon &&
|
{!props.noIcon &&
|
||||||
<i className="fa fa-search"></i>}
|
<i className="fa fa-search" />}
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class RawEditFarmEvent extends React.Component<AddEditFarmEventProps, {}>
|
||||||
<DesignerPanelHeader
|
<DesignerPanelHeader
|
||||||
panelName={panelName}
|
panelName={panelName}
|
||||||
panel={Panel.FarmEvents}
|
panel={Panel.FarmEvents}
|
||||||
title={t("Edit Event")} />
|
title={t("Edit event")} />
|
||||||
<DesignerPanelContent panelName={panelName}>
|
<DesignerPanelContent panelName={panelName}>
|
||||||
<EditFEForm farmEvent={fe}
|
<EditFEForm farmEvent={fe}
|
||||||
deviceTimezone={this.props.deviceTimezone}
|
deviceTimezone={this.props.deviceTimezone}
|
||||||
|
@ -31,7 +31,7 @@ export class RawEditFarmEvent extends React.Component<AddEditFarmEventProps, {}>
|
||||||
executableOptions={this.props.executableOptions}
|
executableOptions={this.props.executableOptions}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
findExecutable={this.props.findExecutable}
|
findExecutable={this.props.findExecutable}
|
||||||
title={t("Edit Event")}
|
title={t("Edit event")}
|
||||||
deleteBtn={true}
|
deleteBtn={true}
|
||||||
timeSettings={this.props.timeSettings}
|
timeSettings={this.props.timeSettings}
|
||||||
autoSyncEnabled={this.props.autoSyncEnabled}
|
autoSyncEnabled={this.props.autoSyncEnabled}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
} from "../../sequences/locals_list/locals_list_support";
|
} from "../../sequences/locals_list/locals_list_support";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../interfaces";
|
import { TimeSettings } from "../../interfaces";
|
||||||
|
import { ErrorBoundary } from "../../error_boundary";
|
||||||
|
|
||||||
export const NEVER: TimeUnit = "never";
|
export const NEVER: TimeUnit = "never";
|
||||||
/** Separate each of the form fields into their own interface. Recombined later
|
/** Separate each of the form fields into their own interface. Recombined later
|
||||||
|
@ -360,19 +361,24 @@ export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
|
||||||
render() {
|
render() {
|
||||||
const { farmEvent } = this.props;
|
const { farmEvent } = this.props;
|
||||||
return <div className="edit-farm-event-form">
|
return <div className="edit-farm-event-form">
|
||||||
<FarmEventForm
|
<ErrorBoundary>
|
||||||
isRegimen={this.isReg}
|
<FarmEventForm
|
||||||
fieldGet={this.fieldGet}
|
isRegimen={this.isReg}
|
||||||
fieldSet={this.fieldSet}
|
fieldGet={this.fieldGet}
|
||||||
timeSettings={this.props.timeSettings}
|
fieldSet={this.fieldSet}
|
||||||
executableOptions={this.props.executableOptions}
|
timeSettings={this.props.timeSettings}
|
||||||
executableSet={this.executableSet}
|
executableOptions={this.props.executableOptions}
|
||||||
executableGet={this.executableGet}
|
executableSet={this.executableSet}
|
||||||
dispatch={this.props.dispatch}
|
executableGet={this.executableGet}
|
||||||
specialStatus={farmEvent.specialStatus || this.state.specialStatusLocal}
|
dispatch={this.props.dispatch}
|
||||||
onSave={() => this.commitViewModel()}>
|
specialStatus={farmEvent.specialStatus
|
||||||
<this.LocalsList />
|
|| this.state.specialStatusLocal}
|
||||||
</FarmEventForm>
|
onSave={() => this.commitViewModel()}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<this.LocalsList />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</FarmEventForm>
|
||||||
|
</ErrorBoundary>
|
||||||
<FarmEventDeleteButton
|
<FarmEventDeleteButton
|
||||||
hidden={!this.props.deleteBtn}
|
hidden={!this.props.deleteBtn}
|
||||||
farmEvent={this.props.farmEvent}
|
farmEvent={this.props.farmEvent}
|
||||||
|
|
|
@ -111,7 +111,7 @@ export class PureFarmEvents
|
||||||
<input
|
<input
|
||||||
value={this.state.searchTerm}
|
value={this.state.searchTerm}
|
||||||
onChange={e => this.setState({ searchTerm: e.currentTarget.value })}
|
onChange={e => this.setState({ searchTerm: e.currentTarget.value })}
|
||||||
placeholder={t("Search events...")} />
|
placeholder={t("Search your events...")} />
|
||||||
</DesignerPanelTop>
|
</DesignerPanelTop>
|
||||||
<DesignerPanelContent panelName={"farm-event"}>
|
<DesignerPanelContent panelName={"farm-event"}>
|
||||||
<div className="farm-events">
|
<div className="farm-events">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
|
||||||
import { MapImage, MapImageProps } from "../map_image";
|
import { MapImage, MapImageProps } from "../map_image";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus } from "farmbot";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
@ -7,6 +6,9 @@ import { trim } from "../../../../../util";
|
||||||
import {
|
import {
|
||||||
fakeMapTransformProps
|
fakeMapTransformProps
|
||||||
} from "../../../../../__test_support__/map_transform_props";
|
} from "../../../../../__test_support__/map_transform_props";
|
||||||
|
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||||
|
|
||||||
|
const NOT_DISPLAYED = "<svg><image></image></svg>";
|
||||||
|
|
||||||
describe("<MapImage />", () => {
|
describe("<MapImage />", () => {
|
||||||
const fakeProps = (): MapImageProps => {
|
const fakeProps = (): MapImageProps => {
|
||||||
|
@ -37,28 +39,28 @@ describe("<MapImage />", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it("doesn't render image", () => {
|
it("doesn't render image", () => {
|
||||||
const wrapper = mount(<MapImage {...fakeProps()} />);
|
const wrapper = svgMount(<MapImage {...fakeProps()} />);
|
||||||
expect(wrapper.html()).toEqual("<image></image>");
|
expect(wrapper.html()).toEqual(NOT_DISPLAYED);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders pre-calibration preview", () => {
|
it("renders pre-calibration preview", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.image && (p.image.body.meta = { x: 0, y: 0, z: 0 });
|
p.image && (p.image.body.meta = { x: 0, y: 0, z: 0 });
|
||||||
const wrapper = mount(<MapImage {...p} />);
|
const wrapper = svgMount(<MapImage {...p} />);
|
||||||
wrapper.setState({ width: 100, height: 100 });
|
wrapper.find(MapImage).setState({ width: 100, height: 100 });
|
||||||
expect(wrapper.html()).toContain("image_url");
|
expect(wrapper.html()).toContain("image_url");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets image size", () => {
|
it("gets image size", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.image && (p.image.body.meta = { x: 0, y: 0, z: 0 });
|
p.image && (p.image.body.meta = { x: 0, y: 0, z: 0 });
|
||||||
const wrapper = mount<MapImage>(<MapImage {...p} />);
|
const wrapper = svgMount(<MapImage {...p} />);
|
||||||
expect(wrapper.state()).toEqual({ width: 0, height: 0 });
|
expect(wrapper.find(MapImage).state()).toEqual({ width: 0, height: 0 });
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.width = 100;
|
img.width = 100;
|
||||||
img.height = 200;
|
img.height = 200;
|
||||||
wrapper.instance().imageCallback(img)();
|
wrapper.find<MapImage>(MapImage).instance().imageCallback(img)();
|
||||||
expect(wrapper.state()).toEqual({ width: 100, height: 200 });
|
expect(wrapper.find(MapImage).state()).toEqual({ width: 100, height: 200 });
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ExpectedData {
|
interface ExpectedData {
|
||||||
|
@ -83,8 +85,8 @@ describe("<MapImage />", () => {
|
||||||
expectedData: ExpectedData,
|
expectedData: ExpectedData,
|
||||||
extra?: ExtraTranslationData) => {
|
extra?: ExtraTranslationData) => {
|
||||||
it(`renders image: INPUT_SET_${num}`, () => {
|
it(`renders image: INPUT_SET_${num}`, () => {
|
||||||
const wrapper = mount(<MapImage {...inputData[num]} />);
|
const wrapper = svgMount(<MapImage {...inputData[num]} />);
|
||||||
wrapper.setState({ width: 480, height: 640 });
|
wrapper.find(MapImage).setState({ width: 480, height: 640 });
|
||||||
expect(wrapper.find("image").props()).toEqual({
|
expect(wrapper.find("image").props()).toEqual({
|
||||||
xlinkHref: "image_url",
|
xlinkHref: "image_url",
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -183,21 +185,21 @@ describe("<MapImage />", () => {
|
||||||
it("doesn't render placeholder image", () => {
|
it("doesn't render placeholder image", () => {
|
||||||
const p = INPUT_SET_1;
|
const p = INPUT_SET_1;
|
||||||
p.image && (p.image.body.attachment_url = "/placehold.");
|
p.image && (p.image.body.attachment_url = "/placehold.");
|
||||||
const wrapper = mount(<MapImage {...p} />);
|
const wrapper = svgMount(<MapImage {...p} />);
|
||||||
expect(wrapper.html()).toEqual("<image></image>");
|
expect(wrapper.html()).toEqual(NOT_DISPLAYED);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't render image taken at different height than calibration", () => {
|
it("doesn't render image taken at different height than calibration", () => {
|
||||||
const p = INPUT_SET_1;
|
const p = INPUT_SET_1;
|
||||||
p.image && (p.image.body.meta.z = 100);
|
p.image && (p.image.body.meta.z = 100);
|
||||||
const wrapper = mount(<MapImage {...p} />);
|
const wrapper = svgMount(<MapImage {...p} />);
|
||||||
expect(wrapper.html()).toEqual("<image></image>");
|
expect(wrapper.html()).toEqual(NOT_DISPLAYED);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't render images that are not adjusted for camera rotation", () => {
|
it("doesn't render images that are not adjusted for camera rotation", () => {
|
||||||
const p = INPUT_SET_1;
|
const p = INPUT_SET_1;
|
||||||
p.image && (p.image.body.meta.name = "na");
|
p.image && (p.image.body.meta.name = "na");
|
||||||
const wrapper = mount(<MapImage {...p} />);
|
const wrapper = svgMount(<MapImage {...p} />);
|
||||||
expect(wrapper.html()).toEqual("<image></image>");
|
expect(wrapper.html()).toEqual(NOT_DISPLAYED);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,11 +93,15 @@ interface NavTabProps {
|
||||||
linkTo: string;
|
linkTo: string;
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
desktopHide?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavTab = (props: NavTabProps) =>
|
const NavTab = (props: NavTabProps) =>
|
||||||
<Link to={props.linkTo} style={{ flex: 0.3 }}
|
<Link to={props.linkTo} style={{ flex: 0.3 }}
|
||||||
className={getCurrentTab() === props.panel ? "active" : ""}>
|
className={[
|
||||||
|
getCurrentTab() === props.panel ? "active" : "",
|
||||||
|
props.desktopHide ? "desktop-hide" : "",
|
||||||
|
].join(" ")}>
|
||||||
<img {...common}
|
<img {...common}
|
||||||
src={TAB_ICON[props.panel]} title={props.title} />
|
src={TAB_ICON[props.panel]} title={props.title} />
|
||||||
</Link>;
|
</Link>;
|
||||||
|
@ -109,7 +113,7 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
||||||
<div className="panel-tabs">
|
<div className="panel-tabs">
|
||||||
<NavTab panel={Panel.Map}
|
<NavTab panel={Panel.Map}
|
||||||
linkTo={"/app/designer"}
|
linkTo={"/app/designer"}
|
||||||
title={t("Map")} />
|
title={t("Map")} desktopHide={true} />
|
||||||
<NavTab
|
<NavTab
|
||||||
panel={Panel.Plants}
|
panel={Panel.Plants}
|
||||||
linkTo={"/app/designer/plants"}
|
linkTo={"/app/designer/plants"}
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class GroupDetailActive
|
||||||
onBack={this.saveGroup}
|
onBack={this.saveGroup}
|
||||||
panelName={Panel.Groups}
|
panelName={Panel.Groups}
|
||||||
panel={Panel.Groups}
|
panel={Panel.Groups}
|
||||||
title={t("Edit Group")}
|
title={t("Edit group")}
|
||||||
backTo={"/app/designer/groups"} />
|
backTo={"/app/designer/groups"} />
|
||||||
<DesignerPanelContent
|
<DesignerPanelContent
|
||||||
panelName={"groups"}>
|
panelName={"groups"}>
|
||||||
|
|
|
@ -6,6 +6,7 @@ jest.mock("../../../farmware/weed_detector/actions", () => ({
|
||||||
|
|
||||||
let mockPath = "/app/designer/points/add";
|
let mockPath = "/app/designer/points/add";
|
||||||
jest.mock("../../../history", () => ({
|
jest.mock("../../../history", () => ({
|
||||||
|
history: { push: jest.fn() },
|
||||||
push: jest.fn(),
|
push: jest.fn(),
|
||||||
getPathArray: () => mockPath.split("/"),
|
getPathArray: () => mockPath.split("/"),
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -23,8 +23,9 @@ import {
|
||||||
import { parseIntInput } from "../../util";
|
import { parseIntInput } from "../../util";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { Panel } from "../panel_header";
|
import { Panel } from "../panel_header";
|
||||||
import { getPathArray } from "../../history";
|
import { history, getPathArray } from "../../history";
|
||||||
import { ListItem } from "../plants/plant_panel";
|
import { ListItem } from "../plants/plant_panel";
|
||||||
|
import { success } from "../../toast/toast";
|
||||||
|
|
||||||
export function mapStateToProps(props: Everything): CreatePointsProps {
|
export function mapStateToProps(props: Everything): CreatePointsProps {
|
||||||
const { position } = props.bot.hardware.location_data;
|
const { position } = props.bot.hardware.location_data;
|
||||||
|
@ -176,9 +177,13 @@ export class RawCreatePoints
|
||||||
radius: this.attr("r"),
|
radius: this.attr("r"),
|
||||||
};
|
};
|
||||||
this.props.dispatch(initSave("Point", body));
|
this.props.dispatch(initSave("Point", body));
|
||||||
|
success(this.panel == "weeds"
|
||||||
|
? t("Weed created.")
|
||||||
|
: t("Point created."));
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.loadDefaultPoint();
|
history.push(`/app/designer/${this.panel}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
PointProperties = () =>
|
PointProperties = () =>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -47,7 +47,7 @@ export class PointInventoryItem extends
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<p className="point-search-item-info">
|
<p className="point-search-item-info">
|
||||||
{`(${point.x}, ${point.y}) ⌀${point.radius * 2}`}
|
<i>{`(${point.x}, ${point.y}) ⌀${point.radius * 2}`}</i>
|
||||||
</p>
|
</p>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ export class RawEditGarden extends React.Component<EditGardenProps, {}> {
|
||||||
<DesignerPanelHeader
|
<DesignerPanelHeader
|
||||||
panelName={"saved-garden"}
|
panelName={"saved-garden"}
|
||||||
panel={Panel.SavedGardens}
|
panel={Panel.SavedGardens}
|
||||||
title={t("Edit Garden")}
|
title={t("Edit garden")}
|
||||||
backTo={"/app/designer/gardens"} />
|
backTo={"/app/designer/gardens"} />
|
||||||
<DesignerPanelContent panelName={"saved-garden-edit"}>
|
<DesignerPanelContent panelName={"saved-garden-edit"}>
|
||||||
{savedGarden
|
{savedGarden
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const GardenInfo = (props: SavedGardenInfoProps) => {
|
||||||
onClick={() => dispatch(openSavedGarden(savedGarden.uuid))}>
|
onClick={() => dispatch(openSavedGarden(savedGarden.uuid))}>
|
||||||
<Col>
|
<Col>
|
||||||
<span>{savedGarden.body.name}</span>
|
<span>{savedGarden.body.name}</span>
|
||||||
<p>{props.plantTemplateCount} {t("plants")}</p>
|
<p><i>{props.plantTemplateCount} {t("plants")}</i></p>
|
||||||
</Col>
|
</Col>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -193,7 +193,7 @@ describe("<Tools />", () => {
|
||||||
p.isActive = () => true;
|
p.isActive = () => true;
|
||||||
p.device.body.mounted_tool_id = undefined;
|
p.device.body.mounted_tool_id = undefined;
|
||||||
const wrapper = mount(<Tools {...p} />);
|
const wrapper = mount(<Tools {...p} />);
|
||||||
expect(wrapper.text().toLowerCase()).toContain("active");
|
expect(wrapper.text().toLowerCase()).toContain("in slot");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays tool as mounted", () => {
|
it("displays tool as mounted", () => {
|
||||||
|
|
|
@ -288,7 +288,7 @@ export const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4} className={"tool-slot-position-info"}>
|
<Col xs={4} className={"tool-slot-position-info"}>
|
||||||
<p className="tool-slot-position">
|
<p className="tool-slot-position">
|
||||||
{botPositionLabel({ x, y, z }, gantry_mounted)}
|
<i>{botPositionLabel({ x, y, z }, gantry_mounted)}</i>
|
||||||
</p>
|
</p>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -303,7 +303,7 @@ interface ToolInventoryItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolInventoryItem = (props: ToolInventoryItemProps) => {
|
const ToolInventoryItem = (props: ToolInventoryItemProps) => {
|
||||||
const activeText = props.active ? t("active") : t("inactive");
|
const activeText = props.active ? t("in slot") : t("inactive");
|
||||||
return <div className={"tool-search-item"}
|
return <div className={"tool-search-item"}
|
||||||
onClick={() => history.push(`/app/designer/tools/${props.toolId}`)}>
|
onClick={() => history.push(`/app/designer/tools/${props.toolId}`)}>
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -315,7 +315,7 @@ const ToolInventoryItem = (props: ToolInventoryItemProps) => {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
<p className="tool-status">
|
<p className="tool-status">
|
||||||
{props.mounted ? t("mounted") : activeText}
|
<i>{props.mounted ? t("mounted") : activeText}</i>
|
||||||
</p>
|
</p>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -294,14 +294,14 @@ describe("<FolderListItem />", () => {
|
||||||
it("starts sequence move", () => {
|
it("starts sequence move", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = shallow(<FolderListItem {...p} />);
|
const wrapper = shallow(<FolderListItem {...p} />);
|
||||||
wrapper.find(".fa-bars").simulate("mouseDown");
|
wrapper.find(".fa-arrows-v").simulate("mouseDown");
|
||||||
expect(p.startSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
expect(p.startSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toggles sequence move", () => {
|
it("toggles sequence move", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = shallow(<FolderListItem {...p} />);
|
const wrapper = shallow(<FolderListItem {...p} />);
|
||||||
wrapper.find(".fa-bars").simulate("mouseUp");
|
wrapper.find(".fa-arrows-v").simulate("mouseUp");
|
||||||
expect(p.toggleSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
expect(p.toggleSequenceMove).toHaveBeenCalledWith(p.sequence.uuid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const FolderListItem = (props: FolderItemProps) => {
|
||||||
<div className="sequence-list-item-icons">
|
<div className="sequence-list-item-icons">
|
||||||
{props.inUse &&
|
{props.inUse &&
|
||||||
<i className="in-use fa fa-hdd-o" title={t(Content.IN_USE)} />}
|
<i className="in-use fa fa-hdd-o" title={t(Content.IN_USE)} />}
|
||||||
<i className="fa fa-bars"
|
<i className="fa fa-arrows-v"
|
||||||
onMouseDown={() => props.startSequenceMove(sequence.uuid)}
|
onMouseDown={() => props.startSequenceMove(sequence.uuid)}
|
||||||
onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} />
|
onMouseUp={() => props.toggleSequenceMove(sequence.uuid)} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -328,7 +328,7 @@ export const FolderPanelTop = (props: FolderPanelTopProps) =>
|
||||||
value={props.searchTerm || ""}
|
value={props.searchTerm || ""}
|
||||||
onChange={e => updateSearchTerm(e.currentTarget.value)}
|
onChange={e => updateSearchTerm(e.currentTarget.value)}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t("Search sequences")} />
|
placeholder={t("Search sequences...")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ToggleFolderBtn
|
<ToggleFolderBtn
|
||||||
|
|
|
@ -40,13 +40,14 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
||||||
noFolder: (localMetaAttributes[PARENTLESS] || {}).sequences || []
|
noFolder: (localMetaAttributes[PARENTLESS] || {}).sequences || []
|
||||||
};
|
};
|
||||||
const index = folders.map(setDefaultParentId).reduce(addToIndex, emptyIndex);
|
const index = folders.map(setDefaultParentId).reduce(addToIndex, emptyIndex);
|
||||||
const childrenOf = (i: number) => sortBy(index[i] || [], (x) => x.name.toLowerCase());
|
const childrenOf = (i: number) =>
|
||||||
|
sortBy(index[i] || [], (x) => x.name.toLowerCase());
|
||||||
|
|
||||||
const terminal = (x: FolderNode): FolderNodeTerminal => ({
|
const terminal = (x: FolderNode): FolderNodeTerminal => ({
|
||||||
...x,
|
...x,
|
||||||
kind: "terminal",
|
kind: "terminal",
|
||||||
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
||||||
open: true,
|
open: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
// children: [],
|
// children: [],
|
||||||
...(localMetaAttributes[x.id] || {})
|
...(localMetaAttributes[x.id] || {})
|
||||||
|
@ -55,7 +56,7 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
||||||
const medial = (x: FolderNode): FolderNodeMedial => ({
|
const medial = (x: FolderNode): FolderNodeMedial => ({
|
||||||
...x,
|
...x,
|
||||||
kind: "medial",
|
kind: "medial",
|
||||||
open: true,
|
open: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
children: childrenOf(x.id).map(terminal),
|
children: childrenOf(x.id).map(terminal),
|
||||||
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
||||||
|
@ -67,7 +68,7 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
||||||
return output.folders.push({
|
return output.folders.push({
|
||||||
...root,
|
...root,
|
||||||
kind: "initial",
|
kind: "initial",
|
||||||
open: true,
|
open: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
children,
|
children,
|
||||||
content: (localMetaAttributes[root.id] || {}).sequences || [],
|
content: (localMetaAttributes[root.id] || {}).sequences || [],
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { selectAllTools } from "../resources/selectors";
|
||||||
import { store } from "../redux/store";
|
import { store } from "../redux/store";
|
||||||
import { getFbosConfig } from "../resources/getters";
|
import { getFbosConfig } from "../resources/getters";
|
||||||
import {
|
import {
|
||||||
isExpressBoard, getFwHardwareValue
|
getFwHardwareValue, hasUTM
|
||||||
} from "../devices/components/firmware_hardware_support";
|
} from "../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
export enum Tours {
|
export enum Tours {
|
||||||
|
@ -25,26 +25,26 @@ export const tourNames = () => [
|
||||||
const hasTools = () =>
|
const hasTools = () =>
|
||||||
selectAllTools(store.getState().resources.index).length > 0;
|
selectAllTools(store.getState().resources.index).length > 0;
|
||||||
|
|
||||||
const isExpress = () =>
|
const noUTM = () =>
|
||||||
isExpressBoard(getFwHardwareValue(
|
!hasUTM(getFwHardwareValue(
|
||||||
getFbosConfig(store.getState().resources.index)));
|
getFbosConfig(store.getState().resources.index)));
|
||||||
|
|
||||||
const toolsStep = () => hasTools()
|
const toolsStep = () => hasTools()
|
||||||
? [{
|
? [{
|
||||||
target: ".tools",
|
target: ".tools",
|
||||||
content: isExpress()
|
content: noUTM()
|
||||||
? t(TourContent.ADD_SEED_CONTAINERS)
|
? t(TourContent.ADD_SEED_CONTAINERS)
|
||||||
: t(TourContent.ADD_TOOLS),
|
: t(TourContent.ADD_TOOLS),
|
||||||
title: isExpress()
|
title: noUTM()
|
||||||
? t("Add seed containers")
|
? t("Add seed containers")
|
||||||
: t("Add tools and seed containers"),
|
: t("Add tools and seed containers"),
|
||||||
}]
|
}]
|
||||||
: [{
|
: [{
|
||||||
target: ".tools",
|
target: ".tools",
|
||||||
content: isExpress()
|
content: noUTM()
|
||||||
? t(TourContent.ADD_SEED_CONTAINERS_AND_SLOTS)
|
? t(TourContent.ADD_SEED_CONTAINERS_AND_SLOTS)
|
||||||
: t(TourContent.ADD_TOOLS_AND_SLOTS),
|
: t(TourContent.ADD_TOOLS_AND_SLOTS),
|
||||||
title: isExpress()
|
title: noUTM()
|
||||||
? t("Add seed containers and slots")
|
? t("Add seed containers and slots")
|
||||||
: t("Add tools and slots"),
|
: t("Add tools and slots"),
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const mockStorj: Dictionary<number | boolean> = {};
|
const mockStorj: Dictionary<number | boolean> = {};
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { RawLogs as Logs } from "../index";
|
import { RawLogs as Logs } from "../index";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { TaggedLog, Dictionary } from "farmbot";
|
import { TaggedLog, Dictionary } from "farmbot";
|
||||||
|
@ -172,4 +172,12 @@ describe("<Logs />", () => {
|
||||||
wrapper.setState({ markdown: false });
|
wrapper.setState({ markdown: false });
|
||||||
expect(wrapper.html()).not.toContain("<code>message</code>");
|
expect(wrapper.html()).not.toContain("<code>message</code>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("changes search term", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = shallow<Logs>(<Logs {...p} />);
|
||||||
|
wrapper.find("input").first().simulate("change",
|
||||||
|
{ currentTarget: { value: "one" } });
|
||||||
|
expect(wrapper.state().searchTerm).toEqual("one");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,8 @@ const logTypes = MESSAGE_TYPES;
|
||||||
|
|
||||||
describe("<LogsFilterMenu />", () => {
|
describe("<LogsFilterMenu />", () => {
|
||||||
const fakeState: LogsState = {
|
const fakeState: LogsState = {
|
||||||
autoscroll: true, markdown: false, success: 1, busy: 1, warn: 1,
|
autoscroll: true, markdown: false, searchTerm: "",
|
||||||
|
success: 1, busy: 1, warn: 1,
|
||||||
error: 1, info: 1, fun: 1, debug: 1, assertion: 1,
|
error: 1, info: 1, fun: 1, debug: 1, assertion: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ describe("<LogsFilterMenu />", () => {
|
||||||
const wrapper = mount(<LogsFilterMenu {...fakeProps()} />);
|
const wrapper = mount(<LogsFilterMenu {...fakeProps()} />);
|
||||||
logTypes.filter(x => x !== "assertion").map(string =>
|
logTypes.filter(x => x !== "assertion").map(string =>
|
||||||
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
|
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
|
||||||
["autoscroll", "markdown"].map(string =>
|
["autoscroll", "markdown", "searchTerm"].map(string =>
|
||||||
expect(wrapper.text().toLowerCase()).not.toContain(string));
|
expect(wrapper.text().toLowerCase()).not.toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ describe("<LogsFilterMenu />", () => {
|
||||||
const wrapper = mount(<LogsFilterMenu {...p} />);
|
const wrapper = mount(<LogsFilterMenu {...p} />);
|
||||||
logTypes.map(string =>
|
logTypes.map(string =>
|
||||||
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
|
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
|
||||||
["autoscroll", "markdown"].map(string =>
|
["autoscroll", "markdown", "searchTerm"].map(string =>
|
||||||
expect(wrapper.text().toLowerCase()).not.toContain(string));
|
expect(wrapper.text().toLowerCase()).not.toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
|
import { bySearchTerm } from "../logs_table";
|
||||||
|
import { fakeLog } from "../../../__test_support__/fake_state/resources";
|
||||||
|
|
||||||
|
describe("bySearchTerm()", () => {
|
||||||
|
it("includes log", () => {
|
||||||
|
const log = fakeLog();
|
||||||
|
log.body.message = "include this log";
|
||||||
|
const result = bySearchTerm("include", fakeTimeSettings())(log);
|
||||||
|
expect(result).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("excludes log", () => {
|
||||||
|
const log = fakeLog();
|
||||||
|
log.body.created_at = undefined;
|
||||||
|
log.body.message = "exclude this log";
|
||||||
|
const result = bySearchTerm("include", fakeTimeSettings())(log);
|
||||||
|
expect(result).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -28,7 +28,7 @@ const menuSort = (a: string, b: string) =>
|
||||||
export const filterStateKeys =
|
export const filterStateKeys =
|
||||||
(state: LogsState, shouldDisplay: ShouldDisplay) =>
|
(state: LogsState, shouldDisplay: ShouldDisplay) =>
|
||||||
Object.keys(state)
|
Object.keys(state)
|
||||||
.filter(key => !["autoscroll", "markdown"].includes(key))
|
.filter(key => !["autoscroll", "markdown", "searchTerm"].includes(key))
|
||||||
.filter(key => shouldDisplay(Feature.assertion_block)
|
.filter(key => shouldDisplay(Feature.assertion_block)
|
||||||
|| key !== "assertion");
|
|| key !== "assertion");
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TaggedLog, ALLOWED_MESSAGE_TYPES } from "farmbot";
|
||||||
import { LogsState, LogsTableProps, Filters } from "../interfaces";
|
import { LogsState, LogsTableProps, Filters } from "../interfaces";
|
||||||
import { formatLogTime } from "../index";
|
import { formatLogTime } from "../index";
|
||||||
import { Classes } from "@blueprintjs/core";
|
import { Classes } from "@blueprintjs/core";
|
||||||
import { isNumber, startCase } from "lodash";
|
import { isNumber, startCase, some } from "lodash";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../interfaces";
|
import { TimeSettings } from "../../interfaces";
|
||||||
import { UUID } from "../../resources/interfaces";
|
import { UUID } from "../../resources/interfaces";
|
||||||
|
@ -81,6 +81,7 @@ export const LogsTable = (props: LogsTableProps) => {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filterByVerbosity(getFilterLevel(props.state), props.logs)
|
{filterByVerbosity(getFilterLevel(props.state), props.logs)
|
||||||
|
.filter(bySearchTerm(props.state.searchTerm, props.timeSettings))
|
||||||
.map((log: TaggedLog) =>
|
.map((log: TaggedLog) =>
|
||||||
<LogsRow
|
<LogsRow
|
||||||
key={log.uuid}
|
key={log.uuid}
|
||||||
|
@ -114,3 +115,18 @@ export const filterByVerbosity =
|
||||||
return displayLog;
|
return displayLog;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const bySearchTerm =
|
||||||
|
(searchTerm: string, timeSettings: TimeSettings) =>
|
||||||
|
(log: TaggedLog) => {
|
||||||
|
const { x, y, z, created_at, message, type } = log.body;
|
||||||
|
const displayedTime = formatLogTime(created_at || NaN, timeSettings);
|
||||||
|
const displayedPosition = xyzTableEntry(x, y, z);
|
||||||
|
const lowerSearchTerm = searchTerm.toLowerCase();
|
||||||
|
return some([message, type]
|
||||||
|
.map(string => string.toLowerCase().includes(lowerSearchTerm))
|
||||||
|
.concat([
|
||||||
|
displayedTime.toLowerCase().includes(lowerSearchTerm),
|
||||||
|
displayedPosition.includes(lowerSearchTerm),
|
||||||
|
]));
|
||||||
|
};
|
||||||
|
|
|
@ -49,6 +49,7 @@ export class RawLogs extends React.Component<LogsProps, Partial<LogsState>> {
|
||||||
fun: this.initialize(NumericSetting.fun_log, 1),
|
fun: this.initialize(NumericSetting.fun_log, 1),
|
||||||
debug: this.initialize(NumericSetting.debug_log, 1),
|
debug: this.initialize(NumericSetting.debug_log, 1),
|
||||||
assertion: this.initialize(NumericSetting.assertion_log, 1),
|
assertion: this.initialize(NumericSetting.assertion_log, 1),
|
||||||
|
searchTerm: "",
|
||||||
markdown: true,
|
markdown: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,13 +86,13 @@ export class RawLogs extends React.Component<LogsProps, Partial<LogsState>> {
|
||||||
const filterBtnColor = this.filterActive ? "green" : "gray";
|
const filterBtnColor = this.filterActive ? "green" : "gray";
|
||||||
return <Page className="logs-page">
|
return <Page className="logs-page">
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={7}>
|
<Col xs={6}>
|
||||||
<h3>
|
<h3>
|
||||||
<i>{t("Logs")}</i>
|
<i>{t("Logs")}</i>
|
||||||
</h3>
|
</h3>
|
||||||
<ToolTip helpText={ToolTips.LOGS} />
|
<ToolTip helpText={ToolTips.LOGS} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={5}>
|
<Col xs={6}>
|
||||||
<div className={"settings-menu-button"}>
|
<div className={"settings-menu-button"}>
|
||||||
<Popover position={Position.TOP_RIGHT}>
|
<Popover position={Position.TOP_RIGHT}>
|
||||||
<i className="fa fa-gear" />
|
<i className="fa fa-gear" />
|
||||||
|
@ -121,6 +122,19 @@ export class RawLogs extends React.Component<LogsProps, Partial<LogsState>> {
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col xs={12} md={5} lg={4}>
|
||||||
|
<div className="thin-search-wrapper">
|
||||||
|
<div className="text-input-wrapper">
|
||||||
|
<i className="fa fa-search" />
|
||||||
|
<input
|
||||||
|
onChange={e =>
|
||||||
|
this.setState({ searchTerm: e.currentTarget.value })}
|
||||||
|
placeholder={t("Search logs...")} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<LogsTable logs={this.props.logs}
|
<LogsTable logs={this.props.logs}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export type Filters = Record<ALLOWED_MESSAGE_TYPES, number>;
|
||||||
|
|
||||||
export interface LogsState extends Filters {
|
export interface LogsState extends Filters {
|
||||||
autoscroll: boolean;
|
autoscroll: boolean;
|
||||||
|
searchTerm: string;
|
||||||
markdown: boolean;
|
markdown: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,23 +9,23 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
|
||||||
return <div className="nav-additional-menu">
|
return <div className="nav-additional-menu">
|
||||||
<div>
|
<div>
|
||||||
<Link to="/app/account" onClick={props.close("accountMenuOpen")}>
|
<Link to="/app/account" onClick={props.close("accountMenuOpen")}>
|
||||||
<i className="fa fa-cog"></i>
|
<i className="fa fa-cog" />
|
||||||
{t("Account Settings")}
|
{t("Account Settings")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Link to="/app/logs" onClick={props.close("accountMenuOpen")}>
|
<Link to="/app/logs" onClick={props.close("accountMenuOpen")}>
|
||||||
<i className="fa fa-list"></i>
|
<i className="fa fa-list" />
|
||||||
{t("Logs")}
|
{t("Logs")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/app/help" onClick={props.close("accountMenuOpen")}>
|
<Link to="/app/help" onClick={props.close("accountMenuOpen")}>
|
||||||
<i className="fa fa-question-circle"></i>
|
<i className="fa fa-question-circle" />
|
||||||
{t("Help")}
|
{t("Help")}
|
||||||
</Link>
|
</Link>
|
||||||
<div>
|
<div>
|
||||||
<a onClick={props.logout}>
|
<a onClick={props.logout}>
|
||||||
<i className="fa fa-sign-out"></i>
|
<i className="fa fa-sign-out" />
|
||||||
{t("Logout")}
|
{t("Logout")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { store } from "../redux/store";
|
||||||
import { warning } from "../toast/toast";
|
import { warning } from "../toast/toast";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { appIsReadonly } from "./app_is_read_only";
|
import { appIsReadonly } from "./app_is_read_only";
|
||||||
|
import { t } from "../i18next_wrapper";
|
||||||
|
|
||||||
export const readOnlyInterceptor = (config: AxiosRequestConfig) => {
|
export const readOnlyInterceptor = (config: AxiosRequestConfig) => {
|
||||||
const method = (config.method || "get").toLowerCase();
|
const method = (config.method || "get").toLowerCase();
|
||||||
|
@ -10,7 +11,7 @@ export const readOnlyInterceptor = (config: AxiosRequestConfig) => {
|
||||||
|
|
||||||
if (relevant && appIsReadonly(store.getState().resources.index)) {
|
if (relevant && appIsReadonly(store.getState().resources.index)) {
|
||||||
if (!(config.url || "").includes("web_app_config")) {
|
if (!(config.url || "").includes("web_app_config")) {
|
||||||
warning("Refusing to modify data in read-only mode");
|
warning(t("Refusing to modify data in read-only mode"));
|
||||||
return Promise.reject(config);
|
return Promise.reject(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,19 +19,12 @@ export const readOnlyInterceptor = (config: AxiosRequestConfig) => {
|
||||||
return Promise.resolve(config);
|
return Promise.resolve(config);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOVE_ME_ELSEWHERE: React.CSSProperties = {
|
|
||||||
float: "right",
|
|
||||||
boxSizing: "inherit",
|
|
||||||
margin: "9px 0px 0px 9px"
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ReadOnlyIcon = (p: { locked: boolean }) => {
|
export const ReadOnlyIcon = (p: { locked: boolean }) => {
|
||||||
if (p.locked) {
|
if (p.locked) {
|
||||||
return <div className="fa-stack fa-lg" style={MOVE_ME_ELSEWHERE}>
|
return <div className=" read-only-icon fa-stack fa-lg">
|
||||||
<i className="fa fa-pencil fa-stack-1x"></i>
|
<i className="fa fa-pencil fa-stack-1x" />
|
||||||
<i className="fa fa-ban fa-stack-2x fa-rotate-90 text-danger"></i>
|
<i className="fa fa-ban fa-stack-2x fa-rotate-90 text-danger" />
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,7 @@ export class RawRegimens extends React.Component<Props, {}> {
|
||||||
<Row>
|
<Row>
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
className={`regimen-list-panel ${activeClasses}`}
|
className={`regimen-list-panel ${activeClasses}`}
|
||||||
title={t("Regimens")}
|
title={t("Regimens")}>
|
||||||
helpText={t(ToolTips.REGIMEN_LIST)}>
|
|
||||||
<RegimensList
|
<RegimensList
|
||||||
usageStats={this.props.regimenUsageStats}
|
usageStats={this.props.regimenUsageStats}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
|
|
@ -18,10 +18,10 @@ const RegimenListHeader = (props: RegimenListHeaderProps) =>
|
||||||
<div className={"panel-top with-button"}>
|
<div className={"panel-top with-button"}>
|
||||||
<div className="thin-search-wrapper">
|
<div className="thin-search-wrapper">
|
||||||
<div className="text-input-wrapper">
|
<div className="text-input-wrapper">
|
||||||
<i className="fa fa-search"></i>
|
<i className="fa fa-search" />
|
||||||
<input
|
<input
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
placeholder={t("Search Regimens...")} />
|
placeholder={t("Search regimens...")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddRegimen dispatch={props.dispatch} length={props.regimenCount} />
|
<AddRegimen dispatch={props.dispatch} length={props.regimenCount} />
|
||||||
|
|
|
@ -11,8 +11,6 @@ import {
|
||||||
isTaggedGenericPointer,
|
isTaggedGenericPointer,
|
||||||
isTaggedSavedGarden,
|
isTaggedSavedGarden,
|
||||||
isTaggedFolder,
|
isTaggedFolder,
|
||||||
isTaggedPoint,
|
|
||||||
isTaggedPointGroup,
|
|
||||||
} from "./tagged_resources";
|
} from "./tagged_resources";
|
||||||
import {
|
import {
|
||||||
ResourceName,
|
ResourceName,
|
||||||
|
@ -127,20 +125,6 @@ export function maybeFindGenericPointerById(index: ResourceIndex, id: number) {
|
||||||
if (resource && isTaggedGenericPointer(resource)) { return resource; }
|
if (resource && isTaggedGenericPointer(resource)) { return resource; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unlike other findById methods, this one allows undefined (missed) values */
|
|
||||||
export function maybeFindPointById(index: ResourceIndex, id: number) {
|
|
||||||
const uuid = index.byKindAndId[joinKindAndId("Point", id)];
|
|
||||||
const resource = index.references[uuid || "nope"];
|
|
||||||
if (resource && isTaggedPoint(resource)) { return resource; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unlike other findById methods, this one allows undefined (missed) values */
|
|
||||||
export function maybeFindGroupById(index: ResourceIndex, id: number) {
|
|
||||||
const uuid = index.byKindAndId[joinKindAndId("PointGroup", id)];
|
|
||||||
const resource = index.references[uuid || "nope"];
|
|
||||||
if (resource && isTaggedPointGroup(resource)) { return resource; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Unlike other findById methods, this one allows undefined (missed) values */
|
/** Unlike other findById methods, this one allows undefined (missed) values */
|
||||||
export function maybeFindSavedGardenById(index: ResourceIndex, id: number) {
|
export function maybeFindSavedGardenById(index: ResourceIndex, id: number) {
|
||||||
const uuid = index.byKindAndId[joinKindAndId("SavedGarden", id)];
|
const uuid = index.byKindAndId[joinKindAndId("SavedGarden", id)];
|
||||||
|
|
|
@ -100,7 +100,7 @@ export function InnerIf(props: IfParams) {
|
||||||
confirmStepDeletion={confirmStepDeletion}>
|
confirmStepDeletion={confirmStepDeletion}>
|
||||||
{recursive &&
|
{recursive &&
|
||||||
<span>
|
<span>
|
||||||
<i className="fa fa-exclamation-triangle"></i>
|
<i className="fa fa-exclamation-triangle" />
|
||||||
{t("Recursive condition.")}
|
{t("Recursive condition.")}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ export function BackArrow(props: BackArrowProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return <a onClick={onClick} className="back-arrow">
|
return <a onClick={onClick} className="back-arrow">
|
||||||
<i className="fa fa-arrow-left"></i>
|
<i className="fa fa-arrow-left" />
|
||||||
</a>;
|
</a>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue