improve element annotation

pull/1724/head
gabrielburnworth 2020-02-28 08:34:28 -08:00
parent cccecf58f6
commit 800625e8a1
175 changed files with 741 additions and 556 deletions

View File

@ -20,7 +20,7 @@ export class DangerousDeleteWidget extends
return <Widget> return <Widget>
<WidgetHeader title={this.props.title} /> <WidgetHeader title={this.props.title} />
<WidgetBody> <WidgetBody>
<div> <div className={"dangerous-delete-warning-messages"}>
{t(this.props.warning)} {t(this.props.warning)}
<br /><br /> <br /><br />
{t(this.props.confirmation)} {t(this.props.confirmation)}
@ -42,6 +42,7 @@ export class DangerousDeleteWidget extends
<button <button
onClick={this.onClick} onClick={this.onClick}
className="red fb-button" className="red fb-button"
title={t(this.props.title)}
type="button"> type="button">
{t(this.props.title)} {t(this.props.title)}
</button> </button>

View File

@ -7,7 +7,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
return <Widget> return <Widget>
<WidgetHeader title={t("Export Account Data")} /> <WidgetHeader title={t("Export Account Data")} />
<WidgetBody> <WidgetBody>
<div> <div className={"export-account-data-description"}>
{t(Content.EXPORT_DATA_DESC)} {t(Content.EXPORT_DATA_DESC)}
</div> </div>
<form> <form>
@ -19,6 +19,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<button className="green fb-button" type="button" <button className="green fb-button" type="button"
title={t("Export")}
onClick={props.onClick}> onClick={props.onClick}>
{t("Export")} {t("Export")}
</button> </button>

View File

@ -47,12 +47,13 @@ export class RawAccount extends React.Component<Props, State> {
(key: keyof User) => (key === "email") && this.setState({ warnThem: true }); (key: keyof User) => (key === "email") && this.setState({ warnThem: true });
onChange = (e: React.FormEvent<HTMLInputElement>) => { onChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget; const { value } = e.currentTarget;
if (isKey(name)) { const field = e.currentTarget.name;
this.tempHack(name); if (isKey(field)) {
this.props.dispatch(edit(this.props.user, { [name]: value })); this.tempHack(field);
this.props.dispatch(edit(this.props.user, { [field]: value }));
} else { } else {
throw new Error("Bad key: " + name); throw new Error("Bad key: " + field);
} }
}; };

View File

