improve element annotation
parent
cccecf58f6
commit
800625e8a1
|
@ -20,7 +20,7 @@ export class DangerousDeleteWidget extends
|
|||
return <Widget>
|
||||
<WidgetHeader title={this.props.title} />
|
||||
<WidgetBody>
|
||||
<div>
|
||||
<div className={"dangerous-delete-warning-messages"}>
|
||||
{t(this.props.warning)}
|
||||
<br /><br />
|
||||
{t(this.props.confirmation)}
|
||||
|
@ -42,6 +42,7 @@ export class DangerousDeleteWidget extends
|
|||
<button
|
||||
onClick={this.onClick}
|
||||
className="red fb-button"
|
||||
title={t(this.props.title)}
|
||||
type="button">
|
||||
{t(this.props.title)}
|
||||
</button>
|
||||
|
|
|
@ -7,7 +7,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
|
|||
return <Widget>
|
||||
<WidgetHeader title={t("Export Account Data")} />
|
||||
<WidgetBody>
|
||||
<div>
|
||||
<div className={"export-account-data-description"}>
|
||||
{t(Content.EXPORT_DATA_DESC)}
|
||||
</div>
|
||||
<form>
|
||||
|
@ -19,6 +19,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
|
|||
</Col>
|
||||
<Col xs={4}>
|
||||
<button className="green fb-button" type="button"
|
||||
title={t("Export")}
|
||||
onClick={props.onClick}>
|
||||
{t("Export")}
|
||||
</button>
|
||||
|
|
|
@ -47,12 +47,13 @@ export class RawAccount extends React.Component<Props, State> {
|
|||
(key: keyof User) => (key === "email") && this.setState({ warnThem: true });
|
||||
|
||||
onChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.currentTarget;
|
||||
if (isKey(name)) {
|
||||
this.tempHack(name);
|
||||
this.props.dispatch(edit(this.props.user, { [name]: value }));
|
||||
const { value } = e.currentTarget;
|
||||
const field = e.currentTarget.name;
|
||||
if (isKey(field)) {
|
||||
this.tempHack(field);
|
||||
this.props.dispatch(edit(this.props.user, { [field]: value }));
|
||||
} else {
|
||||
throw new Error("Bad key: " + name);
|
||||
throw new Error("Bad key: " + field);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ interface LabsFeaturesListProps {
|
|||
}
|
||||
|
||||
export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
||||
return <div>
|
||||
return <div className="labs-features-list">
|
||||
{fetchLabFeatures(props.getConfigValue).map((feature, i) => {
|
||||
const displayValue = feature.displayInvert ? !feature.value : feature.value;
|
||||
return <Row key={i}>
|
||||
|
@ -23,6 +23,7 @@ export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
|||
</Col>
|
||||
<Col xs={2}>
|
||||
<ToggleButton
|
||||
title={t("toggle feature")}
|
||||
toggleValue={displayValue ? 1 : 0}
|
||||
toggleAction={() => props.onToggle(feature)
|
||||
.then(() => feature.callback && feature.callback())}
|
||||
|
|
|
@ -9,9 +9,8 @@ interface DataDumpExport { device?: DeviceAccountSettings; }
|
|||
type Response = AxiosResponse<DataDumpExport | undefined>;
|
||||
|
||||
export function generateFilename({ device }: DataDumpExport): string {
|
||||
let name: string;
|
||||
name = device ? (device.name + "_" + device.id) : "farmbot";
|
||||
return `export_${name}.json`.toLowerCase();
|
||||
const nameAndId = device ? (device.name + "_" + device.id) : "farmbot";
|
||||
return `export_${nameAndId}.json`.toLowerCase();
|
||||
}
|
||||
|
||||
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917
|
||||
|
|
|
@ -3,17 +3,19 @@ import { Row, Col } from "../ui/index";
|
|||
import { AxisDisplayGroupProps } from "./interfaces";
|
||||
import { isNumber } from "lodash";
|
||||
import { t } from "../i18next_wrapper";
|
||||
import { Xyz } from "farmbot";
|
||||
|
||||
const Axis = ({ val }: { val: number | undefined }) => <Col xs={3}>
|
||||
<input disabled value={isNumber(val) ? val : "---"} />
|
||||
</Col>;
|
||||
const Axis = ({ axis, val }: { val: number | undefined, axis: Xyz }) =>
|
||||
<Col xs={3}>
|
||||
<input disabled name={axis} value={isNumber(val) ? val : "---"} />
|
||||
</Col>;
|
||||
|
||||
export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => {
|
||||
const { x, y, z } = position;
|
||||
return <Row>
|
||||
<Axis val={x} />
|
||||
<Axis val={y} />
|
||||
<Axis val={z} />
|
||||
<Axis axis={"x"} val={x} />
|
||||
<Axis axis={"y"} val={y} />
|
||||
<Axis axis={"z"} val={z} />
|
||||
<Col xs={3}>
|
||||
<label>
|
||||
{t(label)}
|
||||
|
|
|
@ -15,12 +15,14 @@ export function KeyValEditRow(p: Props) {
|
|||
return <Row>
|
||||
<Col xs={6}>
|
||||
<input type="text"
|
||||
name="label"
|
||||
placeholder={p.labelPlaceholder}
|
||||
value={p.label}
|
||||
onChange={p.onLabelChange} />
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<input type={p.valueType}
|
||||
name="value"
|
||||
value={p.value}
|
||||
placeholder={p.valuePlaceholder}
|
||||
onChange={p.onValueChange} />
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface BotPositionRowsProps {
|
|||
|
||||
export const BotPositionRows = (props: BotPositionRowsProps) => {
|
||||
const { locationData, getValue, arduinoBusy } = props;
|
||||
return <div>
|
||||
return <div className={"bot-position-rows"}>
|
||||
<Row>
|
||||
<Col xs={3}>
|
||||
<label>{t("X AXIS")}</label>
|
||||
|
|
|
@ -22,7 +22,7 @@ export const JogControlsGroup = (props: JogControlsGroupProps) => {
|
|||
const {
|
||||
dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings
|
||||
} = props;
|
||||
return <div>
|
||||
return <div className={"jog-controls-group"}>
|
||||
<label className="text-center">
|
||||
{t("MOVE AMOUNT (mm)")}
|
||||
</label>
|
||||
|
|
|
@ -46,9 +46,9 @@ const getLastEntry = (): Entry | undefined => {
|
|||
const findYLimit = (): number => {
|
||||
const array = getArray();
|
||||
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) =>
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -80,19 +80,19 @@ const getPaths = (): Paths => {
|
|||
const paths = newPaths();
|
||||
if (last) {
|
||||
getReversedArray().map(entry => {
|
||||
["position", "scaled_encoders"].map((name: LocationName) => {
|
||||
["position", "scaled_encoders"].map((key: LocationName) => {
|
||||
["x", "y", "z"].map((axis: Xyz) => {
|
||||
const lastPos = last.locationData[name][axis];
|
||||
const pos = entry.locationData[name][axis];
|
||||
const lastPos = last.locationData[key][axis];
|
||||
const pos = entry.locationData[key][axis];
|
||||
if (isNumber(lastPos) && isFinite(lastPos)
|
||||
&& isNumber(maxY) && isNumber(pos)) {
|
||||
if (!paths[name][axis].startsWith("M")) {
|
||||
if (!paths[key][axis].startsWith("M")) {
|
||||
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 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 });
|
||||
const paths = getPaths();
|
||||
return <g id="plot_lines">
|
||||
{["position", "scaled_encoders"].map((name: LocationName) =>
|
||||
{["position", "scaled_encoders"].map((key: LocationName) =>
|
||||
["x", "y", "z"].map((axis: Xyz) =>
|
||||
<path key={name + axis} fill={"none"}
|
||||
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[name]}
|
||||
<path key={key + axis} fill={"none"}
|
||||
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[key]}
|
||||
strokeLinecap={"round"} strokeLinejoin={"round"}
|
||||
d={paths[name][axis]} />))}
|
||||
d={paths[key][axis]} />))}
|
||||
</g>;
|
||||
};
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ export const MoveWidgetSettingsMenu = (
|
|||
setting={BooleanSetting.home_button_homing} />
|
||||
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<div>
|
||||
<div className={"motor-position-plot-setting-row"}>
|
||||
<p>{t("Motor position plot")}</p>
|
||||
<Setting
|
||||
label={t("show")}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { StepSizeSelectorProps } from "./interfaces";
|
||||
import { first, last } from "lodash";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}> {
|
||||
cssForIndex(num: number) {
|
||||
|
@ -22,10 +23,10 @@ export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}>
|
|||
return <div className="move-amount-wrapper">
|
||||
{
|
||||
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)}
|
||||
onClick={() => this.props.selector(item)}
|
||||
key={inx}>
|
||||
onClick={() => this.props.selector(item)}>
|
||||
{item}
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -86,15 +86,17 @@ export class Peripherals
|
|||
render() {
|
||||
const { isEditing } = this.state;
|
||||
const status = getArrayStatus(this.props.peripherals);
|
||||
|
||||
const editButtonText = isEditing
|
||||
? t("Back")
|
||||
: t("Edit");
|
||||
return <Widget className="peripherals-widget">
|
||||
<WidgetHeader title={t("Peripherals")} helpText={ToolTips.PERIPHERALS}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
onClick={this.toggle}
|
||||
title={editButtonText}
|
||||
disabled={!!status && isEditing}>
|
||||
{!isEditing && t("Edit")}
|
||||
{isEditing && t("Back")}
|
||||
{editButtonText}
|
||||
</button>
|
||||
<SaveBtn
|
||||
hidden={!isEditing}
|
||||
|
@ -104,6 +106,7 @@ export class Peripherals
|
|||
hidden={!isEditing}
|
||||
className="fb-button green"
|
||||
type="button"
|
||||
title={t("add peripheral")}
|
||||
onClick={() => this.newPeripheral()}>
|
||||
<i className="fa fa-plus" />
|
||||
</button>
|
||||
|
@ -111,6 +114,7 @@ export class Peripherals
|
|||
hidden={!isEditing || this.props.firmwareHardware == "none"}
|
||||
className="fb-button green"
|
||||
type="button"
|
||||
title={t("add stock peripherals")}
|
||||
onClick={() => this.stockPeripherals.map(p =>
|
||||
this.newPeripheral(p.pin, p.label))}>
|
||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||
|
|
|
@ -22,6 +22,7 @@ interface NameInputBoxProps {
|
|||
|
||||
export const NameInputBox = (props: NameInputBoxProps) =>
|
||||
<input type="text"
|
||||
name="name"
|
||||
placeholder={t("Name")}
|
||||
value={props.value}
|
||||
onChange={e => props.dispatch(edit(props.resource, {
|
||||
|
|
|
@ -25,11 +25,11 @@ describe("filterSensorReadings()", () => {
|
|||
});
|
||||
|
||||
const createLocatedReading = (locations: Record<Xyz, number | undefined>[]) =>
|
||||
locations.map(location => {
|
||||
locations.map(xyzLocation => {
|
||||
const sr = fakeSensorReading();
|
||||
sr.body.x = location.x;
|
||||
sr.body.y = location.y;
|
||||
sr.body.z = location.z;
|
||||
sr.body.x = xyzLocation.x;
|
||||
sr.body.y = xyzLocation.y;
|
||||
sr.body.z = xyzLocation.z;
|
||||
sr.body.created_at = defaultCreatedAt;
|
||||
return sr;
|
||||
});
|
||||
|
@ -38,7 +38,7 @@ describe("filterSensorReadings()", () => {
|
|||
sensor: undefined,
|
||||
timePeriod: 3600 * 24 * 7,
|
||||
endDate: 1515715140,
|
||||
location: undefined,
|
||||
xyzLocation: undefined,
|
||||
showPreviousPeriod: false,
|
||||
deviation: 0,
|
||||
hovered: undefined,
|
||||
|
@ -87,7 +87,7 @@ describe("filterSensorReadings()", () => {
|
|||
const locations = [expected, { x: 0, y: 0, z: 0 }];
|
||||
const filters = sensorReadingsState();
|
||||
filters.endDate = moment(defaultCreatedAt).unix() + 1;
|
||||
filters.location = expected;
|
||||
filters.xyzLocation = expected;
|
||||
const result = filterSensorReadings(
|
||||
createLocatedReading(locations), filters)("current");
|
||||
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 filters = sensorReadingsState();
|
||||
filters.endDate = moment(defaultCreatedAt).unix() + 1;
|
||||
filters.location = expected;
|
||||
filters.xyzLocation = expected;
|
||||
filters.deviation = 100;
|
||||
const result = filterSensorReadings(
|
||||
createLocatedReading(locations), filters)("current");
|
||||
|
|
|
@ -6,7 +6,7 @@ import { LocationSelectionProps } from "../interfaces";
|
|||
describe("<LocationSelection />", () => {
|
||||
function fakeProps(): LocationSelectionProps {
|
||||
return {
|
||||
location: undefined,
|
||||
xyzLocation: undefined,
|
||||
deviation: 0,
|
||||
setLocation: jest.fn(),
|
||||
setDeviation: jest.fn(),
|
||||
|
@ -22,7 +22,7 @@ describe("<LocationSelection />", () => {
|
|||
|
||||
it("changes location", () => {
|
||||
const p = fakeProps();
|
||||
p.location = { x: 10, y: 20, z: 30 };
|
||||
p.xyzLocation = { x: 10, y: 20, z: 30 };
|
||||
const wrapper = mount(<LocationSelection {...p} />);
|
||||
wrapper.find("input").first().simulate("submit");
|
||||
expect(p.setLocation).toHaveBeenCalledWith({ x: 10, y: 20, z: 30 });
|
||||
|
@ -30,7 +30,7 @@ describe("<LocationSelection />", () => {
|
|||
|
||||
it("changes location: undefined", () => {
|
||||
const p = fakeProps();
|
||||
p.location = undefined;
|
||||
p.xyzLocation = undefined;
|
||||
const wrapper = mount(<LocationSelection {...p} />);
|
||||
wrapper.find("input").first().simulate("submit");
|
||||
expect(p.setLocation).toHaveBeenCalledWith({ x: undefined });
|
||||
|
@ -47,7 +47,7 @@ describe("<LocationSelection />", () => {
|
|||
|
||||
describe("<LocationDisplay />", () => {
|
||||
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 txt = wrapper.text().toLowerCase();
|
||||
["x", "y", "z", "8–12", "18–22", "28–32"]
|
||||
|
|
|
@ -43,9 +43,9 @@ describe("<SensorReadings />", () => {
|
|||
it("sets location", () => {
|
||||
const expectedLocation = { x: 1, y: 2, z: undefined };
|
||||
const wrapper = mount<SensorReadings>(<SensorReadings {...fakeProps()} />);
|
||||
expect(wrapper.instance().state.location).toEqual(undefined);
|
||||
expect(wrapper.instance().state.xyzLocation).toEqual(undefined);
|
||||
wrapper.instance().setLocation(expectedLocation);
|
||||
expect(wrapper.instance().state.location).toEqual(expectedLocation);
|
||||
expect(wrapper.instance().state.xyzLocation).toEqual(expectedLocation);
|
||||
});
|
||||
|
||||
it("sets end date", () => {
|
||||
|
@ -87,9 +87,9 @@ describe("<SensorReadings />", () => {
|
|||
const p = fakeProps();
|
||||
p.sensors = [s];
|
||||
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();
|
||||
expect(wrapper.instance().state.location).toEqual(undefined);
|
||||
expect(wrapper.instance().state.xyzLocation).toEqual(undefined);
|
||||
expect(wrapper.instance().state.sensor).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ export const filterSensorReadings =
|
|||
sensorReadingsState: SensorReadingsState) =>
|
||||
(period: "current" | "previous"): TaggedSensorReading[] => {
|
||||
const {
|
||||
sensor, endDate, timePeriod, showPreviousPeriod, location, deviation
|
||||
sensor, endDate, timePeriod, showPreviousPeriod, xyzLocation, deviation
|
||||
} = sensorReadingsState;
|
||||
|
||||
// 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 by location
|
||||
.filter(sensorReading => {
|
||||
if (location) {
|
||||
if (xyzLocation) {
|
||||
const { body } = sensorReading;
|
||||
return every(["x", "y", "z"].map((axis: Xyz) => {
|
||||
const a = body[axis];
|
||||
const input = location[axis];
|
||||
const input = xyzLocation[axis];
|
||||
return isNumber(a) && isNumber(input)
|
||||
? (a <= input + deviation) && (a >= input - deviation)
|
||||
: true;
|
||||
|
|
|
@ -15,7 +15,7 @@ export interface SensorReadingsState {
|
|||
/** seconds */
|
||||
endDate: number;
|
||||
/** location filter setting */
|
||||
location: AxisInputBoxGroupState | undefined;
|
||||
xyzLocation: AxisInputBoxGroupState | undefined;
|
||||
/** Show the previous time period in addition to the current time period. */
|
||||
showPreviousPeriod: boolean;
|
||||
/** mm */
|
||||
|
@ -50,11 +50,11 @@ export interface SensorSelectionProps {
|
|||
}
|
||||
|
||||
export interface LocationSelectionProps {
|
||||
location: AxisInputBoxGroupState | undefined;
|
||||
xyzLocation: AxisInputBoxGroupState | undefined;
|
||||
/** mm */
|
||||
deviation: number;
|
||||
setDeviation: (deviation: number) => void;
|
||||
setLocation: (location: AxisInputBoxGroupState | undefined) => void;
|
||||
setLocation: (xyzLocation: AxisInputBoxGroupState | undefined) => void;
|
||||
}
|
||||
|
||||
export interface TimePeriodSelectionProps {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { t } from "../../i18next_wrapper";
|
|||
|
||||
/** Select a location filter for sensor readings. */
|
||||
export const LocationSelection =
|
||||
({ location, deviation, setDeviation, setLocation }: LocationSelectionProps) =>
|
||||
({ xyzLocation, deviation, setDeviation, setLocation }: LocationSelectionProps) =>
|
||||
<div className="sensor-history-location-selection">
|
||||
<Row>
|
||||
{["x", "y", "z"].map(axis =>
|
||||
|
@ -26,9 +26,9 @@ export const LocationSelection =
|
|||
<AxisInputBox
|
||||
key={axis}
|
||||
axis={axis}
|
||||
value={location ? location[axis] : undefined}
|
||||
value={xyzLocation ? xyzLocation[axis] : undefined}
|
||||
onChange={(a: Xyz, v) => {
|
||||
const newLocation = (location || {});
|
||||
const newLocation = (xyzLocation || {});
|
||||
newLocation[a] = v;
|
||||
setLocation(newLocation);
|
||||
}} />)}
|
||||
|
@ -42,15 +42,15 @@ export const LocationSelection =
|
|||
</div>;
|
||||
|
||||
/** Display sensor reading location filter settings. */
|
||||
export const LocationDisplay = ({ location, deviation }: {
|
||||
location: AxisInputBoxGroupState | undefined,
|
||||
export const LocationDisplay = ({ xyzLocation, deviation }: {
|
||||
xyzLocation: AxisInputBoxGroupState | undefined,
|
||||
deviation: number
|
||||
}) => {
|
||||
return <div className="location">
|
||||
{["x", "y", "z"].map((axis: Xyz) => {
|
||||
const axisString = () => {
|
||||
if (location) {
|
||||
const axisValue = location[axis];
|
||||
if (xyzLocation) {
|
||||
const axisValue = xyzLocation[axis];
|
||||
if (isNumber(axisValue)) {
|
||||
return deviation
|
||||
? `${axisValue - deviation}–${axisValue + deviation}`
|
||||
|
|
|
@ -20,7 +20,7 @@ export class SensorReadings
|
|||
sensor: undefined,
|
||||
timePeriod: 3600 * 24,
|
||||
endDate: getEndDate(this.props.sensorReadings),
|
||||
location: undefined,
|
||||
xyzLocation: undefined,
|
||||
showPreviousPeriod: false,
|
||||
deviation: 0,
|
||||
hovered: undefined,
|
||||
|
@ -32,15 +32,15 @@ export class SensorReadings
|
|||
setSensor = (sensor: TaggedSensor | undefined) => this.setState({ sensor });
|
||||
setEndDate = (endDate: number) => this.setState({ endDate });
|
||||
setTimePeriod = (timePeriod: number) => this.setState({ timePeriod });
|
||||
setLocation = (location: AxisInputBoxGroupState | undefined) =>
|
||||
this.setState({ location });
|
||||
setLocation = (xyzLocation: AxisInputBoxGroupState | undefined) =>
|
||||
this.setState({ xyzLocation });
|
||||
setDeviation = (deviation: number) => this.setState({ deviation });
|
||||
hover = (hovered: string | undefined) => this.setState({ hovered });
|
||||
clearFilters = () => this.setState({
|
||||
sensor: undefined,
|
||||
timePeriod: 3600 * 24,
|
||||
endDate: getEndDate(this.props.sensorReadings),
|
||||
location: undefined,
|
||||
xyzLocation: undefined,
|
||||
showPreviousPeriod: false,
|
||||
deviation: 0,
|
||||
});
|
||||
|
@ -55,7 +55,9 @@ export class SensorReadings
|
|||
<WidgetHeader
|
||||
title={t("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")}
|
||||
</button>
|
||||
</WidgetHeader>
|
||||
|
@ -72,7 +74,7 @@ export class SensorReadings
|
|||
setPeriod={this.setTimePeriod}
|
||||
togglePrevious={this.togglePrevious} />
|
||||
<LocationSelection
|
||||
location={this.state.location}
|
||||
xyzLocation={this.state.xyzLocation}
|
||||
deviation={this.state.deviation}
|
||||
setLocation={this.setLocation}
|
||||
setDeviation={this.setDeviation} />
|
||||
|
@ -100,7 +102,7 @@ export class SensorReadings
|
|||
timePeriod={this.state.timePeriod}
|
||||
timeSettings={this.props.timeSettings} />
|
||||
<LocationDisplay
|
||||
location={this.state.location}
|
||||
xyzLocation={this.state.xyzLocation}
|
||||
deviation={this.state.deviation} />
|
||||
</div>
|
||||
</WidgetFooter>
|
||||
|
|
|
@ -74,6 +74,7 @@ export const TimePeriodSelection = (props: TimePeriodSelectionProps) => {
|
|||
<Col xs={ColWidth.showPrevious}>
|
||||
<div className="fb-checkbox large">
|
||||
<input type="checkbox"
|
||||
name="previous"
|
||||
checked={showPreviousPeriod}
|
||||
onChange={togglePrevious} />
|
||||
</div>
|
||||
|
|
|
@ -58,15 +58,17 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
|
|||
render() {
|
||||
const { isEditing } = this.state;
|
||||
const status = getArrayStatus(this.props.sensors);
|
||||
|
||||
const editButtonText = isEditing
|
||||
? t("Back")
|
||||
: t("Edit");
|
||||
return <Widget className="sensors-widget">
|
||||
<WidgetHeader title={t("Sensors")} helpText={ToolTips.SENSORS}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
onClick={this.toggle}
|
||||
title={editButtonText}
|
||||
disabled={!!status && isEditing}>
|
||||
{!isEditing && t("Edit")}
|
||||
{isEditing && t("Back")}
|
||||
{editButtonText}
|
||||
</button>
|
||||
<SaveBtn
|
||||
hidden={!isEditing}
|
||||
|
@ -76,6 +78,7 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
|
|||
hidden={!isEditing}
|
||||
className="fb-button green"
|
||||
type="button"
|
||||
title={t("add sensors")}
|
||||
onClick={() => this.newSensor()}>
|
||||
<i className="fa fa-plus" />
|
||||
</button>
|
||||
|
@ -83,6 +86,7 @@ export class Sensors extends React.Component<SensorsProps, SensorState> {
|
|||
hidden={!isEditing || this.props.firmwareHardware == "none"}
|
||||
className="fb-button green"
|
||||
type="button"
|
||||
title={t("add stock sensors")}
|
||||
onClick={this.stockSensors}>
|
||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||
{t("Stock sensors")}
|
||||
|
|
|
@ -40,6 +40,6 @@ describe("<IndexIndicator/>", () => {
|
|||
|
||||
it("doesn't render index indicator", () => {
|
||||
const wrapper = mount(<IndexIndicator i={0} total={1} />);
|
||||
expect(wrapper.html()).toEqual("<div></div>");
|
||||
expect(wrapper.html()).toEqual("<div class=\"no-index-indicator\"></div>");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,16 +32,19 @@ export function Edit(props: WebcamPanelProps) {
|
|||
<button
|
||||
className="fb-button gray"
|
||||
disabled={unsaved.length > 0}
|
||||
title={t("Back")}
|
||||
onClick={props.onToggle}>
|
||||
{t("Back")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button green"
|
||||
title={t("Save")}
|
||||
onClick={() => { unsaved.map(x => props.save(x)); }}>
|
||||
{t("Save")}{unsaved.length > 0 ? "*" : ""}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button green"
|
||||
title={t("Add webcam")}
|
||||
onClick={props.init}>
|
||||
<i className="fa fa-plus" />
|
||||
</button>
|
||||
|
|
|
@ -25,7 +25,7 @@ export function IndexIndicator(props: { i: number, total: number }): JSX.Element
|
|||
style={{
|
||||
width: `${percentWidth}%`,
|
||||
left: `calc(-10px + ${props.i} * ${percentWidth}%)`
|
||||
}} /> : <div />;
|
||||
}} /> : <div className={"no-index-indicator"} />;
|
||||
}
|
||||
|
||||
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}>
|
||||
<button
|
||||
className="fb-button gray"
|
||||
title={t("Edit")}
|
||||
onClick={props.onToggle}>
|
||||
{t("Edit")}
|
||||
</button>
|
||||
|
@ -74,6 +75,7 @@ export class Show extends React.Component<WebcamPanelProps, State> {
|
|||
onClick={() => flipper.down((_, current) => this.setState({ current }))}
|
||||
hidden={feeds.length < 2}
|
||||
disabled={false}
|
||||
title={t("Previous image")}
|
||||
className="image-flipper-left fb-button">
|
||||
{t("Prev")}
|
||||
</button>
|
||||
|
@ -81,6 +83,7 @@ export class Show extends React.Component<WebcamPanelProps, State> {
|
|||
onClick={() => flipper.up((_, current) => this.setState({ current }))}
|
||||
hidden={feeds.length < 2}
|
||||
disabled={false}
|
||||
title={t("Next image")}
|
||||
className="image-flipper-right fb-button">
|
||||
{t("Next")}
|
||||
</button>
|
||||
|
|
|
@ -945,7 +945,9 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
.clear-button {
|
||||
.preview-button,
|
||||
.cancel-button,
|
||||
.save-button {
|
||||
text-transform: uppercase;
|
||||
font-size: 1rem;
|
||||
border: 1px solid;
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
import { uuid } from "farmbot";
|
||||
import axios from "axios";
|
||||
import { ExternalUrl } from "../external_urls";
|
||||
import { t } from "../i18next_wrapper";
|
||||
|
||||
interface State {
|
||||
error: Error | undefined;
|
||||
|
@ -23,7 +24,7 @@ export const WAITING_ON_API = "Planting your demo garden...";
|
|||
// APPLICATION CODE ==============================
|
||||
export class DemoIframe extends React.Component<{}, State> {
|
||||
state: State =
|
||||
{ error: undefined, stage: "DEMO THE APP" };
|
||||
{ error: undefined, stage: t("DEMO THE APP") };
|
||||
|
||||
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" />
|
||||
</video>
|
||||
<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}
|
||||
</button>
|
||||
</div>;
|
||||
|
|
|
@ -285,13 +285,13 @@ export function MCUFactoryReset() {
|
|||
|
||||
/** Toggle a firmware setting. */
|
||||
export function settingToggle(
|
||||
name: ConfigKey,
|
||||
key: ConfigKey,
|
||||
sourceFwConfig: SourceFwConfig,
|
||||
displayAlert?: string | undefined
|
||||
) {
|
||||
return function (dispatch: Function, getState: () => Everything) {
|
||||
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 toggleFirmwareConfig = (fwConfig: TaggedFirmwareConfig) => {
|
||||
dispatch(edit(fwConfig, update));
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
describe("<PinGuardMCUInputGroup/>", () => {
|
||||
const fakeProps = (): PinGuardMCUInputGroupProps => {
|
||||
return {
|
||||
name: "Pin Guard 1",
|
||||
label: "Pin Guard 1",
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -17,7 +17,7 @@ import { updateMCU } from "../../actions";
|
|||
describe("<PinNumberDropdown />", () => {
|
||||
const fakeProps =
|
||||
(firmwareConfig?: TaggedFirmwareConfig): PinGuardMCUInputGroupProps => ({
|
||||
name: "Pin Guard 1",
|
||||
label: "Pin Guard 1",
|
||||
pinNumKey: "pin_guard_1_pin_nr",
|
||||
timeoutKey: "pin_guard_1_time_out",
|
||||
activeStateKey: "pin_guard_1_active_state",
|
||||
|
|
|
@ -110,11 +110,9 @@ export class FarmbotOsSettings
|
|||
<div className="note">
|
||||
{this.maybeWarnTz()}
|
||||
</div>
|
||||
<div>
|
||||
<TimezoneSelector
|
||||
currentTimezone={this.props.deviceAccount.body.timezone}
|
||||
onUpdate={this.handleTimezone} />
|
||||
</div>
|
||||
<TimezoneSelector
|
||||
currentTimezone={this.props.deviceAccount.body.timezone}
|
||||
onUpdate={this.handleTimezone} />
|
||||
</Col>
|
||||
</Highlight>
|
||||
</Row>
|
||||
|
|
|
@ -56,14 +56,12 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
|||
</label>
|
||||
</Col>
|
||||
<Col xs={ColWidth.description}>
|
||||
<div>
|
||||
<FBSelect
|
||||
key={this.apiValue}
|
||||
extraClass={this.state.sending ? "dim" : ""}
|
||||
list={getFirmwareChoices()}
|
||||
selectedItem={this.selectedBoard}
|
||||
onChange={this.sendOffConfig} />
|
||||
</div>
|
||||
<FBSelect
|
||||
key={this.apiValue}
|
||||
extraClass={this.state.sending ? "dim" : ""}
|
||||
list={getFirmwareChoices()}
|
||||
selectedItem={this.selectedBoard}
|
||||
onChange={this.sendOffConfig} />
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<FirmwareHardwareStatus
|
||||
|
|
|
@ -92,14 +92,12 @@ export class CameraSelection
|
|||
</label>
|
||||
</Col>
|
||||
<Col xs={ColWidth.description}>
|
||||
<div>
|
||||
<FBSelect
|
||||
allowEmpty={false}
|
||||
list={CAMERA_CHOICES()}
|
||||
selectedItem={this.selectedCamera()}
|
||||
onChange={this.sendOffConfig}
|
||||
extraClass={this.props.botOnline ? "" : "disabled"} />
|
||||
</div>
|
||||
<FBSelect
|
||||
allowEmpty={false}
|
||||
list={CAMERA_CHOICES()}
|
||||
selectedItem={this.selectedCamera()}
|
||||
onChange={this.sendOffConfig}
|
||||
extraClass={this.props.botOnline ? "" : "disabled"} />
|
||||
</Col>
|
||||
</Highlight>
|
||||
</Row>;
|
||||
|
|
|
@ -90,6 +90,7 @@ export class ChangeOwnershipForm
|
|||
<Row>
|
||||
<button
|
||||
className={"fb-button gray"}
|
||||
title={t("submit")}
|
||||
onClick={() => submitOwnershipChange(this.state)}>
|
||||
{t("submit")}
|
||||
</button>
|
||||
|
|
|
@ -4,16 +4,16 @@ import { Content, DeviceSetting } from "../../../constants";
|
|||
import { factoryReset, updateConfig } from "../../actions";
|
||||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||
import { FactoryResetRowProps } from "./interfaces";
|
||||
import { FactoryResetRowsProps } from "./interfaces";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
import { t } from "../../../i18next_wrapper";
|
||||
import { Highlight } from "../maybe_highlight";
|
||||
|
||||
export function FactoryResetRow(props: FactoryResetRowProps) {
|
||||
export function FactoryResetRows(props: FactoryResetRowsProps) {
|
||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||
const disableFactoryReset = sourceFbosConfig("disable_factory_reset");
|
||||
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
|
||||
return <div>
|
||||
return <div className={"factory-reset-options"}>
|
||||
<Row>
|
||||
<Highlight settingName={DeviceSetting.factoryReset}>
|
||||
<Col xs={ColWidth.label}>
|
||||
|
@ -31,6 +31,7 @@ export function FactoryResetRow(props: FactoryResetRowProps) {
|
|||
className="fb-button red"
|
||||
type="button"
|
||||
onClick={factoryReset}
|
||||
title={t("FACTORY RESET")}
|
||||
disabled={!botOnline}>
|
||||
{t("FACTORY RESET")}
|
||||
</button>
|
||||
|
|
|
@ -32,6 +32,7 @@ export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
|||
className={`fb-button ${props.color}`}
|
||||
type="button"
|
||||
onClick={props.action}
|
||||
title={t(props.buttonText)}
|
||||
disabled={!props.botOnline}>
|
||||
{t(props.buttonText)}
|
||||
</button>
|
||||
|
|
|
@ -267,7 +267,7 @@ export function FbosDetails(props: FbosDetailsProps) {
|
|||
const infoFwCommit = firmware_version?.includes(".") ? firmware_commit : "---";
|
||||
const firmwareCommit = firmware_version?.split("-")[1] || infoFwCommit;
|
||||
|
||||
return <div>
|
||||
return <div className={"farmbot-os-details"}>
|
||||
<LastSeen
|
||||
dispatch={props.dispatch}
|
||||
botToMqttLastSeen={props.botToMqttLastSeen}
|
||||
|
|
|
@ -49,6 +49,7 @@ export const FlashFirmwareBtn = (props: FlashFirmwareBtnProps) => {
|
|||
const { apiFirmwareValue } = props;
|
||||
return <button className="fb-button yellow"
|
||||
disabled={!apiFirmwareValue || !props.botOnline}
|
||||
title={t("flash firmware")}
|
||||
onClick={() => isFwHardwareValue(apiFirmwareValue) &&
|
||||
flashFirmware(apiFirmwareValue)}>
|
||||
{t("flash firmware")}
|
||||
|
|
|
@ -59,7 +59,7 @@ export interface PowerAndResetProps {
|
|||
botOnline: boolean;
|
||||
}
|
||||
|
||||
export interface FactoryResetRowProps {
|
||||
export interface FactoryResetRowsProps {
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
botOnline: boolean;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Header } from "../hardware_settings/header";
|
||||
import { Collapse, Popover, Position } from "@blueprintjs/core";
|
||||
import { FactoryResetRow } from "./factory_reset_row";
|
||||
import { FactoryResetRows } from "./factory_reset_row";
|
||||
import { PowerAndResetProps } from "./interfaces";
|
||||
import { ChangeOwnershipForm } from "./change_ownership_form";
|
||||
import { FbosButtonRow } from "./fbos_button_row";
|
||||
|
@ -42,7 +42,7 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
|||
buttonText={t("RESTART")}
|
||||
color={"yellow"}
|
||||
action={restartFirmware} />
|
||||
<FactoryResetRow
|
||||
<FactoryResetRows
|
||||
dispatch={dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
botOnline={botOnline} />
|
||||
|
|
|
@ -45,11 +45,13 @@ export class HardwareSettings extends
|
|||
<WidgetBody>
|
||||
<button
|
||||
className={"fb-button gray no-float"}
|
||||
title={t("Expand All")}
|
||||
onClick={() => dispatch(bulkToggleControlPanel(true))}>
|
||||
{t("Expand All")}
|
||||
</button>
|
||||
<button
|
||||
className={"fb-button gray no-float"}
|
||||
title={t("Collapse All")}
|
||||
onClick={() => dispatch(bulkToggleControlPanel(false))}>
|
||||
{t("Collapse All")}
|
||||
</button>
|
||||
|
|
|
@ -27,6 +27,7 @@ export function CalibrationRow(props: CalibrationRowProps) {
|
|||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
||||
<LockableButton
|
||||
disabled={hardwareDisabled || botDisconnected}
|
||||
title={t(props.axisTitle)}
|
||||
onClick={() => props.action(axis)}>
|
||||
{`${t(props.axisTitle)} ${axis}`}
|
||||
</LockableButton>
|
||||
|
|
|
@ -36,6 +36,7 @@ export function DangerZone(props: DangerZoneProps) {
|
|||
<button
|
||||
className="fb-button red"
|
||||
disabled={botDisconnected}
|
||||
title={t("RESET")}
|
||||
onClick={onReset}>
|
||||
{t("RESET")}
|
||||
</button>
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Highlight } from "../maybe_highlight";
|
|||
|
||||
export const calculateScale =
|
||||
(sourceFwConfig: SourceFwConfig): Record<Xyz, number | undefined> => {
|
||||
const getV = (name: McuParamName) => sourceFwConfig(name).value;
|
||||
const getV = (key: McuParamName) => sourceFwConfig(key).value;
|
||||
return {
|
||||
x: calcMicrostepsPerMm(getV("movement_step_per_mm_x"),
|
||||
getV("movement_microsteps_x")),
|
||||
|
|
|
@ -41,7 +41,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
</Col>
|
||||
</Row>
|
||||
<PinGuardMCUInputGroup
|
||||
name={t("Pin Guard {{ num }}", { num: 1 })}
|
||||
label={t("Pin Guard {{ num }}", { num: 1 })}
|
||||
pinNumKey={"pin_guard_1_pin_nr"}
|
||||
timeoutKey={"pin_guard_1_time_out"}
|
||||
activeStateKey={"pin_guard_1_active_state"}
|
||||
|
@ -49,7 +49,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
name={t("Pin Guard {{ num }}", { num: 2 })}
|
||||
label={t("Pin Guard {{ num }}", { num: 2 })}
|
||||
pinNumKey={"pin_guard_2_pin_nr"}
|
||||
timeoutKey={"pin_guard_2_time_out"}
|
||||
activeStateKey={"pin_guard_2_active_state"}
|
||||
|
@ -57,7 +57,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
name={t("Pin Guard {{ num }}", { num: 3 })}
|
||||
label={t("Pin Guard {{ num }}", { num: 3 })}
|
||||
pinNumKey={"pin_guard_3_pin_nr"}
|
||||
timeoutKey={"pin_guard_3_time_out"}
|
||||
activeStateKey={"pin_guard_3_active_state"}
|
||||
|
@ -65,7 +65,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
name={t("Pin Guard {{ num }}", { num: 4 })}
|
||||
label={t("Pin Guard {{ num }}", { num: 4 })}
|
||||
pinNumKey={"pin_guard_4_pin_nr"}
|
||||
timeoutKey={"pin_guard_4_time_out"}
|
||||
activeStateKey={"pin_guard_4_active_state"}
|
||||
|
@ -73,7 +73,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
resources={resources}
|
||||
sourceFwConfig={sourceFwConfig} />
|
||||
<PinGuardMCUInputGroup
|
||||
name={t("Pin Guard {{ num }}", { num: 5 })}
|
||||
label={t("Pin Guard {{ num }}", { num: 5 })}
|
||||
pinNumKey={"pin_guard_5_pin_nr"}
|
||||
timeoutKey={"pin_guard_5_time_out"}
|
||||
activeStateKey={"pin_guard_5_active_state"}
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface NumericMCUInputGroupProps {
|
|||
export interface PinGuardMCUInputGroupProps {
|
||||
sourceFwConfig: SourceFwConfig;
|
||||
dispatch: Function;
|
||||
name: string;
|
||||
label: string;
|
||||
pinNumKey: McuParamName;
|
||||
timeoutKey: McuParamName;
|
||||
activeStateKey: McuParamName;
|
||||
|
|
|
@ -4,13 +4,15 @@ interface Props {
|
|||
onClick: Function;
|
||||
disabled: boolean;
|
||||
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";
|
||||
return <button
|
||||
className={"fb-button " + className}
|
||||
disabled={disabled}
|
||||
title={title}
|
||||
onClick={() => disabled ? "" : onClick()}>
|
||||
{children}
|
||||
</button>;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { PinNumberDropdown } from "./pin_number_dropdown";
|
|||
|
||||
export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) {
|
||||
|
||||
const { sourceFwConfig, dispatch, name, pinNumKey, timeoutKey, activeStateKey
|
||||
const { sourceFwConfig, dispatch, label, pinNumKey, timeoutKey, activeStateKey
|
||||
} = props;
|
||||
const activeStateValue = sourceFwConfig(activeStateKey).value;
|
||||
const inactiveState = isUndefined(activeStateValue)
|
||||
|
@ -19,7 +19,7 @@ export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) {
|
|||
return <Row>
|
||||
<Col xs={3}>
|
||||
<label>
|
||||
{name}
|
||||
{label}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
|
|
@ -26,8 +26,8 @@ export class Connectivity
|
|||
extends React.Component<ConnectivityProps, ConnectivityState> {
|
||||
state: ConnectivityState = { hoveredConnection: undefined };
|
||||
|
||||
hover = (name: string) =>
|
||||
() => this.setState({ hoveredConnection: name });
|
||||
hover = (connectionName: string) =>
|
||||
() => this.setState({ hoveredConnection: connectionName });
|
||||
|
||||
render() {
|
||||
const { informational_settings } = this.props.bot.hardware;
|
||||
|
|
|
@ -28,7 +28,7 @@ export function Diagnosis(props: DiagnosisProps) {
|
|||
const diagnosisBoolean = diagnosisStatus(props);
|
||||
const diagnosisColor = diagnosisBoolean ? "green" : "red";
|
||||
const title = diagnosisBoolean ? t("Ok") : t("Error");
|
||||
return <div>
|
||||
return <div className={"diagnosis-section"}>
|
||||
<div className={"connectivity-diagnosis"}>
|
||||
<h4>{t("Diagnosis")}</h4>
|
||||
</div>
|
||||
|
|
|
@ -49,8 +49,9 @@ const diagramPositions: CowardlyDictionary<Record<"x" | "y", number>> = {
|
|||
subRight: { x: 40, y: 110 }
|
||||
};
|
||||
|
||||
export function getTextPosition(name: DiagramNodes): Record<"x" | "y", number> {
|
||||
const position = diagramPositions[name];
|
||||
export function getTextPosition(
|
||||
positionKey: DiagramNodes): Record<"x" | "y", number> {
|
||||
const position = diagramPositions[positionKey];
|
||||
if (position) {
|
||||
return {
|
||||
x: position.x,
|
||||
|
|
|
@ -27,7 +27,7 @@ export function MustBeOnline(props: MBOProps) {
|
|||
const { children, hideBanner, lockOpen, networkState, syncStatus } = props;
|
||||
const banner = hideBanner ? "" : "banner";
|
||||
if (isBotOnline(syncStatus, networkState) || lockOpen) {
|
||||
return <div> {children} </div>;
|
||||
return <div className={"bot-is-online-wrapper"}>{children}</div>;
|
||||
} else {
|
||||
return <div
|
||||
className={`unavailable ${banner}`}
|
||||
|
|
|
@ -58,7 +58,7 @@ describe("<PinBindingInputGroup/>", () => {
|
|||
it("no pin selected", () => {
|
||||
const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
expect(buttons.last().props().title).toEqual("BIND");
|
||||
buttons.last().simulate("click");
|
||||
expect(error).toHaveBeenCalledWith("Pin number cannot be blank.");
|
||||
});
|
||||
|
@ -66,7 +66,7 @@ describe("<PinBindingInputGroup/>", () => {
|
|||
it("no target selected", () => {
|
||||
const wrapper = mount(<PinBindingInputGroup {...fakeProps()} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
expect(buttons.last().props().title).toEqual("BIND");
|
||||
wrapper.setState({ pinNumberInput: AVAILABLE_PIN });
|
||||
buttons.last().simulate("click");
|
||||
expect(error).toHaveBeenCalledWith("Please select a sequence or action.");
|
||||
|
@ -77,7 +77,7 @@ describe("<PinBindingInputGroup/>", () => {
|
|||
p.dispatch = jest.fn();
|
||||
const wrapper = mount(<PinBindingInputGroup {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
expect(buttons.last().props().title).toEqual("BIND");
|
||||
wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 });
|
||||
buttons.last().simulate("click");
|
||||
expect(mockDevice.registerGpio).not.toHaveBeenCalled();
|
||||
|
@ -94,7 +94,7 @@ describe("<PinBindingInputGroup/>", () => {
|
|||
p.dispatch = jest.fn();
|
||||
const wrapper = mount(<PinBindingInputGroup {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
expect(buttons.last().props().title).toEqual("BIND");
|
||||
wrapper.setState({
|
||||
pinNumberInput: 0,
|
||||
bindingType: PinBindingType.special,
|
||||
|
|
|
@ -133,8 +133,9 @@ export class PinBindingInputGroup
|
|||
<button
|
||||
className="fb-button green"
|
||||
type="button"
|
||||
title={t("BIND")}
|
||||
onClick={this.bindPin}>
|
||||
{t("BIND")}
|
||||
<i className={"fa fa-plus"} />
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
|
|
|
@ -81,12 +81,12 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
|||
portalClassName={"bindings-warning-icon"}
|
||||
popoverClassName={"help"}>
|
||||
<i className="fa fa-exclamation-triangle" />
|
||||
<div>
|
||||
<div className={"pin-binding-warning"}>
|
||||
{t(ToolTips.PIN_BINDING_WARNING)}
|
||||
</div>
|
||||
</Popover>
|
||||
</Row>
|
||||
<div>
|
||||
<div className={"pin-bindings-list-and-input"}>
|
||||
<PinBindingsListHeader />
|
||||
<PinBindingsList
|
||||
pinBindings={pinBindings}
|
||||
|
|
|
@ -47,6 +47,7 @@ export const StockPinBindingsButton = (props: StockPinBindingsButtonProps) =>
|
|||
<button
|
||||
className="fb-button green"
|
||||
hidden={!hasButtons(props.firmwareHardware)}
|
||||
title={t("add stock pin bindings")}
|
||||
onClick={() => stockPinBindings.map(binding =>
|
||||
props.dispatch(initSave("PinBinding", pinBindingBody(binding))))}>
|
||||
<i className="fa fa-plus" />
|
||||
|
|
|
@ -18,7 +18,7 @@ export class ErrorBoundary extends React.Component<Props, State> {
|
|||
|
||||
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)(); }
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
this.setState(betterMerge(this.state, {
|
||||
fe: { [name]: value },
|
||||
fe: { [key]: value },
|
||||
specialStatusLocal: SpecialStatus.DIRTY
|
||||
}))
|
||||
|
||||
fieldGet = (name: FarmEventViewModelKey): string =>
|
||||
(this.state.fe[name] || this.viewModel[name] || "").toString()
|
||||
fieldGet = (key: FarmEventViewModelKey): string =>
|
||||
(this.state.fe[key] || this.viewModel[key] || "").toString()
|
||||
|
||||
nextItemTime = (fe: FarmEvent, now: moment.Moment
|
||||
): moment.Moment | undefined => {
|
||||
|
@ -390,8 +390,8 @@ export class EditFEForm extends React.Component<EditFEProps, EditFEFormState> {
|
|||
|
||||
export interface StartTimeFormProps {
|
||||
isRegimen: boolean;
|
||||
fieldGet(name: FarmEventViewModelKey): string;
|
||||
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||
fieldGet(key: FarmEventViewModelKey): string;
|
||||
fieldSet(key: FarmEventViewModelKey, value: string): void;
|
||||
timeSettings: TimeSettings;
|
||||
}
|
||||
|
||||
|
@ -426,8 +426,8 @@ export const StartTimeForm = (props: StartTimeFormProps) => {
|
|||
|
||||
export interface RepeatFormProps {
|
||||
isRegimen: boolean;
|
||||
fieldGet(name: FarmEventViewModelKey): string;
|
||||
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||
fieldGet(key: FarmEventViewModelKey): string;
|
||||
fieldSet(key: FarmEventViewModelKey, value: string): void;
|
||||
timeSettings: TimeSettings;
|
||||
}
|
||||
|
||||
|
@ -437,13 +437,14 @@ export const RepeatForm = (props: RepeatFormProps) => {
|
|||
{!props.isRegimen
|
||||
? <label>
|
||||
<input type="checkbox"
|
||||
name="timeUnit"
|
||||
onChange={e => props.fieldSet("timeUnit",
|
||||
(!e.currentTarget.checked || props.isRegimen) ? "never" : "daily")}
|
||||
disabled={props.isRegimen}
|
||||
checked={allowRepeat} />
|
||||
{t("Repeats?")}
|
||||
</label>
|
||||
: <div />}
|
||||
: <div className={"no-repeat"} />}
|
||||
<FarmEventRepeatForm
|
||||
timeSettings={props.timeSettings}
|
||||
disabled={!allowRepeat}
|
||||
|
@ -459,7 +460,7 @@ export const RepeatForm = (props: RepeatFormProps) => {
|
|||
};
|
||||
|
||||
export const dateCheck = (
|
||||
fieldGet: (name: FarmEventViewModelKey) => string
|
||||
fieldGet: (key: FarmEventViewModelKey) => string
|
||||
): string | undefined => {
|
||||
const startDate = fieldGet("startDate");
|
||||
const endDate = fieldGet("endDate");
|
||||
|
@ -469,7 +470,7 @@ export const dateCheck = (
|
|||
};
|
||||
|
||||
export const timeCheck = (
|
||||
fieldGet: (name: FarmEventViewModelKey) => string,
|
||||
fieldGet: (key: FarmEventViewModelKey) => string,
|
||||
timeSettings: TimeSettings
|
||||
): string | undefined => {
|
||||
const startDate = fieldGet("startDate");
|
||||
|
@ -491,6 +492,7 @@ export interface FarmEventDeleteButtonProps {
|
|||
|
||||
export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
|
||||
<button className="fb-button red" hidden={props.hidden}
|
||||
title={t("Delete")}
|
||||
onClick={() =>
|
||||
props.dispatch(destroy(props.farmEvent.uuid))
|
||||
.then(() => {
|
||||
|
@ -502,8 +504,8 @@ export const FarmEventDeleteButton = (props: FarmEventDeleteButtonProps) =>
|
|||
|
||||
export interface FarmEventFormProps {
|
||||
isRegimen: boolean;
|
||||
fieldGet(name: FarmEventViewModelKey): string;
|
||||
fieldSet(name: FarmEventViewModelKey, value: string): void;
|
||||
fieldGet(key: FarmEventViewModelKey): string;
|
||||
fieldSet(key: FarmEventViewModelKey, value: string): void;
|
||||
timeSettings: TimeSettings;
|
||||
executableOptions: DropDownItem[];
|
||||
executableSet(ddi: DropDownItem): void;
|
||||
|
|
|
@ -15,7 +15,7 @@ export interface FarmEventRepeatFormProps {
|
|||
disabled: boolean;
|
||||
/** Should the form be shown _at all_? */
|
||||
hidden: boolean;
|
||||
fieldSet(name: keyof FarmEventViewModel, value: string): void;
|
||||
fieldSet(key: keyof FarmEventViewModel, value: string): void;
|
||||
timeUnit: TimeUnit;
|
||||
repeat: string;
|
||||
endDate: string;
|
||||
|
@ -30,53 +30,55 @@ const OPTN_LOOKUP = keyBy(repeatOptions, indexKey);
|
|||
|
||||
export function FarmEventRepeatForm(props: FarmEventRepeatFormProps) {
|
||||
const { disabled, fieldSet, repeat, endDate, endTime, timeUnit } = props;
|
||||
return props.hidden ? <div /> : <div className="farm-event-repeat-form">
|
||||
<label>
|
||||
{t("Every")}
|
||||
</label>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<BlurableInput
|
||||
disabled={disabled}
|
||||
placeholder="(Number)"
|
||||
type="number"
|
||||
className="add-event-repeat-frequency"
|
||||
name="repeat"
|
||||
value={repeat}
|
||||
onCommit={e => fieldSet("repeat", e.currentTarget.value)}
|
||||
min={1} />
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<FBSelect
|
||||
list={repeatOptions}
|
||||
onChange={ddi => fieldSet("timeUnit", "" + ddi.value)}
|
||||
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
|
||||
</Col>
|
||||
</Row>
|
||||
<label>
|
||||
{t("Until")}
|
||||
</label>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<BlurableInput
|
||||
disabled={disabled}
|
||||
type="date"
|
||||
className="add-event-end-date"
|
||||
name="endDate"
|
||||
value={endDate}
|
||||
onCommit={e => fieldSet("endDate", e.currentTarget.value)}
|
||||
error={props.dateError} />
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<EventTimePicker
|
||||
disabled={disabled}
|
||||
className="add-event-end-time"
|
||||
name="endTime"
|
||||
timeSettings={props.timeSettings}
|
||||
value={endTime}
|
||||
onCommit={e => fieldSet("endTime", e.currentTarget.value)}
|
||||
error={props.timeError} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
return props.hidden
|
||||
? <div className={"no-repeat-form"} />
|
||||
: <div className="farm-event-repeat-form">
|
||||
<label>
|
||||
{t("Every")}
|
||||
</label>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<BlurableInput
|
||||
disabled={disabled}
|
||||
placeholder="(Number)"
|
||||
type="number"
|
||||
className="add-event-repeat-frequency"
|
||||
name="repeat"
|
||||
value={repeat}
|
||||
onCommit={e => fieldSet("repeat", e.currentTarget.value)}
|
||||
min={1} />
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<FBSelect
|
||||
list={repeatOptions}
|
||||
onChange={ddi => fieldSet("timeUnit", "" + ddi.value)}
|
||||
selectedItem={OPTN_LOOKUP[timeUnit] || OPTN_LOOKUP["daily"]} />
|
||||
</Col>
|
||||
</Row>
|
||||
<label>
|
||||
{t("Until")}
|
||||
</label>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<BlurableInput
|
||||
disabled={disabled}
|
||||
type="date"
|
||||
className="add-event-end-date"
|
||||
name="endDate"
|
||||
value={endDate}
|
||||
onCommit={e => fieldSet("endDate", e.currentTarget.value)}
|
||||
error={props.dateError} />
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<EventTimePicker
|
||||
disabled={disabled}
|
||||
className="add-event-end-time"
|
||||
name="endTime"
|
||||
timeSettings={props.timeSettings}
|
||||
value={endTime}
|
||||
onCommit={e => fieldSet("endTime", e.currentTarget.value)}
|
||||
error={props.timeError} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ export class PureFarmEvents
|
|||
noIcon={true}>
|
||||
<i className="fa fa-calendar" onClick={this.resetCalendar} />
|
||||
<input
|
||||
name="searchTerm"
|
||||
value={this.state.searchTerm}
|
||||
onChange={e => this.setState({ searchTerm: e.currentTarget.value })}
|
||||
placeholder={t("Search your events...")} />
|
||||
|
|
|
@ -49,10 +49,10 @@ export const gridOffset: AxisNumberProperty = { x: 50, y: 50 };
|
|||
export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
||||
|
||||
initializeSetting =
|
||||
(name: keyof State, defaultValue: boolean): boolean => {
|
||||
const currentValue = this.props.getConfigValue(name);
|
||||
(key: keyof State, defaultValue: boolean): boolean => {
|
||||
const currentValue = this.props.getConfigValue(key);
|
||||
if (isUndefined(currentValue)) {
|
||||
this.props.dispatch(setWebAppConfigValue(name, defaultValue));
|
||||
this.props.dispatch(setWebAppConfigValue(key, defaultValue));
|
||||
return defaultValue;
|
||||
} else {
|
||||
return !!currentValue;
|
||||
|
@ -87,10 +87,10 @@ export class RawFarmDesigner extends React.Component<Props, Partial<State>> {
|
|||
this.updateZoomLevel(0)();
|
||||
}
|
||||
|
||||
toggle = (name: keyof State) => () => {
|
||||
const newValue = !this.state[name];
|
||||
this.props.dispatch(setWebAppConfigValue(name, newValue));
|
||||
this.setState({ [name]: newValue });
|
||||
toggle = (key: keyof State) => () => {
|
||||
const newValue = !this.state[key];
|
||||
this.props.dispatch(setWebAppConfigValue(key, newValue));
|
||||
this.setState({ [key]: newValue });
|
||||
}
|
||||
|
||||
updateBotOriginQuadrant = (payload: BotOriginQuadrant) => () => {
|
||||
|
|
|
@ -125,6 +125,7 @@ export const BugsControls = () =>
|
|||
? <div className="more-bugs">
|
||||
<button
|
||||
className="fb-button green"
|
||||
title={t("more bugs!")}
|
||||
onClick={resetBugs}>
|
||||
{t("more bugs!")}
|
||||
</button>
|
||||
|
@ -133,4 +134,4 @@ export const BugsControls = () =>
|
|||
{t("{{seconds}} seconds!", { seconds: getBugTime() })}
|
||||
</p>}
|
||||
</div>
|
||||
: <div />;
|
||||
: <div className={"no-bugs"} />;
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
|
||||
describe("<BotFigure/>", () => {
|
||||
const fakeProps = (): BotFigureProps => ({
|
||||
name: "",
|
||||
figureName: "",
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
plantAreaOffset: { x: 100, y: 100 },
|
||||
|
@ -30,11 +30,11 @@ describe("<BotFigure/>", () => {
|
|||
["motors", 4, { x: 3000, y: 1500 }, true, EXPECTED_MOTORS_OPACITY],
|
||||
["encoders", 2, { x: 0, y: 0 }, false, 0.25],
|
||||
])("shows %s in correct location for quadrant %i",
|
||||
(name, quadrant, expected, xySwap, opacity) => {
|
||||
(figureName, quadrant, expected, xySwap, opacity) => {
|
||||
const p = fakeProps();
|
||||
p.mapTransformProps.quadrant = quadrant;
|
||||
p.mapTransformProps.xySwap = xySwap;
|
||||
p.name = name;
|
||||
p.figureName = figureName;
|
||||
const result = shallow<BotFigure>(<BotFigure {...p} />);
|
||||
|
||||
const expectedGantryProps = expect.objectContaining({
|
||||
|
|
|
@ -5,6 +5,7 @@ import { VirtualFarmBotProps } from "../../../interfaces";
|
|||
import {
|
||||
fakeMapTransformProps
|
||||
} from "../../../../../__test_support__/map_transform_props";
|
||||
import { BotFigure } from "../bot_figure";
|
||||
|
||||
describe("<VirtualFarmBot/>", () => {
|
||||
function fakeProps(): VirtualFarmBotProps {
|
||||
|
@ -27,9 +28,9 @@ describe("<VirtualFarmBot/>", () => {
|
|||
const p = fakeProps();
|
||||
p.getConfigValue = () => false;
|
||||
const wrapper = shallow(<VirtualFarmBot {...p} />);
|
||||
const figures = wrapper.find("BotFigure");
|
||||
const figures = wrapper.find(BotFigure);
|
||||
expect(figures.length).toEqual(1);
|
||||
expect(figures.last().props().name).toEqual("motor-position");
|
||||
expect(figures.last().props().figureName).toEqual("motor-position");
|
||||
});
|
||||
|
||||
it("shows trail", () => {
|
||||
|
@ -39,8 +40,8 @@ describe("<VirtualFarmBot/>", () => {
|
|||
|
||||
it("shows encoder position", () => {
|
||||
const wrapper = shallow(<VirtualFarmBot {...fakeProps()} />);
|
||||
const figures = wrapper.find("BotFigure");
|
||||
const figures = wrapper.find(BotFigure);
|
||||
expect(figures.length).toEqual(2);
|
||||
expect(figures.last().props().name).toEqual("encoder-position");
|
||||
expect(figures.last().props().figureName).toEqual("encoder-position");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { reduceToolName } from "../tool_slots/tool_slot_point";
|
|||
import { noop } from "lodash";
|
||||
|
||||
export interface BotFigureProps {
|
||||
name: string;
|
||||
figureName: string;
|
||||
position: BotPosition;
|
||||
mapTransformProps: MapTransformProps;
|
||||
plantAreaOffset: AxisNumberProperty;
|
||||
|
@ -29,14 +29,14 @@ export class BotFigure extends
|
|||
|
||||
render() {
|
||||
const {
|
||||
name, position, plantAreaOffset, eStopStatus, mapTransformProps,
|
||||
figureName, position, plantAreaOffset, eStopStatus, mapTransformProps,
|
||||
} = this.props;
|
||||
const { xySwap } = mapTransformProps;
|
||||
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
|
||||
const positionQ = transformXY(
|
||||
(position.x || 0), (position.y || 0), mapTransformProps);
|
||||
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 = {
|
||||
x: positionQ.qx,
|
||||
y: positionQ.qy,
|
||||
|
@ -45,7 +45,7 @@ export class BotFigure extends
|
|||
uuid: "utm",
|
||||
xySwap,
|
||||
};
|
||||
return <g id={name}>
|
||||
return <g id={figureName}>
|
||||
<rect id="gantry"
|
||||
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
|
||||
y={xySwap ? positionQ.qy - 10 : -plantAreaOffset.y}
|
||||
|
|
|
@ -24,14 +24,14 @@ export function VirtualFarmBot(props: VirtualFarmBotProps) {
|
|||
plantAreaOffset={plantAreaOffset}
|
||||
peripherals={peripherals}
|
||||
getConfigValue={getConfigValue} />
|
||||
<BotFigure name={"motor-position"}
|
||||
<BotFigure figureName={"motor-position"}
|
||||
position={props.botLocationData.position}
|
||||
mapTransformProps={mapTransformProps}
|
||||
plantAreaOffset={plantAreaOffset}
|
||||
mountedToolName={props.mountedToolName}
|
||||
eStopStatus={eStopStatus} />
|
||||
{encoderFigure &&
|
||||
<BotFigure name={"encoder-position"}
|
||||
<BotFigure figureName={"encoder-position"}
|
||||
position={props.botLocationData.scaled_encoders}
|
||||
mapTransformProps={mapTransformProps}
|
||||
plantAreaOffset={plantAreaOffset} />}
|
||||
|
|
|
@ -17,12 +17,12 @@ const parse = (str: string | undefined) => {
|
|||
};
|
||||
|
||||
/* 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; }
|
||||
return name &&
|
||||
(name.includes("rotated")
|
||||
|| name.includes("marked")
|
||||
|| name.includes("calibration_result"));
|
||||
return annotation &&
|
||||
(annotation.includes("rotated")
|
||||
|| annotation.includes("marked")
|
||||
|| annotation.includes("calibration_result"));
|
||||
};
|
||||
|
||||
/* Check if the calibration data is valid for the image provided using z. */
|
||||
|
|
|
@ -24,6 +24,7 @@ const LengthInput = (props: LengthInputProps) =>
|
|||
<Col xs={5}>
|
||||
<input
|
||||
type="number"
|
||||
name={props.setting}
|
||||
value={"" + props.value}
|
||||
onChange={e => props.dispatch(setWebAppConfigValue(
|
||||
props.setting, e.currentTarget.value))} />
|
||||
|
|
|
@ -64,7 +64,7 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
|
|||
render() {
|
||||
const { x, y } = this.props.chosenLocation;
|
||||
const { botOnline } = this.props;
|
||||
return <div>
|
||||
return <div className={"move-to-form"}>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<label>{t("X AXIS")}</label>
|
||||
|
@ -78,10 +78,10 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
|
|||
</Row>
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<input disabled value={isNumber(x) ? x : "---"} />
|
||||
<input disabled name="x" value={isNumber(x) ? x : "---"} />
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<input disabled value={isNumber(y) ? y : "---"} />
|
||||
<input disabled name="y" value={isNumber(y) ? y : "---"} />
|
||||
</Col>
|
||||
<AxisInputBox
|
||||
onChange={(_, val: number) => this.setState({ z: val })}
|
||||
|
@ -91,7 +91,9 @@ export class MoveToForm extends React.Component<MoveToFormProps, MoveToFormState
|
|||
<button
|
||||
onClick={() => moveAbs(this.vector)}
|
||||
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")}
|
||||
</button>
|
||||
</Row>
|
||||
|
|
|
@ -189,7 +189,7 @@ describe("<EditDatePlanted />", () => {
|
|||
describe("<EditPlantLocation />", () => {
|
||||
const fakeProps = (): EditPlantLocationProps => ({
|
||||
uuid: "Plant.0.0",
|
||||
location: { x: 1, y: 2 },
|
||||
xyLocation: { x: 1, y: 2 },
|
||||
updatePlant: jest.fn(),
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ interface APDProps {
|
|||
}
|
||||
|
||||
const AddPlantDescription = ({ svgIcon, children }: APDProps) =>
|
||||
<div>
|
||||
<div className={"add-plant-description"}>
|
||||
<img className="crop-drag-info-image"
|
||||
src={svgToUrl(svgIcon)}
|
||||
alt={t("plant icon")}
|
||||
|
|
|
@ -74,6 +74,7 @@ export class RawCropCatalog extends React.Component<CropCatalogProps, {}> {
|
|||
onChange={this.handleChange}
|
||||
onKeyPress={this.handleChange}
|
||||
className="search"
|
||||
name="searchTerm"
|
||||
placeholder={t("Search OpenFarm...")} />
|
||||
{this.showResultChangeSpinner &&
|
||||
<Spinner radius={10} strokeWidth={3} />}
|
||||
|
|
|
@ -44,7 +44,7 @@ const InfoField = (props: InfoFieldProps) =>
|
|||
<p>
|
||||
{t(startCase(props.title))}
|
||||
</p>
|
||||
<div>
|
||||
<div className={"crop-info-field-data"}>
|
||||
{props.children}
|
||||
</div>
|
||||
</li>;
|
||||
|
@ -67,11 +67,13 @@ const NO_VALUE = t("Not Set");
|
|||
const SvgIcon = ({ i, field, value }: SummaryItemProps) =>
|
||||
<InfoField key={i} title={field}>
|
||||
{value
|
||||
? <div><img
|
||||
src={svgToUrl(value)}
|
||||
width={100}
|
||||
height={100}
|
||||
onDragStart={setDragIcon(value)} /></div>
|
||||
? <div className={"svg-img"}>
|
||||
<img
|
||||
src={svgToUrl(value)}
|
||||
width={100}
|
||||
height={100}
|
||||
onDragStart={setDragIcon(value)} />
|
||||
</div>
|
||||
: <span>{NO_VALUE}</span>}
|
||||
</InfoField>;
|
||||
|
||||
|
@ -149,6 +151,7 @@ const AddPlantHereButton = (props: {
|
|||
dispatch, openedSavedGarden
|
||||
}) : () => { };
|
||||
return <button className="fb-button gray no-float"
|
||||
title={t("Add plant at current location")}
|
||||
disabled={!botXY} onClick={click}>
|
||||
{t("Add plant at current FarmBot location {{coordinate}}",
|
||||
{ coordinate: botXYLabel })}
|
||||
|
|
|
@ -23,14 +23,13 @@ describe("PlantGrid", () => {
|
|||
const p = fakeProps();
|
||||
const el = mount<PlantGrid>(<PlantGrid {...p} />);
|
||||
// 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");
|
||||
previewButton.simulate("click");
|
||||
|
||||
// After clicking PREVIEW, there should be two buttons.
|
||||
const saveAndCancelBtns = el.find("a.clear-button");
|
||||
const cancel = saveAndCancelBtns.at(0);
|
||||
const save = saveAndCancelBtns.at(1);
|
||||
const cancel = el.find("a.cancel-button");
|
||||
const save = el.find("a.save-button");
|
||||
expect(cancel.text()).toContain("Cancel");
|
||||
expect(save.text()).toContain("Save");
|
||||
expect(el.state().status).toEqual("dirty");
|
||||
|
|
|
@ -77,17 +77,23 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
|||
buttons = () => {
|
||||
switch (this.state.status) {
|
||||
case "clean":
|
||||
return <div>
|
||||
<a className={"clear-button"} onClick={this.performPreview}>
|
||||
return <div className={"preview-grid-button"}>
|
||||
<a className={"preview-button"}
|
||||
title={t("Preview")}
|
||||
onClick={this.performPreview}>
|
||||
{t("Preview")}
|
||||
</a>
|
||||
</div>;
|
||||
case "dirty":
|
||||
return <div>
|
||||
<a className={"clear-button"} onClick={this.revertPreview}>
|
||||
return <div className={"save-or-cancel-grid-button"}>
|
||||
<a className={"cancel-button"}
|
||||
title={t("Cancel")}
|
||||
onClick={this.revertPreview}>
|
||||
{t("Cancel")}
|
||||
</a>
|
||||
<a className={"clear-button"} onClick={this.saveGrid}>
|
||||
<a className={"save-button"}
|
||||
title={t("Save")}
|
||||
onClick={this.saveGrid}>
|
||||
{t("Save")}
|
||||
</a>
|
||||
</div>;
|
||||
|
@ -95,7 +101,7 @@ export class PlantGrid extends React.Component<PlantGridProps, PlantGridState> {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
return <div className={"grid-and-row-planting"}>
|
||||
<hr style={{ borderTop: "1.5px solid rgba(255, 255, 255 ,0.7)" }} />
|
||||
<h3>
|
||||
{t("Grid and Row Planting")}
|
||||
|
|
|
@ -32,33 +32,31 @@ export class OpenFarmResults extends React.Component<SearchResultProps, {}> {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.cropSearchResults.length > 0}
|
||||
graphic={EmptyStateGraphic.no_crop_results}
|
||||
title={this.props.cropSearchInProgress
|
||||
? t("Searching...")
|
||||
: t("No search results")}
|
||||
textElement={this.props.cropSearchInProgress ? <div /> : this.text}
|
||||
colorScheme={"plants"}>
|
||||
{this.props.cropSearchResults.map(resp => {
|
||||
const { crop, image } = resp;
|
||||
return <Link
|
||||
key={resp.crop.slug}
|
||||
draggable={false}
|
||||
to={`/app/designer/plants/crop_search/` + crop.slug.toString()}>
|
||||
<div className="plant-catalog-tile col-xs-6">
|
||||
<label>
|
||||
{crop.name}
|
||||
</label>
|
||||
<div
|
||||
className="plant-catalog-image"
|
||||
style={{ background: `url(${image}) top center no-repeat` }}
|
||||
draggable={false} />
|
||||
</div>
|
||||
</Link>;
|
||||
})}
|
||||
</EmptyStateWrapper>
|
||||
</div>;
|
||||
return <EmptyStateWrapper
|
||||
notEmpty={this.props.cropSearchResults.length > 0}
|
||||
graphic={EmptyStateGraphic.no_crop_results}
|
||||
title={this.props.cropSearchInProgress
|
||||
? t("Searching...")
|
||||
: t("No search results")}
|
||||
textElement={this.props.cropSearchInProgress ? undefined : this.text}
|
||||
colorScheme={"plants"}>
|
||||
{this.props.cropSearchResults.map(resp => {
|
||||
const { crop, image } = resp;
|
||||
return <Link
|
||||
key={resp.crop.slug}
|
||||
draggable={false}
|
||||
to={`/app/designer/plants/crop_search/` + crop.slug.toString()}>
|
||||
<div className="plant-catalog-tile col-xs-6">
|
||||
<label>
|
||||
{crop.name}
|
||||
</label>
|
||||
<div
|
||||
className="plant-catalog-image"
|
||||
style={{ background: `url(${image}) top center no-repeat` }}
|
||||
draggable={false} />
|
||||
</div>
|
||||
</Link>;
|
||||
})}
|
||||
</EmptyStateWrapper>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export class RawPlants extends React.Component<PlantInventoryProps, State> {
|
|||
panel={Panel.Plants}
|
||||
linkTo={"/app/designer/plants/crop_search"}
|
||||
title={t("Add plant")}>
|
||||
<input type="text" onChange={this.update}
|
||||
<input type="text" onChange={this.update} name="searchTerm"
|
||||
placeholder={t("Search your plants...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"plant"}>
|
||||
|
|
|
@ -51,18 +51,18 @@ export const EditDatePlanted = (props: EditDatePlantedProps) => {
|
|||
};
|
||||
|
||||
export interface EditPlantLocationProps extends EditPlantProperty {
|
||||
location: Record<"x" | "y", number>;
|
||||
xyLocation: Record<"x" | "y", number>;
|
||||
}
|
||||
|
||||
export const EditPlantLocation = (props: EditPlantLocationProps) => {
|
||||
const { location, updatePlant, uuid } = props;
|
||||
const { xyLocation, updatePlant, uuid } = props;
|
||||
return <Row>
|
||||
{["x", "y"].map((axis: "x" | "y") =>
|
||||
<Col xs={6} key={axis}>
|
||||
<label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label>
|
||||
<BlurableInput
|
||||
type="number"
|
||||
value={location[axis]}
|
||||
value={xyLocation[axis]}
|
||||
min={0}
|
||||
onCommit={e => updatePlant(uuid, {
|
||||
[axis]: round(parseIntInput(e.currentTarget.value))
|
||||
|
@ -89,6 +89,7 @@ interface MoveToPlantProps {
|
|||
const MoveToPlant = (props: MoveToPlantProps) =>
|
||||
<button className="fb-button gray no-float"
|
||||
style={{ marginTop: "1rem" }}
|
||||
title={t("Move to this plant")}
|
||||
onClick={() => props.dispatch(chooseLocation({ x: props.x, y: props.y }))
|
||||
.then(() => history.push("/app/designer/move_to"))}>
|
||||
{t("Move FarmBot to this plant")}
|
||||
|
@ -99,20 +100,22 @@ interface DeleteButtonsProps {
|
|||
}
|
||||
|
||||
const DeleteButtons = (props: DeleteButtonsProps) =>
|
||||
<div>
|
||||
<div>
|
||||
<div className={"plant-delete-buttons"}>
|
||||
<div className={"plant-delete-button-label"}>
|
||||
<label>
|
||||
{t("Delete this plant")}
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
className="fb-button red no-float"
|
||||
title={t("Delete")}
|
||||
onClick={props.destroy}>
|
||||
{t("Delete")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button gray no-float"
|
||||
style={{ marginRight: "10px" }}
|
||||
title={t("Delete multiple")}
|
||||
onClick={() => history.push("/app/designer/plants/select")}>
|
||||
{t("Delete multiple")}
|
||||
</button>
|
||||
|
@ -128,7 +131,7 @@ export const ListItem = (props: ListItemProps) =>
|
|||
<p>
|
||||
{props.name}
|
||||
</p>
|
||||
<div>
|
||||
<div className={"plant-info-field-data"}>
|
||||
{props.children}
|
||||
</div>
|
||||
</li>;
|
||||
|
@ -171,7 +174,7 @@ export function PlantPanel(props: PlantPanelProps) {
|
|||
</Row>}
|
||||
<ListItem name={t("Location")}>
|
||||
<EditPlantLocation uuid={uuid}
|
||||
location={{ x, y }}
|
||||
xyLocation={{ x, y }}
|
||||
updatePlant={updatePlant} />
|
||||
</ListItem>
|
||||
<MoveToPlant x={x} y={y} dispatch={dispatch} />
|
||||
|
|
|
@ -60,10 +60,12 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
<div className="panel-action-buttons">
|
||||
<div className="button-row">
|
||||
<button className="fb-button gray"
|
||||
title={t("Select none")}
|
||||
onClick={() => this.props.dispatch(selectPlant(undefined))}>
|
||||
{t("Select none")}
|
||||
</button>
|
||||
<button className="fb-button gray"
|
||||
title={t("Select all")}
|
||||
onClick={() => this.props
|
||||
.dispatch(selectPlant(this.props.plants.map(p => p.uuid)))}>
|
||||
{t("Select all")}
|
||||
|
@ -72,10 +74,12 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
<label>{t("SELECTION ACTIONS")}</label>
|
||||
<div className="button-row">
|
||||
<button className="fb-button red"
|
||||
title={t("Delete")}
|
||||
onClick={() => this.destroySelected(this.props.selected)}>
|
||||
{t("Delete")}
|
||||
</button>
|
||||
<button className="fb-button dark-blue"
|
||||
title={t("Create group")}
|
||||
onClick={() => !this.props.gardenOpen
|
||||
? this.props.dispatch(createGroup({ pointUuids: this.selected }))
|
||||
: error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}>
|
||||
|
|
|
@ -4,9 +4,9 @@ import { TaggedPoint } from "farmbot";
|
|||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("sort()", () => {
|
||||
const phony = (name: string, x: number, y: number): TaggedPoint => {
|
||||
const phony = (plantName: string, x: number, y: number): TaggedPoint => {
|
||||
const plant = fakePlant();
|
||||
plant.body.name = name;
|
||||
plant.body.name = plantName;
|
||||
plant.body.x = x;
|
||||
plant.body.y = y;
|
||||
return plant;
|
||||
|
|
|
@ -45,12 +45,15 @@ export class AddEqCriteria<T extends string | number>
|
|||
</Col>
|
||||
<Col xs={4}>
|
||||
<input type={this.props.type}
|
||||
name="value"
|
||||
placeholder={t("value")}
|
||||
value={this.state.value}
|
||||
onChange={e => this.setState({ value: e.currentTarget.value })} />
|
||||
</Col>
|
||||
<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" />
|
||||
</button>
|
||||
</Col>
|
||||
|
@ -149,7 +152,9 @@ export class AddStringCriteria
|
|||
onChange={this.change} />
|
||||
</Col>
|
||||
<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" />
|
||||
</button>
|
||||
</Col>
|
||||
|
@ -182,6 +187,7 @@ export class AddNumberCriteria
|
|||
<Row>
|
||||
<Col xs={4}>
|
||||
<input type="string"
|
||||
name="key"
|
||||
placeholder={t("field")}
|
||||
value={this.state.key}
|
||||
onChange={this.changeKey} />
|
||||
|
@ -191,11 +197,14 @@ export class AddNumberCriteria
|
|||
</Col>
|
||||
<Col xs={4}>
|
||||
<input type="number"
|
||||
name="value"
|
||||
value={this.state.value}
|
||||
onChange={this.changeValue} />
|
||||
</Col>
|
||||
<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" />
|
||||
</button>
|
||||
</Col>
|
||||
|
|
|
@ -21,12 +21,14 @@ export class GroupCriteria extends
|
|||
const commonProps = { group, criteria, dispatch };
|
||||
return <div className="group-criteria">
|
||||
<label className="criteria-heading">{t("criteria")}</label>
|
||||
<button className="fb-button red" onClick={() => {
|
||||
dispatch(overwrite(group, {
|
||||
...group.body, criteria: DEFAULT_CRITERIA
|
||||
}));
|
||||
dispatch(save(group.uuid));
|
||||
}}>
|
||||
<button className="fb-button red"
|
||||
title={t("clear all criteria")}
|
||||
onClick={() => {
|
||||
dispatch(overwrite(group, {
|
||||
...group.body, criteria: DEFAULT_CRITERIA
|
||||
}));
|
||||
dispatch(save(group.uuid));
|
||||
}}>
|
||||
{t("clear all criteria")}
|
||||
</button>
|
||||
<div className="group-criteria-presets">
|
||||
|
@ -60,13 +62,13 @@ export class GroupCriteria extends
|
|||
export const GroupPointCountBreakdown = (props: GroupPointCountBreakdownProps) =>
|
||||
<div className={"criteria-point-count-breakdown"}>
|
||||
<div className={"manual-group-member-count"}>
|
||||
<div>
|
||||
<div className={"manual-selection-count"}>
|
||||
{props.manualCount}
|
||||
</div>
|
||||
<p>{t("manually selected")}</p>
|
||||
</div>
|
||||
<div className={"criteria-group-member-count"}>
|
||||
<div>
|
||||
<div className={"criteria-selection-count"}>
|
||||
{props.totalCount - props.manualCount}
|
||||
</div>
|
||||
<p>{t("selected by criteria")}</p>
|
||||
|
|
|
@ -32,18 +32,20 @@ export class EqCriteriaSelection<T extends string | number>
|
|||
{values.map((value, valueIndex) =>
|
||||
<Row key={"" + keyIndex + valueIndex}>
|
||||
<Col xs={9}>
|
||||
<input
|
||||
<input name="value"
|
||||
disabled={true}
|
||||
value={value} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<button className="fb-button red" onClick={() => {
|
||||
const tempCriteriaField = cloneDeep(criteriaField);
|
||||
toggleEqCriteria<T>(tempCriteriaField)(key, value);
|
||||
dispatch(editCriteria(group, {
|
||||
[criteriaKey]: tempCriteriaField
|
||||
}));
|
||||
}}>
|
||||
<button className="fb-button red"
|
||||
title={t("remove criteria")}
|
||||
onClick={() => {
|
||||
const tempCriteriaField = cloneDeep(criteriaField);
|
||||
toggleEqCriteria<T>(tempCriteriaField)(key, value);
|
||||
dispatch(editCriteria(group, {
|
||||
[criteriaKey]: tempCriteriaField
|
||||
}));
|
||||
}}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</Col>
|
||||
|
@ -69,17 +71,20 @@ export const NumberCriteriaSelection = (props: NumberCriteriaProps) => {
|
|||
</Col>
|
||||
<Col xs={4}>
|
||||
<input key={"" + keyIndex}
|
||||
name="value"
|
||||
disabled={true}
|
||||
value={value} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<button className="fb-button red" onClick={() => {
|
||||
const tempNumberCriteria = cloneDeep(criteriaField);
|
||||
delete tempNumberCriteria[key];
|
||||
props.dispatch(editCriteria(props.group, {
|
||||
[props.criteriaKey]: tempNumberCriteria
|
||||
}));
|
||||
}}>
|
||||
<button className="fb-button red"
|
||||
title={t("remove number criteria")}
|
||||
onClick={() => {
|
||||
const tempNumberCriteria = cloneDeep(criteriaField);
|
||||
delete tempNumberCriteria[key];
|
||||
props.dispatch(editCriteria(props.group, {
|
||||
[props.criteriaKey]: tempNumberCriteria
|
||||
}));
|
||||
}}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</Col>
|
||||
|
@ -112,11 +117,12 @@ export const DaySelection = (props: CriteriaSelectionProps) => {
|
|||
}))} />
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
<input type="number" value={dayCriteria.days_ago} onChange={e => {
|
||||
const { op } = dayCriteria;
|
||||
const days_ago = parseInt(e.currentTarget.value);
|
||||
dispatch(editCriteria(group, { day: { days_ago, op } }));
|
||||
}} />
|
||||
<input type="number" value={dayCriteria.days_ago} name="days_ago"
|
||||
onChange={e => {
|
||||
const { op } = dayCriteria;
|
||||
const days_ago = parseInt(e.currentTarget.value);
|
||||
dispatch(editCriteria(group, { day: { days_ago, op } }));
|
||||
}} />
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<p>{t("days old")}</p>
|
||||
|
@ -136,6 +142,7 @@ export const LocationSelection = (props: LocationSelectionProps) => {
|
|||
<Col xs={4}>
|
||||
<input key={JSON.stringify(gtCriteria)}
|
||||
type="number"
|
||||
name={`${axis}-number-gt`}
|
||||
defaultValue={gtCriteria[axis]}
|
||||
onBlur={e => {
|
||||
const tempGtCriteria = cloneDeep(gtCriteria);
|
||||
|
@ -155,6 +162,7 @@ export const LocationSelection = (props: LocationSelectionProps) => {
|
|||
<Col xs={4}>
|
||||
<input key={JSON.stringify(ltCriteria)}
|
||||
type="number"
|
||||
name={`${axis}-number-lt`}
|
||||
defaultValue={ltCriteria[axis]}
|
||||
onBlur={e => {
|
||||
const tempLtCriteria = cloneDeep(ltCriteria);
|
||||
|
@ -195,14 +203,19 @@ export class AddCriteria
|
|||
<Row>
|
||||
<Col xs={5}>
|
||||
<input value={CRITERIA_TYPE_DDI_LOOKUP()[key].label}
|
||||
name="key"
|
||||
disabled={true} />
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<input value={this.labelLookup(key, value)} disabled={true} />
|
||||
<input value={this.labelLookup(key, value)}
|
||||
name="value"
|
||||
disabled={true} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<button className="fb-button red" onClick={() => props.dispatch(
|
||||
toggleStringCriteria(props.group, key, value))}>
|
||||
<button className="fb-button red"
|
||||
title={t("remove criteria")}
|
||||
onClick={() => props.dispatch(
|
||||
toggleStringCriteria(props.group, key, value))}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</Col>
|
||||
|
|
|
@ -50,6 +50,7 @@ export class RawGroupListPanel extends React.Component<GroupListPanelProps, Stat
|
|||
linkTo={"/app/designer/plants/select"}
|
||||
title={t("Add group")}>
|
||||
<input type="text"
|
||||
name="searchTerm"
|
||||
onChange={this.update}
|
||||
placeholder={t("Search your groups...")} />
|
||||
</DesignerPanelTop>
|
||||
|
|
|
@ -47,7 +47,7 @@ describe("<EditPointName />", () => {
|
|||
describe("<EditPointLocation />", () => {
|
||||
const fakeProps = (): EditPointLocationProps => ({
|
||||
updatePoint: jest.fn(),
|
||||
location: { x: 1, y: 2 },
|
||||
xyLocation: { x: 1, y: 2 },
|
||||
});
|
||||
|
||||
it("edits location", () => {
|
||||
|
|
|
@ -187,7 +187,7 @@ export class RawCreatePoints
|
|||
PointProperties = () =>
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<div className={"point-name-input"}>
|
||||
<label>{t("Name")}</label>
|
||||
<BlurableInput
|
||||
name="name"
|
||||
|
@ -241,6 +241,7 @@ export class RawCreatePoints
|
|||
PointActions = () =>
|
||||
<Row>
|
||||
<button className="fb-button green"
|
||||
title={t("save")}
|
||||
onClick={this.createPoint}>
|
||||
{t("Save")}
|
||||
</button>
|
||||
|
@ -254,6 +255,7 @@ export class RawCreatePoints
|
|||
? t("Delete all of the weeds created through this panel.")
|
||||
: t("Delete all of the points created through this panel.")}</p>
|
||||
<button className="fb-button red delete"
|
||||
title={t("delete all")}
|
||||
onClick={() => {
|
||||
if (confirm(type === "weed"
|
||||
? t("Delete all the weeds you have created?")
|
||||
|
|
|
@ -27,7 +27,7 @@ export interface EditPointPropertiesProps {
|
|||
export const EditPointProperties = (props: EditPointPropertiesProps) =>
|
||||
<ul>
|
||||
<li>
|
||||
<div>
|
||||
<div className={"point-name-input"}>
|
||||
<EditPointName
|
||||
name={props.point.body.name}
|
||||
updatePoint={props.updatePoint} />
|
||||
|
@ -35,7 +35,7 @@ export const EditPointProperties = (props: EditPointPropertiesProps) =>
|
|||
</li>
|
||||
<ListItem name={t("Location")}>
|
||||
<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} />
|
||||
</ListItem>
|
||||
<ListItem name={t("Size")}>
|
||||
|
@ -59,15 +59,17 @@ export interface PointActionsProps {
|
|||
}
|
||||
|
||||
export const PointActions = ({ x, y, z, uuid, dispatch }: PointActionsProps) =>
|
||||
<div>
|
||||
<div className={"point-actions"}>
|
||||
<button
|
||||
className="fb-button gray no-float"
|
||||
type="button"
|
||||
title={t("move to location")}
|
||||
onClick={() => getDevice().moveAbsolute({ x, y, z })}>
|
||||
{t("Move Device to location")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button red no-float"
|
||||
title={t("delete")}
|
||||
onClick={() => dispatch(destroy(uuid))}>
|
||||
{t("Delete")}
|
||||
</button>
|
||||
|
@ -84,6 +86,7 @@ export const EditPointName = (props: EditPointNameProps) =>
|
|||
<label>{t("Name")}</label>
|
||||
<BlurableInput
|
||||
type="text"
|
||||
name="name"
|
||||
value={props.name}
|
||||
onCommit={e => props.updatePoint({ name: e.currentTarget.value })} />
|
||||
</Col>
|
||||
|
@ -91,7 +94,7 @@ export const EditPointName = (props: EditPointNameProps) =>
|
|||
|
||||
export interface EditPointLocationProps {
|
||||
updatePoint(update: Partial<TaggedGenericPointer["body"]>): void;
|
||||
location: Record<"x" | "y", number>;
|
||||
xyLocation: Record<"x" | "y", number>;
|
||||
}
|
||||
|
||||
export const EditPointLocation = (props: EditPointLocationProps) =>
|
||||
|
@ -101,7 +104,8 @@ export const EditPointLocation = (props: EditPointLocationProps) =>
|
|||
<label style={{ marginTop: 0 }}>{t("{{axis}} (mm)", { axis })}</label>
|
||||
<BlurableInput
|
||||
type="number"
|
||||
value={props.location[axis]}
|
||||
name={axis}
|
||||
value={props.xyLocation[axis]}
|
||||
min={0}
|
||||
onCommit={e => props.updatePoint({
|
||||
[axis]: round(parseIntInput(e.currentTarget.value))
|
||||
|
@ -120,6 +124,7 @@ export const EditPointRadius = (props: EditPointRadiusProps) =>
|
|||
<label style={{ marginTop: 0 }}>{t("radius (mm)")}</label>
|
||||
<BlurableInput
|
||||
type="number"
|
||||
name="radius"
|
||||
value={props.radius}
|
||||
min={0}
|
||||
onCommit={e => props.updatePoint({
|
||||
|
|
|
@ -50,7 +50,7 @@ export class RawPoints extends React.Component<PointsProps, PointsState> {
|
|||
panel={Panel.Points}
|
||||
linkTo={"/app/designer/points/add"}
|
||||
title={t("Add point")}>
|
||||
<input type="text" onChange={this.update}
|
||||
<input type="text" onChange={this.update} name="searchTerm"
|
||||
placeholder={t("Search your points...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"points"}>
|
||||
|
|
|
@ -49,7 +49,7 @@ export class RawWeeds extends React.Component<WeedsProps, WeedsState> {
|
|||
panel={Panel.Weeds}
|
||||
linkTo={"/app/designer/weeds/add"}
|
||||
title={t("Add weed")}>
|
||||
<input type="text" onChange={this.update}
|
||||
<input type="text" onChange={this.update} name="searchTerm"
|
||||
placeholder={t("Search your weeds...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"weeds-inventory"}>
|
||||
|
|
|
@ -12,6 +12,7 @@ jest.mock("../../../api/crud", () => ({
|
|||
|
||||
let mockPath = "";
|
||||
jest.mock("../../../history", () => ({
|
||||
history: { push: jest.fn() },
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
}));
|
||||
|
||||
|
|
|
@ -46,14 +46,14 @@ describe("<GardenSnapshot />", () => {
|
|||
wrapper.find("input").first().simulate("change", {
|
||||
currentTarget: { value: "new name" }
|
||||
});
|
||||
expect(wrapper.instance().state.name).toEqual("new name");
|
||||
expect(wrapper.instance().state.gardenName).toEqual("new name");
|
||||
});
|
||||
|
||||
it("creates new garden", () => {
|
||||
const wrapper = shallow<GardenSnapshot>(<GardenSnapshot {...fakeProps()} />);
|
||||
wrapper.setState({ name: "new saved garden" });
|
||||
wrapper.setState({ gardenName: "new saved garden" });
|
||||
wrapper.find("button").last().simulate("click");
|
||||
expect(newSavedGarden).toHaveBeenCalledWith("new saved garden");
|
||||
expect(wrapper.instance().state.name).toEqual("");
|
||||
expect(wrapper.instance().state.gardenName).toEqual("");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,8 +11,9 @@ import { t } from "../../i18next_wrapper";
|
|||
import { stopTracking } from "../../connectivity/data_consistency";
|
||||
|
||||
/** Save all Plant to PlantTemplates in a new SavedGarden. */
|
||||
export const snapshotGarden = (name?: string | undefined) =>
|
||||
axios.post<void>(API.current.snapshotPath, name ? { name } : {})
|
||||
export const snapshotGarden = (gardenName?: string | undefined) =>
|
||||
axios.post<void>(API.current.snapshotPath, gardenName
|
||||
? { name: gardenName } : {})
|
||||
.then(() => {
|
||||
success(t("Garden Saved."));
|
||||
history.push("/app/designer/gardens");
|
||||
|
@ -63,9 +64,9 @@ export const openOrCloseGarden = (props: {
|
|||
: props.dispatch(closeSavedGarden());
|
||||
|
||||
/** Create a new SavedGarden with the chosen name. */
|
||||
export const newSavedGarden = (name: string) =>
|
||||
export const newSavedGarden = (gardenName: string) =>
|
||||
(dispatch: Function) => {
|
||||
dispatch(initSave("SavedGarden", { name: name || "Untitled Garden" }))
|
||||
dispatch(initSave("SavedGarden", { name: gardenName || "Untitled Garden" }))
|
||||
.then(() => {
|
||||
success(t("Garden Saved."));
|
||||
history.push("/app/designer/gardens");
|
||||
|
@ -93,8 +94,8 @@ export const copySavedGarden = ({ newSGName, savedGarden, plantTemplates }: {
|
|||
}) =>
|
||||
(dispatch: Function) => {
|
||||
const sourceSavedGardenId = savedGarden.body.id;
|
||||
const name = newSGName || `${savedGarden.body.name} (${t("copy")})`;
|
||||
dispatch(initSaveGetId(savedGarden.kind, { name }))
|
||||
const gardenName = newSGName || `${savedGarden.body.name} (${t("copy")})`;
|
||||
dispatch(initSaveGetId(savedGarden.kind, { name: gardenName }))
|
||||
.then((newSGId: number) => {
|
||||
plantTemplates
|
||||
.filter(x => x.body.saved_garden_id === sourceSavedGardenId)
|
||||
|
|
|
@ -13,7 +13,7 @@ import { Everything } from "../../interfaces";
|
|||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "../designer_panel";
|
||||
import { getPathArray } from "../../history";
|
||||
import { history, getPathArray } from "../../history";
|
||||
import { isNumber } from "lodash";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
@ -28,6 +28,7 @@ const GardenViewButton = (props: GardenViewButtonProps) => {
|
|||
: t("view");
|
||||
return <button
|
||||
className={`fb-button ${gardenIsOpen ? "gray" : "yellow"}`}
|
||||
title={btnText}
|
||||
onClick={onClick}>
|
||||
{btnText}
|
||||
</button>;
|
||||
|
@ -38,6 +39,7 @@ const ApplyGardenButton =
|
|||
(props: { plantPointerCount: number, gardenId: number, dispatch: Function }) =>
|
||||
<button
|
||||
className="fb-button green"
|
||||
title={t("apply garden")}
|
||||
onClick={() => props.plantPointerCount > 0
|
||||
? error(trim(`${t("Please clear current garden first.")}
|
||||
(${props.plantPointerCount} ${t("plants")})`))
|
||||
|
@ -49,6 +51,7 @@ const DestroyGardenButton =
|
|||
(props: { dispatch: Function, gardenUuid: string }) =>
|
||||
<button
|
||||
className="fb-button red"
|
||||
title={t("delete garden")}
|
||||
onClick={() => props.dispatch(destroySavedGarden(props.gardenUuid))}>
|
||||
{t("delete")}
|
||||
</button>;
|
||||
|
@ -75,6 +78,7 @@ export const mapStateToProps = (props: Everything): EditGardenProps => {
|
|||
export class RawEditGarden extends React.Component<EditGardenProps, {}> {
|
||||
render() {
|
||||
const { savedGarden } = this.props;
|
||||
!savedGarden && history.push("/app/designer/gardens");
|
||||
return <DesignerPanel panelName={"saved-garden-edit"}
|
||||
panel={Panel.SavedGardens}>
|
||||
<DesignerPanelHeader
|
||||
|
@ -84,7 +88,7 @@ export class RawEditGarden extends React.Component<EditGardenProps, {}> {
|
|||
backTo={"/app/designer/gardens"} />
|
||||
<DesignerPanelContent panelName={"saved-garden-edit"}>
|
||||
{savedGarden
|
||||
? <div>
|
||||
? <div className={"saved-garden-content"}>
|
||||
<Row>
|
||||
<label>{t("name")}</label>
|
||||
<BlurableInput
|
||||
|
|
|
@ -10,44 +10,46 @@ export interface GardenSnapshotProps {
|
|||
}
|
||||
|
||||
interface GardenSnapshotState {
|
||||
name: string;
|
||||
gardenName: string;
|
||||
}
|
||||
|
||||
/** New SavedGarden name input and snapshot/create buttons. */
|
||||
export class GardenSnapshot
|
||||
extends React.Component<GardenSnapshotProps, GardenSnapshotState> {
|
||||
state = { name: "" };
|
||||
state = { gardenName: "" };
|
||||
|
||||
snapshot = () => {
|
||||
const { currentSavedGarden, plantTemplates } = this.props;
|
||||
!currentSavedGarden
|
||||
? snapshotGarden(this.state.name)
|
||||
? snapshotGarden(this.state.gardenName)
|
||||
: this.props.dispatch(copySavedGarden({
|
||||
newSGName: this.state.name,
|
||||
newSGName: this.state.gardenName,
|
||||
savedGarden: currentSavedGarden,
|
||||
plantTemplates
|
||||
}));
|
||||
this.setState({ name: "" });
|
||||
this.setState({ gardenName: "" });
|
||||
}
|
||||
|
||||
new = () => {
|
||||
this.props.dispatch(newSavedGarden(this.state.name));
|
||||
this.setState({ name: "" });
|
||||
this.props.dispatch(newSavedGarden(this.state.gardenName));
|
||||
this.setState({ gardenName: "" });
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className="garden-snapshot">
|
||||
<label>{t("name")}</label>
|
||||
<input
|
||||
onChange={e => this.setState({ name: e.currentTarget.value })}
|
||||
value={this.state.name} />
|
||||
<input name="name"
|
||||
onChange={e => this.setState({ gardenName: e.currentTarget.value })}
|
||||
value={this.state.gardenName} />
|
||||
<button
|
||||
className={"fb-button gray wide"}
|
||||
title={t("Snapshot current garden")}
|
||||
onClick={this.snapshot}>
|
||||
{t("Snapshot current garden")}
|
||||
</button>
|
||||
<button
|
||||
className="fb-button green wide"
|
||||
title={t("Add new garden")}
|
||||
onClick={this.new}>
|
||||
{t("Add new garden")}
|
||||
</button>
|
||||
|
|
|
@ -46,7 +46,7 @@ export class RawSavedGardens
|
|||
panel={Panel.SavedGardens}
|
||||
linkTo={"/app/designer/gardens/add"}
|
||||
title={t("Add garden")}>
|
||||
<input type="text" onChange={this.onChange}
|
||||
<input type="text" onChange={this.onChange} name="searchTerm"
|
||||
placeholder={t("Search your gardens...")} />
|
||||
</DesignerPanelTop>
|
||||
<EmptyStateWrapper
|
||||
|
@ -66,6 +66,7 @@ export class RawSavedGardens
|
|||
export const SavedGardensLink = () =>
|
||||
<button className="fb-button green"
|
||||
hidden={true}
|
||||
title={t("open saved gardens panel")}
|
||||
onClick={() => history.push("/app/designer/gardens")}>
|
||||
{t("Saved Gardens")}
|
||||
</button>;
|
||||
|
@ -80,10 +81,12 @@ export const SavedGardenHUD = (props: { dispatch: Function }) =>
|
|||
<div className="saved-garden-indicator">
|
||||
<label>{t("Viewing saved garden")}</label>
|
||||
<button className="fb-button gray"
|
||||
title={t("open saved gardens panel")}
|
||||
onClick={() => history.push("/app/designer/gardens")}>
|
||||
{t("Menu")}
|
||||
</button>
|
||||
<button className="fb-button green"
|
||||
title={t("open plants panel")}
|
||||
onClick={() => {
|
||||
history.push("/app/designer/plants");
|
||||
unselectPlant(props.dispatch)();
|
||||
|
@ -91,6 +94,7 @@ export const SavedGardenHUD = (props: { dispatch: Function }) =>
|
|||
{t("Edit")}
|
||||
</button>
|
||||
<button className="fb-button red"
|
||||
title={t("close saved garden")}
|
||||
onClick={() => props.dispatch(closeSavedGarden())}>
|
||||
{t("Exit")}
|
||||
</button>
|
||||
|
|
|
@ -99,6 +99,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
|||
return <div className={`fb-checkbox ${alreadyAdded ? "disabled" : ""}`}>
|
||||
<input type="checkbox" key={JSON.stringify(this.state.toAdd)}
|
||||
title={alreadyAdded ? t("Already added.") : ""}
|
||||
name="toolName"
|
||||
checked={checked}
|
||||
onChange={() => checked
|
||||
? this.remove(toolName)
|
||||
|
@ -118,6 +119,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
|||
</ul>
|
||||
<button
|
||||
className="fb-button green"
|
||||
title={t("add selected stock names")}
|
||||
onClick={() => {
|
||||
this.state.toAdd.filter(this.filterExisting)
|
||||
.map(n => this.newTool(n));
|
||||
|
@ -141,6 +143,7 @@ export class RawAddTool extends React.Component<AddToolProps, AddToolState> {
|
|||
<ToolSVG toolName={this.state.toolName} />
|
||||
<label>{t("Name")}</label>
|
||||
<input defaultValue={this.state.toolName}
|
||||
name="name"
|
||||
onChange={e =>
|
||||
this.setState({ toolName: e.currentTarget.value })} />
|
||||
<SaveBtn onClick={this.save} status={SpecialStatus.DIRTY} />
|
||||
|
|
|
@ -226,7 +226,7 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
panel={Panel.Tools}
|
||||
linkTo={!hasTools ? "/app/designer/tools/add" : 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} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"tools"}>
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface GantryMountedInputProps {
|
|||
export const GantryMountedInput = (props: GantryMountedInputProps) =>
|
||||
<fieldset className="gantry-mounted-input">
|
||||
<label>{t("Gantry-mounted")}</label>
|
||||
<input type="checkbox"
|
||||
<input type="checkbox" name="gantry_mounted"
|
||||
onChange={() => props.onChange({ gantry_mounted: !props.gantryMounted })}
|
||||
checked={props.gantryMounted} />
|
||||
</fieldset>;
|
||||
|
@ -120,7 +120,7 @@ export const SlotLocationInputRow = (props: SlotLocationInputRowProps) =>
|
|||
<Col xs={4} key={axis}>
|
||||
<label>{t("{{axis}} (mm)", { axis })}</label>
|
||||
{axis == "x" && props.gantryMounted
|
||||
? <input disabled value={t("Gantry")} />
|
||||
? <input disabled value={t("Gantry")} name={axis} />
|
||||
: <BlurableInput
|
||||
type="number"
|
||||
value={props.slotLocation[axis]}
|
||||
|
|
|
@ -53,7 +53,7 @@ export class RawZones extends React.Component<ZonesProps, ZonesState> {
|
|||
}))
|
||||
.then((id: number) => this.navigate(id)).catch(() => { })}
|
||||
title={t("Add zone")}>
|
||||
<input type="text" onChange={this.update}
|
||||
<input type="text" onChange={this.update} name="searchTerm"
|
||||
placeholder={t("Search your zones...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"zones-inventory"}>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue