You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

310 lines
8.3 KiB

import { assign } from "lodash";
import { observer } from "mobx-react";
import * as qs from "query-string";
import * as React from "react";
import { RouteComponentProps, withRouter } from "react-router";
import {
Button,
CheckboxProps,
Form,
Icon,
Input,
InputOnChangeData,
Menu,
Modal
} from "semantic-ui-react";
import { ProgramSequenceView, ScheduleView } from "@client/components";
7 years ago
import * as route from "@client/routePaths";
import { AppState, injectState } from "@client/state";
import { ISprinklersDevice } from "@common/httpApi";
import log from "@common/logger";
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
import classNames = require("classnames");
import { action } from "mobx";
import * as moment from "moment";
interface ProgramPageProps
extends RouteComponentProps<{ deviceId: string; programId: string }> {
appState: AppState;
}
@observer
class ProgramPage extends React.Component<ProgramPageProps> {
get isEditing(): boolean {
return qs.parse(this.props.location.search).editing != null;
}
deviceInfo: ISprinklersDevice | null = null;
device: SprinklersDevice | null = null;
program: Program | null = null;
programView: Program | null = null;
componentWillUnmount() {
if (this.device) {
this.device.release();
}
}
updateProgram() {
const { userStore, sprinklersRpc } = this.props.appState;
const devId = Number(this.props.match.params.deviceId);
const programId = Number(this.props.match.params.programId);
// tslint:disable-next-line:prefer-conditional-expression
if (this.deviceInfo == null || this.deviceInfo.id !== devId) {
this.deviceInfo = userStore.findDevice(devId);
}
if (!this.deviceInfo || !this.deviceInfo.deviceId) {
if (this.device) {
this.device.release();
this.device = null;
}
return;
} else {
if (this.device == null || this.device.id !== this.deviceInfo.deviceId) {
if (this.device) {
this.device.release();
}
this.device = sprinklersRpc.acquireDevice(this.deviceInfo.deviceId);
}
}
if (!this.program || this.program.id !== programId) {
if (this.device.programs.length > programId && programId >= 0) {
this.program = this.device.programs[programId];
} else {
return;
}
}
if (this.isEditing) {
if (this.programView == null && this.program) {
// this.programView = createViewModel(this.program);
// this.programView = observable(toJS(this.program));
this.programView = this.program.clone();
}
} else {
if (this.programView != null) {
// this.programView.reset();
this.programView = null;
}
}
}
renderName(program: Program) {
const { name } = program;
if (this.isEditing) {
return (
<Menu.Item header>
<Form>
<Form.Group inline>
<Form.Field inline>
<label>
<h4>Program</h4>
</label>
<Input
placeholder="Program Name"
type="text"
value={name}
onChange={this.onNameChange}
/>
</Form.Field>
({program.id})
</Form.Group>
</Form>
</Menu.Item>
);
} else {
return (
<Menu.Item header as="h4">
Program {name} ({program.id})
</Menu.Item>
);
}
}
renderActions(program: Program) {
const { running } = program;
const editing = this.isEditing;
let editButtons;
if (editing) {
editButtons = (
<React.Fragment>
<Button primary onClick={this.save}>
<Icon name="save" />
Save
</Button>
<Button negative onClick={this.stopEditing}>
<Icon name="cancel" />
Cancel
</Button>
</React.Fragment>
);
} else {
editButtons = (
<Button primary onClick={this.startEditing}>
<Icon name="edit" />
Edit
</Button>
);
}
const stopStartButton = (
<Button onClick={this.cancelOrRun} positive={!running} negative={running}>
<Icon name={running ? "stop" : "play"} />
{running ? "Stop" : "Run"}
</Button>
);
return (
<Modal.Actions>
{stopStartButton}
{editButtons}
<Button onClick={this.close}>
<Icon name="arrow left" />
Close
</Button>
</Modal.Actions>
);
}
render() {
this.updateProgram();
const program = this.programView || this.program;
if (!this.device || !program) {
return null;
}
const editing = this.isEditing;
const { running, enabled, schedule, sequence } = program;
const className = classNames("programEditor", editing && "editing");
return (
<Modal open onClose={this.close} className={className}>
<Modal.Header>{this.renderName(program)}</Modal.Header>
<Modal.Content>
<Form>
<Form.Group>
<Form.Checkbox
toggle
label="Enabled"
checked={enabled}
onChange={this.onEnabledChange}
/>
<Form.Checkbox
toggle
label="Running"
checked={running}
readOnly={!editing}
/>
</Form.Group>
<Form.Field>
<label>
<h4>Sequence</h4>
</label>
<ProgramSequenceView
sequence={sequence}
sections={this.device.sections}
editing={editing}
/>
</Form.Field>
<ScheduleView
schedule={schedule}
editing={editing}
label={<h4>Schedule</h4>}
/>
{ !editing && (
<h4 className="program--nextRun">Next run: </h4>)
}
{
!editing && (
program.nextRun
? <time title={moment(program.nextRun).toString()}>{moment(program.nextRun).fromNow()}</time>
: <time title="never">never</time>
)
}
</Form>
</Modal.Content>
{this.renderActions(program)}
</Modal>
);
}
@action.bound
private cancelOrRun() {
if (!this.program) {
return;
}
this.program.running ? this.program.cancel() : this.program.run();
}
@action.bound
private startEditing() {
this.props.history.push({ search: qs.stringify({ editing: true }) });
}
@action.bound
private save() {
if (!this.programView || !this.program) {
return;
}
assign(this.program, this.programView);
this.program.update().then(
data => {
log.info({ data }, "Program updated");
},
err => {
log.error({ err }, "error updating Program");
this.props.appState.uiStore.addMessage({
error: true,
content: `Error updating program: ${err}`,
});
}
);
this.stopEditing();
}
@action.bound
private stopEditing() {
this.props.history.push({ search: "" });
}
@action.bound
private close() {
const { deviceId } = this.props.match.params;
this.props.history.push({ pathname: route.device(deviceId), search: "" });
}
@action.bound
private onNameChange(e: any, p: InputOnChangeData) {
if (this.programView) {
this.programView.name = p.value;
}
}
@action.bound
private onEnabledChange(e: any, p: CheckboxProps) {
if (p.checked !== undefined && this.program) {
this.program.enabled = p.checked;
this.program.update().then(
data => {
log.info({ data }, "Program updated");
this.props.appState.uiStore.addMessage({
success: true,
content: `Program ${this.program!.name} ${this.program!.enabled ? "enabled" : "disabled"}`,
timeout: 2000,
});
},
err => {
log.error({ err }, "error updating Program");
this.props.appState.uiStore.addMessage({
error: true,
content: `Error updating program: ${err}`,
});
}
);
}
}
}
const DecoratedProgramPage = injectState(withRouter(ProgramPage));
export default DecoratedProgramPage;