Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into new_steps
commit
6aff7445f1
|
@ -5,7 +5,7 @@ class LogService < AbstractServiceRunner
|
|||
THROTTLE_POLICY = ThrottlePolicy.new(name, min: 250, hour: 5_000, day: 25_000)
|
||||
|
||||
LOG_TPL = Rails.env.test? ?
|
||||
"\e[32m.\e[0m" : "FBOS LOG (device_%s): %s\n"
|
||||
"\e[32m.\e[0m" : "FBOS LOG (device_%s) [v%s]: %s\n"
|
||||
ERR_TPL = "MALFORMED LOG CAPTURE: %s"
|
||||
# Clean up excess logs in a non-deterministic manner.
|
||||
# Performs the slow DB query every nth request.
|
||||
|
@ -41,7 +41,9 @@ class LogService < AbstractServiceRunner
|
|||
dev, log = [data.device, data.payload]
|
||||
dev.maybe_unthrottle
|
||||
Log.deliver(Logs::Create.run!(log, device: dev).id)
|
||||
print LOG_TPL % [data.device_id, data.payload["message"] || "??"]
|
||||
print LOG_TPL % [data.device_id,
|
||||
dev.fbos_version || "?",
|
||||
data.payload["message"] || "??"]
|
||||
rescue => x
|
||||
Rollbar.error(x)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import * as React from "react";
|
||||
import { fetchLabFeatures, LabsFeature } from "./labs_features_list_data";
|
||||
import { KeyValShowRow } from "../../controls/key_val_show_row";
|
||||
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { ToggleButton } from "../../controls/toggle_button";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
interface LabsFeaturesListProps {
|
||||
onToggle(feature: LabsFeature): Promise<void>;
|
||||
|
@ -10,19 +12,23 @@ interface LabsFeaturesListProps {
|
|||
|
||||
export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
||||
return <div>
|
||||
{fetchLabFeatures(props.getConfigValue).map((p, i) => {
|
||||
const displayValue = p.displayInvert ? !p.value : p.value;
|
||||
return <KeyValShowRow key={i}
|
||||
label={p.name}
|
||||
labelPlaceholder=""
|
||||
value={p.description}
|
||||
toggleValue={displayValue ? 1 : 0}
|
||||
valuePlaceholder=""
|
||||
onClick={() => {
|
||||
props.onToggle(p)
|
||||
.then(() => p.callback && p.callback());
|
||||
}}
|
||||
disabled={false} />;
|
||||
{fetchLabFeatures(props.getConfigValue).map((feature, i) => {
|
||||
const displayValue = feature.displayInvert ? !feature.value : feature.value;
|
||||
return <Row key={i}>
|
||||
<Col xs={4}>
|
||||
<label>{feature.name}</label>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<p>{feature.description}</p>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<ToggleButton
|
||||
toggleValue={displayValue ? 1 : 0}
|
||||
toggleAction={() => props.onToggle(feature)
|
||||
.then(() => feature.callback && feature.callback())}
|
||||
customText={{ textFalse: t("off"), textTrue: t("on") }} />
|
||||
</Col>
|
||||
</Row>;
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { KeyValShowRow, KeyValRowProps } from "../key_val_show_row";
|
||||
|
||||
describe("<KeyValShowRow />", () => {
|
||||
const fakeProps = (): KeyValRowProps => ({
|
||||
label: "label",
|
||||
labelPlaceholder: "",
|
||||
value: "value",
|
||||
valuePlaceholder: "",
|
||||
onClick: jest.fn(),
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<KeyValShowRow {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("label");
|
||||
expect(wrapper.text()).toContain("value");
|
||||
});
|
||||
});
|
|
@ -2,19 +2,31 @@ import * as React from "react";
|
|||
import { pinToggle } from "../../devices/actions";
|
||||
import { PeripheralListProps } from "./interfaces";
|
||||
import { sortResourcesById } from "../../util";
|
||||
import { KeyValShowRow } from "../key_val_show_row";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { ToggleButton } from "../toggle_button";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export const PeripheralList = (props: PeripheralListProps) =>
|
||||
<div className="peripheral-list">
|
||||
{sortResourcesById(props.peripherals).map(p =>
|
||||
<KeyValShowRow key={p.uuid}
|
||||
label={p.body.label}
|
||||
labelPlaceholder=""
|
||||
value={"" + p.body.pin}
|
||||
toggleValue={(props.pins[p.body.pin || -1] || { value: undefined }).value}
|
||||
valuePlaceholder=""
|
||||
title={t(`Toggle ${p.body.label}`)}
|
||||
onClick={() => p.body.pin && pinToggle(p.body.pin)}
|
||||
disabled={!!props.disabled} />)}
|
||||
{sortResourcesById(props.peripherals).map(peripheral => {
|
||||
const toggleValue =
|
||||
(props.pins[peripheral.body.pin || -1] || { value: undefined }).value;
|
||||
return <Row key={peripheral.uuid}>
|
||||
<Col xs={6}>
|
||||
<label>{peripheral.body.label}</label>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<p>{"" + peripheral.body.pin}</p>
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<ToggleButton
|
||||
toggleValue={toggleValue}
|
||||
toggleAction={() =>
|
||||
peripheral.body.pin && pinToggle(peripheral.body.pin)}
|
||||
title={t(`Toggle ${peripheral.body.label}`)}
|
||||
customText={{ textFalse: t("off"), textTrue: t("on") }}
|
||||
disabled={!!props.disabled} />
|
||||
</Col>
|
||||
</Row>;
|
||||
})}
|
||||
</div>;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Edit } from "../edit";
|
|||
import { SpecialStatus } from "farmbot";
|
||||
import { clickButton } from "../../../__test_support__/helpers";
|
||||
import { WebcamPanelProps } from "../interfaces";
|
||||
import { KeyValEditRow } from "../../key_val_edit_row";
|
||||
|
||||
describe("<Edit/>", () => {
|
||||
const fakeProps = (): WebcamPanelProps => {
|
||||
|
@ -48,4 +49,22 @@ describe("<Edit/>", () => {
|
|||
wrapper.find("WidgetBody").find("KeyValEditRow").first().simulate("click");
|
||||
expect(p.destroy).toHaveBeenCalledWith(p.feeds[0]);
|
||||
});
|
||||
|
||||
it("changes name", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<Edit {...p} />);
|
||||
wrapper.find(KeyValEditRow).first().simulate("labelChange", {
|
||||
currentTarget: { value: "new_name" }
|
||||
});
|
||||
expect(p.edit).toHaveBeenCalledWith(p.feeds[0], { name: "new_name" });
|
||||
});
|
||||
|
||||
it("changes url", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<Edit {...p} />);
|
||||
wrapper.find(KeyValEditRow).first().simulate("valueChange", {
|
||||
currentTarget: { value: "new_url" }
|
||||
});
|
||||
expect(p.edit).toHaveBeenCalledWith(p.feeds[0], { url: "new_url" });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -303,12 +303,25 @@
|
|||
margin-bottom: 0px;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.buttonrow {
|
||||
float:left;
|
||||
.button-row {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
.plant-status-bulk-update {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
margin-left: 1rem;
|
||||
.filter-search {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
line-height: 4.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.panel-content {
|
||||
padding-top: 10rem;
|
||||
padding-top: 15rem;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
padding-bottom: 5rem;
|
||||
|
@ -528,6 +541,9 @@
|
|||
}
|
||||
p {
|
||||
line-height: 3rem;
|
||||
&.tool-slot-position {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
.mounted-tool-header {
|
||||
|
|
|
@ -37,7 +37,7 @@ type HOUR =
|
|||
| 23;
|
||||
type TimeTable = Record<HOUR, DropDownItem>;
|
||||
type EveryTimeTable = Record<PreferredHourFormat, TimeTable>;
|
||||
const TIME_TABLE_12H: TimeTable = {
|
||||
const TIME_TABLE_12H = (): TimeTable => ({
|
||||
0: { label: t("Midnight"), value: 0 },
|
||||
1: { label: "1:00 AM", value: 1 },
|
||||
2: { label: "2:00 AM", value: 2 },
|
||||
|
@ -63,8 +63,8 @@ const TIME_TABLE_12H: TimeTable = {
|
|||
22: { label: "10:00 PM", value: 22 },
|
||||
23: { label: "11:00 PM", value: 23 },
|
||||
[IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY },
|
||||
};
|
||||
const TIME_TABLE_24H: TimeTable = {
|
||||
});
|
||||
const TIME_TABLE_24H = (): TimeTable => ({
|
||||
0: { label: "00:00", value: 0 },
|
||||
1: { label: "01:00", value: 1 },
|
||||
2: { label: "02:00", value: 2 },
|
||||
|
@ -90,13 +90,13 @@ const TIME_TABLE_24H: TimeTable = {
|
|||
22: { label: "22:00", value: 22 },
|
||||
23: { label: "23:00", value: 23 },
|
||||
[IMMEDIATELY]: { label: t("as soon as possible"), value: IMMEDIATELY },
|
||||
};
|
||||
});
|
||||
|
||||
const DEFAULT_HOUR: keyof TimeTable = IMMEDIATELY;
|
||||
const TIME_FORMATS: EveryTimeTable = {
|
||||
"12h": TIME_TABLE_12H,
|
||||
"24h": TIME_TABLE_24H
|
||||
};
|
||||
const TIME_FORMATS = (): EveryTimeTable => ({
|
||||
"12h": TIME_TABLE_12H(),
|
||||
"24h": TIME_TABLE_24H()
|
||||
});
|
||||
|
||||
interface OtaTimeSelectorProps {
|
||||
disabled: boolean;
|
||||
|
@ -136,7 +136,7 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => {
|
|||
}
|
||||
};
|
||||
|
||||
const theTimeTable = TIME_FORMATS[props.timeFormat];
|
||||
const theTimeTable = TIME_FORMATS()[props.timeFormat];
|
||||
const list = Object
|
||||
.values(theTimeTable)
|
||||
.map(x => ({ ...x, label: t(x.label) }))
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { isNumber } from "lodash";
|
||||
import { BotPosition } from "../../../../devices/interfaces";
|
||||
|
||||
export function botPositionLabel(position: BotPosition) {
|
||||
const show = (n: number | undefined) => isNumber(n) ? n : "---";
|
||||
return `(${show(position.x)}, ${show(position.y)}, ${show(position.z)})`;
|
||||
}
|
||||
export const botPositionLabel =
|
||||
(position: BotPosition, gantryMounted?: boolean) => {
|
||||
const show = (n: number | undefined) => isNumber(n) ? n : "---";
|
||||
const x = gantryMounted ? "gantry" : show(position.x);
|
||||
return `(${x}, ${show(position.y)}, ${show(position.z)})`;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
jest.mock("../../../history", () => ({ history: { push: jest.fn() } }));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
edit: jest.fn(),
|
||||
save: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
PlantPanel, PlantPanelProps, EditPlantStatus, EditPlantStatusProps,
|
||||
PlantPanel, PlantPanelProps, EditPlantStatusProps,
|
||||
EditDatePlantedProps, EditDatePlanted, EditPlantLocationProps,
|
||||
EditPlantLocation
|
||||
EditPlantLocation,
|
||||
} from "../plant_panel";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { FormattedPlantInfo } from "../map_state_to_props";
|
||||
|
@ -13,6 +18,11 @@ import { clickButton } from "../../../__test_support__/helpers";
|
|||
import { history } from "../../../history";
|
||||
import moment from "moment";
|
||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||
import { fakePlant } from "../../../__test_support__/fake_state/resources";
|
||||
import { edit } from "../../../api/crud";
|
||||
import {
|
||||
EditPlantStatus, PlantStatusBulkUpdateProps, PlantStatusBulkUpdate
|
||||
} from "../edit_plant_status";
|
||||
|
||||
describe("<PlantPanel/>", () => {
|
||||
const info: FormattedPlantInfo = {
|
||||
|
@ -120,6 +130,42 @@ describe("<EditPlantStatus />", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("<PlantStatusBulkUpdate />", () => {
|
||||
const fakeProps = (): PlantStatusBulkUpdateProps => ({
|
||||
plants: [],
|
||||
selected: [],
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("doesn't update plant statuses", () => {
|
||||
const p = fakeProps();
|
||||
const plant1 = fakePlant();
|
||||
const plant2 = fakePlant();
|
||||
p.plants = [plant1, plant2];
|
||||
p.selected = [plant1.uuid];
|
||||
const wrapper = shallow(<PlantStatusBulkUpdate {...p} />);
|
||||
window.confirm = jest.fn(() => false);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: "planted" });
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(edit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates plant statuses", () => {
|
||||
const p = fakeProps();
|
||||
const plant1 = fakePlant();
|
||||
const plant2 = fakePlant();
|
||||
const plant3 = fakePlant();
|
||||
p.plants = [plant1, plant2, plant3];
|
||||
p.selected = [plant1.uuid, plant2.uuid];
|
||||
const wrapper = shallow(<PlantStatusBulkUpdate {...p} />);
|
||||
window.confirm = jest.fn(() => true);
|
||||
wrapper.find("FBSelect").simulate("change", { label: "", value: "planted" });
|
||||
expect(window.confirm).toHaveBeenCalledWith(
|
||||
"Change the plant status to 'planted' for 2 plants?");
|
||||
expect(edit).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<EditDatePlanted />", () => {
|
||||
const fakeProps = (): EditDatePlantedProps => ({
|
||||
uuid: "Plant.0.0",
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import * as React from "react";
|
||||
import { FBSelect, DropDownItem } from "../../ui";
|
||||
import { PlantOptions } from "../interfaces";
|
||||
import { PlantStage } from "farmbot";
|
||||
import moment from "moment";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TaggedPlant } from "../map/interfaces";
|
||||
import { UUID } from "../../resources/interfaces";
|
||||
import { edit, save } from "../../api/crud";
|
||||
import { EditPlantStatusProps } from "./plant_panel";
|
||||
|
||||
const PLANT_STAGES: DropDownItem[] = [
|
||||
{ value: "planned", label: t("Planned") },
|
||||
{ value: "planted", label: t("Planted") },
|
||||
{ value: "sprouted", label: t("Sprouted") },
|
||||
{ value: "harvested", label: t("Harvested") },
|
||||
];
|
||||
|
||||
const PLANT_STAGES_DDI = {
|
||||
[PLANT_STAGES[0].value]: {
|
||||
label: PLANT_STAGES[0].label,
|
||||
value: PLANT_STAGES[0].value
|
||||
},
|
||||
[PLANT_STAGES[1].value]: {
|
||||
label: PLANT_STAGES[1].label,
|
||||
value: PLANT_STAGES[1].value
|
||||
},
|
||||
[PLANT_STAGES[2].value]: {
|
||||
label: PLANT_STAGES[2].label,
|
||||
value: PLANT_STAGES[2].value
|
||||
},
|
||||
[PLANT_STAGES[3].value]: {
|
||||
label: PLANT_STAGES[3].label,
|
||||
value: PLANT_STAGES[3].value
|
||||
},
|
||||
};
|
||||
|
||||
/** Change `planted_at` value based on `plant_stage` update. */
|
||||
const getUpdateByPlantStage = (plant_stage: PlantStage): PlantOptions => {
|
||||
const update: PlantOptions = { plant_stage };
|
||||
switch (plant_stage) {
|
||||
case "planned":
|
||||
update.planted_at = undefined;
|
||||
break;
|
||||
case "planted":
|
||||
update.planted_at = moment().toISOString();
|
||||
}
|
||||
return update;
|
||||
};
|
||||
|
||||
/** Select a `plant_stage` for a plant. */
|
||||
export function EditPlantStatus(props: EditPlantStatusProps) {
|
||||
const { plantStatus, updatePlant, uuid } = props;
|
||||
return <FBSelect
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={PLANT_STAGES_DDI[plantStatus]}
|
||||
onChange={ddi =>
|
||||
updatePlant(uuid, getUpdateByPlantStage(ddi.value as PlantStage))} />;
|
||||
}
|
||||
|
||||
export interface PlantStatusBulkUpdateProps {
|
||||
plants: TaggedPlant[];
|
||||
selected: UUID[];
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
/** Update `plant_stage` for multiple plants at once. */
|
||||
export const PlantStatusBulkUpdate = (props: PlantStatusBulkUpdateProps) =>
|
||||
<div className="plant-status-bulk-update">
|
||||
<p>{t("update plant status to")}</p>
|
||||
<FBSelect
|
||||
key={JSON.stringify(props.selected)}
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={undefined}
|
||||
customNullLabel={t("Select a status")}
|
||||
onChange={ddi => {
|
||||
const plant_stage = ddi.value as PlantStage;
|
||||
const update = getUpdateByPlantStage(plant_stage);
|
||||
const plants = props.plants.filter(plant =>
|
||||
props.selected.includes(plant.uuid)
|
||||
&& plant.kind === "Point"
|
||||
&& plant.body.plant_stage != plant_stage);
|
||||
plants.length > 0 && confirm(
|
||||
t("Change the plant status to '{{ status }}' for {{ num }} plants?",
|
||||
{ status: plant_stage, num: plants.length }))
|
||||
&& plants.map(plant => {
|
||||
props.dispatch(edit(plant, update));
|
||||
props.dispatch(save(plant.uuid));
|
||||
});
|
||||
}} />
|
||||
</div>;
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
import { FormattedPlantInfo } from "./map_state_to_props";
|
||||
import { round } from "../map/util";
|
||||
import { history } from "../../history";
|
||||
import { FBSelect, DropDownItem, BlurableInput, Row, Col } from "../../ui";
|
||||
import { BlurableInput, Row, Col } from "../../ui";
|
||||
import { PlantOptions } from "../interfaces";
|
||||
import { PlantStage } from "farmbot";
|
||||
import { Moment } from "moment";
|
||||
|
@ -14,6 +14,7 @@ import { parseIntInput } from "../../util";
|
|||
import { startCase } from "lodash";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TimeSettings } from "../../interfaces";
|
||||
import { EditPlantStatus } from "./edit_plant_status";
|
||||
|
||||
export interface PlantPanelProps {
|
||||
info: FormattedPlantInfo;
|
||||
|
@ -24,32 +25,6 @@ export interface PlantPanelProps {
|
|||
timeSettings?: TimeSettings;
|
||||
}
|
||||
|
||||
export const PLANT_STAGES: DropDownItem[] = [
|
||||
{ value: "planned", label: t("Planned") },
|
||||
{ value: "planted", label: t("Planted") },
|
||||
{ value: "sprouted", label: t("Sprouted") },
|
||||
{ value: "harvested", label: t("Harvested") },
|
||||
];
|
||||
|
||||
export const PLANT_STAGES_DDI = {
|
||||
[PLANT_STAGES[0].value]: {
|
||||
label: PLANT_STAGES[0].label,
|
||||
value: PLANT_STAGES[0].value
|
||||
},
|
||||
[PLANT_STAGES[1].value]: {
|
||||
label: PLANT_STAGES[1].label,
|
||||
value: PLANT_STAGES[1].value
|
||||
},
|
||||
[PLANT_STAGES[2].value]: {
|
||||
label: PLANT_STAGES[2].label,
|
||||
value: PLANT_STAGES[2].value
|
||||
},
|
||||
[PLANT_STAGES[3].value]: {
|
||||
label: PLANT_STAGES[3].label,
|
||||
value: PLANT_STAGES[3].value
|
||||
},
|
||||
};
|
||||
|
||||
interface EditPlantProperty {
|
||||
uuid: string;
|
||||
updatePlant(uuid: string, update: PlantOptions): void;
|
||||
|
@ -59,25 +34,6 @@ export interface EditPlantStatusProps extends EditPlantProperty {
|
|||
plantStatus: PlantStage;
|
||||
}
|
||||
|
||||
export function EditPlantStatus(props: EditPlantStatusProps) {
|
||||
const { plantStatus, updatePlant, uuid } = props;
|
||||
return <FBSelect
|
||||
list={PLANT_STAGES}
|
||||
selectedItem={PLANT_STAGES_DDI[plantStatus]}
|
||||
onChange={e => {
|
||||
const plant_stage = e.value as PlantStage;
|
||||
const update: PlantOptions = { plant_stage };
|
||||
switch (plant_stage) {
|
||||
case "planned":
|
||||
update.planted_at = undefined;
|
||||
break;
|
||||
case "planted":
|
||||
update.planted_at = moment().toISOString();
|
||||
}
|
||||
updatePlant(uuid, update);
|
||||
}} />;
|
||||
}
|
||||
|
||||
export interface EditDatePlantedProps extends EditPlantProperty {
|
||||
datePlanted: Moment;
|
||||
timeSettings: TimeSettings;
|
||||
|
|
|
@ -15,6 +15,7 @@ import { t } from "../../i18next_wrapper";
|
|||
import { createGroup } from "../point_groups/actions";
|
||||
import { PanelColor } from "../panel_header";
|
||||
import { error } from "../../toast/toast";
|
||||
import { PlantStatusBulkUpdate } from "./edit_plant_status";
|
||||
|
||||
export const mapStateToProps = (props: Everything): SelectPlantsProps => ({
|
||||
selected: props.resources.consumers.farm_designer.selectedPlants,
|
||||
|
@ -57,7 +58,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
|
||||
ActionButtons = () =>
|
||||
<div className="panel-action-buttons">
|
||||
<div className="buttonrow">
|
||||
<div className="button-row">
|
||||
<button className="fb-button gray"
|
||||
onClick={() => this.props.dispatch(selectPlant(undefined))}>
|
||||
{t("Select none")}
|
||||
|
@ -69,7 +70,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
</button>
|
||||
</div>
|
||||
<label>{t("SELECTION ACTIONS")}</label>
|
||||
<div className="buttonrow">
|
||||
<div className="button-row">
|
||||
<button className="fb-button red"
|
||||
onClick={() => this.destroySelected(this.props.selected)}>
|
||||
{t("Delete")}
|
||||
|
@ -80,6 +81,10 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
: error(t(Content.ERROR_PLANT_TEMPLATE_GROUP))}>
|
||||
{t("Create group")}
|
||||
</button>
|
||||
<PlantStatusBulkUpdate
|
||||
plants={this.props.plants}
|
||||
selected={this.selected}
|
||||
dispatch={this.props.dispatch} />
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
|
|
|
@ -48,18 +48,24 @@ describe("<Tools />", () => {
|
|||
|
||||
it("renders with tools", () => {
|
||||
const p = fakeProps();
|
||||
p.tools = [fakeTool(), fakeTool()];
|
||||
p.tools = [fakeTool(), fakeTool(), fakeTool()];
|
||||
p.tools[0].body.id = 1;
|
||||
p.tools[0].body.status = "inactive";
|
||||
p.tools[0].body.name = undefined;
|
||||
p.tools[1].body.id = 2;
|
||||
p.tools[1].body.name = "my tool";
|
||||
p.toolSlots = [fakeToolSlot()];
|
||||
p.tools[2].body.id = 3;
|
||||
p.tools[2].body.name = "my tool";
|
||||
p.toolSlots = [fakeToolSlot(), fakeToolSlot()];
|
||||
p.toolSlots[0].body.tool_id = 2;
|
||||
p.toolSlots[0].body.x = 1;
|
||||
p.toolSlots[1].body.tool_id = 3;
|
||||
p.toolSlots[1].body.gantry_mounted = true;
|
||||
p.toolSlots[1].body.y = 2;
|
||||
const wrapper = mount(<Tools {...p} />);
|
||||
["foo", "my tool", "unnamed tool", "(1, 0, 0)", "unknown"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
[
|
||||
"foo", "my tool", "unnamed tool", "(1, 0, 0)", "unknown", "(gantry, 2, 0)"
|
||||
].map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
||||
it("navigates to tool", () => {
|
||||
|
|
|
@ -208,7 +208,7 @@ interface ToolSlotInventoryItemProps {
|
|||
}
|
||||
|
||||
const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
||||
const { x, y, z, id, tool_id } = props.toolSlot.body;
|
||||
const { x, y, z, id, tool_id, gantry_mounted } = props.toolSlot.body;
|
||||
return <div
|
||||
className={`tool-slot-search-item ${props.hovered ? "hovered" : ""}`}
|
||||
onClick={() => history.push(`/app/designer/tool-slots/${id}`)}
|
||||
|
@ -219,7 +219,9 @@ const ToolSlotInventoryItem = (props: ToolSlotInventoryItemProps) => {
|
|||
<p>{props.getToolName(tool_id) || t("No tool")}</p>
|
||||
</Col>
|
||||
<Col xs={5}>
|
||||
<p style={{ float: "right" }}>{botPositionLabel({ x, y, z })}</p>
|
||||
<p className="tool-slot-position">
|
||||
{botPositionLabel({ x, y, z }, gantry_mounted)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
|
|
Loading…
Reference in New Issue