commit
310686508f
|
@ -27,7 +27,7 @@ module Devices
|
|||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: -200,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
|
@ -37,25 +37,18 @@ module Devices
|
|||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: -200,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_3
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_3,
|
||||
x: 0,
|
||||
y: 75,
|
||||
z: -200,
|
||||
tool: tools_seed_trough_3,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_3; end
|
||||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
|
||||
|
@ -69,11 +62,6 @@ module Devices
|
|||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
|
||||
def tools_seed_trough_3
|
||||
@tools_seed_trough_3 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_3)
|
||||
end
|
||||
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
|
|
@ -75,6 +75,9 @@ module Devices
|
|||
tool: tools_weeder)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
|
||||
def tools_seed_bin
|
||||
@tools_seed_bin ||=
|
||||
add_tool(ToolNames::SEED_BIN)
|
||||
|
@ -87,7 +90,6 @@ module Devices
|
|||
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
|
||||
def tools_seeder
|
||||
@tools_seeder ||=
|
||||
|
|
|
@ -37,7 +37,6 @@ module Devices
|
|||
:tools_seed_tray,
|
||||
:tools_seed_trough_1,
|
||||
:tools_seed_trough_2,
|
||||
:tools_seed_trough_3,
|
||||
:tools_seeder,
|
||||
:tools_soil_sensor,
|
||||
:tools_watering_nozzle,
|
||||
|
@ -50,6 +49,8 @@ module Devices
|
|||
:tool_slots_slot_4,
|
||||
:tool_slots_slot_5,
|
||||
:tool_slots_slot_6,
|
||||
:tool_slots_slot_7,
|
||||
:tool_slots_slot_8,
|
||||
|
||||
# WEBCAM FEEDS ===========================
|
||||
:webcam_feeds,
|
||||
|
@ -152,11 +153,12 @@ module Devices
|
|||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
|
|
@ -31,7 +31,6 @@ module Devices
|
|||
LIGHTING = "Lighting"
|
||||
SEED_TROUGH_1 = "Seed Trough 1"
|
||||
SEED_TROUGH_2 = "Seed Trough 2"
|
||||
SEED_TROUGH_3 = "Seed Trough 3"
|
||||
end
|
||||
|
||||
# Stub plants ==============================
|
||||
|
|
|
@ -6,6 +6,36 @@ module Devices
|
|||
.fbos_config
|
||||
.update!(firmware_hardware: FbosConfig::FARMDUINO_K15)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_8
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tools_seed_trough_1
|
||||
@tools_seed_trough_1 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_1)
|
||||
end
|
||||
|
||||
def tools_seed_trough_2
|
||||
@tools_seed_trough_2 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,36 @@ module Devices
|
|||
def settings_default_map_size_y
|
||||
device.web_app_config.update!(map_size_y: 2_900)
|
||||
end
|
||||
|
||||
def tool_slots_slot_7
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||
x: 0,
|
||||
y: 25,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_1,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tool_slots_slot_8
|
||||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||
x: 0,
|
||||
y: 50,
|
||||
z: 0,
|
||||
tool: tools_seed_trough_2,
|
||||
pullout_direction: ToolSlot::NONE,
|
||||
gantry_mounted: true)
|
||||
end
|
||||
|
||||
def tools_seed_trough_1
|
||||
@tools_seed_trough_1 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_1)
|
||||
end
|
||||
|
||||
def tools_seed_trough_2
|
||||
@tools_seed_trough_2 ||=
|
||||
add_tool(ToolNames::SEED_TROUGH_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,11 +28,12 @@ module Devices
|
|||
def tool_slots_slot_4; end
|
||||
def tool_slots_slot_5; end
|
||||
def tool_slots_slot_6; end
|
||||
def tool_slots_slot_7; end
|
||||
def tool_slots_slot_8; end
|
||||
def tools_seed_bin; end
|
||||
def tools_seed_tray; end
|
||||
def tools_seed_trough_1; end
|
||||
def tools_seed_trough_2; end
|
||||
def tools_seed_trough_3; end
|
||||
def tools_seeder; end
|
||||
def tools_soil_sensor; end
|
||||
def tools_watering_nozzle; end
|
||||
|
|
|
@ -4,7 +4,10 @@ export const panelState = (): ControlPanelState => {
|
|||
return {
|
||||
homing_and_calibration: false,
|
||||
motors: false,
|
||||
encoders_and_endstops: false,
|
||||
encoders: false,
|
||||
endstops: false,
|
||||
error_handling: false,
|
||||
pin_bindings: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false
|
||||
|
|
|
@ -4,12 +4,15 @@ export const bot: Everything["bot"] = {
|
|||
"consistent": true,
|
||||
"stepSize": 100,
|
||||
"controlPanelState": {
|
||||
"homing_and_calibration": false,
|
||||
"motors": false,
|
||||
"encoders_and_endstops": false,
|
||||
"danger_zone": false,
|
||||
"power_and_reset": false,
|
||||
"pin_guard": false,
|
||||
homing_and_calibration: false,
|
||||
motors: false,
|
||||
encoders: false,
|
||||
endstops: false,
|
||||
error_handling: false,
|
||||
pin_bindings: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false,
|
||||
},
|
||||
"hardware": {
|
||||
"gpio_registry": {},
|
||||
|
|
|
@ -157,7 +157,6 @@ describe("mapStateToProps()", () => {
|
|||
const state = fakeState();
|
||||
const config = fakeFbosConfig();
|
||||
config.body.auto_sync = true;
|
||||
config.body.api_migrated = true;
|
||||
const fakeEnv = fakeFarmwareEnv();
|
||||
state.resources = buildResourceIndex([config, fakeEnv]);
|
||||
state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" };
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
jest.unmock("../external_urls");
|
||||
import { ExternalUrl } from "../external_urls";
|
||||
|
||||
/* tslint:disable:max-line-length */
|
||||
|
||||
describe("ExternalUrl", () => {
|
||||
it("returns urls", () => {
|
||||
expect(ExternalUrl.featureMinVersions)
|
||||
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/FEATURE_MIN_VERSIONS.json");
|
||||
expect(ExternalUrl.osReleaseNotes)
|
||||
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/RELEASE_NOTES.md");
|
||||
expect(ExternalUrl.latestRelease)
|
||||
.toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest");
|
||||
expect(ExternalUrl.webAppRepo)
|
||||
.toEqual("https://github.com/FarmBot/Farmbot-Web-App");
|
||||
expect(ExternalUrl.gitHubFarmBot)
|
||||
.toEqual("https://github.com/FarmBot");
|
||||
expect(ExternalUrl.softwareDocs)
|
||||
.toEqual("https://software.farm.bot/docs");
|
||||
expect(ExternalUrl.softwareForum)
|
||||
.toEqual("http://forum.farmbot.org/c/software");
|
||||
expect(ExternalUrl.OpenFarm.cropApi)
|
||||
.toEqual("https://openfarm.cc/api/v1/crops/");
|
||||
expect(ExternalUrl.OpenFarm.cropBrowse)
|
||||
.toEqual("https://openfarm.cc/crops/");
|
||||
expect(ExternalUrl.OpenFarm.newCrop)
|
||||
.toEqual("https://openfarm.cc/en/crops/new");
|
||||
expect(ExternalUrl.Videos.desktop)
|
||||
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018");
|
||||
expect(ExternalUrl.Videos.mobile)
|
||||
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097");
|
||||
});
|
||||
});
|
|
@ -158,6 +158,10 @@ export class API {
|
|||
get farmwareInstallationPath() {
|
||||
return `${this.baseUrl}/api/farmware_installations/`;
|
||||
}
|
||||
/** /api/first_party_farmwares */
|
||||
get firstPartyFarmwarePath() {
|
||||
return `${this.baseUrl}/api/first_party_farmwares`;
|
||||
}
|
||||
/** /api/alerts/:id */
|
||||
get alertPath() { return `${this.baseUrl}/api/alerts/`; }
|
||||
/** /api/global_bulletins/:id */
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from "react";
|
||||
import { Session } from "./session";
|
||||
import { ExternalUrl } from "./external_urls";
|
||||
|
||||
const OUTER_STYLE: React.CSSProperties = {
|
||||
borderRadius: "10px",
|
||||
|
@ -47,7 +48,7 @@ export function Apology(_: {}) {
|
|||
<li>
|
||||
<span>
|
||||
Send a report to our developer team via the
|
||||
<a href="http://forum.farmbot.org/c/software">FarmBot software
|
||||
<a href={ExternalUrl.softwareForum}>FarmBot software
|
||||
forum</a>. Including additional information (such as steps leading up
|
||||
to the error) helps us identify solutions more quickly.
|
||||
</span>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import {
|
||||
fetchReleases, fetchMinOsFeatureData, FEATURE_MIN_VERSIONS_URL,
|
||||
fetchReleases, fetchMinOsFeatureData,
|
||||
fetchLatestGHBetaRelease
|
||||
} from "../devices/actions";
|
||||
import { AuthState } from "./interfaces";
|
||||
|
@ -16,6 +16,7 @@ import { Actions } from "../constants";
|
|||
import { connectDevice } from "../connectivity/connect_device";
|
||||
import { getFirstPartyFarmwareList } from "../farmware/actions";
|
||||
import { readOnlyInterceptor } from "../read_only_mode";
|
||||
import { ExternalUrl } from "../external_urls";
|
||||
|
||||
export function didLogin(authState: AuthState, dispatch: Function) {
|
||||
API.setBaseUrl(authState.token.unencoded.iss);
|
||||
|
@ -24,7 +25,7 @@ export function didLogin(authState: AuthState, dispatch: Function) {
|
|||
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
|
||||
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
|
||||
dispatch(getFirstPartyFarmwareList());
|
||||
dispatch(fetchMinOsFeatureData(FEATURE_MIN_VERSIONS_URL));
|
||||
dispatch(fetchMinOsFeatureData(ExternalUrl.featureMinVersions));
|
||||
dispatch(setToken(authState));
|
||||
Sync.fetchSyncData(dispatch);
|
||||
dispatch(connectDevice(authState));
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
jest.mock("../../slow_down", () => {
|
||||
return {
|
||||
slowDown: jest.fn((fn: Function) => fn),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("../../../devices/actions", () => ({
|
||||
badVersion: jest.fn(),
|
||||
EXPECTED_MAJOR: 1,
|
||||
EXPECTED_MINOR: 0,
|
||||
jest.mock("../../slow_down", () => ({
|
||||
slowDown: jest.fn((fn: Function) => fn)
|
||||
}));
|
||||
|
||||
jest.mock("../../../devices/actions", () => ({ badVersion: jest.fn() }));
|
||||
|
||||
import {
|
||||
onStatus,
|
||||
incomingStatus,
|
||||
|
@ -49,8 +43,10 @@ describe("onStatus()", () => {
|
|||
});
|
||||
|
||||
it("version ok", () => {
|
||||
globalConfig.MINIMUM_FBOS_VERSION = "1.0.0";
|
||||
callOnStatus("1.0.0");
|
||||
expect(badVersion).not.toHaveBeenCalled();
|
||||
delete globalConfig.MINIMUM_FBOS_VERSION;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -8,13 +8,7 @@ import { success, error, info, warning, fun, busy } from "../toast/toast";
|
|||
import { HardwareState } from "../devices/interfaces";
|
||||
import { GetState, ReduxAction } from "../redux/interfaces";
|
||||
import { Content, Actions } from "../constants";
|
||||
import {
|
||||
EXPECTED_MAJOR,
|
||||
EXPECTED_MINOR,
|
||||
commandOK,
|
||||
badVersion,
|
||||
commandErr
|
||||
} from "../devices/actions";
|
||||
import { commandOK, badVersion, commandErr } from "../devices/actions";
|
||||
import { init } from "../api/crud";
|
||||
import { AuthState } from "../auth/interfaces";
|
||||
import { autoSync } from "./auto_sync";
|
||||
|
@ -123,7 +117,7 @@ const setBothUp = () => bothUp();
|
|||
const legacyChecks = (getState: GetState) => {
|
||||
const { controller_version } = getState().bot.hardware.informational_settings;
|
||||
if (HACKY_FLAGS.needVersionCheck && controller_version) {
|
||||
const IS_OK = versionOK(controller_version, EXPECTED_MAJOR, EXPECTED_MINOR);
|
||||
const IS_OK = versionOK(controller_version);
|
||||
if (!IS_OK) { badVersion(); }
|
||||
HACKY_FLAGS.needVersionCheck = false;
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ export namespace ToolTips {
|
|||
few sequences to verify that everything works as expected.`);
|
||||
|
||||
export const PIN_BINDINGS =
|
||||
trim(`Assign a sequence to execute when a Raspberry Pi GPIO pin is
|
||||
activated.`);
|
||||
trim(`Assign an action or sequence to execute when a Raspberry Pi
|
||||
GPIO pin is activated.`);
|
||||
|
||||
export const PIN_BINDING_WARNING =
|
||||
trim(`Warning: Binding to a pin without a physical button and
|
||||
|
@ -51,24 +51,38 @@ export namespace ToolTips {
|
|||
trim(`Diagnose connectivity issues with FarmBot and the browser.`);
|
||||
|
||||
// Hardware Settings: Homing and Calibration
|
||||
export const HOMING =
|
||||
export const HOMING_ENCODERS =
|
||||
trim(`If encoders or end-stops are enabled, home axis (find zero).`);
|
||||
|
||||
export const CALIBRATION =
|
||||
export const HOMING_STALL_DETECTION =
|
||||
trim(`If stall detection or end-stops are enabled, home axis
|
||||
(find zero).`);
|
||||
|
||||
export const CALIBRATION_ENCODERS =
|
||||
trim(`If encoders or end-stops are enabled, home axis and determine
|
||||
maximum.`);
|
||||
|
||||
export const CALIBRATION_STALL_DETECTION =
|
||||
trim(`If stall detection or end-stops are enabled, home axis and
|
||||
determine maximum.`);
|
||||
|
||||
export const SET_ZERO_POSITION =
|
||||
trim(`Set the current location as zero.`);
|
||||
|
||||
export const FIND_HOME_ON_BOOT =
|
||||
export const FIND_HOME_ON_BOOT_ENCODERS =
|
||||
trim(`If encoders or end-stops are enabled, find the home position
|
||||
when the device powers on.
|
||||
Warning! This will perform homing on all axes when the
|
||||
device powers on. Encoders or endstops must be enabled.
|
||||
when the device powers on. Warning! This will perform homing on all
|
||||
axes when the device powers on. Encoders or endstops must be enabled.
|
||||
It is recommended to make sure homing works properly before enabling
|
||||
this feature. (default: disabled)`);
|
||||
|
||||
export const FIND_HOME_ON_BOOT_STALL_DETECTION =
|
||||
trim(`If stall detection or end-stops are enabled, find the home
|
||||
position when the device powers on. Warning! This will perform homing
|
||||
on all axes when the device powers on. Stall detection or endstops
|
||||
must be enabled. It is recommended to make sure homing works properly
|
||||
before enabling this feature. (default: disabled)`);
|
||||
|
||||
export const STOP_AT_HOME =
|
||||
trim(`Stop at the home location of the axis. (default: disabled)`);
|
||||
|
||||
|
@ -85,18 +99,7 @@ export namespace ToolTips {
|
|||
trim(`Set the length of each axis to provide software limits.
|
||||
Used only if STOP AT MAX is enabled. (default: 0 (disabled))`);
|
||||
|
||||
export const TIMEOUT_AFTER =
|
||||
trim(`Amount of time to wait for a command to execute before stopping.
|
||||
(default: 120s)`);
|
||||
|
||||
// Hardware Settings: Motors
|
||||
export const MAX_MOVEMENT_RETRIES =
|
||||
trim(`Number of times to retry a movement before stopping. (default: 3)`);
|
||||
|
||||
export const E_STOP_ON_MOV_ERR =
|
||||
trim(`Emergency stop if movement is not complete after the maximum
|
||||
number of retries. (default: disabled)`);
|
||||
|
||||
export const MAX_SPEED =
|
||||
trim(`Maximum travel speed after acceleration in millimeters per second.
|
||||
(default: x: 80mm/s, y: 80mm/s, z: 16mm/s)`);
|
||||
|
@ -132,18 +135,22 @@ export namespace ToolTips {
|
|||
export const MOTOR_CURRENT =
|
||||
trim(`Motor current in milliamps. (default: 600)`);
|
||||
|
||||
export const STALL_SENSITIVITY =
|
||||
trim(`Motor stall sensitivity. (default: 30)`);
|
||||
|
||||
export const ENABLE_X2_MOTOR =
|
||||
trim(`Enable use of a second x-axis motor. Connects to E0 on RAMPS.
|
||||
(default: enabled)`);
|
||||
|
||||
// Hardware Settings: Encoders and Endstops
|
||||
// Hardware Settings: Encoders / Stall Detection
|
||||
export const ENABLE_ENCODERS =
|
||||
trim(`Enable use of rotary encoders for stall detection,
|
||||
calibration and homing. (default: enabled)`);
|
||||
|
||||
export const ENABLE_STALL_DETECTION =
|
||||
trim(`Enable use of motor stall detection for detecting missed steps,
|
||||
calibration and homing. (default: enabled)`);
|
||||
|
||||
export const STALL_SENSITIVITY =
|
||||
trim(`Motor stall sensitivity. (default: 30)`);
|
||||
|
||||
export const ENCODER_POSITIONING =
|
||||
trim(`Use encoders for positioning. (default: disabled)`);
|
||||
|
||||
|
@ -151,17 +158,22 @@ export namespace ToolTips {
|
|||
trim(`Reverse the direction of encoder position reading.
|
||||
(default: disabled)`);
|
||||
|
||||
export const MAX_MISSED_STEPS =
|
||||
export const MAX_MISSED_STEPS_ENCODERS =
|
||||
trim(`Number of steps missed (determined by encoder) before motor is
|
||||
considered to have stalled. (default: 5)`);
|
||||
|
||||
export const ENCODER_MISSED_STEP_DECAY =
|
||||
export const MAX_MISSED_STEPS_STALL_DETECTION =
|
||||
trim(`Number of steps missed (determined by motor stall detection) before
|
||||
motor is considered to have stalled. (default: 5)`);
|
||||
|
||||
export const MISSED_STEP_DECAY =
|
||||
trim(`Reduction to missed step total for every good step. (default: 5)`);
|
||||
|
||||
export const ENCODER_SCALING =
|
||||
trim(`encoder scaling factor = 10000 * (motor resolution * microsteps)
|
||||
/ (encoder resolution). (default: 5556 (10000*200/360))`);
|
||||
|
||||
// Hardware Settings: Endstops
|
||||
export const ENABLE_ENDSTOPS =
|
||||
trim(`Enable use of electronic end-stops for end detection,
|
||||
calibration and homing. (default: disabled)`);
|
||||
|
@ -173,6 +185,18 @@ export namespace ToolTips {
|
|||
trim(`Invert axis end-stops. Enable for normally closed (NC),
|
||||
disable for normally open (NO). (default: disabled)`);
|
||||
|
||||
// Hardware Settings: Error Handling
|
||||
export const TIMEOUT_AFTER =
|
||||
trim(`Amount of time to wait for a command to execute before stopping.
|
||||
(default: 120s)`);
|
||||
|
||||
export const MAX_MOVEMENT_RETRIES =
|
||||
trim(`Number of times to retry a movement before stopping. (default: 3)`);
|
||||
|
||||
export const E_STOP_ON_MOV_ERR =
|
||||
trim(`Emergency stop if movement is not complete after the maximum
|
||||
number of retries. (default: disabled)`);
|
||||
|
||||
// Hardware Settings: Pin Guard
|
||||
export const PIN_GUARD_PIN_NUMBER =
|
||||
trim(`The number of the pin to guard. This pin will be set to the specified
|
||||
|
@ -263,8 +287,12 @@ export namespace ToolTips {
|
|||
|
||||
export const FIND_HOME =
|
||||
trim(`The Find Home step instructs the device to perform a homing
|
||||
command (using encoders or endstops) to find and set zero for
|
||||
the chosen axis or axes.`);
|
||||
command (using encoders, stall detection, or endstops) to find and set
|
||||
zero for the chosen axis or axes.`);
|
||||
|
||||
export const CALIBRATE =
|
||||
trim(`If encoders, stall detection, or end-stops are enabled,
|
||||
home axis and determine maximum.`);
|
||||
|
||||
export const IF =
|
||||
trim(`Execute a sequence if a condition is satisfied. If the condition
|
||||
|
@ -715,8 +743,8 @@ export namespace Content {
|
|||
|
||||
export const END_DETECTION_DISABLED =
|
||||
trim(`This command will not execute correctly because you do not have
|
||||
encoders or endstops enabled for the chosen axis. Enable endstops or
|
||||
encoders from the Device page for: `);
|
||||
encoders, stall detection, or endstops enabled for the chosen axis.
|
||||
Enable endstops, encoders, or stall detection from the Device page for: `);
|
||||
|
||||
export const IN_USE =
|
||||
trim(`Used in another resource. Protected from deletion.`);
|
||||
|
@ -924,8 +952,7 @@ export namespace DiagnosticMessages {
|
|||
but we have no recent record of FarmBot connecting to the internet.
|
||||
This usually happens because of poor WiFi connectivity in the garden,
|
||||
a bad password during configuration, a very long power outage, or
|
||||
blocked ports on FarmBot's local network. Please refer IT staff to
|
||||
https://software.farm.bot/docs/for-it-security-professionals`);
|
||||
blocked ports on FarmBot's local network. Please refer IT staff to:`);
|
||||
|
||||
export const NO_WS_AVAILABLE = trim(`You are either offline, using a web
|
||||
browser that does not support WebSockets, or are behind a firewall that
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from "react";
|
|||
import { get } from "lodash";
|
||||
import { Page } from "./ui/index";
|
||||
import { Session } from "./session";
|
||||
import { ExternalUrl } from "./external_urls";
|
||||
|
||||
/** Use currying to pass down `error` object for now. */
|
||||
export function crashPage(error: object) {
|
||||
|
@ -24,7 +25,7 @@ export function crashPage(error: object) {
|
|||
<li>Perform a "hard refresh" (<strong>CTRL + SHIFT + R</strong> on most machines).</li>
|
||||
<li><span><a onClick={() => Session.clear()}>Log out by clicking here.</a></span></li>
|
||||
<li>Send the error information (below) to our developer team via the
|
||||
<a href="http://forum.farmbot.org/c/software">FarmBot software
|
||||
<a href={ExternalUrl.softwareForum}>FarmBot software
|
||||
forum</a>. Including additional information (such as steps leading up
|
||||
to the error) help us identify solutions more quickly. </li>
|
||||
</ol>
|
||||
|
|
|
@ -580,6 +580,7 @@
|
|||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
.tools-header,
|
||||
.tool-slots-header {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
|
@ -624,6 +625,12 @@
|
|||
}
|
||||
}
|
||||
.add-stock-tools {
|
||||
.filter-search {
|
||||
margin-bottom: 1rem;
|
||||
button {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
font-size: 1.2rem;
|
||||
padding-left: 1rem;
|
||||
|
|
|
@ -433,14 +433,17 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
.pin-bindings-widget {
|
||||
.pin-bindings {
|
||||
.fa-exclamation-triangle {
|
||||
color: $orange;
|
||||
margin-left: 1rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.fa-th-large {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
left: 0.5rem;
|
||||
color: $dark_gray;
|
||||
margin-top: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.fb-button {
|
||||
&.green {
|
||||
|
@ -449,16 +452,27 @@ a {
|
|||
}
|
||||
.bindings-list {
|
||||
margin-bottom: 1rem;
|
||||
margin-left: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.binding-type-dropdown {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.stock-pin-bindings-button {
|
||||
button {
|
||||
margin: 0 !important;
|
||||
margin: 1rem;
|
||||
float: left;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
.bp3-popover-wrapper {
|
||||
display: inline;
|
||||
float: none !important;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sensor-history-widget {
|
||||
|
@ -1308,6 +1322,12 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.boolean-camera-calibration-config {
|
||||
input[type=checkbox] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.tour-list {
|
||||
margin: auto;
|
||||
max-width: 300px;
|
||||
|
@ -1519,16 +1539,24 @@ textarea:focus {
|
|||
box-shadow: 0 0 10px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.sort-path-info-bar {
|
||||
background: lightgray;
|
||||
.sort-option-bar {
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1.75rem;
|
||||
&:hover {
|
||||
background: darken(lightgray, 10%);
|
||||
border: 2px solid $panel_light_blue;
|
||||
&:hover, &.selected {
|
||||
border: 2px solid $medium_gray;
|
||||
border-radius: 2px;
|
||||
.sort-path-info-bar {
|
||||
background: darken($light_gray, 10%);
|
||||
}
|
||||
}
|
||||
.sort-path-info-bar {
|
||||
background: $light_gray;
|
||||
font-size: 1.2rem;
|
||||
padding-left: 0.5rem;
|
||||
white-space: nowrap;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.camera-calibration,
|
||||
.weed-detector{
|
||||
.farmware-button{
|
||||
position: relative;
|
||||
|
|
|
@ -2,16 +2,13 @@ import { connect, MqttClient } from "mqtt";
|
|||
import React from "react";
|
||||
import { uuid } from "farmbot";
|
||||
import axios from "axios";
|
||||
import { ExternalUrl } from "../external_urls";
|
||||
|
||||
interface State {
|
||||
error: Error | undefined;
|
||||
stage: string;
|
||||
}
|
||||
|
||||
const VIDEO_URL =
|
||||
"https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018";
|
||||
const PHONE_URL =
|
||||
"https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097";
|
||||
const WS_CONFIG = {
|
||||
username: "farmbot_demo",
|
||||
password: "required, but not used.",
|
||||
|
@ -63,9 +60,9 @@ export class DemoIframe extends React.Component<{}, State> {
|
|||
|
||||
return <div className="demo-container">
|
||||
<video muted={true} autoPlay={true} loop={true} className="demo-video">
|
||||
<source src={VIDEO_URL} type="video/mp4" />
|
||||
<source src={ExternalUrl.Videos.desktop} type="video/mp4" />
|
||||
</video>
|
||||
<img className="demo-phone" src={PHONE_URL} />
|
||||
<img className="demo-phone" src={ExternalUrl.Videos.mobile} />
|
||||
<button className="demo-button" onClick={this.requestAccount}>
|
||||
{this.state.stage}
|
||||
</button>
|
||||
|
|
|
@ -307,7 +307,7 @@ describe("commandErr()", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("toggleControlPanel()", function () {
|
||||
describe("toggleControlPanel()", () => {
|
||||
it("toggles", () => {
|
||||
const action = actions.toggleControlPanel("homing_and_calibration");
|
||||
expect(action.payload).toEqual("homing_and_calibration");
|
||||
|
|
|
@ -35,7 +35,6 @@ describe("mapStateToProps()", () => {
|
|||
it("uses the API as the source of FBOS settings", () => {
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.auto_sync = true;
|
||||
fakeApiConfig.body.api_migrated = true;
|
||||
mockFbosConfig = fakeApiConfig;
|
||||
const props = mapStateToProps(fakeState());
|
||||
expect(props.sourceFbosConfig("auto_sync")).toEqual({
|
||||
|
@ -53,19 +52,6 @@ describe("mapStateToProps()", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("uses the bot as the source of FBOS settings: ignore API defaults", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.configuration.auto_sync = false;
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.auto_sync = true;
|
||||
fakeApiConfig.body.api_migrated = false;
|
||||
mockFbosConfig = fakeApiConfig;
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.sourceFbosConfig("auto_sync")).toEqual({
|
||||
value: false, consistent: true
|
||||
});
|
||||
});
|
||||
|
||||
it("returns API Farmware env vars", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.user_env = {};
|
||||
|
|
|
@ -26,11 +26,6 @@ import { t } from "../i18next_wrapper";
|
|||
|
||||
const ON = 1, OFF = 0;
|
||||
export type ConfigKey = keyof McuParams;
|
||||
export const EXPECTED_MAJOR = 6;
|
||||
export const EXPECTED_MINOR = 0;
|
||||
export const FEATURE_MIN_VERSIONS_URL =
|
||||
"https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/" +
|
||||
"FEATURE_MIN_VERSIONS.json";
|
||||
// Already filtering messages in FarmBot OS and the API- this is just for
|
||||
// an additional layer of safety.
|
||||
const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"];
|
||||
|
@ -132,7 +127,7 @@ export function sync(): Thunk {
|
|||
return function (_dispatch, getState) {
|
||||
const currentFBOSversion =
|
||||
getState().bot.hardware.informational_settings.controller_version;
|
||||
const IS_OK = versionOK(currentFBOSversion, EXPECTED_MAJOR, EXPECTED_MINOR);
|
||||
const IS_OK = versionOK(currentFBOSversion);
|
||||
if (IS_OK) {
|
||||
getDevice()
|
||||
.sync()
|
||||
|
|
|
@ -65,13 +65,7 @@ describe("<HardwareSettings />", () => {
|
|||
it("shows param export menu", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareConfig = fakeFirmwareConfig().body;
|
||||
p.firmwareConfig.api_migrated = true;
|
||||
const wrapper = shallow(<HardwareSettings {...p} />);
|
||||
expect(wrapper.html()).toContain("fa-download");
|
||||
});
|
||||
|
||||
it("doesn't show param export menu", () => {
|
||||
const wrapper = shallow(<HardwareSettings {...fakeProps()} />);
|
||||
expect(wrapper.html()).not.toContain("fa-download");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import { t } from "../../i18next_wrapper";
|
|||
import { FarmbotOsProps, FarmbotOsState, Feature } from "../interfaces";
|
||||
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui";
|
||||
import { save, edit } from "../../api/crud";
|
||||
import { MustBeOnline, isBotOnline } from "../must_be_online";
|
||||
import { isBotOnline } from "../must_be_online";
|
||||
import { Content } from "../../constants";
|
||||
import { TimezoneSelector } from "../timezones/timezone_selector";
|
||||
import { timezoneMismatch } from "../timezones/guess_timezone";
|
||||
|
@ -15,6 +15,7 @@ import { AutoUpdateRow } from "./fbos_settings/auto_update_row";
|
|||
import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
|
||||
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
||||
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
|
||||
import { ExternalUrl } from "../../external_urls";
|
||||
|
||||
export enum ColWidth {
|
||||
label = 3,
|
||||
|
@ -22,15 +23,12 @@ export enum ColWidth {
|
|||
button = 2
|
||||
}
|
||||
|
||||
const OS_RELEASE_NOTES_URL =
|
||||
"https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md";
|
||||
|
||||
export class FarmbotOsSettings
|
||||
extends React.Component<FarmbotOsProps, FarmbotOsState> {
|
||||
state: FarmbotOsState = { allOsReleaseNotes: "" };
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchReleaseNotes(OS_RELEASE_NOTES_URL);
|
||||
this.fetchReleaseNotes(ExternalUrl.osReleaseNotes);
|
||||
}
|
||||
|
||||
get osMajorVersion() {
|
||||
|
@ -116,54 +114,46 @@ export class FarmbotOsSettings
|
|||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<MustBeOnline
|
||||
syncStatus={sync_status}
|
||||
networkState={this.props.botToMqttStatus}
|
||||
lockOpen={process.env.NODE_ENV !== "production"
|
||||
|| this.props.isValidFbosConfig}>
|
||||
<CameraSelection
|
||||
env={this.props.env}
|
||||
botOnline={botOnline}
|
||||
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
dispatch={this.props.dispatch} />
|
||||
<BoardType
|
||||
botOnline={botOnline}
|
||||
bot={bot}
|
||||
alerts={this.props.alerts}
|
||||
dispatch={this.props.dispatch}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
timeSettings={this.props.timeSettings}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<AutoUpdateRow
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
timeFormat={timeFormat}
|
||||
device={this.props.deviceAccount}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<FarmbotOsRow
|
||||
bot={this.props.bot}
|
||||
osReleaseNotesHeading={this.osReleaseNotes.heading}
|
||||
osReleaseNotes={this.osReleaseNotes.notes}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline}
|
||||
botToMqttLastSeen={new Date(this.props.botToMqttLastSeen).getTime()}
|
||||
timeSettings={this.props.timeSettings}
|
||||
deviceAccount={this.props.deviceAccount} />
|
||||
<AutoSyncRow
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
{this.props.shouldDisplay(Feature.boot_sequence) &&
|
||||
<BootSequenceSelector />}
|
||||
<PowerAndReset
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline} />
|
||||
</MustBeOnline>
|
||||
<CameraSelection
|
||||
env={this.props.env}
|
||||
botOnline={botOnline}
|
||||
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
dispatch={this.props.dispatch} />
|
||||
<BoardType
|
||||
botOnline={botOnline}
|
||||
bot={bot}
|
||||
alerts={this.props.alerts}
|
||||
dispatch={this.props.dispatch}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
timeSettings={this.props.timeSettings}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<AutoUpdateRow
|
||||
timeFormat={timeFormat}
|
||||
device={this.props.deviceAccount}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<FarmbotOsRow
|
||||
bot={this.props.bot}
|
||||
osReleaseNotesHeading={this.osReleaseNotes.heading}
|
||||
osReleaseNotes={this.osReleaseNotes.notes}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline}
|
||||
botToMqttLastSeen={new Date(this.props.botToMqttLastSeen).getTime()}
|
||||
timeSettings={this.props.timeSettings}
|
||||
deviceAccount={this.props.deviceAccount} />
|
||||
<AutoSyncRow
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
{this.props.shouldDisplay(Feature.boot_sequence) &&
|
||||
<BootSequenceSelector />}
|
||||
<PowerAndReset
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
botOnline={botOnline} />
|
||||
</WidgetBody>
|
||||
</form>
|
||||
</Widget>;
|
||||
|
|
|
@ -21,7 +21,6 @@ describe("<AutoUpdateRow/>", () => {
|
|||
|
||||
const fakeProps = (): AutoUpdateRowProps => ({
|
||||
timeFormat: "12h",
|
||||
shouldDisplay: jest.fn(() => true),
|
||||
device: fakeDevice(),
|
||||
dispatch: jest.fn(x => x(jest.fn(), () => state)),
|
||||
sourceFbosConfig: () => ({ value: 1, consistent: true })
|
||||
|
|
|
@ -69,6 +69,9 @@ describe("<BoardType/>", () => {
|
|||
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
||||
{ label: "Farmduino (Genesis v1.3)", value: "farmduino" },
|
||||
{ label: "Farmduino (Genesis v1.4)", value: "farmduino_k14" },
|
||||
{ label: "Farmduino (Genesis v1.5)", value: "farmduino_k15" },
|
||||
{ label: "Farmduino (Express v1.0)", value: "express_k10" },
|
||||
{ label: "None", value: "none" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ describe("<FirmwareHardwareStatusDetails />", () => {
|
|||
apiFirmwareValue: undefined,
|
||||
botFirmwareValue: undefined,
|
||||
mcuFirmwareValue: undefined,
|
||||
shouldDisplay: () => true,
|
||||
timeSettings: fakeTimeSettings(),
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
@ -79,7 +78,6 @@ describe("<FirmwareHardwareStatus />", () => {
|
|||
alerts: [],
|
||||
botOnline: true,
|
||||
apiFirmwareValue: undefined,
|
||||
shouldDisplay: () => true,
|
||||
timeSettings: fakeTimeSettings(),
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
|
|
@ -25,30 +25,25 @@ describe("<PowerAndReset/>", () => {
|
|||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([fakeConfig]);
|
||||
|
||||
const fakeProps = (): PowerAndResetProps => {
|
||||
return {
|
||||
controlPanelState: panelState(),
|
||||
dispatch: jest.fn(x => x(jest.fn(), () => state)),
|
||||
sourceFbosConfig: () => ({ value: true, consistent: true }),
|
||||
shouldDisplay: jest.fn(),
|
||||
botOnline: true,
|
||||
};
|
||||
};
|
||||
const fakeProps = (): PowerAndResetProps => ({
|
||||
controlPanelState: panelState(),
|
||||
dispatch: jest.fn(x => x(jest.fn(), () => state)),
|
||||
sourceFbosConfig: () => ({ value: true, consistent: true }),
|
||||
botOnline: true,
|
||||
});
|
||||
|
||||
it("open", () => {
|
||||
it("renders in open state", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
["Power and Reset", "Restart", "Shutdown", "Factory Reset",
|
||||
"Automatic Factory Reset", "Connection Attempt Period", "Change Ownership"]
|
||||
["Power and Reset", "Restart", "Shutdown", "Restart Firmware",
|
||||
"Factory Reset", "Automatic Factory Reset",
|
||||
"Connection Attempt Period", "Change Ownership"]
|
||||
.map(string => expect(wrapper.text().toLowerCase())
|
||||
.toContain(string.toLowerCase()));
|
||||
["Restart Firmware"]
|
||||
.map(string => expect(wrapper.text().toLowerCase())
|
||||
.not.toContain(string.toLowerCase()));
|
||||
});
|
||||
|
||||
it("closed", () => {
|
||||
it("renders as closed", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = false;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
|
@ -73,7 +68,7 @@ describe("<PowerAndReset/>", () => {
|
|||
p.sourceFbosConfig = () => ({ value: false, consistent: true });
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
clickButton(wrapper, 3, "yes");
|
||||
clickButton(wrapper, 4, "yes");
|
||||
expect(edit).toHaveBeenCalledWith(fakeConfig, { disable_factory_reset: true });
|
||||
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
||||
});
|
||||
|
@ -81,7 +76,6 @@ describe("<PowerAndReset/>", () => {
|
|||
it("restarts firmware", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
p.shouldDisplay = () => true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
expect(wrapper.text().toLowerCase())
|
||||
.toContain("Restart Firmware".toLowerCase());
|
||||
|
|
|
@ -7,17 +7,16 @@ import { Content } from "../../../constants";
|
|||
import { AutoUpdateRowProps } from "./interfaces";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector";
|
||||
import { Feature } from "../../interfaces";
|
||||
|
||||
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
||||
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
||||
|
||||
return <div>
|
||||
{props.shouldDisplay(Feature.ota_update_hour) && <OtaTimeSelector
|
||||
<OtaTimeSelector
|
||||
timeFormat={props.timeFormat}
|
||||
disabled={!osAutoUpdate.value}
|
||||
value={props.device.body.ota_hour}
|
||||
onChange={changeOtaHour(props.dispatch, props.device)} />}
|
||||
onChange={changeOtaHour(props.dispatch, props.device)} />
|
||||
<Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
|
|
|
@ -57,7 +57,7 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
|||
<FBSelect
|
||||
key={this.apiValue}
|
||||
extraClass={this.state.sending ? "dim" : ""}
|
||||
list={getFirmwareChoices(this.props.shouldDisplay)}
|
||||
list={getFirmwareChoices()}
|
||||
selectedItem={this.selectedBoard}
|
||||
onChange={this.sendOffConfig} />
|
||||
</div>
|
||||
|
@ -69,8 +69,7 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
|||
alerts={this.props.alerts}
|
||||
bot={this.props.bot}
|
||||
dispatch={this.props.dispatch}
|
||||
timeSettings={this.props.timeSettings}
|
||||
shouldDisplay={this.props.shouldDisplay} />
|
||||
timeSettings={this.props.timeSettings} />
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { timeFormatString } from "../../../util";
|
|||
import { TimeSettings } from "../../../interfaces";
|
||||
import { StringConfigKey } from "farmbot/dist/resources/configs/fbos";
|
||||
import { boardType, FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support";
|
||||
import { ExternalUrl, FarmBotRepo } from "../../../external_urls";
|
||||
|
||||
/** Return an indicator color for the given temperature (C). */
|
||||
export const colorFromTemp = (temp: number | undefined): string => {
|
||||
|
@ -170,7 +171,7 @@ const shortenCommit = (longCommit: string) => (longCommit || "").slice(0, 8);
|
|||
|
||||
interface CommitDisplayProps {
|
||||
title: string;
|
||||
repo: string;
|
||||
repo: FarmBotRepo;
|
||||
commit: string;
|
||||
}
|
||||
|
||||
|
@ -184,7 +185,7 @@ const CommitDisplay = (
|
|||
{shortCommit === "---"
|
||||
? shortCommit
|
||||
: <a
|
||||
href={`https://github.com/FarmBot/${repo}/tree/${shortCommit}`}
|
||||
href={`${ExternalUrl.gitHubFarmBot}/${repo}/tree/${shortCommit}`}
|
||||
target="_blank">
|
||||
{shortCommit}
|
||||
</a>}
|
||||
|
@ -270,14 +271,15 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
timeSettings={props.timeSettings}
|
||||
device={props.deviceAccount} />
|
||||
<p><b>{t("Environment")}: </b>{env}</p>
|
||||
<CommitDisplay title={t("Commit")} repo={"farmbot_os"} commit={commit} />
|
||||
<CommitDisplay title={t("Commit")}
|
||||
repo={FarmBotRepo.FarmBotOS} commit={commit} />
|
||||
<p><b>{t("Target")}: </b>{target}</p>
|
||||
<p><b>{t("Node name")}: </b>{last((node_name || "").split("@"))}</p>
|
||||
<p><b>{t("Device ID")}: </b>{props.deviceAccount.body.id}</p>
|
||||
{isString(private_ip) && <p><b>{t("Local IP address")}: </b>{private_ip}</p>}
|
||||
<p><b>{t("Firmware")}: </b>{reformatFwVersion(firmware_version)}</p>
|
||||
<CommitDisplay title={t("Firmware commit")}
|
||||
repo={"farmbot-arduino-firmware"} commit={firmwareCommit} />
|
||||
repo={FarmBotRepo.FarmBotArduinoFirmware} commit={firmwareCommit} />
|
||||
<p><b>{t("Firmware code")}: </b>{firmware_version}</p>
|
||||
{isNumber(uptime) && <UptimeDisplay uptime_sec={uptime} />}
|
||||
{isNumber(memory_usage) &&
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Popover, Position } from "@blueprintjs/core";
|
|||
import { FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support";
|
||||
import { flashFirmware } from "../../actions";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { BotState, Feature, ShouldDisplay } from "../../interfaces";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { FirmwareAlerts } from "../../../messages/alerts";
|
||||
import { TimeSettings } from "../../../interfaces";
|
||||
import { trim } from "../../../util";
|
||||
|
@ -36,7 +36,6 @@ export interface FirmwareHardwareStatusDetailsProps {
|
|||
apiFirmwareValue: string | undefined;
|
||||
botFirmwareValue: string | undefined;
|
||||
mcuFirmwareValue: string | undefined;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
timeSettings: TimeSettings;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
@ -81,13 +80,10 @@ export const FirmwareHardwareStatusDetails =
|
|||
<p>{lookup(props.botFirmwareValue) || t("unknown")}</p>
|
||||
<label>{t("Arduino/Farmduino")}</label>
|
||||
<p>{lookup(props.mcuFirmwareValue) || t("unknown")}</p>
|
||||
{props.shouldDisplay(Feature.flash_firmware) &&
|
||||
<div>
|
||||
<label>{t("Actions")}</label>
|
||||
<FirmwareActions
|
||||
apiFirmwareValue={props.apiFirmwareValue}
|
||||
botOnline={props.botOnline} />
|
||||
</div>}
|
||||
<label>{t("Actions")}</label>
|
||||
<FirmwareActions
|
||||
apiFirmwareValue={props.apiFirmwareValue}
|
||||
botOnline={props.botOnline} />
|
||||
<FirmwareAlerts
|
||||
alerts={props.alerts}
|
||||
dispatch={props.dispatch}
|
||||
|
@ -102,7 +98,6 @@ export interface FirmwareHardwareStatusProps {
|
|||
bot: BotState;
|
||||
botOnline: boolean;
|
||||
timeSettings: TimeSettings;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
|
@ -122,7 +117,6 @@ export const FirmwareHardwareStatus = (props: FirmwareHardwareStatusProps) => {
|
|||
botFirmwareValue={firmware_hardware}
|
||||
mcuFirmwareValue={boardType(firmware_version)}
|
||||
timeSettings={props.timeSettings}
|
||||
dispatch={props.dispatch}
|
||||
shouldDisplay={props.shouldDisplay} />
|
||||
dispatch={props.dispatch} />
|
||||
</Popover>;
|
||||
};
|
||||
|
|
|
@ -24,7 +24,6 @@ export interface AutoUpdateRowProps {
|
|||
timeFormat: PreferredHourFormat;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
device: TaggedDevice;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
}
|
||||
|
||||
export interface CameraSelectionProps {
|
||||
|
@ -53,7 +52,6 @@ export interface PowerAndResetProps {
|
|||
controlPanelState: ControlPanelState;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
botOnline: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,23 +4,20 @@ import { Collapse, Popover, Position } from "@blueprintjs/core";
|
|||
import { FactoryResetRow } from "./factory_reset_row";
|
||||
import { PowerAndResetProps } from "./interfaces";
|
||||
import { ChangeOwnershipForm } from "./change_ownership_form";
|
||||
import { Feature } from "../../interfaces";
|
||||
import { FbosButtonRow } from "./fbos_button_row";
|
||||
import { Content } from "../../../constants";
|
||||
import { reboot, powerOff, restartFirmware } from "../../actions";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
|
||||
export function PowerAndReset(props: PowerAndResetProps) {
|
||||
const { dispatch, sourceFbosConfig, shouldDisplay, botOnline } = props;
|
||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||
const { power_and_reset } = props.controlPanelState;
|
||||
return <section>
|
||||
<div style={{ fontSize: "1px" }}>
|
||||
<Header
|
||||
expanded={power_and_reset}
|
||||
title={t("Power and Reset")}
|
||||
name={"power_and_reset"}
|
||||
dispatch={dispatch} />
|
||||
</div>
|
||||
<Header
|
||||
expanded={power_and_reset}
|
||||
title={t("Power and Reset")}
|
||||
name={"power_and_reset"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!power_and_reset}>
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
|
@ -36,14 +33,13 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
|||
buttonText={t("SHUTDOWN")}
|
||||
color={"red"}
|
||||
action={powerOff} />
|
||||
{shouldDisplay(Feature.firmware_restart) &&
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
label={t("RESTART FIRMWARE")}
|
||||
description={Content.RESTART_FIRMWARE}
|
||||
buttonText={t("RESTART")}
|
||||
color={"yellow"}
|
||||
action={restartFirmware} />}
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
label={t("RESTART FIRMWARE")}
|
||||
description={Content.RESTART_FIRMWARE}
|
||||
buttonText={t("RESTART")}
|
||||
color={"yellow"}
|
||||
action={restartFirmware} />
|
||||
<FactoryResetRow
|
||||
dispatch={dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { FirmwareHardware } from "farmbot";
|
||||
import { ShouldDisplay, Feature } from "../interfaces";
|
||||
|
||||
export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => {
|
||||
const values: FirmwareHardware[] = [
|
||||
|
@ -77,12 +76,11 @@ export const FIRMWARE_CHOICES_DDI = {
|
|||
[NONE.value]: NONE
|
||||
};
|
||||
|
||||
export const getFirmwareChoices =
|
||||
(shouldDisplay: ShouldDisplay = () => true) => ([
|
||||
ARDUINO,
|
||||
FARMDUINO,
|
||||
FARMDUINO_K14,
|
||||
...(shouldDisplay(Feature.farmduino_k15) ? [FARMDUINO_K15] : []),
|
||||
...(shouldDisplay(Feature.express_k10) ? [EXPRESS_K10] : []),
|
||||
...(shouldDisplay(Feature.none_firmware) ? [NONE] : []),
|
||||
]);
|
||||
export const getFirmwareChoices = () => ([
|
||||
ARDUINO,
|
||||
FARMDUINO,
|
||||
FARMDUINO_K14,
|
||||
FARMDUINO_K15,
|
||||
EXPRESS_K10,
|
||||
NONE,
|
||||
]);
|
||||
|
|
|
@ -2,11 +2,12 @@ import * as React from "react";
|
|||
import { MCUFactoryReset, bulkToggleControlPanel } from "../actions";
|
||||
import { Widget, WidgetHeader, WidgetBody } from "../../ui/index";
|
||||
import { HardwareSettingsProps } from "../interfaces";
|
||||
import { MustBeOnline, isBotOnline } from "../must_be_online";
|
||||
import { isBotOnline } from "../must_be_online";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { DangerZone } from "./hardware_settings/danger_zone";
|
||||
import { PinGuard } from "./hardware_settings/pin_guard";
|
||||
import { EncodersAndEndStops } from "./hardware_settings/encoders_and_endstops";
|
||||
import { Encoders } from "./hardware_settings/encoders";
|
||||
import { EndStops } from "./hardware_settings/endstops";
|
||||
import { Motors } from "./hardware_settings/motors";
|
||||
import { SpacePanelHeader } from "./hardware_settings/space_panel_header";
|
||||
import {
|
||||
|
@ -15,6 +16,8 @@ import {
|
|||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { FwParamExportMenu } from "./hardware_settings/export_menu";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { PinBindings } from "./hardware_settings/pin_bindings";
|
||||
import { ErrorHandling } from "./hardware_settings/error_handling";
|
||||
|
||||
export class HardwareSettings extends
|
||||
React.Component<HardwareSettingsProps, {}> {
|
||||
|
@ -27,15 +30,9 @@ export class HardwareSettings extends
|
|||
const { informational_settings } = this.props.bot.hardware;
|
||||
const { sync_status } = informational_settings;
|
||||
const botDisconnected = !isBotOnline(sync_status, botToMqttStatus);
|
||||
const commonProps = { dispatch, controlPanelState };
|
||||
return <Widget className="hardware-widget">
|
||||
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS}>
|
||||
<MustBeOnline
|
||||
hideBanner={true}
|
||||
syncStatus={sync_status}
|
||||
networkState={this.props.botToMqttStatus}
|
||||
lockOpen={process.env.NODE_ENV !== "production"}>
|
||||
</MustBeOnline>
|
||||
</WidgetHeader>
|
||||
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS} />
|
||||
<WidgetBody>
|
||||
<button
|
||||
className={"fb-button gray no-float"}
|
||||
|
@ -47,46 +44,37 @@ export class HardwareSettings extends
|
|||
onClick={() => dispatch(bulkToggleControlPanel(false))}>
|
||||
{t("Collapse All")}
|
||||
</button>
|
||||
{firmwareConfig &&
|
||||
<Popover position={Position.BOTTOM_RIGHT}>
|
||||
<i className="fa fa-download" />
|
||||
<FwParamExportMenu firmwareConfig={firmwareConfig} />
|
||||
</Popover>}
|
||||
<MustBeOnline
|
||||
networkState={this.props.botToMqttStatus}
|
||||
syncStatus={sync_status}
|
||||
lockOpen={process.env.NODE_ENV !== "production" || !!firmwareConfig}>
|
||||
<div className="label-headings">
|
||||
<SpacePanelHeader />
|
||||
</div>
|
||||
<HomingAndCalibration
|
||||
dispatch={dispatch}
|
||||
bot={bot}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareConfig={firmwareConfig}
|
||||
botDisconnected={botDisconnected} />
|
||||
<Motors
|
||||
dispatch={dispatch}
|
||||
controlPanelState={controlPanelState}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<EncodersAndEndStops
|
||||
dispatch={dispatch}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
controlPanelState={controlPanelState}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<PinGuard
|
||||
dispatch={dispatch}
|
||||
resources={resources}
|
||||
controlPanelState={controlPanelState}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<DangerZone
|
||||
dispatch={dispatch}
|
||||
controlPanelState={controlPanelState}
|
||||
onReset={MCUFactoryReset}
|
||||
botDisconnected={botDisconnected} />
|
||||
</MustBeOnline>
|
||||
<Popover position={Position.BOTTOM_RIGHT}>
|
||||
<i className="fa fa-download" />
|
||||
<FwParamExportMenu firmwareConfig={firmwareConfig} />
|
||||
</Popover>
|
||||
<div className="label-headings">
|
||||
<SpacePanelHeader />
|
||||
</div>
|
||||
<HomingAndCalibration {...commonProps}
|
||||
bot={bot}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareConfig={firmwareConfig}
|
||||
firmwareHardware={firmwareHardware}
|
||||
botDisconnected={botDisconnected} />
|
||||
<Motors {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<Encoders {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
firmwareHardware={firmwareHardware} />
|
||||
<EndStops {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<ErrorHandling {...commonProps}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuard {...commonProps}
|
||||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<DangerZone {...commonProps}
|
||||
onReset={MCUFactoryReset}
|
||||
botDisconnected={botDisconnected} />
|
||||
<PinBindings {...commonProps}
|
||||
resources={resources} />
|
||||
</WidgetBody>
|
||||
</Widget>;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,40 @@
|
|||
const mockDevice = {
|
||||
calibrate: jest.fn(() => Promise.resolve({}))
|
||||
};
|
||||
jest.mock("../../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
}));
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { CalibrationRow } from "../calibration_row";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { CalibrationRowProps } from "../../interfaces";
|
||||
|
||||
describe("<CalibrationRow />", () => {
|
||||
const fakeProps = (): CalibrationRowProps => ({
|
||||
type: "calibrate",
|
||||
hardware: bot.hardware.mcu_params,
|
||||
botDisconnected: false,
|
||||
action: jest.fn(),
|
||||
toolTip: "calibrate",
|
||||
title: "calibrate",
|
||||
axisTitle: "calibrate",
|
||||
});
|
||||
|
||||
describe("<HomingRow />", () => {
|
||||
it("calls device", () => {
|
||||
const result = mount(<CalibrationRow
|
||||
hardware={bot.hardware.mcu_params}
|
||||
botDisconnected={false} />);
|
||||
const p = fakeProps();
|
||||
const result = mount(<CalibrationRow {...p} />);
|
||||
p.hardware.encoder_enabled_x = 1;
|
||||
p.hardware.encoder_enabled_y = 1;
|
||||
p.hardware.encoder_enabled_z = 0;
|
||||
[0, 1, 2].map(i => result.find("LockableButton").at(i).simulate("click"));
|
||||
expect(mockDevice.calibrate).toHaveBeenCalledTimes(2);
|
||||
[{ axis: "y" }, { axis: "x" }].map(x =>
|
||||
expect(mockDevice.calibrate).toHaveBeenCalledWith(x));
|
||||
expect(p.action).toHaveBeenCalledTimes(2);
|
||||
["y", "x"].map(x => expect(p.action).toHaveBeenCalledWith(x));
|
||||
});
|
||||
|
||||
it("is not disabled", () => {
|
||||
const p = fakeProps();
|
||||
p.type = "zero";
|
||||
const result = mount(<CalibrationRow {...p} />);
|
||||
p.hardware.encoder_enabled_x = 0;
|
||||
p.hardware.encoder_enabled_y = 1;
|
||||
p.hardware.encoder_enabled_z = 0;
|
||||
[0, 1, 2].map(i => result.find("LockableButton").at(i).simulate("click"));
|
||||
expect(p.action).toHaveBeenCalledTimes(3);
|
||||
["x", "y", "z"].map(x => expect(p.action).toHaveBeenCalledWith(x));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { EncodersAndEndStops } from "../encoders_and_endstops";
|
||||
import { EncodersProps, NumericMCUInputGroupProps } from "../../interfaces";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { Dictionary } from "farmbot";
|
||||
|
||||
describe("<EncodersAndEndStops />", () => {
|
||||
const mockFeatures: Dictionary<boolean> = {};
|
||||
const fakeProps = (): EncodersProps => ({
|
||||
dispatch: jest.fn(),
|
||||
controlPanelState: panelState(),
|
||||
sourceFwConfig: x =>
|
||||
({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||
shouldDisplay: jest.fn(key => mockFeatures[key]),
|
||||
firmwareHardware: undefined,
|
||||
});
|
||||
|
||||
it("shows encoder labels", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = undefined;
|
||||
const wrapper = mount(<EncodersAndEndStops {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("encoder");
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("stall");
|
||||
});
|
||||
|
||||
it("shows stall labels", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = "express_k10";
|
||||
const wrapper = mount(<EncodersAndEndStops {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("encoder");
|
||||
expect(wrapper.text().toLowerCase()).toContain("stall");
|
||||
});
|
||||
|
||||
it.each<["short" | "long"]>([
|
||||
["short"],
|
||||
["long"],
|
||||
])("uses %s int scaling factor", (size) => {
|
||||
mockFeatures.long_scaling_factor = size === "short" ? false : true;
|
||||
const wrapper = shallow(<EncodersAndEndStops {...fakeProps()} />);
|
||||
const sfProps = wrapper.find("NumericMCUInputGroup").at(2)
|
||||
.props() as NumericMCUInputGroupProps;
|
||||
expect(sfProps.name).toEqual("Encoder Scaling");
|
||||
expect(sfProps.intSize).toEqual(size);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { Encoders } from "../encoders";
|
||||
import { EncodersProps } from "../../interfaces";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<Encoders />", () => {
|
||||
const fakeProps = (): EncodersProps => ({
|
||||
dispatch: jest.fn(),
|
||||
controlPanelState: panelState(),
|
||||
sourceFwConfig: x =>
|
||||
({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||
firmwareHardware: undefined,
|
||||
});
|
||||
|
||||
it("shows encoder labels", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = undefined;
|
||||
const wrapper = mount(<Encoders {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("encoder");
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("stall");
|
||||
});
|
||||
|
||||
it("shows stall labels", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = "express_k10";
|
||||
const wrapper = mount(<Encoders {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("encoder");
|
||||
expect(wrapper.text().toLowerCase()).toContain("stall");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { EndStops } from "../endstops";
|
||||
import { EndStopsProps } from "../../interfaces";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<EndStops />", () => {
|
||||
const fakeProps = (): EndStopsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
controlPanelState: panelState(),
|
||||
sourceFwConfig: x =>
|
||||
({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||
});
|
||||
|
||||
it("shows endstop labels", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<EndStops {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("endstop");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
jest.mock("../../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { ErrorHandling } from "../error_handling";
|
||||
import { ErrorHandlingProps } from "../../interfaces";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { edit, save } from "../../../../api/crud";
|
||||
import { fakeState } from "../../../../__test_support__/fake_state";
|
||||
import {
|
||||
fakeFirmwareConfig
|
||||
} from "../../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<ErrorHandling />", () => {
|
||||
const fakeConfig = fakeFirmwareConfig();
|
||||
const state = fakeState();
|
||||
state.resources = buildResourceIndex([fakeConfig]);
|
||||
const fakeProps = (): ErrorHandlingProps => ({
|
||||
dispatch: jest.fn(x => x(jest.fn(), () => state)),
|
||||
controlPanelState: panelState(),
|
||||
sourceFwConfig: x =>
|
||||
({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||
});
|
||||
|
||||
it("shows error handling labels", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<ErrorHandling {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("error handling");
|
||||
});
|
||||
|
||||
it("toggles retries e-stop parameter", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.error_handling = true;
|
||||
p.sourceFwConfig = () => ({ value: 1, consistent: true });
|
||||
const wrapper = mount(<ErrorHandling {...p} />);
|
||||
wrapper.find("button").at(0).simulate("click");
|
||||
expect(edit).toHaveBeenCalledWith(fakeConfig, { param_e_stop_on_mov_err: 0 });
|
||||
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
||||
});
|
||||
});
|
|
@ -1,7 +1,17 @@
|
|||
jest.mock("../../../actions", () => ({ updateMCU: jest.fn() }));
|
||||
jest.mock("../../../actions", () => ({
|
||||
updateMCU: jest.fn(),
|
||||
commandErr: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockDevice = {
|
||||
calibrate: jest.fn(() => Promise.resolve({})),
|
||||
findHome: jest.fn(() => Promise.resolve({})),
|
||||
setZero: jest.fn(() => Promise.resolve({})),
|
||||
};
|
||||
jest.mock("../../../../device", () => ({ getDevice: () => mockDevice }));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { HomingAndCalibration } from "../homing_and_calibration";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { updateMCU } from "../../../actions";
|
||||
|
@ -10,20 +20,28 @@ import {
|
|||
} from "../../../../__test_support__/fake_state/resources";
|
||||
import { error, warning } from "../../../../toast/toast";
|
||||
import { inputEvent } from "../../../../__test_support__/fake_html_events";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { HomingAndCalibrationProps } from "../../interfaces";
|
||||
import { CalibrationRow } from "../calibration_row";
|
||||
|
||||
describe("<HomingAndCalibration />", () => {
|
||||
const fakeProps = (): HomingAndCalibrationProps => ({
|
||||
dispatch: jest.fn(),
|
||||
bot,
|
||||
controlPanelState: panelState(),
|
||||
sourceFwConfig: x => ({
|
||||
value: bot.hardware.mcu_params[x], consistent: true
|
||||
}),
|
||||
firmwareConfig: fakeFirmwareConfig().body,
|
||||
botDisconnected: false,
|
||||
firmwareHardware: undefined,
|
||||
});
|
||||
|
||||
function testAxisLengthInput(
|
||||
provided: string, expected: string | undefined) {
|
||||
const dispatch = jest.fn();
|
||||
bot.controlPanelState.homing_and_calibration = true;
|
||||
const result = mount(<HomingAndCalibration
|
||||
dispatch={dispatch}
|
||||
bot={bot}
|
||||
firmwareConfig={fakeFirmwareConfig().body}
|
||||
sourceFwConfig={x => ({
|
||||
value: bot.hardware.mcu_params[x], consistent: true
|
||||
})}
|
||||
botDisconnected={false} />);
|
||||
const p = fakeProps();
|
||||
p.bot.controlPanelState.homing_and_calibration = true;
|
||||
const result = mount(<HomingAndCalibration {...p} />);
|
||||
const e = inputEvent(provided);
|
||||
const input = result.find("input").first().props();
|
||||
input.onChange && input.onChange(e);
|
||||
|
@ -45,4 +63,33 @@ describe("<HomingAndCalibration />", () => {
|
|||
expect(warning).not.toHaveBeenCalled();
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("finds home", () => {
|
||||
const wrapper = shallow(<HomingAndCalibration {...fakeProps()} />);
|
||||
wrapper.find(CalibrationRow).first().props().action("x");
|
||||
expect(mockDevice.findHome).toHaveBeenCalledWith({
|
||||
axis: "x", speed: 100
|
||||
});
|
||||
});
|
||||
|
||||
it("calibrates", () => {
|
||||
const wrapper = shallow(<HomingAndCalibration {...fakeProps()} />);
|
||||
wrapper.find(CalibrationRow).at(1).props().action("all");
|
||||
expect(mockDevice.calibrate).toHaveBeenCalledWith({ axis: "all" });
|
||||
});
|
||||
|
||||
it("sets zero", () => {
|
||||
const wrapper = shallow(<HomingAndCalibration {...fakeProps()} />);
|
||||
wrapper.find(CalibrationRow).last().props().action("all");
|
||||
expect(mockDevice.setZero).toHaveBeenCalledWith("all");
|
||||
});
|
||||
|
||||
it("shows express board related labels", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = "express_k10";
|
||||
p.controlPanelState.homing_and_calibration = true;
|
||||
const wrapper = shallow(<HomingAndCalibration {...p} />);
|
||||
expect(wrapper.find(CalibrationRow).first().props().toolTip)
|
||||
.toContain("stall detection");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
const mockDevice = {
|
||||
findHome: jest.fn(() => Promise.resolve({}))
|
||||
};
|
||||
|
||||
jest.mock("../../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
}));
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { HomingRow } from "../homing_row";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<HomingRow />", () => {
|
||||
it("renders three buttons", () => {
|
||||
const wrapper = mount(<HomingRow
|
||||
hardware={bot.hardware.mcu_params}
|
||||
botDisconnected={false} />);
|
||||
const txt = wrapper.text().toUpperCase();
|
||||
["X", "Y", "Z"].map(function (axis) {
|
||||
expect(txt).toContain(`HOME ${axis}`);
|
||||
});
|
||||
});
|
||||
|
||||
it("calls device", () => {
|
||||
const result = mount(<HomingRow
|
||||
hardware={bot.hardware.mcu_params}
|
||||
botDisconnected={false} />);
|
||||
[0, 1, 2].map(i =>
|
||||
result.find("LockableButton").at(i).simulate("click"));
|
||||
[{ axis: "x", speed: 100 }, { axis: "y", speed: 100 }].map(x =>
|
||||
expect(mockDevice.findHome).toHaveBeenCalledWith(x));
|
||||
});
|
||||
});
|
|
@ -37,8 +37,6 @@ describe("<Motors/>", () => {
|
|||
it("renders the base case", () => {
|
||||
const wrapper = render(<Motors {...fakeProps()} />);
|
||||
["Enable 2nd X Motor",
|
||||
"Max Retries",
|
||||
"E-Stop on Movement Error",
|
||||
"Max Speed (mm/s)"
|
||||
].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
|
||||
|
@ -48,16 +46,14 @@ describe("<Motors/>", () => {
|
|||
const p = fakeProps();
|
||||
p.firmwareHardware = "express_k10";
|
||||
const wrapper = render(<Motors {...p} />);
|
||||
expect(wrapper.text()).toContain("Stall");
|
||||
expect(wrapper.text()).toContain("Current");
|
||||
expect(wrapper.text()).toContain("Motor Current");
|
||||
});
|
||||
|
||||
it("doesn't show TMC parameters", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = "farmduino";
|
||||
const wrapper = render(<Motors {...p} />);
|
||||
expect(wrapper.text()).not.toContain("Stall");
|
||||
expect(wrapper.text()).not.toContain("Current");
|
||||
expect(wrapper.text()).not.toContain("Motor Current");
|
||||
});
|
||||
|
||||
const testParamToggle = (
|
||||
|
@ -72,15 +68,6 @@ describe("<Motors/>", () => {
|
|||
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
||||
});
|
||||
};
|
||||
testParamToggle("toggles retries e-stop parameter", "param_e_stop_on_mov_err", 0);
|
||||
testParamToggle("toggles enable X2", "movement_secondary_motor_x", 7);
|
||||
testParamToggle("toggles invert X2", "movement_secondary_motor_invert_x", 8);
|
||||
|
||||
it("renders TMC params", () => {
|
||||
const p = fakeProps();
|
||||
p.firmwareHardware = "express_k10";
|
||||
const wrapper = render(<Motors {...p} />);
|
||||
expect(wrapper.text()).toContain("Motor Current");
|
||||
expect(wrapper.text()).toContain("Stall Sensitivity");
|
||||
});
|
||||
testParamToggle("toggles enable X2", "movement_secondary_motor_x", 6);
|
||||
testParamToggle("toggles invert X2", "movement_secondary_motor_invert_x", 7);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { PinBindings } from "../pin_bindings";
|
||||
import { PinBindingsProps } from "../../interfaces";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<PinBindings />", () => {
|
||||
const fakeProps = (): PinBindingsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
controlPanelState: panelState(),
|
||||
resources: buildResourceIndex([]).index,
|
||||
});
|
||||
|
||||
it("shows pin binding labels", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("pin bindings");
|
||||
});
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
const mockDevice = {
|
||||
setZero: jest.fn(() => Promise.resolve())
|
||||
};
|
||||
jest.mock("../../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
}));
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { ZeroRow } from "../zero_row";
|
||||
|
||||
describe("<HomingRow />", () => {
|
||||
it("calls device", () => {
|
||||
const result = mount(<ZeroRow botDisconnected={false} />);
|
||||
[0, 1, 2].map(i => result.find("ZeroButton").at(i).simulate("click"));
|
||||
["x", "y", "z"].map(x =>
|
||||
expect(mockDevice.setZero).toHaveBeenCalledWith(x));
|
||||
expect(mockDevice.setZero).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
|
@ -1,19 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { getDevice } from "../../../device";
|
||||
import { Axis } from "../../interfaces";
|
||||
import { LockableButton } from "../lockable_button";
|
||||
import { axisTrackingStatus } from "../axis_tracking_status";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { Row, Col, Help } from "../../../ui/index";
|
||||
import { CalibrationRowProps } from "../interfaces";
|
||||
import { commandErr } from "../../actions";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
||||
const calibrate = (axis: Axis) => getDevice()
|
||||
.calibrate({ axis })
|
||||
.catch(commandErr("Calibration"));
|
||||
|
||||
export function CalibrationRow(props: CalibrationRowProps) {
|
||||
|
||||
const { hardware, botDisconnected } = props;
|
||||
|
@ -21,18 +13,20 @@ export function CalibrationRow(props: CalibrationRowProps) {
|
|||
return <Row>
|
||||
<Col xs={6} className={"widget-body-tooltips"}>
|
||||
<label>
|
||||
{t("CALIBRATION")}
|
||||
{t(props.title)}
|
||||
</label>
|
||||
<Help text={ToolTips.CALIBRATION} requireClick={true} position={Position.RIGHT} />
|
||||
<Help text={t(props.toolTip)}
|
||||
requireClick={true} position={Position.RIGHT} />
|
||||
</Col>
|
||||
{axisTrackingStatus(hardware)
|
||||
.map(row => {
|
||||
const { axis, disabled } = row;
|
||||
const { axis } = row;
|
||||
const hardwareDisabled = props.type == "zero" ? false : row.disabled;
|
||||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
||||
<LockableButton
|
||||
disabled={disabled || botDisconnected}
|
||||
onClick={() => calibrate(axis)}>
|
||||
{t("CALIBRATE {{axis}}", { axis })}
|
||||
disabled={hardwareDisabled || botDisconnected}
|
||||
onClick={() => props.action(axis)}>
|
||||
{`${t(props.axisTitle)} ${axis}`}
|
||||
</LockableButton>
|
||||
</Col>;
|
||||
})}
|
||||
|
|
|
@ -5,41 +5,53 @@ import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
|||
import { EncodersProps } from "../interfaces";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { Feature } from "../../interfaces";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { isExpressBoard } from "../firmware_hardware_support";
|
||||
|
||||
export function EncodersAndEndStops(props: EncodersProps) {
|
||||
export function Encoders(props: EncodersProps) {
|
||||
|
||||
const { encoders_and_endstops } = props.controlPanelState;
|
||||
const { dispatch, sourceFwConfig, shouldDisplay, firmwareHardware } = props;
|
||||
const { encoders } = props.controlPanelState;
|
||||
const { dispatch, sourceFwConfig, firmwareHardware } = props;
|
||||
|
||||
const encodersDisabled = {
|
||||
x: !sourceFwConfig("encoder_enabled_x").value,
|
||||
y: !sourceFwConfig("encoder_enabled_y").value,
|
||||
z: !sourceFwConfig("encoder_enabled_z").value
|
||||
};
|
||||
const isExpress = isExpressBoard(firmwareHardware);
|
||||
|
||||
return <section>
|
||||
<Header
|
||||
expanded={encoders_and_endstops}
|
||||
title={isExpressBoard(firmwareHardware)
|
||||
? t("Stall Detection and Endstops")
|
||||
: t("Encoders and Endstops")}
|
||||
name={"encoders_and_endstops"}
|
||||
expanded={encoders}
|
||||
title={isExpress
|
||||
? t("Stall Detection")
|
||||
: t("Encoders")}
|
||||
name={"encoders"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!encoders_and_endstops}>
|
||||
<Collapse isOpen={!!encoders}>
|
||||
<BooleanMCUInputGroup
|
||||
name={isExpressBoard(firmwareHardware)
|
||||
name={isExpress
|
||||
? t("Enable Stall Detection")
|
||||
: t("Enable Encoders")}
|
||||
tooltip={ToolTips.ENABLE_ENCODERS}
|
||||
tooltip={isExpress
|
||||
? ToolTips.ENABLE_STALL_DETECTION
|
||||
: ToolTips.ENABLE_ENCODERS}
|
||||
x={"encoder_enabled_x"}
|
||||
y={"encoder_enabled_y"}
|
||||
z={"encoder_enabled_z"}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
{!isExpressBoard(firmwareHardware) &&
|
||||
{isExpress &&
|
||||
<NumericMCUInputGroup
|
||||
name={t("Stall Sensitivity")}
|
||||
tooltip={ToolTips.STALL_SENSITIVITY}
|
||||
x={"movement_stall_sensitivity_x"}
|
||||
y={"movement_stall_sensitivity_y"}
|
||||
z={"movement_stall_sensitivity_z"}
|
||||
gray={encodersDisabled}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />}
|
||||
{!isExpress &&
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Use Encoders for Positioning")}
|
||||
tooltip={ToolTips.ENCODER_POSITIONING}
|
||||
|
@ -49,7 +61,7 @@ export function EncodersAndEndStops(props: EncodersProps) {
|
|||
grayscale={encodersDisabled}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />}
|
||||
{!isExpressBoard(firmwareHardware) &&
|
||||
{!isExpress &&
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Invert Encoders")}
|
||||
tooltip={ToolTips.INVERT_ENCODERS}
|
||||
|
@ -61,7 +73,9 @@ export function EncodersAndEndStops(props: EncodersProps) {
|
|||
sourceFwConfig={sourceFwConfig} />}
|
||||
<NumericMCUInputGroup
|
||||
name={t("Max Missed Steps")}
|
||||
tooltip={ToolTips.MAX_MISSED_STEPS}
|
||||
tooltip={isExpress
|
||||
? ToolTips.MAX_MISSED_STEPS_STALL_DETECTION
|
||||
: ToolTips.MAX_MISSED_STEPS_ENCODERS}
|
||||
x={"encoder_missed_steps_max_x"}
|
||||
y={"encoder_missed_steps_max_y"}
|
||||
z={"encoder_missed_steps_max_z"}
|
||||
|
@ -70,14 +84,14 @@ export function EncodersAndEndStops(props: EncodersProps) {
|
|||
dispatch={dispatch} />
|
||||
<NumericMCUInputGroup
|
||||
name={t("Missed Step Decay")}
|
||||
tooltip={ToolTips.ENCODER_MISSED_STEP_DECAY}
|
||||
tooltip={ToolTips.MISSED_STEP_DECAY}
|
||||
x={"encoder_missed_steps_decay_x"}
|
||||
y={"encoder_missed_steps_decay_y"}
|
||||
z={"encoder_missed_steps_decay_z"}
|
||||
gray={encodersDisabled}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />
|
||||
{!isExpressBoard(firmwareHardware) &&
|
||||
{!isExpress &&
|
||||
<NumericMCUInputGroup
|
||||
name={t("Encoder Scaling")}
|
||||
tooltip={ToolTips.ENCODER_SCALING}
|
||||
|
@ -87,44 +101,10 @@ export function EncodersAndEndStops(props: EncodersProps) {
|
|||
xScale={sourceFwConfig("movement_microsteps_x").value}
|
||||
yScale={sourceFwConfig("movement_microsteps_y").value}
|
||||
zScale={sourceFwConfig("movement_microsteps_z").value}
|
||||
intSize={shouldDisplay(Feature.long_scaling_factor) ? "long" : "short"}
|
||||
intSize={"long"}
|
||||
gray={encodersDisabled}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />}
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Enable Endstops")}
|
||||
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
||||
x={"movement_enable_endpoints_x"}
|
||||
y={"movement_enable_endpoints_y"}
|
||||
z={"movement_enable_endpoints_z"}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Swap Endstops")}
|
||||
tooltip={ToolTips.SWAP_ENDPOINTS}
|
||||
x={"movement_invert_endpoints_x"}
|
||||
y={"movement_invert_endpoints_y"}
|
||||
z={"movement_invert_endpoints_z"}
|
||||
grayscale={{
|
||||
x: !sourceFwConfig("movement_enable_endpoints_x").value,
|
||||
y: !sourceFwConfig("movement_enable_endpoints_y").value,
|
||||
z: !sourceFwConfig("movement_enable_endpoints_z").value
|
||||
}}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Invert Endstops")}
|
||||
tooltip={ToolTips.INVERT_ENDPOINTS}
|
||||
x={"movement_invert_2_endpoints_x"}
|
||||
y={"movement_invert_2_endpoints_y"}
|
||||
z={"movement_invert_2_endpoints_z"}
|
||||
grayscale={{
|
||||
x: !sourceFwConfig("movement_enable_endpoints_x").value,
|
||||
y: !sourceFwConfig("movement_enable_endpoints_y").value,
|
||||
z: !sourceFwConfig("movement_enable_endpoints_z").value
|
||||
}}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import * as React from "react";
|
||||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { EndStopsProps } from "../interfaces";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
|
||||
export function EndStops(props: EndStopsProps) {
|
||||
|
||||
const { endstops } = props.controlPanelState;
|
||||
const { dispatch, sourceFwConfig } = props;
|
||||
|
||||
return <section>
|
||||
<Header
|
||||
expanded={endstops}
|
||||
title={"Endstops"}
|
||||
name={"endstops"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!endstops}>
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Enable Endstops")}
|
||||
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
||||
x={"movement_enable_endpoints_x"}
|
||||
y={"movement_enable_endpoints_y"}
|
||||
z={"movement_enable_endpoints_z"}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Swap Endstops")}
|
||||
tooltip={ToolTips.SWAP_ENDPOINTS}
|
||||
x={"movement_invert_endpoints_x"}
|
||||
y={"movement_invert_endpoints_y"}
|
||||
z={"movement_invert_endpoints_z"}
|
||||
grayscale={{
|
||||
x: !sourceFwConfig("movement_enable_endpoints_x").value,
|
||||
y: !sourceFwConfig("movement_enable_endpoints_y").value,
|
||||
z: !sourceFwConfig("movement_enable_endpoints_z").value
|
||||
}}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Invert Endstops")}
|
||||
tooltip={ToolTips.INVERT_ENDPOINTS}
|
||||
x={"movement_invert_2_endpoints_x"}
|
||||
y={"movement_invert_2_endpoints_y"}
|
||||
z={"movement_invert_2_endpoints_z"}
|
||||
grayscale={{
|
||||
x: !sourceFwConfig("movement_enable_endpoints_x").value,
|
||||
y: !sourceFwConfig("movement_enable_endpoints_y").value,
|
||||
z: !sourceFwConfig("movement_enable_endpoints_z").value
|
||||
}}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import * as React from "react";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { ErrorHandlingProps } from "../interfaces";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { McuInputBox } from "../mcu_input_box";
|
||||
import { settingToggle } from "../../actions";
|
||||
import { SingleSettingRow } from "./single_setting_row";
|
||||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
|
||||
export function ErrorHandling(props: ErrorHandlingProps) {
|
||||
|
||||
const { error_handling } = props.controlPanelState;
|
||||
const { dispatch, sourceFwConfig } = props;
|
||||
const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err");
|
||||
|
||||
return <section>
|
||||
<Header
|
||||
expanded={error_handling}
|
||||
title={"Error Handling"}
|
||||
name={"error_handling"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!error_handling}>
|
||||
<NumericMCUInputGroup
|
||||
name={t("Timeout after (seconds)")}
|
||||
tooltip={ToolTips.TIMEOUT_AFTER}
|
||||
x={"movement_timeout_x"}
|
||||
y={"movement_timeout_y"}
|
||||
z={"movement_timeout_z"}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />
|
||||
<SingleSettingRow settingType="input"
|
||||
label={t("Max Retries")}
|
||||
tooltip={ToolTips.MAX_MOVEMENT_RETRIES}>
|
||||
<McuInputBox
|
||||
setting="param_mov_nr_retry"
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />
|
||||
</SingleSettingRow>
|
||||
<SingleSettingRow settingType="button"
|
||||
label={t("E-Stop on Movement Error")}
|
||||
tooltip={ToolTips.E_STOP_ON_MOV_ERR}>
|
||||
<ToggleButton
|
||||
toggleValue={eStopOnMoveError.value}
|
||||
dim={!eStopOnMoveError.consistent}
|
||||
toggleAction={() => dispatch(
|
||||
settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} />
|
||||
</SingleSettingRow>
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
|
@ -25,7 +25,7 @@ const getSubKeyName = (key: string) => {
|
|||
};
|
||||
|
||||
export const FwParamExportMenu =
|
||||
({ firmwareConfig }: { firmwareConfig: FirmwareConfig }) => {
|
||||
({ firmwareConfig }: { firmwareConfig: FirmwareConfig | undefined }) => {
|
||||
/** Filter out unnecessary parameters. */
|
||||
const filteredConfig = pickBy(firmwareConfig, (_, key) =>
|
||||
!["id", "device_id", "api_migrated", "created_at", "updated_at",
|
||||
|
|
|
@ -2,19 +2,23 @@ import * as React from "react";
|
|||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { HomingRow } from "./homing_row";
|
||||
import { CalibrationRow } from "./calibration_row";
|
||||
import { ZeroRow } from "./zero_row";
|
||||
import { disabledAxisMap } from "../axis_tracking_status";
|
||||
import { HomingAndCalibrationProps } from "../interfaces";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { calculateScale } from "./motors";
|
||||
import { isExpressBoard } from "../firmware_hardware_support";
|
||||
import { getDevice } from "../../../device";
|
||||
import { commandErr } from "../../actions";
|
||||
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
||||
|
||||
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||
|
||||
const { dispatch, bot, sourceFwConfig, firmwareConfig, botDisconnected
|
||||
const {
|
||||
dispatch, bot, sourceFwConfig, firmwareConfig, botDisconnected,
|
||||
firmwareHardware
|
||||
} = props;
|
||||
const hardware = firmwareConfig ? firmwareConfig : bot.hardware.mcu_params;
|
||||
const { homing_and_calibration } = props.bot.controlPanelState;
|
||||
|
@ -34,12 +38,43 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
|||
dispatch={dispatch}
|
||||
expanded={homing_and_calibration} />
|
||||
<Collapse isOpen={!!homing_and_calibration}>
|
||||
<HomingRow hardware={hardware} botDisconnected={botDisconnected} />
|
||||
<CalibrationRow hardware={hardware} botDisconnected={botDisconnected} />
|
||||
<ZeroRow botDisconnected={botDisconnected} />
|
||||
<CalibrationRow
|
||||
type={"find_home"}
|
||||
title={t("HOMING")}
|
||||
axisTitle={t("FIND HOME")}
|
||||
toolTip={isExpressBoard(firmwareHardware)
|
||||
? ToolTips.HOMING_STALL_DETECTION
|
||||
: ToolTips.HOMING_ENCODERS}
|
||||
action={axis => getDevice()
|
||||
.findHome({ speed: CONFIG_DEFAULTS.speed, axis })
|
||||
.catch(commandErr("'Find Home' request"))}
|
||||
hardware={hardware}
|
||||
botDisconnected={botDisconnected} />
|
||||
<CalibrationRow
|
||||
type={"calibrate"}
|
||||
title={t("CALIBRATION")}
|
||||
axisTitle={t("CALIBRATE")}
|
||||
toolTip={isExpressBoard(firmwareHardware)
|
||||
? ToolTips.CALIBRATION_STALL_DETECTION
|
||||
: ToolTips.CALIBRATION_ENCODERS}
|
||||
action={axis => getDevice().calibrate({ axis })
|
||||
.catch(commandErr("Calibration"))}
|
||||
hardware={hardware}
|
||||
botDisconnected={botDisconnected} />
|
||||
<CalibrationRow
|
||||
type={"zero"}
|
||||
title={t("SET ZERO POSITION")}
|
||||
axisTitle={t("ZERO")}
|
||||
toolTip={ToolTips.SET_ZERO_POSITION}
|
||||
action={axis => getDevice().setZero(axis)
|
||||
.catch(commandErr("Zeroing"))}
|
||||
hardware={hardware}
|
||||
botDisconnected={botDisconnected} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Find Home on Boot")}
|
||||
tooltip={ToolTips.FIND_HOME_ON_BOOT}
|
||||
tooltip={isExpressBoard(firmwareHardware)
|
||||
? ToolTips.FIND_HOME_ON_BOOT_STALL_DETECTION
|
||||
: ToolTips.FIND_HOME_ON_BOOT_ENCODERS}
|
||||
disable={disabled}
|
||||
x={"movement_home_at_boot_x"}
|
||||
y={"movement_home_at_boot_y"}
|
||||
|
@ -88,14 +123,6 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
|||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch}
|
||||
intSize={"long"} />
|
||||
<NumericMCUInputGroup
|
||||
name={t("Timeout after (seconds)")}
|
||||
tooltip={ToolTips.TIMEOUT_AFTER}
|
||||
x={"movement_timeout_x"}
|
||||
y={"movement_timeout_y"}
|
||||
z={"movement_timeout_z"}
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { HomingRowProps } from "../interfaces";
|
||||
import { LockableButton } from "../lockable_button";
|
||||
import { axisTrackingStatus } from "../axis_tracking_status";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { Row, Col, Help } from "../../../ui/index";
|
||||
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
||||
import { commandErr } from "../../actions";
|
||||
import { Axis } from "../../interfaces";
|
||||
import { getDevice } from "../../../device";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
||||
const speed = CONFIG_DEFAULTS.speed;
|
||||
const findHome = (axis: Axis) => getDevice()
|
||||
.findHome({ speed, axis })
|
||||
.catch(commandErr("'Find Home' request"));
|
||||
|
||||
export function HomingRow(props: HomingRowProps) {
|
||||
const { hardware, botDisconnected } = props;
|
||||
|
||||
return <Row>
|
||||
<Col xs={6} className={"widget-body-tooltips"}>
|
||||
<label>
|
||||
{t("HOMING")}
|
||||
</label>
|
||||
<Help text={ToolTips.HOMING} requireClick={true} position={Position.RIGHT}/>
|
||||
</Col>
|
||||
{axisTrackingStatus(hardware)
|
||||
.map((row) => {
|
||||
const { axis, disabled } = row;
|
||||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
||||
<LockableButton
|
||||
disabled={disabled || botDisconnected}
|
||||
onClick={() => findHome(axis)}>
|
||||
{t("FIND HOME {{axis}}", { axis })}
|
||||
</LockableButton>
|
||||
</Col>;
|
||||
})}
|
||||
</Row>;
|
||||
}
|
|
@ -5,32 +5,14 @@ import { ToggleButton } from "../../../controls/toggle_button";
|
|||
import { settingToggle } from "../../actions";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { MotorsProps } from "../interfaces";
|
||||
import { Row, Col, Help } from "../../../ui/index";
|
||||
import { Header } from "./header";
|
||||
import { Collapse, Position } from "@blueprintjs/core";
|
||||
import { McuInputBox } from "../mcu_input_box";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { Xyz, McuParamName } from "farmbot";
|
||||
import { SourceFwConfig } from "../../interfaces";
|
||||
import { calcMicrostepsPerMm } from "../../../controls/move/direction_axes_props";
|
||||
import { isTMCBoard, isExpressBoard } from "../firmware_hardware_support";
|
||||
|
||||
const SingleSettingRow =
|
||||
({ label, tooltip, settingType, children }: {
|
||||
label: string,
|
||||
tooltip: string,
|
||||
children: React.ReactChild,
|
||||
settingType: "button" | "input",
|
||||
}) =>
|
||||
<Row>
|
||||
<Col xs={6} className={"widget-body-tooltips"}>
|
||||
<label>{label}</label>
|
||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
||||
</Col>
|
||||
{settingType === "button"
|
||||
? <Col xs={2} className={"centered-button-div"}>{children}</Col>
|
||||
: <Col xs={6}>{children}</Col>}
|
||||
</Row>;
|
||||
import { isTMCBoard } from "../firmware_hardware_support";
|
||||
import { SingleSettingRow } from "./single_setting_row";
|
||||
|
||||
export const calculateScale =
|
||||
(sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => {
|
||||
|
@ -51,13 +33,8 @@ export function Motors(props: MotorsProps) {
|
|||
} = props;
|
||||
const enable2ndXMotor = sourceFwConfig("movement_secondary_motor_x");
|
||||
const invert2ndXMotor = sourceFwConfig("movement_secondary_motor_invert_x");
|
||||
const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err");
|
||||
const scale = calculateScale(sourceFwConfig);
|
||||
const encodersDisabled = {
|
||||
x: !sourceFwConfig("encoder_enabled_x").value,
|
||||
y: !sourceFwConfig("encoder_enabled_y").value,
|
||||
z: !sourceFwConfig("encoder_enabled_z").value,
|
||||
};
|
||||
|
||||
return <section>
|
||||
<Header
|
||||
expanded={controlPanelState.motors}
|
||||
|
@ -65,23 +42,6 @@ export function Motors(props: MotorsProps) {
|
|||
name={"motors"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!controlPanelState.motors}>
|
||||
<SingleSettingRow settingType="input"
|
||||
label={t("Max Retries")}
|
||||
tooltip={ToolTips.MAX_MOVEMENT_RETRIES}>
|
||||
<McuInputBox
|
||||
setting="param_mov_nr_retry"
|
||||
sourceFwConfig={sourceFwConfig}
|
||||
dispatch={dispatch} />
|
||||
</SingleSettingRow>
|
||||
<SingleSettingRow settingType="button"
|
||||
label={t("E-Stop on Movement Error")}
|
||||
tooltip={ToolTips.E_STOP_ON_MOV_ERR}>
|
||||
<ToggleButton
|
||||
toggleValue={eStopOnMoveError.value}
|
||||
dim={!eStopOnMoveError.consistent}
|
||||
toggleAction={() => dispatch(
|
||||
settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} />
|
||||
</SingleSettingRow>
|
||||
<NumericMCUInputGroup
|
||||
name={t("Max Speed (mm/s)")}
|
||||
tooltip={ToolTips.MAX_SPEED}
|
||||
|
@ -171,16 +131,6 @@ export function Motors(props: MotorsProps) {
|
|||
z={"movement_motor_current_z"}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />}
|
||||
{isExpressBoard(firmwareHardware) &&
|
||||
<NumericMCUInputGroup
|
||||
name={t("Stall Sensitivity")}
|
||||
tooltip={ToolTips.STALL_SENSITIVITY}
|
||||
x={"movement_stall_sensitivity_x"}
|
||||
y={"movement_stall_sensitivity_y"}
|
||||
z={"movement_stall_sensitivity_z"}
|
||||
gray={encodersDisabled}
|
||||
dispatch={dispatch}
|
||||
sourceFwConfig={sourceFwConfig} />}
|
||||
<SingleSettingRow settingType="button"
|
||||
label={t("Enable 2nd X Motor")}
|
||||
tooltip={ToolTips.ENABLE_X2_MOTOR}>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import * as React from "react";
|
||||
import { PinBindingsProps } from "../interfaces";
|
||||
import { Header } from "./header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { PinBindingsContent } from "../../pin_bindings/pin_bindings";
|
||||
|
||||
export function PinBindings(props: PinBindingsProps) {
|
||||
|
||||
const { pin_bindings } = props.controlPanelState;
|
||||
const { dispatch, resources } = props;
|
||||
|
||||
return <section>
|
||||
<Header
|
||||
expanded={pin_bindings}
|
||||
title={"Pin Bindings"}
|
||||
name={"pin_bindings"}
|
||||
dispatch={dispatch} />
|
||||
<Collapse isOpen={!!pin_bindings}>
|
||||
<PinBindingsContent dispatch={dispatch} resources={resources} />
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, Help } from "../../../ui/index";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
||||
export const SingleSettingRow =
|
||||
({ label, tooltip, settingType, children }: {
|
||||
label: string,
|
||||
tooltip: string,
|
||||
children: React.ReactChild,
|
||||
settingType: "button" | "input",
|
||||
}) =>
|
||||
<Row>
|
||||
<Col xs={6} className={"widget-body-tooltips"}>
|
||||
<label>{label}</label>
|
||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
||||
</Col>
|
||||
{settingType === "button"
|
||||
? <Col xs={2} className={"centered-button-div"}>{children}</Col>
|
||||
: <Col xs={6}>{children}</Col>}
|
||||
</Row>;
|
|
@ -1,40 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { getDevice } from "../../../device";
|
||||
import { Axis } from "../../interfaces";
|
||||
import { ToolTips } from "../../../constants";
|
||||
import { Row, Col, Help } from "../../../ui/index";
|
||||
import { ZeroRowProps } from "../interfaces";
|
||||
import { commandErr } from "../../actions";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
|
||||
const zero =
|
||||
(axis: Axis) => getDevice().setZero(axis).catch(commandErr("Zeroing"));
|
||||
const AXES: Axis[] = ["x", "y", "z"];
|
||||
|
||||
export function ZeroButton(props: { axis: Axis; disabled: boolean; }) {
|
||||
const { axis, disabled } = props;
|
||||
return <button
|
||||
className="fb-button yellow"
|
||||
disabled={disabled}
|
||||
onClick={() => zero(axis)}>
|
||||
{t("zero {{axis}}", { axis })}
|
||||
</button>;
|
||||
}
|
||||
|
||||
export function ZeroRow({ botDisconnected }: ZeroRowProps) {
|
||||
return <Row>
|
||||
<Col xs={6} className={"widget-body-tooltips"}>
|
||||
<label>
|
||||
{t("SET ZERO POSITION")}
|
||||
</label>
|
||||
<Help text={ToolTips.SET_ZERO_POSITION} requireClick={true}
|
||||
position={Position.RIGHT} />
|
||||
</Col>
|
||||
{AXES.map((axis) => {
|
||||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
||||
<ZeroButton axis={axis} disabled={botDisconnected} />
|
||||
</Col>;
|
||||
})}
|
||||
</Row>;
|
||||
}
|
|
@ -1,17 +1,12 @@
|
|||
import {
|
||||
BotState, Xyz, SourceFwConfig,
|
||||
ControlPanelState, ShouldDisplay
|
||||
ControlPanelState, Axis
|
||||
} from "../interfaces";
|
||||
import { McuParamName, McuParams, FirmwareHardware } from "farmbot/dist";
|
||||
import { IntegerSize } from "../../util";
|
||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
|
||||
export interface HomingRowProps {
|
||||
hardware: McuParams;
|
||||
botDisconnected: boolean;
|
||||
}
|
||||
|
||||
export interface ZeroRowProps {
|
||||
botDisconnected: boolean;
|
||||
}
|
||||
|
@ -19,9 +14,11 @@ export interface ZeroRowProps {
|
|||
export interface HomingAndCalibrationProps {
|
||||
dispatch: Function;
|
||||
bot: BotState;
|
||||
controlPanelState: ControlPanelState;
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
firmwareConfig: FirmwareConfig | undefined;
|
||||
botDisconnected: boolean;
|
||||
firmwareHardware: FirmwareHardware | undefined;
|
||||
}
|
||||
|
||||
export interface BooleanMCUInputGroupProps {
|
||||
|
@ -39,8 +36,13 @@ export interface BooleanMCUInputGroupProps {
|
|||
}
|
||||
|
||||
export interface CalibrationRowProps {
|
||||
type: "find_home" | "calibrate" | "zero";
|
||||
hardware: McuParams;
|
||||
botDisconnected: boolean;
|
||||
action(axis: Axis): void;
|
||||
toolTip: string;
|
||||
title: string;
|
||||
axisTitle: string;
|
||||
}
|
||||
|
||||
export interface NumericMCUInputGroupProps {
|
||||
|
@ -85,12 +87,29 @@ export interface MotorsProps {
|
|||
|
||||
export interface EncodersProps {
|
||||
dispatch: Function;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
controlPanelState: ControlPanelState;
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
firmwareHardware: FirmwareHardware | undefined;
|
||||
}
|
||||
|
||||
export interface EndStopsProps {
|
||||
dispatch: Function;
|
||||
controlPanelState: ControlPanelState;
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
}
|
||||
|
||||
export interface ErrorHandlingProps {
|
||||
dispatch: Function;
|
||||
controlPanelState: ControlPanelState;
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
}
|
||||
|
||||
export interface PinBindingsProps {
|
||||
dispatch: Function;
|
||||
controlPanelState: ControlPanelState;
|
||||
resources: ResourceIndex;
|
||||
}
|
||||
|
||||
export interface DangerZoneProps {
|
||||
dispatch: Function;
|
||||
controlPanelState: ControlPanelState;
|
||||
|
|
|
@ -56,8 +56,12 @@ const pinNumOrNamedPin =
|
|||
}
|
||||
: pin;
|
||||
|
||||
const DISABLE_DDI = (): DropDownItem => ({
|
||||
label: t("None"), value: 0
|
||||
});
|
||||
|
||||
const listItems = (resources: ResourceIndex): DropDownItem[] =>
|
||||
[...peripheralItems(resources), ...pinDropdowns(n => n)];
|
||||
[DISABLE_DDI(), ...peripheralItems(resources), ...pinDropdowns(n => n)];
|
||||
|
||||
const peripheralItems = (resources: ResourceIndex): DropDownItem[] => {
|
||||
const list = selectAllSavedPeripherals(resources)
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { Dictionary } from "farmbot";
|
||||
import { DiagnosticMessages } from "../../constants";
|
||||
import { docLink } from "../../ui/doc_link";
|
||||
import { trim } from "../../util/util";
|
||||
|
||||
const DiagnosticMessagesWiFiOrConfig =
|
||||
trim(`${DiagnosticMessages.WIFI_OR_CONFIG}
|
||||
${docLink("for-it-security-professionals")}`);
|
||||
|
||||
// I don't like this at all.
|
||||
// If anyone has a cleaner solution, I'd love to hear it.
|
||||
|
@ -16,13 +22,13 @@ export const TRUTH_TABLE: Readonly<Dictionary<string | undefined>> = {
|
|||
// 17: No MQTT connections.
|
||||
[0b10001]: DiagnosticMessages.NO_WS_AVAILABLE,
|
||||
// 24: Browser is connected to API and MQTT.
|
||||
[0b11000]: DiagnosticMessages.WIFI_OR_CONFIG,
|
||||
[0b11000]: DiagnosticMessagesWiFiOrConfig,
|
||||
// 9: At least the browser is connected to MQTT.
|
||||
[0b01001]: DiagnosticMessages.WIFI_OR_CONFIG,
|
||||
[0b01001]: DiagnosticMessagesWiFiOrConfig,
|
||||
// 8: At least the browser is connected to MQTT.
|
||||
[0b01000]: DiagnosticMessages.WIFI_OR_CONFIG,
|
||||
[0b01000]: DiagnosticMessagesWiFiOrConfig,
|
||||
// 25: Farmbot offline.
|
||||
[0b11001]: DiagnosticMessages.WIFI_OR_CONFIG,
|
||||
[0b11001]: DiagnosticMessagesWiFiOrConfig,
|
||||
// 2: Browser offline. Farmbot last seen by the API recently.
|
||||
[0b00010]: DiagnosticMessages.NO_WS_AVAILABLE,
|
||||
// 18: Farmbot last seen by the API recently.
|
||||
|
|
|
@ -5,7 +5,6 @@ import { FarmbotOsSettings } from "./components/farmbot_os_settings";
|
|||
import { Page, Col, Row } from "../ui/index";
|
||||
import { mapStateToProps } from "./state_to_props";
|
||||
import { Props } from "./interfaces";
|
||||
import { PinBindings } from "./pin_bindings/pin_bindings";
|
||||
import { getStatus } from "../connectivity/reducer_support";
|
||||
import { isFwHardwareValue } from "./components/firmware_hardware_support";
|
||||
|
||||
|
@ -48,9 +47,6 @@ export class RawDevices extends React.Component<Props, {}> {
|
|||
firmwareHardware={firmwareHardware}
|
||||
sourceFwConfig={this.props.sourceFwConfig}
|
||||
firmwareConfig={this.props.firmwareConfig} />
|
||||
<PinBindings
|
||||
dispatch={this.props.dispatch}
|
||||
resources={this.props.resources} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Page>;
|
||||
|
|
|
@ -93,7 +93,7 @@ export enum Feature {
|
|||
variables = "variables",
|
||||
}
|
||||
|
||||
/** Object fetched from FEATURE_MIN_VERSIONS_URL. */
|
||||
/** Object fetched from ExternalUrl.featureMinVersions. */
|
||||
export type MinOsFeatureLookup = Partial<Record<Feature, string>>;
|
||||
|
||||
export interface BotState {
|
||||
|
@ -245,7 +245,10 @@ export interface HardwareSettingsProps {
|
|||
export interface ControlPanelState {
|
||||
homing_and_calibration: boolean;
|
||||
motors: boolean;
|
||||
encoders_and_endstops: boolean;
|
||||
encoders: boolean;
|
||||
endstops: boolean;
|
||||
error_handling: boolean;
|
||||
pin_bindings: boolean;
|
||||
danger_zone: boolean;
|
||||
power_and_reset: boolean;
|
||||
pin_guard: boolean;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { sortByNameAndPin, ButtonPin } from "../list_and_label_support";
|
||||
import { sortByNameAndPin, ButtonPin, getSpecialActionLabel } from "../list_and_label_support";
|
||||
import { PinBindingSpecialAction } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("sortByNameAndPin()", () => {
|
||||
|
||||
|
@ -26,3 +27,11 @@ describe("sortByNameAndPin()", () => {
|
|||
sortTest(1, 1, Order.equal); // GPIO 1 == GPIO 1
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSpecialActionLabel()", () => {
|
||||
it("handles undefined values", () => {
|
||||
expect(getSpecialActionLabel(undefined)).toEqual("None");
|
||||
expect(getSpecialActionLabel("wrong" as PinBindingSpecialAction))
|
||||
.toEqual("");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { PinBindings } from "../pin_bindings";
|
||||
import { PinBindingsContent } from "../pin_bindings";
|
||||
import { mount } from "enzyme";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import {
|
||||
|
@ -8,15 +8,15 @@ import {
|
|||
import {
|
||||
fakeSequence, fakePinBinding
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { PinBindingsProps } from "../interfaces";
|
||||
import { PinBindingsContentProps } from "../interfaces";
|
||||
import {
|
||||
SpecialPinBinding,
|
||||
PinBindingType,
|
||||
PinBindingSpecialAction
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("<PinBindings/>", () => {
|
||||
function fakeProps(): PinBindingsProps {
|
||||
describe("<PinBindingsContent/>", () => {
|
||||
function fakeProps(): PinBindingsContentProps {
|
||||
const fakeSequence1 = fakeSequence();
|
||||
fakeSequence1.body.id = 1;
|
||||
fakeSequence1.body.name = "Sequence 1";
|
||||
|
@ -51,8 +51,8 @@ describe("<PinBindings/>", () => {
|
|||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
["pin bindings", "pin number", "none", "bind", "stock bindings"]
|
||||
const wrapper = mount(<PinBindingsContent {...p} />);
|
||||
["pin number", "none", "bind", "stock bindings"]
|
||||
.map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
["26", "action"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
PinBindingSpecialAction
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export interface PinBindingsProps {
|
||||
export interface PinBindingsContentProps {
|
||||
dispatch: Function;
|
||||
resources: ResourceIndex;
|
||||
}
|
||||
|
|
|
@ -32,9 +32,14 @@ export const specialActionLabelLookup: { [x: string]: string } = {
|
|||
|
||||
export const specialActionList: DropDownItem[] =
|
||||
Object.values(PinBindingSpecialAction)
|
||||
.filter(action => action != PinBindingSpecialAction.dump_info)
|
||||
.map((action: PinBindingSpecialAction) =>
|
||||
({ label: specialActionLabelLookup[action], value: action }));
|
||||
|
||||
export const getSpecialActionLabel =
|
||||
(action: PinBindingSpecialAction | undefined) =>
|
||||
specialActionLabelLookup[action || ""] || "";
|
||||
|
||||
/** Pin numbers for standard buttons. */
|
||||
export enum ButtonPin {
|
||||
estop = 16,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, FBSelect, NULL_CHOICE, DropDownItem } from "../../ui";
|
||||
import { Row, Col, FBSelect, DropDownItem } from "../../ui";
|
||||
import { PinBindingColWidth } from "./pin_bindings";
|
||||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { RpiGpioDiagram } from "./rpi_gpio_diagram";
|
||||
|
@ -13,9 +13,10 @@ import { pinBindingBody } from "./tagged_pin_binding_init";
|
|||
import { error, warning } from "../../toast/toast";
|
||||
import {
|
||||
validGpioPins, sysBindings, generatePinLabel, RpiPinList,
|
||||
bindingTypeLabelLookup, specialActionLabelLookup, specialActionList,
|
||||
bindingTypeLabelLookup, specialActionList,
|
||||
reservedPiGPIO,
|
||||
bindingTypeList
|
||||
bindingTypeList,
|
||||
getSpecialActionLabel
|
||||
} from "./list_and_label_support";
|
||||
import { SequenceSelectBox } from "../../sequences/sequence_select_box";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
|
@ -119,8 +120,6 @@ export class PinBindingInputGroup
|
|||
<BindingTypeDropDown
|
||||
bindingType={bindingType}
|
||||
setBindingType={this.setBindingType} />
|
||||
</Col>
|
||||
<Col xs={PinBindingColWidth.target}>
|
||||
{bindingType == PinBindingType.special
|
||||
? <ActionTargetDropDown
|
||||
specialActionInput={specialActionInput}
|
||||
|
@ -152,10 +151,10 @@ export const PinNumberInputGroup = (props: {
|
|||
const selectedPinNumber = isNumber(pinNumberInput) ? {
|
||||
label: generatePinLabel(pinNumberInput),
|
||||
value: "" + pinNumberInput
|
||||
} : NULL_CHOICE;
|
||||
} : undefined;
|
||||
|
||||
return <Row>
|
||||
<Col xs={1}>
|
||||
<Col xs={3}>
|
||||
<Popover position={Position.TOP}>
|
||||
<i className="fa fa-th-large" />
|
||||
<RpiGpioDiagram
|
||||
|
@ -181,7 +180,7 @@ export const BindingTypeDropDown = (props: {
|
|||
setBindingType: (ddi: DropDownItem) => void,
|
||||
}) => {
|
||||
const { bindingType, setBindingType } = props;
|
||||
return <FBSelect
|
||||
return <FBSelect extraClass={"binding-type-dropdown"}
|
||||
key={"binding_type_input_" + bindingType}
|
||||
onChange={setBindingType}
|
||||
selectedItem={{
|
||||
|
@ -213,12 +212,13 @@ export const ActionTargetDropDown = (props: {
|
|||
const { specialActionInput, setSpecialAction } = props;
|
||||
|
||||
const selectedSpecialAction = specialActionInput ? {
|
||||
label: specialActionLabelLookup[specialActionInput || ""],
|
||||
label: getSpecialActionLabel(specialActionInput),
|
||||
value: "" + specialActionInput
|
||||
} : NULL_CHOICE;
|
||||
} : undefined;
|
||||
|
||||
return <FBSelect
|
||||
key={"special_action_input_" + specialActionInput}
|
||||
customNullLabel={t("Select an action")}
|
||||
onChange={setSpecialAction}
|
||||
selectedItem={selectedSpecialAction}
|
||||
list={specialActionList} />;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { Widget, WidgetBody, WidgetHeader, Row, Col } from "../../ui";
|
||||
import { Row, Col, Help } from "../../ui";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { selectAllPinBindings } from "../../resources/selectors";
|
||||
import { PinBindingsProps, PinBindingListItems } from "./interfaces";
|
||||
import { PinBindingsContentProps, PinBindingListItems } from "./interfaces";
|
||||
import { PinBindingsList } from "./pin_bindings_list";
|
||||
import { PinBindingInputGroup } from "./pin_binding_input_group";
|
||||
import {
|
||||
|
@ -20,9 +20,8 @@ import { t } from "../../i18next_wrapper";
|
|||
/** Width of UI columns in Pin Bindings widget. */
|
||||
export enum PinBindingColWidth {
|
||||
pin = 4,
|
||||
type = 3,
|
||||
target = 4,
|
||||
button = 1
|
||||
type = 6,
|
||||
button = 2
|
||||
}
|
||||
|
||||
/** Use binding type to return a sequence ID or a special action. */
|
||||
|
@ -64,34 +63,29 @@ const PinBindingsListHeader = () =>
|
|||
<label>
|
||||
{t("Binding")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={PinBindingColWidth.target}>
|
||||
<label>
|
||||
{t("target")}
|
||||
</label>
|
||||
<Help text={ToolTips.PIN_BINDINGS} />
|
||||
</Col>
|
||||
</Row>;
|
||||
|
||||
export const PinBindings = (props: PinBindingsProps) => {
|
||||
export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
||||
const { dispatch, resources } = props;
|
||||
const pinBindings = apiPinBindings(resources);
|
||||
|
||||
return <Widget className="pin-bindings-widget">
|
||||
<WidgetHeader
|
||||
title={t("Pin Bindings")}
|
||||
helpText={ToolTips.PIN_BINDINGS}>
|
||||
return <div className="pin-bindings">
|
||||
<Row>
|
||||
<StockPinBindingsButton dispatch={dispatch} />
|
||||
<Popover
|
||||
position={Position.RIGHT_TOP}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
portalClassName={"bindings-warning-icon"}
|
||||
popoverClassName={"help"}>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<div>
|
||||
{t(ToolTips.PIN_BINDING_WARNING)}
|
||||
</div>
|
||||
</Popover>
|
||||
<StockPinBindingsButton dispatch={dispatch} />
|
||||
</WidgetHeader>
|
||||
<WidgetBody>
|
||||
</Row>
|
||||
<div>
|
||||
<PinBindingsListHeader />
|
||||
<PinBindingsList
|
||||
pinBindings={pinBindings}
|
||||
|
@ -101,6 +95,6 @@ export const PinBindings = (props: PinBindingsProps) => {
|
|||
pinBindings={pinBindings}
|
||||
dispatch={dispatch}
|
||||
resources={resources} />
|
||||
</WidgetBody>
|
||||
</Widget>;
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
bindingTypeLabelLookup, specialActionLabelLookup,
|
||||
generatePinLabel, sortByNameAndPin
|
||||
bindingTypeLabelLookup,
|
||||
generatePinLabel, sortByNameAndPin, getSpecialActionLabel
|
||||
} from "./list_and_label_support";
|
||||
import { destroy } from "../../api/crud";
|
||||
import { error } from "../../toast/toast";
|
||||
|
@ -36,12 +36,10 @@ export const PinBindingsList = (props: PinBindingsListProps) => {
|
|||
{generatePinLabel(pin_number)}
|
||||
</Col>
|
||||
<Col xs={PinBindingColWidth.type}>
|
||||
{t(bindingTypeLabelLookup[binding_type || ""])}
|
||||
</Col>
|
||||
<Col xs={PinBindingColWidth.target}>
|
||||
{t(bindingTypeLabelLookup[binding_type || ""])}:
|
||||
{sequence_id
|
||||
? findSequenceById(resources, sequence_id).body.name
|
||||
: t(specialActionLabelLookup[special_action || ""])}
|
||||
: t(getSpecialActionLabel(special_action))}
|
||||
</Col>
|
||||
<Col xs={PinBindingColWidth.button}>
|
||||
<button
|
||||
|
|
|
@ -11,7 +11,6 @@ import { maybeNegateStatus } from "../connectivity/maybe_negate_status";
|
|||
import { ReduxAction } from "../redux/interfaces";
|
||||
import { connectivityReducer, PingResultPayload } from "../connectivity/reducer";
|
||||
import { versionOK } from "../util";
|
||||
import { EXPECTED_MAJOR, EXPECTED_MINOR } from "./actions";
|
||||
import { DeepPartial } from "redux";
|
||||
import { incomingLegacyStatus } from "../connectivity/connect_device";
|
||||
import { merge } from "lodash";
|
||||
|
@ -27,7 +26,10 @@ export const initialState = (): BotState => ({
|
|||
controlPanelState: {
|
||||
homing_and_calibration: false,
|
||||
motors: false,
|
||||
encoders_and_endstops: false,
|
||||
encoders: false,
|
||||
endstops: false,
|
||||
error_handling: false,
|
||||
pin_bindings: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false
|
||||
|
@ -116,7 +118,10 @@ export const botReducer = generateReducer<BotState>(initialState())
|
|||
.add<boolean>(Actions.BULK_TOGGLE_CONTROL_PANEL, (s, a) => {
|
||||
s.controlPanelState.homing_and_calibration = a.payload;
|
||||
s.controlPanelState.motors = a.payload;
|
||||
s.controlPanelState.encoders_and_endstops = a.payload;
|
||||
s.controlPanelState.encoders = a.payload;
|
||||
s.controlPanelState.endstops = a.payload;
|
||||
s.controlPanelState.error_handling = a.payload;
|
||||
s.controlPanelState.pin_bindings = a.payload;
|
||||
s.controlPanelState.pin_guard = a.payload;
|
||||
s.controlPanelState.danger_zone = a.payload;
|
||||
return s;
|
||||
|
@ -199,8 +204,7 @@ function legacyStatusHandler(state: BotState,
|
|||
|
||||
const nextSyncStatus = maybeNegateStatus(info);
|
||||
|
||||
versionOK(informational_settings.controller_version,
|
||||
EXPECTED_MAJOR, EXPECTED_MINOR);
|
||||
versionOK(informational_settings.controller_version);
|
||||
state.hardware.informational_settings.sync_status = nextSyncStatus;
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
enum Org {
|
||||
FarmBot = "FarmBot",
|
||||
FarmBotLabs = "FarmBot-Labs",
|
||||
}
|
||||
|
||||
export enum FarmBotRepo {
|
||||
FarmBotWebApp = "Farmbot-Web-App",
|
||||
FarmBotOS = "farmbot_os",
|
||||
FarmBotArduinoFirmware = "farmbot-arduino-firmware",
|
||||
}
|
||||
|
||||
enum FbosFile {
|
||||
featureMinVersions = "FEATURE_MIN_VERSIONS.json",
|
||||
osReleaseNotes = "RELEASE_NOTES.md",
|
||||
}
|
||||
|
||||
export namespace ExternalUrl {
|
||||
const GITHUB = "https://github.com";
|
||||
const GITHUB_RAW = "https://raw.githubusercontent.com";
|
||||
const GITHUB_API = "https://api.github.com";
|
||||
const OPENFARM = "https://openfarm.cc";
|
||||
const SOFTWARE_DOCS = "https://software.farm.bot";
|
||||
const FORUM = "http://forum.farmbot.org";
|
||||
const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files";
|
||||
|
||||
const FBOS_RAW = `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}`;
|
||||
export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`;
|
||||
export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`;
|
||||
|
||||
export const latestRelease =
|
||||
`${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`;
|
||||
|
||||
export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`;
|
||||
export const webAppRepo =
|
||||
`${GITHUB}/${Org.FarmBot}/${FarmBotRepo.FarmBotWebApp}`;
|
||||
|
||||
export const softwareDocs = `${SOFTWARE_DOCS}/docs`;
|
||||
export const softwareForum = `${FORUM}/c/software`;
|
||||
|
||||
export namespace OpenFarm {
|
||||
export const cropApi = `${OPENFARM}/api/v1/crops/`;
|
||||
export const cropBrowse = `${OPENFARM}/crops/`;
|
||||
export const newCrop = `${OPENFARM}/en/crops/new`;
|
||||
}
|
||||
|
||||
export namespace Videos {
|
||||
export const desktop =
|
||||
`${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`;
|
||||
export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ export function FarmBotLayer(props: FarmBotLayerProps) {
|
|||
visible, stopAtHome, botSize, plantAreaOffset, mapTransformProps,
|
||||
peripherals, eStopStatus, botLocationData, getConfigValue
|
||||
} = props;
|
||||
return visible ? <g id="farmbot-layer">
|
||||
return visible ? <g id="farmbot-layer" style={{ pointerEvents: "none" }}>
|
||||
<VirtualFarmBot
|
||||
mapTransformProps={mapTransformProps}
|
||||
botLocationData={botLocationData}
|
||||
|
|
|
@ -50,6 +50,14 @@ describe("<ToolbaySlot />", () => {
|
|||
const wrapper = svgMount(<ToolbaySlot {...p} />);
|
||||
expect(wrapper.find("use").props().transform).toEqual(expected);
|
||||
});
|
||||
|
||||
it("handles bad data", () => {
|
||||
const p = fakeProps();
|
||||
p.pulloutDirection = 1.1;
|
||||
p.quadrant = 1.1;
|
||||
const wrapper = svgMount(<ToolbaySlot {...p} />);
|
||||
expect(wrapper.find("use").props().transform).toEqual("rotate(0, 10, 20)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<Tool/>", () => {
|
||||
|
|
|
@ -49,4 +49,8 @@ describe("textAnchorPosition()", () => {
|
|||
expect(textAnchorPosition(4, 3, true)).toEqual(END);
|
||||
expect(textAnchorPosition(4, 4, true)).toEqual(START);
|
||||
});
|
||||
|
||||
it("handles bad data", () => {
|
||||
expect(textAnchorPosition(1.1, 1.1, false)).toEqual(START);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,11 @@ jest.mock("../../../../../history", () => ({
|
|||
getPathArray: jest.fn(() => { return mockPath.split("/"); })
|
||||
}));
|
||||
|
||||
let mockDev = false;
|
||||
jest.mock("../../../../../account/dev/dev_support", () => ({
|
||||
DevSettings: { futureFeaturesEnabled: () => mockDev }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer";
|
||||
import {
|
||||
|
@ -53,6 +58,7 @@ describe("<ToolSlotLayer/>", () => {
|
|||
});
|
||||
|
||||
it("navigates to tools page", async () => {
|
||||
mockDev = true;
|
||||
mockPath = "/app/designer/plants";
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<ToolSlotLayer {...p} />);
|
||||
|
|
|
@ -48,10 +48,10 @@ describe("<ToolSlotPoint/>", () => {
|
|||
const p = fakeProps();
|
||||
p.slot.toolSlot.body.id = 1;
|
||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||
mockDev = false;
|
||||
mockDev = true;
|
||||
wrapper.find("g").first().simulate("click");
|
||||
expect(history.push).not.toHaveBeenCalled();
|
||||
mockDev = true;
|
||||
mockDev = false;
|
||||
wrapper.find("g").first().simulate("click");
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/1");
|
||||
});
|
||||
|
|
|
@ -180,23 +180,30 @@ export interface GantryToolSlotGraphicProps {
|
|||
xySwap: boolean;
|
||||
}
|
||||
|
||||
/** dimensions */
|
||||
enum Trough {
|
||||
width = 20,
|
||||
length = 45,
|
||||
wall = 4,
|
||||
}
|
||||
|
||||
export const GantryToolSlot = (props: GantryToolSlotGraphicProps) => {
|
||||
const { x, y, xySwap } = props;
|
||||
const slotLengthX = xySwap ? 24 : 49;
|
||||
const slotLengthY = xySwap ? 49 : 24;
|
||||
const slotLengthX = Trough.wall + (xySwap ? Trough.width : Trough.length);
|
||||
const slotLengthY = Trough.wall + (xySwap ? Trough.length : Trough.width);
|
||||
return <g id={"gantry-toolbay-slot"}>
|
||||
<rect
|
||||
x={x - slotLengthX / 2} y={y - slotLengthY / 2}
|
||||
width={slotLengthX} height={slotLengthY}
|
||||
stroke={Color.mediumGray} strokeWidth={4} strokeOpacity={0.25}
|
||||
stroke={Color.mediumGray} strokeWidth={Trough.wall} strokeOpacity={0.25}
|
||||
fill="transparent" />
|
||||
</g>;
|
||||
};
|
||||
|
||||
const SeedTrough = (props: ToolGraphicProps) => {
|
||||
const { x, y, hovered, dispatch, uuid, xySwap } = props;
|
||||
const slotLengthX = xySwap ? 20 : 45;
|
||||
const slotLengthY = xySwap ? 45 : 20;
|
||||
const slotLengthX = xySwap ? Trough.width : Trough.length;
|
||||
const slotLengthY = xySwap ? Trough.length : Trough.width;
|
||||
return <g id={"seed-trough"}
|
||||
onMouseOver={() => dispatch(setToolHover(uuid))}
|
||||
onMouseLeave={() => dispatch(setToolHover(undefined))}>
|
||||
|
|
|
@ -39,7 +39,7 @@ export const textAnchorPosition = (
|
|||
case Anchor.end: return { anchor: "end", x: -40, y: 10 };
|
||||
case Anchor.middleTop: return { anchor: "middle", x: 0, y: 60 };
|
||||
case Anchor.middleBottom: return { anchor: "middle", x: 0, y: -40 };
|
||||
default: return { anchor: "start", x: 40, y: 10 };
|
||||
default: throw new Error("https://xkcd.com/2200");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export function ToolSlotLayer(props: ToolSlotLayerProps) {
|
|||
const pathArray = getPathArray();
|
||||
const canClickTool = !(pathArray[3] === "plants" && pathArray.length > 4);
|
||||
const goToToolsPage = () => canClickTool &&
|
||||
!DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
|
||||
DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
|
||||
const { slots, visible, mapTransformProps } = props;
|
||||
const cursor = canClickTool ? "pointer" : "default";
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
|
|||
xySwap,
|
||||
};
|
||||
return <g id={"toolslot-" + id}
|
||||
onClick={() => DevSettings.futureFeaturesEnabled() &&
|
||||
onClick={() => !DevSettings.futureFeaturesEnabled() &&
|
||||
history.push(`/app/designer/tool-slots/${id}`)}>
|
||||
{pullout_direction &&
|
||||
<ToolbaySlot
|
||||
|
|
|
@ -67,9 +67,6 @@ export namespace OpenFarm {
|
|||
type: string;
|
||||
attributes: ImageAttrs;
|
||||
}
|
||||
|
||||
export const cropUrl = "https://openfarm.cc/api/v1/crops";
|
||||
export const browsingCropUrl = "https://openfarm.cc/crops/";
|
||||
}
|
||||
/** Returned by https://openfarm.cc/api/v1/crops?filter=q */
|
||||
export interface CropSearchResult {
|
||||
|
|
|
@ -139,7 +139,7 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
|||
panel={Panel.Weeds}
|
||||
linkTo={"/app/designer/weeds"}
|
||||
title={t("Weeds")} />
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
{!DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.Tools}
|
||||
linkTo={"/app/designer/tools"}
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { startCase, isArray, chain, isNumber } from "lodash";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Panel } from "../panel_header";
|
||||
import { ExternalUrl } from "../../external_urls";
|
||||
|
||||
interface InfoFieldProps {
|
||||
title: string;
|
||||
|
@ -170,7 +171,7 @@ const CropDragInfoTile =
|
|||
const EditOnOpenFarm = ({ slug }: { slug: string }) =>
|
||||
<div className="edit-on-openfarm">
|
||||
<span>{t("Edit on")} </span>
|
||||
<a href={OpenFarm.browsingCropUrl + slug} target="_blank"
|
||||
<a href={ExternalUrl.OpenFarm.cropBrowse + slug} target="_blank"
|
||||
title={t("Open OpenFarm.cc in a new tab")}>
|
||||
{"OpenFarm"}
|
||||
</a>
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as React from "react";
|
|||
import { mount } from "enzyme";
|
||||
import { PlantGrid } from "../plant_grid";
|
||||
import { saveGrid, stashGrid } from "../thunks";
|
||||
import { error } from "../../../../toast/toast";
|
||||
import { error, success } from "../../../../toast/toast";
|
||||
|
||||
describe("PlantGrid", () => {
|
||||
function fakeProps() {
|
||||
|
@ -39,8 +39,11 @@ describe("PlantGrid", () => {
|
|||
it("saves a grid", async () => {
|
||||
const props = fakeProps();
|
||||
const pg = mount<PlantGrid>(<PlantGrid {...props} />).instance();
|
||||
const oldId = pg.state.gridId;
|
||||
await pg.saveGrid();
|
||||
expect(saveGrid).toHaveBeenCalledWith(pg.state.gridId);
|
||||
expect(saveGrid).toHaveBeenCalledWith(oldId);
|
||||
expect(success).toHaveBeenCalledWith("16 plants added.");
|
||||
expect(pg.state.gridId).not.toEqual(oldId);
|
||||
});
|
||||
|
||||
it("stashes a grid", async () => {
|
||||
|
|
|
@ -9,13 +9,18 @@ import { initPlantGrid } from "./generate_grid";
|
|||
import { init } from "../../../api/crud";
|
||||
import { uuid } from "farmbot";
|
||||
import { saveGrid, stashGrid } from "./thunks";
|
||||
import { error } from "../../../toast/toast";
|
||||
import { error, success } from "../../../toast/toast";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { GridInput } from "./grid_input";
|
||||
|
||||
export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
||||
state: PlantGridState = { ...EMPTY_PLANT_GRID, gridId: uuid() };
|
||||
|
||||
get plantCount() {
|
||||
const { numPlantsH, numPlantsV } = this.state.grid;
|
||||
return numPlantsH * numPlantsV;
|
||||
}
|
||||
|
||||
onchange = (key: PlantGridKey, val: number) => {
|
||||
const grid = { ...this.state.grid, [key]: val };
|
||||
this.setState({ grid });
|
||||
|
@ -33,9 +38,7 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
|||
}
|
||||
|
||||
performPreview = () => {
|
||||
const { numPlantsH, numPlantsV } = this.state.grid;
|
||||
const total = numPlantsH * numPlantsV;
|
||||
if (total > 100) {
|
||||
if (this.plantCount > 100) {
|
||||
error(t("Please make a grid with less than 100 plants"));
|
||||
return;
|
||||
}
|
||||
|
@ -57,7 +60,10 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
|||
|
||||
saveGrid = () => {
|
||||
const p: Promise<{}> = this.props.dispatch(saveGrid(this.state.gridId));
|
||||
return p.then(() => this.setState(EMPTY_PLANT_GRID));
|
||||
return p.then(() => {
|
||||
success(t("{{ count }} plants added.", { count: this.plantCount }));
|
||||
this.setState({ ...EMPTY_PLANT_GRID, gridId: uuid() });
|
||||
});
|
||||
}
|
||||
|
||||
inputs = () => {
|
||||
|
@ -73,16 +79,16 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
|||
case "clean":
|
||||
return <div>
|
||||
<a className={"clear-button"} onClick={this.performPreview}>
|
||||
Preview
|
||||
{t("Preview")}
|
||||
</a>
|
||||
</div>;
|
||||
case "dirty":
|
||||
return <div>
|
||||
<a className={"clear-button"} onClick={this.revertPreview}>
|
||||
Cancel
|
||||
{t("Cancel")}
|
||||
</a>
|
||||
<a className={"clear-button"} onClick={this.saveGrid}>
|
||||
Save
|
||||
{t("Save")}
|
||||
</a>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { ExternalUrl } from "../../external_urls";
|
||||
|
||||
/** A stripped down version of OFSearchResult */
|
||||
interface Result {
|
||||
|
@ -24,7 +25,7 @@ export class OpenFarmResults extends React.Component<SearchResultProps, {}> {
|
|||
|
||||
get text(): JSX.Element {
|
||||
return <p>{`${t(Content.CROP_NOT_FOUND_INTRO)} `}
|
||||
<a href="https://openfarm.cc/en/crops/new" target="_blank">
|
||||
<a href={ExternalUrl.OpenFarm.newCrop} target="_blank">
|
||||
{t(Content.CROP_NOT_FOUND_LINK)}
|
||||
</a>
|
||||
</p>;
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { save, edit } from "../../../api/crud";
|
||||
import { SpecialStatus } from "farmbot";
|
||||
import { DEFAULT_CRITERIA } from "../criteria/interfaces";
|
||||
import { Content } from "../../../constants";
|
||||
|
||||
describe("<GroupDetailActive/>", () => {
|
||||
const fakeProps = (): GroupDetailActiveProps => {
|
||||
|
@ -105,16 +106,23 @@ describe("<GroupDetailActive/>", () => {
|
|||
});
|
||||
|
||||
it("shows paths", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<GroupDetailActive {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("optimized");
|
||||
});
|
||||
|
||||
it("doesn't show paths", () => {
|
||||
mockDev = false;
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<GroupDetailActive {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("optimized");
|
||||
expect(wrapper.text().toLowerCase()).toContain("0m");
|
||||
});
|
||||
|
||||
it("doesn't show paths", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<GroupDetailActive {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("0m");
|
||||
});
|
||||
|
||||
it("shows random warning text", () => {
|
||||
const p = fakeProps();
|
||||
p.group.body.sort_type = "random";
|
||||
const wrapper = mount(<GroupDetailActive {...p} />);
|
||||
expect(wrapper.text()).toContain(Content.SORT_DESCRIPTION);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
|
||||
|
||||
let mockDev = false;
|
||||
jest.mock("../../../account/dev/dev_support", () => ({
|
||||
DevSettings: {
|
||||
futureFeaturesEnabled: () => mockDev,
|
||||
}
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import {
|
||||
|
@ -141,6 +148,7 @@ describe("<Paths />", () => {
|
|||
p.pathPoints = cases.order.xy_ascending;
|
||||
const wrapper = mount<Paths>(<Paths {...p} />);
|
||||
expect(wrapper.state().pathData).toEqual(cases.distance);
|
||||
expect(wrapper.text().toLowerCase()).not.toContain("optimized");
|
||||
});
|
||||
|
||||
it.each<[PointGroupSortType]>([
|
||||
|
@ -154,4 +162,24 @@ describe("<Paths />", () => {
|
|||
expect(SORT_OPTIONS[sortType](cases.order.xy_ascending))
|
||||
.toEqual(cases.order[sortType]);
|
||||
});
|
||||
|
||||
it("renders new sort type", () => {
|
||||
mockDev = true;
|
||||
const p = fakeProps();
|
||||
const cases = pathTestCases();
|
||||
p.pathPoints = cases.order.xy_ascending;
|
||||
const wrapper = mount<Paths>(<Paths {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("optimized");
|
||||
});
|
||||
|
||||
it("doesn't generate data twice", () => {
|
||||
const p = fakeProps();
|
||||
const cases = pathTestCases();
|
||||
p.pathPoints = cases.order.xy_ascending;
|
||||
const wrapper = mount<Paths>(<Paths {...p} />);
|
||||
expect(wrapper.state().pathData).toEqual(cases.distance);
|
||||
wrapper.setState({ pathData: { nn: 0 } });
|
||||
wrapper.update();
|
||||
expect(wrapper.state().pathData).toEqual({ nn: 0 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,7 +45,6 @@ describe("<PointGroupItem/>", () => {
|
|||
const p = fakeProps();
|
||||
p.point = fakePlant();
|
||||
const i = new PointGroupItem(p);
|
||||
i.setState = jest.fn();
|
||||
const fakeImgEvent = imgEvent();
|
||||
await i.maybeGetCachedIcon(fakeImgEvent);
|
||||
const slug = i.props.point.body.pointer_type === "Plant" ?
|
||||
|
@ -55,11 +54,17 @@ describe("<PointGroupItem/>", () => {
|
|||
expect(setImgSrc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets icon in state", () => {
|
||||
const i = new PointGroupItem(fakeProps());
|
||||
i.setState = jest.fn();
|
||||
i.setIconState("fake icon");
|
||||
expect(i.setState).toHaveBeenCalledWith({ icon: "fake icon" });
|
||||
});
|
||||
|
||||
it("fetches point icon", () => {
|
||||
const p = fakeProps();
|
||||
p.point = fakePoint();
|
||||
const i = new PointGroupItem(p);
|
||||
i.setState = jest.fn();
|
||||
const fakeImgEvent = imgEvent();
|
||||
i.maybeGetCachedIcon(fakeImgEvent);
|
||||
expect(maybeGetCachedPlantIcon).not.toHaveBeenCalled();
|
||||
|
@ -71,7 +76,6 @@ describe("<PointGroupItem/>", () => {
|
|||
const p = fakeProps();
|
||||
p.point = fakeToolSlot();
|
||||
const i = new PointGroupItem(p);
|
||||
i.setState = jest.fn();
|
||||
const fakeImgEvent = imgEvent();
|
||||
i.maybeGetCachedIcon(fakeImgEvent);
|
||||
expect(maybeGetCachedPlantIcon).not.toHaveBeenCalled();
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
isSortType, sortTypeChange, SORT_OPTIONS, PointGroupSortSelector,
|
||||
PointGroupSortSelectorProps
|
||||
isSortType, sortTypeChange, SORT_OPTIONS
|
||||
} from "../point_group_sort_selector";
|
||||
import { DropDownItem } from "../../../ui";
|
||||
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { mount } from "enzyme";
|
||||
import { Content } from "../../../constants";
|
||||
|
||||
const tests: [string, boolean][] = [
|
||||
["", false],
|
||||
|
@ -89,15 +85,3 @@ describe("sort()", () => {
|
|||
expect(results).toEqual(["C", "D", "B", "A"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<PointGroupSortSelector />", () => {
|
||||
const fakeProps = (): PointGroupSortSelectorProps => ({
|
||||
onChange: jest.fn(),
|
||||
value: "random",
|
||||
});
|
||||
|
||||
it("shows random warning text", () => {
|
||||
const wrapper = mount(<PointGroupSortSelector {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain(Content.SORT_DESCRIPTION);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -97,9 +97,24 @@ export class GroupDetailActive
|
|||
defaultValue={group.body.name}
|
||||
onChange={this.update}
|
||||
onBlur={this.saveGroup} />
|
||||
<PointGroupSortSelector
|
||||
value={group.body.sort_type}
|
||||
onChange={this.changeSortType} />
|
||||
<div>
|
||||
<label>
|
||||
{t("SORT BY")}
|
||||
</label>
|
||||
{!DevSettings.futureFeaturesEnabled()
|
||||
? <Paths
|
||||
key={JSON.stringify(this.pointsSelectedByGroup
|
||||
.map(p => p.body.id))}
|
||||
pathPoints={this.pointsSelectedByGroup}
|
||||
dispatch={dispatch}
|
||||
group={group} />
|
||||
: <PointGroupSortSelector
|
||||
value={group.body.sort_type}
|
||||
onChange={this.changeSortType} />}
|
||||
<p>
|
||||
{group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}
|
||||
</p>
|
||||
</div>
|
||||
<label>
|
||||
{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}
|
||||
</label>
|
||||
|
@ -117,11 +132,6 @@ export class GroupDetailActive
|
|||
{this.props.shouldDisplay(Feature.criteria_groups) &&
|
||||
<GroupCriteria dispatch={dispatch}
|
||||
group={group} slugs={this.props.slugs} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<Paths
|
||||
pathPoints={this.pointsSelectedByGroup}
|
||||
dispatch={dispatch}
|
||||
group={group} />}
|
||||
<DeleteButton
|
||||
className="group-delete-btn"
|
||||
dispatch={dispatch}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { MapTransformProps } from "../map/interfaces";
|
||||
import { sortGroupBy, sortOptionsTable } from "./point_group_sort_selector";
|
||||
import { sortBy } from "lodash";
|
||||
import { sortBy, isNumber } from "lodash";
|
||||
import { PointsPathLine } from "./group_order_visual";
|
||||
import { Color } from "../../ui";
|
||||
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
|
||||
|
@ -10,6 +10,7 @@ import { Actions } from "../../constants";
|
|||
import { edit } from "../../api/crud";
|
||||
import { TaggedPointGroup, TaggedPoint } from "farmbot";
|
||||
import { error } from "../../toast/toast";
|
||||
import { DevSettings } from "../../account/dev/dev_support";
|
||||
|
||||
const xy = (point: TaggedPoint) => ({ x: point.body.x, y: point.body.y });
|
||||
|
||||
|
@ -66,7 +67,8 @@ export const PathInfoBar = (props: PathInfoBarProps) => {
|
|||
const normalizedLength = pathLength / maxLength * 100;
|
||||
const sortLabel =
|
||||
sortTypeKey == "nn" ? "Optimized" : sortOptionsTable()[sortTypeKey];
|
||||
return <div className={"sort-path-info-bar"}
|
||||
const selected = group.body.sort_type == sortTypeKey;
|
||||
return <div className={`sort-option-bar ${selected ? "selected" : ""}`}
|
||||
onMouseEnter={() =>
|
||||
dispatch({ type: Actions.TRY_SORT_TYPE, payload: sortTypeKey })}
|
||||
onMouseLeave={() =>
|
||||
|
@ -74,9 +76,11 @@ export const PathInfoBar = (props: PathInfoBarProps) => {
|
|||
onClick={() =>
|
||||
sortTypeKey == "nn"
|
||||
? error(t("Not supported yet."))
|
||||
: dispatch(edit(group, { sort_type: sortTypeKey }))}
|
||||
style={{ width: `${normalizedLength}%` }}>
|
||||
{`${sortLabel}: ${Math.round(pathLength / 10) / 100}m`}
|
||||
: dispatch(edit(group, { sort_type: sortTypeKey }))}>
|
||||
<div className={"sort-path-info-bar"}
|
||||
style={{ width: `${normalizedLength}%` }}>
|
||||
{`${sortLabel}: ${Math.round(pathLength / 10) / 100}m`}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -101,15 +105,17 @@ export class Paths extends React.Component<PathsProps, PathsState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.pathData.nn) { this.generatePathData(this.props.pathPoints); }
|
||||
return <div>
|
||||
<label>{t("Path lengths by sort type")}</label>
|
||||
{SORT_TYPES.concat("nn").map(st =>
|
||||
<PathInfoBar key={st}
|
||||
sortTypeKey={st}
|
||||
dispatch={this.props.dispatch}
|
||||
group={this.props.group}
|
||||
pathData={this.state.pathData} />)}
|
||||
if (!isNumber(this.state.pathData.nn)) {
|
||||
this.generatePathData(this.props.pathPoints);
|
||||
}
|
||||
return <div className={"group-sort-types"}>
|
||||
{SORT_TYPES.concat(DevSettings.futureFeaturesEnabled() ? "nn" : [])
|
||||
.map(sortType =>
|
||||
<PathInfoBar key={sortType}
|
||||
sortTypeKey={sortType}
|
||||
dispatch={this.props.dispatch}
|
||||
group={this.props.group}
|
||||
pathData={this.state.pathData} />)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ export class PointGroupItem
|
|||
this.leave();
|
||||
}
|
||||
|
||||
setIconState = (icon: string) => this.setState({ icon });
|
||||
|
||||
get criteriaIcon() {
|
||||
return !this.props.group.body.point_ids
|
||||
.includes(this.props.point.body.id || 0);
|
||||
|
@ -67,7 +69,7 @@ export class PointGroupItem
|
|||
switch (this.props.point.body.pointer_type) {
|
||||
case "Plant":
|
||||
const slug = this.props.point.body.openfarm_slug;
|
||||
maybeGetCachedPlantIcon(slug, img, icon => this.setState({ icon }));
|
||||
maybeGetCachedPlantIcon(slug, img, this.setIconState);
|
||||
break;
|
||||
case "GenericPointer":
|
||||
const { color } = this.props.point.body.meta;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
|
|||
import { FBSelect, DropDownItem } from "../../ui";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { shuffle, sortBy } from "lodash";
|
||||
import { Content } from "../../constants";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
|
||||
export interface PointGroupSortSelectorProps {
|
||||
|
@ -42,22 +41,11 @@ export const sortTypeChange = (cb: Function) => (ddi: DropDownItem) => {
|
|||
};
|
||||
|
||||
export function PointGroupSortSelector(p: PointGroupSortSelectorProps) {
|
||||
|
||||
return <div>
|
||||
<div className="default-value-tooltip">
|
||||
<label>
|
||||
{t("SORT BY")}
|
||||
</label>
|
||||
</div>
|
||||
<FBSelect
|
||||
key={p.value}
|
||||
list={optionPlusDescriptions()}
|
||||
selectedItem={selected(p.value as PointGroupSortType)}
|
||||
onChange={sortTypeChange(p.onChange)} />
|
||||
<p>
|
||||
{(p.value == "random") ? t(Content.SORT_DESCRIPTION) : ""}
|
||||
</p>
|
||||
</div>;
|
||||
return <FBSelect
|
||||
key={p.value}
|
||||
list={optionPlusDescriptions()}
|
||||
selectedItem={selected(p.value as PointGroupSortType)}
|
||||
onChange={sortTypeChange(p.onChange)} />;
|
||||
}
|
||||
|
||||
type Sorter = (p: TaggedPoint[]) => TaggedPoint[];
|
||||
|
|
|
@ -11,6 +11,7 @@ import { fakeState } from "../../../__test_support__/fake_state";
|
|||
import { SaveBtn } from "../../../ui";
|
||||
import { initSave } from "../../../api/crud";
|
||||
import { history } from "../../../history";
|
||||
import { error } from "../../../toast/toast";
|
||||
|
||||
describe("<AddTool />", () => {
|
||||
const fakeProps = (): AddToolProps => ({
|
||||
|
@ -37,10 +38,19 @@ describe("<AddTool />", () => {
|
|||
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
|
||||
});
|
||||
|
||||
it("adds stock tools", () => {
|
||||
it("doesn't add stock tools", () => {
|
||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(initSave).toHaveBeenCalledTimes(6);
|
||||
expect(error).toHaveBeenCalledWith("Please choose a FarmBot model.");
|
||||
expect(initSave).not.toHaveBeenCalledTimes(6);
|
||||
expect(history.push).not.toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
|
||||
it("adds stock tools", () => {
|
||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
||||
wrapper.setState({ model: "express" });
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(initSave).toHaveBeenCalledTimes(2);
|
||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,20 @@ import {
|
|||
} from "../designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { SaveBtn } from "../../ui";
|
||||
import { SaveBtn, FBSelect, DropDownItem } from "../../ui";
|
||||
import { SpecialStatus } from "farmbot";
|
||||
import { initSave } from "../../api/crud";
|
||||
import { Panel } from "../panel_header";
|
||||
import { history } from "../../history";
|
||||
import { error } from "../../toast/toast";
|
||||
|
||||
enum Model { genesis14 = "genesis14", genesis15 = "genesis15", express = "express" }
|
||||
|
||||
const MODEL_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
|
||||
[Model.genesis14]: { label: t("Genesis v1.2-v1.4"), value: Model.genesis14 },
|
||||
[Model.genesis15]: { label: t("Genesis v1.5+"), value: Model.genesis15 },
|
||||
[Model.express]: { label: t("Express"), value: Model.express },
|
||||
});
|
||||
|
||||
export interface AddToolProps {
|
||||
dispatch: Function;
|
||||
|
@ -17,6 +26,7 @@ export interface AddToolProps {
|
|||
|
||||
export interface AddToolState {
|
||||
toolName: string;
|
||||
model: Model | undefined;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): AddToolProps => ({
|
||||
|
@ -24,7 +34,7 @@ export const mapStateToProps = (props: Everything): AddToolProps => ({
|
|||
});
|
||||
|
||||
export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||
state: AddToolState = { toolName: "" };
|
||||
state: AddToolState = { toolName: "", model: undefined };
|
||||
|
||||
newTool = (name: string) => {
|
||||
this.props.dispatch(initSave("Tool", { name }));
|
||||
|
@ -35,28 +45,60 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
|||
history.push("/app/designer/tools");
|
||||
}
|
||||
|
||||
get stockToolNames() {
|
||||
return [
|
||||
t("Seeder"),
|
||||
t("Watering Nozzle"),
|
||||
t("Weeder"),
|
||||
t("Soil Sensor"),
|
||||
t("Seed Bin"),
|
||||
t("Seed Tray"),
|
||||
];
|
||||
stockToolNames = (model: Model) => {
|
||||
switch (model) {
|
||||
case Model.genesis14:
|
||||
return [
|
||||
t("Seeder"),
|
||||
t("Watering Nozzle"),
|
||||
t("Weeder"),
|
||||
t("Soil Sensor"),
|
||||
t("Seed Bin"),
|
||||
t("Seed Tray"),
|
||||
];
|
||||
case Model.genesis15:
|
||||
return [
|
||||
t("Seeder"),
|
||||
t("Watering Nozzle"),
|
||||
t("Weeder"),
|
||||
t("Soil Sensor"),
|
||||
t("Seed Bin"),
|
||||
t("Seed Tray"),
|
||||
t("Seed Trough 1"),
|
||||
t("Seed Trough 2"),
|
||||
];
|
||||
case Model.express:
|
||||
return [
|
||||
t("Seed Trough 1"),
|
||||
t("Seed Trough 2"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
AddStockTools = () =>
|
||||
<div className="add-stock-tools">
|
||||
<label>{t("Add stock tools")}</label>
|
||||
<ul>
|
||||
{this.stockToolNames.map(n => <li key={n}>{n}</li>)}
|
||||
</ul>
|
||||
<FBSelect
|
||||
customNullLabel={t("Choose model")}
|
||||
list={Object.values(MODEL_DDI_LOOKUP())}
|
||||
selectedItem={this.state.model
|
||||
? MODEL_DDI_LOOKUP()[this.state.model]
|
||||
: undefined}
|
||||
onChange={ddi => this.setState({ model: ddi.value as Model })}
|
||||
/>
|
||||
{this.state.model &&
|
||||
<ul>
|
||||
{this.stockToolNames(this.state.model).map(n => <li key={n}>{n}</li>)}
|
||||
</ul>}
|
||||
<button
|
||||
className="fb-button green"
|
||||
onClick={() => {
|
||||
this.stockToolNames.map(n => this.newTool(n));
|
||||
history.push("/app/designer/tools");
|
||||
if (this.state.model) {
|
||||
this.stockToolNames(this.state.model).map(n => this.newTool(n));
|
||||
history.push("/app/designer/tools");
|
||||
} else {
|
||||
error(t("Please choose a FarmBot model."));
|
||||
}
|
||||
}}>
|
||||
<i className="fa fa-plus" />
|
||||
{t("Stock Tools")}
|
||||
|
|
|
@ -159,14 +159,20 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
getToolName={this.getToolName} />)}
|
||||
</div>
|
||||
|
||||
InactiveTools = () =>
|
||||
<div className="inactive-tools">
|
||||
<label>{t("inactive tools")}</label>
|
||||
Tools = () =>
|
||||
<div className="tools">
|
||||
<div className="tools-header">
|
||||
<label>{t("tools")}</label>
|
||||
<Link to={"/app/designer/tools/add"}>
|
||||
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
||||
<i className="fa fa-plus" title={t("Add tool")} />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
{this.props.tools
|
||||
.filter(tool => !tool.body.name ||
|
||||
tool.body.name && tool.body.name.toLowerCase()
|
||||
.includes(this.state.searchTerm.toLowerCase()))
|
||||
.filter(tool => tool.body.status === "inactive")
|
||||
.map(tool =>
|
||||
<ToolInventoryItem key={tool.uuid}
|
||||
toolId={tool.body.id}
|
||||
|
@ -175,25 +181,26 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
|
||||
render() {
|
||||
const panelName = "tools";
|
||||
const hasTools = this.props.tools.length > 0;
|
||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Tools}
|
||||
linkTo={"/app/designer/tools/add"}
|
||||
title={t("Add tool")}>
|
||||
linkTo={!hasTools ? "/app/designer/tools/add" : undefined}
|
||||
title={!hasTools ? t("Add tool") : undefined}>
|
||||
<input type="text" onChange={this.update}
|
||||
placeholder={t("Search your tools...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.tools.length > 0}
|
||||
notEmpty={hasTools}
|
||||
graphic={EmptyStateGraphic.tools}
|
||||
title={t("Add a tool")}
|
||||
text={Content.NO_TOOLS}
|
||||
colorScheme={"tools"}>
|
||||
<this.MountedToolInfo />
|
||||
<this.ToolSlots />
|
||||
<this.InactiveTools />
|
||||
<this.Tools />
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue