Merge branch 'master' into master
commit
f22b209872
|
@ -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`
|
||||
|
|
|
@ -19,6 +19,7 @@ import { BooleanSetting } from "../session_keys";
|
|||
|
||||
describe("<LoadingPlant/>", () => {
|
||||
it("renders loading text", () => {
|
||||
mockStorj[BooleanSetting.disableAnimations] = true;
|
||||
const wrapper = shallow(<LoadingPlant />);
|
||||
expect(wrapper.find(".loading-plant").length).toEqual(0);
|
||||
expect(wrapper.find(".loading-plant-text").props().y).toEqual(150);
|
||||
|
@ -27,7 +28,7 @@ describe("<LoadingPlant/>", () => {
|
|||
});
|
||||
|
||||
it("renders loading animation", () => {
|
||||
mockStorj[BooleanSetting.plantAnimations] = true;
|
||||
mockStorj[BooleanSetting.disableAnimations] = false;
|
||||
const wrapper = shallow(<LoadingPlant />);
|
||||
expect(wrapper.find(".loading-plant")).toBeTruthy();
|
||||
const circleProps = wrapper.find(".loading-plant-circle").props();
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -9,11 +9,12 @@ interface LabsFeaturesListProps {
|
|||
export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
||||
return <div>
|
||||
{fetchLabFeatures().map((p, i) => {
|
||||
const displayValue = p.displayInvert ? !p.value : p.value;
|
||||
return <KeyValShowRow key={i}
|
||||
label={p.name}
|
||||
labelPlaceholder=""
|
||||
value={p.description}
|
||||
toggleValue={p.value ? 1 : 0}
|
||||
toggleValue={displayValue ? 1 : 0}
|
||||
valuePlaceholder=""
|
||||
onClick={() => props.onToggle(p)}
|
||||
disabled={false} />;
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 974px) {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
// Regimen Editor
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -136,6 +136,7 @@ export interface PeripheralsProps {
|
|||
export interface FarmwareProps {
|
||||
dispatch: Function;
|
||||
env: Partial<WD_ENV>;
|
||||
user_env: Record<string, string | undefined>;
|
||||
images: TaggedImage[];
|
||||
currentImage: TaggedImage | undefined;
|
||||
syncStatus: SyncStatus;
|
||||
|
|
|
@ -36,6 +36,7 @@ describe("<GardenPlant/>", () => {
|
|||
}
|
||||
|
||||
it("renders plant", () => {
|
||||
mockStorj[BooleanSetting.disableAnimations] = true;
|
||||
const wrapper = shallow(<GardenPlant {...fakeProps() } />);
|
||||
expect(wrapper.find("image").length).toEqual(1);
|
||||
expect(wrapper.find("image").props().opacity).toEqual(1);
|
||||
|
@ -46,7 +47,7 @@ describe("<GardenPlant/>", () => {
|
|||
});
|
||||
|
||||
it("renders plant animations", () => {
|
||||
mockStorj[BooleanSetting.plantAnimations] = true;
|
||||
mockStorj[BooleanSetting.disableAnimations] = false;
|
||||
const wrapper = shallow(<GardenPlant {...fakeProps() } />);
|
||||
expect(wrapper.find(".soil-cloud").length).toEqual(1);
|
||||
expect(wrapper.find(".animate").length).toEqual(1);
|
||||
|
|
|
@ -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 <g id={"plant-" + id}>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
jest.mock("../../../../session", () => {
|
||||
return {
|
||||
Session: {
|
||||
getBool: () => { return true; }
|
||||
getBool: () => { return false; }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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 <g id="hovered-plant-layer">
|
||||
{this.props.visible && hovered &&
|
||||
|
|
|
@ -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 <g id={"spread-" + id}>
|
||||
{!selected &&
|
||||
|
|
|
@ -21,7 +21,7 @@ function fakeFarmwares(): Dictionary<FarmwareManifest | undefined> {
|
|||
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<FarmwareManifest | undefined> {
|
|||
}
|
||||
|
||||
describe("<FarmwareForms/>", () => {
|
||||
it("doesn't render", () => {
|
||||
const farmwares = fakeFarmwares();
|
||||
const farmware = farmwares.farmware_0;
|
||||
if (farmware) { farmware.config = []; }
|
||||
const wrapper = mount(<FarmwareForms
|
||||
farmwares={farmwares}
|
||||
user_env={{}} />);
|
||||
expect(wrapper.text()).toEqual("");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<FarmwareForms farmwares={fakeFarmwares()} />);
|
||||
const wrapper = mount(<FarmwareForms
|
||||
farmwares={fakeFarmwares()}
|
||||
user_env={{}} />);
|
||||
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(<FarmwareForms farmwares={fakeFarmwares()} />);
|
||||
const wrapper = mount(<FarmwareForms
|
||||
farmwares={fakeFarmwares()}
|
||||
user_env={{}} />);
|
||||
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" });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ describe("<FarmwarePage />", () => {
|
|||
farmwares: {},
|
||||
syncStatus: "unknown",
|
||||
env: {},
|
||||
user_env: {},
|
||||
dispatch: jest.fn(),
|
||||
currentImage: undefined,
|
||||
images: []
|
||||
|
|
|
@ -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<FarmwareManifest | undefined>;
|
||||
user_env: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
// 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<HTMLInputElement>) {
|
||||
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<Pair>((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 <div id="farmware-forms">
|
||||
{farmwareData.map((farmware, i) => {
|
||||
return farmware ?
|
||||
<Col key={i} xs={12} sm={6}>
|
||||
<Widget>
|
||||
<Widget className={_.kebabCase(farmware.name)}>
|
||||
<WidgetHeader
|
||||
title={farmware.name}
|
||||
helpText={farmware.meta.version ? " version: "
|
||||
+ farmware.meta.version : ""}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
onClick={() => getDevice().execScript(farmware.name)}>
|
||||
className="fb-button green"
|
||||
onClick={() => run(farmware.name, farmware.config)}>
|
||||
{t("Run")}
|
||||
</button>
|
||||
</WidgetHeader>
|
||||
|
@ -47,8 +66,17 @@ export function FarmwareForms(props: FarmwareFormsProps): JSX.Element {
|
|||
<div>
|
||||
<label>Description</label>
|
||||
<p>{farmware.meta.description}</p>
|
||||
<hr />
|
||||
</div>}
|
||||
{/* TODO: Render inputs described in "farmware.config". */}
|
||||
{farmware.config.map((config) => {
|
||||
return <div key={config.name} id={config.name}>
|
||||
<label>{config.label}</label>
|
||||
<BlurableInput type="text"
|
||||
onCommit={(e) =>
|
||||
inputChange(getEnvName(farmware.name, config.name), e)}
|
||||
value={getValue(farmware.name, config)} />
|
||||
</div>;
|
||||
})}
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</Col> : <div key={i} />;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { CameraCalibration } from "./camera_calibration/camera_calibration";
|
|||
import { FarmwareProps } from "../devices/interfaces";
|
||||
import { WeedDetector } from "./weed_detector/index";
|
||||
import { envGet } from "./weed_detector/remote_env/selectors";
|
||||
// import { FarmwareForms } from "./farmware_forms";
|
||||
import { FarmwareForms } from "./farmware_forms";
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class FarmwarePage extends React.Component<FarmwareProps, {}> {
|
||||
|
@ -48,7 +48,8 @@ export class FarmwarePage extends React.Component<FarmwareProps, {}> {
|
|||
<WeedDetector {...this.props} />
|
||||
</Col>
|
||||
</Row>
|
||||
{/* <FarmwareForms farmwares={this.props.farmwares} /> */}
|
||||
<FarmwareForms farmwares={this.props.farmwares}
|
||||
user_env={this.props.user_env} />
|
||||
</Page>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ export function mapStateToProps(props: Everything): FarmwareProps {
|
|||
farmwares,
|
||||
syncStatus,
|
||||
env: prepopulateEnv(props.bot.hardware.user_env),
|
||||
user_env: props.bot.hardware.user_env,
|
||||
dispatch: props.dispatch,
|
||||
currentImage,
|
||||
images
|
||||
|
|
|
@ -13,6 +13,7 @@ describe("<WeedDetector />", () => {
|
|||
farmwares: {},
|
||||
syncStatus: "unknown",
|
||||
env: {},
|
||||
user_env: {},
|
||||
dispatch: jest.fn(),
|
||||
currentImage: undefined,
|
||||
images: []
|
||||
|
|
|
@ -84,8 +84,8 @@ export class ImageWorkspace extends React.Component<Props, {}> {
|
|||
onRelease={this.onHslChange("H")}
|
||||
lowest={RANGES.H.LOWEST}
|
||||
highest={RANGES.H.HIGHEST}
|
||||
lowValue={H_LO}
|
||||
highValue={H_HI} />
|
||||
lowValue={Math.min(H_LO, H_HI)}
|
||||
highValue={Math.max(H_LO, H_HI)} />
|
||||
<label htmlFor="saturation">{t("SATURATION")}</label>
|
||||
<WeedDetectorSlider
|
||||
onRelease={this.onHslChange("S")}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Session } from "./session";
|
|||
import { BooleanSetting } from "./session_keys";
|
||||
|
||||
export function LoadingPlant() {
|
||||
const animations = Session.getBool(BooleanSetting.plantAnimations);
|
||||
const animations = !Session.getBool(BooleanSetting.disableAnimations);
|
||||
return <div className="loading-plant-div-container">
|
||||
<svg width="300px" height="500px">
|
||||
{animations &&
|
||||
|
|
|
@ -17,7 +17,7 @@ export enum BooleanSetting {
|
|||
hideWebcamWidget = "hideWebcamWidget",
|
||||
dynamicMap = "dynamicMap",
|
||||
mapXL = "mapXL",
|
||||
plantAnimations = "plantAnimations",
|
||||
disableAnimations = "disableAnimations",
|
||||
}
|
||||
|
||||
export enum NumericSetting {
|
||||
|
|
Loading…
Reference in New Issue