Merge branch 'mod-room' of lab.si.usi.ch:sa4-2020/the-sanmarinoes/frontend into mod-room

This commit is contained in:
Nicola Brunner 2020-03-17 17:09:46 +01:00
commit 6bf3bd07fa
23 changed files with 845 additions and 273 deletions

View file

@ -3,6 +3,7 @@ image: node:latest
stages: stages:
- build - build
- test - test
- deploy
cache: cache:
paths: paths:
@ -22,3 +23,22 @@ testing_testing:
script: script:
- cd smart-hut - cd smart-hut
- yarn test - yarn test
smartHut_deploy:
stage: deploy
# tags:
# - dind
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay
before_script:
- docker version
- docker info
- docker login -u smarthutsm -p $CI_DOCKER_PASS #GiovanniRoberto
script:
- "docker build -t smarthutsm/smarthut:${CI_COMMIT_BRANCH} ."
- "docker push smarthutsm/smarthut:${CI_COMMIT_BRANCH}"
after_script:
- docker logout

39
Dockerfile Normal file
View file

@ -0,0 +1,39 @@
#FROM mhart/alpine-node:11 AS pleaseGodWork
#WORKDIR /app
#COPY . /app
#RUN ls
#RUN yarn run build
#
#RUN yarn global add serve
#
#
#CMD ["serve", "-p", "8080", "-s", "."]
# base image
FROM node:9.6.1
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY smart-hut/package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts@1.1.1 -g --silent
CMD ["npm", "start"]
FROM node:9.6.1 as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY smart-hut/package.json /usr/src/app/package.json
RUN npm install --silent
RUN npm install react-scripts@1.1.1 -g --silent
COPY smart-hut/. /usr/src/app
RUN npm run build
FROM nginx:1.13.9-alpine
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

View file

