add missing panels

pull/1724/head
gabrielburnworth 2020-02-28 08:35:17 -08:00
parent de6f886586
commit 25d944e4b7
10 changed files with 208 additions and 204 deletions

View File

@ -52,6 +52,6 @@ describe("<EditFarmEvent />", () => {
const p = fakeProps();
p.getFarmEvent = jest.fn();
const wrapper = mount(<EditFarmEvent {...p} />);
expect(wrapper.text()).toContain("Loading");
expect(wrapper.text()).toContain("Redirecting");
});
});

View File

@ -3,7 +3,6 @@ import { AddEditFarmEventProps } from "../interfaces";
import { connect } from "react-redux";
import { mapStateToPropsAddEdit } from "./map_state_to_props_add_edit";
import { history } from "../../history";
import { TaggedFarmEvent } from "farmbot";
import { EditFEForm } from "./edit_fe_form";
import { t } from "../../i18next_wrapper";
import { Panel } from "../panel_header";
@ -12,12 +11,9 @@ import {
} from "../designer_panel";
export class RawEditFarmEvent extends React.Component<AddEditFarmEventProps, {}> {
redirect() {
history.push("/app/designer/events");
return <div>{t("Loading")}...</div>;
}
renderForm(fe: TaggedFarmEvent) {
render() {
const fe = this.props.getFarmEvent();
!fe && history.push("/app/designer/events");
const panelName = "edit-farm-event";
return <DesignerPanel panelName={panelName} panel={Panel.FarmEvents}>
<DesignerPanelHeader
@ -25,26 +21,23 @@ export class RawEditFarmEvent extends React.Component<AddEditFarmEventProps, {}>
panel={Panel.FarmEvents}
title={t("Edit event")} />
<DesignerPanelContent panelName={panelName}>
<EditFEForm farmEvent={fe}
deviceTimezone={this.props.deviceTimezone}
repeatOptions={this.props.repeatOptions}
executableOptions={this.props.executableOptions}
dispatch={this.props.dispatch}
findExecutable={this.props.findExecutable}
title={t("Edit event")}
deleteBtn={true}
timeSettings={this.props.timeSettings}
autoSyncEnabled={this.props.autoSyncEnabled}
resources={this.props.resources}
shouldDisplay={this.props.shouldDisplay} />
{fe
? <EditFEForm farmEvent={fe}
deviceTimezone={this.props.deviceTimezone}
repeatOptions={this.props.repeatOptions}
executableOptions={this.props.executableOptions}
dispatch={this.props.dispatch}
findExecutable={this.props.findExecutable}
title={t("Edit event")}
deleteBtn={true}
timeSettings={this.props.timeSettings}
autoSyncEnabled={this.props.autoSyncEnabled}
resources={this.props.resources}
shouldDisplay={this.props.shouldDisplay} />
: <div className={"redirect"}>{t("Redirecting")}...</div>}
</DesignerPanelContent>
</DesignerPanel>;
}
render() {
const fe = this.props.getFarmEvent();
return fe ? this.renderForm(fe) : this.redirect();
}
}
export const EditFarmEvent = connect(mapStateToPropsAddEdit)(RawEditFarmEvent);

View File

@ -4,7 +4,9 @@ import { mapStateToProps, formatPlantInfo } from "./map_state_to_props";
import { PlantPanel } from "./plant_panel";
import { unselectPlant } from "../map/actions";
import { TaggedPlant } from "../map/interfaces";
import { DesignerPanel, DesignerPanelHeader } from "../designer_panel";
import {
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
} from "../designer_panel";
import { t } from "../../i18next_wrapper";
import { EditPlantInfoProps, PlantOptions } from "../interfaces";
import { isString } from "lodash";
@ -36,7 +38,17 @@ export class RawPlantInfo extends React.Component<EditPlantInfoProps, {}> {
fallback = () => {
history.push("/app/designer/plants");
return <span>{t("Redirecting...")}</span>;
return <DesignerPanel panelName={"plant-info"} panel={Panel.Plants}>
<DesignerPanelHeader
panelName={"plant-info"}
panel={Panel.Plants}
title={`${t("Edit")}`}
backTo={"/app/designer/plants"}
onBack={unselectPlant(this.props.dispatch)} />
<DesignerPanelContent panelName={"plants"}>
<span>{t("Redirecting")}...</span>
</DesignerPanelContent>
</DesignerPanel>;
}
default = (plant_info: TaggedPlant) => {

View File

@ -11,6 +11,11 @@ import { ShouldDisplay } from "../../devices/interfaces";
import { getShouldDisplayFn } from "../../farmware/state_to_props";
import { uniq } from "lodash";
import { UUID } from "../../resources/interfaces";
import {
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
} from "../designer_panel";
import { Panel } from "../panel_header";
import { t } from "../../i18next_wrapper";
interface GroupDetailProps {
dispatch: Function;
@ -42,15 +47,21 @@ function mapStateToProps(props: Everything): GroupDetailProps {
}
export class RawGroupDetail extends React.Component<GroupDetailProps, {}> {
render() {
const { group } = this.props;
if (group) {
return <GroupDetailActive {...this.props} group={group} />;
} else {
push("/app/designer/groups");
return <div>loading...</div>;
}
!group && push("/app/designer/groups");
return <DesignerPanel panelName={"group-detail"} panel={Panel.Groups}>
<DesignerPanelHeader
panelName={Panel.Groups}
panel={Panel.Groups}
title={t("Edit group")}
backTo={"/app/designer/groups"} />
<DesignerPanelContent panelName={"groups"}>
{group
? <GroupDetailActive {...this.props} group={group} />
: <div className={"redirect"}>{t("Redirecting")}...</div>}
</DesignerPanelContent>
</DesignerPanel>;
}
}
export const GroupDetail = connect(mapStateToProps)(RawGroupDetail);

View File

@ -1,9 +1,5 @@
import * as React from "react";
import { Panel } from "../panel_header";
import { t } from "../../i18next_wrapper";
import {
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
} from "../designer_panel";
import { TaggedPointGroup, TaggedPoint } from "farmbot";
import { DeleteButton } from "../../ui/delete_button";
import { save, edit } from "../../api/crud";
@ -82,62 +78,52 @@ export class GroupDetailActive
render() {
const { group, dispatch } = this.props;
return <DesignerPanel panelName={"group-detail"} panel={Panel.Groups}>
<DesignerPanelHeader
onBack={this.saveGroup}
panelName={Panel.Groups}
panel={Panel.Groups}
title={t("Edit group")}
backTo={"/app/designer/groups"} />
<DesignerPanelContent
panelName={"groups"}>
<ErrorBoundary>
<label>{t("GROUP NAME")}</label>
<i style={{ float: "right" }}>{this.saved ? "" : " saving..."}</i>
<input
defaultValue={group.body.name}
onChange={this.update}
onBlur={this.saveGroup} />
<div>
<label>
{t("SORT BY")}
</label>
<Paths
key={JSON.stringify(this.pointsSelectedByGroup
.map(p => p.body.id))}
pathPoints={this.pointsSelectedByGroup}
dispatch={dispatch}
group={group} />
<p>
{group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}
</p>
</div>
<label>
{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}
</label>
{this.props.shouldDisplay(Feature.criteria_groups) &&
<GroupPointCountBreakdown
manualCount={group.body.point_ids.length}
totalCount={this.pointsSelectedByGroup.length} />}
<p>{t("Click plants in map to add or remove.")}</p>
{this.props.shouldDisplay(Feature.criteria_groups) &&
this.pointsSelectedByGroup.length != group.body.point_ids.length &&
<p>{t(Content.CRITERIA_SELECTION_COUNT)}</p>}
<div className="groups-list-wrapper">
{this.icons}
</div>
{this.props.shouldDisplay(Feature.criteria_groups) &&
<GroupCriteria dispatch={dispatch}
group={group} slugs={this.props.slugs} />}
<DeleteButton
className="group-delete-btn"
dispatch={dispatch}
uuid={group.uuid}
onDestroy={history.back}>
{t("DELETE GROUP")}
</DeleteButton>
</ErrorBoundary>
</DesignerPanelContent>
</DesignerPanel>;
return <ErrorBoundary>
<label>{t("GROUP NAME")}</label>
<i style={{ float: "right" }}>{this.saved ? "" : " saving..."}</i>
<input
name="name"
defaultValue={group.body.name}
onChange={this.update}
onBlur={this.saveGroup} />
<div className={"group-sort-section"}>
<label>
{t("SORT BY")}
</label>
<Paths
key={JSON.stringify(this.pointsSelectedByGroup
.map(p => p.body.id))}
pathPoints={this.pointsSelectedByGroup}
dispatch={dispatch}
group={group} />
<p>
{group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}
</p>
</div>
<label>
{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}
</label>
{this.props.shouldDisplay(Feature.criteria_groups) &&
<GroupPointCountBreakdown
manualCount={group.body.point_ids.length}
totalCount={this.pointsSelectedByGroup.length} />}
<p>{t("Click plants in map to add or remove.")}</p>
{this.props.shouldDisplay(Feature.criteria_groups) &&
this.pointsSelectedByGroup.length != group.body.point_ids.length &&
<p>{t(Content.CRITERIA_SELECTION_COUNT)}</p>}
<div className="groups-list-wrapper">
{this.icons}
</div>
{this.props.shouldDisplay(Feature.criteria_groups) &&
<GroupCriteria dispatch={dispatch}
group={group} slugs={this.props.slugs} />}
<DeleteButton
className="group-delete-btn"
dispatch={dispatch}
uuid={group.uuid}
onDestroy={history.back}>
{t("DELETE GROUP")}
</DeleteButton>
</ErrorBoundary>;
}
}

View File

@ -34,13 +34,8 @@ export class RawEditPoint extends React.Component<EditPointProps, {}> {
get panelName() { return "point-info"; }
get backTo() { return "/app/designer/points"; }
fallback = () => {
history.push(this.backTo);
return <span>{t("Redirecting...")}</span>;
}
default = (point: TaggedGenericPointer) => {
const { x, y, z } = point.body;
render() {
!this.point && history.push(this.backTo);
return <DesignerPanel panelName={this.panelName} panel={Panel.Points}>
<DesignerPanelHeader
panelName={this.panelName}
@ -51,17 +46,21 @@ export class RawEditPoint extends React.Component<EditPointProps, {}> {
type: Actions.TOGGLE_HOVERED_POINT, payload: undefined
})} />
<DesignerPanelContent panelName={this.panelName}>
<EditPointProperties point={point}
updatePoint={updatePoint(point, this.props.dispatch)} />
<PointActions x={x} y={y} z={z} uuid={point.uuid}
dispatch={this.props.dispatch} />
{this.point
? <div className={"point-panel-content-wrapper"}>
<EditPointProperties point={this.point}
updatePoint={updatePoint(this.point, this.props.dispatch)} />
<PointActions
x={this.point.body.x}
y={this.point.body.y}
z={this.point.body.z}
uuid={this.point.uuid}
dispatch={this.props.dispatch} />
</div>
: <span>{t("Redirecting")}...</span>}
</DesignerPanelContent>
</DesignerPanel>;
}
render() {
return this.point ? this.default(this.point) : this.fallback();
}
}
export const EditPoint = connect(mapStateToProps)(RawEditPoint);

View File

@ -34,13 +34,8 @@ export class RawEditWeed extends React.Component<EditWeedProps, {}> {
get panelName() { return "weed-info"; }
get backTo() { return "/app/designer/weeds"; }
fallback = () => {
history.push(this.backTo);
return <span>{t("Redirecting...")}</span>;
}
default = (point: TaggedGenericPointer) => {
const { x, y, z } = point.body;
render() {
!this.point && history.push(this.backTo);
return <DesignerPanel panelName={this.panelName} panel={Panel.Weeds}>
<DesignerPanelHeader
panelName={this.panelName}
@ -51,17 +46,21 @@ export class RawEditWeed extends React.Component<EditWeedProps, {}> {
type: Actions.TOGGLE_HOVERED_POINT, payload: undefined
})} />
<DesignerPanelContent panelName={this.panelName}>
<EditPointProperties point={point}
updatePoint={updatePoint(point, this.props.dispatch)} />
<PointActions x={x} y={y} z={z} uuid={point.uuid}
dispatch={this.props.dispatch} />
{this.point
? <div className={"weed-panel-content-wrapper"}>
<EditPointProperties point={this.point}
updatePoint={updatePoint(this.point, this.props.dispatch)} />
<PointActions
x={this.point.body.x}
y={this.point.body.y}
z={this.point.body.z}
uuid={this.point.uuid}
dispatch={this.props.dispatch} />
</div>
: <span>{t("Redirecting")}...</span>}
</DesignerPanelContent>
</DesignerPanel>;
}
render() {
return this.point ? this.default(this.point) : this.fallback();
}
}
export const EditWeed = connect(mapStateToProps)(RawEditWeed);

View File

@ -50,18 +50,45 @@ export class RawEditTool extends React.Component<EditToolProps, EditToolState> {
fallback = () => {
history.push("/app/designer/tools");
return <span>{t("Redirecting...")}</span>;
return <this.PanelWrapper>
<span>{t("Redirecting")}...</span>
</this.PanelWrapper>;
}
default = (tool: TaggedTool) => {
const { dispatch } = this.props;
const { toolName } = this.state;
const panelName = "edit-tool";
const isMounted = this.props.mountedToolId == tool.body.id;
const message = isMounted
? t("Cannot delete while mounted.")
: t("Cannot delete while in a slot.");
const activeOrMounted = this.props.isActive(tool.body.id) || isMounted;
return <this.PanelWrapper>
<ToolSVG toolName={this.state.toolName} />
<label>{t("Name")}</label>
<input name="name"
value={toolName}
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
<SaveBtn
onClick={() => {
dispatch(edit(tool, { name: toolName }));
history.push("/app/designer/tools");
}}
status={SpecialStatus.DIRTY} />
<button
className={`fb-button red no-float ${activeOrMounted
? "pseudo-disabled" : ""}`}
title={activeOrMounted ? message : t("delete")}
onClick={() => activeOrMounted
? error(t(message))
: dispatch(destroy(tool.uuid))}>
{t("Delete")}
</button>
</this.PanelWrapper>;
}
PanelWrapper = (props: { children: React.ReactChild | React.ReactChild[] }) => {
const panelName = "edit-tool";
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
<DesignerPanelHeader
panelName={panelName}
@ -69,26 +96,7 @@ export class RawEditTool extends React.Component<EditToolProps, EditToolState> {
backTo={"/app/designer/tools"}
panel={Panel.Tools} />
<DesignerPanelContent panelName={panelName}>
<ToolSVG toolName={this.state.toolName} />
<label>{t("Name")}</label>
<input
value={toolName}
onChange={e => this.setState({ toolName: e.currentTarget.value })} />
<SaveBtn
onClick={() => {
dispatch(edit(tool, { name: toolName }));
history.push("/app/designer/tools");
}}
status={SpecialStatus.DIRTY} />
<button
className={`fb-button red no-float ${activeOrMounted
? "pseudo-disabled" : ""}`}
title={activeOrMounted ? message : t("delete")}
onClick={() => activeOrMounted
? error(t(message))
: dispatch(destroy(tool.uuid))}>
{t("Delete")}
</button>
{props.children}
</DesignerPanelContent>
</DesignerPanel>;
}

View File

@ -24,18 +24,15 @@ export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
return this.toolSlot && this.props.findTool(this.toolSlot.body.tool_id || 0);
}
fallback = () => {
history.push("/app/designer/tools");
return <span>{t("Redirecting...")}</span>;
}
updateSlot = (toolSlot: TaggedToolSlotPointer) =>
(update: Partial<TaggedToolSlotPointer["body"]>) => {
this.props.dispatch(edit(toolSlot, update));
this.props.dispatch(save(toolSlot.uuid));
}
default = (toolSlot: TaggedToolSlotPointer) => {
render() {
const { toolSlot } = this;
!toolSlot && history.push("/app/designer/tools");
const panelName = "edit-tool-slot";
return <DesignerPanel panelName={panelName} panel={Panel.Tools}>
<DesignerPanelHeader
@ -44,39 +41,41 @@ export class RawEditToolSlot extends React.Component<EditToolSlotProps> {
backTo={"/app/designer/tools"}
panel={Panel.Tools} />
<DesignerPanelContent panelName={panelName}>
<SlotEditRows
isExpress={isExpressBoard(this.props.firmwareHardware)}
toolSlot={toolSlot}
tools={this.props.tools}
tool={this.tool}
botPosition={this.props.botPosition}
xySwap={this.props.xySwap}
quadrant={this.props.quadrant}
isActive={this.props.isActive}
updateToolSlot={this.updateSlot(toolSlot)} />
<button
className="fb-button gray no-float"
onClick={() => {
const x = toolSlot.body.gantry_mounted
? this.props.botPosition.x ?? toolSlot.body.x
: toolSlot.body.x;
const { y, z } = toolSlot.body;
moveAbs({ x, y, z });
}}>
{t("Move FarmBot to slot location")}
</button>
<button
className="fb-button red no-float"
onClick={() => this.props.dispatch(destroy(toolSlot.uuid))}>
{t("Delete")}
</button>
{toolSlot
? <div className={"edit-tool-slot-content-wrapper"}>
<SlotEditRows
isExpress={isExpressBoard(this.props.firmwareHardware)}
toolSlot={toolSlot}
tools={this.props.tools}
tool={this.tool}
botPosition={this.props.botPosition}
xySwap={this.props.xySwap}
quadrant={this.props.quadrant}
isActive={this.props.isActive}
updateToolSlot={this.updateSlot(toolSlot)} />
<button
className="fb-button gray no-float"
title={t("move to this location")}
onClick={() => {
const x = toolSlot.body.gantry_mounted
? this.props.botPosition.x ?? toolSlot.body.x
: toolSlot.body.x;
const { y, z } = toolSlot.body;
moveAbs({ x, y, z });
}}>
{t("Move FarmBot to slot location")}
</button>
<button
className="fb-button red no-float"
title={t("Delete")}
onClick={() => this.props.dispatch(destroy(toolSlot.uuid))}>
{t("Delete")}
</button>
</div>
: <span>{t("Redirecting")}...</span>}
</DesignerPanelContent>
</DesignerPanel>;
}
render() {
return this.toolSlot ? this.default(this.toolSlot) : this.fallback();
}
}
export const EditToolSlot = connect(mapStateToPropsEdit)(RawEditToolSlot);

View File

@ -31,12 +31,9 @@ export class RawEditZone extends React.Component<EditZoneProps, {}> {
}
}
fallback = () => {
history.push("/app/designer/zones");
return <span>{t("Redirecting...")}</span>;
}
default = (zone: TaggedPointGroup) => {
render() {
const { zone } = this;
!zone && history.push("/app/designer/zones");
return <DesignerPanel panelName={"zone-info"} panel={Panel.Zones}>
<DesignerPanelHeader
panelName={"zone-info"}
@ -44,24 +41,24 @@ export class RawEditZone extends React.Component<EditZoneProps, {}> {
title={`${t("Edit")} zone`}
backTo={"/app/designer/zones"} />
<DesignerPanelContent panelName={"zone-info"}>
<label>{t("zone name")}</label>
<input
defaultValue={zone.body.name}
onBlur={e => {
this.props.dispatch(edit(zone, { name: e.currentTarget.value }));
this.props.dispatch(save(zone.uuid));
}} />
<LocationSelection
group={zone}
criteria={zone.body.criteria}
dispatch={this.props.dispatch} />
{zone
? <div className={"zone-info-panel-content-wrapper"}>
<label>{t("zone name")}</label>
<input name="name"
defaultValue={zone.body.name}
onBlur={e => {
this.props.dispatch(edit(zone, { name: e.currentTarget.value }));
this.props.dispatch(save(zone.uuid));
}} />
<LocationSelection
group={zone}
criteria={zone.body.criteria}
dispatch={this.props.dispatch} />
</div>
: <span>{t("Redirecting")}...</span>}
</DesignerPanelContent>
</DesignerPanel>;
}
render() {
return this.zone ? this.default(this.zone) : this.fallback();
}
}
export const EditZone = connect(mapStateToProps)(RawEditZone);