diff --git a/README.md b/README.md
index 7137991a2..864dd74de 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ You will need the following:
1. A Linux or Mac based machine. We do not support windows at this time.
0. [Docker 17.06.0-ce or greater](https://docs.docker.com/engine/installation/)
- 0. [Ruby 2.4.1](http://rvm.io/rvm/install)
+ 0. [Ruby 2.4.2](http://rvm.io/rvm/install)
0. [ImageMagick](https://www.imagemagick.org/script/index.php) (`brew install imagemagick` (Mac) or `sudo apt-get install imagemagick` (Ubuntu))
0. [Node JS > v6](https://nodejs.org/en/download/)
0. [`libpq-dev` and `postgresql`](http://stackoverflow.com/questions/6040583/cant-find-the-libpq-fe-h-header-when-trying-to-install-pg-gem/6040822#6040822) and `postgresql-contrib`
diff --git a/webpack/__tests__/loading_plant_test.tsx b/webpack/__tests__/loading_plant_test.tsx
index ff4295360..da9fc76a7 100644
--- a/webpack/__tests__/loading_plant_test.tsx
+++ b/webpack/__tests__/loading_plant_test.tsx
@@ -19,6 +19,7 @@ import { BooleanSetting } from "../session_keys";
describe("", () => {
it("renders loading text", () => {
+ mockStorj[BooleanSetting.disableAnimations] = true;
const wrapper = shallow();
expect(wrapper.find(".loading-plant").length).toEqual(0);
expect(wrapper.find(".loading-plant-text").props().y).toEqual(150);
@@ -27,7 +28,7 @@ describe("", () => {
});
it("renders loading animation", () => {
- mockStorj[BooleanSetting.plantAnimations] = true;
+ mockStorj[BooleanSetting.disableAnimations] = false;
const wrapper = shallow();
expect(wrapper.find(".loading-plant")).toBeTruthy();
const circleProps = wrapper.find(".loading-plant-circle").props();
diff --git a/webpack/account/labs/labs_features_list_data.ts b/webpack/account/labs/labs_features_list_data.ts
index 42b90d2c9..fceb9480e 100644
--- a/webpack/account/labs/labs_features_list_data.ts
+++ b/webpack/account/labs/labs_features_list_data.ts
@@ -11,14 +11,16 @@ export interface LabsFeature {
storageKey: BooleanSetting;
value: boolean;
experimental?: boolean;
+ displayInvert?: boolean;
}
export const fetchLabFeatures = (): LabsFeature[] => ([
{
- name: t("Disable Web App internationalization"),
- description: t("Set Web App to English."),
+ name: t("Internationalize Web App"),
+ description: t("Turn off to set Web App to English."),
storageKey: BooleanSetting.disableI18n,
- value: false
+ value: false,
+ displayInvert: true
},
{
name: t("Confirm Sequence step deletion"),
@@ -51,9 +53,10 @@ export const fetchLabFeatures = (): LabsFeature[] => ([
},
{
name: t("Display plant animations"),
- description: trim(t(`Turn on plant animations in the Farm Designer.`)),
- storageKey: BooleanSetting.plantAnimations,
- value: true
+ description: trim(t(`Enable plant animations in the Farm Designer.`)),
+ storageKey: BooleanSetting.disableAnimations,
+ value: false,
+ displayInvert: true
}
].map(fetchRealValue));
diff --git a/webpack/account/labs/labs_features_list_ui.tsx b/webpack/account/labs/labs_features_list_ui.tsx
index dce8acea0..b2e5a4856 100644
--- a/webpack/account/labs/labs_features_list_ui.tsx
+++ b/webpack/account/labs/labs_features_list_ui.tsx
@@ -9,11 +9,12 @@ interface LabsFeaturesListProps {
export function LabsFeaturesList(props: LabsFeaturesListProps) {
return
{fetchLabFeatures().map((p, i) => {
+ const displayValue = p.displayInvert ? !p.value : p.value;
return
props.onToggle(p)}
disabled={false} />;
diff --git a/webpack/css/regimens.scss b/webpack/css/regimens.scss
index 00b9c9d8a..00767954a 100644
--- a/webpack/css/regimens.scss
+++ b/webpack/css/regimens.scss
@@ -10,6 +10,10 @@
margin-bottom: 1rem;
}
}
+ @media screen and (max-width: 974px) {
+ margin-left: 15px;
+ margin-right: 15px;
+ }
}
// Regimen Editor
diff --git a/webpack/css/sequences.scss b/webpack/css/sequences.scss
index a44162322..b5ed297cb 100644
--- a/webpack/css/sequences.scss
+++ b/webpack/css/sequences.scss
@@ -13,20 +13,34 @@
margin-top: 0.4rem;
}
@media screen and (max-width: 974px) {
+ h3, p {
+ margin-left: 15px;
+ margin-right: 15px;
+ }
h3 {
margin-bottom: 2.5rem;
}
+ .button-group {
+ margin-right: 15px;
+ }
}
}
.sequence-editor-content,
.regimen-editor-content {
margin-right: -15px;
+ @media screen and (max-width: 974px) {
+ margin-left: 15px;
+ margin-right: 0;
+ }
}
.sequence-editor-tools,
.regimen-editor-tools {
margin-right: 15px;
+ @media screen and (max-width: 974px) {
+ margin-right: 10px;
+ }
}
.sequence,
@@ -47,7 +61,7 @@
.regimen-list {
overflow-y: auto;
overflow-x: hidden;
- height: calc(100vh - 21rem);
+ max-height: calc(100vh - 21rem);
}
.step-button-cluster,
@@ -55,6 +69,20 @@
margin-right: -15px;
}
+.step-button-cluster-panel {
+ @media screen and (max-width: 974px) {
+ margin-left: 15px;
+ margin-right: 15px;
+ }
+}
+
+.step-button-cluster {
+ @media screen and (max-width: 974px) {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
+
.sequence-list-items {
margin-right: 15px;
}
@@ -64,6 +92,10 @@
padding-top: 0.4rem;
margin-bottom: 3rem;
margin-right: 5px;
+ @media screen and (max-width: 974px) {
+ margin-left: 15px;
+ margin-right: 15px;
+ }
}
.sequence-list-panel input,
diff --git a/webpack/devices/interfaces.ts b/webpack/devices/interfaces.ts
index bbdd70ca6..c81b36cd4 100644
--- a/webpack/devices/interfaces.ts
+++ b/webpack/devices/interfaces.ts
@@ -136,6 +136,7 @@ export interface PeripheralsProps {
export interface FarmwareProps {
dispatch: Function;
env: Partial;
+ user_env: Record;
images: TaggedImage[];
currentImage: TaggedImage | undefined;
syncStatus: SyncStatus;
diff --git a/webpack/farm_designer/map/__tests__/garden_plant_test.tsx b/webpack/farm_designer/map/__tests__/garden_plant_test.tsx
index 750d49294..5139b7a1c 100644
--- a/webpack/farm_designer/map/__tests__/garden_plant_test.tsx
+++ b/webpack/farm_designer/map/__tests__/garden_plant_test.tsx
@@ -36,6 +36,7 @@ describe("", () => {
}
it("renders plant", () => {
+ mockStorj[BooleanSetting.disableAnimations] = true;
const wrapper = shallow();
expect(wrapper.find("image").length).toEqual(1);
expect(wrapper.find("image").props().opacity).toEqual(1);
@@ -46,7 +47,7 @@ describe("", () => {
});
it("renders plant animations", () => {
- mockStorj[BooleanSetting.plantAnimations] = true;
+ mockStorj[BooleanSetting.disableAnimations] = false;
const wrapper = shallow();
expect(wrapper.find(".soil-cloud").length).toEqual(1);
expect(wrapper.find(".animate").length).toEqual(1);
diff --git a/webpack/farm_designer/map/garden_plant.tsx b/webpack/farm_designer/map/garden_plant.tsx
index 2298d2071..9929ef0c6 100644
--- a/webpack/farm_designer/map/garden_plant.tsx
+++ b/webpack/farm_designer/map/garden_plant.tsx
@@ -37,7 +37,7 @@ export class GardenPlant extends
const { qx, qy } = getXYFromQuadrant(round(x), round(y), quadrant, gridSize);
const alpha = dragging ? 0.4 : 1.0;
- const animate = Session.getBool(BooleanSetting.plantAnimations);
+ const animate = !Session.getBool(BooleanSetting.disableAnimations);
return
diff --git a/webpack/farm_designer/map/layers/__tests__/plant_layer_test.tsx b/webpack/farm_designer/map/layers/__tests__/plant_layer_test.tsx
index fae1eb579..af0e8dacd 100644
--- a/webpack/farm_designer/map/layers/__tests__/plant_layer_test.tsx
+++ b/webpack/farm_designer/map/layers/__tests__/plant_layer_test.tsx
@@ -1,7 +1,7 @@
jest.mock("../../../../session", () => {
return {
Session: {
- getBool: () => { return true; }
+ getBool: () => { return false; }
}
};
});
diff --git a/webpack/farm_designer/map/layers/hovered_plant_layer.tsx b/webpack/farm_designer/map/layers/hovered_plant_layer.tsx
index efe6ad937..ec2f6bf83 100644
--- a/webpack/farm_designer/map/layers/hovered_plant_layer.tsx
+++ b/webpack/farm_designer/map/layers/hovered_plant_layer.tsx
@@ -47,7 +47,7 @@ export class HoveredPlantLayer extends
const hovered = !!this.props.designer.hoveredPlant.icon;
const scaledRadius = currentPlant ? radius : radius * 1.2;
const alpha = dragging ? 0.4 : 1.0;
- const animate = Session.getBool(BooleanSetting.plantAnimations);
+ const animate = !Session.getBool(BooleanSetting.disableAnimations);
return
{this.props.visible && hovered &&
diff --git a/webpack/farm_designer/map/layers/spread_layer.tsx b/webpack/farm_designer/map/layers/spread_layer.tsx
index 870e01bd8..d105bf67b 100644
--- a/webpack/farm_designer/map/layers/spread_layer.tsx
+++ b/webpack/farm_designer/map/layers/spread_layer.tsx
@@ -83,7 +83,7 @@ export class SpreadCircle extends
const { selected, mapTransformProps } = this.props;
const { quadrant, gridSize } = mapTransformProps;
const { qx, qy } = getXYFromQuadrant(round(x), round(y), quadrant, gridSize);
- const animate = Session.getBool(BooleanSetting.plantAnimations);
+ const animate = !Session.getBool(BooleanSetting.disableAnimations);
return
{!selected &&
diff --git a/webpack/farmware/__tests__/farmware_forms_test.tsx b/webpack/farmware/__tests__/farmware_forms_test.tsx
index 1b2a0ba05..99ed646f1 100644
--- a/webpack/farmware/__tests__/farmware_forms_test.tsx
+++ b/webpack/farmware/__tests__/farmware_forms_test.tsx
@@ -21,7 +21,7 @@ function fakeFarmwares(): Dictionary {
args: ["my_farmware.fth"],
url: "https://",
path: "my_farmware",
- config: [],
+ config: [{ name: "config_1", label: "Config 1", value: "4" }],
meta: {
min_os_version_major: "3",
description: "Does things.",
@@ -35,19 +35,40 @@ function fakeFarmwares(): Dictionary {
}
describe("", () => {
+ it("doesn't render", () => {
+ const farmwares = fakeFarmwares();
+ const farmware = farmwares.farmware_0;
+ if (farmware) { farmware.config = []; }
+ const wrapper = mount();
+ expect(wrapper.text()).toEqual("");
+ });
+
it("renders", () => {
- const wrapper = mount();
+ const wrapper = mount();
expect(wrapper.text()).toContain("My Farmware");
expect(wrapper.text()).toContain("version: 0.0.0");
expect(wrapper.text()).toContain("Does things.");
+ expect(wrapper.find("label").last().text()).toContain("Config 1");
+ expect(wrapper.find("input").props().value).toEqual("4");
});
it("runs", () => {
const runFarmware = getDevice().execScript as jest.Mock<{}>;
- const wrapper = mount();
+ const wrapper = mount();
const run = wrapper.find("button").first();
run.simulate("click");
expect(run.text()).toEqual("Run");
- expect(runFarmware).toHaveBeenCalledWith("My Farmware");
+ const argsList = runFarmware.mock.calls[0];
+ expect(argsList[0]).toEqual("My Farmware");
+ const pairs = argsList[1][0];
+ expect(pairs.kind).toEqual("pair");
+ expect(pairs.args)
+ .toEqual({ "label": "my_farmware_config_1", "value": "4" });
});
});
diff --git a/webpack/farmware/__tests__/farmware_test.tsx b/webpack/farmware/__tests__/farmware_test.tsx
index 66f1e1c86..0e50ace86 100644
--- a/webpack/farmware/__tests__/farmware_test.tsx
+++ b/webpack/farmware/__tests__/farmware_test.tsx
@@ -19,6 +19,7 @@ describe("", () => {
farmwares: {},
syncStatus: "unknown",
env: {},
+ user_env: {},
dispatch: jest.fn(),
currentImage: undefined,
images: []
diff --git a/webpack/farmware/farmware_forms.tsx b/webpack/farmware/farmware_forms.tsx
index fe6b4349b..b1ad2c4aa 100644
--- a/webpack/farmware/farmware_forms.tsx
+++ b/webpack/farmware/farmware_forms.tsx
@@ -1,44 +1,63 @@
import * as React from "react";
-import { Widget, WidgetHeader, WidgetBody, Col } from "../ui/index";
+import {
+ Widget, WidgetHeader, WidgetBody, Col, BlurableInput
+} from "../ui/index";
import { t } from "i18next";
-import { FarmwareManifest, Dictionary } from "farmbot";
+import { FarmwareManifest, Dictionary, Pair, FarmwareConfig } from "farmbot";
import { betterCompact } from "../util";
import { getDevice } from "../device";
+import * as _ from "lodash";
interface FarmwareFormsProps {
farmwares: Dictionary;
+ user_env: Record;
}
-// TODO: download and parse the "manifest.json" file instead.
-const firstParty = [
- "camera-calibration",
- "historical-camera-calibration",
- "take-photo",
- "plant-detection",
- "historical-plant-detection"];
-
export function FarmwareForms(props: FarmwareFormsProps): JSX.Element {
- const { farmwares } = props;
+
+ function inputChange(key: string, e: React.SyntheticEvent) {
+ const value = e.currentTarget.value;
+ getDevice().setUserEnv({ [key]: value });
+ }
+
+ function getEnvName(farmwareName: string, configName: string) {
+ return `${_.snakeCase(farmwareName)}_${configName}`;
+ }
+
+ function getValue(farmwareName: string, currentConfig: FarmwareConfig) {
+ return (user_env[getEnvName(farmwareName, currentConfig.name)]
+ || _.toString(currentConfig.value));
+ }
+
+ function run(farmwareName: string, config: FarmwareConfig[]) {
+ const pairs = config.map((x) => {
+ const label = getEnvName(farmwareName, x.name);
+ const value = getValue(farmwareName, x);
+ return { kind: "pair", args: { value, label } };
+ });
+ getDevice().execScript(farmwareName, pairs);
+ }
+
+ const { farmwares, user_env } = props;
const farmwareData = betterCompact(Object
.keys(farmwares)
.map(x => farmwares[x]))
.map((fw) => {
- // TODO: Add optional "config" field to farmbot-js and check for it here.
- const needsWidget = !firstParty.includes(fw.name);
+ const needsWidget = fw.config && fw.config.length > 0;
return needsWidget ? fw : undefined;
});
return