Merge branch 'dev' into 'master'

Dev

See merge request sa4-2020/the-sanmarinoes/frontend!160
This commit is contained in:
Claudio Maggioni 2020-05-27 23:17:43 +02:00
commit bda73469b2
90 changed files with 8549 additions and 4458 deletions

View File

@ -3,6 +3,7 @@ image: node:latest
stages: stages:
- build - build
- test - test
- code_quality
- deploy - deploy
cache: cache:
@ -24,6 +25,15 @@ testing_testing:
- cd smart-hut - cd smart-hut
- yarn test - yarn test
sonar-scanner:
stage: code_quality
only:
- dev
script:
- cd smart-hut
- yarn eslint:report || true
- yarn sonar-scanner -Dsonar.host.url=$SONAR_URL -Dsonar.login=$SONAR_LOGIN -Dsonar.projectName=$CI_PROJECT_PATH_SLUG -Dsonar.projectKey=$CI_PROJECT_PATH_SLUG -Dsonar.exclusion=cypress -Dsonar.sources=src -Dsonar.javascript.file.suffixes=.js,.jsx -Dsonar.sourceEncoding=UTF-8 -Dsonar.scm.disabled=True -Dsonar.eslint.reportPaths=eslint-report.json
smartHut_deploy: smartHut_deploy:
stage: deploy stage: deploy
tags: tags:

8
.mailmap Normal file
View File

@ -0,0 +1,8 @@
Claudio Maggioni <maggicl@usi.ch> Claudio Maggioni (maggicl) <maggicl@kolabnow.ch>
Claudio Maggioni <maggicl@usi.ch> Claudio Maggioni (maggicl) <maggicl@usi.ch>
Filippo Cesana <cesanf@usi.ch> FilippoCesana <cesanf@usi.ch>
Filippo Cesana <cesanf@usi.ch> Fil Cesana <cesanf@usi.ch>
Andrea Brites Marto <britea@usi.ch> britea <andreabritesma@gmail.com>
Christian Capeáns Pérez <capeac@usi.ch> christiancp <capeac@usi.ch>
Tommaso Rodolfo Masera <rodolt@usi.ch> tommi27 <tommi99@hotmail.it>

View File

@ -1,10 +1,11 @@
#!/bin/sh #!/bin/sh
FILES=$(git diff --cached --name-only --diff-filter=ACMR "*.js" "*.jsx" | sed 's| |\\ |g') FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g')
[ -z "$FILES" ] && exit 0
# Prettify all selected files # Prettify all selected files
echo "$FILES" | xargs ./smart-hut/node_modules/.bin/prettier --write cd $(git rev-parse --show-toplevel)/smart-hut
npm run eslint-fix
cd ..
# Add back the modified/prettified files to staging # Add back the modified/prettified files to staging
echo "$FILES" | xargs git add echo "$FILES" | xargs git add

54
smart-hut/.eslintrc Normal file
View File

@ -0,0 +1,54 @@
{
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"semi": "error",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/jsx-no-bind": [
"error",
{
"allowArrowFunctions": true,
"allowBind": false,
"ignoreRefs": true
}
],
"react/no-did-update-set-state": "error",
"react/no-unknown-property": "error",
"react/no-unused-prop-types": "error",
"react/prop-types": "error",
"react/react-in-jsx-scope": "error",
"no-unused-expressions": 0,
"chai-friendly/no-unused-expressions": 2,
"indent": [0, 4]
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"airbnb"
],
"env": {
"node": true,
"browser": true,
"jest": true
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"settings": {
"react": {
"version": "detect"
}
},
"plugins": ["react", "chai-friendly"]
}

File diff suppressed because it is too large Load Diff

View File

