diff --git a/app/components/DeviceView.tsx b/app/components/DeviceView.tsx index 6ba218f..6211bf9 100644 --- a/app/components/DeviceView.tsx +++ b/app/components/DeviceView.tsx @@ -1,8 +1,7 @@ import * as classNames from "classnames"; import { observer } from "mobx-react"; import * as React from "react"; -import FontAwesome = require("react-fontawesome"); -import { Header, Item } from "semantic-ui-react"; +import { Header, Icon, Item } from "semantic-ui-react"; import { injectState, MqttApiState } from "@app/state"; import { SprinklersDevice } from "@common/sprinklers"; @@ -16,7 +15,7 @@ const ConnectionState = ({ connected }: { connected: boolean }) => { }); return ( -   +   {connected ? "Connected" : "Disconnected"} ); @@ -51,10 +50,10 @@ class DeviceView extends React.Component { Raspberry Pi Grinklers Instance - + ); diff --git a/app/components/ProgramTable.tsx b/app/components/ProgramTable.tsx index 0e290e5..4215477 100644 --- a/app/components/ProgramTable.tsx +++ b/app/components/ProgramTable.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react"; import * as React from "react"; -import { Table } from "semantic-ui-react"; +import { Button, Table } from "semantic-ui-react"; import { Duration } from "@common/Duration"; import { Program, Schedule } from "@common/sprinklers"; @@ -27,11 +27,17 @@ export default class ProgramTable extends React.Component<{ programs: Program[]
  • Section {item.section + 1 + ""} for {duration.toString()}
  • ); }); + const cancelOrRun = () => running ? program.cancel() : program.run(); return [( {"" + (i + 1)} {name} - {running ? "Running" : "Not running"} + + {running ? "Running" : "Not running"} + + {enabled ? "Enabled" : "Not enabled"} ), ( diff --git a/app/components/SectionRunnerView.tsx b/app/components/SectionRunnerView.tsx index 669b1c1..4763d5d 100644 --- a/app/components/SectionRunnerView.tsx +++ b/app/components/SectionRunnerView.tsx @@ -1,16 +1,55 @@ +import * as classNames from "classnames"; import { observer } from "mobx-react"; import * as React from "react"; -import { Segment } from "semantic-ui-react"; +import { Button, Icon, Segment } from "semantic-ui-react"; -import { SectionRunner } from "@common/sprinklers"; +import { Duration } from "@common/Duration"; +import { Section, SectionRun, SectionRunner } from "@common/sprinklers"; + +function PausedState({ paused }: { paused: boolean }) { + const classes = classNames({ + "sectionRunner--pausedState": true, + "sectionRunner--pausedState-paused": paused, + "sectionRunner--pausedState-unpaused": !paused, + }); + return ( + + + {paused ? "Paused" : "Processing"} + + ); +} + +function SectionRunView({ run, sections }: + { run: SectionRun, sections: Section[] }) { + const section = sections[run.section]; + const current = run.startTime != null; + const duration = Duration.fromSeconds(run.duration); + const cancel = run.cancel; + return ( + + '{section.name}' for {duration.toString()} + + + ); +} @observer -export default class SectionRunnerView extends React.Component<{ sectionRunner: SectionRunner }, {}> { +export default class SectionRunnerView extends React.Component<{ + sectionRunner: SectionRunner, sections: Section[], +}, {}> { render() { + const { current, queue, paused } = this.props.sectionRunner; + const { sections } = this.props; + const queueView = queue.map((run) => + ); return ( -

    Section Runner Queue

    - {this.props.sectionRunner.toString()} +

    Section Runner Queue

    + + {current && } + {queueView} +
    ); } diff --git a/app/components/SectionTable.tsx b/app/components/SectionTable.tsx index 1a69ca8..77eb2b2 100644 --- a/app/components/SectionTable.tsx +++ b/app/components/SectionTable.tsx @@ -1,8 +1,7 @@ import * as classNames from "classnames"; import { observer } from "mobx-react"; import * as React from "react"; -import FontAwesome = require("react-fontawesome"); -import { Table } from "semantic-ui-react"; +import { Icon, Table } from "semantic-ui-react"; import { Section } from "@common/sprinklers"; @@ -21,7 +20,7 @@ export default class SectionTable extends React.Component<{ sections: Section[] "section--state-false": !state, }); const sectionState = state ? - ( Irrigating) + ( Irrigating) : "Not irrigating"; return ( diff --git a/app/styles/app.css b/app/styles/app.css index 24ace86..a503e78 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -12,6 +12,20 @@ color: #D20000; } +.sectionRunner--pausedState { + padding-left: .75em; + font-size: .75em; + font-weight: lighter; +} + +.sectionRunner--pausedState > .fa { + padding-right: .2em; +} + +.sectionRunner--pausedState-unpaused { + color: #BBBBBB; +} + .section--number, .program--number { width: 2em diff --git a/common/sprinklers/SectionRunner.ts b/common/sprinklers/SectionRunner.ts index e4d2d56..a05a90a 100644 --- a/common/sprinklers/SectionRunner.ts +++ b/common/sprinklers/SectionRunner.ts @@ -15,9 +15,7 @@ export class SectionRun { this.section = section; } - cancel() { - return this.sectionRunner.cancelRunById(this.id); - } + cancel = () => this.sectionRunner.cancelRunById(this.id); toString() { return `SectionRun{id=${this.id}, section=${this.section}, duration=${this.duration},` + @@ -40,6 +38,18 @@ export class SectionRunner { return this.device.cancelSectionRunId({ runId }); } + setPaused(paused: boolean) { + return this.device.pauseSectionRunner({ paused }); + } + + pause() { + return this.setPaused(true); + } + + unpause() { + return this.setPaused(false); + } + toString(): string { return `SectionRunner{queue="${this.queue}", current="${this.current}", paused=${this.paused}}`; } diff --git a/common/sprinklers/schema/index.ts b/common/sprinklers/schema/index.ts index 14c026c..c228126 100644 --- a/common/sprinklers/schema/index.ts +++ b/common/sprinklers/schema/index.ts @@ -21,7 +21,7 @@ export const section: ModelSchema = { }; export const sectionRun: ModelSchema = { - factory: (c) => new s.SectionRun(c.json.id), + factory: (c) => new s.SectionRun(c.parentContext.target, c.json.id), props: { id: primitive(), section: primitive(), diff --git a/common/sprinklers/schema/requests.ts b/common/sprinklers/schema/requests.ts index a1f48b6..f44949e 100644 --- a/common/sprinklers/schema/requests.ts +++ b/common/sprinklers/schema/requests.ts @@ -1,4 +1,4 @@ -import { createSimpleSchema, deserialize, ModelSchema, object, primitive, serialize } from "serializr"; +import { createSimpleSchema, deserialize, ModelSchema, primitive, serialize } from "serializr"; import * as requests from "../requests"; import * as common from "./common"; @@ -16,9 +16,12 @@ export const withSection: ModelSchema = createSimpleSchema sectionId: primitive(), }); -export const updateProgramData: ModelSchema = createSimpleSchema({ +export const updateProgram: ModelSchema = createSimpleSchema({ ...withProgram.props, - data: object(createSimpleSchema({ "*": true })), + data: { + serializer: (data) => data, + deserializer: (json, done) => { done(null, json); }, + }, }); export const runSection: ModelSchema = createSimpleSchema({ @@ -42,7 +45,7 @@ export function getRequestSchema(request: requests.WithType): ModelSchema { case "cancelProgram": return withProgram; case "updateProgram": - throw new Error("updateProgram not implemented"); + return updateProgram; case "runSection": return runSection; case "cancelSection": diff --git a/package.json b/package.json index d54714c..9d1da4c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "@types/prop-types": "^15.5.1", "@types/react": "^16.0.10", "@types/react-dom": "^16.0.1", - "@types/react-fontawesome": "^1.5.0", "@types/react-hot-loader": "^3.0.4", "@types/webpack-env": "^1.13.0", "@types/ws": "^3.2.0", @@ -85,7 +84,6 @@ "react": "^16.0.0", "react-dev-utils": "^4.1.0", "react-dom": "^16.0.0", - "react-fontawesome": "^1.6.1", "react-hot-loader": "^3.0.0-beta.6", "semantic-ui-css": "^2.2.10", "semantic-ui-react": "^0.74.2", diff --git a/server/index.ts b/server/index.ts index 4d41429..afb9410 100644 --- a/server/index.ts +++ b/server/index.ts @@ -54,7 +54,7 @@ async function deviceCallRequest(socket: WebSocket, data: ws.IDeviceCallRequest) function webSocketHandler(socket: WebSocket) { const stop = autorunAsync(() => { const json = serialize(schema.sprinklersDevice, device); - log.info({ device: json }); + log.trace({ device: json }); const data = { type: "deviceUpdate", name: "grinklers", data: json }; socket.send(JSON.stringify(data)); }, 100); diff --git a/yarn.lock b/yarn.lock index 25ca78d..5d8c49e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -70,12 +70,6 @@ "@types/node" "*" "@types/react" "*" -"@types/react-fontawesome@^1.5.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@types/react-fontawesome/-/react-fontawesome-1.6.1.tgz#a07ff96f89c9a778cc7abb8e66b52f0c47bb3188" - dependencies: - "@types/react" "*" - "@types/react-hot-loader@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/react-hot-loader/-/react-hot-loader-3.0.4.tgz#7fc081509830c64218d8a99a865e2fb4a94572ad" @@ -4432,7 +4426,7 @@ promise@^8.0.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -4619,12 +4613,6 @@ react-error-overlay@^2.0.2: settle-promise "1.0.0" source-map "0.5.6" -react-fontawesome@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/react-fontawesome/-/react-fontawesome-1.6.1.tgz#eddce17e7dc731aa09fd4a186688a61793a16c5c" - dependencies: - prop-types "^15.5.6" - react-hot-loader@^3.0.0-beta.6: version "3.0.0-beta.7" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.0.0-beta.7.tgz#d5847b8165d731c4d5b30d86d5d4716227a0fa83"