Made things look better
This commit is contained in:
parent
5ff2825fb1
commit
19d973ee71
24
app/components/DeviceView.scss
Normal file
24
app/components/DeviceView.scss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
.device {
|
||||||
|
.header {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
@media only screen and (min-width : 768px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.connectionState {
|
||||||
|
@media only screen and (min-width : 768px) {
|
||||||
|
margin-left: .75em;
|
||||||
|
}
|
||||||
|
font-size: .75em;
|
||||||
|
font-weight: lighter;
|
||||||
|
|
||||||
|
&.connected {
|
||||||
|
color: #13D213;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disconnected {
|
||||||
|
color: #D20000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,13 @@ import { Grid, Header, Icon, Item } from "semantic-ui-react";
|
|||||||
import { injectState, StateBase } from "@app/state";
|
import { injectState, StateBase } from "@app/state";
|
||||||
import { SprinklersDevice } from "@common/sprinklers";
|
import { SprinklersDevice } from "@common/sprinklers";
|
||||||
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
|
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
|
||||||
|
import "./DeviceView.scss";
|
||||||
|
|
||||||
function ConnectionState({ connected, className }: { connected: boolean, className?: string }) {
|
function ConnectionState({ connected, className }: { connected: boolean, className?: string }) {
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
"device--connectionState": true,
|
connectionState: true,
|
||||||
"device--connectionState-connected": connected,
|
connected: connected, /* tslint:disable-line:object-literal-shorthand */
|
||||||
"device--connectionState-disconnected": !connected,
|
disconnected: !connected,
|
||||||
}, className);
|
}, className);
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
@ -42,13 +43,13 @@ class DeviceView extends React.Component<DeviceViewProps> {
|
|||||||
return (
|
return (
|
||||||
<Item>
|
<Item>
|
||||||
<Item.Image src={require("@app/images/raspberry_pi.png")} />
|
<Item.Image src={require("@app/images/raspberry_pi.png")} />
|
||||||
<Item.Content>
|
<Item.Content className="device">
|
||||||
<Header as="h1">
|
<Header as="h1">
|
||||||
<div>Device <kbd>{id}</kbd></div>
|
<div>Device <kbd>{id}</kbd></div>
|
||||||
<ConnectionState connected={connected} />
|
<ConnectionState connected={connected} />
|
||||||
</Header>
|
</Header>
|
||||||
<Item.Meta>
|
<Item.Meta>
|
||||||
Raspberry Pi Grinklers Instance
|
Raspberry Pi Grinklers Device
|
||||||
</Item.Meta>
|
</Item.Meta>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.Column mobile={16} largeScreen={8}>
|
<Grid.Column mobile={16} largeScreen={8}>
|
||||||
@ -58,7 +59,7 @@ class DeviceView extends React.Component<DeviceViewProps> {
|
|||||||
<RunSectionForm sections={sections} />
|
<RunSectionForm sections={sections} />
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ProgramTable programs={programs} />
|
<ProgramTable programs={programs} sections={sections} />
|
||||||
<SectionRunnerView sectionRunner={sectionRunner} sections={sections} />
|
<SectionRunnerView sectionRunner={sectionRunner} sections={sections} />
|
||||||
</Item.Content>
|
</Item.Content>
|
||||||
</Item>
|
</Item>
|
||||||
|
@ -16,7 +16,7 @@ export default class DurationInput extends React.Component<{
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<label>Duration</label>
|
<label>Duration</label>
|
||||||
<div className="fields">
|
<div className="ui two fields">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
className="field durationInput--minutes"
|
className="field durationInput--minutes"
|
||||||
|
@ -1,60 +1,49 @@
|
|||||||
|
import { flatMap } from "lodash";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import * as moment from "moment";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Button, Table } from "semantic-ui-react";
|
import { Button, Table } from "semantic-ui-react";
|
||||||
|
|
||||||
import { Duration } from "@common/Duration";
|
import { Duration } from "@common/Duration";
|
||||||
import { Program, Schedule } from "@common/sprinklers";
|
import { Program, Schedule, TimeOfDay, Weekday, DateOfYear, Section } from "@common/sprinklers";
|
||||||
|
|
||||||
|
function timeToString(time: TimeOfDay) {
|
||||||
|
return moment(time).format("LTS");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateOfYear(day: DateOfYear | null, prefix: string) {
|
||||||
|
if (day == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return prefix + moment(day).format("l");
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ScheduleView extends React.Component<{ schedule: Schedule }> {
|
export class ScheduleView extends React.Component<{ schedule: Schedule }> {
|
||||||
render() {
|
render() {
|
||||||
|
const { schedule } = this.props;
|
||||||
|
const times = schedule.times.map((time, i) => timeToString(time))
|
||||||
|
.join(", ");
|
||||||
|
const weekdays = schedule.weekdays.map((weekday) =>
|
||||||
|
Weekday[weekday]).join(", ");
|
||||||
|
const from = formatDateOfYear(schedule.from, "From ");
|
||||||
|
const to = formatDateOfYear(schedule.to, "To ");
|
||||||
return (
|
return (
|
||||||
<div>{JSON.stringify(this.props.schedule)}</div>
|
<div>
|
||||||
|
At {times} <br />
|
||||||
|
On {weekdays} <br />
|
||||||
|
{from} <br />
|
||||||
|
{to}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class ProgramTable extends React.Component<{ programs: Program[] }> {
|
export default class ProgramTable extends React.Component<{ programs: Program[], sections: Section[] }> {
|
||||||
private static renderRows(program: Program, i: number): JSX.Element[] | null {
|
|
||||||
if (!program) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { name, running, enabled, schedule, sequence } = program;
|
|
||||||
const sequenceItems = sequence.map((item, index) => {
|
|
||||||
const duration = Duration.fromSeconds(item.duration);
|
|
||||||
return (
|
|
||||||
<li key={index}>Section {item.section + 1 + ""} for {duration.toString()}</li>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const cancelOrRun = () => running ? program.cancel() : program.run();
|
|
||||||
return [(
|
|
||||||
<Table.Row key={i}>
|
|
||||||
<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"}
|
|
||||||
<Button onClick={cancelOrRun}>
|
|
||||||
{running ? "Cancel" : "Run"}
|
|
||||||
</Button>
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell className="program--enabled">{enabled ? "Enabled" : "Not enabled"}</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
), (
|
|
||||||
<Table.Row key={i + .5}>
|
|
||||||
<Table.Cell className="program--sequence" colSpan="4">
|
|
||||||
<ul>
|
|
||||||
{sequenceItems}
|
|
||||||
</ul>
|
|
||||||
<ScheduleView schedule={schedule} />
|
|
||||||
</Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
)];
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const programRows = Array.prototype.concat.apply([],
|
const programRows = Array.prototype.concat.apply([],
|
||||||
this.props.programs.map(ProgramTable.renderRows));
|
this.props.programs.map(this.renderRows));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table celled>
|
<Table celled>
|
||||||
@ -75,4 +64,39 @@ export default class ProgramTable extends React.Component<{ programs: Program[]
|
|||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderRows = (program: Program, i: number): JSX.Element[] | null => {
|
||||||
|
if (!program) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { name, running, enabled, schedule, sequence } = program;
|
||||||
|
const sequenceItems = flatMap(sequence, (item, index) => {
|
||||||
|
const section = this.props.sections[item.section];
|
||||||
|
const duration = Duration.fromSeconds(item.duration);
|
||||||
|
return [
|
||||||
|
<em key={index}>"{section.name}"</em>, ` for ${duration.toString()}, `,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
const cancelOrRun = () => running ? program.cancel() : program.run();
|
||||||
|
return [(
|
||||||
|
<Table.Row key={i}>
|
||||||
|
<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"}
|
||||||
|
<Button className="program--runningButton" onClick={cancelOrRun}>
|
||||||
|
{running ? "Cancel" : "Run"}
|
||||||
|
</Button>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell className="program--enabled">{enabled ? "Enabled" : "Not enabled"}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
), (
|
||||||
|
<Table.Row key={i + .5}>
|
||||||
|
<Table.Cell className="program--sequence" colSpan="4">
|
||||||
|
<h4>Sequence: </h4> {sequenceItems}
|
||||||
|
<h4>Schedule: </h4> <ScheduleView schedule={schedule} />
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ export default class RunSectionForm extends React.Component<{
|
|||||||
<Segment>
|
<Segment>
|
||||||
<Header>Run Section</Header>
|
<Header>Run Section</Header>
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Group className="doubling stackable three column ui grid">
|
|
||||||
<Form.Select
|
<Form.Select
|
||||||
label="Section"
|
label="Section"
|
||||||
placeholder="Section"
|
placeholder="Section"
|
||||||
@ -50,7 +49,6 @@ export default class RunSectionForm extends React.Component<{
|
|||||||
>
|
>
|
||||||
Run
|
Run
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
</Form.Group>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Segment>
|
</Segment>
|
||||||
);
|
);
|
||||||
|
@ -2,20 +2,6 @@
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device--connectionState {
|
|
||||||
margin-left: .75em;
|
|
||||||
font-size: .75em;
|
|
||||||
font-weight: lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device--connectionState-connected {
|
|
||||||
color: #13D213;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device--connectionState-disconnected {
|
|
||||||
color: #D20000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionRunner--pausedState {
|
.sectionRunner--pausedState {
|
||||||
padding-left: .75em;
|
padding-left: .75em;
|
||||||
font-size: .75em;
|
font-size: .75em;
|
||||||
@ -45,6 +31,10 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.program--runningButton {
|
||||||
|
margin-left: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
.section--state-true {
|
.section--state-true {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
@ -53,9 +43,13 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.durationInput--minutes > input,
|
.durationInput--minutes,
|
||||||
.durationInput--seconds > input {
|
.durationInput--seconds {
|
||||||
width: 6em !important;
|
min-width: 6em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.durationInput .ui.labeled.input > .label {
|
||||||
|
width: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
const autoprefixer = require("autoprefixer");
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
|
||||||
@ -8,6 +7,7 @@ const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
|
|||||||
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
|
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
|
||||||
// const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
// const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||||
const MinifyPlugin = require("babel-minify-webpack-plugin");
|
const MinifyPlugin = require("babel-minify-webpack-plugin");
|
||||||
|
const cssnext = require("postcss-cssnext");
|
||||||
|
|
||||||
const { getClientEnvironment } = require("../env");
|
const { getClientEnvironment } = require("../env");
|
||||||
const paths = require("../paths");
|
const paths = require("../paths");
|
||||||
@ -38,7 +38,7 @@ const postCssConfig = {
|
|||||||
ident: "postcss",
|
ident: "postcss",
|
||||||
plugins: () => [
|
plugins: () => [
|
||||||
require("postcss-flexbugs-fixes"),
|
require("postcss-flexbugs-fixes"),
|
||||||
autoprefixer({
|
cssnext({
|
||||||
browsers: [
|
browsers: [
|
||||||
">1%",
|
">1%",
|
||||||
"last 4 versions",
|
"last 4 versions",
|
||||||
@ -51,6 +51,11 @@ const postCssConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sassConfig = {
|
||||||
|
loader: require.resolve("sass-loader"),
|
||||||
|
options: {},
|
||||||
|
};
|
||||||
|
|
||||||
const rules = (env) => {
|
const rules = (env) => {
|
||||||
// "postcss" loader applies autoprefixer to our CSS.
|
// "postcss" loader applies autoprefixer to our CSS.
|
||||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||||
@ -95,6 +100,44 @@ const rules = (env) => {
|
|||||||
publicPath,
|
publicPath,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
const sassRule = (env === "dev") ?
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
require.resolve("style-loader"),
|
||||||
|
{
|
||||||
|
loader: require.resolve("css-loader"),
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sassConfig,
|
||||||
|
],
|
||||||
|
} :
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
loader: ExtractTextPlugin.extract({
|
||||||
|
fallback: require.resolve('style-loader'),
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: require.resolve('css-loader'),
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
minimize: true,
|
||||||
|
sourceMap: shouldUseSourceMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sassConfig
|
||||||
|
],
|
||||||
|
// ExtractTextPlugin expects the build output to be flat.
|
||||||
|
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
|
||||||
|
// However, our output is structured with css, js and media folders.
|
||||||
|
// To have this structure working with relative paths, we have to use custom options.
|
||||||
|
publicPath: shouldUseRelativeAssetPaths ?
|
||||||
|
Array(cssFilename.split("/").length).join("../") :
|
||||||
|
publicPath,
|
||||||
|
})
|
||||||
|
};
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
@ -119,6 +162,7 @@ const rules = (env) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cssRule,
|
cssRule,
|
||||||
|
sassRule,
|
||||||
// Process TypeScript with TSC.
|
// Process TypeScript with TSC.
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/, use: {
|
test: /\.tsx?$/, use: {
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
"lint": "run-p lint:*"
|
"lint": "run-p lint:*"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist", "public"
|
"dist",
|
||||||
|
"public"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -38,8 +39,10 @@
|
|||||||
"express-pino-logger": "^2.0.0",
|
"express-pino-logger": "^2.0.0",
|
||||||
"mobx": "^3.1.11",
|
"mobx": "^3.1.11",
|
||||||
"module-alias": "^2.0.1",
|
"module-alias": "^2.0.1",
|
||||||
|
"moment": "^2.19.1",
|
||||||
"mqtt": "^2.13.0",
|
"mqtt": "^2.13.0",
|
||||||
"pino": "^4.7.2",
|
"pino": "^4.7.2",
|
||||||
|
"postcss-cssnext": "^3.0.2",
|
||||||
"serializr": "^1.1.13"
|
"serializr": "^1.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -74,6 +77,7 @@
|
|||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mobx-react": "^4.2.1",
|
"mobx-react": "^4.2.1",
|
||||||
"mobx-react-devtools": "^4.2.13",
|
"mobx-react-devtools": "^4.2.13",
|
||||||
|
"node-sass": "^4.5.3",
|
||||||
"nodemon": "^1.12.1",
|
"nodemon": "^1.12.1",
|
||||||
"npm-run-all": "^4.1.1",
|
"npm-run-all": "^4.1.1",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -85,6 +89,7 @@
|
|||||||
"react-dev-utils": "^4.1.0",
|
"react-dev-utils": "^4.1.0",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^16.0.0",
|
||||||
"react-hot-loader": "^3.0.0-beta.6",
|
"react-hot-loader": "^3.0.0-beta.6",
|
||||||
|
"sass-loader": "^6.0.6",
|
||||||
"semantic-ui-css": "^2.2.10",
|
"semantic-ui-css": "^2.2.10",
|
||||||
"semantic-ui-react": "^0.74.2",
|
"semantic-ui-react": "^0.74.2",
|
||||||
"source-map-loader": "^0.2.1",
|
"source-map-loader": "^0.2.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user