Browse Source

More improvements

update-deps
Alex Mikhalev 7 years ago
parent
commit
8c94246f33
  1. 29
      app/script/components/App.tsx
  2. 21
      app/script/components/DeviceView.tsx
  3. 59
      app/script/components/MessagesView.tsx
  4. 36
      app/script/components/ProgramTable.tsx
  5. 35
      app/script/components/SectionTable.tsx
  6. 16
      app/script/index.tsx
  7. 3
      app/script/ui.ts
  8. 4
      app/script/utils.ts
  9. 27
      app/style/app.css
  10. 1
      package.json
  11. 7
      tslint.json
  12. 12
      yarn.lock

29
app/script/components/App.tsx

@ -1,22 +1,25 @@
import "app/style/app.css"; import { observer } from "mobx-react";
import "font-awesome/css/font-awesome.css";
import {observer} from "mobx-react";
import DevTools from "mobx-react-devtools"; import DevTools from "mobx-react-devtools";
import * as React from "react"; import * as React from "react";
import { Item } from "semantic-ui-react";
import { DeviceView, MessagesView } from ".";
import { SprinklersDevice } from "../sprinklers";
import { UiStore } from "../ui";
import "app/style/app.css";
import "font-awesome/css/font-awesome.css";
import "semantic-ui-css/semantic.css"; import "semantic-ui-css/semantic.css";
import {Item} from "semantic-ui-react";
import {DeviceView, MessagesView} from ".";
import {SprinklersDevice} from "../sprinklers";
import {UiStore} from "../ui";
@observer @observer
export default class App extends React.PureComponent<{ device: SprinklersDevice, uiStore: UiStore }, any> { export default class App extends React.Component<{ device: SprinklersDevice, uiStore: UiStore }> {
render() { render() {
return <Item.Group divided> return (
<MessagesView uiStore={this.props.uiStore}/> <Item.Group divided>
<DeviceView device={this.props.device}/> <MessagesView uiStore={this.props.uiStore} />
<DevTools/> <DeviceView device={this.props.device} />
</Item.Group>; <DevTools />
</Item.Group>
);
} }
} }

21
app/script/components/DeviceView.tsx