@ -13,13 +13,16 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"immutability-helper": "^3.0.2", "immutability-helper": "^3.0.2",
"material-ui-image": "^3.2.3", "material-ui-image": "^3.2.3",
"rc-slider": "^9.2.4",
"react": "^16.12.0", "react": "^16.12.0",
"react-axios": "^2.0.3", "react-axios": "^2.0.3",
"react-circular-input": "^0.1.6", "react-circular-input": "^0.1.6",
"react-circular-slider-svg": "^0.1.5", "react-circular-slider-svg": "^0.1.5",
"react-confirm-alert": "^2.6.1",
"react-device-detect": "^1.11.14", "react-device-detect": "^1.11.14",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-modal": "^2.2.2", "react-modal": "^2.2.2",
"react-rangeslider": "^2.2.0",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"react-round-slider": "^1.0.1", "react-round-slider": "^1.0.1",
"react-router": "^5.1.2", "react-router": "^5.1.2",
@ -34,6 +37,9 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eslint": "eslint src",
"eslint-fix": "eslint --fix src",
"eslint:report": "eslint src -f json -o eslint-report.json",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
@ -52,6 +58,9 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"prettier": "2.0.1" "prettier": "2.0.1",
"sonarqube-scanner": "^2.6.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-plugin-chai-friendly": "^0.6.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,28 +1,29 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom"; import {
import Home from "./views/Home"; BrowserRouter, Switch, Route, Redirect,
import Dashboard from "./views/Dashboard"; } from 'react-router-dom';
import Signup from "./views/Signup"; import queryString from 'query-string';
import Login from "./views/Login"; import { connect } from 'react-redux';
import FourOhFour from "./views/FourOhFour"; import Home from './views/Home';
import ForgotPass from "./views/Forgot-password"; import Dashboard from './views/Dashboard';
import ChangePass from "./views/Forgot-pass-reset"; import Signup from './views/Signup';
import ConfirmForgotPasswrod from "./views/ConfirmForgotPassword"; import Login from './views/Login';
import ConfirmRegistration from "./views/ConfirmRegistration"; import FourOhFour from './views/FourOhFour';
import ConfirmResetPassword from "./views/ConfirmResetPassword"; import ForgotPass from './views/Forgot-password';
import Instruction from "./views/Instruction"; import ConfirmForgotPasswrod from './views/ConfirmForgotPassword';
import Videocam from "./views/Videocam"; import ConfirmRegistration from './views/ConfirmRegistration';
import queryString from "query-string"; import ConfirmResetPassword from './views/ConfirmResetPassword';
import { RemoteService } from "./remote"; import Instruction from './views/Instruction';
import { connect } from "react-redux"; import Videocam from './views/Videocam';
import { RemoteService } from './remote';
class App extends Component { class App extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = { this.state = {
query: "", query: '',
info: "", info: '',
}; };
} }
@ -36,7 +37,7 @@ class App extends Component {
} }
render() { render() {
console.log("rendering root", this.props.loggedIn, this.state.query); console.log('rendering root', this.props.loggedIn, this.state.query);
return ( return (
<BrowserRouter> <BrowserRouter>
<Switch> <Switch>
@ -49,7 +50,7 @@ class App extends Component {
{this.props.loggedIn ? <Dashboard /> : <Redirect to="/login" />} {this.props.loggedIn ? <Dashboard /> : <Redirect to="/login" />}
</Route> </Route>
<Route path="/forgot-password"> <Route path="/forgot-password">
<ForgotPass /> <ForgotPass type="FPassword1" />
</Route> </Route>
<Route path="/sent-email"> <Route path="/sent-email">
<ConfirmForgotPasswrod /> <ConfirmForgotPasswrod />
@ -60,9 +61,11 @@ class App extends Component {
<Route path="/instruction"> <Route path="/instruction">
<Instruction /> <Instruction />
</Route> </Route>
<Route path="/forgot-pass-reset"> </Route> <Route path="/forgot-pass-reset">
<ForgotPass type="FPassword1" />
</Route>
<Route path="/password-reset"> <Route path="/password-reset">
<ChangePass query={this.state.query} /> <ForgotPass type="FPassword2" query={this.state.query} />
</Route> </Route>
<Route path="/conf-reset-pass"> <Route path="/conf-reset-pass">
<ConfirmResetPassword /> <ConfirmResetPassword />

View File

@ -1,19 +1,19 @@
import React from "react"; import React from 'react';
import { render } from "@testing-library/react"; import { render } from '@testing-library/react';
import { Router } from "react-router"; import { Router } from 'react-router';
import { createMemoryHistory } from "history"; import { createMemoryHistory } from 'history';
import App from "./App"; import { Provider } from 'react-redux';
import { Provider } from "react-redux"; import App from './App';
import smartHutStore from "./store"; import smartHutStore from './store';
test("redirects to homepage", () => { test('redirects to homepage', () => {
const history = createMemoryHistory(); const history = createMemoryHistory();
render( render(
<Router history={history}> <Router history={history}>
<Provider store={smartHutStore}> <Provider store={smartHutStore}>
<App /> <App />
</Provider> </Provider>
</Router> </Router>,
); );
expect(history.location.pathname).toBe("/"); expect(history.location.pathname).toBe('/');
}); });

View File

@ -1,189 +0,0 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { RemoteService } from "../remote";
import { appActions } from "../storeActions";
class AutomationModal extends Component {
constructor(props) {
super(props);
this.state = this.initialState;
this.setInitialState();
this.addAutomationModal = this.addAutomationModal.bind(this);
this.modifyAutomationModal = this.modifyAutomationModal.bind(this);
this.deleteAutomation = this.deleteAutomation.bind(this);
}
get initialState() {
return {
//INITIAL STATE HERE
};
}
setInitialState() {
this.setState(this.initialState);
}
get type() {
return !this.props.id ? "new" : "modify";
}
addAutomationModal = (e) => {
/*let data = {
// DATA HERE
};*/
// TODO CALL TO REMOTE SERVER TO ADD SCENE
/*this.props
.saveRoom(data, null)
.then(() => {
this.setInitialState();
this.closeModal();
})
.catch((err) => console.error("error in creating room", err));*/
};
modifyAutomationModal = (e) => {
/* let data = {
// DATA HERE
};*/
// TODO CALL TO REMOTE SERVER TO MODIFY SCENE
/*this.props
.saveRoom(data, this.props.id)
.then(() => {
this.setInitialState();
this.closeModal();
})
.catch((err) => console.error("error in updating room", err));*/
};
deleteAutomation = (e) => {
// TODO CALL TO REMOTE SERVER TO DELETE SCENE
/*
this.props
.deleteRoom(this.props.id)
.then(() => this.closeModal())
.catch((err) => console.error("error in deleting room", err));*/
};
changeSomething = (event) => {
let nam = event.target.name;
let val = event.target.value;
this.setState({ [nam]: val });
};
closeModal = (e) => {
this.setState({ openModal: false });
};
openModal = (e) => {
this.setState({ openModal: true });
};
render() {
return (
<div>
{/*
{!this.props.nicolaStop ? (
<div>
<Responsive minWidth={768}>
{this.type === "new" ? (
<Button
icon
labelPosition="left"
inverted
onClick={this.openModal}
>
<Icon name="plus" size="small" />
ADD AUTOMATION
</Button>
) : (
<Icon name="pencil" size="small" onClick={this.openModal} />
)}
</Responsive>
<Responsive maxWidth={768}>
{this.type === "new" ? (
<Button
icon
fluid
labelPosition="left"
onClick={this.openModal}
>
<Icon name="plus" size="small" />
ADD AUTOMATION
</Button>
) : (
<Button
icon
fluid
labelPosition="left"
onClick={this.openModal}
>
<Icon name="pencil" size="small" />
EDIT AUTOMATION
</Button>
)}
</Responsive>
</div>
) : null}
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Header>
{this.type === "new" ? "Add new automation" : "Modify automation"}
</Header>
<Modal.Content>
{
//TODO FORM TO ADD OR MODIFY SCENE
}
{this.type === "modify" ? (
<Button
icon
labelPosition="left"
inverted
color="red"
onClick={this.deleteAutomation}
>
<Icon name="trash alternate" />
Delete Automation
</Button>
) : null}
</Modal.Content>
<Modal.Actions>
<Button color="red" onClick={this.closeModal}>
<Icon name="remove" />{" "}
{this.type === "new" ? "Cancel" : "Discard changes"}
</Button>
<Button
color="green"
onClick={
this.type === "new"
? this.addAutomationModal
: this.modifyAutomationModal
}
>
<Icon name="checkmark" />{" "}
{this.type === "new" ? "Add automation" : "Save changes"}
</Button>
</Modal.Actions>
</Modal>*/}
</div>
);
}
}
const setActiveAutomation = (activeAutomation) => {
return (dispatch) =>
dispatch(appActions.setActiveAutomation(activeAutomation));
};
const mapStateToProps = (state, ownProps) => ({
automations: ownProps.id ? state.automations[ownProps.id] : null,
});
const AutomationModalContainer = connect(
mapStateToProps,
{ ...RemoteService, setActiveAutomation },
null,
{ forwardRef: true }
)(AutomationModal);
export default AutomationModalContainer;

View File

@ -1,34 +1,34 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import Paper from "@material-ui/core/Paper"; import Paper from '@material-ui/core/Paper';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
import Grid from "@material-ui/core/Grid"; import Grid from '@material-ui/core/Grid';
import Link from "@material-ui/core/Link"; import Link from '@material-ui/core/Link';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
mainFeaturedPost: { mainFeaturedPost: {
position: "relative", position: 'relative',
backgroundColor: theme.palette.grey[800], backgroundColor: theme.palette.grey[800],
color: theme.palette.common.white, color: theme.palette.common.white,
marginBottom: theme.spacing(4), marginBottom: theme.spacing(4),
backgroundImage: "img/banner.jpg", backgroundImage: 'img/banner.jpg',
backgroundSize: "cover", backgroundSize: 'cover',
backgroundRepeat: "no-repeat", backgroundRepeat: 'no-repeat',
backgroundPosition: "center", backgroundPosition: 'center',
}, },
overlay: { overlay: {
position: "absolute", position: 'absolute',
top: 0, top: 0,
bottom: 0, bottom: 0,
right: 0, right: 0,
left: 0, left: 0,
backgroundColor: "rgba(0,0,0,.3)", backgroundColor: 'rgba(0,0,0,.3)',
}, },
mainFeaturedPostContent: { mainFeaturedPostContent: {
position: "relative", position: 'relative',
padding: theme.spacing(3), padding: theme.spacing(3),
[theme.breakpoints.up("md")]: { [theme.breakpoints.up('md')]: {
padding: theme.spacing(6), padding: theme.spacing(6),
paddingRight: 0, paddingRight: 0,
}, },
@ -47,7 +47,7 @@ export default function Banner(props) {
{/* Increase the priority of the hero background image */} {/* Increase the priority of the hero background image */}
{ {
<img <img
style={{ display: "none" }} style={{ display: 'none' }}
src={post.image} src={post.image}
alt={post.imageText} alt={post.imageText}
/> />

View File

@ -0,0 +1,15 @@
import React from 'react';
import { Dropdown } from 'semantic-ui-react';
const options = [
{ key: 'Living Room', text: 'Living Room', value: 'Living Room' },
{ key: 'Kitchen', text: 'Kitchen', value: 'Kitchen' },
{ key: 'Garden', text: 'Garden', value: 'Garden' },
{ key: 'Bedroom 1', text: 'Bedroom 1', value: 'Bedroom 1' },
];
const DropdownSimulation = () => (
<Dropdown placeholder="Skills" fluid multiple selection options={options} />
);
export default DropdownSimulation;

View File

@ -1,82 +0,0 @@
import { Dropdown } from "semantic-ui-react";
import React, { Component } from "react";
export default class FilterDevices extends Component {
render() {
const tagOptions = [
{
key: "regularLight",
text: "regularLight",
value: "regularLight",
label: { color: "red", empty: true, circular: true },
},
{
key: "dimmableLight",
text: "dimmableLight",
value: "dimmableLight",
label: { color: "blue", empty: true, circular: true },
},
{
key: "buttonDimmer",
text: "buttonDimmer",
value: "buttonDimmer",
label: { color: "black", empty: true, circular: true },
},
{
key: "knobDimmer",
text: "knobDimmer",
value: "knobDimmer",
label: { color: "purple", empty: true, circular: true },
},
{
key: "motionSensor",
text: "motionSensor",
value: "motionSensor",
label: { color: "orange", empty: true, circular: true },
},
{
key: "sensor",
text: "sensor",
value: "sensor",
label: { empty: true, circular: true },
},
{
key: "smartPlug",
text: "smartPlug",
value: "smartPlug",
label: { color: "pink", empty: true, circular: true },
},
{
key: "switch",
text: "switch",
value: "switch",
label: { color: "green", empty: true, circular: true },
},
];
return (
<Dropdown
text="Filter Devices"
icon="filter"
floating
labeled
button
className="icon"
>
<Dropdown.Menu>
<Dropdown.Divider />
<Dropdown.Header icon="tags" content="Tag Device" />
<Dropdown.Menu scrolling>
{tagOptions.map((option) => (
<Dropdown.Item
key={option.value}
{...option}
onClick={this.props.filterDevices}
/>
))}
</Dropdown.Menu>
</Dropdown.Menu>
</Dropdown>
);
}
}

View File

@ -1,4 +1,4 @@
import React from "react"; import React from 'react';
export default function Footer() { export default function Footer() {
return ( return (

View File

@ -1,12 +1,12 @@
import React from "react"; import React from 'react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles';
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from '@material-ui/core/Toolbar';
import Button from "@material-ui/core/Button"; import Button from '@material-ui/core/Button';
import IconButton from "@material-ui/core/IconButton"; import IconButton from '@material-ui/core/IconButton';
import SearchIcon from "@material-ui/icons/Search"; import SearchIcon from '@material-ui/icons/Search';
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography';
import Link from "@material-ui/core/Link"; import Link from '@material-ui/core/Link';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
toolbar: { toolbar: {
@ -16,8 +16,8 @@ const useStyles = makeStyles((theme) => ({
flex: 1, flex: 1,
}, },
toolbarSecondary: { toolbarSecondary: {
justifyContent: "space-between", justifyContent: 'space-between',
overflowX: "auto", overflowX: 'auto',
}, },
toolbarLink: { toolbarLink: {
padding: theme.spacing(1), padding: theme.spacing(1),
@ -30,7 +30,7 @@ export default function Header(props) {
const { sections, title } = props; const { sections, title } = props;
return ( return (
<React.Fragment> <>
<Toolbar className={classes.toolbar}> <Toolbar className={classes.toolbar}>
<Typography <Typography
component="h2" component="h2"
@ -60,7 +60,7 @@ export default function Header(props) {
</Link> </Link>
))} ))}
</Toolbar> </Toolbar>
<Button size="small" variant="outlined" style={{ margin: "0 1rem" }}> <Button size="small" variant="outlined" style={{ margin: '0 1rem' }}>
Login Login
</Button> </Button>
@ -68,7 +68,7 @@ export default function Header(props) {
Sign up Sign up
</Button> </Button>
</Toolbar> </Toolbar>
</React.Fragment> </>
); );
} }

View File

@ -1,14 +1,22 @@
import React from "react"; import React from 'react';
import { Grid, Divider, Button, Label, Responsive } from "semantic-ui-react"; import {
import { Segment, Image } from "semantic-ui-react"; Grid,
import { RemoteService } from "../remote"; Divider,
import { withRouter } from "react-router-dom"; Button,
import { connect } from "react-redux"; Label,
Responsive,
Checkbox,
Segment, Image,
} from 'semantic-ui-react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { RemoteService } from '../remote';
import SimulationPanel from './SimulationPanel';
const IconHomeImage = () => ( const IconHomeImage = () => (
<Image <Image
src="smart-home.png" src="smart-home.png"
style={{ width: "50px", height: "auto" }} style={{ width: '50px', height: 'auto' }}
centered centered
as="a" as="a"
href="/" href="/"
@ -17,41 +25,72 @@ const IconHomeImage = () => (
const TitleImage = () => <Image src="sm_logo.png" size="medium" centered />; const TitleImage = () => <Image src="sm_logo.png" size="medium" centered />;
export class MyHeader extends React.Component { class MyHeader extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { simulationPanel: undefined };
this.getInfo(); this.getInfo();
this.logout = this.logout.bind(this); this.logout = this.logout.bind(this);
} }
logout() { logout() {
this.props.logout().then(() => this.props.history.push("/")); this.props.logout().then(() => this.props.history.push('/'));
} }
getInfo() { getInfo() {
this.props this.props
.fetchUserInfo() .fetchUserInfo()
.catch((err) => console.error("MyHeader fetch user info error", err)); .catch((err) => console.error('MyHeader fetch user info error', err));
} }
setCameraEnabled(val) {
const enabled = {
cameraEnabled: val,
};
this.props
.userPermissions(enabled)
.then(() => this.getInfo())
.catch((err) => console.error('Camera enabled', err));
}
openSimulationPanel = () => {
this.setState((state) => ({ simulationPanel: true }));
};
closeSimulationPanel = () => {
this.setState((state) => ({ simulationPanel: undefined }));
};
render() { render() {
return ( return (
<div> <div>
<SimulationPanel
simulationPanel={this.state.simulationPanel}
closeSimulationPanel={this.closeSimulationPanel}
/>
<Responsive minWidth={768}> <Responsive minWidth={768}>
<Grid columns="equal" divided inverted padded> <Grid verticalAlign="middle" columns="equal" divided inverted padded>
<Grid.Row color="black" textAlign="center"> <Grid.Row color="black" textAlign="center">
<Grid.Column width={3} height={0.5}> <Grid.Column width={4} height={0.5}>
<Grid.Row>
<Segment color="black" inverted> <Segment color="black" inverted>
<IconHomeImage /> <IconHomeImage />
</Segment> </Segment>
</Grid.Row>
<Divider />
<Grid.Row>
<Button basic inverted onClick={this.openSimulationPanel}>
Simulation Panel
</Button>
</Grid.Row>
</Grid.Column> </Grid.Column>
<Grid.Column> <Grid.Column>
<Segment color="black" inverted> <Segment color="black" inverted>
<TitleImage /> <TitleImage />
</Segment> </Segment>
</Grid.Column> </Grid.Column>
<Grid.Column width={2} heigth={1}> <Grid.Column width={4} heigth={1}>
<Label as="a" image color="black"> <Label as="a" image color="black">
<img alt="SmartHut logo" src="smart-home.png" /> <img alt="SmartHut logo" src="smart-home.png" />
{this.props.username} {this.props.username}
@ -60,6 +99,23 @@ export class MyHeader extends React.Component {
<Button basic inverted onClick={this.logout}> <Button basic inverted onClick={this.logout}>
Logout Logout
</Button> </Button>
<Divider />
<Segment
color="grey"
inverted
compact
style={{
margin: 'auto',
marginTop: '1em',
textAlign: 'center',
}}
>
<Checkbox
label={<label>Share cameras</label>}
checked={this.props.cameraEnabled}
onChange={(e, val) => this.setCameraEnabled(val.checked)}
/>
</Segment>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>
@ -73,15 +129,43 @@ export class MyHeader extends React.Component {
</Grid.Row> </Grid.Row>
<Grid.Row color="black" textAlign="center"> <Grid.Row color="black" textAlign="center">
<Grid.Column> <Grid.Column>
<Grid.Row>
<IconHomeImage /> <IconHomeImage />
</Grid.Row>
<Divider />
<Grid.Row>
<Button onClick={this.openSimulationPanel}>
Simulation Panel
</Button>
</Grid.Row>
</Grid.Column> </Grid.Column>
<Divider />
<Grid.Column> <Grid.Column>
<Grid.Column width={4} heigth={1}>
<Label as="a" image color="black"> <Label as="a" image color="black">
<img alt="SmartHut logo" src="smart-home.png" /> <img alt="SmartHut logo" src="smart-home.png" />
{this.props.username} {this.props.username}
</Label> </Label>
<Divider /> <Divider />
<Button onClick={this.logout}>Logout</Button> <Button basic inverted onClick={this.logout}>
Logout
</Button>
<Divider />
<Segment
compact
style={{
margin: 'auto',
marginTop: '1em',
textAlign: 'center',
}}
>
<Checkbox
label={<label>Share cameras</label>}
checked={this.props.cameraEnabled}
onChange={(e, val) => this.setCameraEnabled(val.checked)}
/>
</Segment>
</Grid.Column>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>
@ -93,10 +177,11 @@ export class MyHeader extends React.Component {
const mapStateToProps = (state, _) => ({ const mapStateToProps = (state, _) => ({
username: username:
state.userInfo && state.userInfo.username ? state.userInfo.username : "", state.userInfo && state.userInfo.username ? state.userInfo.username : '',
cameraEnabled: state.userInfo ? state.userInfo.cameraEnabled : false,
}); });
const LoginContainer = connect( const LoginContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(withRouter(MyHeader)); )(withRouter(MyHeader));
export default LoginContainer; export default LoginContainer;

View File

@ -1,5 +1,5 @@
import _ from "lodash"; import _ from 'lodash';
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Container, Container,
Icon, Icon,
@ -7,7 +7,7 @@ import {
Menu, Menu,
Sidebar, Sidebar,
Responsive, Responsive,
} from "semantic-ui-react"; } from 'semantic-ui-react';
const NavBarMobile = ({ const NavBarMobile = ({
children, children,
@ -30,7 +30,7 @@ const NavBarMobile = ({
<Sidebar.Pusher <Sidebar.Pusher
dimmed={visible} dimmed={visible}
onClick={onPusherClick} onClick={onPusherClick}
style={{ minHeight: "100vh" }} style={{ minHeight: '100vh' }}
> >
<Menu fixed="top" inverted> <Menu fixed="top" inverted>
<Menu.Item> <Menu.Item>
@ -67,7 +67,7 @@ const NavBarDesktop = ({ leftItems, rightItems }) => (
); );
const NavBarChildren = ({ children }) => ( const NavBarChildren = ({ children }) => (
<Container style={{ marginTop: "5em" }}>{children}</Container> <Container style={{ marginTop: '5em' }}>{children}</Container>
); );
class HomeNavabarApp extends Component { class HomeNavabarApp extends Component {
@ -75,10 +75,11 @@ class HomeNavabarApp extends Component {
super(props); super(props);
this.state = { this.state = {
logged: true, logged: true,
email: "", email: '',
password: "", password: '',
}; };
} }
state = { state = {
visible: false, visible: false,
}; };
@ -117,17 +118,23 @@ class HomeNavabarApp extends Component {
} }
} }
const leftItems = [{ as: "a", content: "Home", key: "home", href: "/" }]; const leftItems = [{
as: 'a', content: 'Home', key: 'home', href: '/',
}];
const rightItems = [ const rightItems = [
{ as: "a", content: "Login", key: "login", href: "/login" }, {
{ as: "a", content: "Sign up", key: "register", href: "/signup" }, as: 'a', content: 'Login', key: 'login', href: '/login',
},
{
as: 'a', content: 'Sign up', key: 'register', href: '/signup',
},
]; ];
const HomeNavbarApp = () => ( const HomeNavbarApp = () => (
<HomeNavabarApp <HomeNavabarApp
leftItems={leftItems} leftItems={leftItems}
rightItems={rightItems} rightItems={rightItems}
></HomeNavabarApp> />
); );
export default HomeNavbarApp; export default HomeNavbarApp;

View File

@ -0,0 +1,134 @@
import React, { Component } from 'react';
import {
Button,
Header,
Modal,
Icon,
Form,
Responsive,
Dropdown,
} from 'semantic-ui-react';
import { connect } from 'react-redux';
import { RemoteService, Forms } from '../remote';
import { appActions } from '../storeActions';
class HostModal extends Component {
constructor(props) {
super(props);
this.state = { guests: [], users: [] };
this.props
.fetchGuests()
.then(() => {
this.setState({
...this.state,
guests: this.props.guests.map((u) => u.id),
});
})
.catch(console.error);
Forms.fetchAllUsers()
.then((users) => this.setState({
...this.state,
users: users
.filter((u) => u.id !== this.props.currentUserId)
.map((u) => ({
key: u.id,
text: `@${u.username} (${u.name})`,
value: u.id,
})),
}))
.catch(console.error);
this.saveGuestSettings = this.saveGuestSettings.bind(this);
this.closeModal = this.closeModal.bind(this);
this.openModal = this.openModal.bind(this);
this.setGuests = this.setGuests.bind(this);
this.saveGuestSettings = this.saveGuestSettings.bind(this);
}
setGuests(_, guests) {
this.setState({ guests: guests.value });
}
closeModal() {
this.setState({ openModal: false });
}
openModal() {
this.setState({ openModal: true });
}
saveGuestSettings() {
this.props
.updateGuests(this.state.guests)
.then(this.closeModal)
.catch(console.error);
}
render() {
return (
<>
<Responsive minWidth={768}>
<Button icon labelPosition="left" inverted onClick={this.openModal}>
<Icon name="plus" size="small" />
Invitation settings
</Button>
</Responsive>
<Responsive maxWidth={768}>
<Button icon fluid labelPosition="left" onClick={this.openModal}>
<Icon name="plus" size="small" />
Invitation settings
</Button>
</Responsive>
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Header>Select guests</Header>
<Modal.Content>
<Form>
<Form.Field style={{ marginTop: '1rem' }}>
<label>Select which users are your guests: </label>
<Dropdown
name="guests"
placeholder="Select Guests"
fluid
multiple
onChange={this.setGuests}
options={this.state.users}
value={this.state.guests}
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button color="red" onClick={this.closeModal}>
<Icon name="remove" />
{' '}
Discard changes
</Button>
<Button color="green" onClick={this.saveGuestSettings}>
<Icon name="checkmark" />
{' '}
Save changes
</Button>
</Modal.Actions>
</Modal>
</>
);
}
}
const setActiveHost = (activeHost) => (dispatch) => dispatch(appActions.setActiveHost(activeHost));
const mapStateToProps = (state) => ({
guests: state.guests,
currentUserId: state.userInfo.id,
});
const HostModalContainer = connect(
mapStateToProps,
{ ...RemoteService, setActiveHost },
null,
{ forwardRef: true },
)(HostModal);
export default HostModalContainer;

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Button, Button,
Header, Header,
@ -8,56 +8,52 @@ import {
Icon, Icon,
Responsive, Responsive,
Image, Image,
} from "semantic-ui-react"; Confirm,
import SelectIcons from "./SelectIcons"; } from 'semantic-ui-react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { RemoteService } from "../remote"; import SelectIcons from './SelectIcons';
import { appActions } from "../storeActions"; import { RemoteService } from '../remote';
import { update } from "immutability-helper"; import { appActions } from '../storeActions';
const NO_IMAGE = "https://react.semantic-ui.com/images/wireframe/image.png"; const NO_IMAGE = 'https://react.semantic-ui.com/images/wireframe/image.png';
class RoomModal extends Component { class RoomModal extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = this.initialState; this.state = this.initialState;
this.setInitialState();
this.fileInputRef = React.createRef(); this.fileInputRef = React.createRef();
this.addRoomModal = this.addRoomModal.bind(this); this.addRoomModal = this.addRoomModal.bind(this);
this.updateIcon = this.updateIcon.bind(this); this.updateIcon = this.updateIcon.bind(this);
this.removeImage = this.removeImage.bind(this); this.unsetImage = this.unsetImage.bind(this);
} }
get initialState() { get initialState() {
return { return {
selectedIcon: this.type === "new" ? "home" : this.props.room.icon, selectedIcon: this.type === 'new' ? 'home' : this.props.room.icon,
name: this.type === "new" ? "New Room" : this.props.room.name, name: this.type === 'new' ? 'New Room' : this.props.room.name,
img: this.type === "new" ? null : this.props.room.image, img: this.type === 'new' ? null : this.props.room.image,
openModal: false, openModal: false,
sure: false,
}; };
} }
removeImage(e) { unsetImage = (e) => {
e.preventDefault(); e.preventDefault();
this.setState( this.setState({ ...this.state, img: '' });
update(this.state, { };
image: { $set: null },
})
);
}
setInitialState() { setInitialState() {
this.setState(this.initialState); this.setState(this.initialState);
} }
get type() { get type() {
return !this.props.id ? "new" : "modify"; return !this.props.id ? 'new' : 'modify';
} }
addRoomModal = (e) => { addRoomModal = (e) => {
let data = { const data = {
icon: this.state.selectedIcon, icon: this.state.selectedIcon,
name: this.state.name, name: this.state.name,
image: this.state.img, image: this.state.img,
@ -69,17 +65,17 @@ class RoomModal extends Component {
this.setInitialState(); this.setInitialState();
this.closeModal(); this.closeModal();
}) })
.catch((err) => console.error("error in creating room", err)); .catch((err) => console.error('error in creating room', err));
}; };
modifyRoomModal = (e) => { modifyRoomModal = (e) => {
let data = { const data = {
icon: this.state.selectedIcon, icon: this.state.selectedIcon,
name: this.state.name, name: this.state.name,
image: this.state.img, image: this.state.img,
}; };
console.log("data", data); console.log('data', data);
this.props this.props
.saveRoom(data, this.props.id) .saveRoom(data, this.props.id)
@ -87,19 +83,27 @@ class RoomModal extends Component {
this.setInitialState(); this.setInitialState();
this.closeModal(); this.closeModal();
}) })
.catch((err) => console.error("error in updating room", err)); .catch((err) => console.error('error in updating room', err));
}; };
deleteRoom = (e) => { deleteRoom = (e) => {
this.props this.props
.deleteRoom(this.props.id) .deleteRoom(this.props.id)
.then(() => this.closeModal()) .then(() => this.closeModal())
.catch((err) => console.error("error in deleting room", err)); .catch((err) => console.error('error in deleting room', err));
};
setSureTrue = () => {
this.setState({ sure: true });
};
setSureFalse = () => {
this.setState({ sure: false });
}; };
changeSomething = (event) => { changeSomething = (event) => {
let nam = event.target.name; const nam = event.target.name;
let val = event.target.value; const val = event.target.value;
this.setState({ [nam]: val }); this.setState({ [nam]: val });
}; };
@ -116,7 +120,7 @@ class RoomModal extends Component {
} }
getBase64(file, callback) { getBase64(file, callback) {
let reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file.target.files[0]); reader.readAsDataURL(file.target.files[0]);
reader.onload = () => { reader.onload = () => {
this.setState(Object.assign(this.state, { img: reader.result })); this.setState(Object.assign(this.state, { img: reader.result }));
@ -126,16 +130,16 @@ class RoomModal extends Component {
render() { render() {
const spaceDiv = { const spaceDiv = {
background: "#f4f4f4", background: '#f4f4f4',
padding: "10px 10px", padding: '10px 10px',
margin: "10px 0px", margin: '10px 0px',
}; };
return ( return (
<div> <div>
{!this.props.nicolaStop ? ( {!this.props.nicolaStop ? (
<div> <div>
<Responsive minWidth={768}> <Responsive minWidth={768}>
{this.type === "new" ? ( {this.type === 'new' ? (
<Button <Button
icon icon
labelPosition="left" labelPosition="left"
@ -150,7 +154,7 @@ class RoomModal extends Component {
)} )}
</Responsive> </Responsive>
<Responsive maxWidth={768}> <Responsive maxWidth={768}>
{this.type === "new" ? ( {this.type === 'new' ? (
<Button <Button
icon icon
fluid fluid
@ -177,7 +181,7 @@ class RoomModal extends Component {
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}> <Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Header> <Header>
{this.type === "new" ? "Add new room" : "Modify room"} {this.type === 'new' ? 'Add new room' : 'Modify room'}
</Header> </Header>
<Modal.Content> <Modal.Content>
<Form> <Form>
@ -220,38 +224,48 @@ class RoomModal extends Component {
<SelectIcons <SelectIcons
updateIcon={this.updateIcon} updateIcon={this.updateIcon}
currentIcon={ currentIcon={
this.type === "new" ? "home" : this.props.room.icon this.type === 'new' ? 'home' : this.props.room.icon
} }
/> />
</div> </div>
{this.type === "modify" ? ( {this.type === 'modify' ? (
<div>
<Button <Button
icon icon
labelPosition="left" labelPosition="left"
inverted inverted
color="red" color="red"
onClick={this.deleteRoom} onClick={this.setSureTrue}
> >
<Icon name="trash alternate" /> <Icon name="trash alternate" />
Delete room Delete Room
{' '}
</Button> </Button>
<Confirm
open={this.state.sure}
onCancel={this.setSureFalse}
onConfirm={this.deleteRoom}
/>
</div>
) : null} ) : null}
</Modal.Content> </Modal.Content>
<Modal.Actions> <Modal.Actions>
<Button color="red" onClick={this.closeModal}> <Button color="red" onClick={this.closeModal}>
<Icon name="remove" />{" "} <Icon name="remove" />
{this.type === "new" ? "Cancel" : "Discard changes"} {' '}
{this.type === 'new' ? 'Cancel' : 'Discard changes'}
</Button> </Button>
<Button <Button
color="green" color="green"
onClick={ onClick={
this.type === "new" ? this.addRoomModal : this.modifyRoomModal this.type === 'new' ? this.addRoomModal : this.modifyRoomModal
} }
> >
<Icon name="checkmark" />{" "} <Icon name="checkmark" />
{this.type === "new" ? "Add room" : "Save changes"} {' '}
{this.type === 'new' ? 'Add room' : 'Save changes'}
</Button> </Button>
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>
@ -260,9 +274,7 @@ class RoomModal extends Component {
} }
} }
const setActiveRoom = (activeRoom) => { const setActiveRoom = (activeRoom) => (dispatch) => dispatch(appActions.setActiveRoom(activeRoom));
return (dispatch) => dispatch(appActions.setActiveRoom(activeRoom));
};
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
room: ownProps.id ? state.rooms[ownProps.id] : null, room: ownProps.id ? state.rooms[ownProps.id] : null,
@ -271,6 +283,6 @@ const RoomModalContainer = connect(
mapStateToProps, mapStateToProps,
{ ...RemoteService, setActiveRoom }, { ...RemoteService, setActiveRoom },
null, null,
{ forwardRef: true } { forwardRef: true },
)(RoomModal); )(RoomModal);
export default RoomModalContainer; export default RoomModalContainer;

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Button, Button,
Header, Header,
@ -7,56 +7,87 @@ import {
Responsive, Responsive,
Form, Form,
Input, Input,
} from "semantic-ui-react"; Dropdown,
import { connect } from "react-redux"; Checkbox,
import { RemoteService } from "../remote"; Segment,
import { appActions } from "../storeActions"; } from 'semantic-ui-react';
//import { update } from "immutability-helper"; import { connect } from 'react-redux';
import SelectIcons from './SelectIcons';
import { RemoteService } from '../remote';
import { appActions } from '../storeActions';
// import { update } from "immutability-helper";
class SceneModal extends Component { class SceneModal extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = this.initialState; this.state = this.initialState;
this.setInitialState();
this.addSceneModal = this.addSceneModal.bind(this); this.addSceneModal = this.addSceneModal.bind(this);
this.modifySceneModal = this.modifySceneModal.bind(this); this.modifySceneModal = this.modifySceneModal.bind(this);
this.deleteScene = this.deleteScene.bind(this); this.deleteScene = this.deleteScene.bind(this);
this.updateIcon = this.updateIcon.bind(this);
this.setGuestAccessEnabled = this.setGuestAccessEnabled.bind(this);
this.setCopyFrom = this.setCopyFrom.bind(this);
}
componentDidUpdate(oldProps) {
// this might bug out since we are just checking the length
// to see if the elements inside this.props.scenes are changing
if (this.props.scenes.length !== oldProps.scenes.length) {
this.setState({ ...this.state, scenes: this.scenes });
}
} }
get initialState() { get initialState() {
return { return {
name: this.type === "new" ? "New Scene" : this.props.scene.name, name: this.type === 'new' ? 'New Scene' : this.props.scene.name,
openModal: false, openModal: false,
selectedIcon: 'home',
scenes: this.scenes,
copyFrom: null,
guestAccessEnabled:
this.type === 'new' ? null : this.props.scene.guestAccessEnabled,
}; };
} }
get scenes() {
return this.props.scenes.map((s) => ({
key: s.id,
text: s.name,
value: s.id,
}));
}
setInitialState() { setInitialState() {
this.setState(this.initialState); this.setState(this.initialState);
} }
get type() { get type() {
return !this.props.id ? "new" : "modify"; return !this.props.id ? 'new' : 'modify';
} }
addSceneModal = (e) => { addSceneModal = (e) => {
let data = { const data = {
name: this.state.name, name: this.state.name,
icon: this.state.selectedIcon,
}; };
this.props this.props
.saveScene(data, null) .saveScene(data, null, this.state.copyFrom)
.then(() => { .then(() => {
this.setInitialState(); this.setInitialState();
this.closeModal(); this.closeModal();
}) })
.catch((err) => console.error("error in creating room", err)); .catch((err) => console.error('error in creating room', err));
this.closeModal();
}; };
modifySceneModal = (e) => { modifySceneModal = (e) => {
let data = { const data = {
name: this.state.name, name: this.state.name,
icon: this.state.selectedIcon,
guestAccessEnabled: this.state.guestAccessEnabled,
}; };
console.log(data);
this.props this.props
.saveScene(data, this.props.id) .saveScene(data, this.props.id)
@ -64,37 +95,55 @@ class SceneModal extends Component {
this.setInitialState(); this.setInitialState();
this.closeModal(); this.closeModal();
}) })
.catch((err) => console.error("error in updating room", err)); .catch((err) => console.error('error in updating room', err));
}; };
deleteScene = (e) => { deleteScene = (e) => {
this.props this.props
.deleteScene(this.props.id) .deleteScene(this.props.id)
.then(() => this.closeModal()) .then(() => this.closeModal())
.catch((err) => console.error("error in deleting room", err)); .catch((err) => console.error('error in deleting room', err));
}; };
changeSomething = (event) => { changeSomething = (event) => {
let nam = event.target.name; const nam = event.target.name;
let val = event.target.value; const val = event.target.value;
this.setState({ [nam]: val }); this.setState({ [nam]: val });
}; };
closeModal = (e) => { closeModal = (e) => {
this.setState({ openModal: false }); this.setState({ ...this.state, openModal: false });
}; };
openModal = (e) => { openModal = (e) => {
this.setState({ openModal: true }); this.setState({ ...this.state, openModal: true });
}; };
updateIcon(e) {
this.setState({ ...this.state, selectedIcon: e });
}
setCopyFrom(_, copyFrom) {
this.setState({ ...this.state, copyFrom: copyFrom.value });
}
setGuestAccessEnabled(val) {
console.log(this.state, val);
this.setState({ ...this.state, guestAccessEnabled: val });
}
render() { render() {
const spaceDiv = {
background: '#f4f4f4',
padding: '10px 10px',
margin: '10px 0px',
};
return ( return (
<div> <div>
{!this.props.nicolaStop ? ( {!this.props.nicolaStop ? (
<div> <div>
<Responsive minWidth={768}> <Responsive minWidth={768}>
{this.type === "new" ? ( {this.type === 'new' ? (
<Button <Button
icon icon
labelPosition="left" labelPosition="left"
@ -109,7 +158,7 @@ class SceneModal extends Component {
)} )}
</Responsive> </Responsive>
<Responsive maxWidth={768}> <Responsive maxWidth={768}>
{this.type === "new" ? ( {this.type === 'new' ? (
<Button <Button
icon icon
fluid fluid
@ -136,13 +185,14 @@ class SceneModal extends Component {
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}> <Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Header> <Header>
{this.type === "new" ? "Add new scene" : "Modify scene"} {this.type === 'new' ? 'Add new scene' : 'Modify scene'}
</Header> </Header>
<Modal.Content> <Modal.Content>
<Form> <Form>
<p>Insert the name of the scene:</p> <p>Insert the name of the scene:</p>
<Form.Field> <Form.Field>
<Input <Input
required
label="Scene name" label="Scene name"
placeholder="Scene Name" placeholder="Scene Name"
name="name" name="name"
@ -151,9 +201,43 @@ class SceneModal extends Component {
value={this.state.name} value={this.state.name}
/> />
</Form.Field> </Form.Field>
<div style={spaceDiv}>
<label>Icon:</label>
<SelectIcons
updateIcon={this.updateIcon}
currentIcon={
this.type === 'new' ? 'home' : this.props.scene.icon
}
/>
</div>
{this.type === 'new' && (
<Form.Field>
<label>Copy configuration from:</label>
<Dropdown
name="guests"
placeholder="Select scene to copy configuration form"
fluid
onChange={this.setCopyFrom}
options={this.state.scenes}
value={this.state.copyFrom}
/>
</Form.Field>
)}
{this.type === 'modify' ? (
<Form.Field>
<Segment compact style={{ marginBottom: '1rem' }}>
<Checkbox
label="Enable guest access"
checked={this.state.guestAccessEnabled}
toggle
onChange={(e, val) => this.setGuestAccessEnabled(val.checked)}
/>
</Segment>
</Form.Field>
) : null}
</Form> </Form>
{this.type === "modify" ? ( {this.type === 'modify' ? (
<Button <Button
icon icon
labelPosition="left" labelPosition="left"
@ -168,18 +252,20 @@ class SceneModal extends Component {
</Modal.Content> </Modal.Content>
<Modal.Actions> <Modal.Actions>
<Button color="red" onClick={this.closeModal}> <Button color="red" onClick={this.closeModal}>
<Icon name="remove" />{" "} <Icon name="remove" />
{this.type === "new" ? "Cancel" : "Discard changes"} {' '}
{this.type === 'new' ? 'Cancel' : 'Discard changes'}
</Button> </Button>
<Button <Button
color="green" color="green"
onClick={ onClick={
this.type === "new" ? this.addSceneModal : this.modifySceneModal this.type === 'new' ? this.addSceneModal : this.modifySceneModal
} }
> >
<Icon name="checkmark" />{" "} <Icon name="checkmark" />
{this.type === "new" ? "Add scene" : "Save changes"} {' '}
{this.type === 'new' ? 'Add scene' : 'Save changes'}
</Button> </Button>
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>
@ -188,17 +274,16 @@ class SceneModal extends Component {
} }
} }
const setActiveScene = (activeScene) => { const setActiveScene = (activeScene) => (dispatch) => dispatch(appActions.setActiveScene(activeScene));
return (dispatch) => dispatch(appActions.setActiveScene(activeScene));
};
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
scene: ownProps.id ? state.scenes[ownProps.id] : null, scene: ownProps.id ? state.scenes[ownProps.id] : null,
scenes: Object.values(state.scenes),
}); });
const SceneModalContainer = connect( const SceneModalContainer = connect(
mapStateToProps, mapStateToProps,
{ ...RemoteService, setActiveScene }, { ...RemoteService, setActiveScene },
null, null,
{ forwardRef: true } { forwardRef: true },
)(SceneModal); )(SceneModal);
export default SceneModalContainer; export default SceneModalContainer;

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Button, Grid } from "semantic-ui-react"; import { Button, Grid } from 'semantic-ui-react';
export default class SelectIcons extends Component { export default class SelectIcons extends Component {
constructor(props) { constructor(props) {
@ -11,7 +11,7 @@ export default class SelectIcons extends Component {
selectIcon = (e) => { selectIcon = (e) => {
let el = e.target.name; let el = e.target.name;
if (e.target.tagName === "I") { if (e.target.tagName === 'I') {
el = e.target.parentNode.name; el = e.target.parentNode.name;
} }
this.props.updateIcon(el); this.props.updateIcon(el);
@ -20,36 +20,32 @@ export default class SelectIcons extends Component {
render() { render() {
const myicons = [ const myicons = [
["home", "coffee", "beer", "glass martini", "film", "video"], ['home', 'coffee', 'beer', 'glass martini', 'film', 'video'],
["music", "headphones", "fax", "phone", "laptop", "bath"], ['music', 'headphones', 'fax', 'phone', 'laptop', 'bath'],
["shower", "bed", "child", "warehouse", "car", "bicycle"], ['shower', 'bed', 'child', 'warehouse', 'car', 'bicycle'],
["motorcycle", "archive", "boxes", "cubes", "chess", "gamepad"], ['motorcycle', 'archive', 'boxes', 'cubes', 'chess', 'gamepad'],
["futbol", "table tennis", "server", "tv", "heart", "camera"], ['futbol', 'table tennis', 'server', 'tv', 'heart', 'camera'],
["trophy", "wrench", "image", "book", "university", "medkit"], ['trophy', 'wrench', 'image', 'book', 'university', 'medkit'],
["paw", "tree", "utensils", "male", "female", "life ring outline"], ['paw', 'tree', 'utensils', 'male', 'female', 'life ring outline'],
]; ];
return ( return (
<Grid centered relaxed> <Grid centered relaxed>
{myicons.map((e, i) => { {myicons.map((e, i) => (
return (
<Grid.Row key={i}> <Grid.Row key={i}>
{e.map((e, i) => { {e.map((e, i) => (
return (
<Grid.Column key={i}> <Grid.Column key={i}>
<Button <Button
name={e} name={e}
color={e === this.state.currentIcon ? "black" : null} color={e === this.state.currentIcon ? 'black' : null}
icon={e} icon={e}
size="small" size="small"
onClick={this.selectIcon} onClick={this.selectIcon}
/> />
</Grid.Column> </Grid.Column>
); ))}
})}
</Grid.Row> </Grid.Row>
); ))}
})}
</Grid> </Grid>
); );
} }

View File

@ -0,0 +1,428 @@
import React, { Component } from 'react';
import {
Grid,
Divider,
Image,
Modal,
Button,
} from 'semantic-ui-react';
import { connect } from 'react-redux';
import { RemoteService } from '../remote';
import SimulationPanelSlider from './SimulationPanelSlider.js';
/*
const SimulationPanel = (props) => (
<Modal
isOpen={!!props.simulationPanel}
contentLabel="Simulation Panel"
onRequestClose={props.closeSimulationPanel}
// style={ReactModalPortal,ReactModal__Overlay, modal}
style={{
overlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
},
content: {
position: 'absolute',
top: '40px',
left: '40px',
right: '40px',
bottom: '40px',
border: '1px solid #ccc',
background: '#fff',
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
borderRadius: '4px',
outline: 'none',
padding: '20px',
backgroundColor: 'rgb(65, 67, 69)',
},
}}
>
{props.simulationPanel}
<Grid celled>
{/* TITLE }
<Grid.Row textAlign="center">
<h1 style={{ color: 'white', padding: '1rem', fontFamily: 'Arial' }}>
Welcome in the Simulation Panel
</h1>
</Grid.Row>
{/* TEMPERATURE SENSOR }
<Grid.Row>
<Grid.Column width={6} textAlign="center">
<Divider
style={{
marginTop: '3rem',
marginBottom: 'auto',
}}
/>
<Image
src="./../img/thermo-simulation.png"
style={{
width: '60px',
height: 'auto',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '1rem',
marginBottom: '1rem',
}}
/>
<p style={{ color: 'white', padding: '0.5rem' }}>
Temperature Sensor
</p>
<Divider />
</Grid.Column>
<Grid.Column width={10} textAlign="center">
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
<Divider />
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
</Grid.Column>
</Grid.Row>
{/* HUMIDTY SENSOR }
<Grid.Row>
<Grid.Column width={6} textAlign="center">
<Divider
style={{
marginTop: '3rem',
marginBottom: 'auto',
}}
/>
<Image
src="./../img/humidity-simulation.png"
style={{
width: '60px',
height: 'auto',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '1rem',
marginBottom: '1rem',
}}
/>
<p style={{ color: 'white', padding: '0.5rem' }}>
Humidity Sensor
</p>
<Divider />
</Grid.Column>
<Grid.Column width={10} textAlign="center">
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
<Divider />
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
</Grid.Column>
</Grid.Row>
{/* LIGHT SENSOR }
<Grid.Row>
<Grid.Column width={6} textAlign="center">
<Divider
style={{
marginTop: '3rem',
marginBottom: 'auto',
}}
/>
<Image
src="./../img/light-simulation.png"
style={{
width: '60px',
height: 'auto',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '1rem',
marginBottom: '1rem',
}}
/>
<p style={{ color: 'white', padding: '0.5rem' }}>
Light Sensor
</p>
<Divider />
</Grid.Column>
<Grid.Column width={10} textAlign="center">
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
<Divider />
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
</Grid.Column>
</Grid.Row>
{/* MOTION SENSOR }
<Grid.Row>
<Grid.Column width={6} textAlign="center">
<Divider
style={{
marginTop: '3rem',
marginBottom: 'auto',
}}
/>
<Image
src="./../img/motion-simulation.png"
style={{
width: '60px',
height: 'auto',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '1rem',
marginBottom: '1rem',
}}
/>
<p style={{ color: 'white', padding: '0.5rem' }}>
Motion Sensor
</p>
<Divider />
</Grid.Column>
<Grid.Column width={10} textAlign="center">
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
<Divider />
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider />
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</Modal>
);
*/
class SimulationPanel extends Component {
constructor(props) {
super(props);
console.log(this.props.devices);
this.state = {
value: null,
error: null,
};
this.updateSliderValues = this.updateSliderValues.bind(this);
this.saveChanges = this.saveChanges.bind(this);
}
updateSliderValues(type, data, motion) {
console.log(data, motion);
this.setState({
[type ? 'error' : 'value']: parseInt(data),
});
}
saveChanges(device) {
console.log(this.state);
const { value } = this.state;
const { error } = this.state;
if (device.kind === 'sensor') {
this.props
.updateSimulation(
{ ...device, typical: value, err: error },
)
.catch((err) => console.error('Simulation panel update error', err));
} else {
const val = device.kind === 'motionSensor' ? 'detected' : 'typical';
this.props
.saveDevice(
{ ...device, [val]: value, err: error },
null,
)
.catch((err) => console.error('Simulation panel update error', err));
}
}
renderImage(device) {
let image = './../img/';
switch (device.kind) {
case 'thermostat':
image += 'thermo-simulation.png';
break;
case 'motionSensor':
image += 'motion-simulation.png';
break;
case 'sensor':
if (device.sensorType === 'TEMPERATURE') {
image += 'thermo-simulation.png';
} else if (device.sensorType === 'HUMIDITY') {
image += 'humidity-simulation.png';
} else {
image += 'light-simulation.png';
}
break;
default:
break;
}
return (
<Image
src={image}
style={{
width: '60px',
height: 'auto',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '1rem',
marginBottom: '1rem',
}}
/>
);
}
render() {
return (
<Modal
closeIcon
open={!!this.props.simulationPanel}
onClose={this.props.closeSimulationPanel}
>
<Grid celled>
<Grid.Row textAlign="center">
<h1 style={{ padding: '1rem', fontFamily: 'Arial' }}>
Welcome in the Simulation Panel
</h1>
</Grid.Row>
{
this.props.devices
? this.props.devices.map((d, i) => (
<Grid.Row key={i}>
<Grid.Column width={6} textAlign="center">
{this.renderImage(d)}
<p style={{ padding: '0.5rem' }}>
{d.name}
</p>
<Divider />
<Button
color="green"
id={d.id}
onClick={(e, { id }) => this.saveChanges(d, id)}
>
Save
</Button>
</Grid.Column>
<Grid.Column width={10} textAlign="center">
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider update={this.updateSliderValues} error={false} device={d} id={d.id} />
</div>
{
d.kind === 'motionSensor'
? null
: (
<>
<Divider />
<div
style={{
display: 'block',
marginLeft: '30%',
}}
>
<SimulationPanelSlider update={this.updateSliderValues} error device={d} id={d.id} />
</div>
</>
)
}
</Grid.Column>
</Grid.Row>
))
: null
}
</Grid>
</Modal>
);
}
}
const mapStateToProps = (state, _) => ({
get devices() {
const deviceInternalSensor = {
thermostat: 'Thermostat',
sensor: 'Sensor',
motionSensor: 'Sensor',
};
const deviceArray = [
...Object.values(state.devices),
].filter((e) => e.kind in deviceInternalSensor);
return deviceArray;
},
});
const SimulationPanelContainer = connect(
mapStateToProps,
RemoteService,
)(SimulationPanel);
export default SimulationPanelContainer;

View File

@ -0,0 +1,131 @@
import React, { Component } from 'react';
import {
Form, Grid, Checkbox, Message, Label,
} from 'semantic-ui-react';
import { connect } from 'react-redux';
import { RemoteService } from '../remote';
class SimulationPanelSlider extends Component {
constructor(props) {
super(props);
console.log(this.props.device);
this.state = {
value: this.internalValue,
error: this.internalValue,
};
this.props.update(this.props.error, this.internalValue);
this.updateSliderValues = this.updateSliderValues.bind(this);
}
updateSliderValues(data) {
this.setState({
[this.props.error ? 'error' : 'value']: data,
});
console.log(this.state);
this.props.update(this.props.error, data, this.props.device.kind === 'motionSensor');
}
settings() {
let max;
let step;
switch (this.props.device.kind) {
case 'sensor':
if (this.props.device.sensorType === 'LIGHT') {
max = this.props.error ? 1000 : 15000;
step = 10;
} else {
max = 100;
step = 1;
}
break;
case 'thermostat':
max = 100;
step = 1;
break;
default:
break;
}
return { max, step };
}
get internalValue() {
if (this.props.device.kind === 'motionSensor') {
return this.props.device.detected;
}
return this.props.error ? this.props.device.err : this.props.device.typical.toFixed(2);
}
get getValue() {
switch (this.props.device.kind) {
case 'motionSensor':
return this.props.device.detected;
case 'thermostat':
return this.props.device.internalSensorTemperature;
case 'sensor':
return this.props.device.value.toFixed(2);
default:
return '';
}
}
render() {
return (
<Grid columns={2}>
<Grid.Column as={Form} textAlign="center">
<Message>
<Message.Header>{this.props.error ? 'Edit error' : 'Edit value'}</Message.Header>
<p>
Actual value of
{' '}
{this.props.device.name}
:
{this.props.device.kind === 'motionSensor'
? this.getValue ? 'on' : 'off' : this.getValue}
</p>
<p>
{this.props.error ? 'error' : 'typical value'}
{' '}
:
{this.props.device.kind === 'motionSensor'
? this.internalValue ? 'on' : 'off' : this.internalValue}
</p>
</Message>
{
this.props.device.kind === 'motionSensor'
? (
<Checkbox
toggle
name="value"
checked={this.state.value}
onChange={(e, { checked }) => this.updateSliderValues(checked)}
/>
)
: (
<>
<Label>
Value :
<Label.Detail>{this.props.error ? this.state.error : this.state.value}</Label.Detail>
</Label>
<Form.Input
min={0}
max={this.settings().max}
name="value"
onChange={(e, { value }) => this.updateSliderValues(value)}
step={this.settings().step}
type="range"
value={this.props.error ? this.state.error : this.state.value}
/>
</>
)
}
</Grid.Column>
</Grid>
);
}
}
const mapStateToProps = (state, ownProps) => ({
device: state.devices[ownProps.id],
});
const SimulationPanelSliderContainer = connect(mapStateToProps, RemoteService)(SimulationPanelSlider);
export default SimulationPanelSliderContainer;

View File

@ -1,5 +1,5 @@
import React from "react"; import React from 'react';
import DevicePanel from "./dashboard/DevicePanel"; import DevicePanel from './dashboard/DevicePanel';
export default class VideoTest extends React.Component { export default class VideoTest extends React.Component {
render() { render() {

View File

@ -0,0 +1,816 @@
import React, { Component, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { RemoteService } from '../../remote';
import update from 'immutability-helper';
import './Automations.css';
import {
Segment,
Grid,
Icon,
Header,
Input,
Button,
Modal,
List,
Divider,
Menu,
Form,
Dropdown,
Checkbox,
} from 'semantic-ui-react';
export const operands = [
{ key: 'EQUAL', text: '=', value: 'EQUAL' },
{
key: 'GREATER_EQUAL',
text: '\u2265',
value: 'GREATER_EQUAL',
},
{
key: 'GREATER',
text: '>',
value: 'GREATER',
},
{
key: 'LESS_EQUAL',
text: '\u2264',
value: 'LESS_EQUAL',
},
{
key: 'LESS',
text: '<',
value: 'LESS',
},
];
const deviceStateOptions = [
{ key: 'off', text: 'off', value: false },
{ key: 'on', text: 'on', value: true },
];
const thermostatOptions = [
{ key: 'HEATING', text: 'HEATING', value: 'HEATING' },
{ key: 'COOLING', text: 'COOLING', value: 'COOLING' },
{ key: 'IDLE', text: 'IDLE', value: 'IDLE' },
{ key: 'OFF', text: 'OFF', value: 'OFF' },
];
const thermostatOperands = [
{ key: 'EQUAL', text: '=', value: 'EQUAL' },
{ key: 'NOTEQUAL', text: '\u2260', value: 'NOTEQUAL' },
];
const CreateTrigger = (props) => {
const [activeOperand, setActiveOperand] = useState(true);
const [activeThermostat, setActiveThermostat] = useState(false);
const operandsRef = useRef(null);
const valuesRef = useRef(null);
const notAdmitedDevices = ['buttonDimmer'];
const hasOperand = new Set([
'knobDimmer',
'dimmableLight',
'curtains',
'sensor',
]);
const deviceList = Object.values(props.devices)
.map((device) => ({
key: device.id,
text: device.name,
value: device.id,
kind: device.kind,
}))
.filter((e) => !notAdmitedDevices.includes(e.kind));
const onChange = (e, val) => {
props.inputChange(val);
setActiveOperand(hasOperand.has(props.devices[val.value].kind));
setActiveThermostat(props.devices[val.value].kind === 'thermostat' && props.condition);
if (operandsRef.current) operandsRef.current.setValue('');
if (valuesRef.current) valuesRef.current.inputRef.current.valueAsNumber = undefined;
};
return (
<List.Item>
<List.Content>
<Form>
<Form.Group>
<Form.Field inline width={7}>
<Dropdown
onChange={onChange}
name="device"
search
selection
options={deviceList}
placeholder="Device"
/>
</Form.Field>
{
activeThermostat ? (
<>
<Form.Field inline width={2}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
ref={operandsRef}
name="operand"
compact
selection
options={thermostatOperands}
/>
</Form.Field>
<Form.Field inline width={7}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
placeholder="State"
name="mode"
compact
selection
options={thermostatOptions}
/>
</Form.Field>
</>
)
: activeOperand ? (
<>
<Form.Field inline width={2}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
ref={operandsRef}
name="operand"
compact
selection
options={operands}
/>
</Form.Field>
<Form.Field inline width={7}>
<Input
onChange={(e, val) => {
props.inputChange(val);
}}
ref={valuesRef}
name="value"
type="number"
placeholder="Value"
/>
</Form.Field>
</>
) : (
<Form.Field inline width={7}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
placeholder="State"
name="on"
compact
selection
options={deviceStateOptions}
/>
</Form.Field>
)
}
</Form.Group>
</Form>
</List.Content>
</List.Item>
);
};
const SceneItem = (props) => {
const position = props.order.indexOf(props.scene.id);
return (
<List.Item>
<List.Header>
<Grid textAlign="center">
<Grid.Row>
<Grid.Column width={4}>
<Checkbox
toggle
onChange={(e, val) => props.orderScenes(props.scene.id, val.checked)}
checked={position + 1 > 0}
/>
</Grid.Column>
<Grid.Column width={8}>
<h3>{props.scene.name}</h3>
</Grid.Column>
<Grid.Column width={4}>
<h3>{position !== -1 ? `# ${position + 1}` : ''}</h3>
</Grid.Column>
</Grid.Row>
</Grid>
</List.Header>
</List.Item>
);
};
const Trigger = ({
deviceName, trigger, onRemove, index,
}) => {
const {
operand, value, on, mode,
} = trigger;
let symbol;
if (operand) {
symbol = operands.filter((opt) => opt.key === operand)[0].text;
}
return (
<List.Item className="trigger-item">
<Menu compact>
<Menu.Item as="span">{deviceName}</Menu.Item>
{operand ? <Menu.Item as="span">{symbol}</Menu.Item> : ''}
<Menu.Item as="span">{mode || (operand ? value : on ? 'on' : 'off')}</Menu.Item>
</Menu>
<Icon
as="i"
onClick={() => onRemove(index)}
className="remove-icon"
name="remove"
/>
</List.Item>
);
};
class AutomationSaveModal extends Component {
constructor(props) {
super(props);
this.state = {
triggerList: [],
conditionsList: [],
order: [],
automationName: 'New Automation',
editName: false,
newTrigger: {},
newCondition: {},
scenesFilter: null,
openModal: false,
};
if (this.props.automation) {
this.state.automationName = this.props.automation.name;
for (const scenePriority of this.props.automation.scenes) {
this.state.order[scenePriority.priority] = scenePriority.sceneId;
}
for (const trigger of this.props.automation.triggers) {
this.state.triggerList.push(
{
device: trigger.deviceId,
kind: trigger.kind,
...(trigger.kind === 'booleanTrigger'
? { on: trigger.on }
: {
operand: trigger.operator,
value: trigger.value,
}),
},
);
}
}
this.setTrigger = this._setter('triggerList');
this.setOrder = this._setter('order');
this.setautomationName = this._setter('automationName');
this.setEditName = this._setter('editName');
this.setNewTrigger = this._setter('newTrigger');
this.addTrigger = this.addTrigger.bind(this);
this.removeTrigger = this.removeTrigger.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.searchScenes = this.searchScenes.bind(this);
this.orderScenes = this.orderScenes.bind(this);
this.onChangeName = this.onChangeName.bind(this);
// Conditions
this.setNewCondition = this._setter('newCondition');
this.addCondition = this.addCondition.bind(this);
this.removeCondition = this.removeCondition.bind(this);
}
openModal = (e) => {
this.setState({ openModal: true });
};
closeModal = (e) => {
this.setState({ openModal: false });
};
get deviceList() {
return Object.values(this.props.devices);
}
_setter(property) {
return (value) => this.setState(update(this.state, { [property]: { $set: value } }));
}
triggerKind(trigger) {
if ('operand' in trigger && 'value' in trigger) {
return 'rangeTrigger';
}
if ('on' in trigger) {
return 'booleanTrigger';
}
return false;
// throw new Error("Trigger kind not handled");
}
conditionKind(condition) {
if ('operand' in condition && 'value' in condition) {
return 'rangeTrigger';
}
if ('on' in condition) {
return 'booleanTrigger';
}
if ('operand' in condition && 'mode' in condition) {
return 'thermostatCondition';
}
return false;
}
checkRange(deviceKind, devicesWithPercentage, trigger, error, device) {
if (!trigger.device || !trigger.operand || !trigger.value) {
return error;
}
if (trigger.value < 0) {
error.message = 'Values cannot be negative';
return error;
}
// If the device's range is a percentage, values cannot exceed 100
if (
devicesWithPercentage.includes(deviceKind)
&& trigger.value > 100
) {
error.message = "The value can't exceed 100, as it's a percentage";
return error;
}
if (
deviceKind === 'sensor'
&& device.sensor === 'HUMIDITY'
&& trigger.value > 100
) {
error.message = "The value can't exceed 100, as it's a percentage";
return error;
}
return false;
}
checkBool(trigger, error) {
if (!trigger.device || trigger.on === null || trigger.on === undefined) return error;
return false;
}
_checkNewTrigger(trigger, isCondition = false) {
const error = {
result: false,
message: 'There are missing fields!',
};
const device = Object.values(this.props.devices).filter(
(d) => d.id === trigger.device,
)[0];
const triggerKind = this.triggerKind(trigger);
const conditionKind = this.conditionKind(trigger);
if (!isCondition && (!device || !triggerKind)) {
error.message = 'There are missing fields';
return error;
}
if (isCondition && !conditionKind) {
error.message = 'There are missing fields';
return error;
}
const deviceKind = device.kind;
const devicesWithPercentage = ['dimmableLight', 'curtains', 'knobDimmer'];
switch (isCondition ? conditionKind : triggerKind) {
case 'booleanTrigger':
const checkBoolTrigger = this.checkBool(trigger, error);
if (checkBoolTrigger) {
return checkBoolTrigger;
}
break;
case 'booleanCondition':
const checkBoolCond = this.checkBool(trigger, error);
if (checkBoolCond) {
return checkBoolCond;
}
break;
case 'rangeTrigger':
const checkRangeTrigger = this.checkRange(deviceKind, devicesWithPercentage, trigger, error, device);
if (checkRangeTrigger) {
return checkRangeTrigger;
}
break;
case 'rangeCondition':
const checkRangeCond = this.checkRange(deviceKind, devicesWithPercentage, trigger, error, device);
if (checkRangeCond) {
return checkRangeCond;
}
break;
case 'thermostatCondition':
if (!trigger.device || trigger.mode === null || trigger.mode === undefined || !trigger.operand) return error;
break;
default:
throw new Error('theoretically unreachable statement');
}
let isNotDuplicate = null;
if (isCondition === true) {
isNotDuplicate = !this.state.conditionsList.some(
(t) => t.device === trigger.device && t.operand === trigger.operand,
);
} else {
isNotDuplicate = !this.state.triggerList.some(
(t) => t.device === trigger.device && t.operand === trigger.operand,
);
}
const type = isCondition ? 'condition' : 'trigger';
const duplicationMessage = `You have already created a ${type} for this device with the same conditions`;
return {
result: isNotDuplicate,
message: isNotDuplicate
? null
: duplicationMessage,
};
}
addTrigger() {
const { result, message } = this._checkNewTrigger(this.state.newTrigger);
if (result) {
this.setState(
update(this.state, {
triggerList: { $push: [this.state.newTrigger] },
}),
);
} else {
alert(message);
}
}
removeTrigger(index) {
this.setState(
update(this.state, { triggerList: { $splice: [[index, 1]] } }),
);
}
// This gets triggered when the devices dropdown changes the value.
onInputChange(val) {
if (val.name === 'device') {
this.setNewTrigger({ [val.name]: val.value });
} else {
this.setNewTrigger({
...this.state.newTrigger,
[val.name]: val.value,
});
}
}
onChangeName(_, val) {
this.setautomationName(val.value);
}
orderScenes = (id, checked) => {
if (checked) {
this.setState(update(this.state, { order: { $push: [id] } }));
} else {
this.setState(
update(this.state, {
order: (prevList) => prevList.filter((e) => e !== id),
}),
);
}
};
searchScenes(_, { value }) {
this.setState(update(this.state, { scenesFilter: { $set: value } }));
this.forceUpdate();
}
get sceneList() {
if (!this.scenesFilter) {
return this.props.scenes;
}
return this.props.scenes.filter((e) => e.name.includes(this.scenesFilter));
}
generateBoolKey(trigger) {
return `${trigger.device}${trigger.on}`;
}
generateRangeKey(trigger) {
return `${trigger.device}${trigger.operand}${trigger.value}`;
}
_generateKey = (trigger, isCondition = false) => {
switch (isCondition ? this.conditionKind(trigger) : this.triggerKind(trigger)) {
case 'booleanTrigger':
return this.generateBoolKey(trigger);
case 'booleanCondition':
return this.generateBoolKey(trigger);
case 'rangeTrigger':
return this.generateRangeKey(trigger);
case 'rangeCondition':
return this.generateRangeKey(trigger);
case 'thermostatCondition':
return `${trigger.device}${trigger.operand}${trigger.mode}`;
default:
throw new Error('theoretically unreachable statement');
}
};
checkBeforeSave = () => {
if (!this.state.automationName) {
alert('Give a name to the automation');
return false;
}
if (this.state.triggerList.length <= 0) {
alert('You have to create a trigger');
return false;
}
if (this.state.order.length <= 0) {
alert('You need at least one active scene');
return false;
}
return true;
};
saveAutomation = () => {
if (this.checkBeforeSave()) {
const automation = {
name: this.state.automationName,
};
if (this.props.id) {
automation.id = this.props.id;
automation.triggers = [];
automation.scenes = [];
automation.conditions = [];
for (let i = 0; i < this.state.order.length; i++) {
automation.scenes.push({
priority: i,
sceneId: this.state.order[i],
});
}
for (const trigger of this.state.triggerList) {
const kind = trigger.kind || this.triggerKind(trigger);
automation.triggers.push(
{
deviceId: trigger.device,
kind,
...(kind === 'booleanTrigger'
? { on: trigger.on }
: {
operator: trigger.operand,
range: parseInt(trigger.value),
}),
},
);
}
for (const condition of this.state.conditionsList) {
const kind = condition.kind || this.conditionKind(condition);
const loSpagnolo = (kind === 'thermostatCondition' ? { operator: condition.operand, mode: condition.mode }
: {
operator: condition.operand,
range: parseInt(condition.value),
});
automation.conditions.push(
{
deviceId: condition.device,
kind,
...(kind === 'booleanTrigger'
? { on: condition.on }
: loSpagnolo
),
},
);
}
this.props
.fastUpdateAutomation(automation)
.then(this.closeModal)
.catch(console.error);
} else {
this.props
.saveAutomation({
automation,
triggerList: this.state.triggerList,
order: this.state.order,
conditionList: this.state.conditionsList,
})
.then(this.closeModal)
.catch(console.error);
}
}
};
get trigger() {
return this.props.id ? (
<Button
style={{ display: 'inline' }}
circular
size="small"
icon="edit"
onClick={this.openModal}
/>
) : (
<Button icon labelPosition="left" color="green" onClick={this.openModal}>
<Icon name="add" />
Create new automation
</Button>
);
}
// CONDITIONS
addCondition() {
// Same method used to check triggers and conditions, not a mistake
const { result, message } = this._checkNewTrigger(this.state.newCondition, true);
if (result) {
this.setState(
update(this.state, {
conditionsList: { $push: [this.state.newCondition] },
}),
);
} else {
alert(message);
}
}
removeCondition(index) {
this.setState(
update(this.state, { conditionsList: { $splice: [[index, 1]] } }),
);
}
onInputChangeCondition = (val) => {
if (val.name === 'device') {
this.setNewCondition({ [val.name]: val.value });
} else {
this.setNewCondition({
...this.state.newCondition,
[val.name]: val.value,
});
}
}
render() {
console.log(this.state.conditionsList);
return (
<Modal
closeIcon
trigger={this.trigger}
open={this.state.openModal}
onClose={this.closeModal}
>
<Segment basic>
<Header style={{ display: 'inline', marginRight: '1rem' }}>
{this.state.editName ? (
<Input
focus
transparent
placeholder="New automation name..."
onChange={this.onChangeName}
/>
) : (
this.state.automationName
)}
</Header>
<Button
onClick={() => this.setEditName((prev) => !prev)}
style={{ display: 'inline' }}
circular
size="small"
icon={this.state.editName ? 'save' : 'edit'}
/>
<Segment placeholder className="segment-automations">
<Grid columns={2} stackable textAlign="center">
<Divider vertical />
<Grid.Row verticalAlign="middle">
<Grid.Column>
<Header>Create Triggers</Header>
<List divided relaxed>
{this.state.triggerList.length > 0
&& this.state.triggerList.map((trigger, i) => {
const deviceName = this.deviceList.filter(
(d) => d.id === trigger.device,
)[0].name;
const key = this._generateKey(trigger);
return (
<Trigger
key={key}
index={i}
deviceName={deviceName}
trigger={trigger}
onRemove={this.removeTrigger}
/>
);
})}
<CreateTrigger
devices={this.props.devices}
inputChange={this.onInputChange}
/>
</List>
<Button
onClick={this.addTrigger}
circular
icon="add"
color="blue"
size="huge"
/>
</Grid.Column>
<Grid.Column>
{this.props.scenes.length > 0 ? (
<>
<Header>Activate Scenes</Header>
<Input
icon="search"
placeholder="Search..."
fluid
onChange={this.searchScenes}
/>
<Divider horizontal />
<List divided relaxed>
{this.sceneList.map((scene) => (
<SceneItem
key={scene.id}
scene={scene}
order={this.state.order}
orderScenes={this.orderScenes}
/>
))}
</List>
</>
) : (
<>
<Header icon>
<Icon name="world" />
</Header>
<Button primary>Create Scene</Button>
</>
)}
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Grid columns={1} stackable textAlign="center">
<Grid.Row verticalAlign="middle">
<Grid.Column>
<Header>Add Conditions</Header>
<List divided relaxed>
{this.state.conditionsList.length > 0
&& this.state.conditionsList.map((condition, i) => {
const deviceName = this.deviceList.filter(
(d) => d.id === condition.device,
)[0].name;
const key = this._generateKey(condition, true);
return (
<Trigger
key={key}
index={i}
deviceName={deviceName}
trigger={condition}
onRemove={this.removeCondition}
/>
);
})}
<CreateTrigger
condition
devices={this.props.devices}
inputChange={this.onInputChangeCondition}
/>
</List>
<Button
onClick={this.addCondition}
circular
icon="add"
color="blue"
size="huge"
/>
</Grid.Column>
</Grid.Row>
</Grid>
<Grid>
<Grid.Row>
<Grid.Column style={{ marginRight: '1rem' }}>
<Button onClick={() => this.saveAutomation()} color="green">
{this.props.id ? 'Save' : 'Create'}
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</Modal>
);
}
}
const mapStateToProps = (state, ownProps) => ({
scenes: Object.values(state.scenes),
devices: state.devices,
automation: ownProps.id ? state.automations[ownProps.id] : null,
});
const AutomationSaveModalContainer = connect(
mapStateToProps,
RemoteService,
)(AutomationSaveModal);
export default AutomationSaveModalContainer;

View File

@ -1,456 +1,64 @@
import React, { Component, useState, useEffect } from "react"; import React, { Component } from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { RemoteService } from "../../remote"; import { RemoteService } from '../../remote';
import "./Automations.css"; import './Automations.css';
import { import {
Segment, Segment,
Grid, Grid,
Icon,
Header, Header,
Input,
Button, Button,
List, List,
Dropdown,
Form,
Divider, Divider,
Checkbox,
Menu, Menu,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import CreateAutomation, { operands } from './AutomationCreationModal';
const operands = [ const Automation = ({
{ key: "EQUAL", text: "=", value: "EQUAL" }, automation, devices, scenes, removeAutomation,
{ }) => {
key: "GREATER_EQUAL", const { triggers, conditions } = automation;
text: "\u2265",
value: "GREATER_EQUAL",
},
{
key: "GREATER",
text: ">",
value: "GREATER",
},
{
key: "LESS_EQUAL",
text: "\u2264",
value: "LESS_EQUAL",
},
{
key: "LESS",
text: "<",
value: "LESS",
},
];
const deviceStateOptions = [
{ key: "off", text: "off", value: false },
{ key: "on", text: "on", value: true },
];
const CreateTrigger = (props) => {
const [activeOperand, setActiveOperand] = useState(true);
const admitedDevices = ["sensor", "regularLight", "dimmableLight"]; // TODO Complete this list
const deviceList = props.devices
.map((device) => {
return {
key: device.id,
text: device.name,
value: device.id,
kind: device.kind,
};
})
.filter((e) => admitedDevices.includes(e.kind));
const onChange = (e, val) => {
props.inputChange(val);
if (
props.devices.filter((d) => d.id === val.value)[0].hasOwnProperty("on")
) {
setActiveOperand(false);
} else {
setActiveOperand(true);
}
};
return (
<List.Item>
<List.Content>
<Form>
<Form.Group>
<Form.Field inline width={7}>
<Dropdown
onChange={onChange}
name="device"
search
selection
options={deviceList}
placeholder="Device"
/>
</Form.Field>
{activeOperand ? (
<React.Fragment>
<Form.Field inline width={2}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
name="operand"
compact
selection
options={operands}
/>
</Form.Field>
<Form.Field inline width={7}>
<Input
onChange={(e, val) => props.inputChange(val)}
name="value"
type="number"
placeholder="Value"
/>
</Form.Field>
</React.Fragment>
) : (
<Form.Field inline width={7}>
<Dropdown
onChange={(e, val) => props.inputChange(val)}
placeholder="State"
name="value"
compact
selection
options={deviceStateOptions}
/>
</Form.Field>
)}
</Form.Group>
</Form>
</List.Content>
</List.Item>
);
};
const SceneItem = (props) => {
let position = props.order.indexOf(props.scene.id);
return (
<List.Item>
<List.Header>
<Grid textAlign="center">
<Grid.Row>
<Grid.Column width={4}>
<Checkbox
toggle
onChange={(e, val) =>
props.orderScenes(props.scene.id, val.checked)
}
checked={position + 1 > 0}
/>
</Grid.Column>
<Grid.Column width={8}>
<h3>{props.scene.name}</h3>
</Grid.Column>
<Grid.Column width={4}>
<h3>{position !== -1 ? "# " + (position + 1) : ""}</h3>
</Grid.Column>
</Grid.Row>
</Grid>
</List.Header>
</List.Item>
);
};
const Trigger = ({ deviceName, trigger, onRemove, index }) => {
const { operand, value } = trigger;
let symbol;
if (operand) {
symbol = operands.filter((opt) => opt.key === operand)[0].text;
}
return (
<List.Item className="trigger-item">
<Menu compact>
<Menu.Item as="span">{deviceName}</Menu.Item>
{operand ? <Menu.Item as="span">{symbol}</Menu.Item> : ""}
<Menu.Item as="span">
{operand ? value : value ? "on" : "off"}
</Menu.Item>
</Menu>
<Icon
as={"i"}
onClick={() => onRemove(index)}
className="remove-icon"
name="remove"
/>
</List.Item>
);
};
export const CreateAutomation = (props) => {
const [triggerList, setTrigger] = useState([]);
const [order, setOrder] = useState([]);
const [stateScenes, setScenes] = useState(props.scenes);
const [automationName, setautomationName] = useState("New Automation");
const [editName, setEditName] = useState(false);
const [newTrigger, setNewTrigger] = useState({});
useEffect(() => {
setScenes(props.scenes);
}, [props]);
const _checkNewTrigger = (trigger) => {
const auxDevice = props.devices.filter((d) => d.id === trigger.device)[0];
if (auxDevice && auxDevice.hasOwnProperty("on")) {
if (!trigger.device || !trigger.value == null) {
return {
result: false,
message: "There are missing fields!",
};
}
} else {
if (!trigger.device || !trigger.operand || !trigger.value) {
return {
result: false,
message: "There are missing fields",
};
}
}
const result = !triggerList.some(
(t) => t.device === trigger.device && t.operand === trigger.operand
);
return {
result: result,
message: result
? ""
: "You have already created a trigger for this device with the same conditions",
};
};
const addTrigger = () => {
const { result, message } = _checkNewTrigger(newTrigger);
const auxTrigger = newTrigger;
if (result) {
if (
props.devices
.filter((d) => d.id === newTrigger.device)[0]
.hasOwnProperty("on")
) {
delete auxTrigger.operand;
}
setTrigger((prevList) => [...prevList, auxTrigger]);
} else {
alert(message);
}
};
const removeTrigger = (index) => {
setTrigger((prevList) => prevList.filter((t, i) => i !== index));
};
// This gets triggered when the devices dropdown changes the value.
const onInputChange = (val) => {
setNewTrigger({ ...newTrigger, [val.name]: val.value });
};
const onChangeName = (e, val) => setautomationName(val.value);
const orderScenes = (id, checked) => {
if (checked) {
setOrder((prevList) => [...prevList, id]);
} else {
setOrder((prevList) => prevList.filter((e) => e !== id));
}
};
const searchScenes = (e, { value }) => {
if (value.length > 0) {
setScenes((prevScenes) => {
return stateScenes.filter((e) => {
return e.name.includes(value);
});
});
} else {
setScenes(props.scenes);
}
};
const _generateKey = (trigger) => {
if (trigger.hasOwnProperty("operand")) {
return trigger.device + trigger.operand + trigger.value;
}
return trigger.device + trigger.value;
};
/*const checkBeforeSave = () => {
if (automationName.length <= 0) {
alert("Give a name to the automation");
return false;
}
if (triggerList.length <= 0) {
alert("You have to create a trigger");
return false;
}
if (order.length <= 0) {
alert("You need at least one active scene");
return false;
}
return true;
};*/
const saveAutomation = () => {
//if(checkBeforeSave()){
const automation = {
name: automationName,
};
props.save({ automation, triggerList, order });
//}
};
return (
<React.Fragment>
<Header style={{ display: "inline", marginRight: "1rem" }}>
{editName ? (
<Input
focus
transparent
placeholder="New automation name..."
onChange={onChangeName}
/>
) : (
automationName
)}
</Header>
<Button
onClick={() => setEditName((prev) => !prev)}
style={{ display: "inline" }}
circular
size="small"
icon={editName ? "save" : "edit"}
/>
<Segment placeholder className="segment-automations">
<Grid columns={2} stackable textAlign="center">
<Divider vertical />
<Grid.Row verticalAlign="middle">
<Grid.Column>
<Header>Create Triggers</Header>
<List divided relaxed>
{triggerList.length > 0 &&
triggerList.map((trigger, i) => {
const deviceName = props.devices.filter(
(d) => d.id === trigger.device
)[0].name;
const key = _generateKey(trigger);
return (
<Trigger
key={key}
index={i}
deviceName={deviceName}
trigger={trigger}
onRemove={removeTrigger}
/>
);
})}
<CreateTrigger
devices={props.devices}
inputChange={onInputChange}
/>
</List>
<Button
onClick={addTrigger}
circular
icon="add"
color="blue"
size="huge"
/>
</Grid.Column>
<Grid.Column>
{props.scenes.length > 0 ? (
<React.Fragment>
<Header>Activate Scenes</Header>
<Input
icon="search"
placeholder="Search..."
fluid
onChange={searchScenes}
/>
<Divider horizontal />
<List divided relaxed>
{stateScenes.map((scene) => (
<SceneItem
key={scene.id}
scene={scene}
order={order}
orderScenes={orderScenes}
/>
))}
</List>
</React.Fragment>
) : (
<React.Fragment>
<Header icon>
<Icon name="world" />
</Header>
<Button primary>Create Scene</Button>
</React.Fragment>
)}
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Grid>
<Grid.Row>
<Grid.Column style={{ marginRight: "1rem" }}>
<Button onClick={() => saveAutomation()} color="green">
SAVE
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</React.Fragment>
);
};
const Automation = ({ automation, devices, scenes, removeAutomation }) => {
const { triggers } = automation;
const scenePriorities = automation.scenes; const scenePriorities = automation.scenes;
const getOperator = (operand) => const getOperator = (operand) => operands.filter((o) => o.key === operand)[0].text;
operands.filter((o) => o.key === operand)[0].text;
return ( return (
<React.Fragment> <>
<Header style={{ display: "inline", marginRight: "1rem" }}> <Header style={{ display: 'inline', marginRight: '1rem' }}>
{automation.name} {automation.name}
</Header> </Header>
<CreateAutomation id={automation.id} />
<Button <Button
style={{ display: "inline" }} style={{ display: 'inline' }}
circular
size="small"
icon={"edit"}
/>
<Button
style={{ display: "inline" }}
circular circular
onClick={() => removeAutomation(automation.id)} onClick={() => removeAutomation(automation.id)}
color="red" color="red"
size="small" size="small"
icon={"trash alternate outline"} icon="trash alternate outline"
/> />
<Segment placeholder> <Segment placeholder>
<Grid columns={2} stackable textAlign="center"> <Grid columns={2} stackable textAlign="center">
<Divider vertical></Divider> <Divider vertical />
<Grid.Row verticalAlign="middle"> <Grid.Row verticalAlign="middle">
<Grid.Column> <Grid.Column>
<Header>Triggers</Header> <Header>Triggers</Header>
<List divided relaxed> <List divided relaxed>
{triggers !== undefined && {triggers !== undefined
triggers.map((trigger) => { && triggers.map((trigger) => {
const device = devices.filter( const device = devices.filter(
(d) => d.id === trigger.deviceId (d) => d.id === trigger.deviceId,
)[0]; )[0];
return ( return (
<Menu key={trigger.id} compact> <Menu key={trigger.id} compact>
<Menu.Item as="span"> <Menu.Item as="span">
{device.name}{" "} {device.name}
{' '}
{trigger.operator {trigger.operator
? getOperator(trigger.operator) + ? `${getOperator(trigger.operator)
" " + } ${
trigger.range trigger.range}`
: trigger.value : trigger.on
? " - on" ? ' - on'
: " - off"} : ' - off'}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
@ -460,11 +68,12 @@ const Automation = ({ automation, devices, scenes, removeAutomation }) => {
<Grid.Column> <Grid.Column>
<Header>Scenes</Header> <Header>Scenes</Header>
<List divided relaxed> <List divided relaxed>
{scenePriorities !== undefined && {scenePriorities !== undefined
scenePriorities.map((sp) => { && scenePriorities.map((sp) => {
const sceneData = scenes.filter( const sceneData = scenes.filter(
(s) => s.id === sp.sceneId (s) => s.id === sp.sceneId,
)[0]; )[0];
if (!sceneData) return '';
return ( return (
<Menu key={sceneData.id} compact> <Menu key={sceneData.id} compact>
<Menu.Item as="span">{sceneData.name}</Menu.Item> <Menu.Item as="span">{sceneData.name}</Menu.Item>
@ -476,7 +85,37 @@ const Automation = ({ automation, devices, scenes, removeAutomation }) => {
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Segment> </Segment>
</React.Fragment> <Grid columns={1} stackable textAlign="center">
<Grid.Row verticalAlign="middle">
<Grid.Column>
<Header>Conditions</Header>
<List divided relaxed>
{conditions !== undefined
&& conditions.map((condition) => {
const device = devices.filter(
(d) => d.id === condition.deviceId,
)[0];
return (
<Menu key={condition.id} compact>
<Menu.Item as="span">
{device.name}
{' '}
{condition.operator
? `${getOperator(condition.operator)
} ${
condition.mode ? condition.mode : condition.range}`
: condition.on
? ' - on'
: ' - off'}
</Menu.Item>
</Menu>
);
})}
</List>
</Grid.Column>
</Grid.Row>
</Grid>
</>
); );
}; };
@ -496,13 +135,13 @@ class AutomationsPanel extends Component {
getDevices() { getDevices() {
this.props this.props
.fetchDevices() .fetchDevices()
.catch((err) => console.error(`error fetching devices:`, err)); .catch((err) => console.error('error fetching devices:', err));
} }
getAutomations() { getAutomations() {
this.props this.props
.fetchAutomations() .fetchAutomations()
.catch((err) => console.error(`error fetching automations:`, err)); .catch((err) => console.error('error fetching automations:', err));
} }
removeAutomation = (id) => { removeAutomation = (id) => {
@ -513,16 +152,12 @@ class AutomationsPanel extends Component {
render() { render() {
return ( return (
<Grid style={{ marginTop: "3rem" }}> <Grid style={{ marginTop: '3rem' }}>
<Grid.Row> <Grid.Row>
<Grid.Column textAlign="center" width={16}> <Grid.Column textAlign="center" width={16}>
{!this.state.openModal ? ( {!this.state.openModal ? (
<List> <List>
<CreateAutomation <CreateAutomation />
save={this.props.saveAutomation}
scenes={this.props.scenes}
devices={this.props.devices}
/>
</List> </List>
) : ( ) : (
<Button color="green">CREATE AUTOMATION</Button> <Button color="green">CREATE AUTOMATION</Button>
@ -530,10 +165,8 @@ class AutomationsPanel extends Component {
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
<Grid.Row> <Grid.Row>
{this.props.automations.map((automation, i) => { {this.props.automations.map((automation, i) => (
console.log(23, automation, i, this.props.automations); <Grid.Column key={i} width={8} style={{ margin: '2rem 0' }}>
return (
<Grid.Column key={i} width={8} style={{ margin: "2rem 0" }}>
<Automation <Automation
removeAutomation={this.removeAutomation} removeAutomation={this.removeAutomation}
scenes={this.props.scenes} scenes={this.props.scenes}
@ -541,8 +174,7 @@ class AutomationsPanel extends Component {
automation={automation} automation={automation}
/> />
</Grid.Column> </Grid.Column>
); ))}
})}
</Grid.Row> </Grid.Row>
</Grid> </Grid>
); );
@ -559,12 +191,11 @@ const mapStateToProps = (state, _) => ({
return Object.values(state.devices); return Object.values(state.devices);
}, },
get automations() { get automations() {
console.log(state.automations);
return Object.values(state.automations); return Object.values(state.automations);
}, },
}); });
const AutomationsPanelContainer = connect( const AutomationsPanelContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(AutomationsPanel); )(AutomationsPanel);
export default AutomationsPanelContainer; export default AutomationsPanelContainer;

View File

@ -1,11 +1,13 @@
// vim: set ts=2 sw=2 et tw=80: // vim: set ts=2 sw=2 et tw=80:
import React, { Component } from "react"; import React, { Component } from 'react';
import { Grid } from "semantic-ui-react"; import {
import Device from "./devices/Device"; Segment, Card, Header, Icon,
import NewDevice from "./devices/NewDevice"; } from 'semantic-ui-react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { RemoteService } from "../../remote"; import Device from './devices/Device';
import NewDevice from './devices/NewDevice';
import { RemoteService } from '../../remote';
class DevicePanel extends Component { class DevicePanel extends Component {
constructor(props) { constructor(props) {
@ -15,29 +17,40 @@ class DevicePanel extends Component {
} }
getDevices() { getDevices() {
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.fetchDevices() .fetchDevices()
.catch((err) => console.error(`error fetching devices:`, err)); .catch((err) => console.error('error fetching devices:', err));
} }
} }
render() { render() {
return ( return (
<Grid doubling columns={2} divided="vertically"> <Card.Group centered style={{ paddingTop: '3rem' }}>
{this.props.devices.map((e, i) => { {this.props.numbeOfRooms > 0 ? (
return ( <>
<Grid.Column key={i}> {this.props.devices.map((e, i) => <Device key={i} tab={this.props.tab} id={e.id} />)}
<Device tab={this.props.tab} id={e.id} />
</Grid.Column>
);
})}
{!this.props.isActiveRoomHome ? ( {!this.props.isActiveRoomHome ? (
<Grid.Column> <Card style={{ height: '27em' }}>
<Segment basic style={{ width: '100%', height: '100%' }}>
<NewDevice /> <NewDevice />
</Grid.Column> </Segment>
</Card>
) : null} ) : null}
</Grid> </>
) : (
<Segment placeholder>
<Header icon>
<Icon
name="exclamation triangle"
style={{ paddingBottom: '1rem' }}
/>
Please create a room on the left, and then add devices to the
same.
</Header>
</Segment>
)}
</Card.Group>
); );
} }
} }
@ -46,20 +59,22 @@ const mapStateToProps = (state, _) => ({
get devices() { get devices() {
if (state.active.activeRoom === -1) { if (state.active.activeRoom === -1) {
return Object.values(state.devices); return Object.values(state.devices);
} else { }
const deviceArray = [ const deviceArray = [
...state.rooms[state.active.activeRoom].devices, ...state.rooms[state.active.activeRoom].devices,
].sort(); ].sort();
return deviceArray.map((id) => state.devices[id]); return deviceArray.map((id) => state.devices[id]);
}
}, },
get isActiveRoomHome() { get isActiveRoomHome() {
return state.active.activeRoom === -1; return state.active.activeRoom === -1;
}, },
activeRoom: state.active.activeRoom, activeRoom: state.active.activeRoom,
get numbeOfRooms() {
return Object.keys(state.rooms).length;
},
}); });
const DevicePanelContainer = connect( const DevicePanelContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(DevicePanel); )(DevicePanel);
export default DevicePanelContainer; export default DevicePanelContainer;

View File

@ -0,0 +1,96 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Card, Segment, Header, Icon, Button,
} from 'semantic-ui-react';
import { RemoteService } from '../../remote';
import Device from './devices/Device';
class HostsPanel extends Component {
componentDidUpdate(oldProps) {
if (
oldProps.activeHost !== this.props.activeHost
&& this.props.activeHost !== -1
) {
this.props.fetchDevices(null, this.props.activeHost).catch(console.error);
this.props.fetchAllRooms(this.props.activeHost).catch(console.error);
this.props.fetchAllScenes(this.props.activeHost).catch(console.error);
}
}
applyHostScene(id) {
this.props
.sceneApply(id, this.props.activeHost)
.then(() => console.log('SCCUESS'))
.catch((err) => console.error('sceneApply update error', err));
}
render() {
if (this.props.isActiveDefaultHost) {
return (
<Card.Group centered style={{ paddingTop: '3rem' }}>
<Segment placeholder>
<Header icon>
<Icon
name="exclamation triangle"
style={{ paddingBottom: '1rem' }}
/>
Please select a host to visit on the left.
</Header>
</Segment>
</Card.Group>
);
}
return (
<>
<Header style={{ textAlign: 'center', marginTop: '3rem' }} as="h3">
Scenes
</Header>
<Card.Group centered>
{this.props.hostScenes.map((scene) => (
<Card>
<Card.Header textAlign="center">
<Header style={{ margin: '1.5rem 0' }} as="h3">
{scene.name}
{' '}
<Icon name={scene.icon} />
</Header>
</Card.Header>
<Card.Content extras>
<div className="ui two buttons">
<Button onClick={() => this.applyHostScene(scene.id)}>
Apply
</Button>
</div>
</Card.Content>
</Card>
))}
</Card.Group>
<Header style={{ textAlign: 'center' }} as="h3">
Devices
</Header>
<Card.Group centered>
{this.props.hostDeviceIds.map((id) => (
<Device
key={id}
hostId={this.props.activeHost}
tab="Hosts"
id={id}
/>
))}
</Card.Group>
</>
);
}
}
const mapStateToProps = (state, _) => ({
isActiveDefaultHost: state.active.activeHost === -1,
activeHost: state.active.activeHost,
hostScenes: state.hostScenes[state.active.activeHost] || [],
hostDevices: state.hostDevices,
hostDeviceIds: Object.keys(state.hostDevices[state.active.activeHost] || {}),
});
const HostsPanelContainer = connect(mapStateToProps, RemoteService)(HostsPanel);
export default HostsPanelContainer;

View File

@ -1,9 +1,11 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Button, Modal, Icon, Image, Form, Dropdown } from "semantic-ui-react"; import {
import { connect } from "react-redux"; Button, Modal, Icon, Image, Form, Dropdown,
import { RemoteService } from "../../remote"; } from 'semantic-ui-react';
import styled from "styled-components"; import { connect } from 'react-redux';
//import { appActions } from "../../storeActions"; import styled from 'styled-components';
import { RemoteService } from '../../remote';
// import { appActions } from "../../storeActions";
const StyledDiv = styled.div` const StyledDiv = styled.div`
background-color: #505bda; background-color: #505bda;
@ -31,27 +33,61 @@ class NewSceneDevice extends Component {
this.state = { this.state = {
openModal: false, openModal: false,
sceneDevices: this.props.scene ? this.props.scene.sceneStates : {}, sceneDevices: this.props.scene ? this.props.scene.sceneStates : {},
deviceName: "", deviceName: '',
availableDevices: [],
}; };
this.getDevices(); this.getDevices();
// this.getSceneStates();
this.availableDevices();
// console.log(this.state);
this.setSceneState = this.setSceneState.bind(this); this.setSceneState = this.setSceneState.bind(this);
this.createState = this.createState.bind(this); this.createState = this.createState.bind(this);
this.availableDevices = this.availableDevices.bind(this);
} }
getDevices() { getDevices() {
this.props this.props
.fetchDevices() .fetchDevices()
.catch((err) => console.error(`error fetching devices:`, err)); .catch((err) => console.error('error fetching devices:', err));
} }
// getSceneStates() {
// this.props
// .fetchStates(this.props.activeScene)
// .catch((err) => console.error(`error fetching states`, err));
// }
handleOpen = () => { handleOpen = () => {
this.setState({ openModal: true }); this.setState({ openModal: true });
}; };
handleClose = () => { handleClose = () => {
this.setState({ openModal: false }); this.setState({ openModal: false });
}; };
availableDevices() {
const availableDevices = [];
this.props.devices.forEach((e) => {
if (
Object.values(this.props.sceneStates).filter((d) => e.id === d.deviceId)
.length < 1
) {
if (e.flowType === 'OUTPUT') {
availableDevices.push({
key: e.id,
text: e.name,
value: e.id,
});
}
} else {
// console.log("NOT FOUND", e);
}
});
this.setState({ availableDevices });
// return availableDevices;
}
resetState = () => { resetState = () => {
this.setState(this.baseState); this.setState(this.baseState);
this.handleClose(); this.handleClose();
@ -62,49 +98,46 @@ class NewSceneDevice extends Component {
} }
createState() { createState() {
for (let i = 0; i < this.state.devicesAttached.length; i++) {
const device = this.props.devices.filter( const device = this.props.devices.filter(
(e) => this.state.devicesAttached[0] === e.id (e) => this.state.devicesAttached[i] === e.id,
); );
let data = { const data = {
sceneId: this.props.activeScene, sceneId: this.props.activeScene,
id: device[0].id, id: device[0].id,
kind: device[0].kind, kind: device[0].kind,
}; };
this.props this.props
.saveState(data) .saveState(data)
.catch((err) => console.error("error in creating state", err)); .catch((err) => console.error('error in creating state', err));
}
this.resetState(); this.resetState();
} }
render() { render() {
const availableDevices = [];
this.props.devices.forEach((e) => {
if (!Object.keys(this.state.sceneDevices).find((d) => e.id === d)) {
if (e.flowType === "OUTPUT") {
availableDevices.push({
key: e.id,
text: e.name,
value: e.id,
});
}
}
});
return ( return (
<Modal <Modal
closeIcon closeIcon
open={this.state.openModal} open={this.state.openModal}
onClose={this.resetState} onClose={this.resetState}
trigger={ trigger={(
<StyledDiv onClick={this.handleOpen}> <StyledDiv
<Image src="/img/add.svg" style={{ filter: "invert()" }} /> onClick={this.handleOpen}
style={{
position: 'relative',
top: 'calc(50% - 5rem)',
left: 'calc(50% - 5rem)',
}}
>
<Image src="/img/add.svg" style={{ filter: 'invert()' }} />
</StyledDiv> </StyledDiv>
} )}
centered={true} centered
> >
<Modal.Header>Add a New Scene State</Modal.Header> <Modal.Header>Add a New Scene State</Modal.Header>
<Modal.Content> <Modal.Content>
<Form> <Form>
<Form.Field style={{ marginTop: "1rem" }}> <Form.Field style={{ marginTop: '1rem' }}>
<label>Select devices you want to attach: </label> <label>Select devices you want to attach: </label>
<Dropdown <Dropdown
name="scene devices" name="scene devices"
@ -112,7 +145,8 @@ class NewSceneDevice extends Component {
fluid fluid
multiple multiple
onChange={this.setSceneState} onChange={this.setSceneState}
options={availableDevices} onClick={() => this.availableDevices()}
options={this.state.availableDevices}
/> />
</Form.Field> </Form.Field>
</Form> </Form>
@ -135,10 +169,20 @@ class NewSceneDevice extends Component {
const mapStateToProps = (state, _) => ({ const mapStateToProps = (state, _) => ({
devices: Object.values(state.devices), devices: Object.values(state.devices),
get sceneStates() {
if (state.active.activeScene !== -1) {
const stateArray = [
...state.scenes[state.active.activeScene].sceneStates,
].sort();
console.log(state.scenes[state.active.activeScene]);
return stateArray.map((id) => state.sceneStates[id]);
}
return [];
},
activeScene: state.active.activeScene, activeScene: state.active.activeScene,
}); });
const NewSceneDeviceContainer = connect( const NewSceneDeviceContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(NewSceneDevice); )(NewSceneDevice);
export default NewSceneDeviceContainer; export default NewSceneDeviceContainer;

View File

@ -1,9 +1,11 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { RemoteService } from "../../remote"; import {
import Device from "./devices/Device"; Button, Card, Segment, Header, Icon,
import NewSceneDevice from "./NewSceneDevice"; } from 'semantic-ui-react';
import { Grid, Button } from "semantic-ui-react"; import { RemoteService } from '../../remote';
import Device from './devices/Device';
import NewSceneDevice from './NewSceneDevice';
class ScenesPanel extends Component { class ScenesPanel extends Component {
constructor(props) { constructor(props) {
@ -12,39 +14,50 @@ class ScenesPanel extends Component {
} }
applyScene() { applyScene() {
console.log(this.props.activeScene); this.props
this.props.sceneApply(this.props.activeScene).then(() => { .sceneApply(this.props.activeScene)
alert("Scene applied."); .then(() => {
}); alert('Scene applied.');
})
.catch(console.error);
} }
render() { render() {
return ( return (
<Grid doubling columns={2} divided="vertically"> <Card.Group centered style={{ paddingTop: '3rem' }}>
{!this.props.isActiveDefaultScene ? ( {!this.props.isActiveDefaultScene ? (
<Grid.Row centered> <Card style={{ height: '27em' }}>
<Button color="blue" onClick={this.applyScene}> <Card.Content>
<Card.Header textAlign="center">
<Header as="h3">Add devices - Apply Scene</Header>
</Card.Header>
<Segment basic style={{ width: '100%', height: '100%' }}>
<NewSceneDevice states={this.props.sceneStates} />
</Segment>
</Card.Content>
<Card.Content extra>
<div className="ui two buttons">
<Button color="green" onClick={this.applyScene}>
Apply Scene Apply Scene
</Button> </Button>
</Grid.Row> </div>
) : null} </Card.Content>
{!this.props.isActiveDefaultScene </Card>
? this.props.sceneStates.map((e, i) => {
return (
<Grid.Column key={i}>
<Device tab={this.props.tab} id={e.id} />
</Grid.Column>
);
})
: null}
{!this.props.isActiveDefaultScene ? (
<Grid.Column>
<NewSceneDevice />
</Grid.Column>
) : ( ) : (
<Grid.Column>Welcome to the Scene View, you add a Scene</Grid.Column> <Segment placeholder>
<Header icon>
<Icon
name="exclamation triangle"
style={{ paddingBottom: '1rem' }}
/>
Please select a scene on the left or add a new one.
</Header>
</Segment>
)} )}
</Grid> {!this.props.isActiveDefaultScene
? this.props.sceneStates.map((e, i) => <Device key={i} tab={this.props.tab} id={e.id} />)
: null}
</Card.Group>
); );
} }
} }
@ -55,11 +68,10 @@ const mapStateToProps = (state, _) => ({
const stateArray = [ const stateArray = [
...state.scenes[state.active.activeScene].sceneStates, ...state.scenes[state.active.activeScene].sceneStates,
].sort(); ].sort();
console.log("STATESCENE", stateArray); console.log(state.scenes[state.active.activeScene]);
return stateArray.map((id) => state.sceneStates[id]); return stateArray.map((id) => state.sceneStates[id]);
} else {
return [];
} }
return [];
}, },
get isActiveDefaultScene() { get isActiveDefaultScene() {
return state.active.activeScene === -1; return state.active.activeScene === -1;
@ -68,6 +80,6 @@ const mapStateToProps = (state, _) => ({
}); });
const ScenesPanelContainer = connect( const ScenesPanelContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(ScenesPanel); )(ScenesPanel);
export default ScenesPanelContainer; export default ScenesPanelContainer;

View File

@ -1,7 +1,8 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import "./Curtains.css"; import './Curtains.css';
import { RemoteService } from "../../../remote"; import { connect } from 'react-redux';
import { connect } from "react-redux"; import { RemoteService } from '../../../remote';
import mapStateToProps from '../../../deviceProps';
class Curtain extends Component { class Curtain extends Component {
constructor(props) { constructor(props) {
@ -14,7 +15,7 @@ class Curtain extends Component {
this.setIntensity = this.setIntensity.bind(this); this.setIntensity = this.setIntensity.bind(this);
} }
//getters // getters
get turnedOn() { get turnedOn() {
return this.props.stateOrDevice.on; return this.props.stateOrDevice.on;
} }
@ -25,14 +26,14 @@ class Curtain extends Component {
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("curtains update error", err)); .catch((err) => console.error('curtains update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.stateOrDevice.id, on: on }, { id: this.props.stateOrDevice.id, on },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
}; };
@ -58,14 +59,14 @@ class Curtain extends Component {
saveIntensity = () => { saveIntensity = () => {
const intensity = Math.round(this.state.intensity); const intensity = Math.round(this.state.intensity);
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, intensity }) .saveDevice({ ...this.props.stateOrDevice, intensity })
.catch((err) => console.error("curtain update error", err)); .catch((err) => console.error('curtain update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.stateOrDevice.id, intensity: intensity }, { id: this.props.stateOrDevice.id, intensity },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
}; };
@ -80,7 +81,7 @@ class Curtain extends Component {
} }
}; };
///*this took me way too much more time than it should have*/ // /*this took me way too much more time than it should have*/
handleChange = (a) => { handleChange = (a) => {
this.setIntensity(a.target.value / 100); this.setIntensity(a.target.value / 100);
@ -93,13 +94,16 @@ class Curtain extends Component {
<div <div
className="open-container" className="open-container"
style={{ style={{
height: (9 * this.props.stateOrDevice.intensity) / 100 + "rem", height: `${(9 * this.props.stateOrDevice.intensity) / 100}rem`,
}} }}
></div>{" "} />
{' '}
<span className="span-open"> <span className="span-open">
{Math.round(this.props.stateOrDevice.intensity)}% {Math.round(this.props.stateOrDevice.intensity)}
%
</span> </span>
<input <input
disabled={this.props.disabled}
onChange={this.handleChange} onChange={this.handleChange}
value={this.props.stateOrDevice.intensity} value={this.props.stateOrDevice.intensity}
className="slider" className="slider"
@ -112,15 +116,5 @@ class Curtain extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.sceneStates[ownProps.id];
}
},
//device: state.devices[ownProps.id],
});
const CurtainContainer = connect(mapStateToProps, RemoteService)(Curtain); const CurtainContainer = connect(mapStateToProps, RemoteService)(Curtain);
export default CurtainContainer; export default CurtainContainer;

View File

@ -58,7 +58,7 @@
.span-open { .span-open {
-webkit-user-select: none; -webkit-user-select: none;
font-family: "Lato"; font-family: "Lato", Sans-serif;
font-weight: bold; font-weight: bold;
font-size: 3rem; font-size: 3rem;
text-emphasis: none; text-emphasis: none;

View File

@ -1,16 +1,26 @@
import React from "react"; import React from 'react';
import Light from "./Light"; import {
import SmartPlug from "./SmartPlug"; Header, Button, Icon, Card,
import Sensor from "./Sensor"; } from 'semantic-ui-react';
import { ButtonDimmer, KnobDimmer } from "./Dimmer"; import { connect } from 'react-redux';
import Switcher from "./Switch"; import Light from './Light';
import Videocam from "./Videocam"; import SmartPlug from './SmartPlug';
import Curtains from "./Curtain"; import Sensor from './Sensor';
import Thermostat from "./Thermostats"; import { ButtonDimmer, KnobDimmer } from './Dimmer';
import { Segment, Grid, Header, Button, Icon } from "semantic-ui-react"; import Switcher from './Switch';
import { RemoteService } from "../../../remote"; import Videocam from './Videocam';
import { connect } from "react-redux"; import Curtains from './Curtain';
import DeviceSettingsModal from "./DeviceSettingsModal"; import Thermostat from './Thermostats';
import { RemoteService } from '../../../remote';
import DeviceSettingsModal from './DeviceSettingsModal';
import mapStateToProps from '../../../deviceProps';
const centerComponent = {
marginLeft: '50%',
transform: 'translateX(-50%)',
marginTop: '10%',
marginBottom: '10%',
};
class Device extends React.Component { class Device extends React.Component {
constructor(props) { constructor(props) {
@ -23,109 +33,58 @@ class Device extends React.Component {
} }
edit() { edit() {
console.log("editing device with id=" + this.props.id); console.log(`editing device with id=${this.props.id}`);
this.modalRef.current.openModal(); this.modalRef.current.openModal();
} }
resetSmartPlug() { resetSmartPlug() {
this.props this.props
.smartPlugReset(this.props.id) .smartPlugReset(this.props.id)
.catch((err) => console.error(`Smart plug reset error`, err)); .catch((err) => console.error('Smart plug reset error', err));
} }
deleteState() { deleteState() {
//console.log("alpaca "+this.props); this.props.deleteState(this.props.id, this.props.stateOrDevice.kind);
this.props.deleteState(this.props.id, this.props.type);
} }
renderDeviceComponent() { renderDeviceComponent() {
switch ( const mapKindToComponent = {
this.props.tab === "Devices" curtains: Curtains,
? this.props.stateOrDevice.kind thermostat: Thermostat,
: this.props.type regularLight: Light,
) { sensor: Sensor,
case "curtains": motionSensor: Sensor,
return ( buttonDimmer: ButtonDimmer,
<Curtains knobDimmer: KnobDimmer,
tab={this.props.tab} smartPlug: SmartPlug,
sceneState={this.props.sceneState} switch: Switcher,
id={this.props.id} dimmableLight: Light,
/> securityCamera: Videocam,
); };
case "thermostat":
return ( if (!(this.props.type in mapKindToComponent)) {
<Thermostat throw new Error(`device kind ${this.props.type} not known`);
tab={this.props.tab}
sceneState={this.props.sceneStat}
id={this.props.stateOrDevice.id}
/>
);
case "regularLight":
return (
<Light
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
);
case "sensor":
return (
<Sensor
tab={this.props.tab}
sceneState={this.props.sceneState}
id={this.props.stateOrDevice.id}
/>
);
case "motionSensor":
return <Sensor tab={this.props.tab} id={this.props.stateOrDevice.id} />;
case "buttonDimmer":
return (
<ButtonDimmer tab={this.props.tab} id={this.props.stateOrDevice.id} />
);
case "knobDimmer":
return (
<KnobDimmer tab={this.props.tab} id={this.props.stateOrDevice.id} />
);
case "smartPlug":
return (
<SmartPlug tab={this.props.tab} id={this.props.stateOrDevice.id} />
);
case "switch":
return (
<Switcher tab={this.props.tab} id={this.props.stateOrDevice.id} />
);
case "dimmableLight":
return <Light id={this.props.stateOrDevice.id} tab={this.props.tab} />;
case "securityCamera":
return (
<Videocam id={this.props.stateOrDevice.id} tab={this.props.tab} />
);
default:
//throw new Error("Device type unknown");
return undefined;
}
} }
render() { return React.createElement(
mapKindToComponent[this.props.type],
{ {
if (this.props.type !== "") { tab: this.props.tab,
id: this.props.id,
hostId: this.props.hostId,
},
'',
);
}
deviceDescription() {
return ( return (
<Segment> <div className="ui two buttons">
<Grid columns={2}> <Button color="blue" icon onClick={this.edit} labelPosition="left">
<Grid.Column>{this.renderDeviceComponent()}</Grid.Column>
{this.props.tab === "Devices" ? (
<Grid.Column textAlign="center">
<Header as="h3">{this.props.stateOrDevice.name}</Header>
<Button
color="blue"
icon
onClick={this.edit}
labelPosition="left"
>
<Icon name="pencil" /> <Icon name="pencil" />
Edit Edit
</Button> </Button>
{this.props.stateOrDevice.kind === "smartPlug" ? ( {this.props.stateOrDevice.kind === 'smartPlug' ? (
<Button <Button
color="orange" color="orange"
icon icon
@ -136,91 +95,64 @@ class Device extends React.Component {
Reset Reset
</Button> </Button>
) : null} ) : null}
</Grid.Column> </div>
) : ( );
<Grid.Column textAlign="center"> }
<Header as="h3">{this.props.device.name}</Header>
{this.props.tab === "Scenes" ? ( stateDescription() {
<Header as="h4">{this.props.roomName}</Header> return (
) : ( <div className="ui two buttons">
""
)}
<Button <Button
color="red" color="red"
icon icon
onClick={this.deleteState} onClick={this.deleteState}
labelPosition="left" labelPosition="left"
> >
<Icon name="undo" /> <Icon name="trash alternate" />
Delete Delete
</Button> </Button>
</Grid.Column> </div>
)} );
</Grid> }
{this.props.stateOrDevice && this.props.tab === "Devices" ? (
get deviceName() {
return this.props.device.name;
}
render() {
return (
<Card style={{ height: '27em' }}>
<Card.Content>
<Card.Header textAlign="center">
<Header as="h3">{this.deviceName}</Header>
<Header as="h4" style={{ marginTop: '.5rem' }}>
{this.props.roomName}
</Header>
</Card.Header>
<Card.Description
style={
this.props.device.kind !== 'curtains' ? centerComponent : null
}
>
{this.renderDeviceComponent()}
</Card.Description>
</Card.Content>
<Card.Content extra>
{this.props.tab === 'Devices'
? this.deviceDescription()
: this.props.tab === 'Scenes' && this.stateDescription()}
</Card.Content>
{this.props.tab === 'Devices' ? (
<DeviceSettingsModal <DeviceSettingsModal
ref={this.modalRef} ref={this.modalRef}
id={this.props.stateOrDevice.id} id={this.props.stateOrDevice.id}
/> />
) : ( ) : null}
"" </Card>
)}
</Segment>
); );
} else {
return null;
}
}
} }
} }
/*
{this.props.stateOrDevice ?
<DeviceSettingsModal
ref={this.modalRef}
id={this.props.stateOrDevice.id}
/> :
""
}
*/
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.sceneStates[ownProps.id];
}
},
get device() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.devices[state.sceneStates[ownProps.id].deviceId];
}
},
get roomName() {
if (state.active.activeTab === "Scenes") {
const device = state.devices[state.sceneStates[ownProps.id].deviceId];
return state.rooms[device.roomId].name;
} else {
return "";
}
},
get type() {
console.log("ALPACA", state, ownProps);
if (state.active.activeTab === "Scenes") {
if (state.sceneStates[ownProps.id]) {
//console.log(state.sceneStates[ownProps.id], ownProps.id);
const id = state.sceneStates[ownProps.id].deviceId;
//console.log(id, state.devices[id].kind);
return state.devices[id].kind;
} else {
return "";
}
} else {
return null;
}
},
});
const DeviceContainer = connect(mapStateToProps, RemoteService)(Device); const DeviceContainer = connect(mapStateToProps, RemoteService)(Device);
export default DeviceContainer; export default DeviceContainer;

View File

@ -1,25 +1,31 @@
import React, { Component, useState } from "react"; import React, { Component, useState } from 'react';
import { Button, Form, Icon, Header, Modal, Input } from "semantic-ui-react"; import {
import { connect } from "react-redux"; Button, Form, Icon, Header, Modal, Input,
import { RemoteService } from "../../../remote"; } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { RemoteService } from '../../../remote';
const DeleteModal = (props) => ( const DeleteModal = (props) => (
<Modal <Modal
trigger={ trigger={(
<Button icon labelPosition="left" inverted color="red"> <Button icon labelPosition="left" inverted color="red">
<Icon name="trash alternate" /> <Icon name="trash alternate" />
Delete device Delete device
</Button> </Button>
} )}
closeIcon closeIcon
> >
<Header icon="archive" content="Are you sure ?" /> <Header icon="archive" content="Are you sure ?" />
<Modal.Actions> <Modal.Actions>
<Button color="red"> <Button color="red">
<Icon name="remove" /> No <Icon name="remove" />
{' '}
No
</Button> </Button>
<Button onClick={() => props.removeDevice()} color="green"> <Button onClick={() => props.removeDevice()} color="green">
<Icon name="checkmark" /> Yes <Icon name="checkmark" />
{' '}
Yes
</Button> </Button>
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>
@ -31,7 +37,7 @@ const SettingsForm = (props) => {
setValues({ ...values, [name]: value }); setValues({ ...values, [name]: value });
}; };
const [values, setValues] = useState({ name: "" }); const [values, setValues] = useState({ name: '' });
return ( return (
<Form> <Form>
@ -42,10 +48,8 @@ const SettingsForm = (props) => {
name="name" name="name"
onChange={handleInputChange} onChange={handleInputChange}
placeholder={props.name} placeholder={props.name}
// {this.props.device.name}
/> />
</Form.Field> </Form.Field>
<Form.Field> <Form.Field>
<DeleteModal removeDevice={() => props.removeDevice(values)} /> <DeleteModal removeDevice={() => props.removeDevice(values)} />
</Form.Field> </Form.Field>
@ -71,6 +75,7 @@ class DeviceSettingsModal extends Component {
this.updateDevice = this.updateDevice.bind(this); this.updateDevice = this.updateDevice.bind(this);
this.deleteDevice = this.deleteDevice.bind(this); this.deleteDevice = this.deleteDevice.bind(this);
// this.useExternalTempSensor = this.useExternalTempSensor.bind(this);
} }
closeModal = (e) => { closeModal = (e) => {
@ -82,44 +87,68 @@ class DeviceSettingsModal extends Component {
}; };
updateDevice(values) { updateDevice(values) {
if (values.name.length === 0) return; console.log(values, this.external);
let { name } = values;
if (values.name.length === 0) {
name = this.props.device.name;
}
const data = {
...this.props.device,
name,
};
if (this.props.device.kind === 'thermostat') {
const external = values.external
? values.external
: this.props.device.useExternalSensors;
console.log(external);
data.useExternalSensors = external;
}
console.log(data.useExternalSensors);
this.props this.props
.saveDevice({ ...this.props.device, name: values.name }) .saveDevice(data)
.then(() => this.setState({ openModal: false })) .then(() => this.setState({ openModal: false }))
.catch((err) => .catch((err) => console.error(
console.error(
`settings modal for device ${this.props.id} deletion error`, `settings modal for device ${this.props.id} deletion error`,
err err,
) ));
);
} }
deleteDevice() { deleteDevice() {
this.props this.props
.deleteDevice(this.props.device) .deleteDevice(this.props.device)
.then(() => this.setState({ open: false })) .then(() => this.setState({ openModal: false }))
.catch((err) => .catch((err) => console.error(
console.error(
`settings modal for device ${this.props.id} deletion error`, `settings modal for device ${this.props.id} deletion error`,
err err,
) ));
);
} }
render() { _editForm = null;
const SettingsModal = () => (
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}> get editForm() {
<Modal.Header>Settings of {this.props.device.name}</Modal.Header> this._editForm = this._editForm || (
<Modal.Content>
<SettingsForm <SettingsForm
name={this.state.name} name={this.state.name}
removeDevice={this.deleteDevice} removeDevice={this.deleteDevice}
saveFunction={this.updateDevice} saveFunction={this.updateDevice}
tempSensor={this.useExternalTempSensor}
/> />
</Modal.Content> );
return this._editForm;
}
render() {
return (
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Modal.Header>
Settings of
{this.props.device.name}
</Modal.Header>
<Modal.Content>{this.editForm}</Modal.Content>
</Modal> </Modal>
); );
return <SettingsModal />;
} }
} }
@ -130,6 +159,6 @@ const DeviceSettingsModalContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService, RemoteService,
null, null,
{ forwardRef: true } { forwardRef: true },
)(DeviceSettingsModal); )(DeviceSettingsModal);
export default DeviceSettingsModalContainer; export default DeviceSettingsModalContainer;

View File

@ -5,20 +5,21 @@
A dimmer without state can just increase or decrease the intensity of a light. <-- DefualtDimmer A dimmer without state can just increase or decrease the intensity of a light. <-- DefualtDimmer
The user can change the state of a dimmer through an intuitive UI in SmartHut . The user can change the state of a dimmer through an intuitive UI in SmartHut .
**/ * */
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
CircularInput, CircularInput,
CircularProgress, CircularProgress,
CircularThumb, CircularThumb,
} from "react-circular-input"; } from 'react-circular-input';
import { connect } from 'react-redux';
import { import {
ButtonDimmerContainer, ButtonDimmerContainer,
MinusPanel, MinusPanel,
PlusPanel, PlusPanel,
ThumbText, ThumbText,
} from "./styleComponents"; } from './styleComponents';
import { import {
CircularThumbStyle, CircularThumbStyle,
KnobDimmerStyle, KnobDimmerStyle,
@ -26,21 +27,21 @@ import {
textStyle, textStyle,
knobIcon, knobIcon,
knobContainer, knobContainer,
} from "./DimmerStyle"; } from './DimmerStyle';
import { RemoteService } from "../../../remote"; import { RemoteService } from '../../../remote';
import { connect } from "react-redux"; import mapStateToProps from '../../../deviceProps';
export class ButtonDimmerComponent extends Component { export class ButtonDimmerComponent extends Component {
increaseIntensity = () => { increaseIntensity = () => {
this.props this.props
.buttonDimmerDim(this.props.id, "UP") .buttonDimmerDim(this.props.id, 'UP')
.catch((err) => console.error("button dimmer increase error", err)); .catch((err) => console.error('button dimmer increase error', err));
}; };
decreaseIntensity = () => { decreaseIntensity = () => {
this.props this.props
.buttonDimmerDim(this.props.id, "DOWN") .buttonDimmerDim(this.props.id, 'DOWN')
.catch((err) => console.error("button dimmer decrease error", err)); .catch((err) => console.error('button dimmer decrease error', err));
}; };
render() { render() {
@ -95,7 +96,7 @@ export class KnobDimmerComponent extends Component {
const val = Math.round(this.state.intensity); const val = Math.round(this.state.intensity);
this.props this.props
.knobDimmerDimTo(this.props.id, val) .knobDimmerDimTo(this.props.id, val)
.catch((err) => console.error("knob dimmer set intensity error", err)); .catch((err) => console.error('knob dimmer set intensity error', err));
} }
render() { render() {
@ -103,8 +104,8 @@ export class KnobDimmerComponent extends Component {
<div style={knobContainer}> <div style={knobContainer}>
<CircularInput <CircularInput
style={KnobDimmerStyle} style={KnobDimmerStyle}
value={+(Math.round(this.state.intensity / 100 + "e+2") + "e-2")} value={+(`${Math.round(`${this.state.intensity / 100}e+2`)}e-2`)}
onChange={this.setIntensity} onChange={this.props.disabled ? null : this.setIntensity}
> >
<text <text
style={textStyle} style={textStyle}
@ -120,7 +121,7 @@ export class KnobDimmerComponent extends Component {
style={{ ...KnobProgress, opacity: this.state.intensity + 0.1 }} style={{ ...KnobProgress, opacity: this.state.intensity + 0.1 }}
/> />
<CircularThumb style={CircularThumbStyle} /> <CircularThumb style={CircularThumbStyle} />
<ThumbText color={"#1a2849"} /> <ThumbText color="#1a2849" />
</CircularInput> </CircularInput>
<img alt="Knob Icon" style={knobIcon} src="/img/knobDimmer.svg" /> <img alt="Knob Icon" style={knobIcon} src="/img/knobDimmer.svg" />
</div> </div>
@ -128,15 +129,6 @@ export class KnobDimmerComponent extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice(){
if(state.active.activeTab==="Devices"){
return state.devices[ownProps.id];
}else{
return state.sceneStates[ownProps.id];
}
},
});
const conn = connect(mapStateToProps, RemoteService); const conn = connect(mapStateToProps, RemoteService);
export const KnobDimmer = conn(KnobDimmerComponent); export const KnobDimmer = conn(KnobDimmerComponent);

View File

@ -1,70 +1,72 @@
export const KnobDimmerStyle = { export const KnobDimmerStyle = {
cursor: "pointer", cursor: 'pointer',
marginTop: "1rem", marginTop: '1rem',
width: "9rem", width: '9rem',
height: "9rem", height: '9rem',
fill: "#1a2849", fill: '#1a2849',
}; };
export const KnobHolder = { export const KnobHolder = {
marginTop: "1rem", marginTop: '1rem',
cursor: "pointer", cursor: 'pointer',
padding: "3rem", padding: '3rem',
backgroundColor: "white", backgroundColor: 'white',
width: "10rem", width: '10rem',
height: "10rem", height: '10rem',
}; };
export const KnobCircularTrack = { export const KnobCircularTrack = {
fill: "white", fill: 'white',
stroke: "#1a2849", stroke: '#1a2849',
}; };
export const KnobIcon = { export const KnobIcon = {
fill: "#1a2849", fill: '#1a2849',
}; };
export const KnobProgress = { export const KnobProgress = {
stroke: "#1a2849", stroke: '#1a2849',
strokeWidth: "3rem", strokeWidth: '3rem',
}; };
export const ValueStyle = { export const ValueStyle = {
pointerEvents: "none", pointerEvents: 'none',
fill: "#1a2849", fill: '#1a2849',
fontSize: "1.3rem", fontSize: '1.3rem',
fontFamily: "Lato", fontFamily: 'Lato',
textAnchor: "middle", textAnchor: 'middle',
userSelect: 'none',
}; };
export const CircularThumbStyle = { export const CircularThumbStyle = {
fill: "white", fill: 'white',
stroke: "#1a2849", stroke: '#1a2849',
strokeWidth: ".2rem", strokeWidth: '.2rem',
r: "1.4rem", r: '1.4rem',
}; };
export const textStyle = { export const textStyle = {
position: "absolute", position: 'absolute',
fill: "#1a2849", fill: '#1a2849',
fontSize: "1.5rem", fontSize: '1.5rem',
fontFamily: "Lato", fontFamily: 'Lato',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
userSelect: 'none',
}; };
export const knobIcon = { export const knobIcon = {
position: "absolute", position: 'absolute',
left: "50%", left: '50%',
top: "30%", top: '30%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
width: "2rem", width: '2rem',
height: "2rem", height: '2rem',
}; };
export const knobContainer = { export const knobContainer = {
position: "relative", position: 'relative',
width: "9rem", width: '9rem',
height: "9rem", height: '9rem',
}; };

View File

@ -7,19 +7,20 @@
* Lights have an internal state that can be changed and it must * Lights have an internal state that can be changed and it must
* be shown accordingly in the SmartHut views (house view and room views). * be shown accordingly in the SmartHut views (house view and room views).
*/ */
import React, { Component } from "react"; import React, { Component } from 'react';
import { Image } from 'semantic-ui-react';
import {
CircularInput,
CircularProgress,
CircularThumb,
} from 'react-circular-input';
import { connect } from 'react-redux';
import { import {
iconStyle, iconStyle,
StyledDiv, StyledDiv,
BottomPanel, BottomPanel,
ThumbText, ThumbText,
} from "./styleComponents"; } from './styleComponents';
import { Image } from "semantic-ui-react";
import {
CircularInput,
CircularProgress,
CircularThumb,
} from "react-circular-input";
import { import {
LightDimmerContainer, LightDimmerContainer,
LightDimmerStyle, LightDimmerStyle,
@ -28,9 +29,9 @@ import {
KnobProgress, KnobProgress,
CircularThumbStyle, CircularThumbStyle,
knobIcon, knobIcon,
} from "./LightStyle"; } from './LightStyle';
import { RemoteService } from "../../../remote"; import { RemoteService } from '../../../remote';
import { connect } from "react-redux"; import mapStateToProps from '../../../deviceProps';
class Light extends Component { class Light extends Component {
constructor(props) { constructor(props) {
@ -40,13 +41,13 @@ class Light extends Component {
timeout: null, timeout: null,
}; };
this.iconOn = "/img/lightOn.svg"; this.iconOn = '/img/lightOn.svg';
this.iconOff = "/img/lightOff.svg"; this.iconOff = '/img/lightOff.svg';
this.setIntensity = this.setIntensity.bind(this); this.setIntensity = this.setIntensity.bind(this);
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps) {
if ( if (
this.props.stateOrDevice.intensity !== prevProps.stateOrDevice.intensity this.props.stateOrDevice.intensity !== prevProps.stateOrDevice.intensity
) { ) {
@ -62,36 +63,35 @@ class Light extends Component {
} }
get intensity() { get intensity() {
return this.props.stateOrDevice.intensity || 0; return this.state.intensity || 0;
} }
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if (this.props.tab === "Devices") { if (this.props.tab !== 'Scenes') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice(
.catch((err) => console.error("regular light update error", err)); { ...this.props.stateOrDevice, on },
} else { this.props.tab === 'Hosts' ? this.props.activeHost : null,
if (this.props.device.kind === "regularLight") { )
.catch((err) => console.error('regular light update error', err));
} else if (this.props.device.kind === 'regularLight') {
this.props this.props
.updateState( .updateState(
{ {
id: this.props.stateOrDevice.id, id: this.props.stateOrDevice.id,
on: on, on,
sceneId: this.props.stateOrDevice.sceneId, sceneId: this.props.stateOrDevice.sceneId,
}, },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
) )
.then((res) => { .then((res) => {
console.log(res); console.log(res);
}); });
} }
}
}; };
getIcon = () => { getIcon = () => (this.turnedOn ? this.iconOn : this.iconOff);
return this.turnedOn ? this.iconOn : this.iconOff;
};
setIntensity(intensity) { setIntensity(intensity) {
intensity *= 100; intensity *= 100;
@ -114,16 +114,18 @@ class Light extends Component {
saveIntensity = () => { saveIntensity = () => {
const intensity = Math.round(this.state.intensity); const intensity = Math.round(this.state.intensity);
if (this.props.tab === "Devices") { if (this.props.tab !== 'Scenes') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, intensity }) .saveDevice(
.catch((err) => console.error("regular light update error", err)); { ...this.props.stateOrDevice, intensity },
this.props.tab === 'Hosts' ? this.props.activeHost : null,
)
.catch((err) => console.error('dimmable light update error', err));
} else { } else {
console.log("CIAOOOOOOOOO", this.props.stateOrDevice);
this.props this.props
.updateState( .updateState(
{ id: this.props.stateOrDevice.id, intensity: intensity }, { id: this.props.stateOrDevice.id, intensity },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
) )
.then((res) => { .then((res) => {
console.log(res, this.props.stateOrDevice.kind); console.log(res, this.props.stateOrDevice.kind);
@ -136,9 +138,9 @@ class Light extends Component {
<div style={LightDimmerContainer}> <div style={LightDimmerContainer}>
<CircularInput <CircularInput
style={LightDimmerStyle} style={LightDimmerStyle}
value={+(Math.round(this.state.intensity / 100 + "e+2") + "e-2")} value={+(`${Math.round(`${this.intensity / 100}e+2`)}e-2`)}
onChange={this.setIntensity} onChange={this.props.disabled ? null : this.setIntensity}
onMouseUp={this.saveIntensity} onMouseUp={this.props.disabled ? null : this.saveIntensity}
> >
<text <text
style={textStyle} style={textStyle}
@ -153,21 +155,21 @@ class Light extends Component {
<CircularProgress <CircularProgress
style={{ style={{
...KnobProgress, ...KnobProgress,
opacity: this.state.intensity / 100 + 0.3, opacity: this.intensity / 100 + 0.3,
}} }}
/> />
<CircularThumb style={CircularThumbStyle} /> <CircularThumb style={CircularThumbStyle} />
<ThumbText color={"#ffd31d"} /> <ThumbText color="#ffd31d" />
</CircularInput> </CircularInput>
<Image style={knobIcon} src="/img/intensityLightIcon.svg" /> <Image style={knobIcon} src="/img/intensityLightIcon.svg" />
</div> </div>
); );
const normalLightView = ( const normalLightView = (
<StyledDiv> <StyledDiv onClick={this.onClickDevice}>
<div onClick={this.onClickDevice}> <div>
<Image src={this.getIcon()} style={iconStyle} /> <Image src={this.getIcon()} style={iconStyle} />
<BottomPanel style={{ backgroundColor: "#ffa41b" }}> <BottomPanel style={{ backgroundColor: '#ffa41b' }}>
<h5 style={nameStyle}>Light</h5> <h5 style={nameStyle}>Light</h5>
</BottomPanel> </BottomPanel>
</div> </div>
@ -176,7 +178,7 @@ class Light extends Component {
return ( return (
<div> <div>
{this.props.device.kind === "dimmableLight" {this.props.device.kind === 'dimmableLight'
? intensityLightView ? intensityLightView
: normalLightView} : normalLightView}
</div> </div>
@ -184,22 +186,5 @@ class Light extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.sceneStates[ownProps.id];
}
},
get device() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.devices[state.sceneStates[ownProps.id].deviceId];
}
},
});
const LightContainer = connect(mapStateToProps, RemoteService)(Light); const LightContainer = connect(mapStateToProps, RemoteService)(Light);
export default LightContainer; export default LightContainer;

View File

@ -1,67 +1,69 @@
export const valueStyle = { export const valueStyle = {
fill: "#3e99ff", fill: '#3e99ff',
fontSize: "2.5rem", fontSize: '2.5rem',
fontFamily: "Lato", fontFamily: 'Lato',
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)", textShadow: '1px 1px 0.5px rgba(0, 0, 0, .2)',
}; };
export const intensityLightStyle = { export const intensityLightStyle = {
fill: "#ffd31d", fill: '#ffd31d',
fontSize: "1.2rem", fontSize: '1.2rem',
fontFamily: "Lato", fontFamily: 'Lato',
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)", textShadow: '1px 1px 0.5px rgba(0, 0, 0, .2)',
}; };
export const textStyle = { export const textStyle = {
position: "absolute", position: 'absolute',
fill: "#ffd31d", fill: '#ffd31d',
fontSize: "1.5rem", fontSize: '1.5rem',
fontFamily: "Lato", fontFamily: 'Lato',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
userSelect: 'none',
}; };
export const nameStyle = { export const nameStyle = {
fontSize: "1rem", fontSize: '1rem',
position: "absolute", position: 'absolute',
top: "30%", top: '30%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
color: "white", color: 'white',
userSelect: 'none',
}; };
export const LightDimmerStyle = { export const LightDimmerStyle = {
cursor: "pointer", cursor: 'pointer',
marginTop: "1rem", marginTop: '1rem',
width: "9rem", width: '9rem',
height: "9rem", height: '9rem',
fill: "#ffd31d", fill: '#ffd31d',
}; };
export const LightDimmerContainer = { export const LightDimmerContainer = {
position: "relative", position: 'relative',
width: "9rem", width: '9rem',
height: "9rem", height: '9rem',
}; };
export const CircularThumbStyle = { export const CircularThumbStyle = {
fill: "white", fill: 'white',
stroke: "#ffd31d", stroke: '#ffd31d',
strokeWidth: ".2rem", strokeWidth: '.2rem',
r: "1.4rem", r: '1.4rem',
}; };
export const KnobProgress = { export const KnobProgress = {
stroke: "#ffd31d", stroke: '#ffd31d',
strokeWidth: "3rem", strokeWidth: '3rem',
}; };
export const knobIcon = { export const knobIcon = {
position: "absolute", position: 'absolute',
left: "50%", left: '50%',
top: "30%", top: '30%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
width: "2rem", width: '2rem',
height: "2rem", height: '2rem',
}; };

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import styled from "styled-components"; import styled from 'styled-components';
import { import {
Button, Button,
Dropdown, Dropdown,
@ -8,9 +8,9 @@ import {
Image, Image,
Input, Input,
Modal, Modal,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { RemoteService } from "../../../remote"; import { connect } from 'react-redux';
import { connect } from "react-redux"; import { RemoteService } from '../../../remote';
const StyledDiv = styled.div` const StyledDiv = styled.div`
background-color: #505bda; background-color: #505bda;
@ -38,7 +38,7 @@ class NewDevice extends Component {
step: 1, step: 1,
openModal: false, openModal: false,
motion: false, motion: false,
deviceName: "", deviceName: '',
}; };
this.baseState = this.state; this.baseState = this.state;
this.createDevice = this.createDevice.bind(this); this.createDevice = this.createDevice.bind(this);
@ -47,6 +47,7 @@ class NewDevice extends Component {
handleOpen = () => { handleOpen = () => {
this.setState({ openModal: true }); this.setState({ openModal: true });
}; };
handleClose = () => { handleClose = () => {
this.setState({ openModal: false }); this.setState({ openModal: false });
}; };
@ -59,12 +60,13 @@ class NewDevice extends Component {
nextStep = () => { nextStep = () => {
this.setState((prevState) => ({ step: prevState.step + 1 })); this.setState((prevState) => ({ step: prevState.step + 1 }));
}; };
previousStep = () => { previousStep = () => {
this.setState((prevState) => ({ step: prevState.step - 1 })); this.setState((prevState) => ({ step: prevState.step - 1 }));
}; };
setTypeOfDevice = (e, d) => { setTypeOfDevice = (e, d) => {
if (d.value === "dimmableLight") { if (d.value === 'dimmableLight') {
this.setState({ typeOfDevice: d.value, intensity: 0 }); this.setState({ typeOfDevice: d.value, intensity: 0 });
} else { } else {
this.setState({ typeOfDevice: d.value }); this.setState({ typeOfDevice: d.value });
@ -77,7 +79,7 @@ class NewDevice extends Component {
setTypeOfSensor = (e, d) => { setTypeOfSensor = (e, d) => {
console.log(d.value); console.log(d.value);
if (d.value === "motionSensor") { if (d.value === 'motionSensor') {
this.setState({ typeOfSensor: d.value, motion: true }); this.setState({ typeOfSensor: d.value, motion: true });
} else { } else {
this.setState({ typeOfSensor: d.value }); this.setState({ typeOfSensor: d.value });
@ -94,155 +96,176 @@ class NewDevice extends Component {
id: null, id: null,
roomId: this.props.activeRoom, roomId: this.props.activeRoom,
name: this.state.deviceName, name: this.state.deviceName,
kind: this.state.motion ? "motionSensor" : this.state.typeOfDevice, kind: this.state.motion ? 'motionSensor' : this.state.typeOfDevice,
}; };
let outputs = null; let outputs = null;
const defaultNames = { const defaultNames = {
regularLight: "New regular light", regularLight: 'New regular light',
dimmableLight: "New intensity light", dimmableLight: 'New intensity light',
smartPlug: "New smart Plug", smartPlug: 'New smart Plug',
sensor: "New sensor", sensor: 'New sensor',
switch: "New switch", switch: 'New switch',
buttonDimmer: "New button dimmer", buttonDimmer: 'New button dimmer',
knobDimmer: "New knob dimmer", knobDimmer: 'New knob dimmer',
securityCamera: "New security camera", securityCamera: 'New security camera',
thermostat: 'New thermostat',
curtains: 'New curtains',
}; };
if (this.state.deviceName === "") { if (this.state.deviceName === '') {
data.name = defaultNames[this.state.typeOfDevice]; data.name = defaultNames[this.state.typeOfDevice];
} }
console.log("-------------------------"); console.log('-------------------------');
console.log(this.state.typeOfDevice); console.log(this.state.typeOfDevice);
switch (this.state.typeOfDevice) { switch (this.state.typeOfDevice) {
//trying to make securityCamera work // trying to make securityCamera work
//case "securityCamera": // case "securityCamera":
// data.path="/security_camera_videos/security_camera_1.mp4"; // data.path="/security_camera_videos/security_camera_1.mp4";
// data.on=false; // data.on=false;
//break; // break;
//trying to make thermostat work // trying to make thermostat work
case "thermostat": case 'thermostat':
data.targetTemperature = 0; data.targetTemperature = 0;
data.measuredTemperature = 0; data.measuredTemperature = 0;
break; break;
case "dimmableLight": case 'dimmableLight':
data.intensity = 0; data.intensity = 0;
break; break;
case "sensor": case 'sensor':
if (!this.state.motion) { if (!this.state.motion) {
data.sensor = this.state.typeOfSensor; data.sensor = this.state.typeOfSensor;
data.value = 0; data.value = 0;
} }
break; break;
case "switch": case 'switch':
case "buttonDimmer": case 'buttonDimmer':
case "knobDimmer": case 'knobDimmer':
outputs = this.state.lightsAttached; outputs = this.state.lightsAttached;
if (
this.state.lightsAttached === undefined
|| this.state.lightsAttached.length === 0
) {
alert(
'No lights attached to this switch! Please, add a light a first.',
);
return;
}
break; break;
default: default:
break; break;
} }
try { try {
let newDevice = await this.props.saveDevice(data); const newDevice = await this.props.saveDevice(data);
if (outputs) { if (outputs) {
await this.props.connectOutputs(newDevice, outputs); await this.props.connectOutputs(newDevice, outputs);
} }
this.resetState(); this.resetState();
} catch (e) { } catch (e) {
console.error("device creation error: ", e); console.error('device creation error: ', e);
} }
} }
render() { render() {
const deviceOptions = [ const deviceOptions = [
//stuff // stuff
{ key: "thermostat", text: "Thermostat", value: "thermostat", image: {} },
{ key: "curtains", text: "Curtain", value: "curtains", image: {} },
//stuff ends
{ {
key: "light", key: 'thermostat',
text: "Normal Light", text: 'Thermostat',
value: "regularLight", value: 'thermostat',
image: { avatar: true, src: "/img/lightOn.svg" }, image: { avatar: true, src: '/img/thermostat-icon.png' },
}, },
{ {
key: "intensity-light", key: 'curtains',
text: "Intensity Light", text: 'Curtain',
value: "dimmableLight", value: 'curtains',
image: { avatar: true, src: "/img/intensity-light.svg" }, image: { avatar: true, src: '/img/curtains-icon.png' },
},
// stuff ends
{
key: 'light',
text: 'Normal Light',
value: 'regularLight',
image: { avatar: true, src: '/img/lightOn.svg' },
}, },
{ {
key: "smart-plug", key: 'intensity-light',
text: "Smart Plug", text: 'Intensity Light',
value: "smartPlug", value: 'dimmableLight',
image: { avatar: true, src: "/img/smart-plug.svg" }, image: { avatar: true, src: '/img/intensity-light.svg' },
}, },
{ {
key: "sensor", key: 'smart-plug',
text: "Sensor", text: 'Smart Plug',
value: "sensor", value: 'smartPlug',
image: { avatar: true, src: "/img/sensorOn.svg" }, image: { avatar: true, src: '/img/smart-plug.svg' },
}, },
{ {
key: "switch", key: 'sensor',
text: "Switch", text: 'Sensor',
value: "switch", value: 'sensor',
image: { avatar: true, src: "/img/switchOn.svg" }, image: { avatar: true, src: '/img/sensorOn.svg' },
}, },
{ {
key: "knobDimmer", key: 'switch',
text: "Knob Dimmer", text: 'Switch',
value: "knobDimmer", value: 'switch',
image: { avatar: true, src: "/img/knob.svg" }, image: { avatar: true, src: '/img/switchOn.svg' },
}, },
{ {
key: "buttonDimmer", key: 'knobDimmer',
text: "Button Dimmer", text: 'Knob Dimmer',
value: "buttonDimmer", value: 'knobDimmer',
image: { avatar: true, src: "/img/plusMinus.svg" }, image: { avatar: true, src: '/img/knob.svg' },
}, },
{ {
key: "securityCamera", key: 'buttonDimmer',
text: "Security Camera", text: 'Button Dimmer',
value: "securityCamera", value: 'buttonDimmer',
image: { avatar: true, src: "/img/plusMinus.svg" }, image: { avatar: true, src: '/img/plusMinus.svg' },
},
{
key: 'securityCamera',
text: 'Security Camera',
value: 'securityCamera',
image: { avatar: true, src: '/img/security-icon.png' },
}, },
]; ];
const sensorOptions = [ const sensorOptions = [
{ {
key: "temperature", key: 'temperature',
text: "Temperature Sensor", text: 'Temperature Sensor',
value: "TEMPERATURE", value: 'TEMPERATURE',
image: { avatar: true, src: "/img/temperature-sensor.svg" }, image: { avatar: true, src: '/img/temperature-sensor.svg' },
}, },
{ {
key: "humidity", key: 'humidity',
text: "Humidity Sensor", text: 'Humidity Sensor',
value: "HUMIDITY", value: 'HUMIDITY',
image: { avatar: true, src: "/img/humidity-sensor.svg" }, image: { avatar: true, src: '/img/humidity-sensor.svg' },
}, },
{ {
key: "light", key: 'light',
text: "Light Sensor", text: 'Light Sensor',
value: "LIGHT", value: 'LIGHT',
image: { avatar: true, src: "/img/light-sensor.svg" }, image: { avatar: true, src: '/img/light-sensor.svg' },
}, },
{ {
key: "motion", key: 'motion',
text: "Motion Sensor", text: 'Motion Sensor',
value: "motionSensor", value: 'motionSensor',
image: { avatar: true, src: "/img/sensorOn.svg" }, image: { avatar: true, src: '/img/sensorOn.svg' },
}, },
]; ];
const availableSwitchDevices = []; const availableSwitchDevices = [];
const availableDimmerDevices = []; const availableDimmerDevices = [];
this.props.devices.forEach((d) => { this.props.devices.forEach((d) => {
if ( if (
d.kind === "regularLight" || d.kind === 'regularLight'
d.kind === "dimmableLight" || || d.kind === 'dimmableLight'
d.kind === "smartPlug" || d.kind === 'smartPlug'
) { ) {
availableSwitchDevices.push({ availableSwitchDevices.push({
key: d.id, key: d.id,
@ -250,7 +273,7 @@ class NewDevice extends Component {
value: d.id, value: d.id,
}); });
} }
if (d.kind === "dimmableLight") { if (d.kind === 'dimmableLight') {
availableDimmerDevices.push({ availableDimmerDevices.push({
key: d.id, key: d.id,
text: d.name, text: d.name,
@ -275,7 +298,7 @@ class NewDevice extends Component {
<label>Device Name: </label> <label>Device Name: </label>
<Input <Input
fluid fluid
size={"large"} size="large"
onChange={this.setDeviceName} onChange={this.setDeviceName}
focus focus
placeholder="Device Name" placeholder="Device Name"
@ -284,7 +307,7 @@ class NewDevice extends Component {
</div> </div>
); );
const sensorForm = ( const sensorForm = (
<Form.Field style={{ marginTop: "1rem" }}> <Form.Field style={{ marginTop: '1rem' }}>
<label>Type of Sensor: </label> <label>Type of Sensor: </label>
<Dropdown <Dropdown
name="typeOfDevice" name="typeOfDevice"
@ -297,7 +320,7 @@ class NewDevice extends Component {
</Form.Field> </Form.Field>
); );
const switchOptions = ( const switchOptions = (
<Form.Field style={{ marginTop: "1rem" }}> <Form.Field style={{ marginTop: '1rem' }}>
<label>Select the lights or smart plugs You Want to Attach: </label> <label>Select the lights or smart plugs You Want to Attach: </label>
<Dropdown <Dropdown
name="typeOfDevice" name="typeOfDevice"
@ -310,7 +333,7 @@ class NewDevice extends Component {
</Form.Field> </Form.Field>
); );
const dimmerOptions = ( const dimmerOptions = (
<Form.Field style={{ marginTop: "1rem" }}> <Form.Field style={{ marginTop: '1rem' }}>
<label>Select the dimmable lights You Want to Attach: </label> <label>Select the dimmable lights You Want to Attach: </label>
<Dropdown <Dropdown
name="typeOfDevice" name="typeOfDevice"
@ -325,12 +348,12 @@ class NewDevice extends Component {
return ( return (
<Form> <Form>
{deviceName} {deviceName}
{this.state.typeOfDevice === "sensor" ? sensorForm : ""} {this.state.typeOfDevice === 'sensor' ? sensorForm : ''}
{this.state.typeOfDevice === "switch" ? switchOptions : ""} {this.state.typeOfDevice === 'switch' ? switchOptions : ''}
{this.state.typeOfDevice === "buttonDimmer" || {this.state.typeOfDevice === 'buttonDimmer'
this.state.typeOfDevice === "knobDimmer" || this.state.typeOfDevice === 'knobDimmer'
? dimmerOptions ? dimmerOptions
: ""} : ''}
</Form> </Form>
); );
}; };
@ -340,12 +363,19 @@ class NewDevice extends Component {
closeIcon closeIcon
open={this.state.openModal} open={this.state.openModal}
onClose={this.resetState} onClose={this.resetState}
trigger={ trigger={(
<StyledDiv onClick={this.handleOpen}> <StyledDiv
<Image src="/img/add.svg" style={{ filter: "invert()" }} /> onClick={this.handleOpen}
style={{
position: 'relative',
top: 'calc(50% - 5rem)',
left: 'calc(50% - 5rem)',
}}
>
<Image src="/img/add.svg" style={{ filter: 'invert()' }} />
</StyledDiv> </StyledDiv>
} )}
centered={true} centered
> >
<Modal.Header>Add a New Device</Modal.Header> <Modal.Header>Add a New Device</Modal.Header>
<Modal.Content>{steps[this.state.step - 1]}</Modal.Content> <Modal.Content>{steps[this.state.step - 1]}</Modal.Content>
@ -361,7 +391,7 @@ class NewDevice extends Component {
Back Back
</Button> </Button>
) : ( ) : (
"" ''
)} )}
{this.state.step < steps.length ? ( {this.state.step < steps.length ? (
<Button <Button
@ -374,7 +404,7 @@ class NewDevice extends Component {
<Icon name="right arrow" /> <Icon name="right arrow" />
</Button> </Button>
) : ( ) : (
"" ''
)} )}
{this.state.step === steps.length ? ( {this.state.step === steps.length ? (
<Button <Button
@ -387,7 +417,7 @@ class NewDevice extends Component {
Finish Finish
</Button> </Button>
) : ( ) : (
"" ''
)} )}
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>

View File

@ -19,8 +19,10 @@
errorStyle, errorStyle,
*/ */
import React, { Component } from "react"; import React, { Component } from 'react';
import { CircularInput, CircularProgress } from "react-circular-input"; import { CircularInput, CircularProgress } from 'react-circular-input';
import { Image } from 'semantic-ui-react';
import { connect } from 'react-redux';
import { import {
container, container,
sensorText, sensorText,
@ -34,10 +36,9 @@ import {
lightSensorColors, lightSensorColors,
humiditySensorColors, humiditySensorColors,
iconSensorStyle, iconSensorStyle,
} from "./SensorStyle"; } from './SensorStyle';
import { Image } from "semantic-ui-react"; import { RemoteService } from '../../../remote';
import { RemoteService } from "../../../remote"; import mapStateToProps from '../../../deviceProps';
import { connect } from "react-redux";
class Sensor extends Component { class Sensor extends Component {
constructor(props) { constructor(props) {
@ -46,32 +47,25 @@ class Sensor extends Component {
value: 0, value: 0,
motion: false, motion: false,
}; };
this.units = ""; this.units = '';
this.stateCallback = (e) => { this.stateCallback = (e) => {
this.setState(Object.assign(this.state, e)); this.setState(Object.assign(this.state, e));
}; };
this.colors = temperatureSensorColors; this.colors = temperatureSensorColors;
this.icon = "temperatureIcon.svg"; this.icon = 'temperatureIcon.svg';
this.name = "Sensor"; this.name = 'Sensor';
} }
// setName = () => {
// if (this.props.device.name.length > 15) {
// return this.props.device.name.slice(0, 12) + "...";
// }
// return this.props.device.name;
// };
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if ( if (
this.props.stateOrDevice.kind === "sensor" && this.props.stateOrDevice.kind === 'sensor'
this.props.stateOrDevice.value !== prevProps.stateOrDevice.value && this.props.stateOrDevice.value !== prevProps.stateOrDevice.value
) { ) {
this.setState({ value: this.props.stateOrDevice.value }); this.setState({ value: this.props.stateOrDevice.value });
} else if ( } else if (
this.props.stateOrDevice.kind === "motionSensor" && this.props.stateOrDevice.kind === 'motionSensor'
this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected && this.props.stateOrDevice.detected !== prevProps.stateOrDevice.detected
) { ) {
this.setState({ this.setState({
motion: true, motion: true,
@ -81,28 +75,28 @@ class Sensor extends Component {
} }
componentDidMount() { componentDidMount() {
if (this.props.stateOrDevice.kind === "sensor") { if (this.props.stateOrDevice.kind === 'sensor') {
switch (this.props.stateOrDevice.sensor) { switch (this.props.stateOrDevice.sensor) {
case "TEMPERATURE": case 'TEMPERATURE':
this.units = "ºC"; this.units = 'ºC';
this.colors = temperatureSensorColors; this.colors = temperatureSensorColors;
this.icon = "temperatureIcon.svg"; this.icon = 'temperatureIcon.svg';
this.name = "Temperature Sensor"; this.name = 'Temperature Sensor';
break; break;
case "HUMIDITY": case 'HUMIDITY':
this.units = "%"; this.units = '%';
this.colors = humiditySensorColors; this.colors = humiditySensorColors;
this.icon = "humidityIcon.svg"; this.icon = 'humidityIcon.svg';
this.name = "Humidity Sensor"; this.name = 'Humidity Sensor';
break; break;
case "LIGHT": case 'LIGHT':
this.units = "lm"; this.units = 'lm';
this.colors = lightSensorColors; this.colors = lightSensorColors;
this.icon = "lightSensorIcon.svg"; this.icon = 'lightSensorIcon.svg';
this.name = "Light Sensor"; this.name = 'Light Sensor';
break; break;
default: default:
this.units = ""; this.units = '';
} }
this.setState({ this.setState({
value: this.props.stateOrDevice.value, value: this.props.stateOrDevice.value,
@ -122,19 +116,30 @@ class Sensor extends Component {
return this.iconOff; return this.iconOff;
}; };
temperatureColor = (value) => {
let hue = 100;
const min = 16;
const max = 20;
if (value >= min && value < max) {
hue = 100 - ((value - min) * 100) / (max - min);
} else if (value >= max) {
hue = 0;
}
return `hsl(${hue}, 100%, 50%)`;
};
render() { render() {
const MotionSensor = (props) => { const MotionSensor = (props) => (
return (
<div <div
style={{ style={{
...motionSensorOuterCircle, ...motionSensorOuterCircle,
backgroundColor: this.state.detected ? "#505bda" : "#00bdaa", backgroundColor: this.state.detected ? '#505bda' : '#00bdaa',
}} }}
> >
<div <div
style={{ style={{
...motionSensorInnerCircle, ...motionSensorInnerCircle,
backgroundColor: this.state.detected ? "#fe346e" : "#00bdaa", backgroundColor: this.state.detected ? '#fe346e' : '#00bdaa',
}} }}
> >
<Image style={motionSensorIcon} src="/img/motionSensorIcon.svg" /> <Image style={motionSensorIcon} src="/img/motionSensorIcon.svg" />
@ -142,17 +147,16 @@ class Sensor extends Component {
</div> </div>
</div> </div>
); );
};
return ( return (
<div style={container}> <div style={container}>
{this.state.motion ? ( {this.state.motion ? (
<MotionSensor /> <MotionSensor />
) : ( ) : (
<React.Fragment> <>
<CircularInput <CircularInput
value={ value={
this.props.stateOrDevice.sensor === "LIGHT" this.props.stateOrDevice.sensor === 'LIGHT'
? this.state.value / 2000 ? this.state.value / 2000
: this.state.value / 100 : this.state.value / 100
} }
@ -160,11 +164,18 @@ class Sensor extends Component {
> >
<CircularProgress <CircularProgress
strokeWidth="2rem" strokeWidth="2rem"
stroke={this.colors.progress} stroke={
this.props.stateOrDevice.sensor === 'TEMPERATURE'
? this.temperatureColor(this.state.value)
: this.colors.progress
}
fill={this.colors.circle} fill={this.colors.circle}
/> />
<text <text
style={{ ...valueStyle, fill: this.colors.text }} style={{
...valueStyle,
fill: this.colors.text,
}}
x={100} x={100}
y={110} y={110}
textAnchor="middle" textAnchor="middle"
@ -172,11 +183,14 @@ class Sensor extends Component {
fontWeight="bold" fontWeight="bold"
fill={this.colors.text} fill={this.colors.text}
> >
{+(Math.round(this.state.value + "e+2") + "e-2")} {+(`${Math.round(`${this.state.value}e+2`)}e-2`)}
{this.units} {this.units}
</text> </text>
<text <text
style={{ ...sensorText, fill: this.colors.text }} style={{
...sensorText,
fill: this.colors.text,
}}
x={100} x={100}
y={150} y={150}
textAnchor="middle" textAnchor="middle"
@ -187,21 +201,12 @@ class Sensor extends Component {
</text> </text>
</CircularInput> </CircularInput>
<Image style={iconSensorStyle} src={`/img/${this.icon}`} /> <Image style={iconSensorStyle} src={`/img/${this.icon}`} />
</React.Fragment> </>
)} )}
</div> </div>
); );
} }
} }
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.sceneStates[ownProps.id];
}
},
});
const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor); const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor);
export default SensorContainer; export default SensorContainer;

View File

@ -1,105 +1,108 @@
export const style = { export const style = {
width: "10rem", width: '10rem',
height: "10rem", height: '10rem',
position: "absolute", position: 'absolute',
top: "0", top: '0',
left: "0", left: '0',
}; };
export const container = { export const container = {
width: "10rem", width: '10rem',
height: "10rem", height: '10rem',
borderRadius: "100%", borderRadius: '100%',
border: "none", border: 'none',
position: "relative", position: 'relative',
}; };
export const sensorText = { export const sensorText = {
fill: "#3e99ff", fill: '#3e99ff',
fontSize: "1.2rem", fontSize: '1.2rem',
fontFamily: "Lato", fontFamily: 'Lato',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
userSelect: 'none',
}; };
export const valueStyle = { export const valueStyle = {
fill: "#3e99ff", fill: '#3e99ff',
fontSize: "2.4rem", fontSize: '2.4rem',
fontFamily: "Lato", fontFamily: 'Lato',
userSelect: 'none',
}; };
export const errorStyle = { export const errorStyle = {
fill: "#ff4050", fill: '#ff4050',
fontSize: "1.5rem", fontSize: '1.5rem',
fontFamily: "Lato", fontFamily: 'Lato',
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)", textShadow: '1px 1px 0.5px rgba(0, 0, 0, .2)',
userSelect: 'none',
}; };
export const motionSensorInnerCircle = { export const motionSensorInnerCircle = {
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
transform: "translate(-50%, -50%)", transform: 'translate(-50%, -50%)',
width: "8rem", width: '8rem',
height: "8rem", height: '8rem',
borderRadius: "100%", borderRadius: '100%',
border: "none", border: 'none',
}; };
export const motionSensorOuterCircle = { export const motionSensorOuterCircle = {
textAlign: "center", textAlign: 'center',
cursor: "pointer", cursor: 'pointer',
position: "relative", position: 'relative',
width: "10rem", width: '10rem',
height: "10rem", height: '10rem',
borderRadius: "100%", borderRadius: '100%',
border: "none", border: 'none',
/*boxShadow: "3px 2px 10px 5px #ccc",*/ /* boxShadow: "3px 2px 10px 5px #ccc", */
}; };
export const nameMotionStyle = { export const nameMotionStyle = {
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
fontSize: "1.2rem", fontSize: '1.2rem',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
}; };
export const motionSensorIcon = { export const motionSensorIcon = {
width: "2rem", width: '2rem',
height: "2rem", height: '2rem',
position: "absolute", position: 'absolute',
top: "15%", top: '15%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
}; };
export const temperatureSensorColors = { export const temperatureSensorColors = {
circle: "#323232", circle: '#323232',
progress: "#ff1e56", progress: '#ff1e56',
text: "white", text: 'white',
}; };
export const lightSensorColors = { export const lightSensorColors = {
circle: "#000839", circle: '#000839',
progress: "#ffa41b", progress: '#ffa41b',
text: "white", text: 'white',
}; };
export const humiditySensorColors = { export const humiditySensorColors = {
circle: "#005082", circle: '#005082',
progress: "#00a8cc", progress: '#00a8cc',
text: "white", text: 'white',
}; };
export const iconSensorStyle = { export const iconSensorStyle = {
position: "absolute", position: 'absolute',
top: "20%", top: '20%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
width: "2rem", width: '2rem',
height: "2rem", height: '2rem',
}; };

View File

@ -3,24 +3,25 @@
SmartHut interface or by a switch. SmartHut interface or by a switch.
The smart plug also stores the total energy consumed while the plug is active, in terms of kilowatt-hours 2(kWh) . The smart plug also stores the total energy consumed while the plug is active, in terms of kilowatt-hours 2(kWh) .
The user can reset this value. The user can reset this value.
**/ * */
import React, { Component } from "react"; import React, { Component } from 'react';
import { BottomPanel, StyledDiv } from "./styleComponents"; import { Image } from 'semantic-ui-react';
import { Image } from "semantic-ui-react"; import { connect } from 'react-redux';
import { BottomPanel, StyledDiv } from './styleComponents';
import { import {
energyConsumedStyle, energyConsumedStyle,
imageStyle, imageStyle,
kwhStyle, kwhStyle,
nameStyle, nameStyle,
} from "./SmartPlugStyle"; } from './SmartPlugStyle';
import { RemoteService } from "../../../remote"; import { RemoteService } from '../../../remote';
import { connect } from "react-redux"; import mapStateToProps from '../../../deviceProps';
class SmartPlug extends Component { class SmartPlug extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.iconOn = "/img/smart-plug.svg"; this.iconOn = '/img/smart-plug.svg';
this.iconOff = "/img/smart-plug-off.svg"; this.iconOff = '/img/smart-plug-off.svg';
} }
get turnedOn() { get turnedOn() {
@ -28,38 +29,36 @@ class SmartPlug extends Component {
} }
get energyConsumed() { get energyConsumed() {
return (this.props.stateOrDevice.totalConsumption / 1000).toFixed(3); return (this.props.device.totalConsumption / 1000).toFixed(3);
} }
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("smart plug update error", err)); .catch((err) => console.error('smart plug update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.stateOrDevice.id, on: on }, { id: this.props.stateOrDevice.id, on },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
}; };
getIcon = () => { getIcon = () => (this.turnedOn ? this.iconOn : this.iconOff);
return this.turnedOn ? this.iconOn : this.iconOff;
};
render() { render() {
return ( return (
<StyledDiv onClick={this.onClickDevice}> <StyledDiv onClick={this.props.disabled ? () => {} : this.onClickDevice}>
<Image src={this.getIcon()} style={imageStyle} /> <Image src={this.getIcon()} style={imageStyle} />
<span style={nameStyle}>Smart Plug</span> <span style={nameStyle}>Smart Plug</span>
<BottomPanel <BottomPanel
style={ style={
this.turnedOn this.turnedOn
? { backgroundColor: "#505bda" } ? { backgroundColor: '#505bda' }
: { backgroundColor: "#1a2849" } : { backgroundColor: '#1a2849' }
} }
> >
<span style={energyConsumedStyle}>{this.energyConsumed}</span> <span style={energyConsumedStyle}>{this.energyConsumed}</span>
@ -70,14 +69,5 @@ class SmartPlug extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
get stateOrDevice() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.sceneStates[ownProps.id];
}
},
});
const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug); const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug);
export default SmartPlugContainer; export default SmartPlugContainer;

View File

@ -1,38 +1,42 @@
export const energyConsumedStyle = { export const energyConsumedStyle = {
color: "white", color: 'white',
fontSize: "1.3rem", fontSize: '1.3rem',
position: "absolute", position: 'absolute',
top: "20%", top: '20%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
userSelect: 'none',
}; };
export const kwhStyle = { export const kwhStyle = {
color: "white", color: 'white',
fontSize: "1rem", fontSize: '1rem',
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
userSelect: 'none',
}; };
export const imageStyle = { export const imageStyle = {
width: "2rem", width: '2rem',
height: "auto", height: 'auto',
position: "absolute", position: 'absolute',
top: "5%", top: '5%',
left: "50%", left: '50%',
transform: "translateX(-35%)", transform: 'translateX(-35%)',
filter: "drop-shadow( 1px 1px 0.5px rgba(0, 0, 0, .25))", filter: 'drop-shadow( 1px 1px 0.5px rgba(0, 0, 0, .25))',
userSelect: 'none',
}; };
export const nameStyle = { export const nameStyle = {
color: "black", color: 'black',
position: "absolute", position: 'absolute',
top: "30%", top: '30%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
userSelect: 'none',
}; };

View File

@ -5,58 +5,54 @@
* The user can change the state of a switch through the SmartHut interface. * The user can change the state of a switch through the SmartHut interface.
*/ */
import React, { Component } from "react"; import React, { Component } from 'react';
import { BottomPanel, StyledDiv } from "./styleComponents"; import { Image } from 'semantic-ui-react';
import { Image } from "semantic-ui-react"; import { connect } from 'react-redux';
import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle"; import { BottomPanel, StyledDiv } from './styleComponents';
import { RemoteService } from "../../../remote"; import { imageStyle, nameStyle, turnedOnStyle } from './SwitchStyle';
import { connect } from "react-redux"; import { RemoteService } from '../../../remote';
import mapStateToProps from '../../../deviceProps';
class Switch extends Component { class Switch extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.iconOn = "/img/switchOn.svg"; this.iconOn = '/img/switchOn.svg';
this.iconOff = "/img/switchOff.svg"; this.iconOff = '/img/switchOff.svg';
} }
get turnedOn() { get turnedOn() {
return this.props.device.on; return this.props.device.on;
} }
getIcon = () => { getIcon = () => (this.turnedOn ? this.iconOn : this.iconOff);
return this.turnedOn ? this.iconOn : this.iconOff;
};
onClickDevice = () => { onClickDevice = () => {
const newOn = !this.turnedOn; const newOn = !this.turnedOn;
const type = newOn ? "ON" : "OFF"; const type = newOn ? 'ON' : 'OFF';
this.props this.props
.switchOperate(this.props.id, type) .switchOperate(this.props.id, type)
.catch((err) => console.error("switch operate failed", err)); .catch((err) => console.error('switch operate failed', err));
}; };
render() { render() {
return ( return (
<StyledDiv onClick={this.onClickDevice}> <StyledDiv onClick={this.props.disabled ? () => {} : this.onClickDevice}>
<Image src={this.getIcon()} style={imageStyle} /> <Image src={this.getIcon()} style={imageStyle} />
<span style={nameStyle}>Switch</span> <span style={nameStyle}>Switch</span>
<BottomPanel <BottomPanel
style={ style={
this.turnedOn this.turnedOn
? { backgroundColor: "#505bda" } ? { backgroundColor: '#505bda' }
: { backgroundColor: "#1a2849" } : { backgroundColor: '#1a2849' }
} }
> >
<span style={turnedOnStyle}>{this.turnedOn ? "ON" : "OFF"}</span> <span style={turnedOnStyle}>{this.turnedOn ? 'ON' : 'OFF'}</span>
</BottomPanel> </BottomPanel>
</StyledDiv> </StyledDiv>
); );
} }
} }
const mapStateToProps = (state, ownProps) => ({
device: state.devices[ownProps.id],
});
const SwitchContainer = connect(mapStateToProps, RemoteService)(Switch); const SwitchContainer = connect(mapStateToProps, RemoteService)(Switch);
export default SwitchContainer; export default SwitchContainer;

View File

@ -1,28 +1,31 @@
export const imageStyle = { export const imageStyle = {
width: "2rem", width: '2rem',
height: "auto", height: 'auto',
position: "absolute", position: 'absolute',
top: "5%", top: '5%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
userSelect: 'none',
}; };
export const nameStyle = { export const nameStyle = {
color: "black", color: 'black',
position: "absolute", position: 'absolute',
top: "30%", top: '30%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
overflow: "hidden", overflow: 'hidden',
whiteSpace: "nowrap", whiteSpace: 'nowrap',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
userSelect: 'none',
}; };
export const turnedOnStyle = { export const turnedOnStyle = {
color: "white", color: 'white',
fontSize: "1.3rem", fontSize: '1.3rem',
position: "absolute", position: 'absolute',
top: "20%", top: '20%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
userSelect: 'none',
}; };

View File

@ -1,38 +1,4 @@
.slider-css { .rangeslider.slider-css {
-webkit-appearance: none; margin-left: 1rem;
width: 20rem; margin-right: 1rem;
font-family: "Lato";
position: absolute;
margin-top: 27%;
margin-left: 50%;
transform: translate(-50%, -50%);
}
.slider-css::-webkit-slider-thumb {
-webkit-appearance: none;
border: 5px solid #ffffff;
width: 18px;
height: 18px;
border-radius: 10px;
background-color: rgba(94, 246, 152, 1);
cursor: pointer;
box-shadow: 1px 1px 15px 2px rgba(0, 0, 0, 0.4);
margin-top: -7px;
}
.slider-css:focus {
-webkit-appearance: none;
outline: none;
}
.slider-css::-webkit-slider-runnable-track {
-webkit-appearance: none;
outline: none;
width: 100%;
height: 7px;
cursor: pointer;
box-shadow: 4.5px 4.5px 20px 1px rgba(0, 0, 0, 0.3);
background: white;
border-radius: 5px;
} }

View File

@ -1,73 +1,63 @@
export const container = { export const container = {
position: "relative", margin: '-1em 0',
width: "25rem", width: '15em',
height: "12rem", height: '15.5em',
boxShadow: "22px 18px 54px -7px rgba(102,102,102,1)", boxShadow: '5px 5px 5px 5px #DDD',
borderRadius: "30px", borderRadius: '1em',
backgroundColor: "white", backgroundColor: 'white',
}; transform: 'translate(-2.5em, 0)',
export const line = {
width: "250px",
height: "47px",
borderBottom: "1px solid #646464",
position: "absolute",
left: "5%",
}; };
export const deviceName = { export const deviceName = {
fontFamily: "Lato", paddingTop: '.5rem',
position: "absolute", paddingLeft: '1rem',
marginTop: "5%", fontFamily: 'Lato',
marginLeft: "8%", fontSize: '1rem',
fontSize: "1rem", fontWeight: 'bold',
fontWeight: "bold",
color: "#646464",
}; };
export const targetTemperature = { export const targetTemperature = {
fontFamily: "Lato", fontFamily: 'Lato',
position: "absolute", marginTop: '.5rem',
marginTop: "20%", width: '100%',
marginLeft: "50%", textAlign: 'center',
transform: "translate(-50%,-50%)", fontSize: '1.3rem',
fontSize: "2rem", fontWeight: 'bold',
fontWeight: "bold", color: '#646464',
color: "#646464",
}; };
export const slider = { export const slider = {
width: "25rem", width: '25rem',
fontFamily: "Lato", fontFamily: 'Lato',
position: "absolute", position: 'absolute',
marginTop: "35%", marginTop: '35%',
marginLeft: "50%", marginLeft: '50%',
transform: "translate(-50%,-50%)", transform: 'translate(-50%,-50%)',
}; };
export const stateTagContainer = { export const stateTagContainer = {
textAlign: "center", textAlign: 'center',
position: "absolute", position: 'absolute',
//width: "3rem", width: '10rem',
height: "2rem", height: '2rem',
marginTop: "35%", bottom: '-.25rem',
marginLeft: "50%", left: '50%',
transform: "translate(-50%,-50%)", transform: 'translate(-50%,-50%)',
padding: "0.5rem 4rem", backgroundColor: '#2b2',
backgroundColor: "rgba(94,246,152,1)", borderRadius: '50px',
borderRadius: "50px",
}; };
export const stateTag = { export const stateTag = {
fontFamily: "Lato", fontFamily: 'Lato',
fontSize: "1.2rem", fontSize: '1.2rem',
color: "white", lineHeight: '2rem',
textTransform: "uppercase", color: 'white',
textTransform: 'uppercase',
}; };
export const toggle = { export const toggle = {
position: "absolute", position: 'absolute',
top: "10%", top: '.7rem',
left: "35%", right: '2.5rem',
transform: "rotate(-360deg)", transform: 'rotate(-360deg)',
}; };

View File

@ -1,224 +1,190 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Checkbox } from "semantic-ui-react"; import { Checkbox, Icon } from 'semantic-ui-react';
import { RemoteService } from "../../../remote"; import { RemoteService } from '../../../remote';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import "./Thermostat.css"; import './Thermostat.css';
import Slider from 'react-rangeslider';
import 'react-rangeslider/lib/index.css';
import mapStateToProps from '../../../deviceProps';
import { import {
stateTag, stateTag,
container, container,
deviceName, deviceName,
targetTemperature, targetTemperature,
slider,
line,
toggle, toggle,
stateTagContainer, stateTagContainer,
} from "./ThermostatStyle"; } from './ThermostatStyle';
//not quite working yet
class Thermostats extends Component { class Thermostats extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
targetTemperature: this.props.stateOrDevice.targetTemperature, targetTemperature: this.props.device.targetTemperature,
internalSensorTemperature: this.props.stateOrDevice mode: this.props.device.mode,
.internalSensorTemperature, measuredTemperature: this.props.device.measuredTemperature,
mode: this.props.stateOrDevice.mode,
measuredTemperature: this.props.stateOrDevice.measuredTemperature,
useExternalSensors: this.props.stateOrDevice.useExternalSensors,
timeout: null,
}; };
this.setMode = this.setMode.bind(this); this.setMode = this.setMode.bind(this);
this.setTargetTemperature = this.setTargetTemperature.bind(this); this.setTargetTemperature = this.setTargetTemperature.bind(this);
this.setInternalSensorTemperature = this.setInternalSensorTemperature.bind(
this
);
}
//getters
get getMode() {
return this.props.stateOrDevice.mode;
}
get getTargetTemperature() {
return this.props.stateOrDevice.targetTemperature;
}
get getInternalSensorTemperature() {
return this.props.stateOrDevice.internalSensorTemperature;
}
get getMeasuredTemperature() {
return this.props.stateOrDevice.measuredTemperature;
}
get getUseExternalSensors() {
return this.props.stateOrDevice.useExternalSensors;
} }
setMode(mode) { setMode(mode) {
if (this.state.timeout) { // i came to the conclusion that is not possible to set mode.
clearTimeout(this.state.timeout); // Good job Jacob (Claudio)
} // this.mode = "HEATING";
console.log(mode);
//i came to the conclusion that is not possible to set mode.
//this.mode = "HEATING";
const turnOn = mode; const turnOn = mode;
console.log(turnOn); if (this.props.tab === 'Devices') {
if (this.props.tab === "Devices") {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, turnOn }) .saveDevice({ ...this.props.stateOrDevice, turnOn })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error('thermostat update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.stateOrDevice.id, turnOn: turnOn }, { id: this.props.stateOrDevice.id, on: turnOn },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
} }
onClickDevice = () => { onClickDevice = () => {
const on = !this.turnedOn; const on = !this.turnedOn;
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.saveDevice({ ...this.props.stateOrDevice, on }) .saveDevice({ ...this.props.stateOrDevice, on })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error('thermostat update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ id: this.props.stateOrDevice.id, on: on }, { id: this.props.stateOrDevice.id, on },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
}; };
//It seems to work
saveTargetTemperature(targetTemperature) { saveTargetTemperature(targetTemperature) {
const turn = this.props.stateOrDevice.mode !== "OFF" ? true : false; const turn = this.props.stateOrDevice.mode !== 'OFF';
if (this.props.tab === "Devices") { if (this.props.tab === 'Devices') {
this.props this.props
.saveDevice({ .saveDevice({
...this.props.stateOrDevice, ...this.props.stateOrDevice,
targetTemperature, targetTemperature,
turnOn: turn, turnOn: turn,
}) })
.catch((err) => console.error("thermostat update error", err)); .catch((err) => console.error('thermostat update error', err));
} else { } else {
this.props.updateState( this.props.updateState(
{ {
id: this.props.stateOrDevice.id, id: this.props.stateOrDevice.id,
targetTemperature: targetTemperature, targetTemperature,
}, },
this.props.stateOrDevice.kind this.props.stateOrDevice.kind,
); );
} }
} }
setTargetTemperature(newTemp) { setTargetTemperature() {
if (this.state.timeout) { this.saveTargetTemperature(this.state.targetTemperature);
clearTimeout(this.state.timeout);
} }
this.setState({
newTemp,
timeout: setTimeout(() => {
this.saveTargetTemperature(newTemp);
this.setState({
targetTemperature: this.state.targetTemperature,
timeout: null,
});
}, 100),
});
}
//I have no idea why it doesn't want to update the temperature
saveInternalSensorTemperature(internalSensorTemperature) {
if (this.props.tab === "Devices") {
this.props
.saveDevice({ ...this.props.device, internalSensorTemperature })
.catch((err) => console.error("thermostat update error", err));
} else {
this.props.updateState(
{
id: this.props.stateOrDevice.id,
internalSensorTemperature: internalSensorTemperature,
},
this.props.stateOrDevice.kind
);
}
}
setInternalSensorTemperature(newTemp) {
if (this.state.timeout) {
clearTimeout(this.state.timeout);
}
this.setState({
newTemp,
timeout: setTimeout(() => {
this.saveInternalSensorTemperature(newTemp);
this.setState({
internalSensorTemperature: this.state.internalSensorTemperature,
timeout: null,
});
}, 100),
});
}
helperMode = () => {
//this.setMode("HEATING");
this.setTargetTemperature(20);
//this.setInternalSensorTemperature(42);
};
handleChange = (value) => { handleChange = (value) => {
this.setTargetTemperature(value); this.setState({ ...this.state, targetTemperature: value });
};
handleCheckbox = (val) => {
const useExternalSensors = val;
const turnOn = this.props.stateOrDevice.mode !== 'OFF';
if (this.props.tab === 'Devices') {
this.props
.saveDevice({ ...this.props.stateOrDevice, useExternalSensors, turnOn })
.catch((err) => console.error('thermostat update error', err));
}
}; };
render() { render() {
return ( return (
<div style={container}> <div style={container}>
<h3 style={deviceName}>{this.props.stateOrDevice.name}</h3> <h3 style={deviceName}>
<div style={line}></div> Thermostat
<Checkbox <Checkbox
checked={this.props.stateOrDevice.mode !== "OFF" ? true : false} disabled={this.props.disabled}
slider checked={
this.props.tab === 'Devices'
? this.props.device.mode !== 'OFF'
: this.props.stateOrDevice.on
}
toggle
style={toggle} style={toggle}
// TODO Manage the state hereconsole.log("CHANGE", val.checked)
onChange={(e, val) => this.setMode(val.checked)} onChange={(e, val) => this.setMode(val.checked)}
/> />
</h3>
<span style={targetTemperature}> <hr />
{this.props.stateOrDevice.targetTemperature}ºC <div style={targetTemperature}>
</span> <Icon name="thermometer half" />
<input {' '}
type="range" {this.props.device.measuredTemperature}
min="0" {' '}
max="40" ºC
<br />
<Icon name="target" />
{' '}
{this.props.device.targetTemperature.toFixed(1)}
{' '}
ºC
</div>
{this.props.tab === 'Devices' ? (
<>
<Slider
disabled={this.props.disabled}
min={10}
max={30}
step={0.1}
tooltip={false}
className="slider-css" className="slider-css"
value={this.props.stateOrDevice.targetTemperature} value={this.state.targetTemperature}
onChange={(event) => this.handleChange(event.target.value)} onChange={(event) => this.handleChange(event)}
id="targetTemperature" onChangeComplete={() => this.setTargetTemperature()}
/> />
<Checkbox
style={{ padding: '0 .7rem' }}
label="Use external sensors"
name="external"
toggle
checked={this.props.stateOrDevice.useExternalSensors}
disabled={!this.props.tempSensorsInRoom}
onChange={(e, val) => this.handleCheckbox(val.checked)}
/>
</>
) : null}
<div style={stateTagContainer}> <div style={stateTagContainer}>
<span style={stateTag}>{this.props.stateOrDevice.mode}</span> <span style={stateTag}>
{this.props.tab !== 'Scenes'
? this.props.device.mode
: this.props.stateOrDevice.on
? 'WILL TURN ON'
: 'WILL TURN OFF'}
</span>
</div> </div>
</div> </div>
); );
} }
} }
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps2 = (state, ownProps) => ({
get stateOrDevice() { ...mapStateToProps(state, ownProps),
if (state.active.activeTab === "Devices") { get tempSensorsInRoom() {
return state.devices[ownProps.id]; if (state.active.activeTab !== 'Devices') return false;
} else { const room = state.rooms[state.devices[ownProps.id].roomId];
return state.sceneStates[ownProps.id]; if (!room) return false;
} const deviceIds = room.devices;
const devices = [...deviceIds].map((id) => state.devices[id]);
const sensors = devices.filter(
(d) => d.kind === 'sensor' && d.sensor === 'TEMPERATURE',
);
return sensors.length > 0;
}, },
}); });
const ThermostatContainer = connect( const ThermostatContainer = connect(
mapStateToProps, mapStateToProps2,
RemoteService RemoteService,
)(Thermostats); )(Thermostats);
export default ThermostatContainer; export default ThermostatContainer;

View File

@ -1,24 +1,24 @@
export const LightDevice = { export const LightDevice = {
img: "/img/lightOff.svg", img: '/img/lightOff.svg',
imgClick: "/img/lightOn.svg", imgClick: '/img/lightOn.svg',
}; };
export const SmartPlugDevice = { export const SmartPlugDevice = {
img: "/img/smart-plug.svg", img: '/img/smart-plug.svg',
imgClick: "/img/smart-plug-off.svg", imgClick: '/img/smart-plug-off.svg',
}; };
export const TemperatureSensor = { export const TemperatureSensor = {
type: "temperature_sensor", type: 'temperature_sensor',
img: "", img: '',
imgClick: "", imgClick: '',
units: "ºC", units: 'ºC',
}; };
export const deviceList = [ export const deviceList = [
"Light", 'Light',
"Dimmer", 'Dimmer',
"Switcher", 'Switcher',
"Smart Plug", 'Smart Plug',
"Sensor", 'Sensor',
]; ];

View File

@ -1,31 +1,45 @@
// vim: set ts=2 sw=2 et tw=80: // vim: set ts=2 sw=2 et tw=80:
import React, { Component } from "react"; import React, { Component } from 'react';
import { StyledDivCamera } from "./styleComponents"; import { Grid, Checkbox } from 'semantic-ui-react';
import { Grid } from "semantic-ui-react"; import { connect } from 'react-redux';
import { RemoteService } from "../../../remote"; import { StyledDivCamera } from './styleComponents';
import { endpointURL } from "../../../endpoint"; import { RemoteService } from '../../../remote';
import { connect } from "react-redux"; import { endpointURL } from '../../../endpoint';
import VideocamModal from "./VideocamModal"; import VideocamModal from './VideocamModal';
import mapStateToProps from '../../../deviceProps';
class Videocam extends Component { class Videocam extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { selectedVideo: undefined }; this.state = { selectedVideo: undefined };
this.setOnOff = this.setOnOff.bind(this);
} }
openModal = () => { openModal = () => {
this.setState((state) => { this.setState((state) => ({ selectedVideo: true }));
return { selectedVideo: true };
});
}; };
closeModal = () => { closeModal = () => {
this.setState((state) => { this.setState((state) => ({ selectedVideo: undefined }));
return { selectedVideo: undefined };
});
}; };
setOnOff(onOff) {
const turn = onOff;
if (this.props.tab === 'Devices' || this.props.tab === 'Hosts') {
this.props
.saveDevice({ ...this.props.device, on: turn })
.then((res) => (turn ? this.refs.vidRef.play() : this.refs.vidRef.pause()))
.catch((err) => console.error('videocamera update error', err));
} else {
this.props.updateState(
{ id: this.props.stateOrDevice.id, on: turn },
this.props.stateOrDevice.kind,
);
}
}
get url() { get url() {
return endpointURL() + this.props.device.path; return endpointURL() + this.props.device.path;
} }
@ -35,7 +49,7 @@ class Videocam extends Component {
<div> <div>
<StyledDivCamera> <StyledDivCamera>
<div onClick={this.openModal}> <div onClick={this.openModal}>
<video autoPlay loop muted width="100%" height="auto"> <video ref="vidRef" autoPlay loop muted width="100%" height="auto">
<source src={this.url} type="video/mp4" /> <source src={this.url} type="video/mp4" />
</video> </video>
</div> </div>
@ -50,6 +64,16 @@ class Videocam extends Component {
/> />
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
<Grid.Row>
<Grid.Column>
<Checkbox
checked={this.props.stateOrDevice.on}
toggle
label="Turn on/off"
onChange={(e, val) => this.setOnOff(val.checked)}
/>
</Grid.Column>
</Grid.Row>
</Grid> </Grid>
</div> </div>
); );
@ -58,8 +82,5 @@ class Videocam extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
device: state.devices[ownProps.id],
});
const VideocamContainer = connect(mapStateToProps, RemoteService)(Videocam); const VideocamContainer = connect(mapStateToProps, RemoteService)(Videocam);
export default VideocamContainer; export default VideocamContainer;

View File

@ -1,34 +1,42 @@
import React from "react"; import React from 'react';
import Modal from "react-modal"; import Modal from 'react-modal';
import { Button } from "semantic-ui-react";
const ModalStyle = {
content: {
top: "20%",
left: "45%",
right: "auto",
bottom: "auto",
marginRight: "-40%",
width: "80%",
transform: "translate(-40%, -10%)",
},
};
const VideocamModal = (props) => ( const VideocamModal = (props) => (
<Modal <Modal
isOpen={!!props.selectedVideo} isOpen={!!props.selectedVideo}
contentLabel="Live Cam" contentLabel="Live Cam"
onRequestClose={props.closeModal} onRequestClose={props.closeModal}
style={ModalStyle} style={{
overlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
},
content: {
position: 'absolute',
top: '40px',
left: '40px',
right: '40px',
bottom: '40px',
border: '1px solid #ccc',
background: '#fff',
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
borderRadius: '4px',
outline: 'none',
padding: '20px',
backgroundColor: 'black',
},
}}
> >
{props.selectedVideo && ( {props.selectedVideo && (
<video autoPlay loop muted width="100%" height="auto"> <video autoPlay loop muted width="100%" height="90%">
<source src={props.url} type="video/mp4" /> <source src={props.url} type="video/mp4" />
</video> </video>
)} )}
<Button fluid primary onClick={props.closeModal}>
Close
</Button>
</Modal> </Modal>
); );

View File

@ -1,104 +1,105 @@
import styled from "styled-components"; import styled from 'styled-components';
import { useCircularInputContext } from "react-circular-input"; import { useCircularInputContext } from 'react-circular-input';
import { ValueStyle } from "./DimmerStyle"; import React from 'react';
import React from "react"; import { ValueStyle } from './DimmerStyle';
export const editButtonStyle = { export const editButtonStyle = {
position: "absolute", position: 'absolute',
top: "0", top: '0',
right: "0", right: '0',
backgroundColor: "#505bda", backgroundColor: '#505bda',
borderRadius: "0 0 0 20px", borderRadius: '0 0 0 20px',
border: "none", border: 'none',
padding: ".4rem 1.2rem", padding: '.4rem 1.2rem',
outline: "none", outline: 'none',
color: "white", color: 'white',
fontFamily: "Lato", fontFamily: 'Lato',
textTransform: "uppercase", textTransform: 'uppercase',
}; };
export const panelStyle = { export const panelStyle = {
backgroundColor: "#fafafa", backgroundColor: '#fafafa',
height: "85vh", height: '85vh',
padding: "0rem 3rem", padding: '0rem 3rem',
color: "#000000", color: '#000000',
overflow: "auto", overflow: 'auto',
maxHeight: "75vh", maxHeight: '75vh',
}; };
export const mobilePanelStyle = { export const mobilePanelStyle = {
backgroundColor: "#fafafa", backgroundColor: '#fafafa',
minHeight: "100vh", minHeight: '100vh',
padding: "0rem 3rem", padding: '0rem 3rem',
color: "#000000", color: '#000000',
}; };
export const editModeStyle = { export const editModeStyle = {
position: "absolute", position: 'absolute',
top: "15%", top: '15%',
right: "0", right: '0',
width: "1.5rem", width: '1.5rem',
height: "1.5rem", height: '1.5rem',
backgroundColor: "black", backgroundColor: 'black',
borderRadius: "100%", borderRadius: '100%',
zIndex: "1000", zIndex: '1000',
cursor: "pointer", cursor: 'pointer',
}; };
export const editModeStyleLeft = { export const editModeStyleLeft = {
position: "absolute", position: 'absolute',
top: "15%", top: '15%',
left: "0", left: '0',
width: "1.5rem", width: '1.5rem',
height: "1.5rem", height: '1.5rem',
backgroundColor: "white", backgroundColor: 'white',
borderRadius: "100%", borderRadius: '100%',
zIndex: "1000", zIndex: '1000',
cursor: "pointer", cursor: 'pointer',
}; };
export const editModeIconStyle = { export const editModeIconStyle = {
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
transform: "translate(-50%, -50%)", transform: 'translate(-50%, -50%)',
width: "0.75rem", width: '0.75rem',
height: "0.75rem", height: '0.75rem',
borderRadius: "20%", borderRadius: '20%',
zIndex: "101", zIndex: '101',
}; };
export const iconStyle = { export const iconStyle = {
width: "3.5rem", width: '3.5rem',
height: "auto", height: 'auto',
position: "absolute", position: 'absolute',
top: "10%", top: '10%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
userSelect: 'none',
}; };
export const nameStyle = { export const nameStyle = {
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
transform: "translateX(-50%)", transform: 'translateX(-50%)',
}; };
export const formStyle = { export const formStyle = {
position: "absolute", position: 'absolute',
zIndex: "1000", zIndex: '1000',
width: "80rem", width: '80rem',
height: "10rem", height: '10rem',
padding: "1rem", padding: '1rem',
margin: "1rem", margin: '1rem',
borderRadius: "10%", borderRadius: '10%',
boxShadow: "1px 1px 5px 2px #5d5d5d", boxShadow: '1px 1px 5px 2px #5d5d5d',
backgroundColor: "#3e99ff", backgroundColor: '#3e99ff',
}; };
export const addDeviceFormStyle = { export const addDeviceFormStyle = {
maxWidth: "400px", maxWidth: '400px',
background: "#3e99ff", background: '#3e99ff',
paddingRight: "5rem", paddingRight: '5rem',
}; };
export const StyledDiv = styled.div` export const StyledDiv = styled.div`

View File

@ -0,0 +1,69 @@
function getStateOrDevice(state, ownProps) {
switch (state.active.activeTab) {
case 'Devices':
return state.devices[ownProps.id];
case 'Scenes':
return state.sceneStates[ownProps.id];
case 'Hosts':
return state.hostDevices[ownProps.hostId][ownProps.id];
default:
throw new Error(
`stateOrDevice has no value in tab "${state.active.activeTab}"`,
);
}
}
function getDevice(state, ownProps) {
switch (state.active.activeTab) {
case 'Scenes':
return state.devices[getStateOrDevice(state, ownProps).deviceId];
case 'Devices':
case 'Hosts':
return getStateOrDevice(state, ownProps);
default:
throw new Error(`device has no value in tab "${state.active.activeTab}"`);
}
}
function getRoomName(state, ownProps) {
switch (state.active.activeTab) {
case 'Scenes':
case 'Devices':
return (state.rooms[getDevice(state, ownProps).roomId] || {}).name;
case 'Hosts':
const hostRooms = state.hostRooms[ownProps.hostId];
if (!hostRooms) return '';
const room = hostRooms[getDevice(state, ownProps).roomId];
if (!room) return '';
return room.name;
default:
throw new Error(
`room name has no value in tab "${state.active.activeTab}"`,
);
}
}
export default function mapStateToProps(state, ownProps) {
return {
activeHost: state.active.activeHost,
get stateOrDevice() {
return getStateOrDevice(state, ownProps);
},
get device() {
return getDevice(state, ownProps);
},
get roomName() {
return getRoomName(state, ownProps);
},
get type() {
return getDevice(state, ownProps).kind;
},
get disabled() {
return (
ownProps.tab === 'Hosts'
&& ['dimmableLight', 'light'].indexOf(getDevice(state, ownProps).kind)
=== -1
);
},
};
}

View File

@ -3,17 +3,17 @@
* @returns {String} endpoint URL * @returns {String} endpoint URL
*/ */
export function endpointURL() { export function endpointURL() {
return window.BACKEND_URL !== "__BACKEND_URL__" return window.BACKEND_URL !== '__BACKEND_URL__'
? window.BACKEND_URL ? window.BACKEND_URL
: "http://localhost:8080"; : 'http://localhost:8080';
} }
export function socketURL(token) { export function socketURL(token) {
const httpURL = new URL(endpointURL()); const httpURL = new URL(endpointURL());
const isSecure = httpURL.protocol === "https:"; const isSecure = httpURL.protocol === 'https:';
const protocol = isSecure ? "wss:" : "ws:"; const protocol = isSecure ? 'wss:' : 'ws:';
const port = httpURL.port || (isSecure ? 443 : 80); const port = httpURL.port || (isSecure ? 443 : 80);
const url = `${protocol}//${httpURL.hostname}:${port}/sensor-socket?token=${token}`; const url = `${protocol}//${httpURL.hostname}:${port}/sensor-socket?token=${token}`;
console.log("socket url: ", url); console.log('socket url: ', url);
return url; return url;
} }

View File

@ -1,9 +1,9 @@
import React from "react"; import React from 'react';
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom';
import App from "./App"; import { Provider } from 'react-redux';
import * as serviceWorker from "./serviceWorker"; import App from './App';
import { Provider } from "react-redux"; import * as serviceWorker from './serviceWorker';
import smartHutStore from "./store"; import smartHutStore from './store';
const index = ( const index = (
<Provider store={smartHutStore}> <Provider store={smartHutStore}>
@ -11,5 +11,5 @@ const index = (
</Provider> </Provider>
); );
ReactDOM.render(index, document.getElementById("root")); ReactDOM.render(index, document.getElementById('root'));
serviceWorker.unregister(); serviceWorker.unregister();

File diff suppressed because it is too large Load Diff

View File

@ -11,17 +11,17 @@
// opt-in, read https://bit.ly/CRA-PWA // opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === "localhost" || window.location.hostname === 'localhost'
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" || || window.location.hostname === '[::1]'
// 127.0.0.0/8 are considered localhost for IPv4. // 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match( || window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
) ),
); );
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
@ -31,7 +31,7 @@ export function register(config) {
return; return;
} }
window.addEventListener("load", () => { window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) { if (isLocalhost) {
@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
"This web app is being served cache-first by a service " + 'This web app is being served cache-first by a service '
"worker. To learn more, visit https://bit.ly/CRA-PWA" + 'worker. To learn more, visit https://bit.ly/CRA-PWA',
); );
}); });
} else { } else {
@ -64,14 +64,14 @@ function registerValidSW(swUrl, config) {
return; return;
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") { if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched, // At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
"New content is available and will be used when all " + 'New content is available and will be used when all '
"tabs for this page are closed. See https://bit.ly/CRA-PWA." + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
); );
// Execute callback // Execute callback
@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log("Content is cached for offline use."); console.log('Content is cached for offline use.');
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
@ -94,21 +94,21 @@ function registerValidSW(swUrl, config) {
}; };
}) })
.catch((error) => { .catch((error) => {
console.error("Error during service worker registration:", error); console.error('Error during service worker registration:', error);
}); });
} }
function checkValidServiceWorker(swUrl, config) { function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, { fetch(swUrl, {
headers: { "Service-Worker": "script" }, headers: { 'Service-Worker': 'script' },
}) })
.then((response) => { .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type"); const contentType = response.headers.get('content-type');
if ( if (
response.status === 404 || response.status === 404
(contentType != null && contentType.indexOf("javascript") === -1) || (contentType != null && contentType.indexOf('javascript') === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => { navigator.serviceWorker.ready.then((registration) => {
@ -123,13 +123,13 @@ function checkValidServiceWorker(swUrl, config) {
}) })
.catch(() => { .catch(() => {
console.log( console.log(
"No internet connection found. App is running in offline mode." 'No internet connection found. App is running in offline mode.',
); );
}); });
} }
export function unregister() { export function unregister() {
if ("serviceWorker" in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready navigator.serviceWorker.ready
.then((registration) => { .then((registration) => {
registration.unregister(); registration.unregister();

View File

@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect"; import '@testing-library/jest-dom/extend-expect';

View File

@ -1,11 +1,12 @@
import { createStore, applyMiddleware, compose } from "redux"; import { createStore, applyMiddleware, compose } from 'redux';
import thunk from "redux-thunk"; import thunk from 'redux-thunk';
import update from "immutability-helper"; import update from 'immutability-helper';
import reduxWebSocket, { connect } from "@giantmachines/redux-websocket"; import reduxWebSocket, { connect } from '@giantmachines/redux-websocket';
import { socketURL } from "./endpoint"; import { socketURL } from './endpoint';
function reducer(previousState, action) { function reducer(previousState, action) {
let newState, change; let newState; let
change;
const createOrUpdateRoom = (room) => { const createOrUpdateRoom = (room) => {
if (!newState.rooms[room.id]) { if (!newState.rooms[room.id]) {
@ -48,6 +49,8 @@ function reducer(previousState, action) {
scenes: { scenes: {
[scene.id]: { [scene.id]: {
name: { $set: scene.name }, name: { $set: scene.name },
icon: { $set: scene.icon },
guestAccessEnabled: { $set: scene.guestAccessEnabled },
}, },
}, },
}); });
@ -87,33 +90,60 @@ function reducer(previousState, action) {
}; };
switch (action.type) { switch (action.type) {
case "LOGIN_UPDATE": case 'LOGIN_UPDATE':
newState = update(previousState, { login: { $set: action.login } }); newState = update(previousState, { login: { $set: action.login } });
break; break;
case "USER_INFO_UPDATE": case 'USER_INFO_UPDATE':
newState = update(previousState, { userInfo: { $set: action.userInfo } }); newState = update(previousState, {
userInfo: { $set: action.userInfo },
});
break; break;
case "ROOMS_UPDATE": case 'ROOMS_UPDATE':
newState = previousState; newState = previousState;
for (const room of action.rooms) { for (const room of action.rooms) {
createOrUpdateRoom(room); createOrUpdateRoom(room);
} }
break; break;
case "SCENES_UPDATE": case 'HOST_ROOMS_UPDATE':
change = {
hostRooms: {
[action.hostId]: { $set: {} },
},
};
const rooms = change.hostRooms[action.hostId].$set;
for (const room of action.rooms) {
rooms[room.id] = room;
}
newState = update(previousState, change);
break;
case 'SCENES_UPDATE':
newState = previousState; newState = previousState;
for (const scene of action.scenes) { for (const scene of action.scenes) {
createOrUpdateScene(scene); createOrUpdateScene(scene);
} }
break; break;
case "STATES_UPDATE": case 'HOST_SCENES_UPDATE':
//console.log(action.sceneStates); change = {
hostScenes: {
[action.hostId]: { $set: action.scenes }, // stored as array
},
};
newState = update(previousState, change);
break;
case 'STATES_UPDATE':
// console.log(action.sceneStates);
change = null; change = null;
// if room is given, delete all devices in that room // if scene is given, delete all sceneStates in that scene
// and remove any join between that room and deleted // and remove any join between that scene and deleted
// devices // sceneStates
change = { change = {
scenes: { [action.sceneId]: { sceneStates: { $set: new Set() } } }, scenes: {
[action.sceneId]: { sceneStates: { $set: new Set() } },
},
sceneStates: { $unset: [] }, sceneStates: { $unset: [] },
}; };
@ -140,11 +170,9 @@ function reducer(previousState, action) {
} }
if (sceneState.sceneId in newState.scenes) { if (sceneState.sceneId in newState.scenes) {
change.scenes[sceneState.sceneId] = change.scenes[sceneState.sceneId] = change.scenes[sceneState.sceneId] || {};
change.scenes[sceneState.sceneId] || {}; change.scenes[sceneState.sceneId].sceneStates = change.scenes[sceneState.sceneId].sceneStates || {};
change.scenes[sceneState.sceneId].sceneStates = const { sceneStates } = change.scenes[sceneState.sceneId];
change.scenes[sceneState.sceneId].sceneStates || {};
const sceneStates = change.scenes[sceneState.sceneId].sceneStates;
sceneStates.$add = sceneStates.$add || []; sceneStates.$add = sceneStates.$add || [];
sceneStates.$add.push(sceneState.id); sceneStates.$add.push(sceneState.id);
} else { } else {
@ -157,7 +185,7 @@ function reducer(previousState, action) {
}; };
} else { } else {
change.pendingJoins.scenes[sceneState.sceneId].$set.add( change.pendingJoins.scenes[sceneState.sceneId].$set.add(
sceneState.id sceneState.id,
); );
} }
} }
@ -166,7 +194,7 @@ function reducer(previousState, action) {
newState = update(newState, change); newState = update(newState, change);
break; break;
case "DEVICES_UPDATE": case 'DEVICES_UPDATE':
change = null; change = null;
// if room is given, delete all devices in that room // if room is given, delete all devices in that room
@ -174,7 +202,9 @@ function reducer(previousState, action) {
// devices // devices
if (action.roomId) { if (action.roomId) {
change = { change = {
rooms: { [action.roomId]: { devices: { $set: new Set() } } }, rooms: {
[action.roomId]: { devices: { $set: new Set() } },
},
devices: { $unset: [] }, devices: { $unset: [] },
}; };
@ -194,7 +224,7 @@ function reducer(previousState, action) {
for (const device of action.devices) { for (const device of action.devices) {
if (!previousState.devices[device.id]) continue; if (!previousState.devices[device.id]) continue;
change.devices.$unset.push(device.id); change.devices.$unset.push(device.id);
const roomId = previousState.devices[device.id].roomId; const { roomId } = previousState.devices[device.id];
if (roomId in previousState.rooms) { if (roomId in previousState.rooms) {
change.rooms[roomId] = change.rooms[roomId] || { change.rooms[roomId] = change.rooms[roomId] || {
@ -234,9 +264,8 @@ function reducer(previousState, action) {
if (device.roomId in newState.rooms) { if (device.roomId in newState.rooms) {
change.rooms[device.roomId] = change.rooms[device.roomId] || {}; change.rooms[device.roomId] = change.rooms[device.roomId] || {};
change.rooms[device.roomId].devices = change.rooms[device.roomId].devices = change.rooms[device.roomId].devices || {};
change.rooms[device.roomId].devices || {}; const { devices } = change.rooms[device.roomId];
const devices = change.rooms[device.roomId].devices;
devices.$add = devices.$add || []; devices.$add = devices.$add || [];
devices.$add.push(device.id); devices.$add.push(device.id);
} else { } else {
@ -255,9 +284,27 @@ function reducer(previousState, action) {
newState = update(newState, change); newState = update(newState, change);
break; break;
case 'HOST_DEVICES_UPDATE':
newState = action.partial
? previousState
: update(previousState, {
hostDevices: { [action.hostId]: { $set: {} } },
});
newState.hostDevices[action.hostId] = newState.hostDevices[action.hostId] || {};
change = {
hostDevices: {
[action.hostId]: {},
},
};
const deviceMap = change.hostDevices[action.hostId];
case "AUTOMATION_UPDATE": for (const device of action.devices) {
newState = previousState; deviceMap[device.id] = { $set: device };
}
newState = update(newState, change);
break;
case 'AUTOMATION_UPDATE':
const automations = {}; const automations = {};
for (const automation of action.automations) { for (const automation of action.automations) {
automations[automation.id] = automation; automations[automation.id] = automation;
@ -268,15 +315,15 @@ function reducer(previousState, action) {
}; };
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "ROOM_SAVE": case 'ROOM_SAVE':
newState = previousState; newState = previousState;
createOrUpdateRoom(action.room); createOrUpdateRoom(action.room);
break; break;
case "SCENE_SAVE": case 'SCENE_SAVE':
newState = previousState; newState = previousState;
createOrUpdateScene(action.scene); createOrUpdateScene(action.scene);
break; break;
case "DEVICE_SAVE": case 'DEVICE_SAVE':
change = { change = {
devices: { [action.device.id]: { $set: action.device } }, devices: { [action.device.id]: { $set: action.device } },
}; };
@ -300,24 +347,48 @@ function reducer(previousState, action) {
} }
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case 'HOST_DEVICE_SAVE':
case "AUTOMATION_SAVE":
console.log("ID: ", action.automation.id);
change = { change = {
automations: { [action.automation.id]: { $set: action.automation } }, hostDevices: {
[action.hostId]: {
[action.device.id]: {
$set: action.device,
},
},
},
};
newState = update(previousState, change);
break;
case 'HOST_DEVICES_DELETE':
change = {
hostDevices: {
[action.hostId]: {
$unset: [action.deviceIds],
},
},
};
newState = update(previousState, change);
break;
case 'AUTOMATION_SAVE':
change = {
automations: {
[action.automation.id]: { $set: action.automation },
},
}; };
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "STATE_SAVE": case 'STATE_SAVE':
change = { change = {
sceneStates: { [action.sceneState.id]: { $set: action.sceneState } }, sceneStates: {
[action.sceneState.id]: { $set: action.sceneState },
},
}; };
if (previousState.scenes[action.sceneState.sceneId]) { if (previousState.scenes[action.sceneState.sceneId]) {
console.log("PREVSTATE", change, previousState);
change.scenes = { change.scenes = {
[action.sceneState.sceneId]: { [action.sceneState.sceneId]: {
sceneStates: { sceneStates: {
@ -335,9 +406,8 @@ function reducer(previousState, action) {
}; };
} }
newState = update(previousState, change); newState = update(previousState, change);
console.log("NEWSTATE ", newState);
break; break;
case "ROOM_DELETE": case 'ROOM_DELETE':
if (!(action.roomId in previousState.rooms)) { if (!(action.roomId in previousState.rooms)) {
console.warn(`Room to delete ${action.roomId} does not exist`); console.warn(`Room to delete ${action.roomId} does not exist`);
break; break;
@ -361,18 +431,14 @@ function reducer(previousState, action) {
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "AUTOMATION_DELETE": case 'AUTOMATION_DELETE':
change = { change = {
automations: { $unset: [action.id] }, automations: { $unset: [action.id] },
}; };
console.log("CHANGE ", change);
console.log("Action id: ", action.id);
newState = update(previousState, change); newState = update(previousState, change);
console.log("NEW STATE ", newState);
break; break;
case "SCENE_DELETE": case 'SCENE_DELETE':
console.log("SCENE", action.sceneId);
if (!(action.sceneId in previousState.scenes)) { if (!(action.sceneId in previousState.scenes)) {
console.warn(`Scene to delete ${action.sceneId} does not exist`); console.warn(`Scene to delete ${action.sceneId} does not exist`);
break; break;
@ -381,7 +447,7 @@ function reducer(previousState, action) {
// This update does not ensure the consistent update of switchId/dimmerId properties // This update does not ensure the consistent update of switchId/dimmerId properties
// on output devices connected to an input device in this room. Please manually request // on output devices connected to an input device in this room. Please manually request
// all devices again if consistent update is desired // all devices again if consistent update is desired
change = { states: { $unset: [] } }; change = { sceneStates: { $unset: [] } };
for (const id of previousState.scenes[action.sceneId].sceneStates) { for (const id of previousState.scenes[action.sceneId].sceneStates) {
change.sceneStates.$unset.push(id); change.sceneStates.$unset.push(id);
@ -395,7 +461,7 @@ function reducer(previousState, action) {
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "STATE_DELETE": case 'STATE_DELETE':
if (!(action.stateId in previousState.sceneStates)) { if (!(action.stateId in previousState.sceneStates)) {
console.warn(`State to delete ${action.stateId} does not exist`); console.warn(`State to delete ${action.stateId} does not exist`);
break; break;
@ -418,16 +484,7 @@ function reducer(previousState, action) {
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "SCENE_APPLY": case 'DEVICE_DELETE':
console.log(action);
//checking that the scene actually exists
/*if (!(action.sceneId in previousState.scenes)) {
console.warn(`Scene ${action.sceneId} does not exist`);
break;
}*/
break;
case "DEVICE_DELETE":
if (!(action.deviceId in previousState.devices)) { if (!(action.deviceId in previousState.devices)) {
console.warn(`Device to delete ${action.deviceId} does not exist`); console.warn(`Device to delete ${action.deviceId} does not exist`);
break; break;
@ -447,59 +504,69 @@ function reducer(previousState, action) {
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "LOGOUT": case 'LOGOUT':
newState = update(initState, {}); newState = update(initState, {});
break; break;
case "SET_ACTIVE_ROOM": case 'SET_ACTIVE':
newState = update(previousState, { newState = update(previousState, {
active: { active: {
activeRoom: { [action.key]: {
$set: action.activeRoom, $set: action.value,
}, },
}, },
}); });
break; break;
case "SET_ACTIVE_TAB": case 'REDUX_WEBSOCKET::MESSAGE':
newState = update(previousState, { const allDevices = JSON.parse(action.payload.message);
active: { const devices = allDevices.filter(
activeTab: { (d) => (d.fromHostId === null || d.fromHostId === undefined) && !d.deleted,
$set: action.activeTab, );
}, const hostDevicesMapByHostId = allDevices
}, .filter((d) => d.fromHostId)
}); .reduce((a, e) => {
break; const hostId = e.fromHostId;
case "SET_ACTIVE_SCENE": // delete e.fromHostId;
newState = update(previousState, { a[hostId] = a[hostId] || { updated: [], deletedIds: [] };
active: { if (e.deleted) {
activeScene: { a[hostId].deletedIds.push(e.id);
$set: action.activeScene, } else {
}, a[hostId].updated.push(e);
}, }
}); return a;
break; }, {});
case "SET_ACTIVE_AUTOMATION":
newState = update(previousState, {
active: {
activeAutomation: {
$set: action.activeAutomation,
},
},
});
break;
case "REDUX_WEBSOCKET::MESSAGE":
const devices = JSON.parse(action.payload.message);
newState = reducer(previousState, { newState = reducer(previousState, {
type: "DEVICES_UPDATE", type: 'DEVICES_UPDATE',
partial: true, partial: true,
devices, devices,
}); });
for (const hostId in hostDevicesMapByHostId) {
if (hostDevicesMapByHostId[hostId].updated.length > 0) {
newState = reducer(newState, {
type: 'HOST_DEVICES_UPDATE',
devices: hostDevicesMapByHostId[hostId].updated,
partial: true,
hostId,
});
}
if (hostDevicesMapByHostId[hostId].deletedIds.length > 0) {
newState = reducer(newState, {
type: 'HOST_DEVICES_DELETE',
deviceIds: hostDevicesMapByHostId[hostId].deletedIds,
partial: true,
hostId,
});
}
}
break;
case 'HG_UPDATE':
newState = update(previousState, {
[action.key]: { $set: action.value },
});
break; break;
default: default:
console.warn(`Action type ${action.type} unknown`, action); console.warn(`Action type ${action.type} unknown`, action);
return previousState; return previousState;
} }
console.log("THETRUEALPACA", newState, action.type, action);
return newState; return newState;
} }
@ -511,9 +578,10 @@ const initState = {
}, },
active: { active: {
activeRoom: -1, activeRoom: -1,
activeTab: "Devices", activeTab: 'Devices',
activeScene: -1, activeScene: -1,
activeAutomation: -1, activeAutomation: -1,
activeHost: -1,
}, },
login: { login: {
loggedIn: false, loggedIn: false,
@ -524,17 +592,26 @@ const initState = {
rooms: {}, rooms: {},
/** @type {[integer]Scene} */ /** @type {[integer]Scene} */
scenes: {}, scenes: {},
hostScenes: {},
/** @type {[integer]Automation} */ /** @type {[integer]Automation} */
automations: {}, automations: {},
/** @type {[integer]Device} */ /** @type {[integer]Device} */
devices: {}, devices: {},
/** @type {[integer]SceneState} */ /** @type {[integer]SceneState} */
sceneStates: {}, sceneStates: {},
/** @type {User[]} */
guests: [],
/** @type {User[]} */
hosts: [],
/** @type {[integer]Device} */
hostDevices: {},
/** @type {[integer]Eoom} */
hostRooms: {},
}; };
function createSmartHutStore() { function createSmartHutStore() {
const token = localStorage.getItem("token"); const token = localStorage.getItem('token');
const exp = localStorage.getItem("exp"); const exp = localStorage.getItem('exp');
const initialState = update(initState, { const initialState = update(initState, {
login: { login: {
@ -544,15 +621,15 @@ function createSmartHutStore() {
}); });
if (!initialState.login.loggedIn) { if (!initialState.login.loggedIn) {
localStorage.removeItem("token"); localStorage.removeItem('token');
localStorage.removeItem("exp"); localStorage.removeItem('exp');
initialState.login.token = null; initialState.login.token = null;
} }
const store = createStore( const store = createStore(
reducer, reducer,
initialState, initialState,
compose(applyMiddleware(thunk), applyMiddleware(reduxWebSocket())) compose(applyMiddleware(thunk), applyMiddleware(reduxWebSocket())),
); );
if (initialState.login.loggedIn) { if (initialState.login.loggedIn) {
store.dispatch(connect(socketURL(token))); store.dispatch(connect(socketURL(token)));

View File

@ -1,119 +1,159 @@
const actions = { const actions = {
loginSuccess: (token) => ({ loginSuccess: (token) => ({
type: "LOGIN_UPDATE", type: 'LOGIN_UPDATE',
login: { login: {
loggedIn: true, loggedIn: true,
token, token,
}, },
}), }),
logout: () => ({ logout: () => ({
type: "LOGOUT", type: 'LOGOUT',
}), }),
userInfoUpdate: (userInfo) => ({ userInfoUpdate: (userInfo) => ({
type: "USER_INFO_UPDATE", type: 'USER_INFO_UPDATE',
userInfo, userInfo,
}), }),
roomSave: (room) => ({ roomSave: (room) => ({
type: "ROOM_SAVE", type: 'ROOM_SAVE',
room, room,
}), }),
sceneSave: (scene) => ({ sceneSave: (scene) => ({
type: "SCENE_SAVE", type: 'SCENE_SAVE',
scene, scene,
}), }),
deviceSave: (device) => ({ deviceSave: (device) => ({
type: "DEVICE_SAVE", type: 'DEVICE_SAVE',
device,
}),
hostDeviceSave: (hostId, device) => ({
type: 'HOST_DEVICE_SAVE',
hostId,
device, device,
}), }),
triggerSave: (automation) => ({ triggerSave: (automation) => ({
type: "TRIGGER_SAVE", type: 'TRIGGER_SAVE',
automation, automation,
}), }),
scenePrioritySave: (automation) => ({ scenePrioritySave: (automation) => ({
type: "SCENE_PRIORITY_SAVE", type: 'SCENE_PRIORITY_SAVE',
automation, automation,
}), }),
automationSave: (automation) => ({ automationSave: (automation) => ({
type: "AUTOMATION_SAVE", type: 'AUTOMATION_SAVE',
automation, automation,
}), }),
automationsUpdate: (automations) => ({ automationsUpdate: (automations) => ({
type: "AUTOMATION_UPDATE", type: 'AUTOMATION_UPDATE',
automations, automations,
}), }),
stateSave: (sceneState) => ({ stateSave: (sceneState) => ({
type: "STATE_SAVE", type: 'STATE_SAVE',
sceneState, sceneState,
}), }),
statesUpdate: (sceneId, sceneStates) => ({ statesUpdate: (sceneId, sceneStates) => ({
type: "STATES_UPDATE", type: 'STATES_UPDATE',
sceneId, sceneId,
sceneStates, sceneStates,
}), }),
devicesUpdate: (roomId, devices, partial = false) => ({ devicesUpdate: (roomId, devices, partial = false) => ({
type: "DEVICES_UPDATE", type: 'DEVICES_UPDATE',
roomId, roomId,
devices, devices,
partial, partial,
}), }),
hostDevicesUpdate: (hostId, devices, partial = false) => ({
type: 'HOST_DEVICES_UPDATE',
hostId,
partial,
devices,
}),
stateDelete: (stateId) => ({ stateDelete: (stateId) => ({
type: "STATE_DELETE", type: 'STATE_DELETE',
stateId, stateId,
}), }),
sceneApply: (sceneId) => ({
type: "SCENE_APPLY",
sceneId,
}),
deviceOperationUpdate: (devices) => ({ deviceOperationUpdate: (devices) => ({
type: "DEVICES_UPDATE", type: 'DEVICES_UPDATE',
devices, devices,
partial: true, partial: true,
}), }),
roomsUpdate: (rooms) => ({ roomsUpdate: (rooms) => ({
type: "ROOMS_UPDATE", type: 'ROOMS_UPDATE',
rooms,
}),
hostRoomsUpdate: (hostId, rooms) => ({
type: 'HOST_ROOMS_UPDATE',
hostId,
rooms, rooms,
}), }),
roomDelete: (roomId) => ({ roomDelete: (roomId) => ({
type: "ROOM_DELETE", type: 'ROOM_DELETE',
roomId, roomId,
}), }),
automationDelete: (id) => ({ automationDelete: (id) => ({
type: "AUTOMATION_DELETE", type: 'AUTOMATION_DELETE',
id, id,
}), }),
sceneDelete: (sceneId) => ({ sceneDelete: (sceneId) => ({
type: "SCENE_DELETE", type: 'SCENE_DELETE',
sceneId, sceneId,
}), }),
scenesUpdate: (scenes) => ({ scenesUpdate: (scenes) => ({
type: "SCENES_UPDATE", type: 'SCENES_UPDATE',
scenes,
}),
hostScenesUpdate: (hostId, scenes) => ({
type: 'HOST_SCENES_UPDATE',
hostId,
scenes, scenes,
}), }),
deviceDelete: (deviceId) => ({ deviceDelete: (deviceId) => ({
type: "DEVICE_DELETE", type: 'DEVICE_DELETE',
deviceId, deviceId,
}), }),
hostsUpdate: (hosts) => ({
type: 'HG_UPDATE',
key: 'hosts',
value: hosts,
}),
guestsUpdate: (hosts) => ({
type: 'HG_UPDATE',
key: 'guests',
value: hosts,
}),
getHostDevices: (host) => ({
type: 'GET_HOST_DEVICES',
host,
}),
guestUpdate: (guests) => ({
type: 'HG_UPDATE',
key: 'guests',
value: guests,
}),
}; };
export const appActions = { export const appActions = {
// -1 for home view // -1 for home view
setActiveRoom: (activeRoom = -1) => ({ setActiveRoom: (activeRoom = -1) => ({
type: "SET_ACTIVE_ROOM", type: 'SET_ACTIVE',
activeRoom, key: 'activeRoom',
value: activeRoom,
}), }),
setActiveTab: (activeTab) => ({ setActiveTab: (activeTab) => ({
type: "SET_ACTIVE_TAB", type: 'SET_ACTIVE',
activeTab, key: 'activeTab',
value: activeTab,
}), }),
setActiveScene: (activeScene = -1) => ({ setActiveScene: (activeScene = -1) => ({
type: "SET_ACTIVE_SCENE", type: 'SET_ACTIVE',
activeScene, key: 'activeScene',
value: activeScene,
}), }),
setActiveAutomations: (activeAutomation = -1) => ({ setActiveHost: (activeHost = -1) => ({
type: "SET_ACTIVE_AUTOMATION", type: 'SET_ACTIVE',
activeAutomation, key: 'activeHost',
value: activeHost,
}), }),
}; };

View File

@ -1,165 +0,0 @@
import React, { Component } from "react";
import {
Menu,
Button,
Grid,
Icon,
Responsive,
Dropdown,
} from "semantic-ui-react";
import { editButtonStyle } from "../components/dashboard/devices/styleComponents";
import AutomationModal from "../components/AutomationModal";
import { RemoteService } from "../remote";
import { connect } from "react-redux";
import { appActions } from "../storeActions";
class AutomationsNavbar extends Component {
constructor(props) {
super(props);
this.state = {
editMode: false,
};
this.toggleEditMode = this.toggleEditMode.bind(this);
this.openCurrentModalMobile = this.openCurrentModalMobile.bind(this);
}
get activeItemAutomation() {
return this.props.activeAutomation;
}
set activeItemAutomation(item) {
this.props.setActiveAutomation(item);
}
get activeItemAutomationsName() {
if (this.props.activeAutomation === -1) return "Home";
return this.props.automations[this.props.activeAutomation].name;
}
openCurrentModalMobile() {
console.log(this.activeItemAutomation, this.props.automationsModalRefs);
const currentModal = this.props.automationsModalRefs[
this.activeItemAutomation
].current;
currentModal.openModal();
}
toggleEditMode(e) {
this.setState((prevState) => ({ editMode: !prevState.editMode }));
}
render() {
return (
<div style={{ background: "#1b1c1d!important", padding: "0 20px" }}>
<Responsive minWidth={768}>
<Grid>
<Grid.Row color="black">
<button style={editButtonStyle} onClick={this.toggleEditMode}>
Edit
</button>
</Grid.Row>
<Grid.Row>
<Menu inverted fluid vertical>
<Menu.Item
key={-1}
id={null}
name="automations"
active={this.activeItemAutomation === -1}
onClick={this.selectAutomations}
>
<Grid>
<Grid.Row>
<Grid.Column>
<Icon name="home" size="small" />
</Grid.Column>
<Grid.Column>AUTOMATIONS</Grid.Column>
</Grid.Row>
</Grid>
</Menu.Item>
{
//INSERT LIST OF AUTOMATIONS HERE
}
<Menu.Item name="newM">
<Grid>
<Grid.Row centered name="new">
<AutomationModal id={null} />
</Grid.Row>
</Grid>
</Menu.Item>
</Menu>
</Grid.Row>
</Grid>
</Responsive>
<Responsive maxWidth={768}>
<Menu>
<Dropdown item fluid text={this.activeItemAutomationName}>
<Dropdown.Menu>
<Dropdown.Item
key={-1}
id={null}
name="scene"
active={this.activeItemAutomation === -1}
onClick={this.selectAutomations}
>
<Grid>
<Grid.Row>
<Grid.Column>
<Icon name="home" size="small" />
</Grid.Column>
<Grid.Column>Automations</Grid.Column>
</Grid.Row>
</Grid>
</Dropdown.Item>
{
//INSERT LIST OF AUTOMATIONS HERE
}
</Dropdown.Menu>
</Dropdown>
</Menu>
<Grid inverted>
<Grid.Row>
<Grid.Column width={8}>
<AutomationModal id={null} />
</Grid.Column>
{this.activeItemAutomation !== -1 ? (
<Grid.Column width={8}>
<Button
icon
fluid
labelPosition="left"
onClick={this.openCurrentModalMobile}
>
<Icon name="pencil" size="small" />
EDIT AUTOMATION
</Button>
</Grid.Column>
) : null}
</Grid.Row>
</Grid>
</Responsive>
</div>
);
}
}
const setActiveAutomation = (activeAutomation) => {
return (dispatch) =>
dispatch(appActions.setActiveAutomation(activeAutomation));
};
const mapStateToProps = (state, _) => ({
automations: state.automations,
activeAutomation: state.active.activeAutomation,
automationModalRefs: Object.keys(state.automations).reduce(
(acc, key) => ({ ...acc, [key]: React.createRef() }),
{}
),
});
const AutomationsNavbarContainer = connect(mapStateToProps, {
...RemoteService,
setActiveAutomation,
})(AutomationsNavbar);
export default AutomationsNavbarContainer;

View File

@ -0,0 +1,39 @@
import React, { Component } from 'react';
import {
Image,
Grid,
Button,
Icon,
Header,
Container,
} from 'semantic-ui-react';
export default class Confirm extends Component {
render() {
return (
<>
<Button circular style={{ margin: '2em' }} href="/">
<Icon name="arrow alternate circle left" />
Go Home
{' '}
</Button>
<Grid
textAlign="center"
style={{ height: '70vh' }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" />
{' '}
Congratulation!
</Header>
<Container textAlign="center">
<p>{this.props.msg}</p>
</Container>
</Grid.Column>
</Grid>
</>
);
}
}

View File

@ -1,40 +1,9 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import Confirm from './Confirm';
Image,
Grid,
Button,
Icon,
Header,
Container,
} from "semantic-ui-react";
const msg = "An E-mail has been sent to your address, confirm your registration by following the enclosed link. If you don't find the E-mail please check also the spam folder.";
export default class ConfirmForgotPasswrod extends Component { export default class ConfirmForgotPasswrod extends Component {
render() { render() {
return ( return <Confirm msg={msg} />;
<React.Fragment>
<Button circular style={{ margin: "2em" }} href="/">
<Icon name="arrow alternate circle left" />
Go Home{" "}
</Button>
<Grid
textAlign="center"
style={{ height: "70vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Link has been sent!
</Header>
<Container textAlign="center">
<p>
An E-mail has been sent to your address, please follow the
instructions to create a new password. If you don't find the
E-mail please check also the spam folder.
</p>
</Container>
</Grid.Column>
</Grid>
</React.Fragment>
);
} }
} }

View File

@ -1,40 +1,9 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import Confirm from './Confirm';
Image,
Grid,
Button,
Icon,
Header,
Container,
} from "semantic-ui-react";
const msg = "An E-mail has been sent to your address, confirm your registration by following the enclosed link. If you don't find the E-mail please check also the spam folder.";
export default class ConfirmRegistration extends Component { export default class ConfirmRegistration extends Component {
render() { render() {
return ( return <Confirm msg={msg} />;
<React.Fragment>
<Button circular style={{ margin: "2em" }} href="/">
<Icon name="arrow alternate circle left" />
Go Home{" "}
</Button>
<Grid
textAlign="center"
style={{ height: "70vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Congratulation!
</Header>
<Container textAlign="center">
<p>
An E-mail has been sent to your address, confirm your
registration by following the enclosed link. If you don't find
the E-mail please check also the spam folder.
</p>
</Container>
</Grid.Column>
</Grid>
</React.Fragment>
);
} }
} }

View File

@ -1,36 +1,10 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import Confirm from './Confirm';
Image,
Grid, const msg = 'Your password has been successfully reset.';
Button,
Icon,
Header,
Container,
} from "semantic-ui-react";
export default class ConfirmResetPassword extends Component { export default class ConfirmResetPassword extends Component {
render() { render() {
return ( return <Confirm msg={msg} />;
<React.Fragment>
<Button circular style={{ margin: "2em" }} href="/">
<Icon name="arrow alternate circle left" />
Go Home{" "}
</Button>
<Grid
textAlign="center"
style={{ height: "70vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Congratulation!
</Header>
<Container textAlign="center">
<p>Your password has been successfully reset.</p>
</Container>
</Grid.Column>
</Grid>
</React.Fragment>
);
} }
} }

View File

@ -1,27 +1,26 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import DevicePanel from "../components/dashboard/DevicePanel";
import ScenesPanel from "../components/dashboard/ScenesPanel";
import AutomationsPanel from "../components/dashboard/AutomationsPanel";
import Navbar from "./Navbar";
import ScenesNavbar from "./ScenesNavbar";
import AutomationsNavbar from "./AutomationsNavbar";
import MyHeader from "../components/HeaderController";
import { Grid, Responsive, Button } from "semantic-ui-react";
import { import {
panelStyle, Grid, Responsive, Button, Menu,
mobilePanelStyle, } from 'semantic-ui-react';
} from "../components/dashboard/devices/styleComponents"; import { connect } from 'react-redux';
import DevicePanel from '../components/dashboard/DevicePanel';
import ScenesPanel from '../components/dashboard/ScenesPanel';
import AutomationsPanel from '../components/dashboard/AutomationsPanel';
import HostsPanel from '../components/dashboard/HostsPanel';
import Navbar from './Navbar';
import ScenesNavbar from './ScenesNavbar';
import HostsNavbar from './HostsNavbar';
import MyHeader from '../components/HeaderController';
import { mobilePanelStyle } from '../components/dashboard/devices/styleComponents';
import { RemoteService } from "../remote"; import { RemoteService } from '../remote';
import { connect } from "react-redux"; import { appActions } from '../storeActions';
import { appActions } from "../storeActions";
class Dashboard extends Component { class Dashboard extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = this.initialState; this.state = this.initialState;
this.setInitialState(); this.activeTab = 'Devices';
this.activeTab = "Automations"; //TODO Remove this to not put automations first
this.selectTab = this.selectTab.bind(this); this.selectTab = this.selectTab.bind(this);
} }
@ -50,12 +49,14 @@ class Dashboard extends Component {
renderTab(tab) { renderTab(tab) {
switch (tab) { switch (tab) {
case "Devices": case 'Devices':
return <DevicePanel tab={this.state.activeTab} />; return <DevicePanel tab={this.state.activeTab} />;
case "Scenes": case 'Scenes':
return <ScenesPanel tab={this.state.activeTab} />; return <ScenesPanel tab={this.state.activeTab} />;
case "Automations": case 'Automations':
return <AutomationsPanel />; return <AutomationsPanel />;
case 'Hosts':
return <HostsPanel />;
default: default:
return <h1>ERROR</h1>; return <h1>ERROR</h1>;
} }
@ -63,63 +64,89 @@ class Dashboard extends Component {
renderNavbar(tab) { renderNavbar(tab) {
switch (tab) { switch (tab) {
case "Devices": case 'Devices':
return <Navbar />; return <Navbar />;
case "Scenes": case 'Scenes':
return <ScenesNavbar />; return <ScenesNavbar />;
case "Automations": case 'Hosts':
return <AutomationsNavbar />; return <HostsNavbar />;
default: default:
return <h1>ERROR</h1>; return <h1>ERROR</h1>;
} }
} }
get hasNavbar() {
return this.state.activeTab !== 'Automations';
}
render() { render() {
// needed to correctly assign the background image
// in case a room has one.
let backgroundImageHelper;
if (this.activeTab === 'Devices') {
backgroundImageHelper = this.props.backgroundImage;
} else {
backgroundImageHelper = null;
}
// console.log("helper is",helper)
return ( return (
<div style={{ background: "#1b1c1d" }}> <div style={{ background: '#1b1c1d' }}>
<Responsive minWidth={768}> <Responsive minWidth={768}>
<Grid> <Grid>
<Grid.Row color="black"> <Grid.Row color="black" style={{ paddingBottom: 0 }}>
<Grid.Column> <Grid.Column>
<MyHeader /> <MyHeader />
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
<Grid.Row color="black"> <Grid.Row color="black" style={{ paddingTop: 0 }}>
<Grid.Column width={3}></Grid.Column> <Grid.Column textAlign="center" width={16}>
<Grid.Column textAlign="center" width={13}> <Menu fluid widths={4} inverted color="grey">
<Button <Menu.Item
basic
name="Devices" name="Devices"
content="Devices" content="Devices"
active={this.activeTab === "Devices"} active={this.activeTab === 'Devices'}
color={this.activeTab === "Devices" ? "yellow" : "grey"}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Button <Menu.Item
basic
name="Scenes" name="Scenes"
content="Scenes" content="Scenes"
active={this.activeTab === "Scenes"} active={this.activeTab === 'Scenes'}
color={this.activeTab === "Scenes" ? "yellow" : "grey"}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Button <Menu.Item
basic
name="Automations" name="Automations"
content="Automations" content="Automations"
active={this.activeTab === "Automations"} active={this.activeTab === 'Automations'}
color={this.activeTab === "Automations" ? "yellow" : "grey"}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Menu.Item
name="Hosts"
content="Hosts and Guests"
active={this.activeTab === 'Hosts'}
onClick={this.selectTab}
/>
</Menu>
</Grid.Column> </Grid.Column>
</Grid.Row>
<Grid.Row color="black"> {this.hasNavbar && (
<Grid.Column width={3}> <Grid.Column width={3}>
{this.renderNavbar(this.activeTab)} {this.renderNavbar(this.activeTab)}
</Grid.Column> </Grid.Column>
)}
<Grid.Column width={13}> <Grid.Column width={this.hasNavbar ? 13 : 16}>
<div style={panelStyle}>{this.renderTab(this.activeTab)}</div> <div
style={{
backgroundImage: `url(${backgroundImageHelper})`,
backgroundColor: '#fafafa',
height: '85vh',
padding: '0rem 3rem',
color: '#000000',
overflow: 'auto',
maxHeight: '75vh',
}}
>
{this.renderTab(this.activeTab)}
</div>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>
@ -137,33 +164,45 @@ class Dashboard extends Component {
basic basic
name="Devices" name="Devices"
content="Devices" content="Devices"
active={this.activeTab === "Devices"} active={this.activeTab === 'Devices'}
color={this.activeTab === "Devices" ? "yellow" : "grey"} color={this.activeTab === 'Devices' ? 'yellow' : 'grey'}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Button <Button
basic basic
name="Scenes" name="Scenes"
content="Scenes" content="Scenes"
active={this.activeTab === "Scenes"} active={this.activeTab === 'Scenes'}
color={this.activeTab === "Scenes" ? "yellow" : "grey"} color={this.activeTab === 'Scenes' ? 'yellow' : 'grey'}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Button <Button
basic basic
name="Automations" name="Automations"
content="Automations" content="Automations"
active={this.activeTab === "Automations"} active={this.activeTab === 'Automations'}
color={this.activeTab === "Automations" ? "yellow" : "grey"} color={this.activeTab === 'Automations' ? 'yellow' : 'grey'}
onClick={this.selectTab}
/>
<Button
basic
name="Hosts"
content="Hosts"
active={this.activeTab === 'Hosts'}
color={
this.activeTab === 'Hosts and Guests' ? 'yellow' : 'grey'
}
onClick={this.selectTab} onClick={this.selectTab}
/> />
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
{this.hasNavbar && (
<Grid.Row color="black"> <Grid.Row color="black">
<Grid.Column color="black"> <Grid.Column color="black">
{this.renderNavbar(this.activeTab)} {this.renderNavbar(this.activeTab)}
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
)}
<Grid.Row> <Grid.Row>
<Grid.Column> <Grid.Column>
<div style={mobilePanelStyle}> <div style={mobilePanelStyle}>
@ -180,11 +219,18 @@ class Dashboard extends Component {
const mapStateToProps = (state, _) => ({ const mapStateToProps = (state, _) => ({
activeTab: state.active.activeTab, activeTab: state.active.activeTab,
get currentRoom() {
return state.active.activeRoom;
},
get backgroundImage() {
if (state.active.activeRoom === -1) {
return null;
}
return state.rooms[state.active.activeRoom].image;
},
}); });
const setActiveTab = (activeTab) => { const setActiveTab = (activeTab) => (dispatch) => dispatch(appActions.setActiveTab(activeTab));
return (dispatch) => dispatch(appActions.setActiveTab(activeTab));
};
const DashboardContainer = connect(mapStateToProps, { const DashboardContainer = connect(mapStateToProps, {
...RemoteService, ...RemoteService,

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Button } from "semantic-ui-react"; import { Button } from 'semantic-ui-react';
export default class Dashboard extends Component { export default class Dashboard extends Component {
handleLogOut = (e) => { handleLogOut = (e) => {
@ -9,8 +9,9 @@ export default class Dashboard extends Component {
render() { render() {
return ( return (
<Button circular style={{ margin: "2em" }} onClick={this.handleLogOut}> <Button circular style={{ margin: '2em' }} onClick={this.handleLogOut}>
Go Home{" "} Go Home
{' '}
</Button> </Button>
); );
} }

View File

@ -1,114 +0,0 @@
import React, { Component } from "react";
import {
Button,
Form,
Grid,
Header,
Image,
Icon,
Message,
} from "semantic-ui-react";
import { Redirect } from "react-router-dom";
import { Forms } from "../remote";
export default class ChangePass extends Component {
constructor(props) {
super(props);
this.state = {
password: "",
error: {
state: false,
message: "",
},
success: false,
};
this.handleChangePassword = this.handleChangePassword.bind(this);
}
onChangeHandler = (event) => {
let nam = event.target.name;
let val = event.target.value;
this.setState({ [nam]: val });
};
handleChangePassword = (e) => {
if (this.state.confirmPassword !== this.state.password) {
this.setState({
error: {
state: true,
message: "Passwords do not match.",
},
});
}
Forms.submitResetPassword(this.props.query.token, this.state.password)
.then(() => this.setState({ success: true }))
.catch((err) =>
this.setState({
error: { state: true, message: err.messages.join(" - ") },
})
);
};
render() {
if (this.state.success) {
return <Redirect to="/login" />;
}
return (
<React.Fragment>
<Button circular style={{ margin: "2em" }} href="/">
<Icon name="arrow alternate circle left" />
Go Home{" "}
</Button>
<Grid
textAlign="center"
style={{ height: "70vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Reset Password
</Header>
<Form
size="large"
style={{ marginTop: "2em" }}
error={this.state.error.state}
>
<Message
error
header="Change Password Error"
content={this.state.error.message}
/>
<Form.Input
icon="address card outline"
iconPosition="left"
placeholder="Reset your password"
name="password"
type="password"
onChange={this.onChangeHandler}
required
/>
<Form.Input
icon="address card outline"
iconPosition="left"
placeholder="Confirm Password"
name="confirmPassword"
type="password"
onChange={this.onChangeHandler}
required
/>
<Button
color="blue"
fluid
size="large"
onClick={this.handleChangePassword}
>
Confirm password
</Button>
</Form>
</Grid.Column>
</Grid>
</React.Fragment>
);
}
}

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Button, Button,
Form, Form,
@ -7,26 +7,28 @@ import {
Image, Image,
Icon, Icon,
Message, Message,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { Redirect } from "react-router-dom"; import { Redirect } from 'react-router-dom';
import { Forms } from "../remote"; import { Forms } from '../remote';
export default class ForgotPass extends Component { export default class ForgotPass extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
user: "", user: '',
error: { error: {
state: false, state: false,
message: [], message: [],
}, },
success: false, success: false,
}; };
this.handleChangePassword = this.handleChangePassword.bind(this);
} }
onChangeHandler = (event) => { onChangeHandler = (event) => {
let nam = event.target.name; const nam = event.target.name;
let val = event.target.value; const val = event.target.value;
this.setState({ [nam]: val }); this.setState({ [nam]: val });
}; };
@ -35,33 +37,52 @@ export default class ForgotPass extends Component {
Forms.submitInitResetPassword(this.state.user) Forms.submitInitResetPassword(this.state.user)
.then(() => this.setState({ success: true })) .then(() => this.setState({ success: true }))
.catch((err) => .catch((err) => this.setState({ error: { state: true, message: err.messages } }));
this.setState({ error: { state: true, message: err.messages } }) };
);
handleChangePassword = (e) => {
if (this.state.confirmPassword !== this.state.password) {
this.setState({
error: {
state: true,
message: 'Passwords do not match.',
},
});
}
Forms.submitResetPassword(this.props.query.token, this.state.password)
.then(() => this.setState({ success: true }))
.catch((err) => this.setState({
error: { state: true, message: err.messages.join(' - ') },
}));
}; };
render() { render() {
console.log(this.props);
if (this.state.success) { if (this.state.success) {
return <Redirect to="sent-email" />; return <Redirect to="sent-email" />;
} }
return ( return (
<React.Fragment> <>
<Button circular style={{ margin: "2em" }} href="/"> <Button circular style={{ margin: '2em' }} href="/">
<Icon name="arrow alternate circle left" /> <Icon name="arrow alternate circle left" />
Go Home{" "} Go Home
{' '}
</Button> </Button>
<Grid <Grid
textAlign="center" textAlign="center"
style={{ height: "70vh" }} style={{ height: '70vh' }}
verticalAlign="middle" verticalAlign="middle"
> >
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center"> <Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Reset Password <Image src="img/logo.png" />
{' '}
Reset Password
</Header> </Header>
<Form <Form
size="large" size="large"
style={{ marginTop: "2em" }} style={{ marginTop: '2em' }}
error={this.state.error.state} error={this.state.error.state}
> >
<Message error> <Message error>
@ -74,6 +95,8 @@ export default class ForgotPass extends Component {
</span> </span>
))} ))}
</Message> </Message>
{this.props.type === 'FPassword1' ? (
<>
<Form.Input <Form.Input
icon="address card outline" icon="address card outline"
iconPosition="left" iconPosition="left"
@ -91,10 +114,41 @@ export default class ForgotPass extends Component {
> >
Send E-mail Send E-mail
</Button> </Button>
</>
) : (
<>
<Form.Input
icon="address card outline"
iconPosition="left"
placeholder="Reset your password"
name="password"
type="password"
onChange={this.onChangeHandler}
required
/>
<Form.Input
icon="address card outline"
iconPosition="left"
placeholder="Confirm Password"
name="confirmPassword"
type="password"
onChange={this.onChangeHandler}
required
/>
<Button
color="blue"
fluid
size="large"
onClick={this.handleChangePassword}
>
Confirm password
</Button>
</>
)}
</Form> </Form>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</React.Fragment> </>
); );
} }
} }

View File

@ -1,91 +0,0 @@
import React, { Component } from "react";
import { render } from "react-dom";
import HomeNavbar from "./../components/HomeNavbar";
import {
Container,
Icon,
Image,
Menu,
Sidebar,
Responsive,
Header,
Divider,
Message,
Grid,
} from "semantic-ui-react";
class Paragraph extends Component {
state = { visible: true };
handleDismiss = () => {
this.setState({ visible: false });
setTimeout(() => {
this.setState({ visible: true });
}, 2000);
};
render() {
if (this.state.visible) {
return (
<Message
onDismiss={this.handleDismiss}
header="Link has been sent!"
content="An e-mail has been sent your address, please follow the
instruction to create a new password"
/>
);
}
return (
<p>
<br />
<i>The message will return in 2s</i>
<br />
<br />
</p>
);
}
}
const MessageReg = () => (
<Grid>
<HomeNavbar />
<Divider />
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}>
<Grid.Column width={6}></Grid.Column>
<Grid.Column width={10}>
<Image src="title5.png" />
</Grid.Column>
<Grid.Column width={3}></Grid.Column>
</Grid.Row>
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}></Grid.Row>
<Grid.Column width={3}></Grid.Column>
<Grid.Column width={4}>
<Image src="./img/logo.png" />
</Grid.Column>
<Grid.Column width={6}>
<Paragraph />
</Grid.Column>
<Grid.Column width={4}></Grid.Column>
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}></Grid.Row>
<Grid.Row height={3}>
<Grid.Column width={3}></Grid.Column>
<Grid.Column width={10}>
<Divider />
</Grid.Column>
<Grid.Column width={3}></Grid.Column>
</Grid.Row>
</Grid>
);
export default class ForgotPasswrod extends React.Component {
render() {
return <MessageReg />;
}
}

View File

@ -1,35 +1,37 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { Grid, Button, Segment, Responsive, Image } from "semantic-ui-react"; import {
import { Link } from "react-router-dom"; Grid, Button, Segment, Responsive, Image,
import MyHeader from "../components/HeaderController"; } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import MyHeader from '../components/HeaderController';
export default class FourOhFour extends Component { export default class FourOhFour extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
const meme = [ const meme = [
"1.jpeg", '1.jpeg',
"2.jpeg", '2.jpeg',
"3.png", '3.png',
"4.jpeg", '4.jpeg',
"5.jpeg", '5.jpeg',
"6.jpg", '6.jpg',
"7.jpg", '7.jpg',
"8.jpg", '8.jpg',
"9.jpeg", '9.jpeg',
"10.jpg", '10.jpg',
"11.jpeg", '11.jpeg',
"12.gif", '12.gif',
"13.gif", '13.gif',
"14.gif", '14.gif',
]; ];
var arrayNum = Math.floor(Math.random() * 13) + 1; const arrayNum = Math.floor(Math.random() * 13) + 1;
var path = "img/room_404_meme/" + meme[arrayNum]; const path = `img/room_404_meme/${meme[arrayNum]}`;
this.state = { meme: path }; this.state = { meme: path };
} }
render() { render() {
return ( return (
<div style={{ height: "110vh", background: "#1b1c1d" }}> <div style={{ height: '110vh', background: '#1b1c1d' }}>
<Responsive minWidth={768}> <Responsive minWidth={768}>
<Grid> <Grid>
<Grid.Row color="black"> <Grid.Row color="black">
@ -59,7 +61,7 @@ export default class FourOhFour extends Component {
our main room! ...or refresh this page some times... our main room! ...or refresh this page some times...
</p> </p>
<Button fluid inverted color="white"> <Button fluid inverted color="white">
<Link style={{ color: "black" }} to="/"> <Link style={{ color: 'black' }} to="/">
Let's go back to our main room! Let's go back to our main room!
</Link> </Link>
</Button> </Button>
@ -103,7 +105,7 @@ export default class FourOhFour extends Component {
our main room! ...or refresh this page some times... our main room! ...or refresh this page some times...
</p> </p>
<Button fluid inverted color="white"> <Button fluid inverted color="white">
<Link style={{ color: "black" }} to="/"> <Link style={{ color: 'black' }} to="/">
Let's go back to our main room! Let's go back to our main room!
</Link> </Link>
</Button> </Button>

View File

@ -1,6 +1,5 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import React, { Component } from "react"; import React, { Component } from 'react';
import HomeNavbar from "./../components/HomeNavbar";
import { import {
Button, Button,
Container, Container,
@ -14,13 +13,14 @@ import {
Segment, Segment,
Sidebar, Sidebar,
Visibility, Visibility,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import HomeNavbar from '../components/HomeNavbar';
// Heads up! // Heads up!
// We using React Static to prerender our docs with server side rendering, this is a quite simple solution. // We using React Static to prerender our docs with server side rendering, this is a quite simple solution.
// For more advanced usage please check Responsive docs under the "Usage" section. // For more advanced usage please check Responsive docs under the "Usage" section.
const getWidth = () => { const getWidth = () => {
const isSSR = typeof window === "undefined"; const isSSR = typeof window === 'undefined';
return isSSR ? Responsive.onlyTablet.minWidth : window.innerWidth; return isSSR ? Responsive.onlyTablet.minWidth : window.innerWidth;
}; };
@ -35,10 +35,10 @@ const HomepageHeading = ({ mobile }) => (
content="SmartHut" content="SmartHut"
inverted inverted
style={{ style={{
fontSize: mobile ? "2em" : "4em", fontSize: mobile ? '2em' : '4em',
fontWeight: "normal", fontWeight: 'normal',
marginBottom: 0, marginBottom: 0,
marginTop: mobile ? "1.5em" : "3em", marginTop: mobile ? '1.5em' : '3em',
}} }}
/> />
<Header <Header
@ -46,9 +46,9 @@ const HomepageHeading = ({ mobile }) => (
content="Keep your Home fully Connected" content="Keep your Home fully Connected"
inverted inverted
style={{ style={{
fontSize: mobile ? "1.5em" : "1.7em", fontSize: mobile ? '1.5em' : '1.7em',
fontWeight: "normal", fontWeight: 'normal',
marginTop: mobile ? "0.5em" : "1.5em", marginTop: mobile ? '0.5em' : '1.5em',
}} }}
/> />
<Button size="huge" color="orange" href="/signup"> <Button size="huge" color="orange" href="/signup">
@ -64,7 +64,9 @@ HomepageHeading.propTypes = {
class DesktopContainer extends Component { class DesktopContainer extends Component {
state = {}; state = {};
hideFixedMenu = () => this.setState({ fixed: false }); hideFixedMenu = () => this.setState({ fixed: false });
showFixedMenu = () => this.setState({ fixed: true }); showFixedMenu = () => this.setState({ fixed: true });
render() { render() {
@ -82,9 +84,9 @@ class DesktopContainer extends Component {
textAlign="center" textAlign="center"
style={{ style={{
minHeight: 700, minHeight: 700,
padding: "1em 0em", padding: '1em 0em',
background: ` linear-gradient(to bottom, rgba(0, 46, 200, 0.51), rgba(0, 0, 0, 0.51)), url("img/header.jpg")`, background: ' linear-gradient(to bottom, rgba(0, 46, 200, 0.51), rgba(0, 0, 0, 0.51)), url("img/header.jpg")',
backgroundSize: "cover", backgroundSize: 'cover',
}} }}
vertical vertical
> >
@ -139,7 +141,7 @@ class MobileContainer extends Component {
<Segment <Segment
inverted inverted
textAlign="center" textAlign="center"
style={{ minHeight: 350, padding: "1em 0em" }} style={{ minHeight: 350, padding: '1em 0em' }}
vertical vertical
> >
<Container> <Container>
@ -151,7 +153,7 @@ class MobileContainer extends Component {
<Button as="a" inverted> <Button as="a" inverted>
Log in Log in
</Button> </Button>
<Button as="a" inverted style={{ marginLeft: "0.5em" }}> <Button as="a" inverted style={{ marginLeft: '0.5em' }}>
Sign Up Sign Up
</Button> </Button>
</Menu.Item> </Menu.Item>
@ -184,20 +186,20 @@ ResponsiveContainer.propTypes = {
const Home = () => ( const Home = () => (
<ResponsiveContainer> <ResponsiveContainer>
<Segment style={{ padding: "8em 0em" }} vertical> <Segment style={{ padding: '8em 0em' }} vertical>
<Grid container stackable verticalAlign="middle"> <Grid container stackable verticalAlign="middle">
<Grid.Row> <Grid.Row>
<Grid.Column width={8}> <Grid.Column width={8}>
<Header as="h3" style={{ fontSize: "2em" }}> <Header as="h3" style={{ fontSize: '2em' }}>
We help you keep your home connected We help you keep your home connected
</Header> </Header>
<p style={{ fontSize: "1.33em" }}> <p style={{ fontSize: '1.33em' }}>
In a few steps your home will be fully connected with SmartHut. In a few steps your home will be fully connected with SmartHut.
</p> </p>
<Header as="h3" style={{ fontSize: "2em" }}> <Header as="h3" style={{ fontSize: '2em' }}>
Choose between a wide range of devices Choose between a wide range of devices
</Header> </Header>
<p style={{ fontSize: "1.33em" }}> <p style={{ fontSize: '1.33em' }}>
SmartHut is a leading worldwide company in technology innovation. SmartHut is a leading worldwide company in technology innovation.
Explore our website to find the best devices for each room of your Explore our website to find the best devices for each room of your
home! home!
@ -210,12 +212,12 @@ const Home = () => (
</Grid> </Grid>
</Segment> </Segment>
<Segment style={{ padding: "8em 0em" }} vertical> <Segment style={{ padding: '8em 0em' }} vertical>
<Container text> <Container text>
<Header as="h3" style={{ fontSize: "2em" }}> <Header as="h3" style={{ fontSize: '2em' }}>
Have you ever dreamt about a smart home? Have you ever dreamt about a smart home?
</Header> </Header>
<p style={{ fontSize: "1.33em" }}> <p style={{ fontSize: '1.33em' }}>
Let us carrying you into the future. With SmartHut, being at home will Let us carrying you into the future. With SmartHut, being at home will
be a refreshing experience. With just a few clicks, you will be able be a refreshing experience. With just a few clicks, you will be able
the set the illumination of your entire place. Follow the intelligent the set the illumination of your entire place. Follow the intelligent
@ -224,7 +226,7 @@ const Home = () => (
</Container> </Container>
</Segment> </Segment>
<Segment inverted vertical style={{ padding: "5em 0em" }}> <Segment inverted vertical style={{ padding: '5em 0em' }}>
<Container> <Container>
<Grid divided inverted stackable> <Grid divided inverted stackable>
<Grid.Row> <Grid.Row>
@ -247,7 +249,7 @@ const Home = () => (
</a> </a>
</List.Item> </List.Item>
<List.Item> <List.Item>
<a href="https://theshell.ch" target="blank"> <a href="https://www.theshell.ch" target="blank">
The Shell The Shell
</a> </a>
</List.Item> </List.Item>

View File

@ -0,0 +1,117 @@
import React, { Component } from 'react';
import {
Menu, Grid, Responsive, Dropdown,
} from 'semantic-ui-react';
import { connect } from 'react-redux';
import HostModal from '../components/HostModal';
import { RemoteService } from '../remote';
import { appActions } from '../storeActions';
class HostsNavbar extends Component {
constructor(props) {
super(props);
this.state = {
editMode: false,
};
this.getHosts();
this.selectHosts = this.selectHosts.bind(this);
}
getHosts() {
this.props.fetchHosts().catch(console.error);
}
selectHosts(_, { id }) {
this.props.setActiveHost(id || -1);
}
get activeItem() {
return this.props.activeHost;
}
get activeItemHostsName() {
if (this.props.activeItem === -1) return 'Home';
return this.props.hosts[this.props.activeHost].name;
}
render() {
return (
<div>
<Responsive minWidth={768}>
<Grid style={{ margin: '1em -1em 0 1em' }}>
<Grid.Row>
<Menu inverted fluid vertical>
<Menu.Item
key={-1}
id={null}
name="hosts"
active={this.activeItem === -1}
onClick={this.selectHosts}
>
<strong>Hosts</strong>
</Menu.Item>
{Object.values(this.props.hosts).map((e, i) => (
<Menu.Item
id={e.id}
key={i}
name={e.name}
active={this.activeItem === e.id}
onClick={this.selectHosts}
>
{e.name}
</Menu.Item>
))}
<Menu.Item name="newM">
<HostModal />
</Menu.Item>
</Menu>
</Grid.Row>
</Grid>
</Responsive>
<Responsive maxWidth={768}>
<Menu>
<Dropdown item fluid text={this.activeItemHostName}>
<Dropdown.Menu>
<Dropdown.Item
key={-1}
id={null}
name="scene"
active={this.activeItem === -1}
onClick={this.selectHosts}
>
<strong>Hosts</strong>
</Dropdown.Item>
{Object.values(this.props.hosts).map((e, i) => (
<Menu.Item
id={e.id}
key={i}
name={e.name}
active={this.activeItem === e.id}
onClick={this.selectHosts}
>
{e.name}
</Menu.Item>
))}
</Dropdown.Menu>
</Dropdown>
</Menu>
</Responsive>
</div>
);
}
}
const setActiveHost = (activeHost) => (dispatch) => dispatch(appActions.setActiveHost(activeHost));
const mapStateToProps = (state, _) => ({
hosts: state.hosts,
activeHost: state.active.activeHost,
});
const HostsNavbarContainer = connect(mapStateToProps, {
...RemoteService,
setActiveHost,
})(HostsNavbar);
export default HostsNavbarContainer;

View File

@ -1,6 +1,8 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import HomeNavbar from "./../components/HomeNavbar"; import {
import { Container, Header, Divider, Grid } from "semantic-ui-react"; Container, Header, Divider, Grid,
} from 'semantic-ui-react';
import HomeNavbar from '../components/HomeNavbar';
const ContainerExampleAlignment = () => ( const ContainerExampleAlignment = () => (
<div> <div>

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Button, Button,
Form, Form,
@ -8,19 +8,19 @@ import {
Message, Message,
Icon, Icon,
Input, Input,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { RemoteService } from "../remote"; import { withRouter } from 'react-router-dom';
import { withRouter } from "react-router-dom"; import { connect } from 'react-redux';
import { connect } from "react-redux"; import { RemoteService } from '../remote';
class Login extends Component { class Login extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
user: "", user: '',
password: "", password: '',
fireRedirect: false, fireRedirect: false,
error: { state: false, message: "" }, error: { state: false, message: '' },
}; };
} }
@ -29,43 +29,43 @@ class Login extends Component {
this.props this.props
.login(this.state.user, this.state.password) .login(this.state.user, this.state.password)
.then(() => this.props.history.push("/dashboard")) .then(() => this.props.history.push('/dashboard'))
.catch((err) => { .catch((err) => {
console.log("CIAO", err);
this.setState({ this.setState({
error: { state: true, message: err.messages.join(" - ") }, error: { state: true, message: err.messages.join(' - ') },
}); });
}); });
}; };
onChangeHandler = (event) => { onChangeHandler = (event) => {
let nam = event.target.name; const nam = event.target.name;
let val = event.target.value; const val = event.target.value;
this.setState({ [nam]: val }); this.setState({ [nam]: val });
}; };
toggle = () => toggle = () => this.setState((prevState) => ({ rememberme: !prevState.rememberme }));
this.setState((prevState) => ({ rememberme: !prevState.rememberme }));
render() { render() {
return ( return (
<React.Fragment> <>
<Button circular style={{ margin: "2em" }} href="/"> <Button circular style={{ margin: '2em' }} href="/">
<Icon name="arrow alternate circle left" /> <Icon name="arrow alternate circle left" />
Go Home Go Home
</Button> </Button>
<Grid <Grid
textAlign="center" textAlign="center"
style={{ height: "70vh" }} style={{ height: '70vh' }}
verticalAlign="middle" verticalAlign="middle"
> >
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center"> <Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Log-in to SmartHut <Image src="img/logo.png" />
{' '}
Log-in to SmartHut
</Header> </Header>
<Form <Form
size="large" size="large"
style={{ marginTop: "2em" }} style={{ marginTop: '2em' }}
error={this.state.error.state} error={this.state.error.state}
> >
<Message <Message
@ -105,12 +105,14 @@ class Login extends Component {
<a href="/forgot-password">Forgot Password?</a> <a href="/forgot-password">Forgot Password?</a>
</p> </p>
<p> <p>
New to us? <a href="/signup"> Sign Up</a> New to us?
{' '}
<a href="/signup"> Sign Up</a>
</p> </p>
</Message> </Message>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</React.Fragment> </>
); );
} }
} }
@ -118,6 +120,6 @@ class Login extends Component {
const mapStateToProps = (state, _) => ({ loggedIn: state.login.loggedIn }); const mapStateToProps = (state, _) => ({ loggedIn: state.login.loggedIn });
const LoginContainer = connect( const LoginContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService,
)(withRouter(Login)); )(withRouter(Login));
export default LoginContainer; export default LoginContainer;

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Menu, Menu,
Button, Button,
@ -6,12 +6,12 @@ import {
Icon, Icon,
Responsive, Responsive,
Dropdown, Dropdown,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { editButtonStyle } from "../components/dashboard/devices/styleComponents"; import { connect } from 'react-redux';
import RoomModal from "../components/RoomModal"; import { editButtonStyle } from '../components/dashboard/devices/styleComponents';
import { RemoteService } from "../remote"; import RoomModal from '../components/RoomModal';
import { connect } from "react-redux"; import { RemoteService } from '../remote';
import { appActions } from "../storeActions"; import { appActions } from '../storeActions';
class Navbar extends Component { class Navbar extends Component {
constructor(props) { constructor(props) {
@ -40,7 +40,7 @@ class Navbar extends Component {
} }
get activeItemName() { get activeItemName() {
if (this.props.activeRoom === -1) return "Home"; if (this.props.activeRoom === -1) return 'Home';
return this.props.rooms[this.props.activeRoom].name; return this.props.rooms[this.props.activeRoom].name;
} }
@ -60,9 +60,9 @@ class Navbar extends Component {
render() { render() {
return ( return (
<div style={{ background: "#1b1c1d!important", padding: "0 20px" }}> <div>
<Responsive minWidth={768}> <Responsive minWidth={768}>
<Grid> <Grid style={{ margin: '1em -1em 0 1em' }}>
<Grid.Row color="black"> <Grid.Row color="black">
<button style={editButtonStyle} onClick={this.toggleEditMode}> <button style={editButtonStyle} onClick={this.toggleEditMode}>
Edit Edit
@ -82,13 +82,14 @@ class Navbar extends Component {
<Grid.Column> <Grid.Column>
<Icon name="home" size="small" /> <Icon name="home" size="small" />
</Grid.Column> </Grid.Column>
<Grid.Column width={8}>House View</Grid.Column> <Grid.Column width={8}>
<strong>Home view</strong>
</Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Menu.Item> </Menu.Item>
{Object.values(this.props.rooms).map((e, i) => { {Object.values(this.props.rooms).map((e, i) => (
return (
<Menu.Item <Menu.Item
id={e.id} id={e.id}
key={i} key={i}
@ -101,7 +102,7 @@ class Navbar extends Component {
<Grid.Column> <Grid.Column>
<Icon name={e.icon} size="small" /> <Icon name={e.icon} size="small" />
</Grid.Column> </Grid.Column>
<Grid.Column width={8}>{e.name}</Grid.Column> <Grid.Column width={11}>{e.name}</Grid.Column>
<Grid.Column floated="right"> <Grid.Column floated="right">
{this.state.editMode ? ( {this.state.editMode ? (
<RoomModal id={e.id} /> <RoomModal id={e.id} />
@ -110,8 +111,7 @@ class Navbar extends Component {
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Menu.Item> </Menu.Item>
); ))}
})}
<Menu.Item name="newM"> <Menu.Item name="newM">
<Grid> <Grid>
@ -146,8 +146,7 @@ class Navbar extends Component {
</Grid> </Grid>
</Dropdown.Item> </Dropdown.Item>
{Object.values(this.props.rooms).map((e, i) => { {Object.values(this.props.rooms).map((e, i) => (
return (
<Dropdown.Item <Dropdown.Item
id={e.id} id={e.id}
key={i} key={i}
@ -165,12 +164,11 @@ class Navbar extends Component {
</Grid> </Grid>
<RoomModal <RoomModal
ref={this.props.roomModalRefs[e.id]} ref={this.props.roomModalRefs[e.id]}
nicolaStop={true} nicolaStop
id={e.id} id={e.id}
/> />
</Dropdown.Item> </Dropdown.Item>
); ))}
})}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
</Menu> </Menu>
@ -200,16 +198,14 @@ class Navbar extends Component {
} }
} }
const setActiveRoom = (activeRoom) => { const setActiveRoom = (activeRoom) => (dispatch) => dispatch(appActions.setActiveRoom(activeRoom));
return (dispatch) => dispatch(appActions.setActiveRoom(activeRoom));
};
const mapStateToProps = (state, _) => ({ const mapStateToProps = (state, _) => ({
rooms: state.rooms, rooms: state.rooms,
activeRoom: state.active.activeRoom, activeRoom: state.active.activeRoom,
roomModalRefs: Object.keys(state.rooms).reduce( roomModalRefs: Object.keys(state.rooms).reduce(
(acc, key) => ({ ...acc, [key]: React.createRef() }), (acc, key) => ({ ...acc, [key]: React.createRef() }),
{} {},
), ),
}); });
const NavbarContainer = connect(mapStateToProps, { const NavbarContainer = connect(mapStateToProps, {

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Menu, Menu,
Button, Button,
@ -6,12 +6,12 @@ import {
Grid, Grid,
Responsive, Responsive,
Dropdown, Dropdown,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { editButtonStyle } from "../components/dashboard/devices/styleComponents"; import { connect } from 'react-redux';
import SceneModal from "../components/SceneModal"; import { editButtonStyle } from '../components/dashboard/devices/styleComponents';
import { RemoteService } from "../remote"; import SceneModal from '../components/SceneModal';
import { connect } from "react-redux"; import { RemoteService } from '../remote';
import { appActions } from "../storeActions"; import { appActions } from '../storeActions';
class ScenesNavbar extends Component { class ScenesNavbar extends Component {
constructor(props) { constructor(props) {
@ -35,7 +35,7 @@ class ScenesNavbar extends Component {
} }
get activeItemSceneName() { get activeItemSceneName() {
if (this.props.activeScene === -1) return "Scene"; if (this.props.activeScene === -1) return 'Scene';
return this.props.scenes[this.props.activeScene].name; return this.props.scenes[this.props.activeScene].name;
} }
@ -44,7 +44,7 @@ class ScenesNavbar extends Component {
} }
openCurrentModalMobile() { openCurrentModalMobile() {
//console.log(this.activeItemScene, this.props.sceneModalRefs); // console.log(this.activeItemScene, this.props.sceneModalRefs);
const currentModal = this.props.sceneModalRefs[this.activeItemScene] const currentModal = this.props.sceneModalRefs[this.activeItemScene]
.current; .current;
currentModal.openModal(); currentModal.openModal();
@ -63,15 +63,15 @@ class ScenesNavbar extends Component {
if (sceneId) { if (sceneId) {
this.props this.props
.fetchStates(sceneId) .fetchStates(sceneId)
.catch((err) => console.error(`error fetching states:`, err)); .catch((err) => console.error('error fetching states:', err));
} }
} }
render() { render() {
return ( return (
<div style={{ background: "#1b1c1d!important", padding: "0 20px" }}> <div>
<Responsive minWidth={768}> <Responsive minWidth={768}>
<Grid> <Grid style={{ margin: '1em -1em 0 1em' }}>
<Grid.Row color="black"> <Grid.Row color="black">
<button style={editButtonStyle} onClick={this.toggleEditMode}> <button style={editButtonStyle} onClick={this.toggleEditMode}>
Edit Edit
@ -86,11 +86,10 @@ class ScenesNavbar extends Component {
active={this.activeItemScene === -1} active={this.activeItemScene === -1}
onClick={this.selectScene} onClick={this.selectScene}
> >
SCENES <strong>Scenes</strong>
</Menu.Item> </Menu.Item>
{Object.values(this.props.scenes).map((e, i) => { {Object.values(this.props.scenes).map((e, i) => (
return (
<Menu.Item <Menu.Item
id={e.id} id={e.id}
key={i} key={i}
@ -100,7 +99,10 @@ class ScenesNavbar extends Component {
> >
<Grid> <Grid>
<Grid.Row> <Grid.Row>
<Grid.Column width={12}>{e.name}</Grid.Column> <Grid.Column>
<Icon name={e.icon} size="small" />
</Grid.Column>
<Grid.Column width={11}>{e.name}</Grid.Column>
<Grid.Column floated="right"> <Grid.Column floated="right">
{this.state.editMode ? ( {this.state.editMode ? (
<SceneModal id={e.id} /> <SceneModal id={e.id} />
@ -109,8 +111,7 @@ class ScenesNavbar extends Component {
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Menu.Item> </Menu.Item>
); ))}
})}
<Menu.Item name="newM"> <Menu.Item name="newM">
<Grid> <Grid>
@ -137,13 +138,12 @@ class ScenesNavbar extends Component {
> >
<Grid> <Grid>
<Grid.Row> <Grid.Row>
<Grid.Column>Scene</Grid.Column> <Grid.Column>Scenes</Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>
</Dropdown.Item> </Dropdown.Item>
{Object.values(this.props.scenes).map((e, i) => { {Object.values(this.props.scenes).map((e, i) => (
return (
<Dropdown.Item <Dropdown.Item
id={e.id} id={e.id}
key={i} key={i}
@ -158,12 +158,11 @@ class ScenesNavbar extends Component {
</Grid> </Grid>
<SceneModal <SceneModal
ref={this.props.sceneModalRefs[e.id]} ref={this.props.sceneModalRefs[e.id]}
nicolaStop={true} nicolaStop
id={e.id} id={e.id}
/> />
</Dropdown.Item> </Dropdown.Item>
); ))}
})}
</Dropdown.Menu> </Dropdown.Menu>
</Dropdown> </Dropdown>
</Menu> </Menu>
@ -193,15 +192,13 @@ class ScenesNavbar extends Component {
} }
} }
const setActiveScene = (activeScene) => { const setActiveScene = (activeScene) => (dispatch) => dispatch(appActions.setActiveScene(activeScene));
return (dispatch) => dispatch(appActions.setActiveScene(activeScene));
};
const mapStateToProps = (state, _) => ({ const mapStateToProps = (state, _) => ({
scenes: state.scenes, scenes: state.scenes,
sceneModalRefs: Object.keys(state.scenes).reduce( sceneModalRefs: Object.keys(state.scenes).reduce(
(acc, key) => ({ ...acc, [key]: React.createRef() }), (acc, key) => ({ ...acc, [key]: React.createRef() }),
{} {},
), ),
get isActiveDefaultScene() { get isActiveDefaultScene() {
return state.active.activeScene === -1; return state.active.activeScene === -1;

View File

@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component } from 'react';
import { import {
Button, Button,
Form, Form,
@ -8,18 +8,18 @@ import {
Icon, Icon,
Input, Input,
Message, Message,
} from "semantic-ui-react"; } from 'semantic-ui-react';
import { Redirect } from "react-router-dom"; import { Redirect } from 'react-router-dom';
import { Forms } from "../remote"; import { Forms } from '../remote';
export default class Signup extends Component { export default class Signup extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
completeName: "", completeName: '',
username: "", username: '',
email: "", email: '',
password: "", password: '',
error: { state: false, message: [] }, error: { state: false, message: [] },
success: false, success: false,
}; };
@ -36,14 +36,12 @@ export default class Signup extends Component {
Forms.submitRegistration(params) Forms.submitRegistration(params)
.then(() => this.setState({ success: true })) .then(() => this.setState({ success: true }))
.catch((err) => .catch((err) => this.setState({ error: { state: true, message: err.messages } }));
this.setState({ error: { state: true, message: err.messages } })
);
}; };
onChangeHandler = (event) => { onChangeHandler = (event) => {
let nam = event.target.name; const nam = event.target.name;
let val = event.target.value; const val = event.target.value;
this.setState({ [nam]: val }); this.setState({ [nam]: val });
}; };
@ -52,23 +50,26 @@ export default class Signup extends Component {
return <Redirect to="sent-email-reg" />; return <Redirect to="sent-email-reg" />;
} }
return ( return (
<React.Fragment> <>
<Button circular style={{ margin: "2em" }} href="/"> <Button circular style={{ margin: '2em' }} href="/">
<Icon name="arrow alternate circle left" /> <Icon name="arrow alternate circle left" />
Go Home{" "} Go Home
{' '}
</Button> </Button>
<Grid <Grid
textAlign="center" textAlign="center"
style={{ height: "70vh" }} style={{ height: '70vh' }}
verticalAlign="middle" verticalAlign="middle"
> >
<Grid.Column style={{ maxWidth: 450 }}> <Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" color="blue" textAlign="center"> <Header as="h2" color="blue" textAlign="center">
<Image src="img/logo.png" /> Sign-up to SmartHut <Image src="img/logo.png" />
{' '}
Sign-up to SmartHut
</Header> </Header>
<Form <Form
size="large" size="large"
style={{ marginTop: "2em" }} style={{ marginTop: '2em' }}
error={this.state.error.state} error={this.state.error.state}
> >
<Message error> <Message error>
@ -107,16 +108,16 @@ export default class Signup extends Component {
iconPosition="left" iconPosition="left"
placeholder="E-mail" placeholder="E-mail"
onChange={this.onChangeHandler} onChange={this.onChangeHandler}
/*error={{ /* error={{
content: 'Please enter a valid email address', content: 'Please enter a valid email address',
pointing: 'below', pointing: 'below',
}}*/ }} */
required required
/> />
<Form.Input <Form.Input
icon="lock" icon="lock"
iconPosition="left" iconPosition="left"
placeholder="Password (at least 8 characters)" placeholder="Password (at least 6 characters)"
name="password" name="password"
type="password" type="password"
onChange={this.onChangeHandler} onChange={this.onChangeHandler}
@ -134,7 +135,7 @@ export default class Signup extends Component {
</Form> </Form>
</Grid.Column> </Grid.Column>
</Grid> </Grid>
</React.Fragment> </>
); );
} }
} }

View File

@ -1,6 +1,6 @@
import _ from "lodash"; import _ from 'lodash';
import React from "react"; import React from 'react';
import HeaderController from "./../components/HeaderController"; import HeaderController from '../components/HeaderController';
export default class TestHeaderController extends React.Component { export default class TestHeaderController extends React.Component {
render() { render() {

View File

@ -1,11 +1,12 @@
import React from "react"; import React from 'react';
import VideoTest from "../components/VideoTest"; import VideoTest from '../components/VideoTest';
export default class TestHeaderController extends React.Component { export default class TestHeaderController extends React.Component {
render() { render() {
return ( return (
<div> <div>
<VideoTest />; <VideoTest />
;
</div> </div>
); );
} }

File diff suppressed because it is too large Load Diff