variable form styling

pull/1144/head
gabrielburnworth 2019-04-09 18:32:18 -07:00
parent c17d22e07a
commit b6eff330ab
8 changed files with 158 additions and 45 deletions

View File

@ -68,12 +68,38 @@
}
.locals-list {
margin-top: 1rem;
max-height: 14rem;
max-height: 25rem;
overflow-y: auto;
overflow-x: hidden;
}
}
.locals-list {
>.location-form {
margin: auto;
margin-top: 1rem;
width: 80%;
max-width: 40rem;
background: $off_white;
label {
margin-top: 0;
}
>.location-form-header {
width: 100%;
background: darken($off_white, 5%);
padding: 1rem;
i {
float: right;
font-size: 2rem;
color: $dark_gray;
}
}
>.location-form-content {
padding: 1rem;
}
}
}
.sequence,
.regimen {
width: 100%;

View File

@ -29,7 +29,7 @@ import {
SequenceEditorMiddleActive, onDrop, SequenceNameAndColor
} from "../sequence_editor_middle_active";
import { mount, shallow } from "enzyme";
import { ActiveMiddleProps } from "../interfaces";
import { ActiveMiddleProps, SequenceHeaderProps } from "../interfaces";
import {
FAKE_RESOURCES, buildResourceIndex
} from "../../__test_support__/resource_index_builder";
@ -44,6 +44,7 @@ import { copySequence, editCurrentSequence } from "../actions";
import { execSequence } from "../../devices/actions";
import { clickButton } from "../../__test_support__/helpers";
import { fakeVariableNameSet } from "../../__test_support__/fake_variables";
import { DropAreaProps } from "../../draggable/interfaces";
describe("<SequenceEditorMiddleActive/>", () => {
const fakeProps = (): ActiveMiddleProps => {
@ -103,10 +104,25 @@ describe("<SequenceEditorMiddleActive/>", () => {
expect(wrapper.find(".drag-drop-area").text()).toEqual("DRAG COMMAND HERE");
});
it("calls DropArea callback", () => {
const p = fakeProps();
const dispatch = jest.fn();
p.dispatch = dispatch;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
const props = wrapper.find("DropArea").props() as DropAreaProps;
props.callback && props.callback("key");
dispatch.mock.calls[0][0](() =>
({ value: 1, intent: "step_splice", draggerId: 2 }));
expect(splice).toHaveBeenCalledWith(expect.objectContaining({
step: 1,
index: Infinity
}));
});
it("has correct height", () => {
const wrapper = mount(<SequenceEditorMiddleActive {...fakeProps()} />);
expect(wrapper.find(".sequence").props().style).toEqual({
height: "calc(100vh - 25rem)"
height: "calc(100vh - 200px)"
});
});
@ -116,7 +132,7 @@ describe("<SequenceEditorMiddleActive/>", () => {
p.shouldDisplay = () => true;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
expect(wrapper.find(".sequence").props().style).toEqual({
height: "calc(100vh - 25rem)"
height: "calc(100vh - 200px)"
});
});
@ -126,7 +142,31 @@ describe("<SequenceEditorMiddleActive/>", () => {
p.shouldDisplay = () => true;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
expect(wrapper.find(".sequence").props().style)
.toEqual({ height: "calc(100vh - 38rem)" });
.toEqual({ height: "calc(100vh - 500px)" });
});
it("has correct height with variable form collapsed", () => {
const p = fakeProps();
p.resources.sequenceMetas = { [p.sequence.uuid]: fakeVariableNameSet() };
p.shouldDisplay = () => true;
const wrapper = mount(<SequenceEditorMiddleActive {...p} />);
wrapper.setState({ variablesCollapsed: true });
expect(wrapper.find(".sequence").props().style)
.toEqual({ height: "calc(100vh - 300px)" });
});
it("automatically calculates height", () => {
document.getElementById = () => ({ offsetHeight: 101 } as HTMLElement);
const wrapper = mount(<SequenceEditorMiddleActive {...fakeProps()} />);
expect(wrapper.find(".sequence").props().style)
.toEqual({ height: "calc(100vh - 301px)" });
});
it("toggles variable form state", () => {
const wrapper = mount(<SequenceEditorMiddleActive {...fakeProps()} />);
const props = wrapper.find("SequenceHeader").props() as SequenceHeaderProps;
props.toggleVarShow();
expect(wrapper.state()).toEqual({ variablesCollapsed: true });
});
});
@ -134,9 +174,8 @@ describe("onDrop()", () => {
it("step_splice", () => {
const dispatch = jest.fn();
onDrop(dispatch, fakeSequence())(0, "fakeUuid");
dispatch.mock.calls[0][0](() => {
return { value: 1, intent: "step_splice", draggerId: 2 };
});
dispatch.mock.calls[0][0](() =>
({ value: 1, intent: "step_splice", draggerId: 2 }));
expect(splice).toHaveBeenCalledWith(expect.objectContaining({
step: 1,
index: 0
@ -146,9 +185,8 @@ describe("onDrop()", () => {
it("step_move", () => {
const dispatch = jest.fn();
onDrop(dispatch, fakeSequence())(3, "fakeUuid");
dispatch.mock.calls[0][0](() => {
return { value: 4, intent: "step_move", draggerId: 5 };
});
dispatch.mock.calls[0][0](() =>
({ value: 4, intent: "step_move", draggerId: 5 }));
expect(move).toHaveBeenCalledWith(expect.objectContaining({
step: 4,
to: 3,

View File

@ -71,6 +71,8 @@ export interface SequenceHeaderProps {
resources: ResourceIndex;
shouldDisplay: ShouldDisplay;
menuOpen: boolean;
variablesCollapsed: boolean;
toggleVarShow: () => void;
}
export type ChannelName = ALLOWED_CHANNEL_NAMES;

View File

@ -94,4 +94,20 @@ describe("<LocationForm/>", () => {
expect(wrapper.find(FBSelect).first().props().list)
.toContainEqual(everyPointDDI("Tool"));
});
it("renders collapse icon: open", () => {
const p = fakeProps();
p.collapsible = true;
p.collapsed = false;
const wrapper = shallow(<LocationForm {...p} />);
expect(wrapper.html()).toContain("fa-caret-up");
});
it("renders collapse icon: closed", () => {
const p = fakeProps();
p.collapsible = true;
p.collapsed = true;
const wrapper = shallow(<LocationForm {...p} />);
expect(wrapper.html()).toContain("fa-caret-down");
});
});

View File

@ -57,6 +57,9 @@ export const LocalsList = (props: LocalsListProps) => {
shouldDisplay={props.shouldDisplay}
hideVariableLabel={Object.values(props.variableData || {}).length < 2}
allowedVariableNodes={props.allowedVariableNodes}
collapsible={props.collapsible}
collapsed={props.collapsed}
toggleVarShow={props.toggleVarShow}
onChange={props.onChange} />)}
</div>;
};

View File

@ -49,6 +49,10 @@ interface CommonProps {
allowedVariableNodes: AllowedVariableNodes;
/** Don't display group dropdown items. */
disallowGroups?: boolean;
/** Add ability to collapse the form content. */
collapsible?: boolean;
collapsed?: boolean;
toggleVarShow?: () => void;
}
export interface LocalsListProps extends CommonProps {

View File

@ -87,36 +87,44 @@ export const LocationForm =
props.hideVariableLabel ? t("Location") : `${label} (${t("Location")})`;
const formTitle = props.hideTypeLabel ? label : formTitleWithType;
return <div className="location-form">
<Row>
<Col xs={12}>
<label>{formTitle}</label>
<FBSelect
key={locationDropdownKey}
list={list}
selectedItem={dropdown}
customNullLabel={NO_VALUE_SELECTED_DDI().label}
onChange={ddi => onChange(convertDDItoVariable({
label, allowedVariableNodes
})(ddi))} />
</Col>
</Row>
{vector &&
<Row>
{["x", "y", "z"].map((axis: Xyz) =>
<Col xs={props.width || 4} key={axis}>
<label>
{t("{{axis}} (mm)", { axis })}
</label>
<BlurableInput type="number"
disabled={isDisabled}
onCommit={manuallyEditAxis({ ...axisPartialProps, axis })}
name={`location-${axis}`}
value={"" + vector[axis]} />
</Col>)}
</Row>}
<DefaultValueForm
variableNode={celeryNode}
resources={resources}
onChange={onChange} />
<div className="location-form-header">
<label>{formTitle}</label>
{props.collapsible &&
<i className={`fa fa-caret-${props.collapsed ? "down" : "up"}`}
onClick={props.toggleVarShow} />}
</div>
{!props.collapsed &&
<div className="location-form-content">
<Row>
<Col xs={12}>
<FBSelect
key={locationDropdownKey}
list={list}
selectedItem={dropdown}
customNullLabel={NO_VALUE_SELECTED_DDI().label}
onChange={ddi => onChange(convertDDItoVariable({
label, allowedVariableNodes
})(ddi))} />
</Col>
</Row>
{vector &&
<Row>
{["x", "y", "z"].map((axis: Xyz) =>
<Col xs={props.width || 4} key={axis}>
<label>
{t("{{axis}} (mm)", { axis })}
</label>
<BlurableInput type="number"
disabled={isDisabled}
onCommit={manuallyEditAxis({ ...axisPartialProps, axis })}
name={`location-${axis}`}
value={"" + vector[axis]} />
</Col>)}
</Row>}
<DefaultValueForm
variableNode={celeryNode}
resources={resources}
onChange={onChange} />
</div>}
</div>;
};

View File

@ -103,7 +103,7 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
const declarations = betterCompact(Object.values(variableData)
.map(v => v &&
isScopeDeclarationBodyItem(v.celeryNode) ? v.celeryNode : undefined));
return <div className="sequence-editor-tools">
return <div id="sequence-editor-tools" className="sequence-editor-tools">
<SequenceBtnGroup {...sequenceAndDispatch}
syncStatus={props.syncStatus}
resources={props.resources}
@ -117,19 +117,32 @@ const SequenceHeader = (props: SequenceHeaderProps) => {
onChange={localListCallback(props)(declarations)}
locationDropdownKey={JSON.stringify(sequence)}
allowedVariableNodes={AllowedVariableNodes.parameter}
collapsible={true}
collapsed={props.variablesCollapsed}
toggleVarShow={props.toggleVarShow}
shouldDisplay={props.shouldDisplay} />
</div>;
};
interface ActiveMiddleState {
variablesCollapsed: boolean;
}
export class SequenceEditorMiddleActive extends
React.Component<ActiveMiddleProps, {}> {
React.Component<ActiveMiddleProps, ActiveMiddleState> {
state: ActiveMiddleState = { variablesCollapsed: false };
/** Make room for the sequence header variable form when necessary. */
get stepSectionHeight() {
const { resources, sequence } = this.props;
let subHeight = 200;
const variables =
Object.keys(resources.sequenceMetas[sequence.uuid] || {}).length > 0;
return `calc(100vh - ${variables ? "38" : "25"}rem)`;
if (variables) { subHeight = 500; }
if (this.state.variablesCollapsed) { subHeight = 300; }
const variablesDiv = document.getElementById("sequence-editor-tools");
if (variablesDiv) { subHeight = 200 + variablesDiv.offsetHeight; }
return `calc(100vh - ${subHeight}px)`;
}
render() {
@ -141,6 +154,9 @@ export class SequenceEditorMiddleActive extends
resources={this.props.resources}
syncStatus={this.props.syncStatus}
shouldDisplay={this.props.shouldDisplay}
variablesCollapsed={this.state.variablesCollapsed}
toggleVarShow={() =>
this.setState({ variablesCollapsed: !this.state.variablesCollapsed })}
menuOpen={this.props.menuOpen} />
<hr />
<div className="sequence" id="sequenceDiv"