model and version updates part 2
parent
310686508f
commit
a04ec59ba5
|
@ -1,10 +1,9 @@
|
||||||
jest.mock("../util", () => {
|
jest.mock("../util", () => ({
|
||||||
return {
|
attachToRoot: jest.fn(),
|
||||||
attachToRoot: jest.fn(),
|
// Incidental mock. Can be removed if errors go away.
|
||||||
// Incidental mock. Can be removed if errors go away.
|
trim: jest.fn(x => x),
|
||||||
trim: jest.fn(x => x)
|
urlFriendly: jest.fn(),
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock("../redux/store", () => {
|
jest.mock("../redux/store", () => {
|
||||||
return { store: { dispatch: jest.fn() } };
|
return { store: { dispatch: jest.fn() } };
|
||||||
|
|
|
@ -6,9 +6,9 @@ import { ExternalUrl } from "../external_urls";
|
||||||
describe("ExternalUrl", () => {
|
describe("ExternalUrl", () => {
|
||||||
it("returns urls", () => {
|
it("returns urls", () => {
|
||||||
expect(ExternalUrl.featureMinVersions)
|
expect(ExternalUrl.featureMinVersions)
|
||||||
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/FEATURE_MIN_VERSIONS.json");
|
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/FEATURE_MIN_VERSIONS.json");
|
||||||
expect(ExternalUrl.osReleaseNotes)
|
expect(ExternalUrl.osReleaseNotes)
|
||||||
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/RELEASE_NOTES.md");
|
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md");
|
||||||
expect(ExternalUrl.latestRelease)
|
expect(ExternalUrl.latestRelease)
|
||||||
.toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest");
|
.toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest");
|
||||||
expect(ExternalUrl.webAppRepo)
|
expect(ExternalUrl.webAppRepo)
|
||||||
|
@ -18,16 +18,16 @@ describe("ExternalUrl", () => {
|
||||||
expect(ExternalUrl.softwareDocs)
|
expect(ExternalUrl.softwareDocs)
|
||||||
.toEqual("https://software.farm.bot/docs");
|
.toEqual("https://software.farm.bot/docs");
|
||||||
expect(ExternalUrl.softwareForum)
|
expect(ExternalUrl.softwareForum)
|
||||||
.toEqual("http://forum.farmbot.org/c/software");
|
.toEqual("https://forum.farmbot.org/c/software");
|
||||||
expect(ExternalUrl.OpenFarm.cropApi)
|
expect(ExternalUrl.OpenFarm.cropApi)
|
||||||
.toEqual("https://openfarm.cc/api/v1/crops/");
|
.toEqual("https://openfarm.cc/api/v1/crops/");
|
||||||
expect(ExternalUrl.OpenFarm.cropBrowse)
|
expect(ExternalUrl.OpenFarm.cropBrowse)
|
||||||
.toEqual("https://openfarm.cc/crops/");
|
.toEqual("https://openfarm.cc/crops/");
|
||||||
expect(ExternalUrl.OpenFarm.newCrop)
|
expect(ExternalUrl.OpenFarm.newCrop)
|
||||||
.toEqual("https://openfarm.cc/en/crops/new");
|
.toEqual("https://openfarm.cc/en/crops/new");
|
||||||
expect(ExternalUrl.Videos.desktop)
|
expect(ExternalUrl.Video.desktop)
|
||||||
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018");
|
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018");
|
||||||
expect(ExternalUrl.Videos.mobile)
|
expect(ExternalUrl.Video.mobile)
|
||||||
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097");
|
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -702,9 +702,9 @@ export namespace Content {
|
||||||
trim(`FarmBot sent a malformed message. You may need to upgrade
|
trim(`FarmBot sent a malformed message. You may need to upgrade
|
||||||
FarmBot OS. Please upgrade FarmBot OS and log back in.`);
|
FarmBot OS. Please upgrade FarmBot OS and log back in.`);
|
||||||
|
|
||||||
export const OLD_FBOS_REC_UPGRADE = trim(`Your version of FarmBot OS is
|
export const OLD_FBOS_REC_UPGRADE =
|
||||||
outdated and will soon no longer be supported. Please update your device as
|
trim(`Your version of FarmBot OS is outdated and will soon no longer
|
||||||
soon as possible.`);
|
be supported. Please update your device as soon as possible.`);
|
||||||
|
|
||||||
export const EXPERIMENTAL_WARNING =
|
export const EXPERIMENTAL_WARNING =
|
||||||
trim(`Warning! This is an EXPERIMENTAL feature. This feature may be
|
trim(`Warning! This is an EXPERIMENTAL feature. This feature may be
|
||||||
|
@ -812,7 +812,10 @@ export namespace Content {
|
||||||
trim(`add this crop on OpenFarm?`);
|
trim(`add this crop on OpenFarm?`);
|
||||||
|
|
||||||
export const NO_TOOLS =
|
export const NO_TOOLS =
|
||||||
trim(`Press "+" to add a new tool.`);
|
trim(`Press "+" to add a new tool or seed container.`);
|
||||||
|
|
||||||
|
export const NO_SEED_CONTAINERS =
|
||||||
|
trim(`Press "+" to add a seed container.`);
|
||||||
|
|
||||||
export const MOUNTED_TOOL =
|
export const MOUNTED_TOOL =
|
||||||
trim(`The tool currently mounted to the UTM can be set here or by using
|
trim(`The tool currently mounted to the UTM can be set here or by using
|
||||||
|
@ -887,12 +890,23 @@ export namespace TourContent {
|
||||||
selecting one, and dragging it into the garden.`);
|
selecting one, and dragging it into the garden.`);
|
||||||
|
|
||||||
export const ADD_TOOLS =
|
export const ADD_TOOLS =
|
||||||
trim(`Press edit and then the + button to add tools and seed containers.`);
|
trim(`Press the + button to add tools and seed containers.`);
|
||||||
|
|
||||||
|
export const ADD_SEED_CONTAINERS =
|
||||||
|
trim(`Press the + button to add seed containers.`);
|
||||||
|
|
||||||
|
export const ADD_TOOLS_AND_SLOTS =
|
||||||
|
trim(`Press the + button to add tools and seed containers. Then create
|
||||||
|
tool slots for them to by pressing the tool slot + button.`);
|
||||||
|
|
||||||
|
export const ADD_SEED_CONTAINERS_AND_SLOTS =
|
||||||
|
trim(`Press the + button to add seed containers. Then create
|
||||||
|
slots for them to by pressing the seed container slot + button.`);
|
||||||
|
|
||||||
export const ADD_TOOLS_SLOTS =
|
export const ADD_TOOLS_SLOTS =
|
||||||
trim(`Add the newly created tools and seed containers to the
|
trim(`Add the newly created tools and seed containers to the
|
||||||
corresponding tool slots on FarmBot:
|
corresponding tool slots on FarmBot:
|
||||||
press edit and then + to create a tool slot.`);
|
press the + button to create a tool slot.`);
|
||||||
|
|
||||||
export const ADD_PERIPHERALS =
|
export const ADD_PERIPHERALS =
|
||||||
trim(`Press edit and then the + button to add peripherals.`);
|
trim(`Press edit and then the + button to add peripherals.`);
|
||||||
|
@ -930,6 +944,87 @@ export namespace TourContent {
|
||||||
trim(`Toggle various settings to customize your web app experience.`);
|
trim(`Toggle various settings to customize your web app experience.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DeviceSetting {
|
||||||
|
// Homing and calibration
|
||||||
|
homingAndCalibration = `Homing and Calibration`,
|
||||||
|
homing = `Homing`,
|
||||||
|
calibration = `Calibration`,
|
||||||
|
setZeroPosition = `Set Zero Position`,
|
||||||
|
findHomeOnBoot = `Find Home on Boot`,
|
||||||
|
stopAtHome = `Stop at Home`,
|
||||||
|
stopAtMax = `Stop at Max`,
|
||||||
|
negativeCoordinatesOnly = `Negative Coordinates Only`,
|
||||||
|
axisLength = `Axis Length (mm)`,
|
||||||
|
|
||||||
|
// Motors
|
||||||
|
motors = `Motors`,
|
||||||
|
maxSpeed = `Max Speed (mm/s)`,
|
||||||
|
homingSpeed = `Homing Speed (mm/s)`,
|
||||||
|
minimumSpeed = `Minimum Speed (mm/s)`,
|
||||||
|
accelerateFor = `Accelerate for (mm)`,
|
||||||
|
stepsPerMm = `Steps per MM`,
|
||||||
|
microstepsPerStep = `Microsteps per step`,
|
||||||
|
alwaysPowerMotors = `Always Power Motors`,
|
||||||
|
invertMotors = `Invert Motors`,
|
||||||
|
motorCurrent = `Motor Current`,
|
||||||
|
enable2ndXMotor = `Enable 2nd X Motor`,
|
||||||
|
invert2ndXMotor = `Invert 2nd X Motor`,
|
||||||
|
|
||||||
|
// Encoders / Stall Detection
|
||||||
|
encoders = `Encoders`,
|
||||||
|
stallDetection = `Stall Detection`,
|
||||||
|
enableEncoders = `Enable Encoders`,
|
||||||
|
enableStallDetection = `Enable Stall Detection`,
|
||||||
|
stallSensitivity = `Stall Sensitivity`,
|
||||||
|
useEncodersForPositioning = `Use Encoders for Positioning`,
|
||||||
|
invertEncoders = `Invert Encoders`,
|
||||||
|
maxMissedSteps = `Max Missed Steps`,
|
||||||
|
missedStepDecay = `Missed Step Decay`,
|
||||||
|
encoderScaling = `Encoder Scaling`,
|
||||||
|
|
||||||
|
// Endstops
|
||||||
|
endstops = `Endstops`,
|
||||||
|
enableEndstops = `Enable Endstops`,
|
||||||
|
swapEndstops = `Swap Endstops`,
|
||||||
|
invertEndstops = `Invert Endstops`,
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
errorHandling = `Error Handling`,
|
||||||
|
timeoutAfter = `Timeout after (seconds)`,
|
||||||
|
maxRetries = `Max Retries`,
|
||||||
|
estopOnMovementError = `E-Stop on Movement Error`,
|
||||||
|
|
||||||
|
// Pin Guard
|
||||||
|
pinGuard = `Pin Guard`,
|
||||||
|
|
||||||
|
// Danger Zone
|
||||||
|
dangerZone = `dangerZone`,
|
||||||
|
resetHardwareParams = `Reset hardware parameter defaults`,
|
||||||
|
|
||||||
|
// Pin Bindings
|
||||||
|
pinBindings = `Pin Bindings`,
|
||||||
|
|
||||||
|
// FarmBot OS
|
||||||
|
name = `name`,
|
||||||
|
timezone = `timezone`,
|
||||||
|
camera = `camera`,
|
||||||
|
firmware = `firmware`,
|
||||||
|
farmbotOSAutoUpdate = `Farmbot OS Auto Update`,
|
||||||
|
farmbotOS = `Farmbot OS`,
|
||||||
|
autoSync = `Auto Sync`,
|
||||||
|
bootSequence = `Boot Sequence`,
|
||||||
|
|
||||||
|
// Power and Reset
|
||||||
|
powerAndReset = `Power and Reset`,
|
||||||
|
restartFarmbot = `Restart Farmbot`,
|
||||||
|
shutdownFarmbot = `Shutdown Farmbot`,
|
||||||
|
restartFirmware = `Restart Firmware`,
|
||||||
|
factoryReset = `Factory Reset`,
|
||||||
|
autoFactoryReset = `Automatic Factory Reset`,
|
||||||
|
connectionAttemptPeriod = `Connection Attempt Period`,
|
||||||
|
changeOwnership = `Change Ownership`,
|
||||||
|
}
|
||||||
|
|
||||||
export namespace DiagnosticMessages {
|
export namespace DiagnosticMessages {
|
||||||
export const OK = trim(`All systems nominal.`);
|
export const OK = trim(`All systems nominal.`);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
||||||
|
|
||||||
peripherals = () => <Peripherals
|
peripherals = () => <Peripherals
|
||||||
|
firmwareHardware={this.props.firmwareHardware}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
peripherals={this.props.peripherals}
|
peripherals={this.props.peripherals}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
@ -50,6 +51,7 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
sensors = () => this.hideSensors
|
sensors = () => this.hideSensors
|
||||||
? <div id="hidden-sensors-widget" />
|
? <div id="hidden-sensors-widget" />
|
||||||
: <Sensors
|
: <Sensors
|
||||||
|
firmwareHardware={this.props.firmwareHardware}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
sensors={this.props.sensors}
|
sensors={this.props.sensors}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { bot } from "../../../__test_support__/fake_state/bot";
|
||||||
import { PeripheralsProps } from "../../../devices/interfaces";
|
import { PeripheralsProps } from "../../../devices/interfaces";
|
||||||
import { fakePeripheral } from "../../../__test_support__/fake_state/resources";
|
import { fakePeripheral } from "../../../__test_support__/fake_state/resources";
|
||||||
import { clickButton } from "../../../__test_support__/helpers";
|
import { clickButton } from "../../../__test_support__/helpers";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus, FirmwareHardware } from "farmbot";
|
||||||
import { error } from "../../../toast/toast";
|
import { error } from "../../../toast/toast";
|
||||||
|
|
||||||
describe("<Peripherals />", () => {
|
describe("<Peripherals />", () => {
|
||||||
|
@ -14,7 +14,8 @@ describe("<Peripherals />", () => {
|
||||||
bot,
|
bot,
|
||||||
peripherals: [fakePeripheral()],
|
peripherals: [fakePeripheral()],
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
disabled: false
|
disabled: false,
|
||||||
|
firmwareHardware: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +74,18 @@ describe("<Peripherals />", () => {
|
||||||
expect(p.dispatch).toHaveBeenCalled();
|
expect(p.dispatch).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds farmduino peripherals", () => {
|
it.each<[FirmwareHardware, number]>([
|
||||||
|
["arduino", 2],
|
||||||
|
["farmduino", 5],
|
||||||
|
["farmduino_k14", 5],
|
||||||
|
["farmduino_k15", 5],
|
||||||
|
["express_k10", 3],
|
||||||
|
])("adds peripherals: %s", (firmware, expectedAdds) => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = firmware;
|
||||||
const wrapper = mount(<Peripherals {...p} />);
|
const wrapper = mount(<Peripherals {...p} />);
|
||||||
wrapper.setState({ isEditing: true });
|
wrapper.setState({ isEditing: true });
|
||||||
clickButton(wrapper, 3, "farmduino");
|
clickButton(wrapper, 3, "stock");
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(5);
|
expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,12 +56,31 @@ export class Peripherals
|
||||||
this.props.dispatch(init("Peripheral", { pin, label }));
|
this.props.dispatch(init("Peripheral", { pin, label }));
|
||||||
};
|
};
|
||||||
|
|
||||||
farmduinoPeripherals = () => {
|
get stockPeripherals() {
|
||||||
this.newPeripheral(7, t("Lighting"));
|
switch (this.props.firmwareHardware) {
|
||||||
this.newPeripheral(8, t("Water"));
|
case "arduino":
|
||||||
this.newPeripheral(9, t("Vacuum"));
|
return [
|
||||||
this.newPeripheral(10, t("Peripheral ") + "4");
|
{ pin: 8, label: t("Water") },
|
||||||
this.newPeripheral(12, t("Peripheral ") + "5");
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
];
|
||||||
|
case "farmduino":
|
||||||
|
case "farmduino_k14":
|
||||||
|
case "farmduino_k15":
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
{ pin: 7, label: t("Lighting") },
|
||||||
|
{ pin: 8, label: t("Water") },
|
||||||
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
{ pin: 10, label: t("Peripheral ") + "4" },
|
||||||
|
{ pin: 12, label: t("Peripheral ") + "5" },
|
||||||
|
];
|
||||||
|
case "express_k10":
|
||||||
|
return [
|
||||||
|
{ pin: 7, label: t("Lighting") },
|
||||||
|
{ pin: 8, label: t("Water") },
|
||||||
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -92,10 +111,11 @@ export class Peripherals
|
||||||
hidden={!isEditing}
|
hidden={!isEditing}
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={this.farmduinoPeripherals}>
|
onClick={() => this.stockPeripherals.map(p =>
|
||||||
|
this.newPeripheral(p.pin, p.label))}>
|
||||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||||
Farmduino
|
{t("Stock")}
|
||||||
</button>
|
</button>
|
||||||
</WidgetHeader>
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
{this.showPins()}
|
{this.showPins()}
|
||||||
|
|
|
@ -18,7 +18,8 @@ describe("<Sensors />", () => {
|
||||||
bot,
|
bot,
|
||||||
sensors: [fakeSensor1, fakeSensor2],
|
sensors: [fakeSensor1, fakeSensor2],
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
disabled: false
|
disabled: false,
|
||||||
|
firmwareHardware: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +69,16 @@ describe("<Sensors />", () => {
|
||||||
it("adds stock sensors", () => {
|
it("adds stock sensors", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = mount(<Sensors {...p} />);
|
const wrapper = mount(<Sensors {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("stock sensors");
|
||||||
wrapper.setState({ isEditing: true });
|
wrapper.setState({ isEditing: true });
|
||||||
clickButton(wrapper, 3, "stock sensors");
|
clickButton(wrapper, 3, "stock sensors");
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(2);
|
expect(p.dispatch).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("doesn't display + stock button", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "express_k10";
|
||||||
|
const wrapper = mount(<Sensors {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("stock sensors");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -100,4 +100,11 @@ describe("<SensorList/>", function () {
|
||||||
readSensorBtn.last().simulate("click");
|
readSensorBtn.last().simulate("click");
|
||||||
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders analog reading", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.pins[50] && (p.pins[50].value = 600);
|
||||||
|
const wrapper = mount(<SensorList {...p} />);
|
||||||
|
expect(wrapper.html()).toContain("margin-left: -3.5rem");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { saveAll, init } from "../../api/crud";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { uniq } from "lodash";
|
import { uniq } from "lodash";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
export class Sensors extends React.Component<SensorsProps, SensorState> {
|
export class Sensors extends React.Component<SensorsProps, SensorState> {
|
||||||
constructor(props: SensorsProps) {
|
constructor(props: SensorsProps) {
|
||||||
|
@ -79,14 +80,15 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
|
||||||
onClick={() => this.newSensor()}>
|
onClick={() => this.newSensor()}>
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
{!isExpressBoard(this.props.firmwareHardware) &&
|
||||||
hidden={!isEditing}
|
<button
|
||||||
className="fb-button green"
|
hidden={!isEditing}
|
||||||
type="button"
|
className="fb-button green"
|
||||||
onClick={this.stockSensors}>
|
type="button"
|
||||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
onClick={this.stockSensors}>
|
||||||
{t("Stock sensors")}
|
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||||
</button>
|
{t("Stock sensors")}
|
||||||
|
</button>}
|
||||||
</WidgetHeader>
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
{this.showPins()}
|
{this.showPins()}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Padding for the popups.
|
// Padding for the popups.
|
||||||
.bp3-popover-content {
|
.bp3-popover-content {
|
||||||
|
z-index: 999;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1629,3 +1629,23 @@ textarea:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight,
|
||||||
|
.unhighlight {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: $light_yellow;
|
||||||
|
box-shadow: 0px 0px 7px 4px $light_yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unhighlight {
|
||||||
|
transition: background-color 10s linear, box-shadow 10s linear;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
|
@ -60,9 +60,9 @@ export class DemoIframe extends React.Component<{}, State> {
|
||||||
|
|
||||||
return <div className="demo-container">
|
return <div className="demo-container">
|
||||||
<video muted={true} autoPlay={true} loop={true} className="demo-video">
|
<video muted={true} autoPlay={true} loop={true} className="demo-video">
|
||||||
<source src={ExternalUrl.Videos.desktop} type="video/mp4" />
|
<source src={ExternalUrl.Video.desktop} type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
<img className="demo-phone" src={ExternalUrl.Videos.mobile} />
|
<img className="demo-phone" src={ExternalUrl.Video.mobile} />
|
||||||
<button className="demo-button" onClick={this.requestAccount}>
|
<button className="demo-button" onClick={this.requestAccount}>
|
||||||
{this.state.stage}
|
{this.state.stage}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -7,13 +7,14 @@ import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
import { settingToggle } from "../../actions";
|
import { settingToggle } from "../../actions";
|
||||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||||
import { BooleanMCUInputGroupProps } from "../interfaces";
|
import { BooleanMCUInputGroupProps } from "../interfaces";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
describe("BooleanMCUInputGroup", () => {
|
describe("BooleanMCUInputGroup", () => {
|
||||||
const fakeProps = (): BooleanMCUInputGroupProps => ({
|
const fakeProps = (): BooleanMCUInputGroupProps => ({
|
||||||
sourceFwConfig: x => ({ value: bot.hardware.mcu_params[x], consistent: true }),
|
sourceFwConfig: x => ({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
tooltip: "Tooltip",
|
tooltip: "Tooltip",
|
||||||
name: "Name",
|
label: DeviceSetting.invertEncoders,
|
||||||
x: "encoder_invert_x",
|
x: "encoder_invert_x",
|
||||||
y: "encoder_invert_y",
|
y: "encoder_invert_y",
|
||||||
z: "encoder_invert_z",
|
z: "encoder_invert_z",
|
||||||
|
|
|
@ -22,6 +22,8 @@ import axios from "axios";
|
||||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
import { edit } from "../../../api/crud";
|
import { edit } from "../../../api/crud";
|
||||||
import { fakeWebAppConfig } from "../../../__test_support__/fake_state/resources";
|
import { fakeWebAppConfig } from "../../../__test_support__/fake_state/resources";
|
||||||
|
import { formEvent } from "../../../__test_support__/fake_html_events";
|
||||||
|
import { Content } from "../../../constants";
|
||||||
|
|
||||||
describe("<FarmbotOsSettings />", () => {
|
describe("<FarmbotOsSettings />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -54,8 +56,8 @@ describe("<FarmbotOsSettings />", () => {
|
||||||
const osSettings = mount(<FarmbotOsSettings {...fakeProps()} />);
|
const osSettings = mount(<FarmbotOsSettings {...fakeProps()} />);
|
||||||
expect(osSettings.find("input").length).toBe(1);
|
expect(osSettings.find("input").length).toBe(1);
|
||||||
expect(osSettings.find("button").length).toBe(7);
|
expect(osSettings.find("button").length).toBe(7);
|
||||||
["NAME", "TIME ZONE", "FARMBOT OS", "CAMERA", "FIRMWARE"]
|
["name", "time zone", "farmbot os", "camera", "firmware"]
|
||||||
.map(string => expect(osSettings.text()).toContain(string));
|
.map(string => expect(osSettings.text().toLowerCase()).toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fetches OS release notes", async () => {
|
it("fetches OS release notes", async () => {
|
||||||
|
@ -115,4 +117,18 @@ describe("<FarmbotOsSettings />", () => {
|
||||||
const osSettings = shallow(<FarmbotOsSettings {...p} />);
|
const osSettings = shallow(<FarmbotOsSettings {...p} />);
|
||||||
expect(osSettings.find("BootSequenceSelector").length).toEqual(1);
|
expect(osSettings.find("BootSequenceSelector").length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prevents default form submit action", () => {
|
||||||
|
const osSettings = shallow(<FarmbotOsSettings {...fakeProps()} />);
|
||||||
|
const e = formEvent();
|
||||||
|
osSettings.find("form").simulate("submit", e);
|
||||||
|
expect(e.preventDefault).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns about timezone mismatch", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.deviceAccount.body.timezone = "different";
|
||||||
|
const osSettings = mount(<FarmbotOsSettings {...p} />);
|
||||||
|
expect(osSettings.text()).toContain(Content.DIFFERENT_TZ_WARNING);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { boardType } from "../firmware_hardware_support";
|
import { boardType, getFwHardwareValue } from "../firmware_hardware_support";
|
||||||
|
import { fakeFbosConfig } from "../../../__test_support__/fake_state/resources";
|
||||||
|
|
||||||
describe("boardType()", () => {
|
describe("boardType()", () => {
|
||||||
it("returns Farmduino", () => {
|
it("returns Farmduino", () => {
|
||||||
|
@ -32,3 +33,18 @@ describe("boardType()", () => {
|
||||||
expect(boardType("none")).toEqual("none");
|
expect(boardType("none")).toEqual("none");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getFwHardwareValue()", () => {
|
||||||
|
it("returns undefined", () => {
|
||||||
|
const fbosConfig = fakeFbosConfig();
|
||||||
|
fbosConfig.body.firmware_hardware = "wrong";
|
||||||
|
expect(getFwHardwareValue(fbosConfig)).toEqual(undefined);
|
||||||
|
expect(getFwHardwareValue(undefined)).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns real value", () => {
|
||||||
|
const fbosConfig = fakeFbosConfig();
|
||||||
|
fbosConfig.body.firmware_hardware = "express_k10";
|
||||||
|
expect(getFwHardwareValue(fbosConfig)).toEqual("express_k10");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
jest.mock("../../actions", () => ({
|
||||||
|
toggleControlPanel: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import {
|
||||||
|
Highlight, HighlightProps, maybeHighlight, maybeOpenPanel, highlight
|
||||||
|
} from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||||
|
import { toggleControlPanel } from "../../actions";
|
||||||
|
|
||||||
|
describe("<Highlight />", () => {
|
||||||
|
const fakeProps = (): HighlightProps => ({
|
||||||
|
settingName: DeviceSetting.motors,
|
||||||
|
children: <div />,
|
||||||
|
className: "section",
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fades highlight", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const wrapper = mount<Highlight>(<Highlight {...p} />);
|
||||||
|
wrapper.setState({ className: "highlight" });
|
||||||
|
wrapper.instance().componentDidMount();
|
||||||
|
expect(wrapper.state().className).toEqual("unhighlight");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("maybeHighlight()", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
highlight.opened = false;
|
||||||
|
highlight.highlighted = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("highlights only once", () => {
|
||||||
|
location.search = "?highlight=motors";
|
||||||
|
expect(maybeHighlight(DeviceSetting.motors)).toEqual("highlight");
|
||||||
|
expect(maybeHighlight(DeviceSetting.motors)).toEqual("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't highlight: different setting", () => {
|
||||||
|
location.search = "?highlight=name";
|
||||||
|
expect(maybeHighlight(DeviceSetting.motors)).toEqual("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't highlight: no matches", () => {
|
||||||
|
location.search = "?highlight=na";
|
||||||
|
expect(maybeHighlight(DeviceSetting.motors)).toEqual("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("maybeOpenPanel()", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
highlight.opened = false;
|
||||||
|
highlight.highlighted = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens panel only once", () => {
|
||||||
|
location.search = "?highlight=motors";
|
||||||
|
maybeOpenPanel(panelState())(jest.fn());
|
||||||
|
expect(toggleControlPanel).toHaveBeenCalledWith("motors");
|
||||||
|
jest.resetAllMocks();
|
||||||
|
maybeOpenPanel(panelState())(jest.fn());
|
||||||
|
expect(toggleControlPanel).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't open panel: already open", () => {
|
||||||
|
location.search = "?highlight=motors";
|
||||||
|
const panels = panelState();
|
||||||
|
panels.motors = true;
|
||||||
|
maybeOpenPanel(panels)(jest.fn());
|
||||||
|
expect(toggleControlPanel).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't open panel: no search term", () => {
|
||||||
|
location.search = "";
|
||||||
|
maybeOpenPanel(panelState())(jest.fn());
|
||||||
|
expect(toggleControlPanel).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,12 +4,14 @@ import { settingToggle } from "../actions";
|
||||||
import { Row, Col, Help } from "../../ui/index";
|
import { Row, Col, Help } from "../../ui/index";
|
||||||
import { BooleanMCUInputGroupProps } from "./interfaces";
|
import { BooleanMCUInputGroupProps } from "./interfaces";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
|
||||||
export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tooltip,
|
tooltip,
|
||||||
name,
|
label,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
z,
|
z,
|
||||||
|
@ -26,40 +28,42 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
||||||
const zParam = sourceFwConfig(z);
|
const zParam = sourceFwConfig(z);
|
||||||
|
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
<Highlight settingName={label}>
|
||||||
<label>
|
<Col xs={6} className={"widget-body-tooltips"}>
|
||||||
{name}
|
<label>
|
||||||
{caution &&
|
{t(label)}
|
||||||
<i className="fa fa-exclamation-triangle caution-icon" />}
|
{caution &&
|
||||||
</label>
|
<i className="fa fa-exclamation-triangle caution-icon" />}
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
</label>
|
||||||
</Col>
|
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</Col>
|
||||||
<ToggleButton
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
grayscale={grayscale?.x}
|
<ToggleButton
|
||||||
disabled={disable?.x}
|
grayscale={grayscale?.x}
|
||||||
dim={!xParam.consistent}
|
disabled={disable?.x}
|
||||||
toggleValue={xParam.value}
|
dim={!xParam.consistent}
|
||||||
toggleAction={() =>
|
toggleValue={xParam.value}
|
||||||
dispatch(settingToggle(x, sourceFwConfig, displayAlert))} />
|
toggleAction={() =>
|
||||||
</Col>
|
dispatch(settingToggle(x, sourceFwConfig, displayAlert))} />
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</Col>
|
||||||
<ToggleButton
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
grayscale={grayscale?.y}
|
<ToggleButton
|
||||||
disabled={disable?.y}
|
grayscale={grayscale?.y}
|
||||||
dim={!yParam.consistent}
|
disabled={disable?.y}
|
||||||
toggleValue={yParam.value}
|
dim={!yParam.consistent}
|
||||||
toggleAction={() =>
|
toggleValue={yParam.value}
|
||||||
dispatch(settingToggle(y, sourceFwConfig, displayAlert))} />
|
toggleAction={() =>
|
||||||
</Col>
|
dispatch(settingToggle(y, sourceFwConfig, displayAlert))} />
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</Col>
|
||||||
<ToggleButton
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
grayscale={grayscale?.z}
|
<ToggleButton
|
||||||
disabled={disable?.z}
|
grayscale={grayscale?.z}
|
||||||
dim={!zParam.consistent}
|
disabled={disable?.z}
|
||||||
toggleValue={zParam.value}
|
dim={!zParam.consistent}
|
||||||
toggleAction={() =>
|
toggleValue={zParam.value}
|
||||||
dispatch(settingToggle(z, sourceFwConfig, displayAlert))} />
|
toggleAction={() =>
|
||||||
</Col>
|
dispatch(settingToggle(z, sourceFwConfig, displayAlert))} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { FarmbotOsProps, FarmbotOsState, Feature } from "../interfaces";
|
||||||
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui";
|
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui";
|
||||||
import { save, edit } from "../../api/crud";
|
import { save, edit } from "../../api/crud";
|
||||||
import { isBotOnline } from "../must_be_online";
|
import { isBotOnline } from "../must_be_online";
|
||||||
import { Content } from "../../constants";
|
import { Content, DeviceSetting } from "../../constants";
|
||||||
import { TimezoneSelector } from "../timezones/timezone_selector";
|
import { TimezoneSelector } from "../timezones/timezone_selector";
|
||||||
import { timezoneMismatch } from "../timezones/guess_timezone";
|
import { timezoneMismatch } from "../timezones/guess_timezone";
|
||||||
import { CameraSelection } from "./fbos_settings/camera_selection";
|
import { CameraSelection } from "./fbos_settings/camera_selection";
|
||||||
|
@ -16,6 +16,7 @@ import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
|
||||||
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
||||||
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
|
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
|
||||||
import { ExternalUrl } from "../../external_urls";
|
import { ExternalUrl } from "../../external_urls";
|
||||||
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
|
||||||
export enum ColWidth {
|
export enum ColWidth {
|
||||||
label = 3,
|
label = 3,
|
||||||
|
@ -85,34 +86,38 @@ export class FarmbotOsSettings
|
||||||
</WidgetHeader>
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.name}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("NAME")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.name)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={9}>
|
</Col>
|
||||||
<input name="name"
|
<Col xs={9}>
|
||||||
onChange={this.changeBot}
|
<input name="name"
|
||||||
onBlur={this.updateBot}
|
onChange={this.changeBot}
|
||||||
value={this.props.deviceAccount.body.name} />
|
onBlur={this.updateBot}
|
||||||
</Col>
|
value={this.props.deviceAccount.body.name} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.timezone}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("TIME ZONE")}
|
<label>
|
||||||
</label>
|
{t("TIME ZONE")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<div className="note">
|
<Col xs={ColWidth.description}>
|
||||||
{this.maybeWarnTz()}
|
<div className="note">
|
||||||
</div>
|
{this.maybeWarnTz()}
|
||||||
<div>
|
</div>
|
||||||
<TimezoneSelector
|
<div>
|
||||||
currentTimezone={this.props.deviceAccount.body.timezone}
|
<TimezoneSelector
|
||||||
onUpdate={this.handleTimezone} />
|
currentTimezone={this.props.deviceAccount.body.timezone}
|
||||||
</div>
|
onUpdate={this.handleTimezone} />
|
||||||
</Col>
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
<CameraSelection
|
<CameraSelection
|
||||||
env={this.props.env}
|
env={this.props.env}
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col } from "../../../ui/index";
|
import { Row, Col } from "../../../ui/index";
|
||||||
import { ToggleButton } from "../../../controls/toggle_button";
|
import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
import { Content } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { updateConfig } from "../../actions";
|
import { updateConfig } from "../../actions";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { AutoSyncRowProps } from "./interfaces";
|
import { AutoSyncRowProps } from "./interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function AutoSyncRow(props: AutoSyncRowProps) {
|
export function AutoSyncRow(props: AutoSyncRowProps) {
|
||||||
const autoSync = props.sourceFbosConfig("auto_sync");
|
const autoSync = props.sourceFbosConfig("auto_sync");
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.autoSync}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("AUTO SYNC")}
|
<label>
|
||||||
</label>
|
{t("AUTO SYNC")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_SYNC)}
|
<p>
|
||||||
</p>
|
{t(Content.AUTO_SYNC)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<ToggleButton
|
<Col xs={ColWidth.button}>
|
||||||
toggleValue={autoSync.value}
|
<ToggleButton
|
||||||
dim={!autoSync.consistent}
|
toggleValue={autoSync.value}
|
||||||
toggleAction={() => {
|
dim={!autoSync.consistent}
|
||||||
props.dispatch(updateConfig({ auto_sync: !autoSync.value }));
|
toggleAction={() => {
|
||||||
}} />
|
props.dispatch(updateConfig({ auto_sync: !autoSync.value }));
|
||||||
</Col>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { Row, Col } from "../../../ui/index";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { ToggleButton } from "../../../controls/toggle_button";
|
import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
import { updateConfig } from "../../actions";
|
import { updateConfig } from "../../actions";
|
||||||
import { Content } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { AutoUpdateRowProps } from "./interfaces";
|
import { AutoUpdateRowProps } from "./interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector";
|
import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
||||||
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
||||||
|
@ -18,23 +19,25 @@ export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
||||||
value={props.device.body.ota_hour}
|
value={props.device.body.ota_hour}
|
||||||
onChange={changeOtaHour(props.dispatch, props.device)} />
|
onChange={changeOtaHour(props.dispatch, props.device)} />
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.farmbotOSAutoUpdate}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("FARMBOT OS AUTO UPDATE")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.farmbotOSAutoUpdate)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.OS_AUTO_UPDATE)}
|
<p>
|
||||||
</p>
|
{t(Content.OS_AUTO_UPDATE)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<ToggleButton toggleValue={osAutoUpdate.value}
|
<Col xs={ColWidth.button}>
|
||||||
dim={!osAutoUpdate.consistent}
|
<ToggleButton toggleValue={osAutoUpdate.value}
|
||||||
toggleAction={() => props.dispatch(updateConfig({
|
dim={!osAutoUpdate.consistent}
|
||||||
os_auto_update: !osAutoUpdate.value
|
toggleAction={() => props.dispatch(updateConfig({
|
||||||
}))} />
|
os_auto_update: !osAutoUpdate.value
|
||||||
</Col>
|
}))} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { FirmwareHardwareStatus } from "./firmware_hardware_status";
|
||||||
import {
|
import {
|
||||||
isFwHardwareValue, getFirmwareChoices, FIRMWARE_CHOICES_DDI
|
isFwHardwareValue, getFirmwareChoices, FIRMWARE_CHOICES_DDI
|
||||||
} from "../firmware_hardware_support";
|
} from "../firmware_hardware_support";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
interface BoardTypeState { sending: boolean }
|
interface BoardTypeState { sending: boolean }
|
||||||
|
|
||||||
|
@ -47,30 +49,32 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.firmware}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("FIRMWARE")}
|
<label>
|
||||||
</label>
|
{t("FIRMWARE")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<div>
|
<Col xs={ColWidth.description}>
|
||||||
<FBSelect
|
<div>
|
||||||
key={this.apiValue}
|
<FBSelect
|
||||||
extraClass={this.state.sending ? "dim" : ""}
|
key={this.apiValue}
|
||||||
list={getFirmwareChoices()}
|
extraClass={this.state.sending ? "dim" : ""}
|
||||||
selectedItem={this.selectedBoard}
|
list={getFirmwareChoices()}
|
||||||
onChange={this.sendOffConfig} />
|
selectedItem={this.selectedBoard}
|
||||||
</div>
|
onChange={this.sendOffConfig} />
|
||||||
</Col>
|
</div>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<FirmwareHardwareStatus
|
<Col xs={ColWidth.button}>
|
||||||
botOnline={this.props.botOnline}
|
<FirmwareHardwareStatus
|
||||||
apiFirmwareValue={this.apiValue}
|
botOnline={this.props.botOnline}
|
||||||
alerts={this.props.alerts}
|
apiFirmwareValue={this.apiValue}
|
||||||
bot={this.props.bot}
|
alerts={this.props.alerts}
|
||||||
dispatch={this.props.dispatch}
|
bot={this.props.bot}
|
||||||
timeSettings={this.props.timeSettings} />
|
dispatch={this.props.dispatch}
|
||||||
</Col>
|
timeSettings={this.props.timeSettings} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { selectAllSequences, findSequenceById } from "../../../resources/selecto
|
||||||
import { betterCompact } from "../../../util";
|
import { betterCompact } from "../../../util";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list: DropDownItem[];
|
list: DropDownItem[];
|
||||||
|
@ -56,18 +58,20 @@ export class RawBootSequenceSelector extends React.Component<Props, {}> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.bootSequence}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("BOOT SEQUENCE")}
|
<label>
|
||||||
</label>
|
{t("BOOT SEQUENCE")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={7}>
|
</Col>
|
||||||
<FBSelect
|
<Col xs={7}>
|
||||||
allowEmpty={true}
|
<FBSelect
|
||||||
list={this.props.list}
|
allowEmpty={true}
|
||||||
selectedItem={this.props.selectedItem}
|
list={this.props.list}
|
||||||
onChange={this.onChange} />
|
selectedItem={this.props.selectedItem}
|
||||||
</Col>
|
onChange={this.onChange} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { getDevice } from "../../../device";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { Feature, UserEnv } from "../../interfaces";
|
import { Feature, UserEnv } from "../../interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Content, ToolTips } from "../../../constants";
|
import { Content, ToolTips, DeviceSetting } from "../../../constants";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
/** Check if the camera has been disabled. */
|
/** Check if the camera has been disabled. */
|
||||||
export const cameraDisabled = (env: UserEnv): boolean =>
|
export const cameraDisabled = (env: UserEnv): boolean =>
|
||||||
|
@ -84,21 +85,23 @@ export class CameraSelection
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.camera}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("CAMERA")}
|
<label>
|
||||||
</label>
|
{t("CAMERA")}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<div>
|
<Col xs={ColWidth.description}>
|
||||||
<FBSelect
|
<div>
|
||||||
allowEmpty={false}
|
<FBSelect
|
||||||
list={CAMERA_CHOICES()}
|
allowEmpty={false}
|
||||||
selectedItem={this.selectedCamera()}
|
list={CAMERA_CHOICES()}
|
||||||
onChange={this.sendOffConfig}
|
selectedItem={this.selectedCamera()}
|
||||||
extraClass={this.props.botOnline ? "" : "disabled"} />
|
onChange={this.sendOffConfig}
|
||||||
</div>
|
extraClass={this.props.botOnline ? "" : "disabled"} />
|
||||||
</Col>
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col } from "../../../ui/index";
|
import { Row, Col } from "../../../ui/index";
|
||||||
import { Content } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { factoryReset, updateConfig } from "../../actions";
|
import { factoryReset, updateConfig } from "../../actions";
|
||||||
import { ToggleButton } from "../../../controls/toggle_button";
|
import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||||
import { FactoryResetRowProps } from "./interfaces";
|
import { FactoryResetRowProps } from "./interfaces";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function FactoryResetRow(props: FactoryResetRowProps) {
|
export function FactoryResetRow(props: FactoryResetRowProps) {
|
||||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||||
|
@ -14,66 +15,72 @@ export function FactoryResetRow(props: FactoryResetRowProps) {
|
||||||
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
|
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
|
||||||
return <div>
|
return <div>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.factoryReset}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("Factory Reset")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.factoryReset)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.FACTORY_RESET_WARNING)}
|
<p>
|
||||||
</p>
|
{t(Content.FACTORY_RESET_WARNING)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<button
|
<Col xs={ColWidth.button}>
|
||||||
className="fb-button red"
|
<button
|
||||||
type="button"
|
className="fb-button red"
|
||||||
onClick={factoryReset}
|
type="button"
|
||||||
disabled={!botOnline}>
|
onClick={factoryReset}
|
||||||
{t("FACTORY RESET")}
|
disabled={!botOnline}>
|
||||||
</button>
|
{t("FACTORY RESET")}
|
||||||
</Col>
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.autoFactoryReset}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("Automatic Factory Reset")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.autoFactoryReset)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_FACTORY_RESET)}
|
<p>
|
||||||
</p>
|
{t(Content.AUTO_FACTORY_RESET)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<ToggleButton
|
<Col xs={ColWidth.button}>
|
||||||
toggleValue={!disableFactoryReset.value}
|
<ToggleButton
|
||||||
dim={!disableFactoryReset.consistent}
|
toggleValue={!disableFactoryReset.value}
|
||||||
toggleAction={() => {
|
dim={!disableFactoryReset.consistent}
|
||||||
dispatch(updateConfig({
|
toggleAction={() => {
|
||||||
disable_factory_reset: !disableFactoryReset.value
|
dispatch(updateConfig({
|
||||||
}));
|
disable_factory_reset: !disableFactoryReset.value
|
||||||
}} />
|
}));
|
||||||
</Col>
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.connectionAttemptPeriod}>
|
||||||
<label style={maybeDisableTimer}>
|
<Col xs={ColWidth.label}>
|
||||||
{t("Connection Attempt Period")}
|
<label style={maybeDisableTimer}>
|
||||||
</label>
|
{t(DeviceSetting.connectionAttemptPeriod)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p style={maybeDisableTimer}>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_FACTORY_RESET_PERIOD)}
|
<p style={maybeDisableTimer}>
|
||||||
</p>
|
{t(Content.AUTO_FACTORY_RESET_PERIOD)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<BotConfigInputBox
|
<Col xs={ColWidth.button}>
|
||||||
setting="network_not_found_timer"
|
<BotConfigInputBox
|
||||||
dispatch={dispatch}
|
setting="network_not_found_timer"
|
||||||
disabled={!!disableFactoryReset.value}
|
dispatch={dispatch}
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
disabled={!!disableFactoryReset.value}
|
||||||
</Col>
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { FarmbotOsRowProps } from "./interfaces";
|
||||||
import { FbosDetails } from "./fbos_details";
|
import { FbosDetails } from "./fbos_details";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { ErrorBoundary } from "../../../error_boundary";
|
import { ErrorBoundary } from "../../../error_boundary";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
const getVersionString =
|
const getVersionString =
|
||||||
(fbosVersion: string | undefined, onBeta: boolean | undefined): string => {
|
(fbosVersion: string | undefined, onBeta: boolean | undefined): string => {
|
||||||
|
@ -21,48 +23,50 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
||||||
} = bot.hardware.informational_settings;
|
} = bot.hardware.informational_settings;
|
||||||
const version = getVersionString(controller_version, currently_on_beta);
|
const version = getVersionString(controller_version, currently_on_beta);
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={DeviceSetting.farmbotOS}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t("FARMBOT OS")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.farmbotOS)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={3}>
|
</Col>
|
||||||
<Popover position={Position.BOTTOM_LEFT}>
|
<Col xs={3}>
|
||||||
<p>
|
<Popover position={Position.BOTTOM_LEFT}>
|
||||||
{t("Version {{ version }}", { version })}
|
<p>
|
||||||
</p>
|
{t("Version {{ version }}", { version })}
|
||||||
<ErrorBoundary>
|
</p>
|
||||||
<FbosDetails
|
<ErrorBoundary>
|
||||||
botInfoSettings={bot.hardware.informational_settings}
|
<FbosDetails
|
||||||
dispatch={dispatch}
|
botInfoSettings={bot.hardware.informational_settings}
|
||||||
shouldDisplay={props.shouldDisplay}
|
dispatch={dispatch}
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
shouldDisplay={props.shouldDisplay}
|
||||||
botToMqttLastSeen={props.botToMqttLastSeen}
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
timeSettings={props.timeSettings}
|
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||||
deviceAccount={props.deviceAccount} />
|
timeSettings={props.timeSettings}
|
||||||
</ErrorBoundary>
|
deviceAccount={props.deviceAccount} />
|
||||||
</Popover>
|
</ErrorBoundary>
|
||||||
</Col>
|
</Popover>
|
||||||
<Col xs={3}>
|
</Col>
|
||||||
<Popover position={Position.BOTTOM}>
|
<Col xs={3}>
|
||||||
<p className="release-notes-button">
|
<Popover position={Position.BOTTOM}>
|
||||||
{t("Release Notes")}
|
<p className="release-notes-button">
|
||||||
|
{t("Release Notes")}
|
||||||
<i className="fa fa-caret-down" />
|
<i className="fa fa-caret-down" />
|
||||||
</p>
|
</p>
|
||||||
<div className="release-notes">
|
<div className="release-notes">
|
||||||
<h1>{props.osReleaseNotesHeading}</h1>
|
<h1>{props.osReleaseNotesHeading}</h1>
|
||||||
<Markdown>
|
<Markdown>
|
||||||
{osReleaseNotes}
|
{osReleaseNotes}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
<OsUpdateButton
|
<OsUpdateButton
|
||||||
bot={bot}
|
bot={bot}
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
shouldDisplay={props.shouldDisplay}
|
shouldDisplay={props.shouldDisplay}
|
||||||
botOnline={botOnline} />
|
botOnline={botOnline} />
|
||||||
</Col>
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ import * as React from "react";
|
||||||
import { Row, Col } from "../../../ui";
|
import { Row, Col } from "../../../ui";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
export interface FbosButtonRowProps {
|
export interface FbosButtonRowProps {
|
||||||
botOnline: boolean;
|
botOnline: boolean;
|
||||||
label: string;
|
label: DeviceSetting;
|
||||||
description: string;
|
description: string;
|
||||||
buttonText: string;
|
buttonText: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
@ -14,24 +16,26 @@ export interface FbosButtonRowProps {
|
||||||
|
|
||||||
export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Highlight settingName={props.label}>
|
||||||
<label>
|
<Col xs={ColWidth.label}>
|
||||||
{t(props.label)}
|
<label>
|
||||||
</label>
|
{t(props.label)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={ColWidth.description}>
|
</Col>
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(props.description)}
|
<p>
|
||||||
</p>
|
{t(props.description)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>
|
||||||
<button
|
<Col xs={ColWidth.button}>
|
||||||
className={`fb-button ${props.color}`}
|
<button
|
||||||
type="button"
|
className={`fb-button ${props.color}`}
|
||||||
onClick={props.action}
|
type="button"
|
||||||
disabled={!props.botOnline}>
|
onClick={props.action}
|
||||||
{t(props.buttonText)}
|
disabled={!props.botOnline}>
|
||||||
</button>
|
{t(props.buttonText)}
|
||||||
</Col>
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,37 +5,39 @@ import { FactoryResetRow } from "./factory_reset_row";
|
||||||
import { PowerAndResetProps } from "./interfaces";
|
import { PowerAndResetProps } from "./interfaces";
|
||||||
import { ChangeOwnershipForm } from "./change_ownership_form";
|
import { ChangeOwnershipForm } from "./change_ownership_form";
|
||||||
import { FbosButtonRow } from "./fbos_button_row";
|
import { FbosButtonRow } from "./fbos_button_row";
|
||||||
import { Content } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { reboot, powerOff, restartFirmware } from "../../actions";
|
import { reboot, powerOff, restartFirmware } from "../../actions";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function PowerAndReset(props: PowerAndResetProps) {
|
export function PowerAndReset(props: PowerAndResetProps) {
|
||||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||||
const { power_and_reset } = props.controlPanelState;
|
const { power_and_reset } = props.controlPanelState;
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.powerAndReset}>
|
||||||
<Header
|
<Header
|
||||||
expanded={power_and_reset}
|
expanded={power_and_reset}
|
||||||
title={t("Power and Reset")}
|
title={DeviceSetting.powerAndReset}
|
||||||
name={"power_and_reset"}
|
panel={"power_and_reset"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!power_and_reset}>
|
<Collapse isOpen={!!power_and_reset}>
|
||||||
<FbosButtonRow
|
<FbosButtonRow
|
||||||
botOnline={botOnline}
|
botOnline={botOnline}
|
||||||
label={t("RESTART FARMBOT")}
|
label={DeviceSetting.restartFarmbot}
|
||||||
description={Content.RESTART_FARMBOT}
|
description={Content.RESTART_FARMBOT}
|
||||||
buttonText={t("RESTART")}
|
buttonText={t("RESTART")}
|
||||||
color={"yellow"}
|
color={"yellow"}
|
||||||
action={reboot} />
|
action={reboot} />
|
||||||
<FbosButtonRow
|
<FbosButtonRow
|
||||||
botOnline={botOnline}
|
botOnline={botOnline}
|
||||||
label={t("SHUTDOWN FARMBOT")}
|
label={DeviceSetting.shutdownFarmbot}
|
||||||
description={Content.SHUTDOWN_FARMBOT}
|
description={Content.SHUTDOWN_FARMBOT}
|
||||||
buttonText={t("SHUTDOWN")}
|
buttonText={t("SHUTDOWN")}
|
||||||
color={"red"}
|
color={"red"}
|
||||||
action={powerOff} />
|
action={powerOff} />
|
||||||
<FbosButtonRow
|
<FbosButtonRow
|
||||||
botOnline={botOnline}
|
botOnline={botOnline}
|
||||||
label={t("RESTART FIRMWARE")}
|
label={DeviceSetting.restartFirmware}
|
||||||
description={Content.RESTART_FIRMWARE}
|
description={Content.RESTART_FIRMWARE}
|
||||||
buttonText={t("RESTART")}
|
buttonText={t("RESTART")}
|
||||||
color={"yellow"}
|
color={"yellow"}
|
||||||
|
@ -45,14 +47,15 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
botOnline={botOnline} />
|
botOnline={botOnline} />
|
||||||
{botOnline &&
|
{botOnline &&
|
||||||
<Popover position={Position.BOTTOM_LEFT}>
|
<Highlight settingName={DeviceSetting.changeOwnership}>
|
||||||
<p className={"release-notes-button"}>
|
<Popover position={Position.BOTTOM_LEFT}>
|
||||||
{t("Change Ownership")}
|
<p className={"release-notes-button"}>
|
||||||
<i className="fa fa-caret-down" />
|
{t(DeviceSetting.changeOwnership)}
|
||||||
</p>
|
<i className="fa fa-caret-down" />
|
||||||
<ChangeOwnershipForm />
|
</p>
|
||||||
</Popover>
|
<ChangeOwnershipForm />
|
||||||
}
|
</Popover>
|
||||||
|
</Highlight>}
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FirmwareHardware } from "farmbot";
|
import { FirmwareHardware, TaggedFbosConfig } from "farmbot";
|
||||||
|
|
||||||
export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => {
|
export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => {
|
||||||
const values: FirmwareHardware[] = [
|
const values: FirmwareHardware[] = [
|
||||||
|
@ -10,6 +10,12 @@ export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => {
|
||||||
return !!values.includes(x as FirmwareHardware);
|
return !!values.includes(x as FirmwareHardware);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFwHardwareValue =
|
||||||
|
(fbosConfig: TaggedFbosConfig | undefined) => {
|
||||||
|
const value = fbosConfig?.body.firmware_hardware;
|
||||||
|
return isFwHardwareValue(value) ? value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const TMC_BOARDS = ["express_k10", "farmduino_k15"];
|
const TMC_BOARDS = ["express_k10", "farmduino_k15"];
|
||||||
const EXPRESS_BOARDS = ["express_k10"];
|
const EXPRESS_BOARDS = ["express_k10"];
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,14 @@ import { FwParamExportMenu } from "./hardware_settings/export_menu";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { PinBindings } from "./hardware_settings/pin_bindings";
|
import { PinBindings } from "./hardware_settings/pin_bindings";
|
||||||
import { ErrorHandling } from "./hardware_settings/error_handling";
|
import { ErrorHandling } from "./hardware_settings/error_handling";
|
||||||
|
import { maybeOpenPanel } from "./maybe_highlight";
|
||||||
|
|
||||||
export class HardwareSettings extends
|
export class HardwareSettings extends
|
||||||
React.Component<HardwareSettingsProps, {}> {
|
React.Component<HardwareSettingsProps, {}> {
|
||||||
|
|
||||||
|
componentDidMount = () =>
|
||||||
|
this.props.dispatch(maybeOpenPanel(this.props.controlPanelState));
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig,
|
bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { mount } from "enzyme";
|
||||||
import { CalibrationRow } from "../calibration_row";
|
import { CalibrationRow } from "../calibration_row";
|
||||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||||
import { CalibrationRowProps } from "../../interfaces";
|
import { CalibrationRowProps } from "../../interfaces";
|
||||||
|
import { DeviceSetting } from "../../../../constants";
|
||||||
|
|
||||||
describe("<CalibrationRow />", () => {
|
describe("<CalibrationRow />", () => {
|
||||||
const fakeProps = (): CalibrationRowProps => ({
|
const fakeProps = (): CalibrationRowProps => ({
|
||||||
|
@ -11,7 +12,7 @@ describe("<CalibrationRow />", () => {
|
||||||
botDisconnected: false,
|
botDisconnected: false,
|
||||||
action: jest.fn(),
|
action: jest.fn(),
|
||||||
toolTip: "calibrate",
|
toolTip: "calibrate",
|
||||||
title: "calibrate",
|
title: DeviceSetting.calibration,
|
||||||
axisTitle: "calibrate",
|
axisTitle: "calibrate",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Header } from "../header";
|
import { Header } from "../header";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
|
import { DeviceSetting } from "../../../../constants";
|
||||||
|
|
||||||
describe("<Header/>", () => {
|
describe("<Header/>", () => {
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const fn = jest.fn();
|
const fn = jest.fn();
|
||||||
const el = mount(<Header
|
const el = mount(<Header
|
||||||
title="FOO"
|
title={DeviceSetting.motors}
|
||||||
expanded={true}
|
expanded={true}
|
||||||
name={"motors"}
|
panel={"motors"}
|
||||||
dispatch={fn} />);
|
dispatch={fn} />);
|
||||||
expect(el.text()).toContain("FOO");
|
expect(el.text().toLowerCase()).toContain("motors");
|
||||||
expect(el.find(".fa-minus").length).toBe(1);
|
expect(el.find(".fa-minus").length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,30 +5,33 @@ import { Row, Col, Help } from "../../../ui/index";
|
||||||
import { CalibrationRowProps } from "../interfaces";
|
import { CalibrationRowProps } from "../interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function CalibrationRow(props: CalibrationRowProps) {
|
export function CalibrationRow(props: CalibrationRowProps) {
|
||||||
|
|
||||||
const { hardware, botDisconnected } = props;
|
const { hardware, botDisconnected } = props;
|
||||||
|
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
<Highlight settingName={props.title}>
|
||||||
<label>
|
<Col xs={6} className={"widget-body-tooltips"}>
|
||||||
{t(props.title)}
|
<label>
|
||||||
</label>
|
{t(props.title)}
|
||||||
<Help text={t(props.toolTip)}
|
</label>
|
||||||
requireClick={true} position={Position.RIGHT} />
|
<Help text={t(props.toolTip)}
|
||||||
</Col>
|
requireClick={true} position={Position.RIGHT} />
|
||||||
{axisTrackingStatus(hardware)
|
</Col>
|
||||||
.map(row => {
|
{axisTrackingStatus(hardware)
|
||||||
const { axis } = row;
|
.map(row => {
|
||||||
const hardwareDisabled = props.type == "zero" ? false : row.disabled;
|
const { axis } = row;
|
||||||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
const hardwareDisabled = props.type == "zero" ? false : row.disabled;
|
||||||
<LockableButton
|
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
||||||
disabled={hardwareDisabled || botDisconnected}
|
<LockableButton
|
||||||
onClick={() => props.action(axis)}>
|
disabled={hardwareDisabled || botDisconnected}
|
||||||
{`${t(props.axisTitle)} ${axis}`}
|
onClick={() => props.action(axis)}>
|
||||||
</LockableButton>
|
{`${t(props.axisTitle)} ${axis}`}
|
||||||
</Col>;
|
</LockableButton>
|
||||||
})}
|
</Col>;
|
||||||
|
})}
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,41 +3,45 @@ import { DangerZoneProps } from "../interfaces";
|
||||||
import { Row, Col } from "../../../ui/index";
|
import { Row, Col } from "../../../ui/index";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { Content } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function DangerZone(props: DangerZoneProps) {
|
export function DangerZone(props: DangerZoneProps) {
|
||||||
|
|
||||||
const { dispatch, onReset, botDisconnected } = props;
|
const { dispatch, onReset, botDisconnected } = props;
|
||||||
const { danger_zone } = props.controlPanelState;
|
const { danger_zone } = props.controlPanelState;
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.dangerZone}>
|
||||||
<Header
|
<Header
|
||||||
expanded={danger_zone}
|
expanded={danger_zone}
|
||||||
title={t("Danger Zone")}
|
title={DeviceSetting.dangerZone}
|
||||||
name={"danger_zone"}
|
panel={"danger_zone"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!danger_zone}>
|
<Collapse isOpen={!!danger_zone}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={4}>
|
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
||||||
<label>
|
<Col xs={4}>
|
||||||
{t("Reset hardware parameter defaults")}
|
<label>
|
||||||
</label>
|
{t(DeviceSetting.resetHardwareParams)}
|
||||||
</Col>
|
</label>
|
||||||
<Col xs={6}>
|
</Col>
|
||||||
<p>
|
<Col xs={6}>
|
||||||
{t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)}
|
<p>
|
||||||
</p>
|
{t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</Col>
|
||||||
<button
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
className="fb-button red"
|
<button
|
||||||
disabled={botDisconnected}
|
className="fb-button red"
|
||||||
onClick={onReset}>
|
disabled={botDisconnected}
|
||||||
{t("RESET")}
|
onClick={onReset}>
|
||||||
</button>
|
{t("RESET")}
|
||||||
</Col>
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>
|
</Row>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||||
import { EncodersProps } from "../interfaces";
|
import { EncodersProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { t } from "../../../i18next_wrapper";
|
|
||||||
import { isExpressBoard } from "../firmware_hardware_support";
|
import { isExpressBoard } from "../firmware_hardware_support";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function Encoders(props: EncodersProps) {
|
export function Encoders(props: EncodersProps) {
|
||||||
|
|
||||||
|
@ -20,19 +20,20 @@ export function Encoders(props: EncodersProps) {
|
||||||
};
|
};
|
||||||
const isExpress = isExpressBoard(firmwareHardware);
|
const isExpress = isExpressBoard(firmwareHardware);
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.encoders}>
|
||||||
<Header
|
<Header
|
||||||
expanded={encoders}
|
expanded={encoders}
|
||||||
title={isExpress
|
title={isExpress
|
||||||
? t("Stall Detection")
|
? DeviceSetting.stallDetection
|
||||||
: t("Encoders")}
|
: DeviceSetting.encoders}
|
||||||
name={"encoders"}
|
panel={"encoders"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!encoders}>
|
<Collapse isOpen={!!encoders}>
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={isExpress
|
label={isExpress
|
||||||
? t("Enable Stall Detection")
|
? DeviceSetting.enableStallDetection
|
||||||
: t("Enable Encoders")}
|
: DeviceSetting.enableEncoders}
|
||||||
tooltip={isExpress
|
tooltip={isExpress
|
||||||
? ToolTips.ENABLE_STALL_DETECTION
|
? ToolTips.ENABLE_STALL_DETECTION
|
||||||
: ToolTips.ENABLE_ENCODERS}
|
: ToolTips.ENABLE_ENCODERS}
|
||||||
|
@ -43,7 +44,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
{isExpress &&
|
{isExpress &&
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Stall Sensitivity")}
|
label={DeviceSetting.stallSensitivity}
|
||||||
tooltip={ToolTips.STALL_SENSITIVITY}
|
tooltip={ToolTips.STALL_SENSITIVITY}
|
||||||
x={"movement_stall_sensitivity_x"}
|
x={"movement_stall_sensitivity_x"}
|
||||||
y={"movement_stall_sensitivity_y"}
|
y={"movement_stall_sensitivity_y"}
|
||||||
|
@ -53,7 +54,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
{!isExpress &&
|
{!isExpress &&
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Use Encoders for Positioning")}
|
label={DeviceSetting.useEncodersForPositioning}
|
||||||
tooltip={ToolTips.ENCODER_POSITIONING}
|
tooltip={ToolTips.ENCODER_POSITIONING}
|
||||||
x={"encoder_use_for_pos_x"}
|
x={"encoder_use_for_pos_x"}
|
||||||
y={"encoder_use_for_pos_y"}
|
y={"encoder_use_for_pos_y"}
|
||||||
|
@ -63,7 +64,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
{!isExpress &&
|
{!isExpress &&
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Invert Encoders")}
|
label={DeviceSetting.invertEncoders}
|
||||||
tooltip={ToolTips.INVERT_ENCODERS}
|
tooltip={ToolTips.INVERT_ENCODERS}
|
||||||
x={"encoder_invert_x"}
|
x={"encoder_invert_x"}
|
||||||
y={"encoder_invert_y"}
|
y={"encoder_invert_y"}
|
||||||
|
@ -72,7 +73,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Max Missed Steps")}
|
label={DeviceSetting.maxMissedSteps}
|
||||||
tooltip={isExpress
|
tooltip={isExpress
|
||||||
? ToolTips.MAX_MISSED_STEPS_STALL_DETECTION
|
? ToolTips.MAX_MISSED_STEPS_STALL_DETECTION
|
||||||
: ToolTips.MAX_MISSED_STEPS_ENCODERS}
|
: ToolTips.MAX_MISSED_STEPS_ENCODERS}
|
||||||
|
@ -83,7 +84,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Missed Step Decay")}
|
label={DeviceSetting.missedStepDecay}
|
||||||
tooltip={ToolTips.MISSED_STEP_DECAY}
|
tooltip={ToolTips.MISSED_STEP_DECAY}
|
||||||
x={"encoder_missed_steps_decay_x"}
|
x={"encoder_missed_steps_decay_x"}
|
||||||
y={"encoder_missed_steps_decay_y"}
|
y={"encoder_missed_steps_decay_y"}
|
||||||
|
@ -93,7 +94,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
{!isExpress &&
|
{!isExpress &&
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Encoder Scaling")}
|
label={DeviceSetting.encoderScaling}
|
||||||
tooltip={ToolTips.ENCODER_SCALING}
|
tooltip={ToolTips.ENCODER_SCALING}
|
||||||
x={"encoder_scaling_x"}
|
x={"encoder_scaling_x"}
|
||||||
y={"encoder_scaling_y"}
|
y={"encoder_scaling_y"}
|
||||||
|
@ -106,5 +107,5 @@ export function Encoders(props: EncodersProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />}
|
dispatch={dispatch} />}
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { EndStopsProps } from "../interfaces";
|
import { EndStopsProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function EndStops(props: EndStopsProps) {
|
export function EndStops(props: EndStopsProps) {
|
||||||
|
|
||||||
const { endstops } = props.controlPanelState;
|
const { endstops } = props.controlPanelState;
|
||||||
const { dispatch, sourceFwConfig } = props;
|
const { dispatch, sourceFwConfig } = props;
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.endstops}>
|
||||||
<Header
|
<Header
|
||||||
expanded={endstops}
|
expanded={endstops}
|
||||||
title={"Endstops"}
|
title={DeviceSetting.endstops}
|
||||||
name={"endstops"}
|
panel={"endstops"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!endstops}>
|
<Collapse isOpen={!!endstops}>
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Enable Endstops")}
|
label={DeviceSetting.enableEndstops}
|
||||||
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
||||||
x={"movement_enable_endpoints_x"}
|
x={"movement_enable_endpoints_x"}
|
||||||
y={"movement_enable_endpoints_y"}
|
y={"movement_enable_endpoints_y"}
|
||||||
|
@ -27,7 +28,7 @@ export function EndStops(props: EndStopsProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Swap Endstops")}
|
label={DeviceSetting.swapEndstops}
|
||||||
tooltip={ToolTips.SWAP_ENDPOINTS}
|
tooltip={ToolTips.SWAP_ENDPOINTS}
|
||||||
x={"movement_invert_endpoints_x"}
|
x={"movement_invert_endpoints_x"}
|
||||||
y={"movement_invert_endpoints_y"}
|
y={"movement_invert_endpoints_y"}
|
||||||
|
@ -40,7 +41,7 @@ export function EndStops(props: EndStopsProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Invert Endstops")}
|
label={DeviceSetting.invertEndstops}
|
||||||
tooltip={ToolTips.INVERT_ENDPOINTS}
|
tooltip={ToolTips.INVERT_ENDPOINTS}
|
||||||
x={"movement_invert_2_endpoints_x"}
|
x={"movement_invert_2_endpoints_x"}
|
||||||
y={"movement_invert_2_endpoints_y"}
|
y={"movement_invert_2_endpoints_y"}
|
||||||
|
@ -53,5 +54,5 @@ export function EndStops(props: EndStopsProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { ErrorHandlingProps } from "../interfaces";
|
import { ErrorHandlingProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { t } from "../../../i18next_wrapper";
|
|
||||||
import { McuInputBox } from "../mcu_input_box";
|
import { McuInputBox } from "../mcu_input_box";
|
||||||
import { settingToggle } from "../../actions";
|
import { settingToggle } from "../../actions";
|
||||||
import { SingleSettingRow } from "./single_setting_row";
|
import { SingleSettingRow } from "./single_setting_row";
|
||||||
import { ToggleButton } from "../../../controls/toggle_button";
|
import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function ErrorHandling(props: ErrorHandlingProps) {
|
export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
|
|
||||||
|
@ -16,15 +16,16 @@ export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
const { dispatch, sourceFwConfig } = props;
|
const { dispatch, sourceFwConfig } = props;
|
||||||
const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err");
|
const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err");
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.errorHandling}>
|
||||||
<Header
|
<Header
|
||||||
expanded={error_handling}
|
expanded={error_handling}
|
||||||
title={"Error Handling"}
|
title={DeviceSetting.errorHandling}
|
||||||
name={"error_handling"}
|
panel={"error_handling"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!error_handling}>
|
<Collapse isOpen={!!error_handling}>
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Timeout after (seconds)")}
|
label={DeviceSetting.timeoutAfter}
|
||||||
tooltip={ToolTips.TIMEOUT_AFTER}
|
tooltip={ToolTips.TIMEOUT_AFTER}
|
||||||
x={"movement_timeout_x"}
|
x={"movement_timeout_x"}
|
||||||
y={"movement_timeout_y"}
|
y={"movement_timeout_y"}
|
||||||
|
@ -32,7 +33,7 @@ export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<SingleSettingRow settingType="input"
|
<SingleSettingRow settingType="input"
|
||||||
label={t("Max Retries")}
|
label={DeviceSetting.maxRetries}
|
||||||
tooltip={ToolTips.MAX_MOVEMENT_RETRIES}>
|
tooltip={ToolTips.MAX_MOVEMENT_RETRIES}>
|
||||||
<McuInputBox
|
<McuInputBox
|
||||||
setting="param_mov_nr_retry"
|
setting="param_mov_nr_retry"
|
||||||
|
@ -40,7 +41,7 @@ export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
</SingleSettingRow>
|
</SingleSettingRow>
|
||||||
<SingleSettingRow settingType="button"
|
<SingleSettingRow settingType="button"
|
||||||
label={t("E-Stop on Movement Error")}
|
label={DeviceSetting.estopOnMovementError}
|
||||||
tooltip={ToolTips.E_STOP_ON_MOV_ERR}>
|
tooltip={ToolTips.E_STOP_ON_MOV_ERR}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
toggleValue={eStopOnMoveError.value}
|
toggleValue={eStopOnMoveError.value}
|
||||||
|
@ -49,5 +50,5 @@ export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} />
|
settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} />
|
||||||
</SingleSettingRow>
|
</SingleSettingRow>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,20 @@ import * as React from "react";
|
||||||
import { ControlPanelState } from "../../interfaces";
|
import { ControlPanelState } from "../../interfaces";
|
||||||
import { toggleControlPanel } from "../../actions";
|
import { toggleControlPanel } from "../../actions";
|
||||||
import { ExpandableHeader } from "../../../ui/expandable_header";
|
import { ExpandableHeader } from "../../../ui/expandable_header";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
name: keyof ControlPanelState;
|
panel: keyof ControlPanelState;
|
||||||
title: string;
|
title: DeviceSetting;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = (props: Props) => {
|
export const Header = (props: Props) => {
|
||||||
const { dispatch, name, title, expanded } = props;
|
const { dispatch, panel, title, expanded } = props;
|
||||||
return <ExpandableHeader
|
return <ExpandableHeader
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
title={title}
|
title={t(title)}
|
||||||
onClick={() => dispatch(toggleControlPanel(name))} />;
|
onClick={() => dispatch(toggleControlPanel(panel))} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||||
import { CalibrationRow } from "./calibration_row";
|
import { CalibrationRow } from "./calibration_row";
|
||||||
import { disabledAxisMap } from "../axis_tracking_status";
|
import { disabledAxisMap } from "../axis_tracking_status";
|
||||||
|
@ -13,6 +13,7 @@ import { isExpressBoard } from "../firmware_hardware_support";
|
||||||
import { getDevice } from "../../../device";
|
import { getDevice } from "../../../device";
|
||||||
import { commandErr } from "../../actions";
|
import { commandErr } from "../../actions";
|
||||||
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
|
|
||||||
|
@ -31,16 +32,17 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
|
|
||||||
const scale = calculateScale(sourceFwConfig);
|
const scale = calculateScale(sourceFwConfig);
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.homingAndCalibration}>
|
||||||
<Header
|
<Header
|
||||||
title={t("Homing and Calibration")}
|
title={DeviceSetting.homingAndCalibration}
|
||||||
name={"homing_and_calibration"}
|
panel={"homing_and_calibration"}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
expanded={homing_and_calibration} />
|
expanded={homing_and_calibration} />
|
||||||
<Collapse isOpen={!!homing_and_calibration}>
|
<Collapse isOpen={!!homing_and_calibration}>
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"find_home"}
|
type={"find_home"}
|
||||||
title={t("HOMING")}
|
title={DeviceSetting.homing}
|
||||||
axisTitle={t("FIND HOME")}
|
axisTitle={t("FIND HOME")}
|
||||||
toolTip={isExpressBoard(firmwareHardware)
|
toolTip={isExpressBoard(firmwareHardware)
|
||||||
? ToolTips.HOMING_STALL_DETECTION
|
? ToolTips.HOMING_STALL_DETECTION
|
||||||
|
@ -52,7 +54,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
botDisconnected={botDisconnected} />
|
botDisconnected={botDisconnected} />
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"calibrate"}
|
type={"calibrate"}
|
||||||
title={t("CALIBRATION")}
|
title={DeviceSetting.calibration}
|
||||||
axisTitle={t("CALIBRATE")}
|
axisTitle={t("CALIBRATE")}
|
||||||
toolTip={isExpressBoard(firmwareHardware)
|
toolTip={isExpressBoard(firmwareHardware)
|
||||||
? ToolTips.CALIBRATION_STALL_DETECTION
|
? ToolTips.CALIBRATION_STALL_DETECTION
|
||||||
|
@ -63,7 +65,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
botDisconnected={botDisconnected} />
|
botDisconnected={botDisconnected} />
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"zero"}
|
type={"zero"}
|
||||||
title={t("SET ZERO POSITION")}
|
title={DeviceSetting.setZeroPosition}
|
||||||
axisTitle={t("ZERO")}
|
axisTitle={t("ZERO")}
|
||||||
toolTip={ToolTips.SET_ZERO_POSITION}
|
toolTip={ToolTips.SET_ZERO_POSITION}
|
||||||
action={axis => getDevice().setZero(axis)
|
action={axis => getDevice().setZero(axis)
|
||||||
|
@ -71,7 +73,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
hardware={hardware}
|
hardware={hardware}
|
||||||
botDisconnected={botDisconnected} />
|
botDisconnected={botDisconnected} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Find Home on Boot")}
|
label={DeviceSetting.findHomeOnBoot}
|
||||||
tooltip={isExpressBoard(firmwareHardware)
|
tooltip={isExpressBoard(firmwareHardware)
|
||||||
? ToolTips.FIND_HOME_ON_BOOT_STALL_DETECTION
|
? ToolTips.FIND_HOME_ON_BOOT_STALL_DETECTION
|
||||||
: ToolTips.FIND_HOME_ON_BOOT_ENCODERS}
|
: ToolTips.FIND_HOME_ON_BOOT_ENCODERS}
|
||||||
|
@ -83,7 +85,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
caution={true} />
|
caution={true} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Stop at Home")}
|
label={DeviceSetting.stopAtHome}
|
||||||
tooltip={ToolTips.STOP_AT_HOME}
|
tooltip={ToolTips.STOP_AT_HOME}
|
||||||
x={"movement_stop_at_home_x"}
|
x={"movement_stop_at_home_x"}
|
||||||
y={"movement_stop_at_home_y"}
|
y={"movement_stop_at_home_y"}
|
||||||
|
@ -91,7 +93,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Stop at Max")}
|
label={DeviceSetting.stopAtMax}
|
||||||
tooltip={ToolTips.STOP_AT_MAX}
|
tooltip={ToolTips.STOP_AT_MAX}
|
||||||
x={"movement_stop_at_max_x"}
|
x={"movement_stop_at_max_x"}
|
||||||
y={"movement_stop_at_max_y"}
|
y={"movement_stop_at_max_y"}
|
||||||
|
@ -99,7 +101,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Negative Coordinates Only")}
|
label={DeviceSetting.negativeCoordinatesOnly}
|
||||||
tooltip={ToolTips.NEGATIVE_COORDINATES_ONLY}
|
tooltip={ToolTips.NEGATIVE_COORDINATES_ONLY}
|
||||||
x={"movement_home_up_x"}
|
x={"movement_home_up_x"}
|
||||||
y={"movement_home_up_y"}
|
y={"movement_home_up_y"}
|
||||||
|
@ -107,7 +109,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Axis Length (mm)")}
|
label={DeviceSetting.axisLength}
|
||||||
tooltip={ToolTips.LENGTH}
|
tooltip={ToolTips.LENGTH}
|
||||||
x={"movement_axis_nr_steps_x"}
|
x={"movement_axis_nr_steps_x"}
|
||||||
y={"movement_axis_nr_steps_y"}
|
y={"movement_axis_nr_steps_y"}
|
||||||
|
@ -124,5 +126,5 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
intSize={"long"} />
|
intSize={"long"} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
import { BooleanMCUInputGroup } from "../boolean_mcu_input_group";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { ToggleButton } from "../../../controls/toggle_button";
|
import { ToggleButton } from "../../../controls/toggle_button";
|
||||||
import { settingToggle } from "../../actions";
|
import { settingToggle } from "../../actions";
|
||||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||||
import { MotorsProps } from "../interfaces";
|
import { MotorsProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { t } from "../../../i18next_wrapper";
|
|
||||||
import { Xyz, McuParamName } from "farmbot";
|
import { Xyz, McuParamName } from "farmbot";
|
||||||
import { SourceFwConfig } from "../../interfaces";
|
import { SourceFwConfig } from "../../interfaces";
|
||||||
import { calcMicrostepsPerMm } from "../../../controls/move/direction_axes_props";
|
import { calcMicrostepsPerMm } from "../../../controls/move/direction_axes_props";
|
||||||
import { isTMCBoard } from "../firmware_hardware_support";
|
import { isTMCBoard } from "../firmware_hardware_support";
|
||||||
import { SingleSettingRow } from "./single_setting_row";
|
import { SingleSettingRow } from "./single_setting_row";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export const calculateScale =
|
export const calculateScale =
|
||||||
(sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => {
|
(sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => {
|
||||||
|
@ -35,15 +35,16 @@ export function Motors(props: MotorsProps) {
|
||||||
const invert2ndXMotor = sourceFwConfig("movement_secondary_motor_invert_x");
|
const invert2ndXMotor = sourceFwConfig("movement_secondary_motor_invert_x");
|
||||||
const scale = calculateScale(sourceFwConfig);
|
const scale = calculateScale(sourceFwConfig);
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.motors}>
|
||||||
<Header
|
<Header
|
||||||
expanded={controlPanelState.motors}
|
expanded={controlPanelState.motors}
|
||||||
title={t("Motors")}
|
title={DeviceSetting.motors}
|
||||||
name={"motors"}
|
panel={"motors"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!controlPanelState.motors}>
|
<Collapse isOpen={!!controlPanelState.motors}>
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Max Speed (mm/s)")}
|
label={DeviceSetting.maxSpeed}
|
||||||
tooltip={ToolTips.MAX_SPEED}
|
tooltip={ToolTips.MAX_SPEED}
|
||||||
x={"movement_max_spd_x"}
|
x={"movement_max_spd_x"}
|
||||||
y={"movement_max_spd_y"}
|
y={"movement_max_spd_y"}
|
||||||
|
@ -54,7 +55,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Homing Speed (mm/s)")}
|
label={DeviceSetting.homingSpeed}
|
||||||
tooltip={ToolTips.HOME_SPEED}
|
tooltip={ToolTips.HOME_SPEED}
|
||||||
x={"movement_home_spd_x"}
|
x={"movement_home_spd_x"}
|
||||||
y={"movement_home_spd_y"}
|
y={"movement_home_spd_y"}
|
||||||
|
@ -65,7 +66,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Minimum Speed (mm/s)")}
|
label={DeviceSetting.minimumSpeed}
|
||||||
tooltip={ToolTips.MIN_SPEED}
|
tooltip={ToolTips.MIN_SPEED}
|
||||||
x={"movement_min_spd_x"}
|
x={"movement_min_spd_x"}
|
||||||
y={"movement_min_spd_y"}
|
y={"movement_min_spd_y"}
|
||||||
|
@ -76,7 +77,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Accelerate for (mm)")}
|
label={DeviceSetting.accelerateFor}
|
||||||
tooltip={ToolTips.ACCELERATE_FOR}
|
tooltip={ToolTips.ACCELERATE_FOR}
|
||||||
x={"movement_steps_acc_dec_x"}
|
x={"movement_steps_acc_dec_x"}
|
||||||
y={"movement_steps_acc_dec_y"}
|
y={"movement_steps_acc_dec_y"}
|
||||||
|
@ -87,7 +88,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Steps per MM")}
|
label={DeviceSetting.stepsPerMm}
|
||||||
tooltip={ToolTips.STEPS_PER_MM}
|
tooltip={ToolTips.STEPS_PER_MM}
|
||||||
x={"movement_step_per_mm_x"}
|
x={"movement_step_per_mm_x"}
|
||||||
y={"movement_step_per_mm_y"}
|
y={"movement_step_per_mm_y"}
|
||||||
|
@ -99,7 +100,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={props.sourceFwConfig}
|
sourceFwConfig={props.sourceFwConfig}
|
||||||
dispatch={props.dispatch} />
|
dispatch={props.dispatch} />
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Microsteps per step")}
|
label={DeviceSetting.microstepsPerStep}
|
||||||
tooltip={ToolTips.MICROSTEPS_PER_STEP}
|
tooltip={ToolTips.MICROSTEPS_PER_STEP}
|
||||||
x={"movement_microsteps_x"}
|
x={"movement_microsteps_x"}
|
||||||
y={"movement_microsteps_y"}
|
y={"movement_microsteps_y"}
|
||||||
|
@ -107,7 +108,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={props.sourceFwConfig}
|
sourceFwConfig={props.sourceFwConfig}
|
||||||
dispatch={props.dispatch} />
|
dispatch={props.dispatch} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Always Power Motors")}
|
label={DeviceSetting.alwaysPowerMotors}
|
||||||
tooltip={ToolTips.ALWAYS_POWER_MOTORS}
|
tooltip={ToolTips.ALWAYS_POWER_MOTORS}
|
||||||
x={"movement_keep_active_x"}
|
x={"movement_keep_active_x"}
|
||||||
y={"movement_keep_active_y"}
|
y={"movement_keep_active_y"}
|
||||||
|
@ -115,7 +116,7 @@ export function Motors(props: MotorsProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
name={t("Invert Motors")}
|
label={DeviceSetting.invertMotors}
|
||||||
tooltip={ToolTips.INVERT_MOTORS}
|
tooltip={ToolTips.INVERT_MOTORS}
|
||||||
x={"movement_invert_motor_x"}
|
x={"movement_invert_motor_x"}
|
||||||
y={"movement_invert_motor_y"}
|
y={"movement_invert_motor_y"}
|
||||||
|
@ -124,7 +125,7 @@ export function Motors(props: MotorsProps) {
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
{isTMCBoard(firmwareHardware) &&
|
{isTMCBoard(firmwareHardware) &&
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
name={t("Motor Current")}
|
label={DeviceSetting.motorCurrent}
|
||||||
tooltip={ToolTips.MOTOR_CURRENT}
|
tooltip={ToolTips.MOTOR_CURRENT}
|
||||||
x={"movement_motor_current_x"}
|
x={"movement_motor_current_x"}
|
||||||
y={"movement_motor_current_y"}
|
y={"movement_motor_current_y"}
|
||||||
|
@ -132,7 +133,7 @@ export function Motors(props: MotorsProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFwConfig={sourceFwConfig} />}
|
sourceFwConfig={sourceFwConfig} />}
|
||||||
<SingleSettingRow settingType="button"
|
<SingleSettingRow settingType="button"
|
||||||
label={t("Enable 2nd X Motor")}
|
label={DeviceSetting.enable2ndXMotor}
|
||||||
tooltip={ToolTips.ENABLE_X2_MOTOR}>
|
tooltip={ToolTips.ENABLE_X2_MOTOR}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
toggleValue={enable2ndXMotor.value}
|
toggleValue={enable2ndXMotor.value}
|
||||||
|
@ -141,7 +142,7 @@ export function Motors(props: MotorsProps) {
|
||||||
settingToggle("movement_secondary_motor_x", sourceFwConfig))} />
|
settingToggle("movement_secondary_motor_x", sourceFwConfig))} />
|
||||||
</SingleSettingRow>
|
</SingleSettingRow>
|
||||||
<SingleSettingRow settingType="button"
|
<SingleSettingRow settingType="button"
|
||||||
label={t("Invert 2nd X Motor")}
|
label={DeviceSetting.invert2ndXMotor}
|
||||||
tooltip={ToolTips.INVERT_MOTORS}>
|
tooltip={ToolTips.INVERT_MOTORS}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
grayscale={!enable2ndXMotor.value}
|
grayscale={!enable2ndXMotor.value}
|
||||||
|
@ -151,5 +152,5 @@ export function Motors(props: MotorsProps) {
|
||||||
settingToggle("movement_secondary_motor_invert_x", sourceFwConfig))} />
|
settingToggle("movement_secondary_motor_invert_x", sourceFwConfig))} />
|
||||||
</SingleSettingRow>
|
</SingleSettingRow>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,23 @@ import { PinBindingsProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse } from "@blueprintjs/core";
|
import { Collapse } from "@blueprintjs/core";
|
||||||
import { PinBindingsContent } from "../../pin_bindings/pin_bindings";
|
import { PinBindingsContent } from "../../pin_bindings/pin_bindings";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function PinBindings(props: PinBindingsProps) {
|
export function PinBindings(props: PinBindingsProps) {
|
||||||
|
|
||||||
const { pin_bindings } = props.controlPanelState;
|
const { pin_bindings } = props.controlPanelState;
|
||||||
const { dispatch, resources } = props;
|
const { dispatch, resources } = props;
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.pinBindings}>
|
||||||
<Header
|
<Header
|
||||||
expanded={pin_bindings}
|
expanded={pin_bindings}
|
||||||
title={"Pin Bindings"}
|
title={DeviceSetting.pinBindings}
|
||||||
name={"pin_bindings"}
|
panel={"pin_bindings"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!pin_bindings}>
|
<Collapse isOpen={!!pin_bindings}>
|
||||||
<PinBindingsContent dispatch={dispatch} resources={resources} />
|
<PinBindingsContent dispatch={dispatch} resources={resources} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,21 @@ import { PinGuardProps } from "../interfaces";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
import { Collapse, Position } from "@blueprintjs/core";
|
import { Collapse, Position } from "@blueprintjs/core";
|
||||||
import { Row, Col, Help } from "../../../ui/index";
|
import { Row, Col, Help } from "../../../ui/index";
|
||||||
import { ToolTips } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
|
||||||
export function PinGuard(props: PinGuardProps) {
|
export function PinGuard(props: PinGuardProps) {
|
||||||
|
|
||||||
const { pin_guard } = props.controlPanelState;
|
const { pin_guard } = props.controlPanelState;
|
||||||
const { dispatch, sourceFwConfig, resources } = props;
|
const { dispatch, sourceFwConfig, resources } = props;
|
||||||
|
|
||||||
return <section>
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.pinGuard}>
|
||||||
<Header
|
<Header
|
||||||
expanded={pin_guard}
|
expanded={pin_guard}
|
||||||
title={t("Pin Guard")}
|
title={DeviceSetting.pinGuard}
|
||||||
name={"pin_guard"}
|
panel={"pin_guard"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!pin_guard}>
|
<Collapse isOpen={!!pin_guard}>
|
||||||
<Row>
|
<Row>
|
||||||
|
@ -79,5 +81,5 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
resources={resources}
|
resources={resources}
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</section>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col, Help } from "../../../ui/index";
|
import { Row, Col, Help } from "../../../ui/index";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
|
||||||
|
export interface SingleSettingRowProps {
|
||||||
|
label: DeviceSetting;
|
||||||
|
tooltip: string;
|
||||||
|
children: React.ReactChild;
|
||||||
|
settingType: "button" | "input";
|
||||||
|
}
|
||||||
|
|
||||||
export const SingleSettingRow =
|
export const SingleSettingRow =
|
||||||
({ label, tooltip, settingType, children }: {
|
({ label, tooltip, settingType, children }: SingleSettingRowProps) =>
|
||||||
label: string,
|
|
||||||
tooltip: string,
|
|
||||||
children: React.ReactChild,
|
|
||||||
settingType: "button" | "input",
|
|
||||||
}) =>
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
<Highlight settingName={label}>
|
||||||
<label>{label}</label>
|
<Col xs={6} className={"widget-body-tooltips"}>
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
<label>{t(label)}</label>
|
||||||
</Col>
|
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
||||||
{settingType === "button"
|
</Col>
|
||||||
? <Col xs={2} className={"centered-button-div"}>{children}</Col>
|
{settingType === "button"
|
||||||
: <Col xs={6}>{children}</Col>}
|
? <Col xs={2} className={"centered-button-div"}>{children}</Col>
|
||||||
|
: <Col xs={6}>{children}</Col>}
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { McuParamName, McuParams, FirmwareHardware } from "farmbot/dist";
|
||||||
import { IntegerSize } from "../../util";
|
import { IntegerSize } from "../../util";
|
||||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
import { ResourceIndex } from "../../resources/interfaces";
|
import { ResourceIndex } from "../../resources/interfaces";
|
||||||
|
import { DeviceSetting } from "../../constants";
|
||||||
|
|
||||||
export interface ZeroRowProps {
|
export interface ZeroRowProps {
|
||||||
botDisconnected: boolean;
|
botDisconnected: boolean;
|
||||||
|
@ -25,7 +26,7 @@ export interface BooleanMCUInputGroupProps {
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
name: string;
|
label: DeviceSetting;
|
||||||
x: McuParamName;
|
x: McuParamName;
|
||||||
y: McuParamName;
|
y: McuParamName;
|
||||||
z: McuParamName;
|
z: McuParamName;
|
||||||
|
@ -41,7 +42,7 @@ export interface CalibrationRowProps {
|
||||||
botDisconnected: boolean;
|
botDisconnected: boolean;
|
||||||
action(axis: Axis): void;
|
action(axis: Axis): void;
|
||||||
toolTip: string;
|
toolTip: string;
|
||||||
title: string;
|
title: DeviceSetting;
|
||||||
axisTitle: string;
|
axisTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ export interface NumericMCUInputGroupProps {
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
name: string;
|
label: DeviceSetting;
|
||||||
x: McuParamName;
|
x: McuParamName;
|
||||||
xScale?: number;
|
xScale?: number;
|
||||||
y: McuParamName;
|
y: McuParamName;
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ControlPanelState } from "../interfaces";
|
||||||
|
import { toggleControlPanel } from "../actions";
|
||||||
|
import { urlFriendly } from "../../util";
|
||||||
|
import { DeviceSetting } from "../../constants";
|
||||||
|
|
||||||
|
const HOMING_PANEL = [
|
||||||
|
DeviceSetting.homingAndCalibration,
|
||||||
|
DeviceSetting.homing,
|
||||||
|
DeviceSetting.calibration,
|
||||||
|
DeviceSetting.setZeroPosition,
|
||||||
|
DeviceSetting.findHomeOnBoot,
|
||||||
|
DeviceSetting.stopAtHome,
|
||||||
|
DeviceSetting.stopAtMax,
|
||||||
|
DeviceSetting.negativeCoordinatesOnly,
|
||||||
|
DeviceSetting.axisLength,
|
||||||
|
];
|
||||||
|
const MOTORS_PANEL = [
|
||||||
|
DeviceSetting.motors,
|
||||||
|
DeviceSetting.maxSpeed,
|
||||||
|
DeviceSetting.homingSpeed,
|
||||||
|
DeviceSetting.minimumSpeed,
|
||||||
|
DeviceSetting.accelerateFor,
|
||||||
|
DeviceSetting.stepsPerMm,
|
||||||
|
DeviceSetting.microstepsPerStep,
|
||||||
|
DeviceSetting.alwaysPowerMotors,
|
||||||
|
DeviceSetting.invertMotors,
|
||||||
|
DeviceSetting.motorCurrent,
|
||||||
|
DeviceSetting.enable2ndXMotor,
|
||||||
|
DeviceSetting.invert2ndXMotor,
|
||||||
|
];
|
||||||
|
const ENCODERS_PANEL = [
|
||||||
|
DeviceSetting.encoders,
|
||||||
|
DeviceSetting.stallDetection,
|
||||||
|
DeviceSetting.enableEncoders,
|
||||||
|
DeviceSetting.enableStallDetection,
|
||||||
|
DeviceSetting.stallSensitivity,
|
||||||
|
DeviceSetting.useEncodersForPositioning,
|
||||||
|
DeviceSetting.invertEncoders,
|
||||||
|
DeviceSetting.maxMissedSteps,
|
||||||
|
DeviceSetting.missedStepDecay,
|
||||||
|
DeviceSetting.encoderScaling,
|
||||||
|
];
|
||||||
|
const ENDSTOPS_PANEL = [
|
||||||
|
DeviceSetting.endstops,
|
||||||
|
DeviceSetting.enableEndstops,
|
||||||
|
DeviceSetting.swapEndstops,
|
||||||
|
DeviceSetting.invertEndstops,
|
||||||
|
];
|
||||||
|
const ERROR_HANDLING_PANEL = [
|
||||||
|
DeviceSetting.errorHandling,
|
||||||
|
DeviceSetting.timeoutAfter,
|
||||||
|
DeviceSetting.maxRetries,
|
||||||
|
DeviceSetting.estopOnMovementError,
|
||||||
|
];
|
||||||
|
const PIN_GUARD_PANEL = [
|
||||||
|
DeviceSetting.pinGuard,
|
||||||
|
];
|
||||||
|
const DANGER_ZONE_PANEL = [
|
||||||
|
DeviceSetting.dangerZone,
|
||||||
|
DeviceSetting.resetHardwareParams,
|
||||||
|
];
|
||||||
|
const PIN_BINDINGS_PANEL = [
|
||||||
|
DeviceSetting.pinBindings,
|
||||||
|
];
|
||||||
|
const POWER_AND_RESET_PANEL = [
|
||||||
|
DeviceSetting.powerAndReset,
|
||||||
|
DeviceSetting.restartFarmbot,
|
||||||
|
DeviceSetting.shutdownFarmbot,
|
||||||
|
DeviceSetting.restartFirmware,
|
||||||
|
DeviceSetting.factoryReset,
|
||||||
|
DeviceSetting.autoFactoryReset,
|
||||||
|
DeviceSetting.connectionAttemptPeriod,
|
||||||
|
DeviceSetting.changeOwnership,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Look up parent panels for settings. */
|
||||||
|
const SETTING_PANEL_LOOKUP = {} as Record<DeviceSetting, keyof ControlPanelState>;
|
||||||
|
HOMING_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "homing_and_calibration");
|
||||||
|
MOTORS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "motors");
|
||||||
|
ENCODERS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "encoders");
|
||||||
|
ENDSTOPS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "endstops");
|
||||||
|
ERROR_HANDLING_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "error_handling");
|
||||||
|
PIN_GUARD_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "pin_guard");
|
||||||
|
DANGER_ZONE_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "danger_zone");
|
||||||
|
PIN_BINDINGS_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "pin_bindings");
|
||||||
|
POWER_AND_RESET_PANEL.map(s => SETTING_PANEL_LOOKUP[s] = "power_and_reset");
|
||||||
|
|
||||||
|
/** Look up parent panels for settings using URL-friendly names. */
|
||||||
|
const URL_FRIENDLY_LOOKUP: Record<string, keyof ControlPanelState> = {};
|
||||||
|
Object.entries(SETTING_PANEL_LOOKUP).map(([setting, panel]) =>
|
||||||
|
URL_FRIENDLY_LOOKUP[urlFriendly(setting)] = panel);
|
||||||
|
|
||||||
|
/** Look up all relevant names for the same setting. */
|
||||||
|
const ALTERNATE_NAMES =
|
||||||
|
Object.values(DeviceSetting).reduce((acc, s) => { acc[s] = [s]; return acc; },
|
||||||
|
{} as Record<DeviceSetting, DeviceSetting[]>);
|
||||||
|
ALTERNATE_NAMES[DeviceSetting.encoders].push(DeviceSetting.stallDetection);
|
||||||
|
ALTERNATE_NAMES[DeviceSetting.stallDetection].push(DeviceSetting.encoders);
|
||||||
|
|
||||||
|
/** Generate array of names for the same setting. Most only have one. */
|
||||||
|
const compareValues = (settingName: DeviceSetting) =>
|
||||||
|
(ALTERNATE_NAMES[settingName]).map(s => urlFriendly(s));
|
||||||
|
|
||||||
|
/** Retrieve a highlight search term. */
|
||||||
|
const getHighlightName = () => location.search.split("?highlight=").pop();
|
||||||
|
|
||||||
|
/** Only open panel and highlight once per app load. Exported for tests. */
|
||||||
|
export const highlight = { opened: false, highlighted: false };
|
||||||
|
|
||||||
|
/** Open a panel if a setting in that panel is highlighted. */
|
||||||
|
export const maybeOpenPanel = (panelState: ControlPanelState) =>
|
||||||
|
(dispatch: Function) => {
|
||||||
|
if (highlight.opened) { return; }
|
||||||
|
const urlFriendlySettingName = urlFriendly(getHighlightName() || "");
|
||||||
|
if (!urlFriendlySettingName) { return; }
|
||||||
|
const panel = URL_FRIENDLY_LOOKUP[urlFriendlySettingName];
|
||||||
|
const panelIsOpen = panelState[panel];
|
||||||
|
if (panelIsOpen) { return; }
|
||||||
|
dispatch(toggleControlPanel(panel));
|
||||||
|
highlight.opened = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Highlight a setting if provided as a search term. */
|
||||||
|
export const maybeHighlight = (settingName: DeviceSetting) => {
|
||||||
|
const item = getHighlightName();
|
||||||
|
if (highlight.highlighted || !item) { return ""; }
|
||||||
|
const isCurrentSetting = compareValues(settingName).includes(item);
|
||||||
|
if (!isCurrentSetting) { return ""; }
|
||||||
|
highlight.highlighted = true;
|
||||||
|
return "highlight";
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface HighlightProps {
|
||||||
|
settingName: DeviceSetting;
|
||||||
|
children: React.ReactChild
|
||||||
|
| React.ReactChild[]
|
||||||
|
| (React.ReactChild | React.ReactChild[])[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HighlightState {
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrap highlight-able settings. */
|
||||||
|
export class Highlight extends React.Component<HighlightProps, HighlightState> {
|
||||||
|
state: HighlightState = { className: maybeHighlight(this.props.settingName) };
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
if (this.state.className == "highlight") {
|
||||||
|
/** Slowly fades highlight. */
|
||||||
|
this.setState({ className: "unhighlight" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className={`${this.props.className} ${this.state.className}`}>
|
||||||
|
{this.props.children}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,48 +3,52 @@ import { McuInputBox } from "./mcu_input_box";
|
||||||
import { NumericMCUInputGroupProps } from "./interfaces";
|
import { NumericMCUInputGroupProps } from "./interfaces";
|
||||||
import { Row, Col, Help } from "../../ui/index";
|
import { Row, Col, Help } from "../../ui/index";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
import { t } from "../../i18next_wrapper";
|
||||||
|
|
||||||
export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) {
|
export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sourceFwConfig, dispatch, tooltip, name, x, y, z, intSize, gray, float,
|
sourceFwConfig, dispatch, tooltip, label, x, y, z, intSize, gray, float,
|
||||||
} = props;
|
} = props;
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
<Highlight settingName={label}>
|
||||||
<label>
|
<Col xs={6} className={"widget-body-tooltips"}>
|
||||||
{name}
|
<label>
|
||||||
</label>
|
{t(label)}
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
</label>
|
||||||
</Col>
|
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
||||||
<Col xs={2}>
|
</Col>
|
||||||
<McuInputBox
|
<Col xs={2}>
|
||||||
setting={x}
|
<McuInputBox
|
||||||
sourceFwConfig={sourceFwConfig}
|
setting={x}
|
||||||
dispatch={dispatch}
|
sourceFwConfig={sourceFwConfig}
|
||||||
intSize={intSize}
|
dispatch={dispatch}
|
||||||
float={float}
|
intSize={intSize}
|
||||||
scale={props.xScale}
|
float={float}
|
||||||
gray={gray?.x} />
|
scale={props.xScale}
|
||||||
</Col>
|
gray={gray?.x} />
|
||||||
<Col xs={2}>
|
</Col>
|
||||||
<McuInputBox
|
<Col xs={2}>
|
||||||
setting={y}
|
<McuInputBox
|
||||||
sourceFwConfig={sourceFwConfig}
|
setting={y}
|
||||||
dispatch={dispatch}
|
sourceFwConfig={sourceFwConfig}
|
||||||
intSize={intSize}
|
dispatch={dispatch}
|
||||||
float={float}
|
intSize={intSize}
|
||||||
scale={props.yScale}
|
float={float}
|
||||||
gray={gray?.y} />
|
scale={props.yScale}
|
||||||
</Col>
|
gray={gray?.y} />
|
||||||
<Col xs={2}>
|
</Col>
|
||||||
<McuInputBox
|
<Col xs={2}>
|
||||||
setting={z}
|
<McuInputBox
|
||||||
sourceFwConfig={sourceFwConfig}
|
setting={z}
|
||||||
dispatch={dispatch}
|
sourceFwConfig={sourceFwConfig}
|
||||||
intSize={intSize}
|
dispatch={dispatch}
|
||||||
float={float}
|
intSize={intSize}
|
||||||
scale={props.zScale}
|
float={float}
|
||||||
gray={gray?.z} />
|
scale={props.zScale}
|
||||||
</Col>
|
gray={gray?.z} />
|
||||||
|
</Col>
|
||||||
|
</Highlight>
|
||||||
</Row>;
|
</Row>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,6 +201,7 @@ export interface PeripheralsProps {
|
||||||
peripherals: TaggedPeripheral[];
|
peripherals: TaggedPeripheral[];
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
disabled: boolean | undefined;
|
disabled: boolean | undefined;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SensorsProps {
|
export interface SensorsProps {
|
||||||
|
@ -208,6 +209,7 @@ export interface SensorsProps {
|
||||||
sensors: TaggedSensor[];
|
sensors: TaggedSensor[];
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
disabled: boolean | undefined;
|
disabled: boolean | undefined;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FarmwareProps {
|
export interface FarmwareProps {
|
||||||
|
@ -248,8 +250,8 @@ export interface ControlPanelState {
|
||||||
encoders: boolean;
|
encoders: boolean;
|
||||||
endstops: boolean;
|
endstops: boolean;
|
||||||
error_handling: boolean;
|
error_handling: boolean;
|
||||||
pin_bindings: boolean;
|
|
||||||
danger_zone: boolean;
|
|
||||||
power_and_reset: boolean;
|
|
||||||
pin_guard: boolean;
|
pin_guard: boolean;
|
||||||
|
danger_zone: boolean;
|
||||||
|
pin_bindings: boolean;
|
||||||
|
power_and_reset: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,17 +89,17 @@ export const piSpi1Pins = [16, 17, 18, 19, 20, 21];
|
||||||
/** Pin numbers used for special purposes by the RPi. (internal pullup, etc.) */
|
/** Pin numbers used for special purposes by the RPi. (internal pullup, etc.) */
|
||||||
export const reservedPiGPIO = piI2c0Pins;
|
export const reservedPiGPIO = piI2c0Pins;
|
||||||
|
|
||||||
const LabeledGpioPins: { [x: number]: string } = {
|
const GPIO_PIN_LABELS = (): { [x: number]: string } => ({
|
||||||
[ButtonPin.estop]: "Button 1: E-STOP",
|
[ButtonPin.estop]: t("Button {{ num }}: E-STOP", { num: 1 }),
|
||||||
[ButtonPin.unlock]: "Button 2: UNLOCK",
|
[ButtonPin.unlock]: t("Button {{ num }}: UNLOCK", { num: 2 }),
|
||||||
[ButtonPin.btn3]: "Button 3",
|
[ButtonPin.btn3]: t("Button {{ num }})", { num: 3 }),
|
||||||
[ButtonPin.btn4]: "Button 4",
|
[ButtonPin.btn4]: t("Button {{ num }}", { num: 4 }),
|
||||||
[ButtonPin.btn5]: "Button 5",
|
[ButtonPin.btn5]: t("Button {{ num }}", { num: 5 }),
|
||||||
};
|
});
|
||||||
|
|
||||||
export const generatePinLabel = (pin: number) =>
|
export const generatePinLabel = (pin: number) =>
|
||||||
LabeledGpioPins[pin]
|
GPIO_PIN_LABELS()[pin]
|
||||||
? `${LabeledGpioPins[pin]} (Pi ${pin})`
|
? `${t(GPIO_PIN_LABELS()[pin])} (Pi ${pin})`
|
||||||
: `Pi GPIO ${pin}`;
|
: `Pi GPIO ${pin}`;
|
||||||
|
|
||||||
/** Raspberry Pi GPIO pin numbers. */
|
/** Raspberry Pi GPIO pin numbers. */
|
||||||
|
|
|
@ -20,10 +20,11 @@ export namespace ExternalUrl {
|
||||||
const GITHUB_API = "https://api.github.com";
|
const GITHUB_API = "https://api.github.com";
|
||||||
const OPENFARM = "https://openfarm.cc";
|
const OPENFARM = "https://openfarm.cc";
|
||||||
const SOFTWARE_DOCS = "https://software.farm.bot";
|
const SOFTWARE_DOCS = "https://software.farm.bot";
|
||||||
const FORUM = "http://forum.farmbot.org";
|
const FORUM = "https://forum.farmbot.org";
|
||||||
const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files";
|
const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files";
|
||||||
|
|
||||||
const FBOS_RAW = `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}`;
|
const FBOS_RAW =
|
||||||
|
`${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/staging`;
|
||||||
export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`;
|
export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`;
|
||||||
export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`;
|
export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`;
|
||||||
|
|
||||||
|
@ -31,8 +32,7 @@ export namespace ExternalUrl {
|
||||||
`${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`;
|
`${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`;
|
||||||
|
|
||||||
export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`;
|
export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`;
|
||||||
export const webAppRepo =
|
export const webAppRepo = `${gitHubFarmBot}/${FarmBotRepo.FarmBotWebApp}`;
|
||||||
`${GITHUB}/${Org.FarmBot}/${FarmBotRepo.FarmBotWebApp}`;
|
|
||||||
|
|
||||||
export const softwareDocs = `${SOFTWARE_DOCS}/docs`;
|
export const softwareDocs = `${SOFTWARE_DOCS}/docs`;
|
||||||
export const softwareForum = `${FORUM}/c/software`;
|
export const softwareForum = `${FORUM}/c/software`;
|
||||||
|
@ -43,7 +43,7 @@ export namespace ExternalUrl {
|
||||||
export const newCrop = `${OPENFARM}/en/crops/new`;
|
export const newCrop = `${OPENFARM}/en/crops/new`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Videos {
|
export namespace Video {
|
||||||
export const desktop =
|
export const desktop =
|
||||||
`${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`;
|
`${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`;
|
||||||
export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`;
|
export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`;
|
||||||
|
|
|
@ -386,6 +386,7 @@ export class GardenMap extends
|
||||||
visible={!!this.props.showPlants}
|
visible={!!this.props.showPlants}
|
||||||
plants={this.props.plants}
|
plants={this.props.plants}
|
||||||
currentPlant={this.getPlant()}
|
currentPlant={this.getPlant()}
|
||||||
|
hoveredPlant={this.props.hoveredPlant}
|
||||||
dragging={!!this.state.isDragging}
|
dragging={!!this.state.isDragging}
|
||||||
editing={this.isEditing}
|
editing={this.isEditing}
|
||||||
boxSelected={this.props.designer.selectedPlants}
|
boxSelected={this.props.designer.selectedPlants}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export type TaggedPlant = TaggedPlantPointer | TaggedPlantTemplate;
|
||||||
export interface PlantLayerProps {
|
export interface PlantLayerProps {
|
||||||
plants: TaggedPlant[];
|
plants: TaggedPlant[];
|
||||||
currentPlant: TaggedPlant | undefined;
|
currentPlant: TaggedPlant | undefined;
|
||||||
|
hoveredPlant: TaggedPlant | undefined;
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
@ -57,12 +58,14 @@ export interface GardenPlantProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
plant: Readonly<TaggedPlant>;
|
plant: Readonly<TaggedPlant>;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
current: boolean;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
zoomLvl: number;
|
zoomLvl: number;
|
||||||
activeDragXY: BotPosition | undefined;
|
activeDragXY: BotPosition | undefined;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
animate: boolean;
|
animate: boolean;
|
||||||
|
hovered: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GardenPlantState {
|
export interface GardenPlantState {
|
||||||
|
|
|
@ -15,17 +15,19 @@ describe("<BotFigure/>", () => {
|
||||||
plantAreaOffset: { x: 100, y: 100 },
|
plantAreaOffset: { x: 100, y: 100 },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const EXPECTED_MOTORS_OPACITY = 0.5;
|
||||||
|
|
||||||
it.each<[
|
it.each<[
|
||||||
string, BotOriginQuadrant, Record<"x" | "y", number>, boolean, number
|
string, BotOriginQuadrant, Record<"x" | "y", number>, boolean, number
|
||||||
]>([
|
]>([
|
||||||
["motors", 1, { x: 3000, y: 0 }, false, 0.75],
|
["motors", 1, { x: 3000, y: 0 }, false, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 2, { x: 0, y: 0 }, false, 0.75],
|
["motors", 2, { x: 0, y: 0 }, false, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 3, { x: 0, y: 1500 }, false, 0.75],
|
["motors", 3, { x: 0, y: 1500 }, false, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 4, { x: 3000, y: 1500 }, false, 0.75],
|
["motors", 4, { x: 3000, y: 1500 }, false, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 1, { x: 0, y: 1500 }, true, 0.75],
|
["motors", 1, { x: 0, y: 1500 }, true, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 2, { x: 0, y: 0 }, true, 0.75],
|
["motors", 2, { x: 0, y: 0 }, true, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 3, { x: 3000, y: 0 }, true, 0.75],
|
["motors", 3, { x: 3000, y: 0 }, true, EXPECTED_MOTORS_OPACITY],
|
||||||
["motors", 4, { x: 3000, y: 1500 }, true, 0.75],
|
["motors", 4, { x: 3000, y: 1500 }, true, EXPECTED_MOTORS_OPACITY],
|
||||||
["encoders", 2, { x: 0, y: 0 }, false, 0.25],
|
["encoders", 2, { x: 0, y: 0 }, false, 0.25],
|
||||||
])("shows %s in correct location for quadrant %i",
|
])("shows %s in correct location for quadrant %i",
|
||||||
(name, quadrant, expected, xySwap, opacity) => {
|
(name, quadrant, expected, xySwap, opacity) => {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class BotFigure extends
|
||||||
const positionQ = transformXY(
|
const positionQ = transformXY(
|
||||||
(position.x || 0), (position.y || 0), mapTransformProps);
|
(position.x || 0), (position.y || 0), mapTransformProps);
|
||||||
const color = eStopStatus ? Color.virtualRed : Color.darkGray;
|
const color = eStopStatus ? Color.virtualRed : Color.darkGray;
|
||||||
const opacity = name.includes("encoder") ? 0.25 : 0.75;
|
const opacity = name.includes("encoder") ? 0.25 : 0.5;
|
||||||
return <g id={name}>
|
return <g id={name}>
|
||||||
<rect id="gantry"
|
<rect id="gantry"
|
||||||
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
|
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
|
||||||
|
|
|
@ -13,6 +13,7 @@ describe("<GardenPlant/>", () => {
|
||||||
return {
|
return {
|
||||||
mapTransformProps: fakeMapTransformProps(),
|
mapTransformProps: fakeMapTransformProps(),
|
||||||
plant: fakePlant(),
|
plant: fakePlant(),
|
||||||
|
current: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
|
@ -21,6 +22,7 @@ describe("<GardenPlant/>", () => {
|
||||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||||
uuid: "plantUuid",
|
uuid: "plantUuid",
|
||||||
animate: false,
|
animate: false,
|
||||||
|
hovered: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +33,8 @@ describe("<GardenPlant/>", () => {
|
||||||
const wrapper = shallow(<GardenPlant {...p} />);
|
const wrapper = shallow(<GardenPlant {...p} />);
|
||||||
expect(wrapper.find("image").length).toEqual(1);
|
expect(wrapper.find("image").length).toEqual(1);
|
||||||
expect(wrapper.find("image").props().opacity).toEqual(1);
|
expect(wrapper.find("image").props().opacity).toEqual(1);
|
||||||
|
expect(wrapper.find("image").props().visibility).toEqual("visible");
|
||||||
|
expect(wrapper.find("image").props().opacity).toEqual(1.0);
|
||||||
expect(wrapper.find("text").length).toEqual(0);
|
expect(wrapper.find("text").length).toEqual(0);
|
||||||
expect(wrapper.find("rect").length).toBeLessThanOrEqual(1);
|
expect(wrapper.find("rect").length).toBeLessThanOrEqual(1);
|
||||||
expect(wrapper.find("use").length).toEqual(0);
|
expect(wrapper.find("use").length).toEqual(0);
|
||||||
|
@ -88,4 +92,21 @@ describe("<GardenPlant/>", () => {
|
||||||
expect(wrapper.find(".plant-indicator").length).toEqual(1);
|
expect(wrapper.find(".plant-indicator").length).toEqual(1);
|
||||||
expect(wrapper.find("Circle").length).toEqual(1);
|
expect(wrapper.find("Circle").length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("doesn't render indicator circle twice", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.selected = true;
|
||||||
|
p.hovered = true;
|
||||||
|
const wrapper = shallow(<GardenPlant {...p} />);
|
||||||
|
expect(wrapper.find(".plant-indicator").length).toEqual(0);
|
||||||
|
expect(wrapper.find("Circle").length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders while dragging", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.dragging = true;
|
||||||
|
const wrapper = shallow(<GardenPlant {...p} />);
|
||||||
|
expect(wrapper.find("image").props().visibility).toEqual("hidden");
|
||||||
|
expect(wrapper.find("image").props().opacity).toEqual(0.4);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,11 +8,13 @@ import { PlantLayer } from "../plant_layer";
|
||||||
import {
|
import {
|
||||||
fakePlant, fakePlantTemplate
|
fakePlant, fakePlantTemplate
|
||||||
} from "../../../../../__test_support__/fake_state/resources";
|
} from "../../../../../__test_support__/fake_state/resources";
|
||||||
import { PlantLayerProps, GardenPlantProps } from "../../../interfaces";
|
import { PlantLayerProps } from "../../../interfaces";
|
||||||
import {
|
import {
|
||||||
fakeMapTransformProps
|
fakeMapTransformProps
|
||||||
} from "../../../../../__test_support__/map_transform_props";
|
} from "../../../../../__test_support__/map_transform_props";
|
||||||
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
import { svgMount } from "../../../../../__test_support__/svg_mount";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import { GardenPlant } from "../garden_plant";
|
||||||
|
|
||||||
describe("<PlantLayer/>", () => {
|
describe("<PlantLayer/>", () => {
|
||||||
const fakeProps = (): PlantLayerProps => ({
|
const fakeProps = (): PlantLayerProps => ({
|
||||||
|
@ -28,6 +30,7 @@ describe("<PlantLayer/>", () => {
|
||||||
zoomLvl: 1,
|
zoomLvl: 1,
|
||||||
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
activeDragXY: { x: undefined, y: undefined, z: undefined },
|
||||||
animate: true,
|
animate: true,
|
||||||
|
hoveredPlant: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows plants", () => {
|
it("shows plants", () => {
|
||||||
|
@ -88,14 +91,14 @@ describe("<PlantLayer/>", () => {
|
||||||
.toEqual("/app/designer/gardens/templates/5");
|
.toEqual("/app/designer/gardens/templates/5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has selected plant", () => {
|
it("has hovered plant", () => {
|
||||||
mockPath = "/app/designer/plants";
|
mockPath = "/app/designer/plants";
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const plant = fakePlant();
|
const plant = fakePlant();
|
||||||
p.plants = [plant];
|
p.plants = [plant];
|
||||||
p.currentPlant = plant;
|
p.hoveredPlant = plant;
|
||||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
const wrapper = shallow(<PlantLayer {...p} />);
|
||||||
expect(wrapper.find("GardenPlant").props().selected).toEqual(true);
|
expect(wrapper.find(GardenPlant).props().hovered).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has plant selected by selection box", () => {
|
it("has plant selected by selection box", () => {
|
||||||
|
@ -105,8 +108,7 @@ describe("<PlantLayer/>", () => {
|
||||||
p.plants = [plant];
|
p.plants = [plant];
|
||||||
p.boxSelected = [plant.uuid];
|
p.boxSelected = [plant.uuid];
|
||||||
const wrapper = svgMount(<PlantLayer {...p} />);
|
const wrapper = svgMount(<PlantLayer {...p} />);
|
||||||
expect((wrapper.find("GardenPlant").props() as GardenPlantProps).selected)
|
expect(wrapper.find("GardenPlant").props().selected).toEqual(true);
|
||||||
.toEqual(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows clicking of unsaved plants", () => {
|
it("allows clicking of unsaved plants", () => {
|
||||||
|
|
|
@ -36,22 +36,22 @@ export class GardenPlant extends
|
||||||
};
|
};
|
||||||
|
|
||||||
get radius() {
|
get radius() {
|
||||||
const { selected, plant } = this.props;
|
const { plant } = this.props;
|
||||||
const { hover } = this.state;
|
const { hover } = this.state;
|
||||||
const { radius } = plant.body;
|
const { radius } = plant.body;
|
||||||
return (hover && !selected) ? radius * 1.1 : radius;
|
return hover ? radius * 1.1 : radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selected, dragging, plant, mapTransformProps,
|
const { current, selected, dragging, plant, mapTransformProps,
|
||||||
activeDragXY, zoomLvl, animate, editing } = this.props;
|
activeDragXY, zoomLvl, animate, editing, hovered } = this.props;
|
||||||
const { id, radius, x, y } = plant.body;
|
const { id, radius, x, y } = plant.body;
|
||||||
const { icon } = this.state;
|
const { icon } = this.state;
|
||||||
|
|
||||||
const { qx, qy } = transformXY(round(x), round(y), mapTransformProps);
|
const { qx, qy } = transformXY(round(x), round(y), mapTransformProps);
|
||||||
const alpha = dragging ? 0.4 : 1.0;
|
const alpha = dragging ? 0.4 : 1.0;
|
||||||
const className = [
|
const className = [
|
||||||
"plant-image", `is-chosen-${selected}`, animate ? "animate" : ""
|
"plant-image", `is-chosen-${current || selected}`, animate ? "animate" : ""
|
||||||
].join(" ");
|
].join(" ");
|
||||||
|
|
||||||
return <g id={"plant-" + id}>
|
return <g id={"plant-" + id}>
|
||||||
|
@ -65,7 +65,7 @@ export class GardenPlant extends
|
||||||
fill={Color.soilCloud}
|
fill={Color.soilCloud}
|
||||||
fillOpacity={0} />}
|
fillOpacity={0} />}
|
||||||
|
|
||||||
{selected && !editing &&
|
{(current || selected) && !editing && !hovered &&
|
||||||
<g id="selected-plant-indicator">
|
<g id="selected-plant-indicator">
|
||||||
<Circle
|
<Circle
|
||||||
className={`plant-indicator ${animate ? "animate" : ""}`}
|
className={`plant-indicator ${animate ? "animate" : ""}`}
|
||||||
|
|
|
@ -19,11 +19,13 @@ export function PlantLayer(props: PlantLayerProps) {
|
||||||
boxSelected,
|
boxSelected,
|
||||||
groupSelected,
|
groupSelected,
|
||||||
animate,
|
animate,
|
||||||
|
hoveredPlant,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return <g id="plant-layer">
|
return <g id="plant-layer">
|
||||||
{visible && plants.map(p => {
|
{visible && plants.map(p => {
|
||||||
const selected = !!(p.uuid === currentPlant?.uuid);
|
const current = p.uuid === currentPlant?.uuid;
|
||||||
|
const hovered = p.uuid === hoveredPlant?.uuid;
|
||||||
const selectedByBox = !!boxSelected?.includes(p.uuid);
|
const selectedByBox = !!boxSelected?.includes(p.uuid);
|
||||||
const selectedByGroup = groupSelected.includes(p.uuid);
|
const selectedByGroup = groupSelected.includes(p.uuid);
|
||||||
const plantCategory = unpackUUID(p.uuid).kind === "PlantTemplate"
|
const plantCategory = unpackUUID(p.uuid).kind === "PlantTemplate"
|
||||||
|
@ -33,12 +35,14 @@ export function PlantLayer(props: PlantLayerProps) {
|
||||||
uuid={p.uuid}
|
uuid={p.uuid}
|
||||||
mapTransformProps={mapTransformProps}
|
mapTransformProps={mapTransformProps}
|
||||||
plant={p}
|
plant={p}
|
||||||
selected={selected || selectedByBox || selectedByGroup}
|
selected={selectedByBox || selectedByGroup}
|
||||||
|
current={current}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
dragging={selected && dragging && editing}
|
dragging={current && dragging && editing}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
zoomLvl={zoomLvl}
|
zoomLvl={zoomLvl}
|
||||||
activeDragXY={activeDragXY}
|
activeDragXY={activeDragXY}
|
||||||
|
hovered={hovered}
|
||||||
animate={animate} />;
|
animate={animate} />;
|
||||||
const wrapperProps = {
|
const wrapperProps = {
|
||||||
className: "plant-link-wrapper",
|
className: "plant-link-wrapper",
|
||||||
|
|
|
@ -66,12 +66,12 @@ describe("<ToolSlotPoint/>", () => {
|
||||||
expect(wrapper.find("text").props().dx).toEqual(-40);
|
expect(wrapper.find("text").props().dx).toEqual(-40);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays 'no tool'", () => {
|
it("displays 'empty'", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.slot.tool = undefined;
|
p.slot.tool = undefined;
|
||||||
p.hoveredToolSlot = p.slot.toolSlot.uuid;
|
p.hoveredToolSlot = p.slot.toolSlot.uuid;
|
||||||
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
const wrapper = svgMount(<ToolSlotPoint {...p} />);
|
||||||
expect(wrapper.find("text").text()).toEqual("no tool");
|
expect(wrapper.find("text").text()).toEqual("empty");
|
||||||
expect(wrapper.find("text").props().dx).toEqual(40);
|
expect(wrapper.find("text").props().dx).toEqual(40);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
|
||||||
const { quadrant, xySwap } = mapTransformProps;
|
const { quadrant, xySwap } = mapTransformProps;
|
||||||
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
|
const xPosition = gantry_mounted ? (botPositionX || 0) : x;
|
||||||
const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps);
|
const { qx, qy } = transformXY(xPosition, y, props.mapTransformProps);
|
||||||
const toolName = props.slot.tool ? props.slot.tool.body.name : "no tool";
|
const toolName = props.slot.tool ? props.slot.tool.body.name : "empty";
|
||||||
const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot;
|
const hovered = props.slot.toolSlot.uuid === props.hoveredToolSlot;
|
||||||
const toolProps = {
|
const toolProps = {
|
||||||
x: qx,
|
x: qx,
|
||||||
|
|
|
@ -40,6 +40,7 @@ describe("<GroupDetailActive/>", () => {
|
||||||
allPoints: [],
|
allPoints: [],
|
||||||
shouldDisplay: () => true,
|
shouldDisplay: () => true,
|
||||||
slugs: [],
|
slugs: [],
|
||||||
|
hovered: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { GroupDetailActive } from "./group_detail_active";
|
||||||
import { ShouldDisplay } from "../../devices/interfaces";
|
import { ShouldDisplay } from "../../devices/interfaces";
|
||||||
import { getShouldDisplayFn } from "../../farmware/state_to_props";
|
import { getShouldDisplayFn } from "../../farmware/state_to_props";
|
||||||
import { uniq } from "lodash";
|
import { uniq } from "lodash";
|
||||||
|
import { UUID } from "../../resources/interfaces";
|
||||||
|
|
||||||
interface GroupDetailProps {
|
interface GroupDetailProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
@ -17,6 +18,7 @@ interface GroupDetailProps {
|
||||||
allPoints: TaggedPoint[];
|
allPoints: TaggedPoint[];
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
slugs: string[];
|
slugs: string[];
|
||||||
|
hovered: UUID | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find a group from a URL-provided ID. */
|
/** Find a group from a URL-provided ID. */
|
||||||
|
@ -35,6 +37,7 @@ function mapStateToProps(props: Everything): GroupDetailProps {
|
||||||
shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot),
|
shouldDisplay: getShouldDisplayFn(props.resources.index, props.bot),
|
||||||
slugs: uniq(selectAllPlantPointers(props.resources.index)
|
slugs: uniq(selectAllPlantPointers(props.resources.index)
|
||||||
.map(p => p.body.openfarm_slug)),
|
.map(p => p.body.openfarm_slug)),
|
||||||
|
hovered: props.resources.consumers.farm_designer.hoveredPlantListItem,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
GroupCriteria, GroupPointCountBreakdown, pointsSelectedByGroup
|
GroupCriteria, GroupPointCountBreakdown, pointsSelectedByGroup
|
||||||
} from "./criteria";
|
} from "./criteria";
|
||||||
import { Content } from "../../constants";
|
import { Content } from "../../constants";
|
||||||
|
import { UUID } from "../../resources/interfaces";
|
||||||
|
|
||||||
export interface GroupDetailActiveProps {
|
export interface GroupDetailActiveProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
@ -25,6 +26,7 @@ export interface GroupDetailActiveProps {
|
||||||
allPoints: TaggedPoint[];
|
allPoints: TaggedPoint[];
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
slugs: string[];
|
slugs: string[];
|
||||||
|
hovered: UUID | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = { timerId?: ReturnType<typeof setInterval> };
|
type State = { timerId?: ReturnType<typeof setInterval> };
|
||||||
|
@ -47,7 +49,7 @@ export class GroupDetailActive
|
||||||
return sortedPoints.map(point => {
|
return sortedPoints.map(point => {
|
||||||
return <PointGroupItem
|
return <PointGroupItem
|
||||||
key={point.uuid}
|
key={point.uuid}
|
||||||
hovered={false}
|
hovered={point.uuid === this.props.hovered}
|
||||||
group={this.props.group}
|
group={this.props.group}
|
||||||
point={point}
|
point={point}
|
||||||
dispatch={this.props.dispatch} />;
|
dispatch={this.props.dispatch} />;
|
||||||
|
|
|
@ -36,7 +36,7 @@ export interface PointsPathLineProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PointsPathLine = (props: PointsPathLineProps) =>
|
export const PointsPathLine = (props: PointsPathLineProps) =>
|
||||||
<g id="group-order"
|
<g id="group-order" style={{ pointerEvents: "none" }}
|
||||||
stroke={props.color || Color.mediumGray}
|
stroke={props.color || Color.mediumGray}
|
||||||
strokeWidth={props.strokeWidth || 3}
|
strokeWidth={props.strokeWidth || 3}
|
||||||
strokeDasharray={props.dash || 12}>
|
strokeDasharray={props.dash || 12}>
|
||||||
|
|
|
@ -93,6 +93,7 @@ export class PointGroupItem
|
||||||
style={{
|
style={{
|
||||||
border: this.criteriaIcon ? "1px solid gray" : "none",
|
border: this.criteriaIcon ? "1px solid gray" : "none",
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
|
background: this.props.hovered ? "lightgray" : "none",
|
||||||
}}
|
}}
|
||||||
src={DEFAULT_ICON}
|
src={DEFAULT_ICON}
|
||||||
onLoad={this.maybeGetCachedIcon}
|
onLoad={this.maybeGetCachedIcon}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import { init, save, edit, destroy } from "../../../api/crud";
|
import { init, save, edit, destroy } from "../../../api/crud";
|
||||||
import { history } from "../../../history";
|
import { history } from "../../../history";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus } from "farmbot";
|
||||||
|
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||||
|
|
||||||
describe("<AddToolSlot />", () => {
|
describe("<AddToolSlot />", () => {
|
||||||
const fakeProps = (): AddToolSlotProps => ({
|
const fakeProps = (): AddToolSlotProps => ({
|
||||||
|
@ -30,14 +31,20 @@ describe("<AddToolSlot />", () => {
|
||||||
botPosition: { x: undefined, y: undefined, z: undefined },
|
botPosition: { x: undefined, y: undefined, z: undefined },
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
findToolSlot: fakeToolSlot,
|
findToolSlot: fakeToolSlot,
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
const wrapper = mount(<AddToolSlot {...fakeProps()} />);
|
const wrapper = mount(<AddToolSlot {...fakeProps()} />);
|
||||||
["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone",
|
["add new tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
|
||||||
"change slot direction", "use current location", "gantry-mounted"
|
"change slot direction", "use current location", "gantry-mounted"
|
||||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
expect(init).toHaveBeenCalled();
|
expect(init).toHaveBeenCalledWith("Point", {
|
||||||
|
pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
|
||||||
|
x: 0, y: 0, z: 0, tool_id: undefined,
|
||||||
|
pullout_direction: ToolPulloutDirection.NONE,
|
||||||
|
gantry_mounted: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders while loading", () => {
|
it("renders while loading", () => {
|
||||||
|
@ -102,6 +109,19 @@ describe("<AddToolSlot />", () => {
|
||||||
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
const wrapper = mount<AddToolSlot>(<AddToolSlot {...p} />);
|
||||||
expect(wrapper.instance().tool).toEqual(undefined);
|
expect(wrapper.instance().tool).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders for express bots", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "express_k10";
|
||||||
|
const wrapper = mount(<AddToolSlot {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("tool");
|
||||||
|
expect(init).toHaveBeenCalledWith("Point", {
|
||||||
|
pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
|
||||||
|
x: 0, y: 0, z: 0, tool_id: undefined,
|
||||||
|
pullout_direction: ToolPulloutDirection.NONE,
|
||||||
|
gantry_mounted: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mapStateToProps()", () => {
|
describe("mapStateToProps()", () => {
|
||||||
|
|
|
@ -11,11 +11,13 @@ import { fakeState } from "../../../__test_support__/fake_state";
|
||||||
import { SaveBtn } from "../../../ui";
|
import { SaveBtn } from "../../../ui";
|
||||||
import { initSave } from "../../../api/crud";
|
import { initSave } from "../../../api/crud";
|
||||||
import { history } from "../../../history";
|
import { history } from "../../../history";
|
||||||
import { error } from "../../../toast/toast";
|
import { FirmwareHardware } from "farmbot";
|
||||||
|
|
||||||
describe("<AddTool />", () => {
|
describe("<AddTool />", () => {
|
||||||
const fakeProps = (): AddToolProps => ({
|
const fakeProps = (): AddToolProps => ({
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
|
existingToolNames: [],
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
@ -38,19 +40,29 @@ describe("<AddTool />", () => {
|
||||||
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
|
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't add stock tools", () => {
|
it.each<[FirmwareHardware, number]>([
|
||||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
["arduino", 6],
|
||||||
|
["farmduino", 6],
|
||||||
|
["farmduino_k14", 6],
|
||||||
|
["farmduino_k15", 8],
|
||||||
|
["express_k10", 2],
|
||||||
|
])("adds peripherals: %s", (firmware, expectedAdds) => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = firmware;
|
||||||
|
const wrapper = mount(<AddTool {...p} />);
|
||||||
wrapper.find("button").last().simulate("click");
|
wrapper.find("button").last().simulate("click");
|
||||||
expect(error).toHaveBeenCalledWith("Please choose a FarmBot model.");
|
expect(initSave).toHaveBeenCalledTimes(expectedAdds);
|
||||||
expect(initSave).not.toHaveBeenCalledTimes(6);
|
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||||
expect(history.push).not.toHaveBeenCalledWith("/app/designer/tools");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds stock tools", () => {
|
it("doesn't add stock tools twice", () => {
|
||||||
const wrapper = mount(<AddTool {...fakeProps()} />);
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "express_k10";
|
||||||
|
p.existingToolNames = ["Seed Trough 1"];
|
||||||
|
const wrapper = mount(<AddTool {...p} />);
|
||||||
wrapper.setState({ model: "express" });
|
wrapper.setState({ model: "express" });
|
||||||
wrapper.find("button").last().simulate("click");
|
wrapper.find("button").last().simulate("click");
|
||||||
expect(initSave).toHaveBeenCalledTimes(2);
|
expect(initSave).toHaveBeenCalledTimes(1);
|
||||||
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@ describe("<EditToolSlot />", () => {
|
||||||
findTool: jest.fn(),
|
findTool: jest.fn(),
|
||||||
botPosition: { x: undefined, y: undefined, z: undefined },
|
botPosition: { x: undefined, y: undefined, z: undefined },
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("redirects", () => {
|
it("redirects", () => {
|
||||||
|
@ -39,7 +40,7 @@ describe("<EditToolSlot />", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.findToolSlot = () => fakeToolSlot();
|
p.findToolSlot = () => fakeToolSlot();
|
||||||
const wrapper = mount(<EditToolSlot {...p} />);
|
const wrapper = mount(<EditToolSlot {...p} />);
|
||||||
["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "toolnone",
|
["edit tool slot", "x (mm)", "y (mm)", "z (mm)", "tool or seed container",
|
||||||
"change slot direction", "use current location", "gantry-mounted"
|
"change slot direction", "use current location", "gantry-mounted"
|
||||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,6 +39,7 @@ describe("<Tools />", () => {
|
||||||
bot,
|
bot,
|
||||||
botToMqttStatus: "down",
|
botToMqttStatus: "down",
|
||||||
hoveredToolSlot: undefined,
|
hoveredToolSlot: undefined,
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders with no tools", () => {
|
it("renders with no tools", () => {
|
||||||
|
@ -64,7 +65,7 @@ describe("<Tools />", () => {
|
||||||
p.toolSlots[1].body.y = 2;
|
p.toolSlots[1].body.y = 2;
|
||||||
const wrapper = mount(<Tools {...p} />);
|
const wrapper = mount(<Tools {...p} />);
|
||||||
[
|
[
|
||||||
"foo", "my tool", "unnamed tool", "(1, 0, 0)", "unknown", "(gantry, 2, 0)"
|
"foo", "my tool", "unnamed", "(1, 0, 0)", "unknown", "(gantry, 2, 0)"
|
||||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -158,6 +159,7 @@ describe("<Tools />", () => {
|
||||||
p.bot.hardware.informational_settings.sync_status = "synced";
|
p.bot.hardware.informational_settings.sync_status = "synced";
|
||||||
p.botToMqttStatus = "up";
|
p.botToMqttStatus = "up";
|
||||||
const wrapper = mount(<Tools {...p} />);
|
const wrapper = mount(<Tools {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("mounted tool");
|
||||||
wrapper.find(".yellow").first().simulate("click");
|
wrapper.find(".yellow").first().simulate("click");
|
||||||
expect(mockDevice.readPin).toHaveBeenCalledWith({
|
expect(mockDevice.readPin).toHaveBeenCalledWith({
|
||||||
label: "pin63", pin_mode: 0, pin_number: 63
|
label: "pin63", pin_mode: 0, pin_number: 63
|
||||||
|
@ -173,6 +175,13 @@ describe("<Tools />", () => {
|
||||||
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
||||||
expect(error).toHaveBeenCalledWith(Content.NOT_AVAILABLE_WHEN_OFFLINE);
|
expect(error).toHaveBeenCalledWith(Content.NOT_AVAILABLE_WHEN_OFFLINE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("doesn't display mounted tool on express models", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "express_k10";
|
||||||
|
const wrapper = mount(<Tools {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).not.toContain("mounted tool");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mapStateToProps()", () => {
|
describe("mapStateToProps()", () => {
|
||||||
|
|
|
@ -136,6 +136,7 @@ describe("<ToolInputRow />", () => {
|
||||||
tools: [],
|
tools: [],
|
||||||
selectedTool: undefined,
|
selectedTool: undefined,
|
||||||
onChange: jest.fn(),
|
onChange: jest.fn(),
|
||||||
|
isExpress: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
@ -149,6 +150,13 @@ describe("<ToolInputRow />", () => {
|
||||||
const wrapper = mount(<ToolInputRow {...p} />);
|
const wrapper = mount(<ToolInputRow {...p} />);
|
||||||
expect(wrapper.text().toLowerCase()).toContain("foo");
|
expect(wrapper.text().toLowerCase()).toContain("foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders for express bots", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.isExpress = true;
|
||||||
|
const wrapper = mount(<ToolInputRow {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain("seed container");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("<SlotLocationInputRow />", () => {
|
describe("<SlotLocationInputRow />", () => {
|
||||||
|
|
|
@ -5,36 +5,37 @@ import {
|
||||||
} from "../designer_panel";
|
} from "../designer_panel";
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { SaveBtn, FBSelect, DropDownItem } from "../../ui";
|
import { SaveBtn } from "../../ui";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus, FirmwareHardware } from "farmbot";
|
||||||
import { initSave } from "../../api/crud";
|
import { initSave } from "../../api/crud";
|
||||||
import { Panel } from "../panel_header";
|
import { Panel } from "../panel_header";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
import { error } from "../../toast/toast";
|
import { selectAllTools } from "../../resources/selectors";
|
||||||
|
import { betterCompact } from "../../util";
|
||||||
enum Model { genesis14 = "genesis14", genesis15 = "genesis15", express = "express" }
|
import {
|
||||||
|
isExpressBoard, getFwHardwareValue
|
||||||
const MODEL_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
|
} from "../../devices/components/firmware_hardware_support";
|
||||||
[Model.genesis14]: { label: t("Genesis v1.2-v1.4"), value: Model.genesis14 },
|
import { getFbosConfig } from "../../resources/getters";
|
||||||
[Model.genesis15]: { label: t("Genesis v1.5+"), value: Model.genesis15 },
|
|
||||||
[Model.express]: { label: t("Express"), value: Model.express },
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface AddToolProps {
|
export interface AddToolProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
existingToolNames: string[];
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddToolState {
|
export interface AddToolState {
|
||||||
toolName: string;
|
toolName: string;
|
||||||
model: Model | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapStateToProps = (props: Everything): AddToolProps => ({
|
export const mapStateToProps = (props: Everything): AddToolProps => ({
|
||||||
dispatch: props.dispatch,
|
dispatch: props.dispatch,
|
||||||
|
existingToolNames: betterCompact(selectAllTools(props.resources.index)
|
||||||
|
.map(tool => tool.body.name)),
|
||||||
|
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
state: AddToolState = { toolName: "", model: undefined };
|
state: AddToolState = { toolName: "" };
|
||||||
|
|
||||||
newTool = (name: string) => {
|
newTool = (name: string) => {
|
||||||
this.props.dispatch(initSave("Tool", { name }));
|
this.props.dispatch(initSave("Tool", { name }));
|
||||||
|
@ -45,9 +46,12 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
history.push("/app/designer/tools");
|
history.push("/app/designer/tools");
|
||||||
}
|
}
|
||||||
|
|
||||||
stockToolNames = (model: Model) => {
|
stockToolNames = () => {
|
||||||
switch (model) {
|
switch (this.props.firmwareHardware) {
|
||||||
case Model.genesis14:
|
case "arduino":
|
||||||
|
case "farmduino":
|
||||||
|
case "farmduino_k14":
|
||||||
|
default:
|
||||||
return [
|
return [
|
||||||
t("Seeder"),
|
t("Seeder"),
|
||||||
t("Watering Nozzle"),
|
t("Watering Nozzle"),
|
||||||
|
@ -56,7 +60,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
t("Seed Bin"),
|
t("Seed Bin"),
|
||||||
t("Seed Tray"),
|
t("Seed Tray"),
|
||||||
];
|
];
|
||||||
case Model.genesis15:
|
case "farmduino_k15":
|
||||||
return [
|
return [
|
||||||
t("Seeder"),
|
t("Seeder"),
|
||||||
t("Watering Nozzle"),
|
t("Watering Nozzle"),
|
||||||
|
@ -67,7 +71,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
t("Seed Trough 1"),
|
t("Seed Trough 1"),
|
||||||
t("Seed Trough 2"),
|
t("Seed Trough 2"),
|
||||||
];
|
];
|
||||||
case Model.express:
|
case "express_k10":
|
||||||
return [
|
return [
|
||||||
t("Seed Trough 1"),
|
t("Seed Trough 1"),
|
||||||
t("Seed Trough 2"),
|
t("Seed Trough 2"),
|
||||||
|
@ -77,31 +81,20 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
|
|
||||||
AddStockTools = () =>
|
AddStockTools = () =>
|
||||||
<div className="add-stock-tools">
|
<div className="add-stock-tools">
|
||||||
<label>{t("Add stock tools")}</label>
|
<label>{t("Add stock names")}</label>
|
||||||
<FBSelect
|
<ul>
|
||||||
customNullLabel={t("Choose model")}
|
{this.stockToolNames().map(n => <li key={n}>{n}</li>)}
|
||||||
list={Object.values(MODEL_DDI_LOOKUP())}
|
</ul>
|
||||||
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
|
<button
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (this.state.model) {
|
this.stockToolNames()
|
||||||
this.stockToolNames(this.state.model).map(n => this.newTool(n));
|
.filter(n => !this.props.existingToolNames.includes(n))
|
||||||
history.push("/app/designer/tools");
|
.map(n => this.newTool(n));
|
||||||
} else {
|
history.push("/app/designer/tools");
|
||||||
error(t("Please choose a FarmBot model."));
|
|
||||||
}
|
|
||||||
}}>
|
}}>
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
{t("Stock Tools")}
|
{t("Stock names")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -110,12 +103,14 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
||||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||||
<DesignerPanelHeader
|
<DesignerPanelHeader
|
||||||
panelName={panelName}
|
panelName={panelName}
|
||||||
title={t("Add new tool")}
|
title={isExpressBoard(this.props.firmwareHardware)
|
||||||
|
? t("Add new")
|
||||||
|
: t("Add new tool")}
|
||||||
backTo={"/app/designer/tools"}
|
backTo={"/app/designer/tools"}
|
||||||
panel={Panel.Tools} />
|
panel={Panel.Tools} />
|
||||||
<DesignerPanelContent panelName={panelName}>
|
<DesignerPanelContent panelName={panelName}>
|
||||||
<div className="add-new-tool">
|
<div className="add-new-tool">
|
||||||
<label>{t("Tool Name")}</label>
|
<label>{t("Name")}</label>
|
||||||
<input onChange={e =>
|
<input onChange={e =>
|
||||||
this.setState({ toolName: e.currentTarget.value })} />
|
this.setState({ toolName: e.currentTarget.value })} />
|
||||||
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
|
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
|
||||||
|
|
|
@ -6,7 +6,9 @@ import {
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { SaveBtn } from "../../ui";
|
import { SaveBtn } from "../../ui";
|
||||||
import { SpecialStatus, TaggedTool, TaggedToolSlotPointer } from "farmbot";
|
import {
|
||||||
|
SpecialStatus, TaggedTool, TaggedToolSlotPointer, FirmwareHardware
|
||||||
|
} from "farmbot";
|
||||||
import { init, save, edit, destroy } from "../../api/crud";
|
import { init, save, edit, destroy } from "../../api/crud";
|
||||||
import { Panel } from "../panel_header";
|
import { Panel } from "../panel_header";
|
||||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||||
|
@ -18,6 +20,10 @@ import { validBotLocationData } from "../../util";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
import { SlotEditRows } from "./tool_slot_edit_components";
|
import { SlotEditRows } from "./tool_slot_edit_components";
|
||||||
import { UUID } from "../../resources/interfaces";
|
import { UUID } from "../../resources/interfaces";
|
||||||
|
import {
|
||||||
|
isExpressBoard, getFwHardwareValue
|
||||||
|
} from "../../devices/components/firmware_hardware_support";
|
||||||
|
import { getFbosConfig } from "../../resources/getters";
|
||||||
|
|
||||||
export interface AddToolSlotProps {
|
export interface AddToolSlotProps {
|
||||||
tools: TaggedTool[];
|
tools: TaggedTool[];
|
||||||
|
@ -25,6 +31,7 @@ export interface AddToolSlotProps {
|
||||||
botPosition: BotPosition;
|
botPosition: BotPosition;
|
||||||
findTool(id: number): TaggedTool | undefined;
|
findTool(id: number): TaggedTool | undefined;
|
||||||
findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined;
|
findToolSlot(uuid: UUID | undefined): TaggedToolSlotPointer | undefined;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddToolSlotState {
|
export interface AddToolSlotState {
|
||||||
|
@ -38,6 +45,7 @@ export const mapStateToProps = (props: Everything): AddToolSlotProps => ({
|
||||||
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
||||||
findToolSlot: (uuid: UUID | undefined) =>
|
findToolSlot: (uuid: UUID | undefined) =>
|
||||||
maybeGetToolSlot(props.resources.index, uuid),
|
maybeGetToolSlot(props.resources.index, uuid),
|
||||||
|
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class RawAddToolSlot
|
export class RawAddToolSlot
|
||||||
|
@ -48,7 +56,8 @@ export class RawAddToolSlot
|
||||||
const action = init("Point", {
|
const action = init("Point", {
|
||||||
pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
|
pointer_type: "ToolSlot", name: "Tool Slot", radius: 0, meta: {},
|
||||||
x: 0, y: 0, z: 0, tool_id: undefined,
|
x: 0, y: 0, z: 0, tool_id: undefined,
|
||||||
pullout_direction: ToolPulloutDirection.NONE, gantry_mounted: false
|
pullout_direction: ToolPulloutDirection.NONE,
|
||||||
|
gantry_mounted: isExpressBoard(this.props.firmwareHardware) ? true : false,
|
||||||
});
|
});
|
||||||
this.setState({ uuid: action.payload.uuid });
|
this.setState({ uuid: action.payload.uuid });
|
||||||
this.props.dispatch(action);
|
this.props.dispatch(action);
|
||||||
|
@ -57,7 +66,7 @@ export class RawAddToolSlot
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.state.uuid && this.toolSlot
|
if (this.state.uuid && this.toolSlot
|
||||||
&& this.toolSlot.specialStatus == SpecialStatus.DIRTY) {
|
&& this.toolSlot.specialStatus == SpecialStatus.DIRTY) {
|
||||||
confirm(t("Save new tool?"))
|
confirm(t("Save new slot?"))
|
||||||
? this.props.dispatch(save(this.state.uuid))
|
? this.props.dispatch(save(this.state.uuid))
|
||||||
: this.props.dispatch(destroy(this.state.uuid, true));
|
: this.props.dispatch(destroy(this.state.uuid, true));
|
||||||
}
|
}
|
||||||
|
@ -86,12 +95,15 @@ export class RawAddToolSlot
|
||||||
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
|
||||||
<DesignerPanelHeader
|
<DesignerPanelHeader
|
||||||
panelName={panelName}
|
panelName={panelName}
|
||||||
title={t("Add new tool slot")}
|
title={isExpressBoard(this.props.firmwareHardware)
|
||||||
|
? t("Add new slot")
|
||||||
|
: t("Add new tool slot")}
|
||||||
backTo={"/app/designer/tools"}
|
backTo={"/app/designer/tools"}
|
||||||
panel={Panel.Tools} />
|
panel={Panel.Tools} />
|
||||||
<DesignerPanelContent panelName={panelName}>
|
<DesignerPanelContent panelName={panelName}>
|
||||||
{this.toolSlot
|
{this.toolSlot
|
||||||
? <SlotEditRows
|
? <SlotEditRows
|
||||||
|
isExpress={isExpressBoard(this.props.firmwareHardware)}
|
||||||
toolSlot={this.toolSlot}
|
toolSlot={this.toolSlot}
|
||||||
tools={this.props.tools}
|
tools={this.props.tools}
|
||||||
tool={this.tool}
|
tool={this.tool}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class RawEditTool extends React.Component<EditToolProps, EditToolState> {
|
||||||
backTo={"/app/designer/tools"}
|
backTo={"/app/designer/tools"}
|
||||||
panel={Panel.Tools} />
|
panel={Panel.Tools} />
|
||||||
<DesignerPanelContent panelName={panelName}>
|
<DesignerPanelContent panelName={panelName}>
|
||||||
<label>{t("Tool Name")}</label>
|
<label>{t("Name")}</label>
|
||||||
<input
|
<input
|
||||||
value={toolName}
|
value={toolName}
|
||||||
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { getPathArray } from "../../history";
|
import { getPathArray } from "../../history";
|
||||||
import { TaggedToolSlotPointer, TaggedTool } from "farmbot";
|
import { TaggedToolSlotPointer, TaggedTool, FirmwareHardware } from "farmbot";
|
||||||
import { edit, save, destroy } from "../../api/crud";
|
import { edit, save, destroy } from "../../api/crud";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
import { Panel } from "../panel_header";
|
import { Panel } from "../panel_header";
|
||||||
|
@ -17,6 +17,10 @@ import { BotPosition } from "../../devices/interfaces";
|
||||||
import { validBotLocationData } from "../../util";
|
import { validBotLocationData } from "../../util";
|
||||||
import { SlotEditRows } from "./tool_slot_edit_components";
|
import { SlotEditRows } from "./tool_slot_edit_components";
|
||||||
import { moveAbs } from "../../devices/actions";
|
import { moveAbs } from "../../devices/actions";
|
||||||
|
import {
|
||||||
|
getFwHardwareValue, isExpressBoard
|
||||||
|
} from "../../devices/components/firmware_hardware_support";
|
||||||
|
import { getFbosConfig } from "../../resources/getters";
|
||||||
|
|
||||||
export interface EditToolSlotProps {
|
export interface EditToolSlotProps {
|
||||||
findToolSlot(id: string): TaggedToolSlotPointer | undefined;
|
findToolSlot(id: string): TaggedToolSlotPointer | undefined;
|
||||||
|
@ -24,6 +28,7 @@ export interface EditToolSlotProps {
|
||||||
findTool(id: number): TaggedTool | undefined;
|
findTool(id: number): TaggedTool | undefined;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
botPosition: BotPosition;
|
botPosition: BotPosition;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapStateToProps = (props: Everything): EditToolSlotProps => ({
|
export const mapStateToProps = (props: Everything): EditToolSlotProps => ({
|
||||||
|
@ -33,6 +38,7 @@ export const mapStateToProps = (props: Everything): EditToolSlotProps => ({
|
||||||
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
findTool: (id: number) => maybeFindToolById(props.resources.index, id),
|
||||||
dispatch: props.dispatch,
|
dispatch: props.dispatch,
|
||||||
botPosition: validBotLocationData(props.bot.hardware.location_data).position,
|
botPosition: validBotLocationData(props.bot.hardware.location_data).position,
|
||||||
|
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
|
export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
|
||||||
|
@ -64,6 +70,7 @@ export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
|
||||||
panel={Panel.Tools} />
|
panel={Panel.Tools} />
|
||||||
<DesignerPanelContent panelName={panelName}>
|
<DesignerPanelContent panelName={panelName}>
|
||||||
<SlotEditRows
|
<SlotEditRows
|
||||||
|
isExpress={isExpressBoard(this.props.firmwareHardware)}
|
||||||
toolSlot={toolSlot}
|
toolSlot={toolSlot}
|
||||||
tools={this.props.tools}
|
tools={this.props.tools}
|
||||||
tool={this.tool}
|
tool={this.tool}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import {
|
import {
|
||||||
TaggedTool, TaggedToolSlotPointer, TaggedDevice, TaggedSensor,
|
TaggedTool, TaggedToolSlotPointer, TaggedDevice, TaggedSensor,
|
||||||
|
FirmwareHardware,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import {
|
import {
|
||||||
selectAllTools, selectAllToolSlotPointers, getDeviceAccountSettings,
|
selectAllTools, selectAllToolSlotPointers, getDeviceAccountSettings,
|
||||||
|
@ -31,6 +32,10 @@ import { getStatus } from "../../connectivity/reducer_support";
|
||||||
import { setToolHover } from "../map/layers/tool_slots/tool_graphics";
|
import { setToolHover } from "../map/layers/tool_slots/tool_graphics";
|
||||||
import { ToolSelection } from "./tool_slot_edit_components";
|
import { ToolSelection } from "./tool_slot_edit_components";
|
||||||
import { error } from "../../toast/toast";
|
import { error } from "../../toast/toast";
|
||||||
|
import {
|
||||||
|
isExpressBoard, getFwHardwareValue
|
||||||
|
} from "../../devices/components/firmware_hardware_support";
|
||||||
|
import { getFbosConfig } from "../../resources/getters";
|
||||||
|
|
||||||
export interface ToolsProps {
|
export interface ToolsProps {
|
||||||
tools: TaggedTool[];
|
tools: TaggedTool[];
|
||||||
|
@ -42,6 +47,7 @@ export interface ToolsProps {
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
botToMqttStatus: NetworkState;
|
botToMqttStatus: NetworkState;
|
||||||
hoveredToolSlot: string | undefined;
|
hoveredToolSlot: string | undefined;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolsState {
|
export interface ToolsState {
|
||||||
|
@ -58,6 +64,7 @@ export const mapStateToProps = (props: Everything): ToolsProps => ({
|
||||||
bot: props.bot,
|
bot: props.bot,
|
||||||
botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]),
|
botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]),
|
||||||
hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot,
|
hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot,
|
||||||
|
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const toolStatus = (value: number | undefined): string => {
|
const toolStatus = (value: number | undefined): string => {
|
||||||
|
@ -108,6 +115,8 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
this.props.botToMqttStatus);
|
this.props.botToMqttStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isExpress() { return isExpressBoard(this.props.firmwareHardware); }
|
||||||
|
|
||||||
MountedToolInfo = () =>
|
MountedToolInfo = () =>
|
||||||
<div className="mounted-tool">
|
<div className="mounted-tool">
|
||||||
<div className="mounted-tool-header">
|
<div className="mounted-tool-header">
|
||||||
|
@ -141,10 +150,10 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
ToolSlots = () =>
|
ToolSlots = () =>
|
||||||
<div className="tool-slots">
|
<div className="tool-slots">
|
||||||
<div className="tool-slots-header">
|
<div className="tool-slots-header">
|
||||||
<label>{t("tool slots")}</label>
|
<label>{this.strings.toolSlots}</label>
|
||||||
<Link to={"/app/designer/tool-slots/add"}>
|
<Link to={"/app/designer/tool-slots/add"}>
|
||||||
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
||||||
<i className="fa fa-plus" title={t("Add tool slot")} />
|
<i className="fa fa-plus" title={this.strings.addSlot} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,10 +171,10 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
Tools = () =>
|
Tools = () =>
|
||||||
<div className="tools">
|
<div className="tools">
|
||||||
<div className="tools-header">
|
<div className="tools-header">
|
||||||
<label>{t("tools")}</label>
|
<label>{this.strings.tools}</label>
|
||||||
<Link to={"/app/designer/tools/add"}>
|
<Link to={"/app/designer/tools/add"}>
|
||||||
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
<div className={`fb-button panel-${TAB_COLOR[Panel.Tools]}`}>
|
||||||
<i className="fa fa-plus" title={t("Add tool")} />
|
<i className="fa fa-plus" title={this.strings.titleText} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,9 +185,32 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
.map(tool =>
|
.map(tool =>
|
||||||
<ToolInventoryItem key={tool.uuid}
|
<ToolInventoryItem key={tool.uuid}
|
||||||
toolId={tool.body.id}
|
toolId={tool.body.id}
|
||||||
toolName={tool.body.name || t("Unnamed tool")} />)}
|
toolName={tool.body.name || t("Unnamed")} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
get strings() {
|
||||||
|
return {
|
||||||
|
placeholder: this.isExpress
|
||||||
|
? t("Search your seed containers...")
|
||||||
|
: t("Search your tools..."),
|
||||||
|
titleText: this.isExpress
|
||||||
|
? t("Add a seed container")
|
||||||
|
: t("Add a tool or seed container"),
|
||||||
|
emptyStateText: this.isExpress
|
||||||
|
? Content.NO_SEED_CONTAINERS
|
||||||
|
: Content.NO_TOOLS,
|
||||||
|
tools: this.isExpress
|
||||||
|
? t("seed containers")
|
||||||
|
: t("tools and seed containers"),
|
||||||
|
toolSlots: this.isExpress
|
||||||
|
? t("seed container slots")
|
||||||
|
: t("tool slots"),
|
||||||
|
addSlot: this.isExpress
|
||||||
|
? t("Add slot")
|
||||||
|
: t("Add tool slot"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const panelName = "tools";
|
const panelName = "tools";
|
||||||
const hasTools = this.props.tools.length > 0;
|
const hasTools = this.props.tools.length > 0;
|
||||||
|
@ -187,18 +219,19 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
<DesignerPanelTop
|
<DesignerPanelTop
|
||||||
panel={Panel.Tools}
|
panel={Panel.Tools}
|
||||||
linkTo={!hasTools ? "/app/designer/tools/add" : undefined}
|
linkTo={!hasTools ? "/app/designer/tools/add" : undefined}
|
||||||
title={!hasTools ? t("Add tool") : undefined}>
|
title={!hasTools ? this.strings.titleText : undefined}>
|
||||||
<input type="text" onChange={this.update}
|
<input type="text" onChange={this.update}
|
||||||
placeholder={t("Search your tools...")} />
|
placeholder={this.strings.placeholder} />
|
||||||
</DesignerPanelTop>
|
</DesignerPanelTop>
|
||||||
<DesignerPanelContent panelName={"tools"}>
|
<DesignerPanelContent panelName={"tools"}>
|
||||||
<EmptyStateWrapper
|
<EmptyStateWrapper
|
||||||
notEmpty={hasTools}
|
notEmpty={hasTools}
|
||||||
graphic={EmptyStateGraphic.tools}
|
graphic={EmptyStateGraphic.tools}
|
||||||
title={t("Add a tool")}
|
title={this.strings.titleText}
|
||||||
text={Content.NO_TOOLS}
|
text={this.strings.emptyStateText}
|
||||||
colorScheme={"tools"}>
|
colorScheme={"tools"}>
|
||||||
<this.MountedToolInfo />
|
{!this.isExpress &&
|
||||||
|
<this.MountedToolInfo />}
|
||||||
<this.ToolSlots />
|
<this.ToolSlots />
|
||||||
<this.Tools />
|
<this.Tools />
|
||||||
</EmptyStateWrapper>
|
</EmptyStateWrapper>
|
||||||
|
@ -223,7 +256,7 @@ const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
||||||
onMouseLeave={() => props.dispatch(setToolHover(undefined))}>
|
onMouseLeave={() => props.dispatch(setToolHover(undefined))}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={7}>
|
<Col xs={7}>
|
||||||
<p>{props.getToolName(tool_id) || t("No tool")}</p>
|
<p>{props.getToolName(tool_id) || t("Empty")}</p>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={5}>
|
<Col xs={5}>
|
||||||
<p className="tool-slot-position">
|
<p className="tool-slot-position">
|
||||||
|
|
|
@ -97,13 +97,18 @@ export interface ToolInputRowProps {
|
||||||
tools: TaggedTool[];
|
tools: TaggedTool[];
|
||||||
selectedTool: TaggedTool | undefined;
|
selectedTool: TaggedTool | undefined;
|
||||||
onChange(update: { tool_id: number }): void;
|
onChange(update: { tool_id: number }): void;
|
||||||
|
isExpress: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolInputRow = (props: ToolInputRowProps) =>
|
export const ToolInputRow = (props: ToolInputRowProps) =>
|
||||||
<div className="tool-slot-tool-input">
|
<div className="tool-slot-tool-input">
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12}>
|
<Col xs={12}>
|
||||||
<label>{t("Tool")}</label>
|
<label>
|
||||||
|
{props.isExpress
|
||||||
|
? t("Seed Container")
|
||||||
|
: t("Tool or Seed Container")}
|
||||||
|
</label>
|
||||||
<ToolSelection
|
<ToolSelection
|
||||||
tools={props.tools}
|
tools={props.tools}
|
||||||
selectedTool={props.selectedTool}
|
selectedTool={props.selectedTool}
|
||||||
|
@ -144,6 +149,7 @@ export interface SlotEditRowsProps {
|
||||||
tool: TaggedTool | undefined;
|
tool: TaggedTool | undefined;
|
||||||
botPosition: BotPosition;
|
botPosition: BotPosition;
|
||||||
updateToolSlot(update: Partial<TaggedToolSlotPointer["body"]>): void;
|
updateToolSlot(update: Partial<TaggedToolSlotPointer["body"]>): void;
|
||||||
|
isExpress: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SlotEditRows = (props: SlotEditRowsProps) =>
|
export const SlotEditRows = (props: SlotEditRowsProps) =>
|
||||||
|
@ -153,16 +159,19 @@ export const SlotEditRows = (props: SlotEditRowsProps) =>
|
||||||
gantryMounted={props.toolSlot.body.gantry_mounted}
|
gantryMounted={props.toolSlot.body.gantry_mounted}
|
||||||
onChange={props.updateToolSlot} />
|
onChange={props.updateToolSlot} />
|
||||||
<ToolInputRow
|
<ToolInputRow
|
||||||
|
isExpress={props.isExpress}
|
||||||
tools={props.tools}
|
tools={props.tools}
|
||||||
selectedTool={props.tool}
|
selectedTool={props.tool}
|
||||||
onChange={props.updateToolSlot} />
|
onChange={props.updateToolSlot} />
|
||||||
<SlotDirectionInputRow
|
{!props.toolSlot.body.gantry_mounted &&
|
||||||
toolPulloutDirection={props.toolSlot.body.pullout_direction}
|
<SlotDirectionInputRow
|
||||||
onChange={props.updateToolSlot} />
|
toolPulloutDirection={props.toolSlot.body.pullout_direction}
|
||||||
|
onChange={props.updateToolSlot} />}
|
||||||
<UseCurrentLocationInputRow
|
<UseCurrentLocationInputRow
|
||||||
botPosition={props.botPosition}
|
botPosition={props.botPosition}
|
||||||
onChange={props.updateToolSlot} />
|
onChange={props.updateToolSlot} />
|
||||||
<GantryMountedInput
|
{!props.isExpress &&
|
||||||
gantryMounted={props.toolSlot.body.gantry_mounted}
|
<GantryMountedInput
|
||||||
onChange={props.updateToolSlot} />
|
gantryMounted={props.toolSlot.body.gantry_mounted}
|
||||||
|
onChange={props.updateToolSlot} />}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -40,11 +40,11 @@ export const setFolderName = (id: number, name: string) => {
|
||||||
return d(save(folder.uuid)) as Promise<{}>;
|
return d(save(folder.uuid)) as Promise<{}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULTS: Folder = {
|
const DEFAULTS = (): Folder => ({
|
||||||
name: "New Folder",
|
name: t("New Folder"),
|
||||||
color: "gray",
|
color: "gray",
|
||||||
parent_id: 0,
|
parent_id: 0,
|
||||||
};
|
});
|
||||||
|
|
||||||
export const addNewSequenceToFolder = (folder_id?: number) => {
|
export const addNewSequenceToFolder = (folder_id?: number) => {
|
||||||
const uuidMap = store.getState().resources.index.byKind["Sequence"];
|
const uuidMap = store.getState().resources.index.byKind["Sequence"];
|
||||||
|
@ -67,7 +67,7 @@ export const addNewSequenceToFolder = (folder_id?: number) => {
|
||||||
|
|
||||||
export const createFolder = (config: DeepPartial<Folder> = {}) => {
|
export const createFolder = (config: DeepPartial<Folder> = {}) => {
|
||||||
const d: Function = store.dispatch;
|
const d: Function = store.dispatch;
|
||||||
const folder: Folder = { ...DEFAULTS, ...config };
|
const folder: Folder = { ...DEFAULTS(), ...config };
|
||||||
const action = initSave("Folder", folder);
|
const action = initSave("Folder", folder);
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
const p: Promise<{}> = d(action);
|
const p: Promise<{}> = d(action);
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const LaptopSplash = ({ className }: { className: string }) =>
|
||||||
<div className="laptop">
|
<div className="laptop">
|
||||||
<div className="laptop-screen">
|
<div className="laptop-screen">
|
||||||
<video muted autoPlay loop>
|
<video muted autoPlay loop>
|
||||||
<source src={ExternalUrl.Videos.desktop} type="video/mp4" />
|
<source src={ExternalUrl.Video.desktop} type="video/mp4" />
|
||||||
</video>
|
</video>
|
||||||
<span className="laptop-shine" />
|
<span className="laptop-shine" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe("<Tour />", () => {
|
||||||
expect(wrapper.state()).toEqual({
|
expect(wrapper.state()).toEqual({
|
||||||
run: true, index: 1, returnPath: "/app/messages"
|
run: true, index: 1, returnPath: "/app/messages"
|
||||||
});
|
});
|
||||||
expect(history.push).toHaveBeenCalledWith("/app/tools");
|
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("navigates through tour: other", () => {
|
it("navigates through tour: other", () => {
|
||||||
|
|
|
@ -1,7 +1,22 @@
|
||||||
jest.mock("../../history", () => ({ history: { push: jest.fn() } }));
|
jest.mock("../../history", () => ({ history: { push: jest.fn() } }));
|
||||||
|
|
||||||
import { tourPageNavigation } from "../tours";
|
let mockDev = false;
|
||||||
|
jest.mock("../../account/dev/dev_support", () => ({
|
||||||
|
DevSettings: {
|
||||||
|
futureFeaturesEnabled: () => mockDev,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { fakeState } from "../../__test_support__/fake_state";
|
||||||
|
const mockState = fakeState();
|
||||||
|
jest.mock("../../redux/store", () => ({
|
||||||
|
store: { getState: () => mockState },
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { tourPageNavigation, TOUR_STEPS, Tours } from "../tours";
|
||||||
import { history } from "../../history";
|
import { history } from "../../history";
|
||||||
|
import { fakeTool, fakeFbosConfig } from "../../__test_support__/fake_state/resources";
|
||||||
|
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
describe("tourPageNavigation()", () => {
|
describe("tourPageNavigation()", () => {
|
||||||
const testCase = (el: string) => {
|
const testCase = (el: string) => {
|
||||||
|
@ -20,8 +35,47 @@ describe("tourPageNavigation()", () => {
|
||||||
testCase(".regimen-list-panel");
|
testCase(".regimen-list-panel");
|
||||||
testCase(".tool-list");
|
testCase(".tool-list");
|
||||||
testCase(".toolbay-list");
|
testCase(".toolbay-list");
|
||||||
|
testCase(".tools");
|
||||||
|
testCase(".tool-slots");
|
||||||
|
testCase(".tools-panel");
|
||||||
testCase(".photos");
|
testCase(".photos");
|
||||||
testCase(".logs-table");
|
testCase(".logs-table");
|
||||||
testCase(".app-settings-widget");
|
testCase(".app-settings-widget");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("includes steps based on tool count", () => {
|
||||||
|
const getTargets = () =>
|
||||||
|
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target);
|
||||||
|
mockDev = false;
|
||||||
|
mockState.resources = buildResourceIndex([]);
|
||||||
|
expect(getTargets()).not.toContain(".tool-slots");
|
||||||
|
mockState.resources = buildResourceIndex([fakeTool()]);
|
||||||
|
expect(getTargets()).toContain(".tool-slots");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has correct content based on board version", () => {
|
||||||
|
const getTitles = () =>
|
||||||
|
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.title);
|
||||||
|
mockDev = false;
|
||||||
|
mockState.resources = buildResourceIndex([]);
|
||||||
|
expect(getTitles()).toContain("Add tools and tool slots");
|
||||||
|
expect(getTitles()).not.toContain("Add seed containers");
|
||||||
|
const fbosConfig = fakeFbosConfig();
|
||||||
|
fbosConfig.body.firmware_hardware = "express_k10";
|
||||||
|
mockState.resources = buildResourceIndex([fbosConfig]);
|
||||||
|
expect(getTitles()).toContain("Add seed containers and slots");
|
||||||
|
expect(getTitles()).not.toContain("Add seed containers");
|
||||||
|
mockState.resources = buildResourceIndex([fbosConfig, fakeTool()]);
|
||||||
|
expect(getTitles()).not.toContain("Add seed containers and slots");
|
||||||
|
expect(getTitles()).toContain("Add seed containers");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes correct tour steps", () => {
|
||||||
|
mockDev = true;
|
||||||
|
const targets =
|
||||||
|
Object.values(TOUR_STEPS()[Tours.gettingStarted]).map(t => t.target);
|
||||||
|
expect(targets).not.toContain(".tools");
|
||||||
|
expect(targets).toContain(".tool-list");
|
||||||
|
expect(targets).toContain(".toolbay-list");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { TOUR_STEPS, tourPageNavigation } from "./tours";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
import { Actions } from "../constants";
|
import { Actions } from "../constants";
|
||||||
import { store } from "../redux/store";
|
import { store } from "../redux/store";
|
||||||
|
import { ErrorBoundary } from "../error_boundary";
|
||||||
|
|
||||||
const strings = () => ({
|
const strings = () => ({
|
||||||
back: t("Back"),
|
back: t("Back"),
|
||||||
|
@ -65,15 +66,17 @@ export class Tour extends React.Component<TourProps, TourState> {
|
||||||
return step;
|
return step;
|
||||||
});
|
});
|
||||||
return <div className="tour">
|
return <div className="tour">
|
||||||
<Joyride
|
<ErrorBoundary>
|
||||||
steps={steps}
|
<Joyride
|
||||||
run={this.state.run}
|
steps={steps}
|
||||||
callback={this.callback}
|
run={this.state.run}
|
||||||
stepIndex={this.state.index}
|
callback={this.callback}
|
||||||
showSkipButton={true}
|
stepIndex={this.state.index}
|
||||||
continuous={true}
|
showSkipButton={true}
|
||||||
styles={STYLES}
|
continuous={true}
|
||||||
locale={strings()} />
|
styles={STYLES}
|
||||||
|
locale={strings()} />
|
||||||
|
</ErrorBoundary>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { history } from "../history";
|
||||||
import { Step as TourStep } from "react-joyride";
|
import { Step as TourStep } from "react-joyride";
|
||||||
import { TourContent } from "../constants";
|
import { TourContent } from "../constants";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../account/dev/dev_support";
|
||||||
|
import { selectAllTools } from "../resources/selectors";
|
||||||
|
import { store } from "../redux/store";
|
||||||
|
import { getFbosConfig } from "../resources/getters";
|
||||||
|
import {
|
||||||
|
isExpressBoard, getFwHardwareValue
|
||||||
|
} from "../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
export enum Tours {
|
export enum Tours {
|
||||||
gettingStarted = "gettingStarted",
|
gettingStarted = "gettingStarted",
|
||||||
|
@ -15,70 +22,105 @@ export const tourNames = () => [
|
||||||
{ name: Tours.funStuff, description: t("find new features") },
|
{ name: Tours.funStuff, description: t("find new features") },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const hasTools = () =>
|
||||||
|
selectAllTools(store.getState().resources.index).length > 0;
|
||||||
|
|
||||||
|
const isExpress = () =>
|
||||||
|
isExpressBoard(getFwHardwareValue(
|
||||||
|
getFbosConfig(store.getState().resources.index)));
|
||||||
|
|
||||||
|
const toolsStep = () => hasTools()
|
||||||
|
? [{
|
||||||
|
target: ".tools",
|
||||||
|
content: isExpress()
|
||||||
|
? t(TourContent.ADD_SEED_CONTAINERS)
|
||||||
|
: t(TourContent.ADD_TOOLS),
|
||||||
|
title: isExpress()
|
||||||
|
? t("Add seed containers")
|
||||||
|
: t("Add tools and seed containers"),
|
||||||
|
}]
|
||||||
|
: [{
|
||||||
|
target: ".tools",
|
||||||
|
content: isExpress()
|
||||||
|
? t(TourContent.ADD_SEED_CONTAINERS_AND_SLOTS)
|
||||||
|
: t(TourContent.ADD_TOOLS_AND_SLOTS),
|
||||||
|
title: isExpress()
|
||||||
|
? t("Add seed containers and slots")
|
||||||
|
: t("Add tools and tool slots"),
|
||||||
|
}];
|
||||||
|
|
||||||
|
const toolSlotsStep = () => hasTools()
|
||||||
|
? [{
|
||||||
|
target: ".tool-slots",
|
||||||
|
content: t(TourContent.ADD_TOOLS_AND_SLOTS),
|
||||||
|
title: t("Add tool slots"),
|
||||||
|
}]
|
||||||
|
: [];
|
||||||
|
|
||||||
export const TOUR_STEPS = (): { [x: string]: TourStep[] } => ({
|
export const TOUR_STEPS = (): { [x: string]: TourStep[] } => ({
|
||||||
[Tours.gettingStarted]: [
|
[Tours.gettingStarted]: [
|
||||||
{
|
{
|
||||||
target: ".plant-inventory-panel",
|
target: ".plant-inventory-panel",
|
||||||
content: TourContent.ADD_PLANTS,
|
content: t(TourContent.ADD_PLANTS),
|
||||||
title: t("Add plants"),
|
title: t("Add plants"),
|
||||||
},
|
},
|
||||||
{
|
...(DevSettings.futureFeaturesEnabled() ? [{
|
||||||
target: ".tool-list",
|
target: ".tool-list",
|
||||||
content: TourContent.ADD_TOOLS,
|
content: t(TourContent.ADD_TOOLS),
|
||||||
title: t("Add tools"),
|
title: t("Add tools and seed containers"),
|
||||||
},
|
}] : toolsStep()),
|
||||||
{
|
...(DevSettings.futureFeaturesEnabled() ? [{
|
||||||
target: ".toolbay-list",
|
target: ".toolbay-list",
|
||||||
content: TourContent.ADD_TOOLS_SLOTS,
|
content: t(TourContent.ADD_TOOLS_SLOTS),
|
||||||
title: t("Add tools to tool bay"),
|
title: t("Add tools to tool bay"),
|
||||||
},
|
}] : toolSlotsStep()),
|
||||||
{
|
{
|
||||||
target: ".peripherals-widget",
|
target: ".peripherals-widget",
|
||||||
content: TourContent.ADD_PERIPHERALS,
|
content: t(TourContent.ADD_PERIPHERALS),
|
||||||
title: t("Add peripherals"),
|
title: t("Add peripherals"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".sequence-list-panel",
|
target: ".sequence-list-panel",
|
||||||
content: TourContent.ADD_SEQUENCES,
|
content: t(TourContent.ADD_SEQUENCES),
|
||||||
title: t("Create sequences"),
|
title: t("Create sequences"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".regimen-list-panel",
|
target: ".regimen-list-panel",
|
||||||
content: TourContent.ADD_REGIMENS,
|
content: t(TourContent.ADD_REGIMENS),
|
||||||
title: t("Create regimens"),
|
title: t("Create regimens"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".farm-event-panel",
|
target: ".farm-event-panel",
|
||||||
content: TourContent.ADD_FARM_EVENTS,
|
content: t(TourContent.ADD_FARM_EVENTS),
|
||||||
title: t("Create events"),
|
title: t("Create events"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[Tours.monitoring]: [
|
[Tours.monitoring]: [
|
||||||
{
|
{
|
||||||
target: ".move-widget",
|
target: ".move-widget",
|
||||||
content: TourContent.LOCATION_GRID,
|
content: t(TourContent.LOCATION_GRID),
|
||||||
title: t("View current location"),
|
title: t("View current location"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".farm-designer",
|
target: ".farm-designer",
|
||||||
content: TourContent.VIRTUAL_FARMBOT,
|
content: t(TourContent.VIRTUAL_FARMBOT),
|
||||||
title: t("View current location"),
|
title: t("View current location"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".logs-table",
|
target: ".logs-table",
|
||||||
content: TourContent.LOGS_TABLE,
|
content: t(TourContent.LOGS_TABLE),
|
||||||
title: t("View log messages"),
|
title: t("View log messages"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ".photos",
|
target: ".photos",
|
||||||
content: TourContent.PHOTOS,
|
content: t(TourContent.PHOTOS),
|
||||||
title: t("Take and view photos"),
|
title: t("Take and view photos"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[Tours.funStuff]: [
|
[Tours.funStuff]: [
|
||||||
{
|
{
|
||||||
target: ".app-settings-widget",
|
target: ".app-settings-widget",
|
||||||
content: TourContent.APP_SETTINGS,
|
content: t(TourContent.APP_SETTINGS),
|
||||||
title: t("Customize your web app experience"),
|
title: t("Customize your web app experience"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -112,6 +154,10 @@ export const tourPageNavigation = (nextStepTarget: string | HTMLElement) => {
|
||||||
case ".toolbay-list":
|
case ".toolbay-list":
|
||||||
history.push("/app/tools");
|
history.push("/app/tools");
|
||||||
break;
|
break;
|
||||||
|
case ".tools":
|
||||||
|
case ".tool-slots":
|
||||||
|
history.push("/app/designer/tools");
|
||||||
|
break;
|
||||||
case ".photos":
|
case ".photos":
|
||||||
history.push("/app/farmware");
|
history.push("/app/farmware");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -52,8 +52,7 @@ describe("<Alerts />", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.alerts = [FIRMWARE_MISSING_ALERT, SEED_DATA_MISSING_ALERT];
|
p.alerts = [FIRMWARE_MISSING_ALERT, SEED_DATA_MISSING_ALERT];
|
||||||
const wrapper = mount(<Alerts {...p} />);
|
const wrapper = mount(<Alerts {...p} />);
|
||||||
expect(wrapper.text()).toContain("2");
|
expect(wrapper.text()).not.toContain("Your device has no firmware");
|
||||||
expect(wrapper.text()).toContain("Your device has no firmware");
|
|
||||||
expect(wrapper.text()).toContain("Choose your FarmBot");
|
expect(wrapper.text()).toContain("Choose your FarmBot");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,7 +60,6 @@ describe("<Alerts />", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.alerts = [FIRMWARE_MISSING_ALERT, UNKNOWN_ALERT];
|
p.alerts = [FIRMWARE_MISSING_ALERT, UNKNOWN_ALERT];
|
||||||
const wrapper = mount(<Alerts {...p} />);
|
const wrapper = mount(<Alerts {...p} />);
|
||||||
expect(wrapper.text()).toContain("1");
|
|
||||||
expect(wrapper.text()).toContain("firmware: alert");
|
expect(wrapper.text()).toContain("firmware: alert");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,6 +34,7 @@ export const Alerts = (props: AlertsProps) =>
|
||||||
<div className="problem-alerts-content">
|
<div className="problem-alerts-content">
|
||||||
{sortAlerts(props.alerts)
|
{sortAlerts(props.alerts)
|
||||||
.filter(filterIncompleteAlerts)
|
.filter(filterIncompleteAlerts)
|
||||||
|
.filter(x => x.problem_tag != "farmbot_os.firmware.missing")
|
||||||
.map(x =>
|
.map(x =>
|
||||||
<AlertCard key={x.slug + x.created_at}
|
<AlertCard key={x.slug + x.created_at}
|
||||||
alert={x}
|
alert={x}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { info } from "../toast/toast";
|
||||||
import { semverCompare, SemverResult, FbosVersionFallback } from "../util";
|
import { semverCompare, SemverResult, FbosVersionFallback } from "../util";
|
||||||
import { Content } from "../constants";
|
import { Content } from "../constants";
|
||||||
import { Dictionary } from "lodash";
|
import { Dictionary } from "lodash";
|
||||||
|
import { t } from "../i18next_wrapper";
|
||||||
|
|
||||||
const IDEAL_VERSION =
|
const IDEAL_VERSION =
|
||||||
globalConfig.FBOS_END_OF_LIFE_VERSION || FbosVersionFallback.NULL;
|
globalConfig.FBOS_END_OF_LIFE_VERSION || FbosVersionFallback.NULL;
|
||||||
|
@ -22,7 +23,7 @@ export function createReminderFn() {
|
||||||
!alreadyChecked[version]
|
!alreadyChecked[version]
|
||||||
// Is it up to date?
|
// Is it up to date?
|
||||||
&& semverCompare(version, IDEAL_VERSION) === SemverResult.RIGHT_IS_GREATER
|
&& semverCompare(version, IDEAL_VERSION) === SemverResult.RIGHT_IS_GREATER
|
||||||
&& info(Content.OLD_FBOS_REC_UPGRADE);
|
&& info(t(Content.OLD_FBOS_REC_UPGRADE));
|
||||||
|
|
||||||
alreadyChecked[version] = true; // Turn off checks for this version now.
|
alreadyChecked[version] = true; // Turn off checks for this version now.
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe("unpackStep()", () => {
|
||||||
step: resourceUpdate({ label: "mounted_tool_id", value: 0 }),
|
step: resourceUpdate({ label: "mounted_tool_id", value: 0 }),
|
||||||
resourceIndex: fakeResourceIndex()
|
resourceIndex: fakeResourceIndex()
|
||||||
});
|
});
|
||||||
expect(result).toEqual(DISMOUNTED);
|
expect(result).toEqual(DISMOUNTED());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unpacks valid tool_ids", () => {
|
it("unpacks valid tool_ids", () => {
|
||||||
|
@ -37,7 +37,7 @@ describe("unpackStep()", () => {
|
||||||
resourceIndex
|
resourceIndex
|
||||||
});
|
});
|
||||||
const actionLabel = "Mounted to: Generic Tool";
|
const actionLabel = "Mounted to: Generic Tool";
|
||||||
const { label, value } = TOOL_MOUNT;
|
const { label, value } = TOOL_MOUNT();
|
||||||
assertGoodness(result, actionLabel, "mounted", label, value);
|
assertGoodness(result, actionLabel, "mounted", label, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ describe("unpackStep()", () => {
|
||||||
resourceIndex: fakeResourceIndex()
|
resourceIndex: fakeResourceIndex()
|
||||||
});
|
});
|
||||||
const actionLabel = "Mounted to: an unknown tool";
|
const actionLabel = "Mounted to: an unknown tool";
|
||||||
const { label, value } = TOOL_MOUNT;
|
const { label, value } = TOOL_MOUNT();
|
||||||
assertGoodness(result, actionLabel, "mounted", label, value);
|
assertGoodness(result, actionLabel, "mounted", label, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,16 @@ import { GenericPointer } from "farmbot/dist/resources/api_resources";
|
||||||
import { MOUNTED_TO } from "./constants";
|
import { MOUNTED_TO } from "./constants";
|
||||||
import { DropDownPair, StepWithResourceIndex } from "./interfaces";
|
import { DropDownPair, StepWithResourceIndex } from "./interfaces";
|
||||||
import { TaggedPoint, TaggedPlantPointer } from "farmbot";
|
import { TaggedPoint, TaggedPlantPointer } from "farmbot";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
|
||||||
export const TOOL_MOUNT: DropDownItem = {
|
export const TOOL_MOUNT = (): DropDownItem => ({
|
||||||
label: "Tool Mount", value: "tool_mount"
|
label: t("Tool Mount"), value: "tool_mount"
|
||||||
};
|
});
|
||||||
const NOT_IN_USE: DropDownItem = { label: "Not Mounted", value: 0 };
|
const NOT_IN_USE = (): DropDownItem => ({ label: t("Not Mounted"), value: 0 });
|
||||||
export const DISMOUNTED: DropDownPair = {
|
export const DISMOUNTED = (): DropDownPair => ({
|
||||||
leftSide: TOOL_MOUNT,
|
leftSide: TOOL_MOUNT(),
|
||||||
rightSide: NOT_IN_USE
|
rightSide: NOT_IN_USE()
|
||||||
};
|
});
|
||||||
const DEFAULT_TOOL_NAME = "Untitled Tool";
|
const DEFAULT_TOOL_NAME = "Untitled Tool";
|
||||||
const REMOVED_ACTION = { label: "Removed", value: "removed" };
|
const REMOVED_ACTION = { label: "Removed", value: "removed" };
|
||||||
|
|
||||||
|
@ -30,13 +31,13 @@ function mountTool(i: StepWithResourceIndex): DropDownPair {
|
||||||
if (typeof value === "number" && value > 0) {
|
if (typeof value === "number" && value > 0) {
|
||||||
try { // Good tool id
|
try { // Good tool id
|
||||||
const tool = findToolById(i.resourceIndex, value as number);
|
const tool = findToolById(i.resourceIndex, value as number);
|
||||||
return { leftSide: TOOL_MOUNT, rightSide: mountedTo(tool.body.name) };
|
return { leftSide: TOOL_MOUNT(), rightSide: mountedTo(tool.body.name) };
|
||||||
} catch { // Bad tool ID or app still loading.
|
} catch { // Bad tool ID or app still loading.
|
||||||
return { leftSide: TOOL_MOUNT, rightSide: mountedTo("an unknown tool") };
|
return { leftSide: TOOL_MOUNT(), rightSide: mountedTo("an unknown tool") };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No tool id
|
// No tool id
|
||||||
return DISMOUNTED;
|
return DISMOUNTED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,10 +56,10 @@ function unknownOption(i: StepWithResourceIndex): DropDownPair {
|
||||||
/** The user wants to mark a the `discarded_at` attribute of a Point. */
|
/** The user wants to mark a the `discarded_at` attribute of a Point. */
|
||||||
function discardPoint(i: StepWithResourceIndex): DropDownPair {
|
function discardPoint(i: StepWithResourceIndex): DropDownPair {
|
||||||
const { resource_id } = i.step.args;
|
const { resource_id } = i.step.args;
|
||||||
const t =
|
const genericPointerBody =
|
||||||
findPointerByTypeAndId(i.resourceIndex, "GenericPointer", resource_id).body;
|
findPointerByTypeAndId(i.resourceIndex, "GenericPointer", resource_id).body;
|
||||||
return {
|
return {
|
||||||
leftSide: pointer2ddi(t as GenericPointer),
|
leftSide: pointer2ddi(genericPointerBody as GenericPointer),
|
||||||
rightSide: REMOVED_ACTION
|
rightSide: REMOVED_ACTION
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ var HelperNamespace = (function () {
|
||||||
var T_REGEX = /[.{[(\s]t\(["`]([\w\s{}().,:'\-=\\?\/%!]*)["`],*\s*.*\)/g;
|
var T_REGEX = /[.{[(\s]t\(["`]([\w\s{}().,:'\-=\\?\/%!]*)["`],*\s*.*\)/g;
|
||||||
|
|
||||||
// '``'
|
// '``'
|
||||||
var C_REGEX = /[`]([\w\s{}().,:'\-=\\?"+!]*)[`].*/g;
|
var C_REGEX = /[`]([\w\s{}().,:'\-=\/\\?"+!]*)[`].*/g;
|
||||||
|
|
||||||
/** Some additional phrases the regex can't find. */
|
/** Some additional phrases the regex can't find. */
|
||||||
var EXTRA_TAGS = [
|
var EXTRA_TAGS = [
|
||||||
|
@ -60,6 +60,7 @@ var HelperNamespace = (function () {
|
||||||
"Else Execute", "Connecting FarmBot to the Internet", "move to home",
|
"Else Execute", "Connecting FarmBot to the Internet", "move to home",
|
||||||
"emergency stop", "SYNC ERROR", "inactive", "error", "No messages.",
|
"emergency stop", "SYNC ERROR", "inactive", "error", "No messages.",
|
||||||
"back to regimens", "back to sequences", "back to farmware list",
|
"back to regimens", "back to sequences", "back to farmware list",
|
||||||
|
"Verify Password",
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,20 +21,20 @@ For example, `sudo docker-compose run web npm run translation-check`._
|
||||||
|
|
||||||
See the [README](https://github.com/FarmBot/Farmbot-Web-App#translating-the-web-app-into-your-language) for contribution instructions.
|
See the [README](https://github.com/FarmBot/Farmbot-Web-App#translating-the-web-app-into-your-language) for contribution instructions.
|
||||||
|
|
||||||
Total number of phrases identified by the language helper for translation: __1139__
|
Total number of phrases identified by the language helper for translation: __1238__
|
||||||
|
|
||||||
|Language|Percent translated|Translated|Untranslated|Other Translations|
|
|Language|Percent translated|Translated|Untranslated|Other Translations|
|
||||||
|:---:|---:|---:|---:|---:|
|
|:---:|---:|---:|---:|---:|
|
||||||
|da|10%|109|1030|44|
|
|da|8%|105|1133|77|
|
||||||
|de|36%|413|726|141|
|
|de|32%|397|841|168|
|
||||||
|es|88%|1002|137|173|
|
|es|78%|965|273|210|
|
||||||
|fr|90%|1022|117|198|
|
|fr|80%|985|253|242|
|
||||||
|it|8%|91|1048|189|
|
|it|7%|87|1151|215|
|
||||||
|nl|7%|79|1060|161|
|
|nl|6%|75|1163|187|
|
||||||
|pt|6%|71|1068|180|
|
|pt|5%|66|1172|207|
|
||||||
|ru|52%|596|543|221|
|
|ru|46%|575|663|246|
|
||||||
|th|0%|0|1139|0|
|
|th|0%|0|1238|0|
|
||||||
|zh|8%|86|1053|161|
|
|zh|7%|82|1156|187|
|
||||||
|
|
||||||
**Percent translated** refers to the percent of phrases identified by the
|
**Percent translated** refers to the percent of phrases identified by the
|
||||||
language helper that have been translated. Additional phrases not identified
|
language helper that have been translated. Additional phrases not identified
|
||||||
|
|
Loading…
Reference in New Issue