From ba4ee3379246452fd0c86ea31b52fa0db5f5f471 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Sat, 6 May 2017 15:39:25 -0600 Subject: [PATCH] Added tslint and fixed all lint issues --- app/script/App.tsx | 70 ++++++++++------- app/script/index.tsx | 4 +- app/script/mqtt.ts | 161 +++++++++++++++++++++++--------------- app/script/paho-mqtt.d.ts | 60 +++++++------- app/script/sprinklers.ts | 62 +++++++-------- app/style/app.css | 29 +++++-- package.json | 7 +- tsconfig.json | 1 + tslint.json | 25 ++++++ 9 files changed, 258 insertions(+), 161 deletions(-) create mode 100644 tslint.json diff --git a/app/script/App.tsx b/app/script/App.tsx index 8955811..6b2630b 100644 --- a/app/script/App.tsx +++ b/app/script/App.tsx @@ -6,30 +6,41 @@ import FontAwesome = require("react-fontawesome"); import * as classNames from "classnames"; import "semantic-ui-css/semantic.css"; -import "font-awesome/css/font-awesome.css" +import "font-awesome/css/font-awesome.css"; import "app/style/app.css"; +/* tslint:disable:object-literal-sort-keys */ + @observer class SectionTable extends React.PureComponent<{ sections: Section[] }, void> { - static renderRow(section: Section, index: number) { + private static renderRow(section: Section, index: number) { const { name, state } = section; return ( - Section {name} - State: {state + ""} + {"" + (index + 1)} + {name} + {state ? + ( Irrigating) + : "Not irrigating"} + ); } - render() { + public render() { return ( Sections - Name - State + # + Name + State @@ -44,26 +55,28 @@ class SectionTable extends React.PureComponent<{ sections: Section[] }, void> { @observer class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> { - static renderRow(program: Program, i: number) { + private static renderRow(program: Program, i: number) { const { name, running } = program; return ( - Program {name} - running: {running + ""} + {"" + (i + 1)} + {name} + {running ? "Running" : "Not running"} ); } - render() { + public render() { return ( -
+
Programs - Name - Running + # + Name + Running @@ -76,9 +89,20 @@ class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> { } } +const ConnectionState = ({ connected }: { connected: boolean }) => + + +   + {connected ? "Connected" : "Disconnected"} + ; + @observer class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> { - render() { + public render() { const { id, connected, sections, programs } = this.props.device; return ( @@ -86,15 +110,7 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void>
Device {id} - - -   - {connected ? "Connected" : "Disconnected"} - +
@@ -109,7 +125,7 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> @observer export default class App extends React.PureComponent<{ device: SprinklersDevice }, any> { - render() { - return + public render() { + return ; } -} \ No newline at end of file +} diff --git a/app/script/index.tsx b/app/script/index.tsx index f9a1040..f957fd9 100644 --- a/app/script/index.tsx +++ b/app/script/index.tsx @@ -21,5 +21,5 @@ if (module.hot) { ReactDOM.render( , rootElem); - }) -} \ No newline at end of file + }); +} diff --git a/app/script/mqtt.ts b/app/script/mqtt.ts index 69044c8..12a3e3e 100644 --- a/app/script/mqtt.ts +++ b/app/script/mqtt.ts @@ -1,47 +1,48 @@ -/// import "paho-mqtt/mqttws31"; import MQTT = Paho.MQTT; import { EventEmitter } from "events"; -import { SprinklersDevice, SprinklersApi, Section, Program } from "./sprinklers"; - +import * as objectAssign from "object-assign"; +import { + SprinklersDevice, SprinklersApi, Section, Program, ProgramItem, Schedule, TimeOfDay, Weekday, +} from "./sprinklers"; export class MqttApiClient extends EventEmitter implements SprinklersApi { - client: MQTT.Client + private static newClientId() { + return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000); + } - connected: boolean + public client: MQTT.Client; - devices: { [prefix: string]: MqttSprinklersDevice } = {}; + public connected: boolean; + + public devices: { [prefix: string]: MqttSprinklersDevice } = {}; constructor() { super(); this.client = new MQTT.Client(location.hostname, 1884, MqttApiClient.newClientId()); - this.client.onMessageArrived = m => this.onMessageArrived(m); - this.client.onConnectionLost = e => this.onConnectionLost(e); - } - - static newClientId() { - return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000); + this.client.onMessageArrived = (m) => this.onMessageArrived(m); + this.client.onConnectionLost = (e) => this.onConnectionLost(e); } - start() { + public start() { console.log("connecting to mqtt with client id %s", this.client.clientId); this.client.connect({ onFailure: (e) => { console.log("mqtt error: ", e.errorMessage); }, onSuccess: () => { - console.log("mqtt connected") + console.log("mqtt connected"); this.connected = true; - for (const prefix in this.devices) { + for (const prefix of Object.keys(this.devices)) { const device = this.devices[prefix]; device.doSubscribe(); } - } - }) + }, + }); } - getDevice(prefix: string): SprinklersDevice { + public getDevice(prefix: string): SprinklersDevice { if (/\//.test(prefix)) { throw new Error("Prefix cannot contain a /"); } @@ -54,16 +55,18 @@ export class MqttApiClient extends EventEmitter implements SprinklersApi { return this.devices[prefix]; } - removeDevice(prefix: string) { + public removeDevice(prefix: string) { const device = this.devices[prefix]; - if (!device) return; + if (!device) { + return; + } device.doUnsubscribe(); delete this.devices[prefix]; } private onMessageArrived(m: MQTT.Message) { // console.log("message arrived: ", m) - const topicIdx = m.destinationName.indexOf('/'); // find the first / + const topicIdx = m.destinationName.indexOf("/"); // find the first / const prefix = m.destinationName.substr(0, topicIdx); // assume prefix does not contain a / const topic = m.destinationName.substr(topicIdx + 1); const device = this.devices[prefix]; @@ -80,8 +83,8 @@ export class MqttApiClient extends EventEmitter implements SprinklersApi { } class MqttSprinklersDevice extends SprinklersDevice { - readonly apiClient: MqttApiClient; - readonly prefix: string; + public readonly apiClient: MqttApiClient; + public readonly prefix: string; constructor(apiClient: MqttApiClient, prefix: string) { super(); @@ -89,27 +92,16 @@ class MqttSprinklersDevice extends SprinklersDevice { this.prefix = prefix; } - private getSubscriptions() { - return [ - `${this.prefix}/connected`, - `${this.prefix}/sections`, - `${this.prefix}/sections/+/#`, - `${this.prefix}/programs`, - `${this.prefix}/programs/+/#` - ]; - } - - doSubscribe() { + public doSubscribe() { const c = this.apiClient.client; this.getSubscriptions() - .forEach(filter => c.subscribe(filter, { qos: 1 })); - + .forEach((filter) => c.subscribe(filter, { qos: 1 })); } - doUnsubscribe() { + public doUnsubscribe() { const c = this.apiClient.client; this.getSubscriptions() - .forEach(filter => c.unsubscribe(filter)); + .forEach((filter) => c.unsubscribe(filter)); } /** @@ -117,78 +109,119 @@ class MqttSprinklersDevice extends SprinklersDevice { * @param topic The topic, with prefix removed * @param payload The payload string */ - onMessage(topic: string, payload: string) { - var matches; - if (topic == "connected") { - this.connected = (payload == "true"); + public onMessage(topic: string, payload: string) { + if (topic === "connected") { + this.connected = (payload === "true"); // console.log(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`) - } else if ((matches = topic.match(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/)) != null) { - const [topic, secStr, subTopic] = matches; + return; + } + let matches = topic.match(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/); + if (matches != null) { + const [_topic, secStr, subTopic] = matches; // console.log(`section: ${secStr}, topic: ${subTopic}, payload: ${payload}`); if (!secStr) { // new number of sections this.sections = new Array(Number(payload)); } else { const secNum = Number(secStr); - var section = this.sections[secNum]; + let section = this.sections[secNum]; if (!section) { this.sections[secNum] = section = new MqttSection(); } (section as MqttSection).onMessage(subTopic, payload); } - } else if ((matches = topic.match(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/)) != null) { - const [topic, progStr, subTopic] = matches; + return; + } + matches = topic.match(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/); + if (matches != null) { + const [_topic, progStr, subTopic] = matches; // console.log(`program: ${progStr}, topic: ${subTopic}, payload: ${payload}`); if (!progStr) { // new number of programs this.programs = new Array(Number(payload)); } else { const progNum = Number(progStr); - var program = this.programs[progNum]; + let program = this.programs[progNum]; if (!program) { this.programs[progNum] = program = new MqttProgram(); } (program as MqttProgram).onMessage(subTopic, payload); } } else { - console.warn(`MqttSprinklersDevice recieved invalid topic: ${topic}`) + console.warn(`MqttSprinklersDevice recieved invalid topic: ${topic}`); } } get id(): string { return this.prefix; } + + private getSubscriptions() { + return [ + `${this.prefix}/connected`, + `${this.prefix}/sections`, + `${this.prefix}/sections/+/#`, + `${this.prefix}/programs`, + `${this.prefix}/programs/+/#`, + ]; + } } -interface SectionJSON { +interface ISectionJSON { name: string; pin: number; } class MqttSection extends Section { - onMessage(topic: string, payload: string) { - if (topic == "state") { - this.state = (payload == "true"); + public onMessage(topic: string, payload: string) { + if (topic === "state") { + this.state = (payload === "true"); } else if (topic == null) { - const json = JSON.parse(payload) as SectionJSON; + const json = JSON.parse(payload) as ISectionJSON; this.name = json.name; } } } -interface ProgramJSON { +interface IScheduleJSON { + times: TimeOfDay[]; + weekdays: number[]; + from?: string; + to?: string; +} + +function scheduleFromJSON(json: IScheduleJSON): Schedule { + const sched = new Schedule(); + sched.times = json.times; + sched.weekdays = json.weekdays; + sched.from = json.from == null ? null : new Date(json.from); + sched.to = json.to == null ? null : new Date(json.to); + return sched; +} + +interface IProgramJSON { name: string; enabled: boolean; - // sequence: Array; - // sched: Schedule; + sequence: ProgramItem[]; + sched: IScheduleJSON; } class MqttProgram extends Program { - onMessage(topic: string, payload: string) { - if (topic == "running") { - this.running = (payload == "true"); + public onMessage(topic: string, payload: string) { + if (topic === "running") { + this.running = (payload === "true"); } else if (topic == null) { - const json = JSON.parse(payload) as Partial; - this.name = json.name; - this.enabled = json.enabled; + const json = JSON.parse(payload) as Partial; + if (json.name != null) { + this.name = json.name; + } + if (json.enabled != null) { + this.enabled = json.enabled; + } + if (json.sequence != null) { + this.sequence = json.sequence; + } + if (json.sched != null) { + this.schedule = scheduleFromJSON(json.sched); + } } } -} \ No newline at end of file +} diff --git a/app/script/paho-mqtt.d.ts b/app/script/paho-mqtt.d.ts index 6581e1c..0fcf695 100644 --- a/app/script/paho-mqtt.d.ts +++ b/app/script/paho-mqtt.d.ts @@ -1,9 +1,11 @@ +/* tslint:disable:interface-name */ + declare namespace Paho { namespace MQTT { - interface MQTTError { errorCode: string, errorMessage: string } - interface WithInvocationContext { invocationContext: object } + interface MQTTError { errorCode: string; errorMessage: string; } + interface WithInvocationContext { invocationContext: object; } interface ErrorWithInvocationContext extends MQTTError, WithInvocationContext {} - interface OnSubscribeSuccessParams extends WithInvocationContext { grantedQos: number } + interface OnSubscribeSuccessParams extends WithInvocationContext { grantedQos: number; } type OnConnectionLostHandler = (error: MQTTError) => void; type OnMessageHandler = (message: Message) => void; interface ConnectionOptions { @@ -18,8 +20,8 @@ declare namespace Paho { onSuccess?: (o: WithInvocationContext) => void; mqttVersion?: number; onFailure?: (e: ErrorWithInvocationContext) => void; - hosts?: Array; - ports?: Array; + hosts?: string[]; + ports?: number[]; } interface SubscribeOptions { qos?: number; @@ -35,41 +37,41 @@ declare namespace Paho { timeout?: number; } class Client { + public readonly clientId: string; + public readonly host: string; + public readonly path: string; + public readonly port: number; + + public onConnectionLost: OnConnectionLostHandler; + public onMessageArrived: OnMessageHandler; + public onMessageDelivered: OnMessageHandler; + // tslint:disable unified-signatures constructor(host: string, port: number, path: string, clientId: string); constructor(host: string, port: number, clientId: string); constructor(hostUri: string, clientId: string); - readonly clientId: string; - readonly host: string; - readonly path: string; - readonly port: number; - - onConnectionLost: OnConnectionLostHandler; - onMessageArrived: OnMessageHandler; - onMessageDelivered: OnMessageHandler; - - connect(connectionOptions?: ConnectionOptions); - disconnect(); + public connect(connectionOptions?: ConnectionOptions); + public disconnect(); - getTraceLog(): Object[]; - startTrace(); - stopTrace(); + public getTraceLog(): object[]; + public startTrace(); + public stopTrace(); - send(message: Message); - subscribe(filter: string, subcribeOptions?: SubscribeOptions); - unsubscribe(filter: string, unsubcribeOptions?: UnsubscribeOptions); + public send(message: Message); + public subscribe(filter: string, subcribeOptions?: SubscribeOptions); + public unsubscribe(filter: string, unsubcribeOptions?: UnsubscribeOptions); } class Message { - constructor(payload: String | ArrayBuffer); + public destinationName: string; + public readonly duplicate: boolean; + public readonly payloadBytes: ArrayBuffer; + public readonly payloadString: string; + public qos: number; + public retained: boolean; - destinationName: string; - readonly duplicate: boolean; - readonly payloadBytes: ArrayBuffer; - readonly payloadString: string; - qos: number; - retained: boolean; + constructor(payload: string | ArrayBuffer); } } } diff --git a/app/script/sprinklers.ts b/app/script/sprinklers.ts index c5c7692..c63f43f 100644 --- a/app/script/sprinklers.ts +++ b/app/script/sprinklers.ts @@ -2,69 +2,69 @@ import { observable } from "mobx"; export class Section { @observable - name: string = "" + public name: string = ""; @observable - state: boolean = false + public state: boolean = false; } -class TimeOfDay { - hour: number - minute: number - second: number - millisecond: number - +export interface ITimeOfDay { + hour: number; + minute: number; + second: number; + millisecond: number; } -enum Weekday { - Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday +export enum Weekday { + Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } -class Schedule { - times: TimeOfDay[] = []; - weekdays: Weekday[] = []; - from?: Date = null; - to?: Date = null; +export class Schedule { + public times: ITimeOfDay[] = []; + public weekdays: Weekday[] = []; + public from?: Date = null; + public to?: Date = null; } -class ProgramItem { - section: number = -1; - // duration in milliseconds - duration: number = 0; +export interface IProgramItem { + // the section number + section: number; + // duration in seconds + duration: number; } export class Program { @observable - name: string = "" + public name: string = ""; @observable - enabled: boolean = false + public enabled: boolean = false; @observable - schedule: Schedule = new Schedule() + public schedule: Schedule = new Schedule(); @observable - sequence: Array = []; + public sequence: IProgramItem[] = []; @observable - running: boolean = false; + public running: boolean = false; } export abstract class SprinklersDevice { @observable - connected: boolean = false; + public connected: boolean = false; @observable - sections: Array
= []; + public sections: Section[] = []; @observable - programs: Array = []; + public programs: Program[] = []; abstract get id(): string; } -export interface SprinklersApi { +export interface ISprinklersApi { start(); - getDevice(id: string) : SprinklersDevice; + getDevice(id: string): SprinklersDevice; - removeDevice(id: string) -} \ No newline at end of file + removeDevice(id: string); +} diff --git a/app/style/app.css b/app/style/app.css index f6c6571..3ce7729 100644 --- a/app/style/app.css +++ b/app/style/app.css @@ -1,19 +1,36 @@ -.device--connectedState-connected { +.device--connectionState { + margin-left: 10px; + font-size: 18px; + font-weight: 100; +} + +.device--connectionState-connected { color: #13D213; } -.device--connectedState-disconnected { +.device--connectionState-disconnected { color: #D20000; } -.section--name { - width: 200px; +.section--number, +.program--number { + width: 20px +} + +.section--name, +.program--name { + width: 150px; + white-space: nowrap; } .section--state { } -.program--name { - width: 200px; +.section--state-true { + color: green; +} + +.section--state-false { + } \ No newline at end of file diff --git a/package.json b/package.json index 555da75..f8d2f31 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "clean": "rm -rf ./dist ./build", "build": "webpack --config ./webpack/prod.config.js", - "start:dev": "webpack-dev-server --config ./webpack/dev.config.js" + "start:dev": "webpack-dev-server --config ./webpack/dev.config.js", + "lint": "tslint app/script/**/* || :" }, "repository": { "type": "git", @@ -23,6 +24,7 @@ "dependencies": { "@types/classnames": "0.0.32", "@types/node": "^7.0.13", + "@types/object-assign": "^4.0.30", "@types/react": "^15.0.23", "@types/react-dom": "^15.5.0", "@types/react-fontawesome": "^1.5.0", @@ -30,12 +32,13 @@ "font-awesome": "^4.7.0", "mobx": "^3.1.9", "mobx-react": "^4.1.8", + "object-assign": "^4.1.1", "paho-mqtt": "^1.0.3", "react": "^15.5.4", "react-dom": "^15.5.4", "react-fontawesome": "^1.6.1", "semantic-ui-css": "^2.2.10", - "semantic-ui-react": "^0.67.0" + "semantic-ui-react": "^0.67.2" }, "devDependencies": { "@types/webpack-env": "^1.13.0", diff --git a/tsconfig.json b/tsconfig.json index f1b25c0..9013f45 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ }, "files": [ "./node_modules/@types/webpack-env/index.d.ts", + "./app/script/paho-mqtt.d.ts", "./app/script/index.tsx" ] } \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..e1a4e5d --- /dev/null +++ b/tslint.json @@ -0,0 +1,25 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-console": [ + false + ], + "max-classes-per-file": [ + false + ], + "ordered-imports": [ + false + ], + "variable-name": [ + "allow-leading-underscore" + ], + "no-namespace": [ + "allow-declarations" + ] + }, + "rulesDirectory": [] +} \ No newline at end of file