Browse Source

Added tslint and fixed all lint issues

update-deps
Alex Mikhalev 8 years ago
parent
commit
ba4ee33792
  1. 68
      app/script/App.tsx
  2. 2
      app/script/index.tsx
  3. 155
      app/script/mqtt.ts
  4. 60
      app/script/paho-mqtt.d.ts
  5. 60
      app/script/sprinklers.ts
  6. 29
      app/style/app.css
  7. 7
      package.json
  8. 1
      tsconfig.json
  9. 25
      tslint.json

68
app/script/App.tsx

@ -6,30 +6,41 @@ import FontAwesome = require("react-fontawesome"); @@ -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 (
<Table.Row key={index}>
<Table.Cell className="section--name">Section {name}</Table.Cell>
<Table.Cell className="section--state">State: {state + ""}</Table.Cell>
<Table.Cell className="section--number">{"" + (index + 1)}</Table.Cell>
<Table.Cell className="section--name">{name}</Table.Cell>
<Table.Cell className={classNames({
"section--state": true,
"section--state-true": state,
"section--state-false": !state,
})}>{state ?
(<span><FontAwesome name="tint" /> Irrigating</span>)
: "Not irrigating"}
</Table.Cell>
</Table.Row>
);
}
render() {
public render() {
return (<Table celled striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell colSpan="3">Sections</Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>State</Table.HeaderCell>
<Table.HeaderCell className="section--number">#</Table.HeaderCell>
<Table.HeaderCell className="section--name">Name</Table.HeaderCell>
<Table.HeaderCell className="section--state">State</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
@ -44,26 +55,28 @@ class SectionTable extends React.PureComponent<{ sections: Section[] }, void> { @@ -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 (
<Table.Row key={i}>
<Table.Cell className="program--name">Program {name}</Table.Cell>
<Table.Cell className="program--running">running: {running + ""}</Table.Cell>
<Table.Cell className="program--number">{"" + (i + 1)}</Table.Cell>
<Table.Cell className="program--name">{name}</Table.Cell>
<Table.Cell className="program--running">{running ? "Running" : "Not running"}</Table.Cell>
</Table.Row>
);
}
render() {
public render() {
return (
<Table celled striped>
<Table celled>
<Table.Header>
<Table.Row>
<Table.HeaderCell colSpan="3">Programs</Table.HeaderCell>
</Table.Row>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Running</Table.HeaderCell>
<Table.HeaderCell className="program--number">#</Table.HeaderCell>
<Table.HeaderCell className="program--name">Name</Table.HeaderCell>
<Table.HeaderCell className="program--running">Running</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
@ -76,9 +89,20 @@ class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> { @@ -76,9 +89,20 @@ class ProgramTable extends React.PureComponent<{ programs: Program[] }, void> {
}
}
const ConnectionState = ({ connected }: { connected: boolean }) =>
<span className={classNames({
"device--connectionState": true,
"device--connectionState-connected": connected,
"device--connectionState-disconnected": !connected,
})}>
<FontAwesome name={connected ? "plug" : "chain-broken"} />
&nbsp;
{connected ? "Connected" : "Disconnected"}
</span>;
@observer
class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> {
render() {
public render() {
const { id, connected, sections, programs } = this.props.device;
return (
<Item>
@ -86,15 +110,7 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> @@ -86,15 +110,7 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void>
<Item.Content>
<Header as="h1">
<span>Device </span><kbd>{id}</kbd>
<small className={classNames({
"device--connectedState": true,
"device--connectedState-connected": connected,
"device--connectedState-disconnected": !connected
})}>
<FontAwesome name={connected ? "plug" : "chain-broken"} />
&nbsp;
{connected ? "Connected" : "Disconnected"}
</small>
<ConnectionState connected={connected} />
</Header>
<Item.Meta>
@ -109,7 +125,7 @@ class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, void> @@ -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 <Item.Group divided><DeviceView device={this.props.device} /></Item.Group>
public render() {
return <Item.Group divided><DeviceView device={this.props.device} /></Item.Group>;
}
}

2
app/script/index.tsx

@ -21,5 +21,5 @@ if (module.hot) { @@ -21,5 +21,5 @@ if (module.hot) {
ReactDOM.render(<AppContainer>
<NextApp device={device} />
</AppContainer>, rootElem);
})
});
}

155
app/script/mqtt.ts

@ -1,47 +1,48 @@ @@ -1,47 +1,48 @@
/// <reference path="./paho-mqtt.d.ts" />
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);
this.client.onMessageArrived = (m) => this.onMessageArrived(m);
this.client.onConnectionLost = (e) => this.onConnectionLost(e);
}
static newClientId() {
return "sprinklers3-MqttApiClient-" + Math.round(Math.random() * 1000);
}
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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<ProgramItem>;
// 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<ProgramJSON>;
const json = JSON.parse(payload) as Partial<IProgramJSON>;
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);
}
}
}
}

60
app/script/paho-mqtt.d.ts vendored

@ -1,9 +1,11 @@ @@ -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 { @@ -18,8 +20,8 @@ declare namespace Paho {
onSuccess?: (o: WithInvocationContext) => void;
mqttVersion?: number;
onFailure?: (e: ErrorWithInvocationContext) => void;
hosts?: Array<string>;
ports?: Array<number>;
hosts?: string[];
ports?: number[];
}
interface SubscribeOptions {
qos?: number;
@ -35,41 +37,41 @@ declare namespace Paho { @@ -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);
}
}
}

60
app/script/sprinklers.ts

@ -2,69 +2,69 @@ import { observable } from "mobx"; @@ -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<ProgramItem> = [];
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<Section> = [];
public sections: Section[] = [];
@observable
programs: Array<Program> = [];
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)
removeDevice(id: string);
}

29
app/style/app.css

@ -1,19 +1,36 @@ @@ -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 {
}

7
package.json

@ -8,7 +8,8 @@ @@ -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 @@ @@ -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 @@ @@ -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",

1
tsconfig.json

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
},
"files": [
"./node_modules/@types/webpack-env/index.d.ts",
"./app/script/paho-mqtt.d.ts",
"./app/script/index.tsx"
]
}

25
tslint.json

@ -0,0 +1,25 @@ @@ -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": []
}
Loading…
Cancel
Save