@ -0,0 +1 @@
<svg id="Layer_1" enable-background="new 0 0 511.929 511.929" height="512" viewBox="0 0 511.929 511.929" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m176.652 174.329v57.6h32v-57.6c0-8.84-7.16-16-16-16-8.83 0-16 7.16-16 16z"/><path d="m303.592 174.329v57.6h32v-57.6c0-8.84-7.17-16-16-16-8.84 0-16 7.16-16 16z"/><path d="m344.052 263.929h-175.86c-8.84 0-16 7.16-16 16 0 8.83 7.16 16 16 16h4.2v41.6c0 21.17 17.22 38.4 38.4 38.4h90.66c21.18 0 38.4-17.23 38.4-38.4v-41.6h4.2c8.84 0 16-7.17 16-16 0-8.84-7.16-16-16-16z"/><path d="m224.122 407.929v8.53c0 11.843 6.438 22.175 16 27.708v51.762c0 8.84 7.16 16 16 16s16-7.16 16-16v-51.762c9.562-5.534 16-15.866 16-27.708v-8.53c-24.422 0-39.67 0-64 0z"/><path d="m193.731 89.704c-6.249 6.248-6.249 16.379 0 22.627 6.248 6.248 16.379 6.249 22.627 0 21.886-21.885 57.309-21.887 79.196 0 3.124 3.124 7.219 4.686 11.313 4.686 14.126 0 21.422-17.206 11.313-27.314-34.391-34.39-90.057-34.394-124.449.001z"/><path d="m363.436 44.448c-59.263-59.264-155.695-59.266-214.96 0-6.249 6.249-6.249 16.379 0 22.627 6.248 6.249 16.379 6.249 22.627 0 46.897-46.896 122.804-46.901 169.706 0 3.124 3.125 7.219 4.687 11.313 4.687 14.126 0 21.422-17.206 11.314-27.314z"/><circle cx="255.955" cy="151.929" r="24"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg id="Layer_1" enable-background="new 0 0 511.929 511.929" height="512" viewBox="0 0 511.929 511.929" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m343.964 263.929v56c0 30.93-25.07 56-56 56h-64c-30.924 0-56-25.071-56-56v-56z" fill="#a2d0ff"/><path d="m343.964 263.929v32h-82.04c-38.53 0-69.96 31.216-69.96 69.96-14.51-10.12-24-26.93-24-45.96v-56z" fill="#8bc4ff"/><path d="m255.965 439.929c-17.624 0-32-14.278-32-32v-32h64v32c-.001 17.673-14.327 32-32 32z" fill="#8bc4ff"/><g fill="#2e58ff"><path d="m367.964 247.929c-13.903 0-26.139 0-40 0v-64c0-8.836-7.163-16-16-16s-16 7.164-16 16v64h-80v-64c0-8.836-7.163-16-16-16s-16 7.164-16 16v64c-13.873 0-26.083 0-40 0-8.837 0-16 7.164-16 16s7.163 16 16 16h8v48c0 32.579 24.475 59.531 56 63.482v16.518c0 20.858 13.377 38.643 32 45.248v42.752c0 8.836 7.163 16 16 16s16-7.164 16-16v-42.752c18.623-6.605 32-24.389 32-45.248v-16.518c31.525-3.951 56-30.904 56-63.482v-48h8c8.837 0 16-7.164 16-16s-7.163-16-16-16zm-96 160c0 8.822-7.178 16-16 16s-16-7.178-16-16v-16h32zm56-80c0 17.645-14.355 32-32 32-16.043 0-63.799 0-80 0-17.645 0-32-14.355-32-32v-48h144z"/><circle cx="255.795" cy="151.929" r="24"/><path d="m306.71 117.017c14.126 0 21.422-17.206 11.313-27.314-34.392-34.391-90.058-34.394-124.45 0-6.249 6.248-6.249 16.379 0 22.627 6.248 6.248 16.379 6.249 22.627 0 21.886-21.885 57.309-21.887 79.196 0 3.124 3.125 7.219 4.687 11.314 4.687z"/><path d="m170.945 67.076c46.897-46.896 122.804-46.901 169.706 0 6.247 6.248 16.379 6.249 22.627 0 6.249-6.248 6.249-16.379 0-22.627-59.263-59.264-155.695-59.266-214.96 0-6.249 6.249-6.249 16.379 0 22.627 6.248 6.249 16.379 6.249 22.627 0z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -59,6 +59,28 @@ export var call = {
return err; return err;
}); });
}, },
getAllDevices: function(token) {
if (!token){
token = tkn;
}
return axios.get(config + 'device', { headers: { Authorization : "Bearer " + token } })
.then(res => {
return res;
}).catch(err => {
return err;
});
},
getAllDevicesByRoom: function(id, token) {
if (!token){
token = tkn;
}
return axios.get(config + 'room/' + id + '/device' , { headers: { Authorization : "Bearer " + token } })
.then(res => {
return res;
}).catch(err => {
return err;
});
},
createRoom: function(data, headers) { createRoom: function(data, headers) {
return axios.post(config + 'room', data, { headers: { Authorization : "Bearer " + tkn } }) return axios.post(config + 'room', data, { headers: { Authorization : "Bearer " + tkn } })
.then(res => { .then(res => {

View file

@ -2,59 +2,107 @@ import React, {Component} from 'react';
import { import {
Grid, Grid,
} from "semantic-ui-react"; } from "semantic-ui-react";
import Device from "./devices/Device";
import NewDevice from "./devices/NewDevice";
import {LightDevice, TemperatureSensor} from "./devices/TypesOfDevices";
import {editButtonStyle, panelStyle} from "./devices/styleComponents"; import {editButtonStyle, panelStyle} from "./devices/styleComponents";
import {checkMaxLength, DEVICE_NAME_MAX_LENGTH} from "./devices/constants";
import DeviceType from './devices/DeviceTypeController';
import NewDevice from "./devices/NewDevice";
class Panel extends Component {
/* const devices = [
{
"id" : 1,
"name": "Bedroom Light",
"type" : "light",
"hasIntensity" : true,
"intensityLevel" : 0.20,
...LightDevice
},
{
"id" : 2,
"name": "Bathroom Light",
"type" : "light",
...LightDevice
},
{
"id" : 3,
"name": "Desktop Light",
"type" : "light",
...LightDevice
},
{
"id" : 4,
"name": "Entrance Light",
"type" : "light",
...LightDevice
},
{
"id" : 5,
"name": "Smart Plug",
"type" : "smartplug",
...SmartPlugDevice
},
{
"id" : 6,
"name": "Bedroom Thermometer",
"type" : "temperature-sensor",
},
];*/
export default class DevicePanel extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
editMode : false editMode : false,
devices : this.props.devices,
}; };
} }
editModeController = (e) => { editModeController = (e) => this.setState((prevState) => ({ editMode: !prevState.editMode }));
this.setState((prevState) => ({ editMode: !prevState.editMode }));
changeDeviceData = (deviceId, newSettings) => {
console.log(newSettings.name, " <-- new name --> ", deviceId );
this.state.devices.map(device => {
if(device.id === deviceId){
for(let prop in newSettings){
if(device.hasOwnProperty(prop)){
if(prop==="name"){
if(checkMaxLength(newSettings[prop])){
device[prop] = newSettings[prop];
}
else{
alert("Name must be less than " + DEVICE_NAME_MAX_LENGTH + " characters.");
}
}else{
device[prop] = newSettings[prop];
}
}
}
}
return null;
});
this.forceUpdate();
}; };
render() { render() {
const devices = [
{
"name": "Bedroom Light",
...LightDevice
},
{
"name": "Bathroom Light",
...LightDevice
},
{
"name": "Desktop Light",
...LightDevice
},
{
"name": "Entrance Light",
...LightDevice
},
{
"name": "Bedroom",
...TemperatureSensor
}
];
return ( return (
<div style={panelStyle}> <div style={panelStyle}>
<button style={editButtonStyle} onClick={this.editModeController}>Edit</button> <button style={editButtonStyle} onClick={this.editModeController}>Edit</button>
<Grid doubling columns={5} divided="vertically"> <Grid doubling divided="vertically">
{devices.map((e, i) => { {
this.props.devices ?
this.props.devices.map((e, i) => {
return ( return (
<Grid.Column key={i.toString()}> <Grid.Column>
<Device device={e} edit={this.state.editMode}/> <DeviceType type={e.type} onChangeData={this.changeDeviceData} device={e} edit={this.state.editMode}/>
</Grid.Column> </Grid.Column>
) )
})} })
:
null
}
<Grid.Column> <Grid.Column>
<NewDevice/> <NewDevice/>
</Grid.Column> </Grid.Column>
@ -65,21 +113,3 @@ class Panel extends Component {
} }
export default class DevicePanel extends Component {
constructor(props) {
super(props);
this.state = {
shownRoom: "All"
}
}
render() {
return (
<Panel/>
)
}
}

View file

@ -1,44 +1,8 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import styled from 'styled-components';
import {Image} from "semantic-ui-react"; import {Image} from "semantic-ui-react";
import Sensor from './Sensor'; import {iconStyle, nameStyle, StyledDiv} from "./styleComponents";
import {editModeIconStyle, editModeStyle} from "./styleComponents"; import Settings from './DeviceSettings';
const StyledDiv = styled.div`
background-color : white;
padding : 3rem;
width: 10rem;
height: 10rem;
border-radius : 100%;
border : none;
position : relative;
box-shadow: 3px 2px 10px 5px #ccc;
transition : all .3s ease-out;
:hover{
background-color : #f2f2f2;
}
:active{
transform : translate(0.3px, 0.8px);
box-shadow: 0.5px 0.5px 7px 3.5px #ccc;
}
`;
const iconStyle = {
width: "4rem",
height: "auto",
position: "absolute",
top: "20%",
left: "50%",
transform: "translateX(-50%)",
filter: "drop-shadow( 1px 1px 0.5px rgba(0, 0, 0, .25))"
};
const nameStyle = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translateX(-50%)"
}
export default class Device extends Component { export default class Device extends Component {
constructor(props) { constructor(props) {
@ -50,6 +14,7 @@ export default class Device extends Component {
} }
onClickDevice = () => { onClickDevice = () => {
if (!this.props.edit) {
if (this.props.device.type === "light") { if (this.props.device.type === "light") {
if (this.state.turnOnOff === "on") { if (this.state.turnOnOff === "on") {
this.setState({ this.setState({
@ -63,20 +28,16 @@ export default class Device extends Component {
}); });
} }
} }
};
render() {
if (this.props.device.type === "temperature_sensor") {
return (
<StyledDiv>
{this.props.edit ? (<span style={editModeStyle}><img src="/img/settings.svg" style={editModeIconStyle}/></span>) : ("")}
<Sensor device={this.props.device}/>
</StyledDiv>
)
} }
};
render() {
return ( return (
<StyledDiv onClick={this.onClickDevice} style={{textAlign: "center"}}> <StyledDiv onClick={this.props.edit ? () => {
{this.props.edit ? (<span style={editModeStyle}><img src="/img/settings.svg" style={editModeIconStyle}/></span>) : ("")} } : this.onClickDevice} style={{textAlign: "center"}}>
<Settings
deviceId={this.props.device.id}
edit={this.props.edit}
onChangeData={(id, newSettings) => this.props.onChangeData(id, newSettings)}/>
<Image src={this.state.icon} style={iconStyle}/> <Image src={this.state.icon} style={iconStyle}/>
<h5 style={nameStyle}>{this.props.device.name}</h5> <h5 style={nameStyle}>{this.props.device.name}</h5>
</StyledDiv> </StyledDiv>

View file

@ -0,0 +1,73 @@
import React, {Component} from "react";
import {Button, Form} from "semantic-ui-react";
import {editModeIconStyle, editModeStyle, formStyle} from "./styleComponents";
class SettingsForm extends Component {
constructor(props) {
super(props);
this.state = {}
};
onChangeHandler = (event) => {
let nam = event.target.name;
let val = event.target.value;
this.setState({[nam]: val});
};
saveChanges = () => {
let newName = this.state["new-name"];
this.props.onChangeData(this.props.id, {"name": newName});
};
render() {
return (
<Form style={formStyle}>
<Form.Field>
<label style={{color: "white"}}>New Device Name</label>
<input name="new-name" placeholder='New name' onChange={this.onChangeHandler}/>
</Form.Field>
<Button type='submit' onClick={this.saveChanges}>Save</Button>
</Form>
)
}
}
export default class Settings extends Component {
constructor(props) {
super(props);
this.state = {
displayForm: false,
}
};
displayForm = () => {
this.setState((prevState) => ({displayForm: !prevState.displayForm}));
};
render() {
const view = (
<div>
{this.state.displayForm ? (
<SettingsForm id={this.props.deviceId} onChangeData={this.props.onChangeData}/>) : ("")}
<div onClick={this.displayForm}>
<span style={editModeStyle}>
{!this.state.displayForm ? (
<img
src="/img/settings.svg"
alt=""
style={editModeIconStyle}/>)
:
<p style={{color: "white"}}>&times;</p>
}
</span>
</div>
</div>
);
return (
<React.Fragment>
{this.props.edit ? view : ("")}
</React.Fragment>
)
};
}

View file

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import Light from "./Light";
import SmartPlug from "./SmartPlug";
import Sensor from "./Sensor";
import DefaultDimmer from "./Dimmer";
import Switcher from "./Switcher";
const DeviceType = (props) => {
switch(props.type) {
case "light":
return <Light onChangeData={props.changeDeviceData} device={props.device} edit={props.edit} />
case "sensor":
return <Sensor onChangeData={props.changeDeviceData} device={props.device} edit={props.edit} />
case "dimmer":
return <DefaultDimmer onChangeData={props.changeDeviceData} device={props.device} edit={props.edit} />
case "smartplug":
return <SmartPlug onChangeData={props.changeDeviceData} device={props.device} edit={props.edit} />
case "switch":
return <Switcher onChangeData={props.changeDeviceData} device={props.device} edit={props.edit} />
}
}
export default DeviceType;

View file

@ -0,0 +1,52 @@
/**
Users can add dimmers, a particular kind of switch that can also modify the intensity level of a given light.
There are two types of dimmers:
A dimmer with state stores a given intensity level and sets the light to that level. <-- StatefulDimmer
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 .
**/
import React, {Component} from 'react';
export class StatefulDimmer extends Component{
constructor(props){
super(props);
this.state = {
intensityLevel : 0,
pointingLDevices:[]
}
}
componentDidMount() {
}
render() {
return(
<div>
This is a Dimmer
</div>
)
}
}
export default class DefaultDimmer extends Component{
// As far as I am concern, even though this dimmer doesn't have state, internally it's needed
constructor(props){
super(props);
this.state = {
pointingDevices :[]
}
}
componentDidMount() {
}
render() {
return(
<div>
This is a Dimmer
</div>
)
}
}

View file

@ -0,0 +1,92 @@
/**
* Users can add lights in their rooms.
* Lights are devices like bulbs, LED strip lights, lamps.
* Lights may support an intensity level (from 0% to 100%).
* Lights have an internal state that can be changed and it must
* be shown accordingly in the SmartHut views (house view and room views).
*/
import React, {Component} from "react";
import {iconStyle, StyledDiv, nameStyle} from "./styleComponents";
import Settings from "./DeviceSettings";
import {Image} from "semantic-ui-react";
import {CircularInput, CircularProgress, CircularThumb, CircularTrack} from "react-circular-input";
import {valueStyle, intensityLightStyle, style} from "./LightStyle";
export default class Light extends Component {
constructor(props) {
super(props);
this.state = {
turnedOn: false,
hasIntensity : false
};
this.iconOn = "/img/lightOn.svg";
this.iconOff = "/img/lightOff.svg"
}
onClickDevice = () => {
this.setState((prevState) => ({turnedOn: !prevState.turnedOn}));
};
setIntensity = (newValue) => {
this.setState({intensityLevel : newValue});
};
getIcon = () => {
if(this.state.turnedOn){
return this.iconOn;
}
return this.iconOff;
};
componentDidMount() {
if(this.props.device.hasOwnProperty("hasIntensity") && this.props.device.hasOwnProperty("intensityLevel")) {
this.setState({
hasIntensity: this.props.device.hasIntensity,
intensityLevel: this.props.device.intensityLevel
});
}
// Get the state and update it
}
render() {
const intensityLightView = (
<CircularInput
value={this.state.intensityLevel}
onChange={this.setIntensity}
style={style}
>
<CircularTrack/>
<CircularProgress/>
<CircularThumb/>
<text style={valueStyle} x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold">
{Math.round(this.state.intensityLevel*100)}%
</text>
<text style={intensityLightStyle} x={100} y={150} textAnchor="middle" dy="0.3em" fontWeight="bold">
{this.props.device.name}
</text>
</CircularInput>
);
const normalLightView = (
<StyledDiv onClick={this.props.edit ? () => {
} : this.onClickDevice} style={{textAlign: "center"}}>
<Settings
deviceId={this.props.device.id}
edit={this.props.edit}
onChangeData={(id, newSettings) => this.props.onChangeData(id, newSettings)}/>
<Image src={this.getIcon()} style={iconStyle}/>
<h5 style={nameStyle}>{this.props.device.name}</h5>
</StyledDiv>
);
return (
<React.Fragment>
{this.state.hasIntensity ? (intensityLightView) : (normalLightView)}
</React.Fragment>
)
}
}

View file

@ -0,0 +1,14 @@
export const style = {width: "10rem", height: "10rem", position: "absolute", top: "0", left: "0"};
export const valueStyle = {
fill: "#3e99ff",
fontSize: "2.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
};
export const intensityLightStyle = {
fill: "#3e99ff",
fontSize: "1.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
};

View file

@ -1,17 +1,8 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import styled, {keyframes} from 'styled-components'; import styled from 'styled-components';
import {Image} from "semantic-ui-react"; import {Button, Dropdown, Form, Image} from "semantic-ui-react";
import {addDeviceFormStyle} from "./styleComponents";
const rotateAddButton = keyframes` import {deviceList} from "./TypesOfDevices";
0% {
transform : translate(0px, 0px) rotate(0deg);
box-shadow: 3px 2px 10px 5px #ccc;
}
100% {
transform : translate(0.3px, 0.8px) rotate(90deg);
box-shadow: 0.5px 0.5px 7px 3.5px #ccc;
}
`;
const StyledDiv = styled.div` const StyledDiv = styled.div`
background-color : #ff4050; background-color : #ff4050;
@ -26,47 +17,91 @@ const StyledDiv = styled.div`
:hover{ :hover{
background-color : #ff2436; background-color : #ff2436;
} }
:active{
animation-name: ${rotateAddButton};
animation-duration: 0.5s;
animation-timing-function: ease;
animation-delay: 0s;
animation-direction: normal;
animation-play-state: running;
animation-fill-mode: forwards;
}
`; `;
const iconStyle = {
width : "4rem",
height : "auto",
position : "absolute",
top : "20%",
left : "50%",
transform : "translateX(-50%)"
};
const nameStyle = { class NewDeviceForm extends Component {
position : "absolute", constructor(props) {
top : "50%", super(props);
left : "50%", }
transform : "translateX(-50%)"
formSelector = (option) => {
switch (option) {
case "Light":
return <LightForm/>;
case "Sensor":
return "This is a sensor form";
default:
return "This is a default text"
}
};
render() {
let options = [];
deviceList.forEach((e, i) => {
options.push({key: i, text: e, value: e})
});
return (
<Form style={addDeviceFormStyle}>
<Form.Field>
<label style={{color: "white"}}>Select the type of device</label>
<Dropdown clearable options={options} selection/>
</Form.Field>
<Button type='submit' onClick={this.saveChanges}>Save</Button>
</Form>
);
}
} }
class LightForm extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
let options = [
{
key: 1,
value: "common",
text: "Normal Light"
},
{
key: 1,
value: "intensity",
text: "Supports intensity level"
}
]
return (
<Form.Field>
<label style={{color: "white"}}>Type of light</label>
<Dropdown clearable options={options} selection/>
</Form.Field>
);
};
};
export default class NewDevice extends Component { export default class NewDevice extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
openForm: false
} }
} }
onClickDevice = (event) => { onClickDevice = (event) => {
console.log(this.props.children); this.setState((prevState) => ({openForm: !prevState.openForm}));
}; };
render() { render() {
return ( return (
<StyledDiv onClick={this.onClickDevice} style={{textAlign: "center"}}> <StyledDiv onClick={this.onClickDevice} style={{textAlign: "center"}}>
<Image src="/img/add.svg" style={{filter : "invert()"}}/> <Image src="/img/add.svg" style={{filter: "invert()"}}/>
{this.state.openForm ? (
<NewDeviceForm/>
) : ""}
</StyledDiv> </StyledDiv>
) )
} }

View file

@ -1,67 +1,56 @@
import React, {useState} from "react"; /**
import { * Users can add sensors in their rooms.
CircularInput, * Sensors typically measure physical quantities in a room.
CircularTrack, * You must support temperature sensors, humidity sensors, light sensors (which measure luminosity1).
CircularProgress, * Sensors have an internal state that cannot be changed by the user.
CircularThumb, * For this story, make the sensors return a constant value with some small random error.
useCircularInputContext, */
} from 'react-circular-input'
import React, {Component} from "react";
import {CircularInput, CircularProgress, CircularTrack} from "react-circular-input";
import {errorStyle, sensorText, style, valueStyle} from "./SensorStyle";
// Example of a custom component to display text on top of the thumb export default class Light extends Component {
constructor(props) {
function TemperatureDisplay() { super(props);
const {getPointFromValue, value} = useCircularInputContext(); this.state = {
const {x, y} = getPointFromValue(); turnedOn: false,
const style = { value: 20,
fontFamily: "Lato", error : 2.4
fontSize: "1.2rem", };
color: "white", this.units = "ºC"
}
setName = () => {
if(this.props.device.name.length > 15){
return this.props.device.name.slice(0,12) + "..."
}
return this.props.device.name;
}; };
return ( componentDidMount() {
<text x={x} y={y} style={style}>
{Math.round(value * 100)}
</text>
)
}
export default function Sensor(props) {
const style = {width: "10rem", height: "10rem", position: "absolute", top: "0", left: "0"};
const valueStyle = {
fill: "#3e99ff",
fontSize: "2.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
} }
const nameStyle = {
fill: "#3e99ff",
fontSize: "1.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
}
const [value, setValue] = useState(0.25);
render() {
return ( return (
<CircularInput <CircularInput
value={value} value={this.state.value/100}
onChange={setValue}
style={style} style={style}
> >
<CircularTrack/> <CircularTrack/>
<CircularProgress/> <CircularProgress/>
<CircularThumb/>
<text style={valueStyle} x={100} y={100} textAnchor="middle" dy="0.3em" fontWeight="bold"> <text style={valueStyle} x={100} y={80} textAnchor="middle" dy="0.3em" fontWeight="bold">
{Math.round(value * props.device.maxValue)}{props.device.units} {Math.round(this.state.value)}{this.units}
</text> </text>
<text style={nameStyle} x={100} y={150} textAnchor="middle" dy="0.3em" fontWeight="bold"> <text style={errorStyle} x={100} y={100} textAnchor="middle" dy="0.6em" fontWeight="bold">
{props.device.name} &#177;{this.state.error}
</text>
<text style={sensorText} x={100} y={150} textAnchor="middle" dy="0.3em" fontWeight="bold">
{this.setName()}
</text> </text>
</CircularInput> </CircularInput>
) )
}
} }

View file

@ -0,0 +1,23 @@
export const style = {width: "10rem", height: "10rem", position: "absolute", top: "0", left: "0"};
export const sensorText = {
fill: "#3e99ff",
fontSize: "1.2rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
}
export const valueStyle = {
fill: "#3e99ff",
fontSize: "2.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
};
export const errorStyle = {
fill: "#ff4050",
fontSize: "1.5rem",
fontFamily: "Lato",
textShadow: "1px 1px 0.5px rgba(0, 0, 0, .2)",
}

View file

@ -0,0 +1,60 @@
/**
A smart plug is a plug that has a boolean internal state, i.e., that can be turned on or off, either with the
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 user can reset this value.
**/
import React, {Component} from 'react';
import {iconStyle, nameStyle, StyledDiv} from "./styleComponents";
import Settings from "./DeviceSettings";
import {Image} from "semantic-ui-react";
import {energyConsumedStyle, imageStyle} from "./SmartPlugStyle";
export default class SmartPlug extends Component {
constructor(props){
super(props);
this.state = {
turnedOn: false,
energyConsumed : 0 // kWh
};
this.iconOn = "/img/smart-plug.svg";
this.iconOff = "/img/smart-plug-off.svg"
}
onClickDevice = () => {
this.setState((prevState) => ({turnedOn: !prevState.turnedOn}));
};
getIcon = () => {
if(this.state.turnedOn){
return this.iconOn;
}
return this.iconOff;
};
resetEnergyConsumedValue = () => {
// In the settings form there must be an option to restore this value
// along with the rename feature.
}
componentDidMount() {
}
render(){
return (
<StyledDiv onClick={this.props.edit ? () => {} : this.onClickDevice} style={{textAlign: "center"}}>
<Settings
deviceId={this.props.device.id}
edit={this.props.edit}
onChangeData={(id, newSettings) => this.props.onChangeData(id, newSettings)}/>
<Image src={this.getIcon()} style={imageStyle}/>
<h4 style={energyConsumedStyle}>{this.state.energyConsumed} KWh</h4>
<h5 style={nameStyle}>{this.props.device.name}</h5>
</StyledDiv>
)
}
}

View file

@ -0,0 +1,19 @@
import {iconStyle} from "./styleComponents";
export const energyConsumedStyle = {
fontSize : "1.3rem",
position: "absolute",
top: "30%",
left: "50%",
transform: "translateX(-50%)"
};
export const imageStyle = {
width: "3.5rem",
height: "auto",
position: "absolute",
top: "10%",
left: "50%",
transform: "translateX(-50%)",
filter: "drop-shadow( 1px 1px 0.5px rgba(0, 0, 0, .25))"
};

View file

@ -1,13 +1,18 @@
export const LightDevice = { export const LightDevice = {
type : "light",
img : "/img/lightOff.svg", img : "/img/lightOff.svg",
imgClick : "/img/lightOn.svg" imgClick : "/img/lightOn.svg"
}; };
export const SmartPlugDevice = {
img : "/img/smart-plug.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",
maxValue : 30
}; };
export const deviceList = ["Light", "Dimmer", "Switcher", "Smart Plug", "Sensor"];

View file

@ -0,0 +1,5 @@
export const DEVICE_NAME_MAX_LENGTH = 15;
export function checkMaxLength(name){
return !(name.length > DEVICE_NAME_MAX_LENGTH);
}

View file

@ -1,3 +1,5 @@
import styled from "styled-components";
export const editButtonStyle = { export const editButtonStyle = {
position : "absolute", position : "absolute",
top: "0", top: "0",
@ -12,6 +14,7 @@ export const editButtonStyle = {
}; };
export const panelStyle = { export const panelStyle = {
position : "relative",
backgroundColor: "#fafafa", backgroundColor: "#fafafa",
height: "100%", height: "100%",
width: "auto", width: "auto",
@ -38,4 +41,58 @@ export const editModeIconStyle = {
height : "0.75rem", height : "0.75rem",
borderRadius : "20%", borderRadius : "20%",
zIndex : "101" zIndex : "101"
} };
export const iconStyle = {
width: "4rem",
height: "auto",
position: "absolute",
top: "20%",
left: "50%",
transform: "translateX(-50%)",
filter: "drop-shadow( 1px 1px 0.5px rgba(0, 0, 0, .25))"
};
export const nameStyle = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translateX(-50%)"
};
export const formStyle = {
position : "absolute",
zIndex: "1000",
width : "80rem",
height : "10rem",
padding : "1rem",
margin : "1rem",
borderRadius : "10%",
boxShadow : "1px 1px 5px 2px #5d5d5d",
backgroundColor: "#3e99ff",
};
export const addDeviceFormStyle = {
maxWidth : "400px",
background : "#3e99ff",
paddingRight : "5rem",
};
export const StyledDiv = styled.div`
background-color : white;
padding : 3rem;
width: 10rem;
height: 10rem;
border-radius : 100%;
border : none;
position : relative;
box-shadow: 3px 2px 10px 5px #ccc;
transition : all .3s ease-out;
:hover{
background-color : #f2f2f2;
}
:active{
transform : translate(0.3px, 0.8px);
box-shadow: 0.5px 0.5px 7px 3.5px #ccc;
}
`;

View file

@ -4,18 +4,24 @@ import Navbar from './Navbar'
import MyHeader from '../components/HeaderController' import MyHeader from '../components/HeaderController'
import { call } from '../client_server'; import { call } from '../client_server';
import {Button} from 'semantic-ui-react'; import { Grid } from 'semantic-ui-react'
import { Menu } from 'semantic-ui-react' /*
import { Grid, Image, Icon } from 'semantic-ui-react' rooms -> actual rooms
activeItem -> the current room in view
devices -> current device in current room view
id of Home is -1
*/
export default class Dashboard extends Component{ export default class Dashboard extends Component{
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
rooms: [], rooms: [],
activeItem: "Home", activeItem: -1,
tkn: this.props.tkn devices: [],
tkn: this.props.tkn,
}; };
this.addRoom = this.addRoom.bind(this); this.addRoom = this.addRoom.bind(this);
@ -33,6 +39,17 @@ export default class Dashboard extends Component{
}).catch(err => { }).catch(err => {
console.log(err); console.log(err);
}); });
call.getAllDevices(this.props.tkn)
.then(res => {
console.log(res);
res.data.forEach((e) => {
this.setState(state => ({
devices: state.devices.concat([e])
}));
});
}).catch(err => {
console.log(err);
});
} }
addRoom(data) { addRoom(data) {
@ -50,11 +67,27 @@ export default class Dashboard extends Component{
}; };
deleteRoom(id) { deleteRoom(id) {
call.deleteRoom(id)
.then(res => {
//remove room in state.rooms
}).catch(err => {
console.log(err);
});
} }
handleItemClick(el) { handleItemClick(el) {
// el -> obj of name and id
//da fare richiesta get della room e settare activeItem //da fare richiesta get della room e settare activeItem
call.getAllDevicesByRoom(el.id, this.props.tkn)
.then(res => {
res.data.forEach((e) => {
this.setState(state => ({
devices: state.devices.concat([e])
}));
});
}).catch(err => {
console.log(err);
});
} }
render () { render () {
@ -72,7 +105,7 @@ export default class Dashboard extends Component{
</Grid.Column> </Grid.Column>
<Grid.Column width={13}> <Grid.Column width={13}>
<DevicePanel /> <DevicePanel devices={this.state.devices} />
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
</Grid> </Grid>

View file

@ -1,5 +1,5 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import { Message} from 'semantic-ui-react'; import { Header, Grid, Message, Button} from 'semantic-ui-react';
import {Link } from "react-router-dom"; import {Link } from "react-router-dom";
@ -7,13 +7,29 @@ export default class FourOhFour extends Component {
render() { render() {
return ( return (
<div style={{height : "110vh", background: '#1b1c1d'}}>
<Grid centered>
<Grid.Row>
<Header as='h1'>404</Header>
</Grid.Row>
<Grid.Row>
<Grid.Column width={10}>
<Message> <Message>
<Message.Header>404 Page Not Found</Message.Header> <Message.Header>404 Page Not Found</Message.Header>
<p> <p>
Hey what are you doing here? Hey what are you doing here?
Go back to our homepage <Link to="/"/> Looks like you are lost, this room does not exist.
</p> </p>
</Message> </Message>
</Grid.Column>
<Grid.Column width={6}>
<Button >
<Link to="/">Go back to our main room</Link>
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
) )
} }
} }