More improvements
This commit is contained in:
parent
d895e2e3e9
commit
8c94246f33
@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
||||||
|
<span className={classes}>
|
||||||
{connected ? "Connected" : "Disconnected"}
|
<FontAwesome name={connected ? "plug" : "chain-broken"} />
|
||||||
</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} />
|
||||||
|
@ -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)}/>;
|
@observer
|
||||||
}
|
export default class MessagesView extends React.Component<{ uiStore: UiStore }> {
|
||||||
|
render() {
|
||||||
private dismiss(index: number) {
|
const messages = this.props.uiStore.messages.map((message, index) => (
|
||||||
this.props.uiStore.messages.splice(index, 1);
|
<MessageView key={message.id} uiStore={this.props.uiStore} message={message} index={index} />
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<div className="messages" >
|
||||||
|
<TransitionGroup animation="scale" duration={200}>
|
||||||
|
{messages}
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
{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
|
|
||||||
{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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -46,31 +46,4 @@
|
|||||||
left: 12px;
|
left: 12px;
|
||||||
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;
|
|
||||||
}
|
}
|
@ -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"
|
||||||
|
@ -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
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…
x
Reference in New Issue
Block a user