Merge branch '72-7-users-can-invite-guests' into 'dev'

Resolve "7. Users can invite guests"

Closes #72

See merge request sa4-2020/the-sanmarinoes/frontend!115
This commit is contained in:
Claudio Maggioni 2020-05-04 12:06:50 +02:00
commit c7334b735f
28 changed files with 761 additions and 540 deletions

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

@ -0,0 +1,137 @@
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";
//import { update } from "immutability-helper";
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.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 (
<React.Fragment>
<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>
<marquee scrollamount="50">
<h1>Spaghetti!</h1>
</marquee>
<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.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>
</React.Fragment>
);
}
}
const setActiveHost = (activeHost) => {
return (dispatch) => dispatch(appActions.setActiveHost(activeHost));
};
const mapStateToProps = (state) => ({
guests: state.guests,
});
const HostModalContainer = connect(
mapStateToProps,
{ ...RemoteService, setActiveHost },
null,
{ forwardRef: true }
)(HostModal);
export default HostModalContainer;

View file

@ -21,7 +21,6 @@ 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();

View file

@ -255,8 +255,6 @@ class AutomationSaveModal extends Component {
} }
_checkNewTrigger(trigger) { _checkNewTrigger(trigger) {
const auxDevice = this.props.devices[trigger.device];
// Check for missing fields for creation // Check for missing fields for creation
const error = { const error = {
result: false, result: false,
@ -271,6 +269,8 @@ class AutomationSaveModal extends Component {
case "rangeTrigger": case "rangeTrigger":
if (!trigger.device || !trigger.operand || !trigger.value) return error; if (!trigger.device || !trigger.operand || !trigger.value) return error;
break; break;
default:
throw new Error("theoretically unreachable statement");
} }
const isNotDuplicate = !this.state.triggerList.some( const isNotDuplicate = !this.state.triggerList.some(
@ -345,6 +345,8 @@ class AutomationSaveModal extends Component {
return "" + trigger.device + trigger.on; return "" + trigger.device + trigger.on;
case "rangeTrigger": case "rangeTrigger":
return "" + trigger.device + trigger.operand + trigger.value; return "" + trigger.device + trigger.operand + trigger.value;
default:
throw new Error("theoretically unreachable statement");
} }
}; };

View file

@ -1,4 +1,4 @@
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";

View file

@ -29,7 +29,7 @@ class DevicePanel extends Component {
return <Device key={i} tab={this.props.tab} id={e.id} />; return <Device key={i} tab={this.props.tab} id={e.id} />;
})} })}
{!this.props.isActiveRoomHome ? ( {!this.props.isActiveRoomHome ? (
<Card style={{ height: "23em" }}> <Card style={{ height: "27em" }}>
<Segment basic style={{ width: "100%", height: "100%" }}> <Segment basic style={{ width: "100%", height: "100%" }}>
<NewDevice /> <NewDevice />
</Segment> </Segment>

View file

@ -0,0 +1,51 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { RemoteService } from "../../remote";
import { Card, Segment, Header, Icon } from "semantic-ui-react";
import Device from "../../components/dashboard/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);
}
}
render() {
return (
<Card.Group centered style={{ paddingTop: "3rem" }}>
{this.props.isActiveDefaultHost && (
<Segment placeholder>
<Header icon>
<Icon name="folder open" />
Please select a host to visit on the left.
</Header>
</Segment>
)}
{this.props.hostDeviceIds.map((id) => {
return (
<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,
hostDevices: state.hostDevices,
hostDeviceIds: Object.keys(state.hostDevices[state.active.activeHost] || {}),
});
const HostsPanelContainer = connect(mapStateToProps, RemoteService)(HostsPanel);
export default HostsPanelContainer;

View file

@ -48,6 +48,7 @@ class NewSceneDevice extends Component {
handleOpen = () => { handleOpen = () => {
this.setState({ openModal: true }); this.setState({ openModal: true });
}; };
handleClose = () => { handleClose = () => {
this.setState({ openModal: false }); this.setState({ openModal: false });
}; };

View file

@ -3,7 +3,7 @@ import { connect } from "react-redux";
import { RemoteService } from "../../remote"; import { RemoteService } from "../../remote";
import Device from "./devices/Device"; import Device from "./devices/Device";
import NewSceneDevice from "./NewSceneDevice"; import NewSceneDevice from "./NewSceneDevice";
import { Grid, Button, Card, Segment, Header } from "semantic-ui-react"; import { Button, Card, Segment, Header, Icon } from "semantic-ui-react";
class ScenesPanel extends Component { class ScenesPanel extends Component {
constructor(props) { constructor(props) {
@ -42,7 +42,12 @@ class ScenesPanel extends Component {
</Card.Content> </Card.Content>
</Card> </Card>
) : ( ) : (
<Grid.Column>Welcome to the Scene View, you add a Scene</Grid.Column> <Segment placeholder>
<Header icon>
<Icon name="folder open" />
Please select a scene on the left or add a new one.
</Header>
</Segment>
)} )}
{!this.props.isActiveDefaultScene {!this.props.isActiveDefaultScene
? this.props.sceneStates.map((e, i) => { ? this.props.sceneStates.map((e, i) => {
@ -60,7 +65,6 @@ 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);
return stateArray.map((id) => state.sceneStates[id]); return stateArray.map((id) => state.sceneStates[id]);
} else { } else {
return []; return [];

View file

@ -2,6 +2,7 @@ import React, { Component } from "react";
import "./Curtains.css"; import "./Curtains.css";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
class Curtain extends Component { class Curtain extends Component {
constructor(props) { constructor(props) {
@ -100,6 +101,7 @@ class Curtain extends Component {
{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 +114,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

@ -7,10 +7,11 @@ import Switcher from "./Switch";
import Videocam from "./Videocam"; import Videocam from "./Videocam";
import Curtains from "./Curtain"; import Curtains from "./Curtain";
import Thermostat from "./Thermostats"; import Thermostat from "./Thermostats";
import { Segment, Grid, Header, Button, Icon, Card } from "semantic-ui-react"; import { Header, Button, Icon, Card } from "semantic-ui-react";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import DeviceSettingsModal from "./DeviceSettingsModal"; import DeviceSettingsModal from "./DeviceSettingsModal";
import mapStateToProps from "../../../deviceProps";
const centerComponent = { const centerComponent = {
marginLeft: "50%", marginLeft: "50%",
@ -41,75 +42,37 @@ class Device extends React.Component {
} }
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.stateOrDevice.kind);
} }
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 React.createElement(
mapKindToComponent[this.props.type],
{
tab: this.props.tab,
id: this.props.id,
hostId: this.props.hostId,
},
""
);
} }
deviceDescription() { deviceDescription() {
@ -151,89 +114,40 @@ class Device extends React.Component {
} }
get deviceName() { get deviceName() {
return this.props.tab === "Devices" return this.props.device.name;
? this.props.stateOrDevice.name
: this.props.device.name;
} }
render() { render() {
{ return (
return ( <Card style={{ height: "27em" }}>
<Card <Card.Content>
style={{ height: this.props.tab === "Devices" ? "23em" : "27em" }} <Card.Header textAlign="center">
> <Header as="h3">{this.deviceName}</Header>
<Card.Content> <Header as="h4">{this.props.roomName}</Header>
<Card.Header textAlign="center"> </Card.Header>
<Header as="h3">{this.deviceName}</Header>
{this.props.tab === "Scenes" ? (
<Header as="h4">{this.props.roomName}</Header>
) : (
""
)}
</Card.Header>
<Card.Description <Card.Description
style={ style={
this.props.device.kind !== "curtains" ? centerComponent : null this.props.device.kind !== "curtains" ? centerComponent : null
} }>
> {this.renderDeviceComponent()}
{this.renderDeviceComponent()} </Card.Description>
</Card.Description> </Card.Content>
</Card.Content> <Card.Content extra>
<Card.Content extra> {this.props.tab === "Devices"
{this.props.tab === "Devices" ? this.deviceDescription()
? this.deviceDescription() : this.props.tab === "Scenes" && this.stateDescription()}
: this.stateDescription()} </Card.Content>
</Card.Content> {this.props.tab === "Devices" ? (
{this.props.tab === "Devices" ? ( <DeviceSettingsModal
<DeviceSettingsModal ref={this.modalRef}
ref={this.modalRef} id={this.props.stateOrDevice.id}
id={this.props.stateOrDevice.id} />
/> ) : null}
) : null} </Card>
</Card> );
);
}
} }
} }
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() {
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

@ -29,6 +29,7 @@ import {
} from "./DimmerStyle"; } from "./DimmerStyle";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
export class ButtonDimmerComponent extends Component { export class ButtonDimmerComponent extends Component {
increaseIntensity = () => { increaseIntensity = () => {
@ -104,7 +105,7 @@ export class KnobDimmerComponent extends Component {
<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}
@ -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

@ -31,6 +31,7 @@ import {
} from "./LightStyle"; } from "./LightStyle";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
class Light extends Component { class Light extends Component {
constructor(props) { constructor(props) {
@ -62,14 +63,17 @@ 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(
{ ...this.props.stateOrDevice, on },
this.props.tab === "Hosts" ? this.props.activeHost : null
)
.catch((err) => console.error("regular light update error", err)); .catch((err) => console.error("regular light update error", err));
} else { } else {
if (this.props.device.kind === "regularLight") { if (this.props.device.kind === "regularLight") {
@ -114,12 +118,14 @@ 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: intensity },
@ -136,9 +142,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,7 +159,7 @@ 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} />
@ -184,22 +190,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

@ -38,6 +38,7 @@ import {
import { Image } from "semantic-ui-react"; import { Image } from "semantic-ui-react";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
class Sensor extends Component { class Sensor extends Component {
constructor(props) { constructor(props) {
@ -194,14 +195,5 @@ class Sensor 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 SensorContainer = connect(mapStateToProps, RemoteService)(Sensor); const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor);
export default SensorContainer; export default SensorContainer;

View file

@ -15,6 +15,7 @@ import {
} from "./SmartPlugStyle"; } from "./SmartPlugStyle";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
class SmartPlug extends Component { class SmartPlug extends Component {
constructor(props) { constructor(props) {
@ -51,7 +52,7 @@ class SmartPlug extends Component {
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>
@ -70,21 +71,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];
}
},
get device() {
if (state.active.activeTab === "Devices") {
return state.devices[ownProps.id];
} else {
return state.devices[state.sceneStates[ownProps.id].deviceId];
}
},
});
const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug); const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug);
export default SmartPlugContainer; export default SmartPlugContainer;

View file

@ -11,6 +11,7 @@ import { Image } from "semantic-ui-react";
import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle"; import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle";
import { RemoteService } from "../../../remote"; import { RemoteService } from "../../../remote";
import { connect } from "react-redux"; import { connect } from "react-redux";
import mapStateToProps from "../../../deviceProps";
class Switch extends Component { class Switch extends Component {
constructor(props) { constructor(props) {
@ -37,7 +38,7 @@ class Switch extends Component {
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>
@ -55,8 +56,5 @@ class Switch extends Component {
} }
} }
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

@ -28,4 +28,4 @@
background: white; background: white;
border-radius: 5px; border-radius: 5px;
} }
*/ */

View file

@ -5,6 +5,7 @@ import { connect } from "react-redux";
import "./Thermostat.css"; import "./Thermostat.css";
import Slider from "react-rangeslider"; import Slider from "react-rangeslider";
import "react-rangeslider/lib/index.css"; import "react-rangeslider/lib/index.css";
import mapStateToProps from "../../../deviceProps";
import { import {
stateTag, stateTag,
@ -24,7 +25,6 @@ class Thermostats extends Component {
measuredTemperature: this.props.device.measuredTemperature, measuredTemperature: this.props.device.measuredTemperature,
useExternalSensors: this.props.device.useExternalSensors, useExternalSensors: this.props.device.useExternalSensors,
}; };
console.log(this.state);
this.setMode = this.setMode.bind(this); this.setMode = this.setMode.bind(this);
this.setTargetTemperature = this.setTargetTemperature.bind(this); this.setTargetTemperature = this.setTargetTemperature.bind(this);
} }
@ -33,13 +33,11 @@ class Thermostats extends Component {
if (this.state.timeout) { if (this.state.timeout) {
clearTimeout(this.state.timeout); clearTimeout(this.state.timeout);
} }
console.log(mode);
//i came to the conclusion that is not possible to set mode. //i came to the conclusion that is not possible to set mode.
// Good job Jacob (Claudio) // Good job Jacob (Claudio)
//this.mode = "HEATING"; //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 })
@ -87,43 +85,6 @@ class Thermostats extends Component {
} }
} }
setTargetTemperature(newTemp) {
if (this.state.timeout) {
clearTimeout(this.state.timeout);
}
this.setState({
newTemp,
timeout: setTimeout(() => {
this.saveTargetTemperature(newTemp);
this.setState({
targetTemperature: this.state.targetTemperature,
timeout: null,
});
}, 100),
});
}
saveTargetTemperature(targetTemperature) {
if (this.props.tab === "Devices") {
this.props
.saveDevice({
...this.props.device,
turnOn: this.props.device.mode !== "OFF",
targetTemperature,
})
.catch((err) => console.error("thermostat update error", err));
} else {
this.props.updateState(
{
id: this.props.stateOrDevice.id,
targetTemperature: targetTemperature,
},
this.props.stateOrDevice.kind
);
}
}
setTargetTemperature() { setTargetTemperature() {
this.saveTargetTemperature(this.state.targetTemperature); this.saveTargetTemperature(this.state.targetTemperature);
} }
@ -138,6 +99,7 @@ class Thermostats extends Component {
<h3 style={deviceName}> <h3 style={deviceName}>
Thermostat Thermostat
<Checkbox <Checkbox
disabled={this.props.disabled}
checked={ checked={
this.props.tab === "Devices" this.props.tab === "Devices"
? this.props.device.mode !== "OFF" ? this.props.device.mode !== "OFF"
@ -157,6 +119,7 @@ class Thermostats extends Component {
</div> </div>
{this.props.tab === "Devices" ? ( {this.props.tab === "Devices" ? (
<Slider <Slider
disabled={this.props.disabled}
min={10} min={10}
max={30} max={30}
step={0.1} step={0.1}
@ -169,7 +132,7 @@ class Thermostats extends Component {
) : null} ) : null}
<div style={stateTagContainer}> <div style={stateTagContainer}>
<span style={stateTag}> <span style={stateTag}>
{this.props.tab === "Devices" {this.props.tab !== "Scenes"
? this.props.device.mode ? this.props.device.mode
: this.props.stateOrDevice.on : this.props.stateOrDevice.on
? "WILL TURN ON" ? "WILL TURN ON"
@ -181,24 +144,6 @@ class Thermostats 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];
}
},
activeTab: state.activeTab,
});
const ThermostatContainer = connect( const ThermostatContainer = connect(
mapStateToProps, mapStateToProps,
RemoteService RemoteService

View file

@ -7,6 +7,7 @@ import { RemoteService } from "../../../remote";
import { endpointURL } from "../../../endpoint"; import { endpointURL } from "../../../endpoint";
import { connect } from "react-redux"; import { connect } from "react-redux";
import VideocamModal from "./VideocamModal"; import VideocamModal from "./VideocamModal";
import mapStateToProps from "../../../deviceProps";
class Videocam extends Component { class Videocam extends Component {
constructor(props) { constructor(props) {
@ -86,12 +87,5 @@ class Videocam extends Component {
} }
} }
const mapStateToProps = (state, ownProps) => ({
device:
ownProps.tab === "Devices"
? state.devices[ownProps.id]
: state.devices[state.sceneStates[ownProps.id].deviceId],
state: state.sceneStates[ownProps.id],
});
const VideocamContainer = connect(mapStateToProps, RemoteService)(Videocam); const VideocamContainer = connect(mapStateToProps, RemoteService)(Videocam);
export default VideocamContainer; export default VideocamContainer;

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

@ -261,13 +261,22 @@ export const RemoteService = {
/** /**
* Fetches all rooms that belong to this user. This call does not * Fetches all rooms that belong to this user. This call does not
* populate the devices attribute in rooms. * populate the devices attribute in rooms.
* @param {Number|null} hostId the user id of the host we need to fetch the rooms from.
* Null if we need to fetch our own rooms.
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects * @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError * with user-fiendly errors as a RemoteError
*/ */
fetchAllRooms: () => { fetchAllRooms: (hostId = null) => {
return (dispatch) => { return (dispatch) => {
return Endpoint.get("/room") return Endpoint.get("/room", hostId ? { hostId } : null)
.then((res) => void dispatch(actions.roomsUpdate(res.data))) .then(
(res) =>
void dispatch(
hostId
? actions.hostRoomsUpdate(hostId, res.data)
: actions.roomsUpdate(res.data)
)
)
.catch((err) => { .catch((err) => {
console.error("Fetch all rooms error", err); console.error("Fetch all rooms error", err);
throw new RemoteError(["Network error"]); throw new RemoteError(["Network error"]);
@ -297,13 +306,25 @@ export const RemoteService = {
* This also updates the devices attribute on values in the map rooms. * This also updates the devices attribute on values in the map rooms.
* @param {Number|null} roomId the rsoom to which fetch devices * @param {Number|null} roomId the rsoom to which fetch devices
* from, null to fetch from all rooms * from, null to fetch from all rooms
* @param {Number|null} hostId the user id of the owner of the devices to get
* (can be used for host view)
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects * @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError * with user-fiendly errors as a RemoteError
*/ */
fetchDevices: (roomId = null) => { fetchDevices: (roomId = null, hostId = null) => {
return (dispatch) => { return (dispatch) => {
return Endpoint.get(roomId ? `/room/${roomId}/device` : "/device") return Endpoint.get(
.then((res) => void dispatch(actions.devicesUpdate(roomId, res.data))) roomId ? `/room/${roomId}/device` : "/device",
hostId ? { hostId } : null
)
.then(
(res) =>
void dispatch(
!hostId
? actions.devicesUpdate(roomId, res.data, hostId)
: actions.hostDevicesUpdate(hostId, res.data)
)
)
.catch((err) => { .catch((err) => {
console.error(`Fetch devices roomId=${roomId} error`, err); console.error(`Fetch devices roomId=${roomId} error`, err);
throw new RemoteError(["Network error"]); throw new RemoteError(["Network error"]);
@ -346,6 +367,56 @@ export const RemoteService = {
}; };
}, },
/**
* Fetches all hosts of a particular user.
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError
*/
fetchHosts: () => {
return (dispatch) => {
return Endpoint.get(`/user/hosts`)
.then((res) => void dispatch(actions.hostsUpdate(res.data)))
.catch((err) => {
console.error(`Fetch hosts error`, err);
throw new RemoteError(["Network error"]);
});
};
},
/**
* Fetches all guests of a particular user.
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError
*/
fetchGuests: () => {
return (dispatch) => {
return Endpoint.get(`/user/guests`)
.then((res) => void dispatch(actions.guestsUpdate(res.data)))
.catch((err) => {
console.error(`Fetch guests error`, err);
throw new RemoteError(["Network error"]);
});
};
},
/**
* Adds the current user as a guest to another user
* identified through a user id.
* @param {Number[]} userId the users to add.
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError
*/
updateGuests: (userIds) => {
return (dispatch) => {
return Endpoint.put(`/user/guests`, {}, { ids: userIds })
.then((res) => void dispatch(actions.guestsUpdate(res.data)))
.catch((err) => {
console.error(`Guest save error`, err);
throw new RemoteError(["Network Error"]);
});
};
},
/** /**
* Creates/Updates a room with the given data * Creates/Updates a room with the given data
* @param {String} data.name the room's name, * @param {String} data.name the room's name,
@ -458,16 +529,24 @@ export const RemoteService = {
* @returns {Promise<Device, RemoteError>} promise that resolves to the saved device and rejects * @returns {Promise<Device, RemoteError>} promise that resolves to the saved device and rejects
* with user-fiendly errors as a RemoteError * with user-fiendly errors as a RemoteError
*/ */
saveDevice: (data) => { saveDevice: (data, hostId = null) => {
return (dispatch) => { return (dispatch) => {
let url = "/device"; let url = "/device";
if ((data.id && data.flowType === "OUTPUT") || !data.id) { if ((data.id && data.flowType === "OUTPUT") || !data.id) {
url = "/" + data.kind; url = "/" + data.kind;
} }
return Endpoint[data.id ? "put" : "post"](url, {}, data) return Endpoint[data.id ? "put" : "post"](
url,
hostId ? { hostId } : {},
data
)
.then((res) => { .then((res) => {
dispatch(actions.deviceSave(res.data)); dispatch(
hostId
? actions.hostDeviceSave(hostId, res.data)
: actions.deviceSave(res.data)
);
return res.data; return res.data;
}) })
.catch((err) => { .catch((err) => {
@ -793,6 +872,15 @@ for (const key in RemoteService) {
} }
export class Forms { export class Forms {
static fetchAllUsers() {
return Endpoint.get(`/user`)
.then((res) => res.data)
.catch((err) => {
console.error(`Fetch users error`, err);
throw new RemoteError(["Network error"]);
});
}
/** /**
* Attempts to create a new user from the given data. * Attempts to create a new user from the given data.
* This method does not update the global state, * This method does not update the global state,

View file

@ -3,6 +3,7 @@ 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";
import actions from "./storeActions";
function reducer(previousState, action) { function reducer(previousState, action) {
let newState, change; let newState, change;
@ -99,6 +100,20 @@ function reducer(previousState, action) {
createOrUpdateRoom(room); createOrUpdateRoom(room);
} }
break; break;
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": case "SCENES_UPDATE":
newState = previousState; newState = previousState;
for (const scene of action.scenes) { for (const scene of action.scenes) {
@ -255,7 +270,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];
for (const device of action.devices) {
deviceMap[device.id] = { $set: device };
}
newState = update(newState, change);
break;
case "AUTOMATION_UPDATE": case "AUTOMATION_UPDATE":
const automations = {}; const automations = {};
for (const automation of action.automations) { for (const automation of action.automations) {
@ -299,6 +334,28 @@ function reducer(previousState, action) {
} }
newState = update(previousState, change); newState = update(previousState, change);
break; break;
case "HOST_DEVICE_SAVE":
change = {
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": case "AUTOMATION_SAVE":
change = { change = {
@ -433,51 +490,62 @@ function reducer(previousState, action) {
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;
case "SET_ACTIVE_TAB":
newState = update(previousState, {
active: {
activeTab: {
$set: action.activeTab,
},
},
});
break;
case "SET_ACTIVE_SCENE":
newState = update(previousState, {
active: {
activeScene: {
$set: action.activeScene,
},
},
});
break;
case "SET_ACTIVE_AUTOMATION":
newState = update(previousState, {
active: {
activeAutomation: {
$set: action.activeAutomation,
}, },
}, },
}); });
break; break;
case "REDUX_WEBSOCKET::MESSAGE": case "REDUX_WEBSOCKET::MESSAGE":
const devices = JSON.parse(action.payload.message); const allDevices = JSON.parse(action.payload.message);
//console.log("socket", JSON.stringify(devices, null, 2)); const devices = allDevices.filter(
(d) =>
(d.fromHostId === null || d.fromHostId === undefined) && !d.deleted
);
const hostDevicesMapByHostId = allDevices
.filter((d) => d.fromHostId)
.reduce((a, e) => {
const hostId = e.fromHostId;
//delete e.fromHostId;
a[hostId] = a[hostId] || { updated: [], deletedIds: [] };
if (e.deleted) {
a[hostId].deletedIds.push(e.id);
} else {
a[hostId].updated.push(e);
}
return a;
}, {});
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);
@ -497,6 +565,7 @@ const initState = {
activeTab: "Devices", activeTab: "Devices",
activeScene: -1, activeScene: -1,
activeAutomation: -1, activeAutomation: -1,
activeHost: -1,
}, },
login: { login: {
loggedIn: false, loggedIn: false,
@ -513,6 +582,14 @@ const initState = {
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() {

View file

@ -25,6 +25,11 @@ const actions = {
type: "DEVICE_SAVE", type: "DEVICE_SAVE",
device, device,
}), }),
hostDeviceSave: (hostId, device) => ({
type: "HOST_DEVICE_SAVE",
hostId,
device,
}),
triggerSave: (automation) => ({ triggerSave: (automation) => ({
type: "TRIGGER_SAVE", type: "TRIGGER_SAVE",
automation, automation,
@ -58,6 +63,11 @@ const actions = {
devices, devices,
partial, partial,
}), }),
hostDevicesUpdate: (hostId, devices) => ({
type: "HOST_DEVICES_UPDATE",
hostId,
devices,
}),
stateDelete: (stateId) => ({ stateDelete: (stateId) => ({
type: "STATE_DELETE", type: "STATE_DELETE",
stateId, stateId,
@ -71,6 +81,11 @@ const actions = {
type: "ROOMS_UPDATE", type: "ROOMS_UPDATE",
rooms, rooms,
}), }),
hostRoomsUpdate: (hostId, rooms) => ({
type: "HOST_ROOMS_UPDATE",
hostId,
rooms,
}),
roomDelete: (roomId) => ({ roomDelete: (roomId) => ({
type: "ROOM_DELETE", type: "ROOM_DELETE",
roomId, roomId,
@ -91,25 +106,48 @@ const actions = {
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

@ -2,8 +2,10 @@ import React, { Component } from "react";
import DevicePanel from "../components/dashboard/DevicePanel"; import DevicePanel from "../components/dashboard/DevicePanel";
import ScenesPanel from "../components/dashboard/ScenesPanel"; import ScenesPanel from "../components/dashboard/ScenesPanel";
import AutomationsPanel from "../components/dashboard/AutomationsPanel"; import AutomationsPanel from "../components/dashboard/AutomationsPanel";
import HostsPanel from "../components/dashboard/HostsPanel";
import Navbar from "./Navbar"; import Navbar from "./Navbar";
import ScenesNavbar from "./ScenesNavbar"; import ScenesNavbar from "./ScenesNavbar";
import HostsNavbar from "./HostsNavbar";
import MyHeader from "../components/HeaderController"; import MyHeader from "../components/HeaderController";
import { Grid, Responsive, Button, Menu } from "semantic-ui-react"; import { Grid, Responsive, Button, Menu } from "semantic-ui-react";
import { import {
@ -19,7 +21,6 @@ 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 = "Devices";
this.selectTab = this.selectTab.bind(this); this.selectTab = this.selectTab.bind(this);
} }
@ -55,6 +56,8 @@ class Dashboard extends Component {
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>;
} }
@ -66,6 +69,8 @@ class Dashboard extends Component {
return <Navbar />; return <Navbar />;
case "Scenes": case "Scenes":
return <ScenesNavbar />; return <ScenesNavbar />;
case "Hosts":
return <HostsNavbar />;
default: default:
return <h1>ERROR</h1>; return <h1>ERROR</h1>;
} }
@ -87,7 +92,7 @@ class Dashboard extends Component {
</Grid.Row> </Grid.Row>
<Grid.Row color="black"> <Grid.Row color="black">
<Grid.Column textAlign="center" width={16}> <Grid.Column textAlign="center" width={16}>
<Menu fluid widths={3} inverted color="grey"> <Menu fluid widths={4} inverted color="grey">
<Menu.Item <Menu.Item
name="Devices" name="Devices"
content="Devices" content="Devices"
@ -106,6 +111,12 @@ class Dashboard extends Component {
active={this.activeTab === "Automations"} active={this.activeTab === "Automations"}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Menu.Item
name="Hosts"
content="Hosts and Guests"
active={this.activeTab === "Hosts"}
onClick={this.selectTab}
/>
</Menu> </Menu>
</Grid.Column> </Grid.Column>
@ -153,6 +164,16 @@ class Dashboard extends Component {
color={this.activeTab === "Automations" ? "yellow" : "grey"} color={this.activeTab === "Automations" ? "yellow" : "grey"}
onClick={this.selectTab} onClick={this.selectTab}
/> />
<Button
basic
name="Hosts"
content="Hosts"
active={this.activeTab === "Hosts"}
color={
this.activeTab === "Hosts and Guests" ? "yellow" : "grey"
}
onClick={this.selectTab}
/>
</Grid.Column> </Grid.Column>
</Grid.Row> </Grid.Row>
{this.hasNavbar && ( {this.hasNavbar && (

View file

@ -0,0 +1,121 @@
import React, { Component } from "react";
import { Menu, Grid, Responsive, Dropdown } from "semantic-ui-react";
import HostModal from "../components/HostModal";
import { RemoteService } from "../remote";
import { connect } from "react-redux";
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) => {
return (
<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) => {
return (
<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) => {
return (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

@ -31,7 +31,6 @@ class Login extends Component {
.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(" - ") },
}); });

View file

@ -82,7 +82,9 @@ 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>

View file

@ -86,7 +86,7 @@ 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) => {
@ -137,7 +137,7 @@ 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>