@ -7,19 +7,22 @@ import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".
import { SprinklersDevice } from "../sprinklers"; import { SprinklersDevice } from "../sprinklers";
import FontAwesome = require("react-fontawesome"); import FontAwesome = require("react-fontawesome");
const ConnectionState = ({ connected }: { connected: boolean }) => const ConnectionState = ({ connected }: { connected: boolean }) => {
<span className={classNames({ const classes = classNames({
"device--connectionState": true, "device--connectionState": true,
"device--connectionState-connected": connected, "device--connectionState-connected": connected,
"device--connectionState-disconnected": !connected, "device--connectionState-disconnected": !connected,
})}> });
<FontAwesome name={connected ? "plug" : "chain-broken"} /> return (
&nbsp; <span className={classes}>
{connected ? "Connected" : "Disconnected"} <FontAwesome name={connected ? "plug" : "chain-broken"} />&nbsp;
</span>; {connected ? "Connected" : "Disconnected"}
</span>
);
};
@observer @observer
export default class DeviceView extends React.PureComponent<{ device: SprinklersDevice }, {}> { export default class DeviceView extends React.Component<{ device: SprinklersDevice }> {
render() { render() {
const { id, connected, sections, programs, sectionRunner } = this.props.device; const { id, connected, sections, programs, sectionRunner } = this.props.device;
return ( return (
@ -31,7 +34,7 @@ export default class DeviceView extends React.PureComponent<{ device: Sprinklers
<ConnectionState connected={connected} /> <ConnectionState connected={connected} />
</Header> </Header>
<Item.Meta> <Item.Meta>
Raspberry Pi Grinklers Instance
</Item.Meta> </Item.Meta>
<SectionRunnerView sectionRunner={sectionRunner} /> <SectionRunnerView sectionRunner={sectionRunner} />
<SectionTable sections={sections} /> <SectionTable sections={sections} />

59
app/script/components/MessagesView.tsx

@ -1,30 +1,47 @@
import {observer} from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import {CSSTransitionGroup} from "react-transition-group"; import { Message, MessageList, TransitionGroup } from "semantic-ui-react";
import {Message} from "semantic-ui-react"; import { Message as UiMessage, UiStore } from "../ui";
import {Message as UiMessage, UiStore} from "../ui";
class MessageView extends React.Component<{
uiStore: UiStore,
message: UiMessage,
index: number,
}> {
@observer
export default class MessagesView extends React.PureComponent<{ uiStore: UiStore }, {}> {
render() { render() {
return <div className="messages"> const { id, header, content, type } = this.props.message;
<CSSTransitionGroup transitionName="message" transitionAppear={true} transitionAppearTimeout={500} return (
transitionEnterTimeout={500} transitionLeaveTimeout={500}> <Message
{this.props.uiStore.messages.map(this.renderMessage)} header={header}
</CSSTransitionGroup> content={content}
</div>; success={type === UiMessage.Type.Success}
info={type === UiMessage.Type.Info}
warning={type === UiMessage.Type.Warning}
error={type === UiMessage.Type.Error}
onDismiss={this.dismiss}
/>
);
} }
private renderMessage = (message: UiMessage, index: number) => { private dismiss = () => {
const {header, content, type} = message; const { uiStore, index } = this.props;
return <Message key={message.id} className="message" uiStore.messages.splice(index, 1);
header={header} content={content}
success={type === UiMessage.Type.Success}
info={type === UiMessage.Type.Info} warning={type === UiMessage.Type.Warning}
error={type === UiMessage.Type.Error} onDismiss={() => this.dismiss(index)}/>;
} }
}
private dismiss(index: number) { @observer
this.props.uiStore.messages.splice(index, 1); export default class MessagesView extends React.Component<{ uiStore: UiStore }> {
render() {
const messages = this.props.uiStore.messages.map((message, index) => (
<MessageView key={message.id} uiStore={this.props.uiStore} message={message} index={index} />
));
return (
<div className="messages" >
<TransitionGroup animation="scale" duration={200}>
{messages}
</TransitionGroup>
</div>
);
} }
} }

36
app/script/components/ProgramTable.tsx

@ -1,10 +1,10 @@
import {observer} from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import {Table} from "semantic-ui-react"; import { Table } from "semantic-ui-react";
import {Program, Schedule} from "../sprinklers"; import { Program, Schedule } from "../sprinklers";
@observer @observer
export class ScheduleView extends React.PureComponent<{ schedule: Schedule }, {}> { export class ScheduleView extends React.Component<{ schedule: Schedule }> {
render() { render() {
return ( return (
<div>{JSON.stringify(this.props.schedule)}</div> <div>{JSON.stringify(this.props.schedule)}</div>
@ -13,35 +13,39 @@ export class ScheduleView extends React.PureComponent<{ schedule: Schedule }, {}
} }
@observer @observer
export default class ProgramTable extends React.PureComponent<{ programs: Program[] }, {}> { export default class ProgramTable extends React.Component<{ programs: Program[] }> {
private static renderRows(program: Program, i: number): JSX.Element[] | null { private static renderRows(program: Program, i: number): JSX.Element[] | null {
if (!program) { if (!program) {
return null; return null;
} }
const {name, running, enabled, schedule, sequence} = program; const { name, running, enabled, schedule, sequence } = program;
return [ const sequenceItems = sequence.map((item, index) => (
<li key={index}>Section {item.section + 1 + ""} for&nbsp;
{item.duration.minutes}M {item.duration.seconds}S</li>
));
return [(
<Table.Row key={i}> <Table.Row key={i}>
<Table.Cell className="program--number">{"" + (i + 1)}</Table.Cell> <Table.Cell className="program--number">{"" + (i + 1)}</Table.Cell>
<Table.Cell className="program--name">{name}</Table.Cell> <Table.Cell className="program--name">{name}</Table.Cell>
<Table.Cell className="program--running">{running ? "Running" : "Not running"}</Table.Cell> <Table.Cell className="program--running">{running ? "Running" : "Not running"}</Table.Cell>
<Table.Cell className="program--enabled">{enabled ? "Enabled" : "Not enabled"}</Table.Cell> <Table.Cell className="program--enabled">{enabled ? "Enabled" : "Not enabled"}</Table.Cell>
</Table.Row> </Table.Row>
, ), (
<Table.Row key={i + .5}> <Table.Row key={i + .5}>
<Table.Cell className="program--sequence" colSpan="4"> <Table.Cell className="program--sequence" colSpan="4">
<ul> <ul>
{sequence.map((item) => {sequenceItems}
(<li>Section {item.section + 1 + ""} for&nbsp;
{item.duration.minutes}M {item.duration.seconds}S</li>))}
</ul> </ul>
<ScheduleView schedule={schedule}/> <ScheduleView schedule={schedule} />
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
, )];
];
} }
render() { render() {
const programRows = Array.prototype.concat.apply([],
this.props.programs.map(ProgramTable.renderRows));
return ( return (
<Table celled> <Table celled>
<Table.Header> <Table.Header>
@ -56,9 +60,7 @@ export default class ProgramTable extends React.PureComponent<{ programs: Progra
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{ {programRows}
Array.prototype.concat.apply([], this.props.programs.map(ProgramTable.renderRows))
}
</Table.Body> </Table.Body>
</Table> </Table>
); );

35
app/script/components/SectionTable.tsx

@ -1,38 +1,41 @@
import * as classNames from "classnames"; import * as classNames from "classnames";
import {observer} from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import {Table} from "semantic-ui-react"; import { Table } from "semantic-ui-react";
import {Section} from "../sprinklers"; import { Section } from "../sprinklers";
import FontAwesome = require("react-fontawesome"); import FontAwesome = require("react-fontawesome");
/* tslint:disable:object-literal-sort-keys */ /* tslint:disable:object-literal-sort-keys */
@observer @observer
export default class SectionTable extends React.PureComponent<{ sections: Section[] }, {}> { export default class SectionTable extends React.Component<{ sections: Section[] }> {
private static renderRow(section: Section, index: number) { private static renderRow(section: Section, index: number) {
if (!section) { if (!section) {
return null; return null;
} }
const {name, state} = section; const { name, state } = section;
const sectionStateClass = classNames({
"section--state": true,
"section--state-true": state,
"section--state-false": !state,
});
const sectionState = state ?
(<span><FontAwesome name="tint" /> Irrigating</span>)
: "Not irrigating";
return ( return (
<Table.Row key={index}> <Table.Row key={index}>
<Table.Cell className="section--number">{"" + (index + 1)}</Table.Cell> <Table.Cell className="section--number">{"" + (index + 1)}</Table.Cell>
<Table.Cell className="section--name">{name}</Table.Cell> <Table.Cell className="section--name">{name}</Table.Cell>
<Table.Cell className={classNames({ <Table.Cell className={sectionStateClass}>{sectionState}</Table.Cell>
"section--state": true,
"section--state-true": state,
"section--state-false": !state,
})}>{state ?
(<span><FontAwesome name="tint"/> Irrigating</span>)
: "Not irrigating"}
</Table.Cell>
</Table.Row> </Table.Row>
); );
} }
render() { render() {
return (<Table celled striped> const rows = this.props.sections.map(SectionTable.renderRow);
return (
<Table celled striped>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.HeaderCell colSpan="3">Sections</Table.HeaderCell> <Table.HeaderCell colSpan="3">Sections</Table.HeaderCell>
@ -44,9 +47,7 @@ export default class SectionTable extends React.PureComponent<{ sections: Sectio
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{ {rows}
this.props.sections.map(SectionTable.renderRow)
}
</Table.Body> </Table.Body>
</Table> </Table>
); );

16
app/script/index.tsx

@ -14,15 +14,19 @@ uiStore.addMessage(new Message("asdf", "boo!", Message.Type.Error));
const rootElem = document.getElementById("app"); const rootElem = document.getElementById("app");
ReactDOM.render(<AppContainer> const doRender = (Component: typeof App) => {
<App device={device} uiStore={uiStore} /> ReactDOM.render((
</AppContainer>, rootElem); <AppContainer>
<Component device={device} uiStore={uiStore} />
</AppContainer>
), rootElem);
};
doRender(App);
if (module.hot) { if (module.hot) {
module.hot.accept("./components/App", () => { module.hot.accept("./components/App", () => {
const NextApp = require<any>("./components/App").default as typeof App; const NextApp = require<any>("./components/App").default as typeof App;
ReactDOM.render(<AppContainer> doRender(NextApp);
<NextApp device={device} uiStore={uiStore} />
</AppContainer>, rootElem);
}); });
} }

3
app/script/ui.ts

@ -1,4 +1,5 @@
import {observable} from "mobx"; import {observable} from "mobx";
import { getRandomId } from "./utils";
export class Message { export class Message {
id: string; id: string;
@ -7,7 +8,7 @@ export class Message {
type: Message.Type = Message.Type.Default; type: Message.Type = Message.Type.Default;
constructor(header: string, content: string = "", type: Message.Type = Message.Type.Default) { constructor(header: string, content: string = "", type: Message.Type = Message.Type.Default) {
this.id = "" + Math.floor(Math.random() * 1000000000); this.id = "" + getRandomId();
this.header = header; this.header = header;
this.content = content; this.content = content;
this.type = type; this.type = type;

4
app/script/utils.ts

@ -7,3 +7,7 @@ export function checkedIndexOf<T>(o: T | number, arr: T[], type: string = "objec
} }
return idx; return idx;
} }
export function getRandomId() {
return Math.floor(Math.random() * 1000000000);
}

27
app/style/app.css

@ -47,30 +47,3 @@
right: 12px; right: 12px;
z-index: 1000; z-index: 1000;
} }
.message-enter,
.message-appear {
opacity: 0.01;
}
.message-enter.message-enter-active,
.message-appear.message-appear-active {
opacity: 1;
transition: all 500ms ease-in;
}
.message-leave {
/*opacity: 1;*/
transform-origin: top;
overflow-y: hidden;
}
.message-leave.message-leave-active {
/*opacity: 0.01;*/
transform: scaleY(0.01);
/*height: 0;*/
margin-top: 0;
padding-top: 0;
padding-bottom: 0;
transition: all 500ms ease-in;
}

1
package.json

@ -58,6 +58,7 @@
"style-loader": "^0.18.1", "style-loader": "^0.18.1",
"ts-loader": "^2.1.0", "ts-loader": "^2.1.0",
"tslint": "^5.4.2", "tslint": "^5.4.2",
"tslint-react": "^3.2.0",
"typescript": "^2.3.4", "typescript": "^2.3.4",
"webpack": "^3.0.0", "webpack": "^3.0.0",
"webpack-dev-server": "^2.4.4" "webpack-dev-server": "^2.4.4"

7
tslint.json

@ -1,7 +1,7 @@
{ {
"defaultSeverity": "error", "defaultSeverity": "warning",
"extends": [ "extends": [
"tslint:latest" "tslint:latest", "tslint-react"
], ],
"jsRules": {}, "jsRules": {},
"rules": { "rules": {
@ -33,7 +33,8 @@
"no-submodule-imports": false, "no-submodule-imports": false,
"no-unused-variable": [ "no-unused-variable": [
true true
] ],
"jsx-boolean-value": [ true, "never" ]
}, },
"rulesDirectory": [] "rulesDirectory": []
} }

12
yarn.lock

@ -4248,6 +4248,12 @@ tslib@^1.7.1:
version "1.7.1" version "1.7.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
tslint-react@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.2.0.tgz#851fb505201c63d0343c51726e6364f7e9ad2e99"
dependencies:
tsutils "^2.8.0"
tslint@^5.4.2: tslint@^5.4.2:
version "5.7.0" version "5.7.0"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.7.0.tgz#c25e0d0c92fa1201c2bc30e844e08e682b4f3552" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.7.0.tgz#c25e0d0c92fa1201c2bc30e844e08e682b4f3552"
@ -4263,6 +4269,12 @@ tslint@^5.4.2:
tslib "^1.7.1" tslib "^1.7.1"
tsutils "^2.8.1" tsutils "^2.8.1"
tsutils@^2.8.0:
version "2.8.2"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a"
dependencies:
tslib "^1.7.1"
tsutils@^2.8.1: tsutils@^2.8.1:
version "2.8.1" version "2.8.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.1.tgz#3771404e7ca9f0bedf5d919a47a4b1890a68efff" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.1.tgz#3771404e7ca9f0bedf5d919a47a4b1890a68efff"

Loading…
Cancel
Save