Farmbot-Web-App/frontend/sequences/sequence_editor_middle_acti...

309 lines
11 KiB
TypeScript
Raw Normal View History

2017-06-29 12:54:02 -06:00
import * as React from "react";
2018-11-12 14:06:52 -07:00
import { ActiveMiddleProps, SequenceHeaderProps } from "./interfaces";
2017-06-29 12:54:02 -06:00
import { editCurrentSequence } from "./actions";
2018-12-05 16:53:42 -07:00
import { splice, move } from "./step_tiles";
import { push } from "../history";
2019-04-09 21:43:05 -06:00
import { BlurableInput, Row, Col, SaveBtn, ColorPicker, Help } from "../ui";
2017-06-29 12:54:02 -06:00
import { DropArea } from "../draggable/drop_area";
import { stepGet } from "../draggable/actions";
import { copySequence } from "./actions";
2018-11-12 14:06:52 -07:00
import { TaggedSequence, SyncStatus } from "farmbot";
2017-06-29 12:54:02 -06:00
import { save, edit, destroy } from "../api/crud";
2017-07-07 08:52:55 -06:00
import { TestButton } from "./test_button";
import { AllSteps } from "./all_steps";
2018-12-20 20:18:10 -07:00
import { LocalsList, localListCallback } from "./locals_list/locals_list";
import { betterCompact, urlFriendly } from "../util";
2019-02-22 19:09:40 -07:00
import { AllowedVariableNodes } from "./locals_list/locals_list_support";
2019-01-12 05:25:02 -07:00
import { ResourceIndex } from "../resources/interfaces";
import { ShouldDisplay } from "../devices/interfaces";
2019-02-22 19:09:40 -07:00
import { isScopeDeclarationBodyItem } from "./locals_list/handle_select";
2019-04-02 13:59:37 -06:00
import { t } from "../i18next_wrapper";
2019-04-09 19:45:59 -06:00
import { Actions } from "../constants";
2019-04-09 21:43:05 -06:00
import { Popover, Position } from "@blueprintjs/core";
import { ToggleButton } from "../controls/toggle_button";
import { Content } from "../constants";
2019-06-05 14:49:24 -06:00
import {
setWebAppConfigValue,
GetWebAppConfigValue,
} from "../config_storage/actions";
2019-04-09 21:43:05 -06:00
import { BooleanSetting } from "../session_keys";
2019-06-05 14:49:24 -06:00
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
2019-07-10 16:45:30 -06:00
import { isUndefined } from "lodash";
2019-10-21 09:47:13 -06:00
import { NO_GROUPS } from "./locals_list/default_value_form";
2019-12-26 13:20:10 -07:00
import { ErrorBoundary } from "../error_boundary";
2017-06-29 12:54:02 -06:00
2017-10-12 21:50:02 -06:00
export const onDrop =
(dispatch1: Function, sequence: TaggedSequence) =>
(index: number, key: string) => {
2017-10-25 18:03:59 -06:00
if (key.length > 0) {
dispatch1(function (dispatch2: Function) {
2017-10-25 18:03:59 -06:00
const dataXferObj = dispatch2(stepGet(key));
const step = dataXferObj.value;
switch (dataXferObj.intent) {
case "step_splice":
return dispatch2(splice({ step, sequence, index }));
case "step_move":
const action =
move({ step, sequence, to: index, from: dataXferObj.draggerId });
return dispatch2(action);
default:
throw new Error("Got unexpected data transfer object.");
}
});
}
};
2017-06-29 12:54:02 -06:00
2019-04-09 21:43:05 -06:00
export interface SequenceSettingsMenuProps {
dispatch: Function;
2019-04-12 20:50:10 -06:00
getWebAppConfigValue: GetWebAppConfigValue;
2019-04-09 21:43:05 -06:00
}
2019-06-28 13:05:37 -06:00
export interface SequenceSettingProps {
2019-06-05 14:49:24 -06:00
label: string;
description: string;
dispatch: Function;
setting: BooleanConfigKey;
getWebAppConfigValue: GetWebAppConfigValue;
confirmation?: string;
2019-07-10 16:45:30 -06:00
defaultOn?: boolean;
2019-06-05 14:49:24 -06:00
}
2019-06-28 13:05:37 -06:00
export const SequenceSetting = (props: SequenceSettingProps) => {
2019-07-10 16:45:30 -06:00
const raw_value = props.getWebAppConfigValue(props.setting);
const value = (props.defaultOn && isUndefined(raw_value)) ? true : !!raw_value;
const proceed = () =>
2019-06-28 13:05:37 -06:00
(props.confirmation && !value) ? confirm(t(props.confirmation)) : true;
2019-06-05 14:49:24 -06:00
return <fieldset>
<label>
{t(props.label)}
</label>
2020-03-13 15:21:44 -06:00
<Help text={t(props.description)} />
2019-06-05 14:49:24 -06:00
<ToggleButton
toggleValue={value}
toggleAction={() => proceed() &&
2019-06-05 14:49:24 -06:00
props.dispatch(setWebAppConfigValue(props.setting, !value))} />
</fieldset>;
};
2019-04-09 21:43:05 -06:00
export const SequenceSettingsMenu =
2019-04-12 20:50:10 -06:00
({ dispatch, getWebAppConfigValue }: SequenceSettingsMenuProps) => {
2019-06-05 14:49:24 -06:00
const commonProps = { dispatch, getWebAppConfigValue };
2019-04-12 20:50:10 -06:00
return <div className="sequence-settings-menu">
2019-06-28 13:05:37 -06:00
<SequenceSetting {...commonProps}
2019-06-05 14:49:24 -06:00
setting={BooleanSetting.confirm_step_deletion}
label={t("Confirm step deletion")}
description={Content.CONFIRM_STEP_DELETION} />
2019-07-10 16:45:30 -06:00
<SequenceSetting {...commonProps}
2019-07-12 14:50:32 -06:00
setting={BooleanSetting.confirm_sequence_deletion}
2019-07-10 16:45:30 -06:00
defaultOn={true}
label={t("Confirm sequence deletion")}
description={Content.CONFIRM_SEQUENCE_DELETION} />
2019-06-28 13:05:37 -06:00
<SequenceSetting {...commonProps}
2019-06-05 14:49:24 -06:00
setting={BooleanSetting.show_pins}
label={t("Show pins")}
description={Content.SHOW_PINS} />
2019-06-28 13:05:37 -06:00
<SequenceSetting {...commonProps}
2019-06-14 17:00:42 -06:00
setting={BooleanSetting.expand_step_options}
2019-06-05 14:49:24 -06:00
label={t("Open options by default")}
description={Content.EXPAND_STEP_OPTIONS} />
2019-06-28 13:05:37 -06:00
<SequenceSetting {...commonProps}
setting={BooleanSetting.discard_unsaved_sequences}
confirmation={Content.DISCARD_UNSAVED_SEQUENCE_CHANGES_CONFIRM}
label={t("Discard unsaved sequence changes")}
description={Content.DISCARD_UNSAVED_SEQUENCE_CHANGES} />
2019-04-09 21:43:05 -06:00
</div>;
2019-04-12 20:50:10 -06:00
};
2019-04-09 21:43:05 -06:00
2019-01-12 05:25:02 -07:00
interface SequenceBtnGroupProps {
dispatch: Function;
sequence: TaggedSequence;
syncStatus: SyncStatus;
resources: ResourceIndex;
shouldDisplay: ShouldDisplay;
menuOpen: boolean;
2019-04-12 20:50:10 -06:00
getWebAppConfigValue: GetWebAppConfigValue;
2019-01-12 05:25:02 -07:00
}
const SequenceBtnGroup = ({
2019-10-10 14:13:35 -06:00
dispatch,
sequence,
syncStatus,
resources,
shouldDisplay,
menuOpen,
2019-04-12 20:50:10 -06:00
getWebAppConfigValue
2019-01-12 05:25:02 -07:00
}: SequenceBtnGroupProps) =>
2018-11-12 14:06:52 -07:00
<div className="button-group">
<SaveBtn status={sequence.specialStatus}
onClick={() => dispatch(save(sequence.uuid)).then(() =>
push(`/app/sequences/${urlFriendly(sequence.body.name)}`))} />
2018-11-12 14:06:52 -07:00
<TestButton
syncStatus={syncStatus}
sequence={sequence}
2019-01-12 05:25:02 -07:00
resources={resources}
shouldDisplay={shouldDisplay}
menuOpen={menuOpen}
dispatch={dispatch} />
2018-11-12 14:06:52 -07:00
<button
className="fb-button red"
2020-02-28 09:34:28 -07:00
title={t("delete sequence")}
2019-07-10 16:45:30 -06:00
onClick={() => {
const confirm = getWebAppConfigValue(
2019-07-12 14:50:32 -06:00
BooleanSetting.confirm_sequence_deletion);
2020-01-03 13:04:45 -07:00
const force = !(confirm ?? true);
2019-07-10 16:45:30 -06:00
dispatch(destroy(sequence.uuid, force))
.then(() => push("/app/sequences/"));
}}>
2018-11-12 14:06:52 -07:00
{t("Delete")}
</button>
<button
className="fb-button yellow"
2020-02-28 09:34:28 -07:00
title={t("copy sequence")}
2018-11-12 14:06:52 -07:00
onClick={() => dispatch(copySequence(sequence))}>
{t("Copy")}
</button>
2019-04-09 21:43:05 -06:00
<div className={"settings-menu-button"}>
<Popover position={Position.BOTTOM_RIGHT}>
<i className="fa fa-gear" />
<SequenceSettingsMenu
dispatch={dispatch}
2019-04-12 20:50:10 -06:00
getWebAppConfigValue={getWebAppConfigValue} />
2019-04-09 21:43:05 -06:00
</Popover>
</div>
2018-11-12 14:06:52 -07:00
</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 };
2018-12-20 20:18:10 -07:00
const variableData = props.resources.sequenceMetas[sequence.uuid] || {};
2019-02-22 19:09:40 -07:00
const declarations = betterCompact(Object.values(variableData)
.map(v => v &&
isScopeDeclarationBodyItem(v.celeryNode) ? v.celeryNode : undefined));
2019-04-09 19:32:18 -06:00
return <div id="sequence-editor-tools" className="sequence-editor-tools">
2019-01-12 05:25:02 -07:00
<SequenceBtnGroup {...sequenceAndDispatch}
syncStatus={props.syncStatus}
resources={props.resources}
shouldDisplay={props.shouldDisplay}
2019-04-12 20:50:10 -06:00
getWebAppConfigValue={props.getWebAppConfigValue}
2019-01-12 05:25:02 -07:00
menuOpen={props.menuOpen} />
2018-11-12 14:06:52 -07:00
<SequenceNameAndColor {...sequenceAndDispatch} />
2019-12-26 13:20:10 -07:00
<ErrorBoundary>
<LocalsList
variableData={variableData}
sequenceUuid={sequence.uuid}
resources={props.resources}
onChange={localListCallback(props)(declarations)}
locationDropdownKey={JSON.stringify(sequence)}
allowedVariableNodes={AllowedVariableNodes.parameter}
collapsible={true}
collapsed={props.variablesCollapsed}
toggleVarShow={props.toggleVarShow}
shouldDisplay={props.shouldDisplay}
hideGroups={true}
customFilterRule={NO_GROUPS} />
</ErrorBoundary>
2018-11-12 14:06:52 -07:00
</div>;
2017-06-29 12:54:02 -06:00
};
2019-04-09 19:32:18 -06:00
interface ActiveMiddleState {
variablesCollapsed: boolean;
}
2017-07-26 09:40:19 -06:00
export class SequenceEditorMiddleActive extends
2019-04-09 19:32:18 -06:00
React.Component<ActiveMiddleProps, ActiveMiddleState> {
state: ActiveMiddleState = { variablesCollapsed: false };
2018-12-20 20:18:10 -07:00
/** Make room for the sequence header variable form when necessary. */
2018-11-12 14:06:52 -07:00
get stepSectionHeight() {
2018-11-29 09:52:26 -07:00
const { resources, sequence } = this.props;
2019-04-09 19:32:18 -06:00
let subHeight = 200;
2018-12-20 20:18:10 -07:00
const variables =
Object.keys(resources.sequenceMetas[sequence.uuid] || {}).length > 0;
2019-04-09 19:32:18 -06:00
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)`;
2018-11-12 14:06:52 -07:00
}
2018-11-29 09:52:26 -07:00
2019-04-12 20:50:10 -06:00
get stepProps() {
const getConfig = this.props.getWebAppConfigValue;
return {
sequence: this.props.sequence,
onDrop: onDrop(this.props.dispatch, this.props.sequence),
dispatch: this.props.dispatch,
resources: this.props.resources,
hardwareFlags: this.props.hardwareFlags,
2019-12-27 11:37:54 -07:00
farmwareData: this.props.farmwareData,
2019-04-12 20:50:10 -06:00
shouldDisplay: this.props.shouldDisplay,
confirmStepDeletion: !!getConfig(BooleanSetting.confirm_step_deletion),
2019-04-18 16:58:29 -06:00
showPins: !!getConfig(BooleanSetting.show_pins),
2019-06-14 17:00:42 -06:00
expandStepOptions: !!getConfig(BooleanSetting.expand_step_options),
2019-04-12 20:50:10 -06:00
};
}
2017-06-29 12:54:02 -06:00
render() {
const { dispatch, sequence } = this.props;
return <div className="sequence-editor-content">
2018-11-12 14:06:52 -07:00
<SequenceHeader
dispatch={this.props.dispatch}
sequence={sequence}
resources={this.props.resources}
syncStatus={this.props.syncStatus}
2019-01-12 05:25:02 -07:00
shouldDisplay={this.props.shouldDisplay}
2019-04-09 19:32:18 -06:00
variablesCollapsed={this.state.variablesCollapsed}
toggleVarShow={() =>
this.setState({ variablesCollapsed: !this.state.variablesCollapsed })}
2019-04-12 20:50:10 -06:00
getWebAppConfigValue={this.props.getWebAppConfigValue}
2019-01-12 05:25:02 -07:00
menuOpen={this.props.menuOpen} />
2018-11-12 14:06:52 -07:00
<hr />
<div className="sequence" id="sequenceDiv"
style={{ height: this.stepSectionHeight }}>
2019-12-26 13:20:10 -07:00
<ErrorBoundary>
<AllSteps {...this.stepProps} />
</ErrorBoundary>
<Row>
<Col xs={12}>
<DropArea isLocked={true}
2018-11-12 14:06:52 -07:00
callback={key => onDrop(dispatch, sequence)(Infinity, key)}>
{t("DRAG COMMAND HERE")}
</DropArea>
2019-04-09 19:45:59 -06:00
<AddCommandButton dispatch={dispatch} index={99999999} />
</Col>
</Row>
2017-07-05 23:49:27 -06:00
</div>
</div>;
2017-06-29 12:54:02 -06:00
}
}
2019-04-09 19:45:59 -06:00
export const AddCommandButton = (props: { dispatch: Function, index: number }) =>
<div className="add-command-button-container">
<button
className="add-command fb-button gray"
2020-02-28 09:34:28 -07:00
title={t("add sequence step")}
2019-04-09 19:45:59 -06:00
onClick={() => props.dispatch({
type: Actions.SET_SEQUENCE_STEP_POSITION,
payload: props.index,
})}>
{t("Add command")}
</button>
</div>;