Farmbot-Web-App/frontend/regimens/editor/active_editor.tsx

171 lines
6.1 KiB
TypeScript

import * as React from "react";
import { RegimenNameInput } from "./regimen_name_input";
import { ActiveEditorProps, ActiveEditorState } from "./interfaces";
import { push } from "../../history";
import {
RegimenItem, CalendarRow, RegimenItemCalendarRow, RegimenProps
} from "../interfaces";
import { TaggedRegimen, ScopeDeclarationBodyItem } from "farmbot";
import { defensiveClone } from "../../util";
import { overwrite, save, destroy } from "../../api/crud";
import { SaveBtn } from "../../ui";
import { CopyButton } from "./copy_button";
import { LocalsList } from "../../sequences/locals_list/locals_list";
import {
AllowedVariableNodes, VariableNode
} from "../../sequences/locals_list/locals_list_support";
import { addOrEditBodyVariables } from "../../sequences/locals_list/handle_select";
import { t } from "../../i18next_wrapper";
import { Actions } from "../../constants";
import { reduceVariables } from "../../sequences/locals_list/variable_support";
import { determineDropdown, withPrefix } from "../../resources/sequence_meta";
import { ResourceIndex } from "../../resources/interfaces";
/**
* The bottom half of the regimen editor panel (when there's something to
* actually edit).
*/
export class ActiveEditor
extends React.Component<ActiveEditorProps, ActiveEditorState> {
state: ActiveEditorState = { variablesCollapsed: false };
get regimenProps() {
return { regimen: this.props.regimen, dispatch: this.props.dispatch };
}
toggleVarShow = () =>
this.setState({ variablesCollapsed: !this.state.variablesCollapsed });
LocalsList = () => {
const { regimen } = this.props;
return <LocalsList
locationDropdownKey={JSON.stringify(regimen)}
bodyVariables={regimen.body.body}
variableData={this.props.variableData}
sequenceUuid={regimen.uuid}
resources={this.props.resources}
onChange={editRegimenVariables(this.regimenProps)(regimen.body.body)}
collapsible={true}
collapsed={this.state.variablesCollapsed}
toggleVarShow={this.toggleVarShow}
allowedVariableNodes={AllowedVariableNodes.parameter}
shouldDisplay={this.props.shouldDisplay} />;
}
render() {
return <div className="regimen-editor-content">
<div className="regimen-editor-tools">
<RegimenButtonGroup {...this.regimenProps} />
<RegimenNameInput {...this.regimenProps} />
<this.LocalsList />
<hr />
</div>
<OpenSchedulerButton dispatch={this.props.dispatch} />
<RegimenRows {...this.regimenProps}
calendar={this.props.calendar}
varsCollapsed={this.state.variablesCollapsed}
resources={this.props.resources} />
</div>;
}
}
export const OpenSchedulerButton = (props: { dispatch: Function }) =>
<button className="open-bulk-scheduler-btn fb-button gray"
onClick={() => props.dispatch({
type: Actions.SET_SCHEDULER_STATE, payload: true
})}>
{t("Schedule item")}
</button>;
export const editRegimenVariables = (props: RegimenProps) =>
(bodyVariables: VariableNode[]) =>
(variable: ScopeDeclarationBodyItem) => {
const copy = defensiveClone(props.regimen);
copy.body.body = addOrEditBodyVariables(bodyVariables, variable);
props.dispatch(overwrite(props.regimen, copy.body));
};
const RegimenButtonGroup = (props: RegimenProps) =>
<div className="button-group">
<SaveBtn
status={props.regimen.specialStatus}
onClick={() => props.dispatch(save(props.regimen.uuid))} />
<CopyButton regimen={props.regimen} dispatch={props.dispatch} />
<button className="fb-button red"
onClick={() => props.dispatch(destroy(props.regimen.uuid))
.then(() => push("/app/regimens/"))}>
{t("Delete")}
</button>
</div>;
/** Make room for the regimen header variable form when necessary. */
const regimenSectionHeight =
(regimen: TaggedRegimen, varsCollapsed: boolean) => {
let subHeight = 200;
const variables = regimen.body.body.length > 0;
if (variables) { subHeight = 500; }
if (varsCollapsed) { subHeight = 300; }
const variablesDiv = document.getElementById("regimen-editor-tools");
if (variablesDiv) { subHeight = 200 + variablesDiv.offsetHeight; }
return `calc(100vh - ${subHeight}px)`;
};
interface RegimenRowsProps {
regimen: TaggedRegimen;
calendar: CalendarRow[];
dispatch: Function;
varsCollapsed: boolean;
resources: ResourceIndex;
}
const RegimenRows = (props: RegimenRowsProps) =>
<div className="regimen" style={{
height: regimenSectionHeight(props.regimen, props.varsCollapsed)
}}>
{props.calendar.map(regimenDay(props.dispatch, props.resources))}
</div>;
const regimenDay = (dispatch: Function, resources: ResourceIndex) =>
(group: CalendarRow, dayIndex: number) =>
<div className="regimen-day" key={dayIndex}>
<label> {t("Day {{day}}", { day: group.day })} </label>
{group.items.map(regimenItemRow(dispatch, resources, dayIndex))}
</div>;
const regimenItemRow = (
dispatch: Function, resources: ResourceIndex, dayIndex: number
) =>
(row: RegimenItemCalendarRow, itemIndex: number) =>
<div className={`${row.color} regimen-event`}
key={`${dayIndex}.${itemIndex}`}>
<span className="regimen-event-title">{row.name}</span>
<span className="regimen-event-time">{row.hhmm}</span>
<DisplayVarValue row={row} resources={resources} />
<i className="fa fa-trash regimen-control" onClick={() =>
dispatch(removeRegimenItem(row.item, row.regimen))} />
</div>;
const removeRegimenItem = (item: RegimenItem, r: TaggedRegimen) => {
const copy = defensiveClone(r);
copy.body.regimen_items = r.body.regimen_items.filter(x => x !== item);
return overwrite(r, copy.body);
};
interface DisplayVarValueProps {
row: RegimenItemCalendarRow;
resources: ResourceIndex;
}
const DisplayVarValue = (props: DisplayVarValueProps) => {
const { variable, regimen } = props.row;
if (variable) {
const variableNode = reduceVariables(regimen.body.body)[variable];
if (variableNode) {
return <span className="regimen-event-variable">
{withPrefix(determineDropdown(variableNode, props.resources).label)}
</span>;
}
}
return <span />;
};