Browse Source

Show next run time for programs

master
Alex Mikhalev 6 years ago
parent
commit
f6d6ef7c0c
  1. 6
      client/components/ProgramSequenceView.tsx
  2. 7
      client/components/ProgramTable.tsx
  3. 41
      client/pages/ProgramPage.tsx
  4. 5
      client/styles/DeviceView.scss
  5. 7
      common/sprinklersRpc/Program.ts
  6. 2
      common/sprinklersRpc/mqtt/MqttProgram.ts
  7. 5
      common/sprinklersRpc/schema/index.ts

6
client/components/ProgramSequenceView.tsx

@ -105,16 +105,17 @@ class ProgramSequenceItem extends React.Component<{
const ProgramSequenceItemD = SortableElement(ProgramSequenceItem); const ProgramSequenceItemD = SortableElement(ProgramSequenceItem);
// tslint:disable: no-shadowed-variable
const ProgramSequenceList = SortableContainer( const ProgramSequenceList = SortableContainer(
observer( observer(
(props: { function ProgramSequenceList(props: {
className: string; className: string;
list: ProgramItem[]; list: ProgramItem[];
sections: Section[]; sections: Section[];
editing: boolean; editing: boolean;
onChange: ItemChangeHandler; onChange: ItemChangeHandler;
onRemove: ItemRemoveHandler; onRemove: ItemRemoveHandler;
}) => { }) {
const { className, list, sections, ...rest } = props; const { className, list, sections, ...rest } = props;
const listItems = list.map((item, index) => { const listItems = list.map((item, index) => {
const key = `item-${index}`; const key = `item-${index}`;
@ -132,7 +133,6 @@ const ProgramSequenceList = SortableContainer(
return <ul className={className}>{listItems}</ul>; return <ul className={className}>{listItems}</ul>;
} }
), ),
{ withRef: true }
); );
@observer @observer

7
client/components/ProgramTable.tsx

@ -8,6 +8,7 @@ import { ProgramSequenceView, ScheduleView } from "@client/components";
import * as route from "@client/routePaths"; import * as route from "@client/routePaths";
import { ISprinklersDevice } from "@common/httpApi"; import { ISprinklersDevice } from "@common/httpApi";
import { Program, SprinklersDevice } from "@common/sprinklersRpc"; import { Program, SprinklersDevice } from "@common/sprinklersRpc";
import moment = require("moment");
@observer @observer
class ProgramRows extends React.Component<{ class ProgramRows extends React.Component<{
@ -69,6 +70,12 @@ class ProgramRows extends React.Component<{
<h4>Sequence: </h4>{" "} <h4>Sequence: </h4>{" "}
<ProgramSequenceView sequence={sequence} sections={sections} /> <ProgramSequenceView sequence={sequence} sections={sections} />
<ScheduleView schedule={schedule} label={<h4>Schedule: </h4>} /> <ScheduleView schedule={schedule} label={<h4>Schedule: </h4>} />
<h4 className="program--nextRun">Next run: </h4>
{
program.nextRun
? <time title={moment(program.nextRun).toString()}>{moment(program.nextRun).fromNow()}</time>
: <time title="never">never</time>
}
</Form> </Form>
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>

41
client/pages/ProgramPage.tsx

@ -20,8 +20,9 @@ import { AppState, injectState } from "@client/state";
import { ISprinklersDevice } from "@common/httpApi"; import { ISprinklersDevice } from "@common/httpApi";
import log from "@common/logger"; import log from "@common/logger";
import { Program, SprinklersDevice } from "@common/sprinklersRpc"; import { Program, SprinklersDevice } from "@common/sprinklersRpc";
import { action } from "mobx";
import classNames = require("classnames"); import classNames = require("classnames");
import { action } from "mobx";
import * as moment from "moment";
interface ProgramPageProps interface ProgramPageProps
extends RouteComponentProps<{ deviceId: string; programId: string }> { extends RouteComponentProps<{ deviceId: string; programId: string }> {
@ -186,7 +187,6 @@ class ProgramPage extends React.Component<ProgramPageProps> {
toggle toggle
label="Enabled" label="Enabled"
checked={enabled} checked={enabled}
readOnly={!editing}
onChange={this.onEnabledChange} onChange={this.onEnabledChange}
/> />
<Form.Checkbox <Form.Checkbox
@ -211,6 +211,16 @@ class ProgramPage extends React.Component<ProgramPageProps> {
editing={editing} editing={editing}
label={<h4>Schedule</h4>} 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> </Form>
</Modal.Content> </Modal.Content>
{this.renderActions(program)} {this.renderActions(program)}
@ -243,6 +253,10 @@ class ProgramPage extends React.Component<ProgramPageProps> {
}, },
err => { err => {
log.error({ err }, "error updating Program"); log.error({ err }, "error updating Program");
this.props.appState.uiStore.addMessage({
error: true,
content: `Error updating program: ${err}`,
});
} }
); );
this.stopEditing(); this.stopEditing();
@ -268,11 +282,28 @@ class ProgramPage extends React.Component<ProgramPageProps> {
@action.bound @action.bound
private onEnabledChange(e: any, p: CheckboxProps) { private onEnabledChange(e: any, p: CheckboxProps) {
if (this.programView) { if (p.checked !== undefined && this.program) {
this.programView.enabled = p.checked!; 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(observer(ProgramPage))); const DecoratedProgramPage = injectState(withRouter(ProgramPage));
export default DecoratedProgramPage; export default DecoratedProgramPage;

5
client/styles/DeviceView.scss

@ -78,6 +78,11 @@ $connected-color: #13d213;
color: green; color: green;
} }
.program--nextRun {
display: inline-block;
padding-right: 0.5em;
}
.ui.modal.programEditor { .ui.modal.programEditor {
&.editing > .content { &.editing > .content {
min-height: 80vh; min-height: 80vh;

7
common/sprinklersRpc/Program.ts

@ -32,6 +32,8 @@ export class Program {
sequence: ProgramItem[] = []; sequence: ProgramItem[] = [];
@observable @observable
running: boolean = false; running: boolean = false;
@observable
nextRun: Date | null = null;
constructor(device: SprinklersDevice, id: number, data?: Partial<Program>) { constructor(device: SprinklersDevice, id: number, data?: Partial<Program>) {
this.device = device; this.device = device;
@ -60,7 +62,8 @@ export class Program {
enabled: this.enabled, enabled: this.enabled,
running: this.running, running: this.running,
schedule: this.schedule.clone(), schedule: this.schedule.clone(),
sequence: this.sequence.slice() sequence: this.sequence.slice(),
nextRun: this.nextRun,
}); });
} }
@ -68,7 +71,7 @@ export class Program {
return ( return (
`Program{name="${this.name}", enabled=${this.enabled}, schedule=${ `Program{name="${this.name}", enabled=${this.enabled}, schedule=${
this.schedule this.schedule
}, ` + `sequence=${this.sequence}, running=${this.running}}` }, ` + `sequence=${this.sequence}, running=${this.running}, nextRun=${this.nextRun}}`
); );
} }
} }

2
common/sprinklersRpc/mqtt/MqttProgram.ts

@ -7,6 +7,8 @@ export class MqttProgram extends s.Program {
onMessage(payload: string, topic: string | undefined) { onMessage(payload: string, topic: string | undefined) {
if (topic === "running") { if (topic === "running") {
this.running = payload === "true"; this.running = payload === "true";
} else if (topic === "nextRun") {
this.nextRun = (payload.length > 0) ? new Date(Number(payload) * 1000.0) : null;
} else if (topic == null) { } else if (topic == null) {
this.updateFromJSON(JSON.parse(payload)); this.updateFromJSON(JSON.parse(payload));
} }

5
common/sprinklersRpc/schema/index.ts

@ -1,4 +1,4 @@
import { createSimpleSchema, ModelSchema, object, primitive } from "serializr"; import { createSimpleSchema, ModelSchema, object, primitive, date } from "serializr";
import * as s from ".."; import * as s from "..";
import list from "./list"; import list from "./list";
@ -85,7 +85,8 @@ export const program: ModelSchema<s.Program> = {
enabled: primitive(), enabled: primitive(),
schedule: object(schedule), schedule: object(schedule),
sequence: list(object(programItem)), sequence: list(object(programItem)),
running: primitive() running: primitive(),
nextRun: date(),
} }
}; };

Loading…
Cancel
Save