Merge branch 'dashboard-feature' into 'dev'
Almost all the devices in the dashboard are implemented, along with the... See merge request sa4-2020/the-sanmarinoes/frontend!26
This commit is contained in:
commit
b2503156a9
17 changed files with 690 additions and 254 deletions
1
smart-hut/public/img/smart-plug-off.svg
Normal file
1
smart-hut/public/img/smart-plug-off.svg
Normal 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 |
1
smart-hut/public/img/smart-plug.svg
Normal file
1
smart-hut/public/img/smart-plug.svg
Normal 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 |
|
@ -4,57 +4,106 @@ import {
|
||||||
} from "semantic-ui-react";
|
} from "semantic-ui-react";
|
||||||
import Device from "./devices/Device";
|
import Device from "./devices/Device";
|
||||||
import NewDevice from "./devices/NewDevice";
|
import NewDevice from "./devices/NewDevice";
|
||||||
import {LightDevice, TemperatureSensor} from "./devices/TypesOfDevices";
|
import {LightDevice, SmartPlugDevice} 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 Light from "./devices/Light";
|
||||||
|
import SmartPlug from "./devices/SmartPlug";
|
||||||
|
import Sensor from "./devices/Sensor";
|
||||||
|
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
class Panel extends Component {
|
class Panel extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
editMode : false
|
editMode : false,
|
||||||
|
devices : 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 columns={5} divided="vertically">
|
||||||
{devices.map((e, i) => {
|
<Grid.Column>
|
||||||
return (
|
<Light onChangeData={this.changeDeviceData} device={devices[0]} edit={this.state.editMode}/>
|
||||||
<Grid.Column key={i.toString()}>
|
</Grid.Column>
|
||||||
<Device device={e} edit={this.state.editMode}/>
|
<Grid.Column>
|
||||||
|
<SmartPlug onChangeData={this.changeDeviceData} device={devices[4]} edit={this.state.editMode}/>
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column>
|
||||||
|
<Sensor onChangeData={this.changeDeviceData} device={devices[5]} edit={this.state.editMode}/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
)
|
|
||||||
})}
|
|
||||||
<Grid.Column>
|
<Grid.Column>
|
||||||
<NewDevice/>
|
<NewDevice/>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
73
smart-hut/src/components/dashboard/devices/DeviceSettings.js
Normal file
73
smart-hut/src/components/dashboard/devices/DeviceSettings.js
Normal 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"}}>×</p>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.props.edit ? view : ("")}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
52
smart-hut/src/components/dashboard/devices/Dimmer.js
Normal file
52
smart-hut/src/components/dashboard/devices/Dimmer.js
Normal 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 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
92
smart-hut/src/components/dashboard/devices/Light.js
Normal file
92
smart-hut/src/components/dashboard/devices/Light.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
14
smart-hut/src/components/dashboard/devices/LightStyle.js
Normal file
14
smart-hut/src/components/dashboard/devices/LightStyle.js
Normal 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)",
|
||||||
|
};
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
±{this.state.error}
|
||||||
|
</text>
|
||||||
|
<text style={sensorText} x={100} y={150} textAnchor="middle" dy="0.3em" fontWeight="bold">
|
||||||
|
{this.setName()}
|
||||||
</text>
|
</text>
|
||||||
</CircularInput>
|
</CircularInput>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
23
smart-hut/src/components/dashboard/devices/SensorStyle.js
Normal file
23
smart-hut/src/components/dashboard/devices/SensorStyle.js
Normal 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)",
|
||||||
|
}
|
60
smart-hut/src/components/dashboard/devices/SmartPlug.js
Normal file
60
smart-hut/src/components/dashboard/devices/SmartPlug.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
19
smart-hut/src/components/dashboard/devices/SmartPlugStyle.js
Normal file
19
smart-hut/src/components/dashboard/devices/SmartPlugStyle.js
Normal 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))"
|
||||||
|
};
|
0
smart-hut/src/components/dashboard/devices/Switcher.js
Normal file
0
smart-hut/src/components/dashboard/devices/Switcher.js
Normal 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"];
|
||||||
|
|
5
smart-hut/src/components/dashboard/devices/constants.js
Normal file
5
smart-hut/src/components/dashboard/devices/constants.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const DEVICE_NAME_MAX_LENGTH = 15;
|
||||||
|
|
||||||
|
export function checkMaxLength(name){
|
||||||
|
return !(name.length > DEVICE_NAME_MAX_LENGTH);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
Loading…
Reference in a new issue