@ -11,7 +11,7 @@ interface LabsFeaturesListProps {
} }
export function LabsFeaturesList(props: LabsFeaturesListProps) { export function LabsFeaturesList(props: LabsFeaturesListProps) {
return <div> return <div className="labs-features-list">
{fetchLabFeatures(props.getConfigValue).map((feature, i) => { {fetchLabFeatures(props.getConfigValue).map((feature, i) => {
const displayValue = feature.displayInvert ? !feature.value : feature.value; const displayValue = feature.displayInvert ? !feature.value : feature.value;
return <Row key={i}> return <Row key={i}>
@ -23,6 +23,7 @@ export function LabsFeaturesList(props: LabsFeaturesListProps) {
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<ToggleButton <ToggleButton
title={t("toggle feature")}
toggleValue={displayValue ? 1 : 0} toggleValue={displayValue ? 1 : 0}
toggleAction={() => props.onToggle(feature) toggleAction={() => props.onToggle(feature)
.then(() => feature.callback && feature.callback())} .then(() => feature.callback && feature.callback())}

View File

@ -9,9 +9,8 @@ interface DataDumpExport { device?: DeviceAccountSettings; }
type Response = AxiosResponse<DataDumpExport | undefined>; type Response = AxiosResponse<DataDumpExport | undefined>;
export function generateFilename({ device }: DataDumpExport): string { export function generateFilename({ device }: DataDumpExport): string {
let name: string; const nameAndId = device ? (device.name + "_" + device.id) : "farmbot";
name = device ? (device.name + "_" + device.id) : "farmbot"; return `export_${nameAndId}.json`.toLowerCase();
return `export_${name}.json`.toLowerCase();
} }
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917 // Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917

View File

@ -3,17 +3,19 @@ import { Row, Col } from "../ui/index";
import { AxisDisplayGroupProps } from "./interfaces"; import { AxisDisplayGroupProps } from "./interfaces";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";
import { Xyz } from "farmbot";
const Axis = ({ val }: { val: number | undefined }) => <Col xs={3}> const Axis = ({ axis, val }: { val: number | undefined, axis: Xyz }) =>
<input disabled value={isNumber(val) ? val : "---"} /> <Col xs={3}>
</Col>; <input disabled name={axis} value={isNumber(val) ? val : "---"} />
</Col>;
export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => { export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => {
const { x, y, z } = position; const { x, y, z } = position;
return <Row> return <Row>
<Axis val={x} /> <Axis axis={"x"} val={x} />
<Axis val={y} /> <Axis axis={"y"} val={y} />
<Axis val={z} /> <Axis axis={"z"} val={z} />
<Col xs={3}> <Col xs={3}>
<label> <label>
{t(label)} {t(label)}

View File

@ -15,12 +15,14 @@ export function KeyValEditRow(p: Props) {
return <Row> return <Row>
<Col xs={6}> <Col xs={6}>
<input type="text" <input type="text"
name="label"
placeholder={p.labelPlaceholder} placeholder={p.labelPlaceholder}
value={p.label} value={p.label}
onChange={p.onLabelChange} /> onChange={p.onLabelChange} />
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input type={p.valueType} <input type={p.valueType}
name="value"
value={p.value} value={p.value}
placeholder={p.valuePlaceholder} placeholder={p.valuePlaceholder}
onChange={p.onValueChange} /> onChange={p.onValueChange} />

View File

@ -19,7 +19,7 @@ export interface BotPositionRowsProps {
export const BotPositionRows = (props: BotPositionRowsProps) => { export const BotPositionRows = (props: BotPositionRowsProps) => {
const { locationData, getValue, arduinoBusy } = props; const { locationData, getValue, arduinoBusy } = props;
return <div> return <div className={"bot-position-rows"}>
<Row> <Row>
<Col xs={3}> <Col xs={3}>
<label>{t("X AXIS")}</label> <label>{t("X AXIS")}</label>

View File

@ -22,7 +22,7 @@ export const JogControlsGroup = (props: JogControlsGroupProps) => {
const { const {
dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings
} = props; } = props;
return <div> return <div className={"jog-controls-group"}>
<label className="text-center"> <label className="text-center">
{t("MOVE AMOUNT (mm)")} {t("MOVE AMOUNT (mm)")}
</label> </label>

View File

@ -46,9 +46,9 @@ const getLastEntry = (): Entry | undefined => {
const findYLimit = (): number => { const findYLimit = (): number => {
const array = getArray(); const array = getArray();
const arrayAbsMax = max(array.map(entry => const arrayAbsMax = max(array.map(entry =>
max(["position", "scaled_encoders"].map((name: LocationName) => max(["position", "scaled_encoders"].map((key: LocationName) =>
max(["x", "y", "z"].map((axis: Xyz) => max(["x", "y", "z"].map((axis: Xyz) =>
Math.abs(entry.locationData[name][axis] || 0) + 1)))))); Math.abs(entry.locationData[key][axis] || 0) + 1))))));
return Math.max(ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX); return Math.max(ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX);
}; };
@ -80,19 +80,19 @@ const getPaths = (): Paths => {
const paths = newPaths(); const paths = newPaths();
if (last) { if (last) {
getReversedArray().map(entry => { getReversedArray().map(entry => {
["position", "scaled_encoders"].map((name: LocationName) => { ["position", "scaled_encoders"].map((key: LocationName) => {
["x", "y", "z"].map((axis: Xyz) => { ["x", "y", "z"].map((axis: Xyz) => {
const lastPos = last.locationData[name][axis]; const lastPos = last.locationData[key][axis];
const pos = entry.locationData[name][axis]; const pos = entry.locationData[key][axis];
if (isNumber(lastPos) && isFinite(lastPos) if (isNumber(lastPos) && isFinite(lastPos)
&& isNumber(maxY) && isNumber(pos)) { && isNumber(maxY) && isNumber(pos)) {
if (!paths[name][axis].startsWith("M")) { if (!paths[key][axis].startsWith("M")) {
const yStart = -lastPos / maxY * HEIGHT / 2; const yStart = -lastPos / maxY * HEIGHT / 2;
paths[name][axis] = `M ${MAX_X},${yStart} `; paths[key][axis] = `M ${MAX_X},${yStart} `;
} }
const x = MAX_X - (last.timestamp - entry.timestamp); const x = MAX_X - (last.timestamp - entry.timestamp);
const y = -pos / maxY * HEIGHT / 2; const y = -pos / maxY * HEIGHT / 2;
paths[name][axis] += `L ${x},${y} `; paths[key][axis] += `L ${x},${y} `;
} }
}); });
}); });
@ -154,12 +154,12 @@ const PlotLines = ({ locationData }: { locationData: BotLocationData }) => {
updateArray({ timestamp: moment().unix(), locationData }); updateArray({ timestamp: moment().unix(), locationData });
const paths = getPaths(); const paths = getPaths();
return <g id="plot_lines"> return <g id="plot_lines">
{["position", "scaled_encoders"].map((name: LocationName) => {["position", "scaled_encoders"].map((key: LocationName) =>
["x", "y", "z"].map((axis: Xyz) => ["x", "y", "z"].map((axis: Xyz) =>
<path key={name + axis} fill={"none"} <path key={key + axis} fill={"none"}
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[name]} stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[key]}
strokeLinecap={"round"} strokeLinejoin={"round"} strokeLinecap={"round"} strokeLinejoin={"round"}
d={paths[name][axis]} />))} d={paths[key][axis]} />))}
</g>; </g>;
}; };

View File

@ -56,7 +56,7 @@ export const MoveWidgetSettingsMenu = (
setting={BooleanSetting.home_button_homing} /> setting={BooleanSetting.home_button_homing} />
{DevSettings.futureFeaturesEnabled() && {DevSettings.futureFeaturesEnabled() &&
<div> <div className={"motor-position-plot-setting-row"}>
<p>{t("Motor position plot")}</p> <p>{t("Motor position plot")}</p>
<Setting <Setting
label={t("show")} label={t("show")}

View File

@ -1,6 +1,7 @@
import * as React from "react"; import * as React from "react";
import { StepSizeSelectorProps } from "./interfaces"; import { StepSizeSelectorProps } from "./interfaces";
import { first, last } from "lodash"; import { first, last } from "lodash";
import { t } from "../../i18next_wrapper";
export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}> { export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}> {
cssForIndex(num: number) { cssForIndex(num: number) {
@ -22,10 +23,10 @@ export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}>
return <div className="move-amount-wrapper"> return <div className="move-amount-wrapper">
{ {
this.props.choices.map( this.props.choices.map(
(item: number, inx: number) => <button (item: number, inx: number) => <button key={inx}
title={t("{{ amount }}mm", { amount: item })}
className={this.cssForIndex(item)} className={this.cssForIndex(item)}
onClick={() => this.props.selector(item)} onClick={() => this.props.selector(item)}>
key={inx}>
{item} {item}
</button> </button>
) )

View File

@ -86,15 +86,17 @@ export class Peripherals
render() { render() {
const { isEditing } = this.state; const { isEditing } = this.state;
const status = getArrayStatus(this.props.peripherals); const status = getArrayStatus(this.props.peripherals);
const editButtonText = isEditing
? t("Back")
: t("Edit");
return <Widget className="peripherals-widget"> return <Widget className="peripherals-widget">
<WidgetHeader title={t("Peripherals")} helpText={ToolTips.PERIPHERALS}> <WidgetHeader title={t("Peripherals")} helpText={ToolTips.PERIPHERALS}>
<button <button
className="fb-button gray" className="fb-button gray"
onClick={this.toggle} onClick={this.toggle}
title={editButtonText}
disabled={!!status && isEditing}> disabled={!!status && isEditing}>
{!isEditing && t("Edit")} {editButtonText}
{isEditing && t("Back")}
</button> </button>
<SaveBtn <SaveBtn
hidden={!isEditing} hidden={!isEditing}
@ -104,6 +106,7 @@ export class Peripherals
hidden={!isEditing} hidden={!isEditing}
className="fb-button green" className="fb-button green"
type="button" type="button"
title={t("add peripheral")}
onClick={() => this.newPeripheral()}> onClick={() => this.newPeripheral()}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>
@ -111,6 +114,7 @@ export class Peripherals
hidden={!isEditing || this.props.firmwareHardware == "none"} hidden={!isEditing || this.props.firmwareHardware == "none"}
className="fb-button green" className="fb-button green"
type="button" type="button"
title={t("add stock peripherals")}
onClick={() => this.stockPeripherals.map(p => onClick={() => this.stockPeripherals.map(p =>
this.newPeripheral(p.pin, p.label))}> this.newPeripheral(p.pin, p.label))}>
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} /> <i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />

View File

@ -22,6 +22,7 @@ interface NameInputBoxProps {
export const NameInputBox = (props: NameInputBoxProps) => export const NameInputBox = (props: NameInputBoxProps) =>
<input type="text" <input type="text"
name="name"
placeholder={t("Name")} placeholder={t("Name")}
value={props.value} value={props.value}
onChange={e => props.dispatch(edit(props.resource, { onChange={e => props.dispatch(edit(props.resource, {

View File

@ -25,11 +25,11 @@ describe("filterSensorReadings()", () => {
}); });
const createLocatedReading = (locations: Record<Xyz, number | undefined>[]) => const createLocatedReading = (locations: Record<Xyz, number | undefined>[]) =>
locations.map(location => { locations.map(xyzLocation => {
const sr = fakeSensorReading(); const sr = fakeSensorReading();
sr.body.x = location.x; sr.body.x = xyzLocation.x;
sr.body.y = location.y; sr.body.y = xyzLocation.y;
sr.body.z = location.z; sr.body.z = xyzLocation.z;
sr.body.created_at = defaultCreatedAt; sr.body.created_at = defaultCreatedAt;
return sr; return sr;
}); });
@ -38,7 +38,7 @@ describe("filterSensorReadings()", () => {
sensor: undefined, sensor: undefined,
timePeriod: 3600 * 24 * 7, timePeriod: 3600 * 24 * 7,
endDate: 1515715140, endDate: 1515715140,
location: undefined, xyzLocation: undefined,
showPreviousPeriod: false, showPreviousPeriod: false,
deviation: 0, deviation: 0,
hovered: undefined, hovered: undefined,
@ -87,7 +87,7 @@ describe("filterSensorReadings()", () => {
const locations = [expected, { x: 0, y: 0, z: 0 }]; const locations = [expected, { x: 0, y: 0, z: 0 }];
const filters = sensorReadingsState(); const filters = sensorReadingsState();
filters.endDate = moment(defaultCreatedAt).unix() + 1; filters.endDate = moment(defaultCreatedAt).unix() + 1;
filters.location = expected; filters.xyzLocation = expected;
const result = filterSensorReadings( const result = filterSensorReadings(
createLocatedReading(locations), filters)("current"); createLocatedReading(locations), filters)("current");
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
@ -101,7 +101,7 @@ describe("filterSensorReadings()", () => {
const locations = [{ x: 1, y: 2, z: 3 }, { x: 0, y: 0, z: 0 }]; const locations = [{ x: 1, y: 2, z: 3 }, { x: 0, y: 0, z: 0 }];
const filters = sensorReadingsState(); const filters = sensorReadingsState();
filters.endDate = moment(defaultCreatedAt).unix() + 1; filters.endDate = moment(defaultCreatedAt).unix() + 1;
filters.location = expected; filters.xyzLocation = expected;
filters.deviation = 100; filters.deviation = 100;
const result = filterSensorReadings( const result = filterSensorReadings(
createLocatedReading(locations), filters)("current"); createLocatedReading(locations), filters)("current");

View File

@ -6,7 +6,7 @@ import { LocationSelectionProps } from "../interfaces";
describe("<LocationSelection />", () => { describe("<LocationSelection />", () => {
function fakeProps(): LocationSelectionProps { function fakeProps(): LocationSelectionProps {
return { return {
location: undefined, xyzLocation: undefined,
deviation: 0, deviation: 0,
setLocation: jest.fn(), setLocation: jest.fn(),
setDeviation: jest.fn(), setDeviation: jest.fn(),
@ -22,7 +22,7 @@ describe("<LocationSelection />", () => {
it("changes location", () => { it("changes location", () => {
const p = fakeProps(); const p = fakeProps();
p.location = { x: 10, y: 20, z: 30 }; p.xyzLocation = { x: 10, y: 20, z: 30 };
const wrapper = mount(<LocationSelection {...p} />); const wrapper = mount(<LocationSelection {...p} />);
wrapper.find("input").first().simulate("submit"); wrapper.find("input").first().simulate("submit");
expect(p.setLocation).toHaveBeenCalledWith({ x: 10, y: 20, z: 30 }); expect(p.setLocation).toHaveBeenCalledWith({ x: 10, y: 20, z: 30 });
@ -30,7 +30,7 @@ describe("<LocationSelection />", () => {
it("changes location: undefined", () => { it("changes location: undefined", () => {
const p = fakeProps(); const p = fakeProps();
p.location = undefined; p.xyzLocation = undefined;
const wrapper = mount(<LocationSelection {...p} />); const wrapper = mount(<LocationSelection {...p} />);
wrapper.find("input").first().simulate("submit"); wrapper.find("input").first().simulate("submit");
expect(p.setLocation).toHaveBeenCalledWith({ x: undefined }); expect(p.setLocation).toHaveBeenCalledWith({ x: undefined });
@ -47,7 +47,7 @@ describe("<LocationSelection />", () => {
describe("<LocationDisplay />", () => { describe("<LocationDisplay />", () => {
it("renders location ranges", () => { it("renders location ranges", () => {
const p = { location: { x: 10, y: 20, z: 30 }, deviation: 2 }; const p = { xyzLocation: { x: 10, y: 20, z: 30 }, deviation: 2 };
const wrapper = mount(<LocationDisplay {...p} />); const wrapper = mount(<LocationDisplay {...p} />);
const txt = wrapper.text().toLowerCase(); const txt = wrapper.text().toLowerCase();
["x", "y", "z", "812", "1822", "2832"] ["x", "y", "z", "812", "1822", "2832"]

View File

@ -43,9 +43,9 @@ describe("<SensorReadings />", () => {
it("sets location", () => { it("sets location", () => {
const expectedLocation = { x: 1, y: 2, z: undefined }; const expectedLocation = { x: 1, y: 2, z: undefined };
const wrapper = mount<SensorReadings>(<SensorReadings {...fakeProps()} />); const wrapper = mount<SensorReadings>(<SensorReadings {...fakeProps()} />);
expect(wrapper.instance().state.location).toEqual(undefined); expect(wrapper.instance().state.xyzLocation).toEqual(undefined);
wrapper.instance().setLocation(expectedLocation); wrapper.instance().setLocation(expectedLocation);
expect(wrapper.instance().state.location).toEqual(expectedLocation); expect(wrapper.instance().state.xyzLocation).toEqual(expectedLocation);
}); });
it("sets end date", () => { it("sets end date", () => {
@ -87,9 +87,9 @@ describe("<SensorReadings />", () => {
const p = fakeProps(); const p = fakeProps();
p.sensors = [s]; p.sensors = [s];
const wrapper = mount<SensorReadings>(<SensorReadings {...p} />); const wrapper = mount<SensorReadings>(<SensorReadings {...p} />);
wrapper.setState({ location: { x: 1, y: 2, z: 3 }, sensor: s }); wrapper.setState({ xyzLocation: { x: 1, y: 2, z: 3 }, sensor: s });
wrapper.instance().clearFilters(); wrapper.instance().clearFilters();
expect(wrapper.instance().state.location).toEqual(undefined); expect(wrapper.instance().state.xyzLocation).toEqual(undefined);
expect(wrapper.instance().state.sensor).toEqual(undefined); expect(wrapper.instance().state.sensor).toEqual(undefined);
}); });
}); });

View File

@ -20,7 +20,7 @@ export const filterSensorReadings =
sensorReadingsState: SensorReadingsState) => sensorReadingsState: SensorReadingsState) =>
(period: "current" | "previous"): TaggedSensorReading[] => { (period: "current" | "previous"): TaggedSensorReading[] => {
const { const {
sensor, endDate, timePeriod, showPreviousPeriod, location, deviation sensor, endDate, timePeriod, showPreviousPeriod, xyzLocation, deviation
} = sensorReadingsState; } = sensorReadingsState;
// Don't return sensor readings from the previous period if not desired. // Don't return sensor readings from the previous period if not desired.
@ -41,11 +41,11 @@ export const filterSensorReadings =
.filter(x => sensor ? x.body.pin === sensor.body.pin : true) .filter(x => sensor ? x.body.pin === sensor.body.pin : true)
// Filter by location // Filter by location
.filter(sensorReading => { .filter(sensorReading => {
if (location) { if (xyzLocation) {
const { body } = sensorReading; const { body } = sensorReading;
return every(["x", "y", "z"].map((axis: Xyz) => { return every(["x", "y", "z"].map((axis: Xyz) => {
const a = body[axis]; const a = body[axis];
const input = location[axis]; const input = xyzLocation[axis];
return isNumber(a) && isNumber(input) return isNumber(a) && isNumber(input)
? (a <= input + deviation) && (a >= input - deviation) ? (a <= input + deviation) && (a >= input - deviation)
: true; : true;

View File

@ -15,7 +15,7 @@ export interface SensorReadingsState {
/** seconds */ /** seconds */
endDate: number; endDate: number;
/** location filter setting */ /** location filter setting */
location: AxisInputBoxGroupState | undefined; xyzLocation: AxisInputBoxGroupState | undefined;
/** Show the previous time period in addition to the current time period. */ /** Show the previous time period in addition to the current time period. */
showPreviousPeriod: boolean; showPreviousPeriod: boolean;
/** mm */ /** mm */
@ -50,11 +50,11 @@ export interface SensorSelectionProps {
} }
export interface LocationSelectionProps { export interface LocationSelectionProps {
location: AxisInputBoxGroupState | undefined; xyzLocation: AxisInputBoxGroupState | undefined;
/** mm */ /** mm */
deviation: number; deviation: number;
setDeviation: (deviation: number) => void; setDeviation: (deviation: number) => void;
setLocation: (location: AxisInputBoxGroupState | undefined) => void; setLocation: (xyzLocation: AxisInputBoxGroupState | undefined) => void;
} }
export interface TimePeriodSelectionProps { export interface TimePeriodSelectionProps {

View File

@ -10,7 +10,7 @@ import { t } from "../../i18next_wrapper";
/** Select a location filter for sensor readings. */ /** Select a location filter for sensor readings. */
export const LocationSelection = export const LocationSelection =
({ location, deviation, setDeviation, setLocation }: LocationSelectionProps) => ({ xyzLocation, deviation, setDeviation, setLocation }: LocationSelectionProps) =>
<div className="sensor-history-location-selection"> <div className="sensor-history-location-selection">
<Row> <Row>
{["x", "y", "z"].map(axis => {["x", "y", "z"].map(axis =>
@ -26,9 +26,9 @@ export const LocationSelection =
<AxisInputBox <AxisInputBox
key={axis} key={axis}
axis={axis} axis={axis}
value={location ? location[axis] : undefined} value={xyzLocation ? xyzLocation[axis] : undefined}
onChange={(a: Xyz, v) => { onChange={(a: Xyz, v) => {
const newLocation = (location || {}); const newLocation = (xyzLocation || {});
newLocation[a] = v; newLocation[a] = v;
setLocation(newLocation); setLocation(newLocation);
}} />)} }} />)}
@ -42,15 +42,15 @@ export const LocationSelection =
</div>; </div>;
/** Display sensor reading location filter settings. */ /** Display sensor reading location filter settings. */
export const LocationDisplay = ({ location, deviation }: { export const LocationDisplay = ({ xyzLocation, deviation }: {
location: AxisInputBoxGroupState | undefined, xyzLocation: AxisInputBoxGroupState | undefined,
deviation: number deviation: number
}) => { }) => {
return <div className="location"> return <div className="location">
{["x", "y", "z"].map((axis: Xyz) => { {["x", "y", "z"].map((axis: Xyz) => {
const axisString = () => { const axisString = () => {
if (location) { if (xyzLocation) {
const axisValue = location[axis]; const axisValue = xyzLocation[axis];
if (isNumber(axisValue)) { if (isNumber(axisValue)) {
return deviation return deviation
? `${axisValue - deviation}${axisValue + deviation}` ? `${axisValue - deviation}${axisValue + deviation}`

View File

@ -20,7 +20,7 @@ export class SensorReadings
sensor: undefined, sensor: undefined,
timePeriod: 3600 * 24, timePeriod: 3600 * 24,
endDate: getEndDate(this.props.sensorReadings), endDate: getEndDate(this.props.sensorReadings),
location: undefined, xyzLocation: undefined,
showPreviousPeriod: false, showPreviousPeriod: false,
deviation: 0, deviation: 0,
hovered: undefined, hovered: undefined,
@ -32,15 +32,15 @@ export class SensorReadings
setSensor = (sensor: TaggedSensor | undefined) => this.setState({ sensor }); setSensor = (sensor: TaggedSensor | undefined) => this.setState({ sensor });
setEndDate = (endDate: number) => this.setState({ endDate }); setEndDate = (endDate: number) => this.setState({ endDate });
setTimePeriod = (timePeriod: number) => this.setState({ timePeriod }); setTimePeriod = (timePeriod: number) => this.setState({ timePeriod });
setLocation = (location: AxisInputBoxGroupState | undefined) => setLocation = (xyzLocation: AxisInputBoxGroupState | undefined) =>
this.setState({ location }); this.setState({ xyzLocation });
setDeviation = (deviation: number) => this.setState({ deviation }); setDeviation = (deviation: number) => this.setState({ deviation });
hover = (hovered: string | undefined) => this.setState({ hovered }); hover = (hovered: string | undefined) => this.setState({ hovered });
clearFilters = () => this.setState({ clearFilters = () => this.setState({
sensor: undefined, sensor: undefined,
timePeriod: 3600 * 24, timePeriod: 3600 * 24,
endDate: getEndDate(this.props.sensorReadings), endDate: getEndDate(this.props.sensorReadings),
location: undefined, xyzLocation: undefined,
showPreviousPeriod: false, showPreviousPeriod: false,
deviation: 0, deviation: 0,
}); });
@ -55,7 +55,9 @@ export class SensorReadings
<WidgetHeader <WidgetHeader
title={t("Sensor History")} title={t("Sensor History")}
helpText={ToolTips.SENSOR_HISTORY}> helpText={ToolTips.SENSOR_HISTORY}>
<button className="fb-button gray" onClick={this.clearFilters}> <button className="fb-button gray"
title={t("clear filters")}
onClick={this.clearFilters}>
{t("clear filters")} {t("clear filters")}
</button> </button>
</WidgetHeader> </WidgetHeader>
@ -72,7 +74,7 @@ export class SensorReadings
setPeriod={this.setTimePeriod} setPeriod={this.setTimePeriod}
togglePrevious={this.togglePrevious} /> togglePrevious={this.togglePrevious} />
<LocationSelection <LocationSelection
location={this.state.location} xyzLocation={this.state.xyzLocation}
deviation={this.state.deviation} deviation={this.state.deviation}
setLocation={this.setLocation} setLocation={this.setLocation}
setDeviation={this.setDeviation} /> setDeviation={this.setDeviation} />
@ -100,7 +102,7 @@ export class SensorReadings
timePeriod={this.state.timePeriod} timePeriod={this.state.timePeriod}
timeSettings={this.props.timeSettings} /> timeSettings={this.props.timeSettings} />
<LocationDisplay <LocationDisplay
location={this.state.location} xyzLocation={this.state.xyzLocation}
deviation={this.state.deviation} /> deviation={this.state.deviation} />
</div> </div>
</WidgetFooter> </WidgetFooter>

View File

@ -74,6 +74,7 @@ export const TimePeriodSelection = (props: TimePeriodSelectionProps) => {
<Col xs={ColWidth.showPrevious}> <Col xs={ColWidth.showPrevious}>
<div className="fb-checkbox large"> <div className="fb-checkbox large">
<input type="checkbox" <input type="checkbox"
name="previous"
checked={showPreviousPeriod} checked={showPreviousPeriod}
onChange={togglePrevious} /> onChange={togglePrevious} />
</div> </div>

View File

@ -58,15 +58,17 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
render() { render() {
const { isEditing } = this.state; const { isEditing } = this.state;
const status = getArrayStatus(this.props.sensors); const status = getArrayStatus(this.props.sensors);
const editButtonText = isEditing
? t("Back")
: t("Edit");
return <Widget className="sensors-widget"> return <Widget className="sensors-widget">
<WidgetHeader title={t("Sensors")} helpText={ToolTips.SENSORS}> <WidgetHeader title={t("Sensors")} helpText={ToolTips.SENSORS}>
<button <button
className="fb-button gray" className="fb-button gray"
onClick={this.toggle} onClick={this.toggle}
title={editButtonText}
disabled={!!status && isEditing}> disabled={!!status && isEditing}>
{!isEditing && t("Edit")} {editButtonText}
{isEditing && t("Back")}
</button> </button>
<SaveBtn <SaveBtn
hidden={!isEditing} hidden={!isEditing}
@ -76,6 +78,7 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
hidden={!isEditing} hidden={!isEditing}
className="fb-button green" className="fb-button green"
type="button" type="button"
title={t("add sensors")}
onClick={() => this.newSensor()}> onClick={() => this.newSensor()}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>
@ -83,6 +86,7 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
hidden={!isEditing || this.props.firmwareHardware == "none"} hidden={!isEditing || this.props.firmwareHardware == "none"}
className="fb-button green" className="fb-button green"
type="button" type="button"
title={t("add stock sensors")}
onClick={this.stockSensors}> onClick={this.stockSensors}>
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} /> <i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
{t("Stock sensors")} {t("Stock sensors")}

View File

@ -40,6 +40,6 @@ describe("<IndexIndicator/>", () => {
it("doesn't render index indicator", () => { it("doesn't render index indicator", () => {
const wrapper = mount(<IndexIndicator i={0} total={1} />); const wrapper = mount(<IndexIndicator i={0} total={1} />);
expect(wrapper.html()).toEqual("<div></div>"); expect(wrapper.html()).toEqual("<div class=\"no-index-indicator\"></div>");
}); });
}); });

View File

@ -32,16 +32,19 @@ export function Edit(props: WebcamPanelProps) {
<button <button
className="fb-button gray" className="fb-button gray"
disabled={unsaved.length > 0} disabled={unsaved.length > 0}
title={t("Back")}
onClick={props.onToggle}> onClick={props.onToggle}>
{t("Back")} {t("Back")}
</button> </button>
<button <button
className="fb-button green" className="fb-button green"
title={t("Save")}
onClick={() => { unsaved.map(x => props.save(x)); }}> onClick={() => { unsaved.map(x => props.save(x)); }}>
{t("Save")}{unsaved.length > 0 ? "*" : ""} {t("Save")}{unsaved.length > 0 ? "*" : ""}
</button> </button>
<button <button
className="fb-button green" className="fb-button green"
title={t("Add webcam")}
onClick={props.init}> onClick={props.init}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>

View File

@ -25,7 +25,7 @@ export function IndexIndicator(props: { i: number, total: number }): JSX.Element
style={{ style={{
width: `${percentWidth}%`, width: `${percentWidth}%`,
left: `calc(-10px + ${props.i} * ${percentWidth}%)` left: `calc(-10px + ${props.i} * ${percentWidth}%)`
}} /> : <div />; }} /> : <div className={"no-index-indicator"} />;
} }
export class Show extends React.Component<WebcamPanelProps, State> { export class Show extends React.Component<WebcamPanelProps, State> {
@ -57,6 +57,7 @@ export class Show extends React.Component<WebcamPanelProps, State> {
<WidgetHeader title={title} helpText={ToolTips.WEBCAM}> <WidgetHeader title={title} helpText={ToolTips.WEBCAM}>
<button <button
className="fb-button gray" className="fb-button gray"
title={t("Edit")}
onClick={props.onToggle}> onClick={props.onToggle}>
{t("Edit")} {t("Edit")}
</button> </button>
@ -74,6 +75,7 @@ export class Show extends React.Component<WebcamPanelProps, State> {
onClick={() => flipper.down((_, current) => this.setState({ current }))} onClick={() => flipper.down((_, current) => this.setState({ current }))}
hidden={feeds.length < 2} hidden={feeds.length < 2}
disabled={false} disabled={false}
title={t("Previous image")}
className="image-flipper-left fb-button"> className="image-flipper-left fb-button">
{t("Prev")} {t("Prev")}
</button> </button>
@ -81,6 +83,7 @@ export class Show extends React.Component<WebcamPanelProps, State> {
onClick={() => flipper.up((_, current) => this.setState({ current }))} onClick={() => flipper.up((_, current) => this.setState({ current }))}
hidden={feeds.length < 2} hidden={feeds.length < 2}
disabled={false} disabled={false}
title={t("Next image")}
className="image-flipper-right fb-button"> className="image-flipper-right fb-button">
{t("Next")} {t("Next")}
</button> </button>

View File

@ -945,7 +945,9 @@
margin: 0; margin: 0;
} }
} }
.clear-button { .preview-button,
.cancel-button,
.save-button {
text-transform: uppercase; text-transform: uppercase;
font-size: 1rem; font-size: 1rem;
border: 1px solid; border: 1px solid;

View File

@ -3,6 +3,7 @@ import React from "react";
import { uuid } from "farmbot"; import { uuid } from "farmbot";
import axios from "axios"; import axios from "axios";
import { ExternalUrl } from "../external_urls"; import { ExternalUrl } from "../external_urls";
import { t } from "../i18next_wrapper";
interface State { interface State {
error: Error | undefined; error: Error | undefined;
@ -23,7 +24,7 @@ export const WAITING_ON_API = "Planting your demo garden...";
// APPLICATION CODE ============================== // APPLICATION CODE ==============================
export class DemoIframe extends React.Component<{}, State> { export class DemoIframe extends React.Component<{}, State> {
state: State = state: State =
{ error: undefined, stage: "DEMO THE APP" }; { error: undefined, stage: t("DEMO THE APP") };
setError = (error?: Error) => this.setState({ error }); setError = (error?: Error) => this.setState({ error });
@ -63,7 +64,9 @@ export class DemoIframe extends React.Component<{}, State> {
<source src={ExternalUrl.Video.desktop} type="video/mp4" /> <source src={ExternalUrl.Video.desktop} type="video/mp4" />
</video> </video>
<img className="demo-phone" src={ExternalUrl.Video.mobile} /> <img className="demo-phone" src={ExternalUrl.Video.mobile} />
<button className="demo-button" onClick={this.requestAccount}> <button className="demo-button"
title={t("demo the app")}
onClick={this.requestAccount}>
{this.state.stage} {this.state.stage}
</button> </button>
</div>; </div>;

View File

@ -285,13 +285,13 @@ export function MCUFactoryReset() {
/** Toggle a firmware setting. */ /** Toggle a firmware setting. */
export function settingToggle( export function settingToggle(
name: ConfigKey, key: ConfigKey,
sourceFwConfig: SourceFwConfig, sourceFwConfig: SourceFwConfig,
displayAlert?: string | undefined displayAlert?: string | undefined
) { ) {
return function (dispatch: Function, getState: () => Everything) { return function (dispatch: Function, getState: () => Everything) {
if (displayAlert) { alert(trim(displayAlert)); } if (displayAlert) { alert(trim(displayAlert)); }
const update = { [name]: (sourceFwConfig(name).value === 0) ? ON : OFF }; const update = { [key]: (sourceFwConfig(key).value === 0) ? ON : OFF };
const firmwareConfig = getFirmwareConfig(getState().resources.index); const firmwareConfig = getFirmwareConfig(getState().resources.index);
const toggleFirmwareConfig = (fwConfig: TaggedFirmwareConfig) => { const toggleFirmwareConfig = (fwConfig: TaggedFirmwareConfig) => {
dispatch(edit(fwConfig, update)); dispatch(edit(fwConfig, update));

View File

@ -13,7 +13,7 @@ import {
describe("<PinGuardMCUInputGroup/>", () => { describe("<PinGuardMCUInputGroup/>", () => {
const fakeProps = (): PinGuardMCUInputGroupProps => { const fakeProps = (): PinGuardMCUInputGroupProps => {
return { return {
name: "Pin Guard 1", label: "Pin Guard 1",
pinNumKey: "pin_guard_1_pin_nr", pinNumKey: "pin_guard_1_pin_nr",
timeoutKey: "pin_guard_1_time_out", timeoutKey: "pin_guard_1_time_out",
activeStateKey: "pin_guard_1_active_state", activeStateKey: "pin_guard_1_active_state",

View File

@ -17,7 +17,7 @@ import { updateMCU } from "../../actions";
describe("<PinNumberDropdown />", () => { describe("<PinNumberDropdown />", () => {
const fakeProps = const fakeProps =
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({ (firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
name: "Pin Guard 1", label: "Pin Guard 1",
pinNumKey: "pin_guard_1_pin_nr", pinNumKey: "pin_guard_1_pin_nr",
timeoutKey: "pin_guard_1_time_out", timeoutKey: "pin_guard_1_time_out",
activeStateKey: "pin_guard_1_active_state", activeStateKey: "pin_guard_1_active_state",

View File

@ -110,11 +110,9 @@ export class FarmbotOsSettings
<div className="note"> <div className="note">
{this.maybeWarnTz()} {this.maybeWarnTz()}
</div> </div>
<div> <TimezoneSelector
<TimezoneSelector currentTimezone={this.props.deviceAccount.body.timezone}
currentTimezone={this.props.deviceAccount.body.timezone} onUpdate={this.handleTimezone} />
onUpdate={this.handleTimezone} />
</div>
</Col> </Col>
</Highlight> </Highlight>
</Row> </Row>

View File

@ -56,14 +56,12 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
</label> </label>
</Col> </Col>
<Col xs={ColWidth.description}> <Col xs={ColWidth.description}>
<div> <FBSelect
<FBSelect key={this.apiValue}
key={this.apiValue} extraClass={this.state.sending ? "dim" : ""}
extraClass={this.state.sending ? "dim" : ""} list={getFirmwareChoices()}
list={getFirmwareChoices()} selectedItem={this.selectedBoard}
selectedItem={this.selectedBoard} onChange={this.sendOffConfig} />
onChange={this.sendOffConfig} />
</div>
</Col> </Col>
<Col xs={ColWidth.button}> <Col xs={ColWidth.button}>
<FirmwareHardwareStatus <FirmwareHardwareStatus

View File

@ -92,14 +92,12 @@ export class CameraSelection
</label> </label>
</Col> </Col>
<Col xs={ColWidth.description}> <Col xs={ColWidth.description}>
<div> <FBSelect
<FBSelect allowEmpty={false}
allowEmpty={false} list={CAMERA_CHOICES()}
list={CAMERA_CHOICES()} selectedItem={this.selectedCamera()}
selectedItem={this.selectedCamera()} onChange={this.sendOffConfig}
onChange={this.sendOffConfig} extraClass={this.props.botOnline ? "" : "disabled"} />
extraClass={this.props.botOnline ? "" : "disabled"} />
</div>
</Col> </Col>
</Highlight> </Highlight>
</Row>; </Row>;

View File

@ -90,6 +90,7 @@ export class ChangeOwnershipForm
<Row> <Row>
<button <button
className={"fb-button gray"} className={"fb-button gray"}
title={t("submit")}
onClick={() => submitOwnershipChange(this.state)}> onClick={() => submitOwnershipChange(this.state)}>
{t("submit")} {t("submit")}
</button> </button>

View File

@ -4,16 +4,16 @@ 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 { FactoryResetRowsProps } 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"; import { Highlight } from "../maybe_highlight";
export function FactoryResetRow(props: FactoryResetRowProps) { export function FactoryResetRows(props: FactoryResetRowsProps) {
const { dispatch, sourceFbosConfig, botOnline } = props; const { dispatch, sourceFbosConfig, botOnline } = props;
const disableFactoryReset = sourceFbosConfig("disable_factory_reset"); const disableFactoryReset = sourceFbosConfig("disable_factory_reset");
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {}; const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
return <div> return <div className={"factory-reset-options"}>
<Row> <Row>
<Highlight settingName={DeviceSetting.factoryReset}> <Highlight settingName={DeviceSetting.factoryReset}>
<Col xs={ColWidth.label}> <Col xs={ColWidth.label}>
@ -31,6 +31,7 @@ export function FactoryResetRow(props: FactoryResetRowProps) {
className="fb-button red" className="fb-button red"
type="button" type="button"
onClick={factoryReset} onClick={factoryReset}
title={t("FACTORY RESET")}
disabled={!botOnline}> disabled={!botOnline}>
{t("FACTORY RESET")} {t("FACTORY RESET")}
</button> </button>

View File

@ -32,6 +32,7 @@ export const FbosButtonRow = (props: FbosButtonRowProps) => {
className={`fb-button ${props.color}`} className={`fb-button ${props.color}`}
type="button" type="button"
onClick={props.action} onClick={props.action}
title={t(props.buttonText)}
disabled={!props.botOnline}> disabled={!props.botOnline}>
{t(props.buttonText)} {t(props.buttonText)}
</button> </button>

View File

@ -267,7 +267,7 @@ export function FbosDetails(props: FbosDetailsProps) {
const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---"; const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---";
const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit; const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit;
return <div> return <div className={"farmbot-os-details"}>
<LastSeen <LastSeen
dispatch={props.dispatch} dispatch={props.dispatch}
botToMqttLastSeen={props.botToMqttLastSeen} botToMqttLastSeen={props.botToMqttLastSeen}

View File

@ -49,6 +49,7 @@ export const FlashFirmwareBtn = (props: FlashFirmwareBtnProps) => {
const { apiFirmwareValue } = props; const { apiFirmwareValue } = props;
return <button className="fb-button yellow" return <button className="fb-button yellow"
disabled={!apiFirmwareValue || !props.botOnline} disabled={!apiFirmwareValue || !props.botOnline}
title={t("flash firmware")}
onClick={() => isFwHardwareValue(apiFirmwareValue) && onClick={() => isFwHardwareValue(apiFirmwareValue) &&
flashFirmware(apiFirmwareValue)}> flashFirmware(apiFirmwareValue)}>
{t("flash firmware")} {t("flash firmware")}

View File

@ -59,7 +59,7 @@ export interface PowerAndResetProps {
botOnline: boolean; botOnline: boolean;
} }
export interface FactoryResetRowProps { export interface FactoryResetRowsProps {
dispatch: Function; dispatch: Function;
sourceFbosConfig: SourceFbosConfig; sourceFbosConfig: SourceFbosConfig;
botOnline: boolean; botOnline: boolean;

View File

@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react";
import { Header } from "../hardware_settings/header"; import { Header } from "../hardware_settings/header";
import { Collapse, Popover, Position } from "@blueprintjs/core"; import { Collapse, Popover, Position } from "@blueprintjs/core";
import { FactoryResetRow } from "./factory_reset_row"; import { FactoryResetRows } 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";
@ -42,7 +42,7 @@ export function PowerAndReset(props: PowerAndResetProps) {
buttonText={t("RESTART")} buttonText={t("RESTART")}
color={"yellow"} color={"yellow"}
action={restartFirmware} /> action={restartFirmware} />
<FactoryResetRow <FactoryResetRows
dispatch={dispatch} dispatch={dispatch}
sourceFbosConfig={sourceFbosConfig} sourceFbosConfig={sourceFbosConfig}
botOnline={botOnline} /> botOnline={botOnline} />

View File

@ -45,11 +45,13 @@ export class HardwareSettings extends
<WidgetBody> <WidgetBody>
<button <button
className={"fb-button gray no-float"} className={"fb-button gray no-float"}
title={t("Expand All")}
onClick={() => dispatch(bulkToggleControlPanel(true))}> onClick={() => dispatch(bulkToggleControlPanel(true))}>
{t("Expand All")} {t("Expand All")}
</button> </button>
<button <button
className={"fb-button gray no-float"} className={"fb-button gray no-float"}
title={t("Collapse All")}
onClick={() => dispatch(bulkToggleControlPanel(false))}> onClick={() => dispatch(bulkToggleControlPanel(false))}>
{t("Collapse All")} {t("Collapse All")}
</button> </button>

View File

@ -27,6 +27,7 @@ export function CalibrationRow(props: CalibrationRowProps) {
return <Col xs={2} key={axis} className={"centered-button-div"}> return <Col xs={2} key={axis} className={"centered-button-div"}>
<LockableButton <LockableButton
disabled={hardwareDisabled || botDisconnected} disabled={hardwareDisabled || botDisconnected}
title={t(props.axisTitle)}
onClick={() => props.action(axis)}> onClick={() => props.action(axis)}>
{`${t(props.axisTitle)} ${axis}`} {`${t(props.axisTitle)} ${axis}`}
</LockableButton> </LockableButton>

View File

@ -36,6 +36,7 @@ export function DangerZone(props: DangerZoneProps) {
<button <button
className="fb-button red" className="fb-button red"
disabled={botDisconnected} disabled={botDisconnected}
title={t("RESET")}
onClick={onReset}> onClick={onReset}>
{t("RESET")} {t("RESET")}
</button> </button>

View File

@ -16,7 +16,7 @@ import { Highlight } from "../maybe_highlight";
export const calculateScale = export const calculateScale =
(sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => { (sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => {
const getV = (name: McuParamName) => sourceFwConfig(name).value; const getV = (key: McuParamName) => sourceFwConfig(key).value;
return { return {
x: calcMicrostepsPerMm(getV("movement_step_per_mm_x"), x: calcMicrostepsPerMm(getV("movement_step_per_mm_x"),
getV("movement_microsteps_x")), getV("movement_microsteps_x")),

View File

@ -41,7 +41,7 @@ export function PinGuard(props: PinGuardProps) {
</Col> </Col>
</Row> </Row>
<PinGuardMCUInputGroup <PinGuardMCUInputGroup
name={t("Pin Guard {{ num }}", { num: 1 })} label={t("Pin Guard {{ num }}", { num: 1 })}
pinNumKey={"pin_guard_1_pin_nr"} pinNumKey={"pin_guard_1_pin_nr"}
timeoutKey={"pin_guard_1_time_out"} timeoutKey={"pin_guard_1_time_out"}
activeStateKey={"pin_guard_1_active_state"} activeStateKey={"pin_guard_1_active_state"}
@ -49,7 +49,7 @@ export function PinGuard(props: PinGuardProps) {
resources={resources} resources={resources}
sourceFwConfig={sourceFwConfig} /> sourceFwConfig={sourceFwConfig} />
<PinGuardMCUInputGroup <PinGuardMCUInputGroup
name={t("Pin Guard {{ num }}", { num: 2 })} label={t("Pin Guard {{ num }}", { num: 2 })}
pinNumKey={"pin_guard_2_pin_nr"} pinNumKey={"pin_guard_2_pin_nr"}
timeoutKey={"pin_guard_2_time_out"} timeoutKey={"pin_guard_2_time_out"}
activeStateKey={"pin_guard_2_active_state"} activeStateKey={"pin_guard_2_active_state"}
@ -57,7 +57,7 @@ export function PinGuard(props: PinGuardProps) {
resources={resources} resources={resources}
sourceFwConfig={sourceFwConfig} /> sourceFwConfig={sourceFwConfig} />
<PinGuardMCUInputGroup <PinGuardMCUInputGroup
name={t("Pin Guard {{ num }}", { num: 3 })} label={t("Pin Guard {{ num }}", { num: 3 })}
pinNumKey={"pin_guard_3_pin_nr"} pinNumKey={"pin_guard_3_pin_nr"}
timeoutKey={"pin_guard_3_time_out"} timeoutKey={"pin_guard_3_time_out"}
activeStateKey={"pin_guard_3_active_state"} activeStateKey={"pin_guard_3_active_state"}
@ -65,7 +65,7 @@ export function PinGuard(props: PinGuardProps) {
resources={resources} resources={resources}
sourceFwConfig={sourceFwConfig} /> sourceFwConfig={sourceFwConfig} />
<PinGuardMCUInputGroup <PinGuardMCUInputGroup
name={t("Pin Guard {{ num }}", { num: 4 })} label={t("Pin Guard {{ num }}", { num: 4 })}
pinNumKey={"pin_guard_4_pin_nr"} pinNumKey={"pin_guard_4_pin_nr"}
timeoutKey={"pin_guard_4_time_out"} timeoutKey={"pin_guard_4_time_out"}
activeStateKey={"pin_guard_4_active_state"} activeStateKey={"pin_guard_4_active_state"}
@ -73,7 +73,7 @@ export function PinGuard(props: PinGuardProps) {
resources={resources} resources={resources}
sourceFwConfig={sourceFwConfig} /> sourceFwConfig={sourceFwConfig} />
<PinGuardMCUInputGroup <PinGuardMCUInputGroup
name={t("Pin Guard {{ num }}", { num: 5 })} label={t("Pin Guard {{ num }}", { num: 5 })}
pinNumKey={"pin_guard_5_pin_nr"} pinNumKey={"pin_guard_5_pin_nr"}
timeoutKey={"pin_guard_5_time_out"} timeoutKey={"pin_guard_5_time_out"}
activeStateKey={"pin_guard_5_active_state"} activeStateKey={"pin_guard_5_active_state"}

View File

@ -65,7 +65,7 @@ export interface NumericMCUInputGroupProps {
export interface PinGuardMCUInputGroupProps { export interface PinGuardMCUInputGroupProps {
sourceFwConfig: SourceFwConfig; sourceFwConfig: SourceFwConfig;
dispatch: Function; dispatch: Function;
name: string; label: string;
pinNumKey: McuParamName; pinNumKey: McuParamName;
timeoutKey: McuParamName; timeoutKey: McuParamName;
activeStateKey: McuParamName; activeStateKey: McuParamName;

View File

@ -4,13 +4,15 @@ interface Props {
onClick: Function; onClick: Function;
disabled: boolean; disabled: boolean;
children?: React.ReactNode; children?: React.ReactNode;
title?: string;
} }
export function LockableButton({ onClick, disabled, children }: Props) { export function LockableButton({ onClick, disabled, children, title }: Props) {
const className = disabled ? "gray" : "yellow"; const className = disabled ? "gray" : "yellow";
return <button return <button
className={"fb-button " + className} className={"fb-button " + className}
disabled={disabled} disabled={disabled}
title={title}
onClick={() => disabled ? "" : onClick()}> onClick={() => disabled ? "" : onClick()}>
{children} {children}
</button>; </button>;

View File

@ -10,7 +10,7 @@ import { PinNumberDropdown } from "./pin_number_dropdown";
export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) { export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) {
const { sourceFwConfig, dispatch, name, pinNumKey, timeoutKey, activeStateKey const { sourceFwConfig, dispatch, label, pinNumKey, timeoutKey, activeStateKey
} = props; } = props;
const activeStateValue = sourceFwConfig(activeStateKey).value; const activeStateValue = sourceFwConfig(activeStateKey).value;
const inactiveState = isUndefined(activeStateValue) const inactiveState = isUndefined(activeStateValue)
@ -19,7 +19,7 @@ export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) {
return <Row> return <Row>
<Col xs={3}> <Col xs={3}>
<label> <label>
{name} {label}
</label> </label>
</Col> </Col>
<Col xs={3}> <Col xs={3}>

View File

@ -26,8 +26,8 @@ export class Connectivity
extends React.Component<ConnectivityProps, ConnectivityState> { extends React.Component<ConnectivityProps, ConnectivityState> {
state: ConnectivityState = { hoveredConnection: undefined }; state: ConnectivityState = { hoveredConnection: undefined };
hover = (name: string) => hover = (connectionName: string) =>
() => this.setState({ hoveredConnection: name }); () => this.setState({ hoveredConnection: connectionName });
render() { render() {
const { informational_settings } = this.props.bot.hardware; const { informational_settings } = this.props.bot.hardware;

View File

@ -28,7 +28,7 @@ export function Diagnosis(props: DiagnosisProps) {
const diagnosisBoolean = diagnosisStatus(props); const diagnosisBoolean = diagnosisStatus(props);
const diagnosisColor = diagnosisBoolean ? "green" : "red"; const diagnosisColor = diagnosisBoolean ? "green" : "red";
const title = diagnosisBoolean ? t("Ok") : t("Error"); const title = diagnosisBoolean ? t("Ok") : t("Error");
return <div> return <div className={"diagnosis-section"}>
<div className={"connectivity-diagnosis"}> <div className={"connectivity-diagnosis"}>
<h4>{t("Diagnosis")}</h4> <h4>{t("Diagnosis")}</h4>
</div> </div>

View File

@ -49,8 +49,9 @@ const diagramPositions: CowardlyDictionary<Record<"x" | "y", number>> = {
subRight: { x: 40, y: 110 } subRight: { x: 40, y: 110 }
}; };
export function getTextPosition(name: DiagramNodes): Record<"x" | "y", number> { export function getTextPosition(
const position = diagramPositions[name]; positionKey: DiagramNodes): Record<"x" | "y", number> {
const position = diagramPositions[positionKey];
if (position) { if (position) {
return { return {
x: position.x, x: position.x,

View File

@ -27,7 +27,7 @@ export function MustBeOnline(props: MBOProps) {
const { children, hideBanner, lockOpen, networkState, syncStatus } = props; const { children, hideBanner, lockOpen, networkState, syncStatus } = props;
const banner = hideBanner ? "" : "banner"; const banner = hideBanner ? "" : "banner";
if (isBotOnline(syncStatus, networkState) || lockOpen) { if (isBotOnline(syncStatus, networkState) || lockOpen) {
return <div> {children} </div>; return <div className={"bot-is-online-wrapper"}>{children}</div>;
} else { } else {
return <div return <div
className={`unavailable ${banner}`} className={`unavailable ${banner}`}

View File

@ -58,7 +58,7 @@ describe("<PinBindingInputGroup/>", () => {
it("no pin selected", () => { it("no pin selected", () => {
const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />); const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
const buttons = wrapper.find("button"); const buttons = wrapper.find("button");
expect(buttons.last().text()).toEqual("BIND"); expect(buttons.last().props().title).toEqual("BIND");
buttons.last().simulate("click"); buttons.last().simulate("click");
expect(error).toHaveBeenCalledWith("Pin number cannot be blank."); expect(error).toHaveBeenCalledWith("Pin number cannot be blank.");
}); });
@ -66,7 +66,7 @@ describe("<PinBindingInputGroup/>", () => {
it("no target selected", () => { it("no target selected", () => {
const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />); const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
const buttons = wrapper.find("button"); const buttons = wrapper.find("button");
expect(buttons.last().text()).toEqual("BIND"); expect(buttons.last().props().title).toEqual("BIND");
wrapper.setState({ pinNumberInput: AVAILABLE_PIN }); wrapper.setState({ pinNumberInput: AVAILABLE_PIN });
buttons.last().simulate("click"); buttons.last().simulate("click");
expect(error).toHaveBeenCalledWith("Please select a sequence or action."); expect(error).toHaveBeenCalledWith("Please select a sequence or action.");
@ -77,7 +77,7 @@ describe("<PinBindingInputGroup/>", () => {
p.dispatch = jest.fn(); p.dispatch = jest.fn();
const wrapper = mount(<PinBindingInputGroup {...p} />); const wrapper = mount(<PinBindingInputGroup {...p} />);
const buttons = wrapper.find("button"); const buttons = wrapper.find("button");
expect(buttons.last().text()).toEqual("BIND"); expect(buttons.last().props().title).toEqual("BIND");
wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 }); wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 });
buttons.last().simulate("click"); buttons.last().simulate("click");
expect(mockDevice.registerGpio).not.toHaveBeenCalled(); expect(mockDevice.registerGpio).not.toHaveBeenCalled();
@ -94,7 +94,7 @@ describe("<PinBindingInputGroup/>", () => {
p.dispatch = jest.fn(); p.dispatch = jest.fn();
const wrapper = mount(<PinBindingInputGroup {...p} />); const wrapper = mount(<PinBindingInputGroup {...p} />);
const buttons = wrapper.find("button"); const buttons = wrapper.find("button");
expect(buttons.last().text()).toEqual("BIND"); expect(buttons.last().props().title).toEqual("BIND");
wrapper.setState({ wrapper.setState({
pinNumberInput: 0, pinNumberInput: 0,
bindingType: PinBindingType.special, bindingType: PinBindingType.special,

View File

@ -133,8 +133,9 @@ export class PinBindingInputGroup
<button <button
className="fb-button green" className="fb-button green"
type="button" type="button"
title={t("BIND")}
onClick={this.bindPin}> onClick={this.bindPin}>
{t("BIND")} <i className={"fa fa-plus"} />
</button> </button>
</Col> </Col>
</Row>; </Row>;

View File

@ -81,12 +81,12 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
portalClassName={"bindings-warning-icon"} portalClassName={"bindings-warning-icon"}
popoverClassName={"help"}> popoverClassName={"help"}>
<i className="fa fa-exclamation-triangle" /> <i className="fa fa-exclamation-triangle" />
<div> <div className={"pin-binding-warning"}>
{t(ToolTips.PIN_BINDING_WARNING)} {t(ToolTips.PIN_BINDING_WARNING)}
</div> </div>
</Popover> </Popover>
</Row> </Row>
<div> <div className={"pin-bindings-list-and-input"}>
<PinBindingsListHeader /> <PinBindingsListHeader />
<PinBindingsList <PinBindingsList
pinBindings={pinBindings} pinBindings={pinBindings}

View File

@ -47,6 +47,7 @@ export const StockPinBindingsButton = (props: StockPinBindingsButtonProps) =>
<button <button
className="fb-button green" className="fb-button green"
hidden={!hasButtons(props.firmwareHardware)} hidden={!hasButtons(props.firmwareHardware)}
title={t("add stock pin bindings")}
onClick={() => stockPinBindings.map(binding => onClick={() => stockPinBindings.map(binding =>
props.dispatch(initSave("PinBinding", pinBindingBody(binding))))}> props.dispatch(initSave("PinBinding", pinBindingBody(binding))))}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />

View File

@ -18,7 +18,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
no = () => this.props.fallback || <Apology />; no = () => this.props.fallback || <Apology />;
ok = () => this.props.children || <div />; ok = () => this.props.children || <div className={"no-children"} />;
render() { return (this.state.hasError ? this.no : this.ok)(); } render() { return (this.state.hasError ? this.no : this.ok)(); }
} }

View File

@ -256,15 +256,15 @@ export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
}; };
} }
fieldSet = (name: FarmEventViewModelKey, value: string) => fieldSet = (key: FarmEventViewModelKey, value: string) =>
// A merge is required to not overwrite `fe`. // A merge is required to not overwrite `fe`.
this.setState(betterMerge(this.state, { this.setState(betterMerge(this.state, {
fe: { [name]: value }, fe: { [key]: value },
specialStatusLocal: SpecialStatus.DIRTY specialStatusLocal: SpecialStatus.DIRTY
})) }))
fieldGet = (name: FarmEventViewModelKey): string => fieldGet = (key: FarmEventViewModelKey): string =>
(this.state.fe[name] || this.viewModel[name] || "").toString() (this.state.fe[key] || this.viewModel[key] || "").toString()
nextItemTime = (fe: FarmEvent, now: moment.Moment nextItemTime = (fe: FarmEvent, now: moment.Moment
): moment.Moment | undefined => { ): moment.Moment | undefined => {
@ -390,8 +390,8 @@ export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
export interface StartTimeFormProps { export interface StartTimeFormProps {
isRegimen: boolean; isRegimen: boolean;
fieldGet(name: FarmEventViewModelKey): string; fieldGet(key: FarmEventViewModelKey): string;
fieldSet(name: FarmEventViewModelKey, value: string): void; fieldSet(key: FarmEventViewModelKey, value: string): void;
timeSettings: TimeSettings; timeSettings: TimeSettings;
} }
@ -426,8 +426,8 @@ export const StartTimeForm = (props: StartTimeFormProps) => {
export interface RepeatFormProps { export interface RepeatFormProps {
isRegimen: boolean; isRegimen: boolean;
fieldGet(name: FarmEventViewModelKey): string; fieldGet(key: FarmEventViewModelKey): string;
fieldSet(name: FarmEventViewModelKey, value: string): void; fieldSet(key: FarmEventViewModelKey, value: string): void;
timeSettings: TimeSettings; timeSettings: TimeSettings;
} }
@ -437,13 +437,14 @@ export const RepeatForm = (props: RepeatFormProps) => {
{!props.isRegimen {!props.isRegimen
? <label> ? <label>
<input type="checkbox" <input type="checkbox"
name="timeUnit"
onChange={e => props.fieldSet("timeUnit", onChange={e => props.fieldSet("timeUnit",
(!e.currentTarget.checked || props.isRegimen) ? "never" : "daily")} (!e.currentTarget.checked || props.isRegimen) ? "never" : "daily")}
disabled={props.isRegimen} disabled={props.isRegimen}
checked={allowRepeat} /> checked={allowRepeat} />
{t("Repeats?")} {t("Repeats?")}
</label> </label>
: <div />} : <div className={"no-repeat"} />}
<FarmEventRepeatForm <FarmEventRepeatForm
timeSettings={props.timeSettings} timeSettings={props.timeSettings}
disabled={!allowRepeat} disabled={!allowRepeat}
@ -459,7 +460,7 @@ export const RepeatForm = (props: RepeatFormProps) => {
}; };
export const dateCheck = ( export const dateCheck = (
fieldGet: (name: FarmEventViewModelKey) => string fieldGet: (key: FarmEventViewModelKey) => string
): string | undefined => { ): string | undefined => {
const startDate = fieldGet("startDate"); const startDate = fieldGet("startDate");
const endDate = fieldGet("endDate"); const endDate = fieldGet("endDate");
@ -469,7 +470,7 @@ export const dateCheck = (
}; };
export const timeCheck = ( export const timeCheck = (
fieldGet: (name: FarmEventViewModelKey) => string, fieldGet: (key: FarmEventViewModelKey) => string,
timeSettings: TimeSettings timeSettings: TimeSettings
): string | undefined => { ): string | undefined => {
const startDate = fieldGet("startDate"); const startDate = fieldGet("startDate");
@ -491,6 +492,7 @@ export interface FarmEventDeleteButtonProps {
export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) => export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
<button className="fb-button red" hidden={props.hidden} <button className="fb-button red" hidden={props.hidden}
title={t("Delete")}
onClick={() => onClick={() =>
props.dispatch(destroy(props.farmEvent.uuid)) props.dispatch(destroy(props.farmEvent.uuid))
.then(() => { .then(() => {
@ -502,8 +504,8 @@ export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
export interface FarmEventFormProps { export interface FarmEventFormProps {
isRegimen: boolean; isRegimen: boolean;
fieldGet(name: FarmEventViewModelKey): string; fieldGet(key: FarmEventViewModelKey): string;
fieldSet(name: FarmEventViewModelKey, value: string): void; fieldSet(key: FarmEventViewModelKey, value: string): void;
timeSettings: TimeSettings; timeSettings: TimeSettings;
executableOptions: DropDownItem[]; executableOptions: DropDownItem[];
executableSet(ddi: DropDownItem): void; executableSet(ddi: DropDownItem): void;

View File

@ -15,7 +15,7 @@ export interface FarmEventRepeatFormProps {
disabled: boolean; disabled: boolean;
/** Should the form be shown _at all_? */ /** Should the form be shown _at all_? */
hidden: boolean; hidden: boolean;
fieldSet(name: keyof FarmEventViewModel, value: string): void; fieldSet(key: keyof FarmEventViewModel, value: string): void;
timeUnit: TimeUnit; timeUnit: TimeUnit;
repeat: string; repeat: string;
endDate: string; endDate: string;
@ -30,53 +30,55 @@ const OPTN_LOOKUP = keyBy(repeatOptions, indexKey);
export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) { export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) {
const { disabled, fieldSet, repeat, endDate, endTime, timeUnit } = props; const { disabled, fieldSet, repeat, endDate, endTime, timeUnit } = props;
return props.hidden ? <div /> : <div className="farm-event-repeat-form"> return props.hidden
<label> ? <div className={"no-repeat-form"} />
{t("Every")} : <div className="farm-event-repeat-form">
</label> <label>
<Row> {t("Every")}
<Col xs={4}> </label>
<BlurableInput <Row>
disabled={disabled} <Col xs={4}>
placeholder="(Number)" <BlurableInput
type="number" disabled={disabled}
className="add-event-repeat-frequency" placeholder="(Number)"
name="repeat" type="number"
value={repeat} className="add-event-repeat-frequency"
onCommit={e => fieldSet("repeat", e.currentTarget.value)} name="repeat"
min={1} /> value={repeat}
</Col> onCommit={e => fieldSet("repeat", e.currentTarget.value)}
<Col xs={8}> min={1} />
<FBSelect </Col>
list={repeatOptions} <Col xs={8}>
onChange={ddi => fieldSet("timeUnit", "" + ddi.value)} <FBSelect
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} /> list={repeatOptions}
</Col> onChange={ddi => fieldSet("timeUnit", "" + ddi.value)}
</Row> selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
<label> </Col>
{t("Until")} </Row>
</label> <label>
<Row> {t("Until")}
<Col xs={6}> </label>
<BlurableInput <Row>
disabled={disabled} <Col xs={6}>
type="date" <BlurableInput
className="add-event-end-date" disabled={disabled}
name="endDate" type="date"
value={endDate} className="add-event-end-date"
onCommit={e => fieldSet("endDate", e.currentTarget.value)} name="endDate"
error={props.dateError} /> value={endDate}
</Col> onCommit={e => fieldSet("endDate", e.currentTarget.value)}
<Col xs={6}> error={props.dateError} />
<EventTimePicker </Col>
disabled={disabled} <Col xs={6}>
className="add-event-end-time" <EventTimePicker
name="endTime" disabled={disabled}
timeSettings={props.timeSettings} className="add-event-end-time"
value={endTime} name="endTime"
onCommit={e => fieldSet("endTime", e.currentTarget.value)} timeSettings={props.timeSettings}
error={props.timeError} /> value={endTime}
</Col> onCommit={e => fieldSet("endTime", e.currentTarget.value)}
</Row> error={props.timeError} />
</div>; </Col>
</Row>
</div>;
} }

View File

@ -109,6 +109,7 @@ export class PureFarmEvents
noIcon={true}> noIcon={true}>
<i className="fa fa-calendar" onClick={this.resetCalendar} /> <i className="fa fa-calendar" onClick={this.resetCalendar} />
<input <input
name="searchTerm"
value={this.state.searchTerm} value={this.state.searchTerm}
onChange={e => this.setState({ searchTerm: e.currentTarget.value })} onChange={e => this.setState({ searchTerm: e.currentTarget.value })}
placeholder={t("Search your events...")} /> placeholder={t("Search your events...")} />

View File

@ -49,10 +49,10 @@ export const gridOffset: AxisNumberProperty = { x: 50, y: 50 };
export class RawFarmDesigner extends React.Component<Props, Partial<State>> { export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
initializeSetting = initializeSetting =
(name: keyof State, defaultValue: boolean): boolean => { (key: keyof State, defaultValue: boolean): boolean => {
const currentValue = this.props.getConfigValue(name); const currentValue = this.props.getConfigValue(key);
if (isUndefined(currentValue)) { if (isUndefined(currentValue)) {
this.props.dispatch(setWebAppConfigValue(name, defaultValue)); this.props.dispatch(setWebAppConfigValue(key, defaultValue));
return defaultValue; return defaultValue;
} else { } else {
return !!currentValue; return !!currentValue;
@ -87,10 +87,10 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
this.updateZoomLevel(0)(); this.updateZoomLevel(0)();
} }
toggle = (name: keyof State) => () => { toggle = (key: keyof State) => () => {
const newValue = !this.state[name]; const newValue = !this.state[key];
this.props.dispatch(setWebAppConfigValue(name, newValue)); this.props.dispatch(setWebAppConfigValue(key, newValue));
this.setState({ [name]: newValue }); this.setState({ [key]: newValue });
} }
updateBotOriginQuadrant = (payload: BotOriginQuadrant) => () => { updateBotOriginQuadrant = (payload: BotOriginQuadrant) => () => {

View File

@ -125,6 +125,7 @@ export const BugsControls = () =>
? <div className="more-bugs"> ? <div className="more-bugs">
<button <button
className="fb-button green" className="fb-button green"
title={t("more bugs!")}
onClick={resetBugs}> onClick={resetBugs}>
{t("more bugs!")} {t("more bugs!")}
</button> </button>
@ -133,4 +134,4 @@ export const BugsControls = () =>
{t("{{seconds}} seconds!", { seconds: getBugTime() })} {t("{{seconds}} seconds!", { seconds: getBugTime() })}
</p>} </p>}
</div> </div>
: <div />; : <div className={"no-bugs"} />;

View File

@ -9,7 +9,7 @@ import {
describe("<BotFigure/>", () => { describe("<BotFigure/>", () => {
const fakeProps = (): BotFigureProps => ({ const fakeProps = (): BotFigureProps => ({
name: "", figureName: "",
position: { x: 0, y: 0, z: 0 }, position: { x: 0, y: 0, z: 0 },
mapTransformProps: fakeMapTransformProps(), mapTransformProps: fakeMapTransformProps(),
plantAreaOffset: { x: 100, y: 100 }, plantAreaOffset: { x: 100, y: 100 },
@ -30,11 +30,11 @@ describe("<BotFigure/>", () => {
["motors", 4, { x: 3000, y: 1500 }, true, EXPECTED_MOTORS_OPACITY], ["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) => { (figureName, quadrant, expected, xySwap, opacity) => {
const p = fakeProps(); const p = fakeProps();
p.mapTransformProps.quadrant = quadrant; p.mapTransformProps.quadrant = quadrant;
p.mapTransformProps.xySwap = xySwap; p.mapTransformProps.xySwap = xySwap;
p.name = name; p.figureName = figureName;
const result = shallow<BotFigure>(<BotFigure {...p} />); const result = shallow<BotFigure>(<BotFigure {...p} />);
const expectedGantryProps = expect.objectContaining({ const expectedGantryProps = expect.objectContaining({

View File

@ -5,6 +5,7 @@ import { VirtualFarmBotProps } from "../../../interfaces";
import { import {
fakeMapTransformProps fakeMapTransformProps
} from "../../../../../__test_support__/map_transform_props"; } from "../../../../../__test_support__/map_transform_props";
import { BotFigure } from "../bot_figure";
describe("<VirtualFarmBot/>", () => { describe("<VirtualFarmBot/>", () => {
function fakeProps(): VirtualFarmBotProps { function fakeProps(): VirtualFarmBotProps {
@ -27,9 +28,9 @@ describe("<VirtualFarmBot/>", () => {
const p = fakeProps(); const p = fakeProps();
p.getConfigValue = () => false; p.getConfigValue = () => false;
const wrapper = shallow(<VirtualFarmBot {...p} />); const wrapper = shallow(<VirtualFarmBot {...p} />);
const figures = wrapper.find("BotFigure"); const figures = wrapper.find(BotFigure);
expect(figures.length).toEqual(1); expect(figures.length).toEqual(1);
expect(figures.last().props().name).toEqual("motor-position"); expect(figures.last().props().figureName).toEqual("motor-position");
}); });
it("shows trail", () => { it("shows trail", () => {
@ -39,8 +40,8 @@ describe("<VirtualFarmBot/>", () => {
it("shows encoder position", () => { it("shows encoder position", () => {
const wrapper = shallow(<VirtualFarmBot {...fakeProps()} />); const wrapper = shallow(<VirtualFarmBot {...fakeProps()} />);
const figures = wrapper.find("BotFigure"); const figures = wrapper.find(BotFigure);
expect(figures.length).toEqual(2); expect(figures.length).toEqual(2);
expect(figures.last().props().name).toEqual("encoder-position"); expect(figures.last().props().figureName).toEqual("encoder-position");
}); });
}); });

View File

@ -9,7 +9,7 @@ import { reduceToolName } from "../tool_slots/tool_slot_point";
import { noop } from "lodash"; import { noop } from "lodash";
export interface BotFigureProps { export interface BotFigureProps {
name: string; figureName: string;
position: BotPosition; position: BotPosition;
mapTransformProps: MapTransformProps; mapTransformProps: MapTransformProps;
plantAreaOffset: AxisNumberProperty; plantAreaOffset: AxisNumberProperty;
@ -29,14 +29,14 @@ export class BotFigure extends
render() { render() {
const { const {
name, position, plantAreaOffset, eStopStatus, mapTransformProps, figureName, position, plantAreaOffset, eStopStatus, mapTransformProps,
} = this.props; } = this.props;
const { xySwap } = mapTransformProps; const { xySwap } = mapTransformProps;
const mapSize = getMapSize(mapTransformProps, plantAreaOffset); const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
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.5; const opacity = figureName.includes("encoder") ? 0.25 : 0.5;
const toolProps = { const toolProps = {
x: positionQ.qx, x: positionQ.qx,
y: positionQ.qy, y: positionQ.qy,
@ -45,7 +45,7 @@ export class BotFigure extends
uuid: "utm", uuid: "utm",
xySwap, xySwap,
}; };
return <g id={name}> return <g id={figureName}>
<rect id="gantry" <rect id="gantry"
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10} x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
y={xySwap ? positionQ.qy - 10 : -plantAreaOffset.y} y={xySwap ? positionQ.qy - 10 : -plantAreaOffset.y}

View File

@ -24,14 +24,14 @@ export function VirtualFarmBot(props: VirtualFarmBotProps) {
plantAreaOffset={plantAreaOffset} plantAreaOffset={plantAreaOffset}
peripherals={peripherals} peripherals={peripherals}
getConfigValue={getConfigValue} /> getConfigValue={getConfigValue} />
<BotFigure name={"motor-position"} <BotFigure figureName={"motor-position"}
position={props.botLocationData.position} position={props.botLocationData.position}
mapTransformProps={mapTransformProps} mapTransformProps={mapTransformProps}
plantAreaOffset={plantAreaOffset} plantAreaOffset={plantAreaOffset}
mountedToolName={props.mountedToolName} mountedToolName={props.mountedToolName}
eStopStatus={eStopStatus} /> eStopStatus={eStopStatus} />
{encoderFigure && {encoderFigure &&
<BotFigure name={"encoder-position"} <BotFigure figureName={"encoder-position"}
position={props.botLocationData.scaled_encoders} position={props.botLocationData.scaled_encoders}
mapTransformProps={mapTransformProps} mapTransformProps={mapTransformProps}
plantAreaOffset={plantAreaOffset} />} plantAreaOffset={plantAreaOffset} />}

View File

@ -17,12 +17,12 @@ const parse = (str: string | undefined) => {
}; };
/* Check if the image has been rotated according to the calibration value. */ /* Check if the image has been rotated according to the calibration value. */
const isRotated = (name: string | undefined, noCalib: boolean) => { const isRotated = (annotation: string | undefined, noCalib: boolean) => {
if (PRE_CALIBRATION_PREVIEW && noCalib) { return true; } if (PRE_CALIBRATION_PREVIEW && noCalib) { return true; }
return name && return annotation &&
(name.includes("rotated") (annotation.includes("rotated")
|| name.includes("marked") || annotation.includes("marked")
|| name.includes("calibration_result")); || annotation.includes("calibration_result"));
}; };
/* Check if the calibration data is valid for the image provided using z. */ /* Check if the calibration data is valid for the image provided using z. */

View File

@ -24,6 +24,7 @@ const LengthInput = (props: LengthInputProps) =>
<Col xs={5}> <Col xs={5}>
<input <input
type="number" type="number"
name={props.setting}
value={"" + props.value} value={"" + props.value}
onChange={e => props.dispatch(setWebAppConfigValue( onChange={e => props.dispatch(setWebAppConfigValue(
props.setting, e.currentTarget.value))} /> props.setting, e.currentTarget.value))} />

View File

@ -64,7 +64,7 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
render() { render() {
const { x, y } = this.props.chosenLocation; const { x, y } = this.props.chosenLocation;
const { botOnline } = this.props; const { botOnline } = this.props;
return <div> return <div className={"move-to-form"}>
<Row> <Row>
<Col xs={4}> <Col xs={4}>
<label>{t("X AXIS")}</label> <label>{t("X AXIS")}</label>
@ -78,10 +78,10 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
</Row> </Row>
<Row> <Row>
<Col xs={4}> <Col xs={4}>
<input disabled value={isNumber(x) ? x : "---"} /> <input disabled name="x" value={isNumber(x) ? x : "---"} />
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input disabled value={isNumber(y) ? y : "---"} /> <input disabled name="y" value={isNumber(y) ? y : "---"} />
</Col> </Col>
<AxisInputBox <AxisInputBox
onChange={(_, val: number) => this.setState({ z: val })} onChange={(_, val: number) => this.setState({ z: val })}
@ -91,7 +91,9 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
<button <button
onClick={() => moveAbs(this.vector)} onClick={() => moveAbs(this.vector)}
className={`fb-button gray ${botOnline ? "" : "pseudo-disabled"}`} className={`fb-button gray ${botOnline ? "" : "pseudo-disabled"}`}
title={botOnline ? "" : t(Content.NOT_AVAILABLE_WHEN_OFFLINE)}> title={botOnline
? t("Move to this coordinate")
: t(Content.NOT_AVAILABLE_WHEN_OFFLINE)}>
{t("Move to this coordinate")} {t("Move to this coordinate")}
</button> </button>
</Row> </Row>

View File

@ -189,7 +189,7 @@ describe("<EditDatePlanted />", () => {
describe("<EditPlantLocation />", () => { describe("<EditPlantLocation />", () => {
const fakeProps = (): EditPlantLocationProps => ({ const fakeProps = (): EditPlantLocationProps => ({
uuid: "Plant.0.0", uuid: "Plant.0.0",
location: { x: 1, y: 2 }, xyLocation: { x: 1, y: 2 },
updatePlant: jest.fn(), updatePlant: jest.fn(),
}); });

View File

@ -30,7 +30,7 @@ interface APDProps {
} }
const AddPlantDescription = ({ svgIcon, children }: APDProps) => const AddPlantDescription = ({ svgIcon, children }: APDProps) =>
<div> <div className={"add-plant-description"}>
<img className="crop-drag-info-image" <img className="crop-drag-info-image"
src={svgToUrl(svgIcon)} src={svgToUrl(svgIcon)}
alt={t("plant icon")} alt={t("plant icon")}

View File

@ -74,6 +74,7 @@ export class RawCropCatalog extends React.Component<CropCatalogProps, {}> {
onChange={this.handleChange} onChange={this.handleChange}
onKeyPress={this.handleChange} onKeyPress={this.handleChange}
className="search" className="search"
name="searchTerm"
placeholder={t("Search OpenFarm...")} /> placeholder={t("Search OpenFarm...")} />
{this.showResultChangeSpinner && {this.showResultChangeSpinner &&
<Spinner radius={10} strokeWidth={3} />} <Spinner radius={10} strokeWidth={3} />}

View File

@ -44,7 +44,7 @@ const InfoField = (props: InfoFieldProps) =>
<p> <p>
{t(startCase(props.title))} {t(startCase(props.title))}
</p> </p>
<div> <div className={"crop-info-field-data"}>
{props.children} {props.children}
</div> </div>
</li>; </li>;
@ -67,11 +67,13 @@ const NO_VALUE = t("Not Set");
const SvgIcon = ({ i, field, value }: SummaryItemProps) => const SvgIcon = ({ i, field, value }: SummaryItemProps) =>
<InfoField key={i} title={field}> <InfoField key={i} title={field}>
{value {value
? <div><img ? <div className={"svg-img"}>
src={svgToUrl(value)} <img
width={100} src={svgToUrl(value)}
height={100} width={100}
onDragStart={setDragIcon(value)} /></div> height={100}
onDragStart={setDragIcon(value)} />
</div>
: <span>{NO_VALUE}</span>} : <span>{NO_VALUE}</span>}
</InfoField>; </InfoField>;
@ -149,6 +151,7 @@ const AddPlantHereButton = (props: {
dispatch, openedSavedGarden dispatch, openedSavedGarden
}) : () => { }; }) : () => { };
return <button className="fb-button gray no-float" return <button className="fb-button gray no-float"
title={t("Add plant at current location")}
disabled={!botXY} onClick={click}> disabled={!botXY} onClick={click}>
{t("Add plant at current FarmBot location {{coordinate}}", {t("Add plant at current FarmBot location {{coordinate}}",
{ coordinate: botXYLabel })} { coordinate: botXYLabel })}

View File

@ -23,14 +23,13 @@ describe("PlantGrid", () => {
const p = fakeProps(); const p = fakeProps();
const el = mount<PlantGrid>(<PlantGrid {...p} />); const el = mount<PlantGrid>(<PlantGrid {...p} />);
// Upon load, there should be one button. // Upon load, there should be one button.
const previewButton = el.find("a.clear-button"); const previewButton = el.find("a.preview-button");
expect(previewButton.text()).toContain("Preview"); expect(previewButton.text()).toContain("Preview");
previewButton.simulate("click"); previewButton.simulate("click");
// After clicking PREVIEW, there should be two buttons. // After clicking PREVIEW, there should be two buttons.
const saveAndCancelBtns = el.find("a.clear-button"); const cancel = el.find("a.cancel-button");
const cancel = saveAndCancelBtns.at(0); const save = el.find("a.save-button");
const save = saveAndCancelBtns.at(1);
expect(cancel.text()).toContain("Cancel"); expect(cancel.text()).toContain("Cancel");
expect(save.text()).toContain("Save"); expect(save.text()).toContain("Save");
expect(el.state().status).toEqual("dirty"); expect(el.state().status).toEqual("dirty");

View File

@ -77,17 +77,23 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
buttons = () => { buttons = () => {
switch (this.state.status) { switch (this.state.status) {
case "clean": case "clean":
return <div> return <div className={"preview-grid-button"}>
<a className={"clear-button"} onClick={this.performPreview}> <a className={"preview-button"}
title={t("Preview")}
onClick={this.performPreview}>
{t("Preview")} {t("Preview")}
</a> </a>
</div>; </div>;
case "dirty": case "dirty":
return <div> return <div className={"save-or-cancel-grid-button"}>
<a className={"clear-button"} onClick={this.revertPreview}> <a className={"cancel-button"}
title={t("Cancel")}
onClick={this.revertPreview}>
{t("Cancel")} {t("Cancel")}
</a> </a>
<a className={"clear-button"} onClick={this.saveGrid}> <a className={"save-button"}
title={t("Save")}
onClick={this.saveGrid}>
{t("Save")} {t("Save")}
</a> </a>
</div>; </div>;
@ -95,7 +101,7 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
} }
render() { render() {
return <div> return <div className={"grid-and-row-planting"}>
<hr style={{ borderTop: "1.5px solid rgba(255, 255, 255 ,0.7)" }} /> <hr style={{ borderTop: "1.5px solid rgba(255, 255, 255 ,0.7)" }} />
<h3> <h3>
{t("Grid and Row Planting")} {t("Grid and Row Planting")}

View File

@ -32,33 +32,31 @@ export class OpenFarmResults extends React.Component<SearchResultProps, {}> {
} }
render() { render() {
return <div> return <EmptyStateWrapper
<EmptyStateWrapper notEmpty={this.props.cropSearchResults.length > 0}
notEmpty={this.props.cropSearchResults.length > 0} graphic={EmptyStateGraphic.no_crop_results}
graphic={EmptyStateGraphic.no_crop_results} title={this.props.cropSearchInProgress
title={this.props.cropSearchInProgress ? t("Searching...")
? t("Searching...") : t("No search results")}
: t("No search results")} textElement={this.props.cropSearchInProgress ? undefined : this.text}
textElement={this.props.cropSearchInProgress ? <div /> : this.text} colorScheme={"plants"}>
colorScheme={"plants"}> {this.props.cropSearchResults.map(resp => {
{this.props.cropSearchResults.map(resp => { const { crop, image } = resp;
const { crop, image } = resp; return <Link
return <Link key={resp.crop.slug}
key={resp.crop.slug} draggable={false}
draggable={false} to={`/app/designer/plants/crop_search/` + crop.slug.toString()}>
to={`/app/designer/plants/crop_search/` + crop.slug.toString()}> <div className="plant-catalog-tile col-xs-6">
<div className="plant-catalog-tile col-xs-6"> <label>
<label> {crop.name}
{crop.name} </label>
</label> <div
<div className="plant-catalog-image"
className="plant-catalog-image" style={{ background: `url(${image}) top center no-repeat` }}
style={{ background: `url(${image}) top center no-repeat` }} draggable={false} />
draggable={false} /> </div>
</div> </Link>;
</Link>; })}
})} </EmptyStateWrapper>;
</EmptyStateWrapper>
</div>;
} }
} }

View File

@ -47,7 +47,7 @@ export class RawPlants extends React.Component<PlantInventoryProps, State> {
panel={Panel.Plants} panel={Panel.Plants}
linkTo={"/app/designer/plants/crop_search"} linkTo={"/app/designer/plants/crop_search"}
title={t("Add plant")}> title={t("Add plant")}>
<input type="text" onChange={this.update} <input type="text" onChange={this.update} name="searchTerm"
placeholder={t("Search your plants...")} /> placeholder={t("Search your plants...")} />
</DesignerPanelTop> </DesignerPanelTop>
<DesignerPanelContent panelName={"plant"}> <DesignerPanelContent panelName={"plant"}>

View File

@ -51,18 +51,18 @@ export const EditDatePlanted = (props: EditDatePlantedProps) => {
}; };
export interface EditPlantLocationProps extends EditPlantProperty { export interface EditPlantLocationProps extends EditPlantProperty {
location: Record<"x" | "y", number>; xyLocation: Record<"x" | "y", number>;
} }
export const EditPlantLocation = (props: EditPlantLocationProps) => { export const EditPlantLocation = (props: EditPlantLocationProps) => {
const { location, updatePlant, uuid } = props; const { xyLocation, updatePlant, uuid } = props;
return <Row> return <Row>
{["x", "y"].map((axis: "x" | "y") => {["x", "y"].map((axis: "x" | "y") =>
<Col xs={6} key={axis}> <Col xs={6} key={axis}>
<label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label> <label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label>
<BlurableInput <BlurableInput
type="number" type="number"
value={location[axis]} value={xyLocation[axis]}
min={0} min={0}
onCommit={e => updatePlant(uuid, { onCommit={e => updatePlant(uuid, {
[axis]: round(parseIntInput(e.currentTarget.value)) [axis]: round(parseIntInput(e.currentTarget.value))
@ -89,6 +89,7 @@ interface MoveToPlantProps {
const MoveToPlant = (props: MoveToPlantProps) => const MoveToPlant = (props: MoveToPlantProps) =>
<button className="fb-button gray no-float" <button className="fb-button gray no-float"
style={{ marginTop: "1rem" }} style={{ marginTop: "1rem" }}
title={t("Move to this plant")}
onClick={() => props.dispatch(chooseLocation({ x: props.x, y: props.y })) onClick={() => props.dispatch(chooseLocation({ x: props.x, y: props.y }))
.then(() => history.push("/app/designer/move_to"))}> .then(() => history.push("/app/designer/move_to"))}>
{t("Move FarmBot to this plant")} {t("Move FarmBot to this plant")}
@ -99,20 +100,22 @@ interface DeleteButtonsProps {
} }
const DeleteButtons = (props: DeleteButtonsProps) => const DeleteButtons = (props: DeleteButtonsProps) =>
<div> <div className={"plant-delete-buttons"}>
<div> <div className={"plant-delete-button-label"}>
<label> <label>
{t("Delete this plant")} {t("Delete this plant")}
</label> </label>
</div> </div>
<button <button
className="fb-button red no-float" className="fb-button red no-float"
title={t("Delete")}
onClick={props.destroy}> onClick={props.destroy}>
{t("Delete")} {t("Delete")}
</button> </button>
<button <button
className="fb-button gray no-float" className="fb-button gray no-float"
style={{ marginRight: "10px" }} style={{ marginRight: "10px" }}
title={t("Delete multiple")}
onClick={() => history.push("/app/designer/plants/select")}> onClick={() => history.push("/app/designer/plants/select")}>
{t("Delete multiple")} {t("Delete multiple")}
</button> </button>
@ -128,7 +131,7 @@ export const ListItem = (props: ListItemProps) =>
<p> <p>
{props.name} {props.name}
</p> </p>
<div> <div className={"plant-info-field-data"}>
{props.children} {props.children}
</div> </div>
</li>; </li>;
@ -171,7 +174,7 @@ export function PlantPanel(props: PlantPanelProps) {
</Row>} </Row>}
<ListItem name={t("Location")}> <ListItem name={t("Location")}>
<EditPlantLocation uuid={uuid} <EditPlantLocation uuid={uuid}
location={{ x, y }} xyLocation={{ x, y }}
updatePlant={updatePlant} /> updatePlant={updatePlant} />
</ListItem> </ListItem>
<MoveToPlant x={x} y={y} dispatch={dispatch} /> <MoveToPlant x={x} y={y} dispatch={dispatch} />

View File

@ -60,10 +60,12 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
<div className="panel-action-buttons"> <div className="panel-action-buttons">
<div className="button-row"> <div className="button-row">
<button className="fb-button gray" <button className="fb-button gray"
title={t("Select none")}
onClick={() => this.props.dispatch(selectPlant(undefined))}> onClick={() => this.props.dispatch(selectPlant(undefined))}>
{t("Select none")} {t("Select none")}
</button> </button>
<button className="fb-button gray" <button className="fb-button gray"
title={t("Select all")}
onClick={() => this.props onClick={() => this.props
.dispatch(selectPlant(this.props.plants.map(p => p.uuid)))}> .dispatch(selectPlant(this.props.plants.map(p => p.uuid)))}>
{t("Select all")} {t("Select all")}
@ -72,10 +74,12 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
<label>{t("SELECTION ACTIONS")}</label> <label>{t("SELECTION ACTIONS")}</label>
<div className="button-row"> <div className="button-row">
<button className="fb-button red" <button className="fb-button red"
title={t("Delete")}
onClick={() => this.destroySelected(this.props.selected)}> onClick={() => this.destroySelected(this.props.selected)}>
{t("Delete")} {t("Delete")}
</button> </button>
<button className="fb-button dark-blue" <button className="fb-button dark-blue"
title={t("Create group")}
onClick={() => !this.props.gardenOpen onClick={() => !this.props.gardenOpen
? this.props.dispatch(createGroup({ pointUuids: this.selected })) ? this.props.dispatch(createGroup({ pointUuids: this.selected }))
: error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}> : error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}>

View File

@ -4,9 +4,9 @@ import { TaggedPoint } from "farmbot";
import { fakePlant } from "../../../__test_support__/fake_state/resources"; import { fakePlant } from "../../../__test_support__/fake_state/resources";
describe("sort()", () => { describe("sort()", () => {
const phony = (name: string, x: number, y: number): TaggedPoint => { const phony = (plantName: string, x: number, y: number): TaggedPoint => {
const plant = fakePlant(); const plant = fakePlant();
plant.body.name = name; plant.body.name = plantName;
plant.body.x = x; plant.body.x = x;
plant.body.y = y; plant.body.y = y;
return plant; return plant;

View File

@ -45,12 +45,15 @@ export class AddEqCriteria<T extends string | number>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input type={this.props.type} <input type={this.props.type}
name="value"
placeholder={t("value")} placeholder={t("value")}
value={this.state.value} value={this.state.value}
onChange={e => this.setState({ value: e.currentTarget.value })} /> onChange={e => this.setState({ value: e.currentTarget.value })} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button green" onClick={this.commit}> <button className="fb-button green"
title={t("add criteria")}
onClick={this.commit}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>
</Col> </Col>
@ -149,7 +152,9 @@ export class AddStringCriteria
onChange={this.change} /> onChange={this.change} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button green" onClick={this.commit}> <button className="fb-button green"
title={t("add string criteria")}
onClick={this.commit}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>
</Col> </Col>
@ -182,6 +187,7 @@ export class AddNumberCriteria
<Row> <Row>
<Col xs={4}> <Col xs={4}>
<input type="string" <input type="string"
name="key"
placeholder={t("field")} placeholder={t("field")}
value={this.state.key} value={this.state.key}
onChange={this.changeKey} /> onChange={this.changeKey} />
@ -191,11 +197,14 @@ export class AddNumberCriteria
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input type="number" <input type="number"
name="value"
value={this.state.value} value={this.state.value}
onChange={this.changeValue} /> onChange={this.changeValue} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button green" onClick={this.commit}> <button className="fb-button green"
title={t("add number criteria")}
onClick={this.commit}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
</button> </button>
</Col> </Col>

View File

@ -21,12 +21,14 @@ export class GroupCriteria extends
const commonProps = { group, criteria, dispatch }; const commonProps = { group, criteria, dispatch };
return <div className="group-criteria"> return <div className="group-criteria">
<label className="criteria-heading">{t("criteria")}</label> <label className="criteria-heading">{t("criteria")}</label>
<button className="fb-button red" onClick={() => { <button className="fb-button red"
dispatch(overwrite(group, { title={t("clear all criteria")}
...group.body, criteria: DEFAULT_CRITERIA onClick={() => {
})); dispatch(overwrite(group, {
dispatch(save(group.uuid)); ...group.body, criteria: DEFAULT_CRITERIA
}}> }));
dispatch(save(group.uuid));
}}>
{t("clear all criteria")} {t("clear all criteria")}
</button> </button>
<div className="group-criteria-presets"> <div className="group-criteria-presets">
@ -60,13 +62,13 @@ export class GroupCriteria extends
export const GroupPointCountBreakdown = (props: GroupPointCountBreakdownProps) => export const GroupPointCountBreakdown = (props: GroupPointCountBreakdownProps) =>
<div className={"criteria-point-count-breakdown"}> <div className={"criteria-point-count-breakdown"}>
<div className={"manual-group-member-count"}> <div className={"manual-group-member-count"}>
<div> <div className={"manual-selection-count"}>
{props.manualCount} {props.manualCount}
</div> </div>
<p>{t("manually selected")}</p> <p>{t("manually selected")}</p>
</div> </div>
<div className={"criteria-group-member-count"}> <div className={"criteria-group-member-count"}>
<div> <div className={"criteria-selection-count"}>
{props.totalCount - props.manualCount} {props.totalCount - props.manualCount}
</div> </div>
<p>{t("selected by criteria")}</p> <p>{t("selected by criteria")}</p>

View File

@ -32,18 +32,20 @@ export class EqCriteriaSelection<T extends string | number>
{values.map((value, valueIndex) => {values.map((value, valueIndex) =>
<Row key={"" + keyIndex + valueIndex}> <Row key={"" + keyIndex + valueIndex}>
<Col xs={9}> <Col xs={9}>
<input <input name="value"
disabled={true} disabled={true}
value={value} /> value={value} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button red" onClick={() => { <button className="fb-button red"
const tempCriteriaField = cloneDeep(criteriaField); title={t("remove criteria")}
toggleEqCriteria<T>(tempCriteriaField)(key, value); onClick={() => {
dispatch(editCriteria(group, { const tempCriteriaField = cloneDeep(criteriaField);
[criteriaKey]: tempCriteriaField toggleEqCriteria<T>(tempCriteriaField)(key, value);
})); dispatch(editCriteria(group, {
}}> [criteriaKey]: tempCriteriaField
}));
}}>
<i className="fa fa-minus" /> <i className="fa fa-minus" />
</button> </button>
</Col> </Col>
@ -69,17 +71,20 @@ export const NumberCriteriaSelection = (props: NumberCriteriaProps) => {
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<input key={"" + keyIndex} <input key={"" + keyIndex}
name="value"
disabled={true} disabled={true}
value={value} /> value={value} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button red" onClick={() => { <button className="fb-button red"
const tempNumberCriteria = cloneDeep(criteriaField); title={t("remove number criteria")}
delete tempNumberCriteria[key]; onClick={() => {
props.dispatch(editCriteria(props.group, { const tempNumberCriteria = cloneDeep(criteriaField);
[props.criteriaKey]: tempNumberCriteria delete tempNumberCriteria[key];
})); props.dispatch(editCriteria(props.group, {
}}> [props.criteriaKey]: tempNumberCriteria
}));
}}>
<i className="fa fa-minus" /> <i className="fa fa-minus" />
</button> </button>
</Col> </Col>
@ -112,11 +117,12 @@ export const DaySelection = (props: CriteriaSelectionProps) => {
}))} /> }))} />
</Col> </Col>
<Col xs={3}> <Col xs={3}>
<input type="number" value={dayCriteria.days_ago} onChange={e => { <input type="number" value={dayCriteria.days_ago} name="days_ago"
const { op } = dayCriteria; onChange={e => {
const days_ago = parseInt(e.currentTarget.value); const { op } = dayCriteria;
dispatch(editCriteria(group, { day: { days_ago, op } })); const days_ago = parseInt(e.currentTarget.value);
}} /> dispatch(editCriteria(group, { day: { days_ago, op } }));
}} />
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<p>{t("days old")}</p> <p>{t("days old")}</p>
@ -136,6 +142,7 @@ export const LocationSelection = (props: LocationSelectionProps) => {
<Col xs={4}> <Col xs={4}>
<input key={JSON.stringify(gtCriteria)} <input key={JSON.stringify(gtCriteria)}
type="number" type="number"
name={`${axis}-number-gt`}
defaultValue={gtCriteria[axis]} defaultValue={gtCriteria[axis]}
onBlur={e => { onBlur={e => {
const tempGtCriteria = cloneDeep(gtCriteria); const tempGtCriteria = cloneDeep(gtCriteria);
@ -155,6 +162,7 @@ export const LocationSelection = (props: LocationSelectionProps) => {
<Col xs={4}> <Col xs={4}>
<input key={JSON.stringify(ltCriteria)} <input key={JSON.stringify(ltCriteria)}
type="number" type="number"
name={`${axis}-number-lt`}
defaultValue={ltCriteria[axis]} defaultValue={ltCriteria[axis]}
onBlur={e => { onBlur={e => {
const tempLtCriteria = cloneDeep(ltCriteria); const tempLtCriteria = cloneDeep(ltCriteria);
@ -195,14 +203,19 @@ export class AddCriteria
<Row> <Row>
<Col xs={5}> <Col xs={5}>
<input value={CRITERIA_TYPE_DDI_LOOKUP()[key].label} <input value={CRITERIA_TYPE_DDI_LOOKUP()[key].label}
name="key"
disabled={true} /> disabled={true} />
</Col> </Col>
<Col xs={5}> <Col xs={5}>
<input value={this.labelLookup(key, value)} disabled={true} /> <input value={this.labelLookup(key, value)}
name="value"
disabled={true} />
</Col> </Col>
<Col xs={2}> <Col xs={2}>
<button className="fb-button red" onClick={() => props.dispatch( <button className="fb-button red"
toggleStringCriteria(props.group, key, value))}> title={t("remove criteria")}
onClick={() => props.dispatch(
toggleStringCriteria(props.group, key, value))}>
<i className="fa fa-minus" /> <i className="fa fa-minus" />
</button> </button>
</Col> </Col>

View File

@ -50,6 +50,7 @@ export class RawGroupListPanel extends React.Component<GroupListPanelProps, Stat
linkTo={"/app/designer/plants/select"} linkTo={"/app/designer/plants/select"}
title={t("Add group")}> title={t("Add group")}>
<input type="text" <input type="text"
name="searchTerm"
onChange={this.update} onChange={this.update}
placeholder={t("Search your groups...")} /> placeholder={t("Search your groups...")} />
</DesignerPanelTop> </DesignerPanelTop>

View File

@ -47,7 +47,7 @@ describe("<EditPointName />", () => {
describe("<EditPointLocation />", () => { describe("<EditPointLocation />", () => {
const fakeProps = (): EditPointLocationProps => ({ const fakeProps = (): EditPointLocationProps => ({
updatePoint: jest.fn(), updatePoint: jest.fn(),
location: { x: 1, y: 2 }, xyLocation: { x: 1, y: 2 },
}); });
it("edits location", () => { it("edits location", () => {

View File

@ -187,7 +187,7 @@ export class RawCreatePoints
PointProperties = () => PointProperties = () =>
<ul> <ul>
<li> <li>
<div> <div className={"point-name-input"}>
<label>{t("Name")}</label> <label>{t("Name")}</label>
<BlurableInput <BlurableInput
name="name" name="name"
@ -241,6 +241,7 @@ export class RawCreatePoints
PointActions = () => PointActions = () =>
<Row> <Row>
<button className="fb-button green" <button className="fb-button green"
title={t("save")}
onClick={this.createPoint}> onClick={this.createPoint}>
{t("Save")} {t("Save")}
</button> </button>
@ -254,6 +255,7 @@ export class RawCreatePoints
? t("Delete all of the weeds created through this panel.") ? t("Delete all of the weeds created through this panel.")
: t("Delete all of the points created through this panel.")}</p> : t("Delete all of the points created through this panel.")}</p>
<button className="fb-button red delete" <button className="fb-button red delete"
title={t("delete all")}
onClick={() => { onClick={() => {
if (confirm(type === "weed" if (confirm(type === "weed"
? t("Delete all the weeds you have created?") ? t("Delete all the weeds you have created?")

View File

@ -27,7 +27,7 @@ export interface EditPointPropertiesProps {
export const EditPointProperties = (props: EditPointPropertiesProps) => export const EditPointProperties = (props: EditPointPropertiesProps) =>
<ul> <ul>
<li> <li>
<div> <div className={"point-name-input"}>
<EditPointName <EditPointName
name={props.point.body.name} name={props.point.body.name}
updatePoint={props.updatePoint} /> updatePoint={props.updatePoint} />
@ -35,7 +35,7 @@ export const EditPointProperties = (props: EditPointPropertiesProps) =>
</li> </li>
<ListItem name={t("Location")}> <ListItem name={t("Location")}>
<EditPointLocation <EditPointLocation
location={{ x: props.point.body.x, y: props.point.body.y }} xyLocation={{ x: props.point.body.x, y: props.point.body.y }}
updatePoint={props.updatePoint} /> updatePoint={props.updatePoint} />
</ListItem> </ListItem>
<ListItem name={t("Size")}> <ListItem name={t("Size")}>
@ -59,15 +59,17 @@ export interface PointActionsProps {
} }
export const PointActions = ({ x, y, z, uuid, dispatch }: PointActionsProps) => export const PointActions = ({ x, y, z, uuid, dispatch }: PointActionsProps) =>
<div> <div className={"point-actions"}>
<button <button
className="fb-button gray no-float" className="fb-button gray no-float"
type="button" type="button"
title={t("move to location")}
onClick={() => getDevice().moveAbsolute({ x, y, z })}> onClick={() => getDevice().moveAbsolute({ x, y, z })}>
{t("Move Device to location")} {t("Move Device to location")}
</button> </button>
<button <button
className="fb-button red no-float" className="fb-button red no-float"
title={t("delete")}
onClick={() => dispatch(destroy(uuid))}> onClick={() => dispatch(destroy(uuid))}>
{t("Delete")} {t("Delete")}
</button> </button>
@ -84,6 +86,7 @@ export const EditPointName = (props: EditPointNameProps) =>
<label>{t("Name")}</label> <label>{t("Name")}</label>
<BlurableInput <BlurableInput
type="text" type="text"
name="name"
value={props.name} value={props.name}
onCommit={e => props.updatePoint({ name: e.currentTarget.value })} /> onCommit={e => props.updatePoint({ name: e.currentTarget.value })} />
</Col> </Col>
@ -91,7 +94,7 @@ export const EditPointName = (props: EditPointNameProps) =>
export interface EditPointLocationProps { export interface EditPointLocationProps {
updatePoint(update: Partial<TaggedGenericPointer["body"]>): void; updatePoint(update: Partial<TaggedGenericPointer["body"]>): void;
location: Record<"x" | "y", number>; xyLocation: Record<"x" | "y", number>;
} }
export const EditPointLocation = (props: EditPointLocationProps) => export const EditPointLocation = (props: EditPointLocationProps) =>
@ -101,7 +104,8 @@ export const EditPointLocation = (props: EditPointLocationProps) =>
<label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label> <label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label>
<BlurableInput <BlurableInput
type="number" type="number"
value={props.location[axis]} name={axis}
value={props.xyLocation[axis]}
min={0} min={0}
onCommit={e => props.updatePoint({ onCommit={e => props.updatePoint({
[axis]: round(parseIntInput(e.currentTarget.value)) [axis]: round(parseIntInput(e.currentTarget.value))
@ -120,6 +124,7 @@ export const EditPointRadius = (props: EditPointRadiusProps) =>
<label style={{ marginTop: 0 }}>{t("radius (mm)")}</label> <label style={{ marginTop: 0 }}>{t("radius (mm)")}</label>
<BlurableInput <BlurableInput
type="number" type="number"
name="radius"
value={props.radius} value={props.radius}
min={0} min={0}
onCommit={e => props.updatePoint({ onCommit={e => props.updatePoint({

View File

@ -50,7 +50,7 @@ export class RawPoints extends React.Component<PointsProps, PointsState> {
panel={Panel.Points} panel={Panel.Points}
linkTo={"/app/designer/points/add"} linkTo={"/app/designer/points/add"}
title={t("Add point")}> title={t("Add point")}>
<input type="text" onChange={this.update} <input type="text" onChange={this.update} name="searchTerm"
placeholder={t("Search your points...")} /> placeholder={t("Search your points...")} />
</DesignerPanelTop> </DesignerPanelTop>
<DesignerPanelContent panelName={"points"}> <DesignerPanelContent panelName={"points"}>

View File

@ -49,7 +49,7 @@ export class RawWeeds extends React.Component<WeedsProps, WeedsState> {
panel={Panel.Weeds} panel={Panel.Weeds}
linkTo={"/app/designer/weeds/add"} linkTo={"/app/designer/weeds/add"}
title={t("Add weed")}> title={t("Add weed")}>
<input type="text" onChange={this.update} <input type="text" onChange={this.update} name="searchTerm"
placeholder={t("Search your weeds...")} /> placeholder={t("Search your weeds...")} />
</DesignerPanelTop> </DesignerPanelTop>
<DesignerPanelContent panelName={"weeds-inventory"}> <DesignerPanelContent panelName={"weeds-inventory"}>

View File

@ -12,6 +12,7 @@ jest.mock("../../../api/crud", () => ({
let mockPath = ""; let mockPath = "";
jest.mock("../../../history", () => ({ jest.mock("../../../history", () => ({
history: { push: jest.fn() },
getPathArray: jest.fn(() => mockPath.split("/")), getPathArray: jest.fn(() => mockPath.split("/")),
})); }));

View File

@ -46,14 +46,14 @@ describe("<GardenSnapshot />", () => {
wrapper.find("input").first().simulate("change", { wrapper.find("input").first().simulate("change", {
currentTarget: { value: "new name" } currentTarget: { value: "new name" }
}); });
expect(wrapper.instance().state.name).toEqual("new name"); expect(wrapper.instance().state.gardenName).toEqual("new name");
}); });
it("creates new garden", () => { it("creates new garden", () => {
const wrapper = shallow<GardenSnapshot>(<GardenSnapshot {...fakeProps()} />); const wrapper = shallow<GardenSnapshot>(<GardenSnapshot {...fakeProps()} />);
wrapper.setState({ name: "new saved garden" }); wrapper.setState({ gardenName: "new saved garden" });
wrapper.find("button").last().simulate("click"); wrapper.find("button").last().simulate("click");
expect(newSavedGarden).toHaveBeenCalledWith("new saved garden"); expect(newSavedGarden).toHaveBeenCalledWith("new saved garden");
expect(wrapper.instance().state.name).toEqual(""); expect(wrapper.instance().state.gardenName).toEqual("");
}); });
}); });

View File

@ -11,8 +11,9 @@ import { t } from "../../i18next_wrapper";
import { stopTracking } from "../../connectivity/data_consistency"; import { stopTracking } from "../../connectivity/data_consistency";
/** Save all Plant to PlantTemplates in a new SavedGarden. */ /** Save all Plant to PlantTemplates in a new SavedGarden. */
export const snapshotGarden = (name?: string | undefined) => export const snapshotGarden = (gardenName?: string | undefined) =>
axios.post<void>(API.current.snapshotPath, name ? { name } : {}) axios.post<void>(API.current.snapshotPath, gardenName
? { name: gardenName } : {})
.then(() => { .then(() => {
success(t("Garden Saved.")); success(t("Garden Saved."));
history.push("/app/designer/gardens"); history.push("/app/designer/gardens");
@ -63,9 +64,9 @@ export const openOrCloseGarden = (props: {
: props.dispatch(closeSavedGarden()); : props.dispatch(closeSavedGarden());
/** Create a new SavedGarden with the chosen name. */ /** Create a new SavedGarden with the chosen name. */
export const newSavedGarden = (name: string) => export const newSavedGarden = (gardenName: string) =>
(dispatch: Function) => { (dispatch: Function) => {
dispatch(initSave("SavedGarden", { name: name || "Untitled Garden" })) dispatch(initSave("SavedGarden", { name: gardenName || "Untitled Garden" }))
.then(() => { .then(() => {
success(t("Garden Saved.")); success(t("Garden Saved."));
history.push("/app/designer/gardens"); history.push("/app/designer/gardens");
@ -93,8 +94,8 @@ export const copySavedGarden = ({ newSGName, savedGarden, plantTemplates }: {
}) => }) =>
(dispatch: Function) => { (dispatch: Function) => {
const sourceSavedGardenId = savedGarden.body.id; const sourceSavedGardenId = savedGarden.body.id;
const name = newSGName || `${savedGarden.body.name} (${t("copy")})`; const gardenName = newSGName || `${savedGarden.body.name} (${t("copy")})`;
dispatch(initSaveGetId(savedGarden.kind, { name })) dispatch(initSaveGetId(savedGarden.kind, { name: gardenName }))
.then((newSGId: number) => { .then((newSGId: number) => {
plantTemplates plantTemplates
.filter(x => x.body.saved_garden_id === sourceSavedGardenId) .filter(x => x.body.saved_garden_id === sourceSavedGardenId)

View File

@ -13,7 +13,7 @@ import { Everything } from "../../interfaces";
import { import {
DesignerPanel, DesignerPanelHeader, DesignerPanelContent DesignerPanel, DesignerPanelHeader, DesignerPanelContent
} from "../designer_panel"; } from "../designer_panel";
import { getPathArray } from "../../history"; import { history, getPathArray } from "../../history";
import { isNumber } from "lodash"; import { isNumber } from "lodash";
import { ResourceIndex } from "../../resources/interfaces"; import { ResourceIndex } from "../../resources/interfaces";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
@ -28,6 +28,7 @@ const GardenViewButton = (props: GardenViewButtonProps) => {
: t("view"); : t("view");
return <button return <button
className={`fb-button ${gardenIsOpen ? "gray" : "yellow"}`} className={`fb-button ${gardenIsOpen ? "gray" : "yellow"}`}
title={btnText}
onClick={onClick}> onClick={onClick}>
{btnText} {btnText}
</button>; </button>;
@ -38,6 +39,7 @@ const ApplyGardenButton =
(props: { plantPointerCount: number, gardenId: number, dispatch: Function }) => (props: { plantPointerCount: number, gardenId: number, dispatch: Function }) =>
<button <button
className="fb-button green" className="fb-button green"
title={t("apply garden")}
onClick={() => props.plantPointerCount > 0 onClick={() => props.plantPointerCount > 0
? error(trim(`${t("Please clear current garden first.")} ? error(trim(`${t("Please clear current garden first.")}
(${props.plantPointerCount} ${t("plants")})`)) (${props.plantPointerCount} ${t("plants")})`))
@ -49,6 +51,7 @@ const DestroyGardenButton =
(props: { dispatch: Function, gardenUuid: string }) => (props: { dispatch: Function, gardenUuid: string }) =>
<button <button
className="fb-button red" className="fb-button red"
title={t("delete garden")}
onClick={() => props.dispatch(destroySavedGarden(props.gardenUuid))}> onClick={() => props.dispatch(destroySavedGarden(props.gardenUuid))}>
{t("delete")} {t("delete")}
</button>; </button>;
@ -75,6 +78,7 @@ export const mapStateToProps = (props: Everything): EditGardenProps => {
export class RawEditGarden extends React.Component<EditGardenProps, {}> { export class RawEditGarden extends React.Component<EditGardenProps, {}> {
render() { render() {
const { savedGarden } = this.props; const { savedGarden } = this.props;
!savedGarden && history.push("/app/designer/gardens");
return <DesignerPanel panelName={"saved-garden-edit"} return <DesignerPanel panelName={"saved-garden-edit"}
panel={Panel.SavedGardens}> panel={Panel.SavedGardens}>
<DesignerPanelHeader <DesignerPanelHeader
@ -84,7 +88,7 @@ export class RawEditGarden extends React.Component<EditGardenProps, {}> {
backTo={"/app/designer/gardens"} /> backTo={"/app/designer/gardens"} />
<DesignerPanelContent panelName={"saved-garden-edit"}> <DesignerPanelContent panelName={"saved-garden-edit"}>
{savedGarden {savedGarden
? <div> ? <div className={"saved-garden-content"}>
<Row> <Row>
<label>{t("name")}</label> <label>{t("name")}</label>
<BlurableInput <BlurableInput

View File

@ -10,44 +10,46 @@ export interface GardenSnapshotProps {
} }
interface GardenSnapshotState { interface GardenSnapshotState {
name: string; gardenName: string;
} }
/** New SavedGarden name input and snapshot/create buttons. */ /** New SavedGarden name input and snapshot/create buttons. */
export class GardenSnapshot export class GardenSnapshot
extends React.Component<GardenSnapshotProps, GardenSnapshotState> { extends React.Component<GardenSnapshotProps, GardenSnapshotState> {
state = { name: "" }; state = { gardenName: "" };
snapshot = () => { snapshot = () => {
const { currentSavedGarden, plantTemplates } = this.props; const { currentSavedGarden, plantTemplates } = this.props;
!currentSavedGarden !currentSavedGarden
? snapshotGarden(this.state.name) ? snapshotGarden(this.state.gardenName)
: this.props.dispatch(copySavedGarden({ : this.props.dispatch(copySavedGarden({
newSGName: this.state.name, newSGName: this.state.gardenName,
savedGarden: currentSavedGarden, savedGarden: currentSavedGarden,
plantTemplates plantTemplates
})); }));
this.setState({ name: "" }); this.setState({ gardenName: "" });
} }
new = () => { new = () => {
this.props.dispatch(newSavedGarden(this.state.name)); this.props.dispatch(newSavedGarden(this.state.gardenName));
this.setState({ name: "" }); this.setState({ gardenName: "" });
}; };
render() { render() {
return <div className="garden-snapshot"> return <div className="garden-snapshot">
<label>{t("name")}</label> <label>{t("name")}</label>
<input <input name="name"
onChange={e => this.setState({ name: e.currentTarget.value })} onChange={e => this.setState({ gardenName: e.currentTarget.value })}
value={this.state.name} /> value={this.state.gardenName} />
<button <button
className={"fb-button gray wide"} className={"fb-button gray wide"}
title={t("Snapshot current garden")}
onClick={this.snapshot}> onClick={this.snapshot}>
{t("Snapshot current garden")} {t("Snapshot current garden")}
</button> </button>
<button <button
className="fb-button green wide" className="fb-button green wide"
title={t("Add new garden")}
onClick={this.new}> onClick={this.new}>
{t("Add new garden")} {t("Add new garden")}
</button> </button>

View File

@ -46,7 +46,7 @@ export class RawSavedGardens
panel={Panel.SavedGardens} panel={Panel.SavedGardens}
linkTo={"/app/designer/gardens/add"} linkTo={"/app/designer/gardens/add"}
title={t("Add garden")}> title={t("Add garden")}>
<input type="text" onChange={this.onChange} <input type="text" onChange={this.onChange} name="searchTerm"
placeholder={t("Search your gardens...")} /> placeholder={t("Search your gardens...")} />
</DesignerPanelTop> </DesignerPanelTop>
<EmptyStateWrapper <EmptyStateWrapper
@ -66,6 +66,7 @@ export class RawSavedGardens
export const SavedGardensLink = () => export const SavedGardensLink = () =>
<button className="fb-button green" <button className="fb-button green"
hidden={true} hidden={true}
title={t("open saved gardens panel")}
onClick={() => history.push("/app/designer/gardens")}> onClick={() => history.push("/app/designer/gardens")}>
{t("Saved Gardens")} {t("Saved Gardens")}
</button>; </button>;
@ -80,10 +81,12 @@ export const SavedGardenHUD = (props: { dispatch: Function }) =>
<div className="saved-garden-indicator"> <div className="saved-garden-indicator">
<label>{t("Viewing saved garden")}</label> <label>{t("Viewing saved garden")}</label>
<button className="fb-button gray" <button className="fb-button gray"
title={t("open saved gardens panel")}
onClick={() => history.push("/app/designer/gardens")}> onClick={() => history.push("/app/designer/gardens")}>
{t("Menu")} {t("Menu")}
</button> </button>
<button className="fb-button green" <button className="fb-button green"
title={t("open plants panel")}
onClick={() => { onClick={() => {
history.push("/app/designer/plants"); history.push("/app/designer/plants");
unselectPlant(props.dispatch)(); unselectPlant(props.dispatch)();
@ -91,6 +94,7 @@ export const SavedGardenHUD = (props: { dispatch: Function }) =>
{t("Edit")} {t("Edit")}
</button> </button>
<button className="fb-button red" <button className="fb-button red"
title={t("close saved garden")}
onClick={() => props.dispatch(closeSavedGarden())}> onClick={() => props.dispatch(closeSavedGarden())}>
{t("Exit")} {t("Exit")}
</button> </button>

View File

@ -99,6 +99,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
return <div className={`fb-checkbox ${alreadyAdded ? "disabled" : ""}`}> return <div className={`fb-checkbox ${alreadyAdded ? "disabled" : ""}`}>
<input type="checkbox" key={JSON.stringify(this.state.toAdd)} <input type="checkbox" key={JSON.stringify(this.state.toAdd)}
title={alreadyAdded ? t("Already added.") : ""} title={alreadyAdded ? t("Already added.") : ""}
name="toolName"
checked={checked} checked={checked}
onChange={() => checked onChange={() => checked
? this.remove(toolName) ? this.remove(toolName)
@ -118,6 +119,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
</ul> </ul>
<button <button
className="fb-button green" className="fb-button green"
title={t("add selected stock names")}
onClick={() => { onClick={() => {
this.state.toAdd.filter(this.filterExisting) this.state.toAdd.filter(this.filterExisting)
.map(n => this.newTool(n)); .map(n => this.newTool(n));
@ -141,6 +143,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
<ToolSVG toolName={this.state.toolName} /> <ToolSVG toolName={this.state.toolName} />
<label>{t("Name")}</label> <label>{t("Name")}</label>
<input defaultValue={this.state.toolName} <input defaultValue={this.state.toolName}
name="name"
onChange={e => 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} />

View File

@ -226,7 +226,7 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
panel={Panel.Tools} panel={Panel.Tools}
linkTo={!hasTools ? "/app/designer/tools/add" : undefined} linkTo={!hasTools ? "/app/designer/tools/add" : undefined}
title={!hasTools ? this.strings.titleText : undefined}> title={!hasTools ? this.strings.titleText : undefined}>
<input type="text" onChange={this.update} <input type="text" onChange={this.update} name="searchTerm"
placeholder={this.strings.placeholder} /> placeholder={this.strings.placeholder} />
</DesignerPanelTop> </DesignerPanelTop>
<DesignerPanelContent panelName={"tools"}> <DesignerPanelContent panelName={"tools"}>

View File

@ -19,7 +19,7 @@ export interface GantryMountedInputProps {
export const GantryMountedInput = (props: GantryMountedInputProps) => export const GantryMountedInput = (props: GantryMountedInputProps) =>
<fieldset className="gantry-mounted-input"> <fieldset className="gantry-mounted-input">
<label>{t("Gantry-mounted")}</label> <label>{t("Gantry-mounted")}</label>
<input type="checkbox" <input type="checkbox" name="gantry_mounted"
onChange={() => props.onChange({ gantry_mounted: !props.gantryMounted })} onChange={() => props.onChange({ gantry_mounted: !props.gantryMounted })}
checked={props.gantryMounted} /> checked={props.gantryMounted} />
</fieldset>; </fieldset>;
@ -120,7 +120,7 @@ export const SlotLocationInputRow = (props: SlotLocationInputRowProps) =>
<Col xs={4} key={axis}> <Col xs={4} key={axis}>
<label>{t("{{axis}} (mm)", { axis })}</label> <label>{t("{{axis}} (mm)", { axis })}</label>
{axis == "x" && props.gantryMounted {axis == "x" && props.gantryMounted
? <input disabled value={t("Gantry")} /> ? <input disabled value={t("Gantry")} name={axis} />
: <BlurableInput : <BlurableInput
type="number" type="number"
value={props.slotLocation[axis]} value={props.slotLocation[axis]}

View File

@ -53,7 +53,7 @@ export class RawZones extends React.Component<ZonesProps, ZonesState> {
})) }))
.then((id: number) => this.navigate(id)).catch(() => { })} .then((id: number) => this.navigate(id)).catch(() => { })}
title={t("Add zone")}> title={t("Add zone")}>
<input type="text" onChange={this.update} <input type="text" onChange={this.update} name="searchTerm"
placeholder={t("Search your zones...")} /> placeholder={t("Search your zones...")} />
</DesignerPanelTop> </DesignerPanelTop>
<DesignerPanelContent panelName={"zones-inventory"}> <DesignerPanelContent panelName={"zones-inventory"}>

Some files were not shown because too many files have changed in this diff Show More