sequence css and refactoring

pull/1040/head
gabrielburnworth 2018-11-12 13:06:52 -08:00
parent 034e6ad033
commit 3d20563b67
11 changed files with 242 additions and 92 deletions

View File

@ -261,6 +261,9 @@ export namespace ToolTips {
For example, you can mark a plant as "planted" during a seeding
sequence or delete a weed after removing it.`);
export const UNKNOWN_STEP =
trim(`Unable to properly display this step.`);
// Regimens
export const BULK_SCHEDULER =
trim(`Add sequences to your regimen by selecting a sequence from the

View File

@ -969,3 +969,9 @@ ul {
width: 75%;
margin-top: 1rem;
}
.parent-variable-form {
.row {
margin-top: 1rem;
}
}

View File

@ -96,6 +96,12 @@
&.take-photo-step {
background: $brown;
}
&.resource-update-step {
background: $brown;
}
&.unknown-step {
background: $gray;
}
.help {
float: right;
}
@ -174,6 +180,12 @@
&.take-photo-step a {
color: $dark_brown;
}
&.resource-update-step {
background: $light_brown;
}
&.unknown-step {
background: $light_gray;
}
input,
select {
width: 100%;

View File

@ -18,9 +18,15 @@ jest.mock("../../devices/actions", () => ({
execSequence: jest.fn()
}));
let mockParent = false;
jest.mock("../locals_list", () => ({
extractParent: () => mockParent,
LocalsList: () => <div />,
}));
import * as React from "react";
import {
SequenceEditorMiddleActive, onDrop
SequenceEditorMiddleActive, onDrop, SequenceNameAndColor
} from "../sequence_editor_middle_active";
import { mount, shallow } from "enzyme";
import { ActiveMiddleProps } from "../interfaces";
@ -41,23 +47,21 @@ import { clickButton } from "../../__test_support__/helpers";
describe("<SequenceEditorMiddleActive/>", () => {
const sequence = fakeSequence();
sequence.specialStatus = SpecialStatus.DIRTY;
function fakeProps(): ActiveMiddleProps {
return {
dispatch: jest.fn(),
sequence: sequence,
resources: buildResourceIndex(FAKE_RESOURCES).index,
syncStatus: "synced",
hardwareFlags: fakeHardwareFlags(),
farmwareInfo: {
farmwareNames: [],
firstPartyFarmwareNames: [],
showFirstPartyFarmware: false,
farmwareConfigs: {},
},
shouldDisplay: jest.fn(),
confirmStepDeletion: false,
};
}
const fakeProps = (): ActiveMiddleProps => ({
dispatch: jest.fn(),
sequence,
resources: buildResourceIndex(FAKE_RESOURCES).index,
syncStatus: "synced",
hardwareFlags: fakeHardwareFlags(),
farmwareInfo: {
farmwareNames: [],
firstPartyFarmwareNames: [],
showFirstPartyFarmware: false,
farmwareConfigs: {},
},
shouldDisplay: jest.fn(),
confirmStepDeletion: false,
});
it("saves", () => {
const wrapper = mount(<SequenceEditorMiddleActive {...fakeProps()} />);
@ -93,25 +97,31 @@ describe("<SequenceEditorMiddleActive/>", () => {
expect(wrapper.find(".drag-drop-area").text()).toEqual("DRAG COMMAND HERE");
});
it("edits name", () => {
const p = fakeProps();
const wrapper = shallow(<SequenceEditorMiddleActive {...p} />);
wrapper.find("BlurableInput").simulate("commit", {
currentTarget: { value: "new name" }
it("has correct height", () => {
const wrapper = mount(<SequenceEditorMiddleActive {...fakeProps()} />);
expect(wrapper.find(".sequence").props().style).toEqual({
height: "calc(100vh - 25rem)"
});
expect(edit).toHaveBeenCalledWith(
expect.objectContaining({ uuid: p.sequence.uuid }),
{ name: "new name" });
});
it("edits color", () => {
it("has correct height with variables", () => {
mockParent = false;
const p = fakeProps();
const wrapper = shallow(<SequenceEditorMiddleActive {...p} />);
wrapper.find("ColorPicker").simulate("change", "red");
expect(editCurrentSequence).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({ uuid: p.sequence.uuid }),
{ color: "red" });
p.shouldDisplay = () => true;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
expect(wrapper.find(".sequence").props().style).toEqual({
height: "calc(100vh - 25rem)"
});
});
it("has correct height with variable form", () => {
mockParent = true;
const p = fakeProps();
p.shouldDisplay = () => true;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
expect(wrapper.find(".sequence").props().style).toEqual({
height: "calc(100vh - 38rem)"
});
});
});
@ -147,3 +157,31 @@ describe("onDrop()", () => {
expect(dispatch).not.toHaveBeenCalled();
});
});
describe("<SequenceNameAndColor />", () => {
const fakeProps = () => ({
dispatch: jest.fn(),
sequence: fakeSequence(),
});
it("edits name", () => {
const p = fakeProps();
const wrapper = shallow(<SequenceNameAndColor {...p} />);
wrapper.find("BlurableInput").simulate("commit", {
currentTarget: { value: "new name" }
});
expect(edit).toHaveBeenCalledWith(
expect.objectContaining({ uuid: p.sequence.uuid }),
{ name: "new name" });
});
it("edits color", () => {
const p = fakeProps();
const wrapper = shallow(<SequenceNameAndColor {...p} />);
wrapper.find("ColorPicker").simulate("change", "red");
expect(editCurrentSequence).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({ uuid: p.sequence.uuid }),
{ color: "red" });
});
});

View File

@ -48,6 +48,14 @@ export interface ActiveMiddleProps extends SequenceEditorMiddleProps {
sequence: TaggedSequence;
}
export interface SequenceHeaderProps {
dispatch: Function;
sequence: TaggedSequence;
syncStatus: SyncStatus;
resources: ResourceIndex;
shouldDisplay: ShouldDisplay;
}
export type ChannelName = ALLOWED_CHANNEL_NAMES;
export const INT_NUMERIC_FIELDS = ["milliseconds", "pin_mode", "pin_number",

View File

@ -178,15 +178,17 @@ export const ParentVariableForm =
const isDisabled = (parent.kind == "parameter_declaration") ||
data_value.kind !== "coordinate";
return <div>
<br /> {/** Lol */}
<h5>Import Coordinates From</h5>
<FBSelect
allowEmpty={true}
list={generateList(resources, [PARENT])}
selectedItem={ddiLabel}
onChange={(ddi) => onChange(handleSelect(resources, ddi))} />
<br /> {/** Lol */}
return <div className="parent-variable-form">
<Row>
<Col xs={12}>
<h5>{t("Import Coordinates From")}</h5>
<FBSelect
allowEmpty={true}
list={generateList(resources, [PARENT])}
selectedItem={ddiLabel}
onChange={(ddi) => onChange(handleSelect(resources, ddi))} />
</Col>
</Row>
<Row>
<Col xs={4}>
<InputBox

View File

@ -1,5 +1,5 @@
import * as React from "react";
import { ActiveMiddleProps } from "./interfaces";
import { ActiveMiddleProps, SequenceHeaderProps } from "./interfaces";
import { execSequence } from "../devices/actions";
import { editCurrentSequence } from "./actions";
import { splice, move } from "./step_tiles/index";
@ -8,12 +8,12 @@ import { BlurableInput, Row, Col, SaveBtn, ColorPicker } from "../ui/index";
import { DropArea } from "../draggable/drop_area";
import { stepGet } from "../draggable/actions";
import { copySequence } from "./actions";
import { TaggedSequence } from "farmbot";
import { TaggedSequence, SyncStatus } from "farmbot";
import { save, edit, destroy } from "../api/crud";
import { TestButton } from "./test_button";
import { warning } from "farmbot-toastr";
import { AllSteps } from "./all_steps";
import { LocalsList } from "./locals_list";
import { LocalsList, extractParent } from "./locals_list";
import { Feature } from "../devices/interfaces";
export const onDrop =
@ -37,62 +37,83 @@ export const onDrop =
}
};
const copy = function (dispatch: Function, sequence: TaggedSequence) {
return () => dispatch(copySequence(sequence));
const SequenceBtnGroup = ({ dispatch, sequence, syncStatus }: {
dispatch: Function, sequence: TaggedSequence, syncStatus: SyncStatus
}) =>
<div className="button-group">
<SaveBtn status={sequence.specialStatus}
onClick={() => dispatch(save(sequence.uuid))} />
<TestButton
syncStatus={syncStatus}
sequence={sequence}
onFail={warning}
onClick={() => execSequence(sequence.body)} />
<button
className="fb-button red"
onClick={() => dispatch(destroy(sequence.uuid))}>
{t("Delete")}
</button>
<button
className="fb-button yellow"
onClick={() => dispatch(copySequence(sequence))}>
{t("Copy")}
</button>
</div>;
export const SequenceNameAndColor = ({ dispatch, sequence }: {
dispatch: Function, sequence: TaggedSequence
}) =>
<Row>
<Col xs={11}>
<BlurableInput value={sequence.body.name}
placeholder={t("Sequence Name")}
onCommit={e =>
dispatch(edit(sequence, { name: e.currentTarget.value }))} />
</Col>
<Col xs={1} className="color-picker-col">
<ColorPicker
current={sequence.body.color}
onChange={color =>
editCurrentSequence(dispatch, sequence, { color })} />
</Col>
</Row>;
const SequenceHeader = (props: SequenceHeaderProps) => {
const { sequence, dispatch } = props;
const sequenceAndDispatch = { sequence, dispatch };
return <div className="sequence-editor-tools">
<SequenceBtnGroup {...sequenceAndDispatch} syncStatus={props.syncStatus} />
<SequenceNameAndColor {...sequenceAndDispatch} />
{props.shouldDisplay(Feature.variables) &&
<LocalsList {...sequenceAndDispatch} resources={props.resources} />}
</div>;
};
export class SequenceEditorMiddleActive extends
React.Component<ActiveMiddleProps, {}> {
get stepSectionHeight() {
const variable = this.props.shouldDisplay(Feature.variables)
? !!extractParent(this.props.sequence.body.args.locals.body)
: false;
return `calc(100vh - ${variable ? "38" : "25"}rem)`;
}
render() {
const { dispatch, sequence } = this.props;
return <div className="sequence-editor-content">
<div className="sequence-editor-tools">
<div className="button-group">
<SaveBtn status={sequence.specialStatus}
onClick={() => { dispatch(save(sequence.uuid)); }} />
<TestButton
syncStatus={this.props.syncStatus}
sequence={sequence}
onFail={warning}
onClick={() => execSequence(sequence.body)} />
<button
className="fb-button red"
onClick={() => dispatch(destroy(sequence.uuid))}>
{t("Delete")}
</button>
<button
className="fb-button yellow"
onClick={copy(dispatch, sequence)}>
{t("Copy")}
</button>
</div>
<Row>
<Col xs={11}>
<BlurableInput value={sequence.body.name}
placeholder={t("Sequence Name")}
onCommit={(e) => {
dispatch(edit(sequence, { name: e.currentTarget.value }));
}} />
</Col>
<Col xs={1} className="color-picker-col">
<ColorPicker
current={sequence.body.color}
onChange={color => editCurrentSequence(dispatch, sequence, { color })} />
</Col>
</Row>
{this.props.shouldDisplay(Feature.variables) && <LocalsList
sequence={this.props.sequence}
resources={this.props.resources}
dispatch={this.props.dispatch} />}
<hr />
</div>
<div className="sequence" id="sequenceDiv">
<SequenceHeader
dispatch={this.props.dispatch}
sequence={sequence}
resources={this.props.resources}
syncStatus={this.props.syncStatus}
shouldDisplay={this.props.shouldDisplay} />
<hr />
<div className="sequence" id="sequenceDiv"
style={{ height: this.stepSectionHeight }}>
<AllSteps onDrop={onDrop(dispatch, sequence)} {...this.props} />
<Row>
<Col xs={12}>
<DropArea isLocked={true}
callback={(key) => onDrop(dispatch, sequence)(Infinity, key)}>
callback={key => onDrop(dispatch, sequence)(Infinity, key)}>
{t("DRAG COMMAND HERE")}
</DropArea>
</Col>

View File

@ -0,0 +1,30 @@
import * as React from "react";
import { mount } from "enzyme";
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
import { StepParams } from "../../interfaces";
import { emptyState } from "../../../resources/reducer";
import { TileUnknown } from "../tile_unknown";
import { SequenceBodyItem } from "farmbot";
import { ToolTips } from "../../../constants";
describe("<TileUnknown/>", () => {
const currentStep = {
kind: "unknown_step",
args: { "weird_arg": "hello" }
};
const fakeProps = (): StepParams => ({
currentSequence: fakeSequence(),
currentStep: currentStep as SequenceBodyItem,
dispatch: jest.fn(),
index: 0,
resources: emptyState().index,
confirmStepDeletion: false,
});
it("renders step", () => {
const wrapper = mount(<TileUnknown {...fakeProps()} />);
[ToolTips.UNKNOWN_STEP, "unknown_step", "weird_arg", "hello"].map(string =>
expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase()));
});
});

View File

@ -21,6 +21,7 @@ import { overwrite } from "../../api/crud";
import { TileFindHome } from "./tile_find_home";
import { t } from "i18next";
import { MarkAs } from "./mark_as";
import { TileUnknown } from "./tile_unknown";
interface MoveParams {
step: Step;
@ -137,7 +138,7 @@ export function renderCeleryNode(props: StepParams) {
case "wait": return <TileWait {...props} />;
case "write_pin": return <TileWritePin {...props} />;
case "resource_update": return <MarkAs {...props} />;
default: return <div><hr /> ? Unknown step ? <hr /></div>;
default: return <TileUnknown {...props} />;
}
}

View File

@ -15,7 +15,7 @@ const NONE: DropDownItem = { value: 0, label: "" };
export class MarkAs extends React.Component<StepParams, MarkAsState> {
state: MarkAsState = { nextResource: undefined };
className = "wait-step";
className = "resource-update-step";
commitSelection = (nextAction: DropDownItem) => {
this.props.dispatch(commitStepChanges({

View File

@ -0,0 +1,29 @@
import * as React from "react";
import { StepParams } from "../interfaces";
import { ToolTips } from "../../constants";
import { StepWrapper, StepHeader, StepContent } from "../step_ui";
import { Col, Row } from "../../ui/index";
import { t } from "i18next";
export function TileUnknown(props: StepParams) {
const { dispatch, currentStep, index, currentSequence } = props;
const className = "unknown-step";
return <StepWrapper>
<StepHeader
className={className}
helpText={ToolTips.UNKNOWN_STEP}
currentSequence={currentSequence}
currentStep={currentStep}
dispatch={dispatch}
index={index}
confirmStepDeletion={props.confirmStepDeletion} />
<StepContent className={className}>
<Row>
<Col xs={12}>
<p>{t(ToolTips.UNKNOWN_STEP)}</p>
<code>{JSON.stringify(currentStep)}</code>
</Col>
</Row>
</StepContent>
</StepWrapper>;
}