WIP on redux conversion for devices
This commit is contained in:
parent
eead3694e6
commit
69a6cbae6d
16 changed files with 232 additions and 395 deletions
|
@ -4,87 +4,32 @@ import React, { Component } from "react";
|
||||||
import { Grid } from "semantic-ui-react";
|
import { Grid } from "semantic-ui-react";
|
||||||
import { editButtonStyle, panelStyle } from "./devices/styleComponents";
|
import { editButtonStyle, panelStyle } from "./devices/styleComponents";
|
||||||
import { checkMaxLength, DEVICE_NAME_MAX_LENGTH } from "./devices/constants";
|
import { checkMaxLength, DEVICE_NAME_MAX_LENGTH } from "./devices/constants";
|
||||||
import DeviceType from "./devices/DeviceTypeController";
|
import Device from "./devices/Device";
|
||||||
import NewDevice from "./devices/NewDevice";
|
import NewDevice from "./devices/NewDevice";
|
||||||
import SettingsModal from "./devices/SettingsModal";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { RemoteService } from "../../remote";
|
import { RemoteService } from "../../remote";
|
||||||
|
|
||||||
class DevicePanel extends Component {
|
class DevicePanel extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
editMode: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addDevice = this.addDevice.bind(this);
|
|
||||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
|
||||||
this.getDevices();
|
this.getDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleEditMode(e) {
|
|
||||||
this.setState((prevState) => ({ editMode: !prevState.editMode }));
|
|
||||||
}
|
|
||||||
|
|
||||||
openModal = (settingsDeviceId) => {
|
|
||||||
this.setState((prevState) => ({
|
|
||||||
openSettingsModal: !prevState.openSettingsModal,
|
|
||||||
settingsDeviceId: settingsDeviceId,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
/*changeDeviceData = (deviceId, newSettings) => {
|
|
||||||
console.log(newSettings.name, " <-- new name --> ", deviceId);
|
|
||||||
|
|
||||||
for (let prop in this.props.devices[deviceId]) {
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};*/
|
|
||||||
|
|
||||||
getDevices() {
|
getDevices() {
|
||||||
this.props.fetchDevices().then();
|
this.props
|
||||||
|
.fetchDevices()
|
||||||
|
.catch((err) => console.error(`error fetching devices:`, err));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const edit = {
|
|
||||||
mode: this.state.editMode,
|
|
||||||
openModal: this.openModal,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*var backGroundImg =
|
|
||||||
this.props.activeItem === -1 ? "" : this.props.room.image;*/
|
|
||||||
const ds = this.state.devices ? this.state.devices : this.props.devices;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={panelStyle}>
|
<div style={panelStyle}>
|
||||||
<button style={editButtonStyle} onClick={this.editModeController}>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<Grid doubling columns={4} divided="vertically">
|
<Grid doubling columns={4} divided="vertically">
|
||||||
{this.state.openSettingsModal ? (
|
|
||||||
<SettingsModal
|
|
||||||
openModal={this.openModal}
|
|
||||||
device={ds.filter((d) => d.id === this.state.settingsDeviceId)[0]}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{this.props.devices.map((e, i) => {
|
{this.props.devices.map((e, i) => {
|
||||||
return (
|
return (
|
||||||
<Grid.Column key={i}>
|
<Grid.Column key={i}>
|
||||||
<DeviceType type={e.kind} device={e} edit={edit} />
|
<Device id={e.id} />
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -110,7 +55,7 @@ const mapStateToProps = (state, _) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
get isActiveRoomHome() {
|
get isActiveRoomHome() {
|
||||||
return this.props.activeRoom === -1;
|
return state.active.activeRoom === -1;
|
||||||
},
|
},
|
||||||
activeRoom: state.active.activeRoom,
|
activeRoom: state.active.activeRoom,
|
||||||
});
|
});
|
||||||
|
|
75
smart-hut/src/components/dashboard/devices/Device.js
Normal file
75
smart-hut/src/components/dashboard/devices/Device.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import React from "react";
|
||||||
|
import Light from "./Light";
|
||||||
|
import SmartPlug from "./SmartPlug";
|
||||||
|
import Sensor from "./Sensor";
|
||||||
|
import { ButtonDimmer, KnobDimmer } from "./Dimmer";
|
||||||
|
import Switcher from "./Switch";
|
||||||
|
import { Segment } from "semantic-ui-react";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import DeviceSettingsModal from "./DeviceSettingsModal";
|
||||||
|
|
||||||
|
class Device extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.modalRef = React.createRef();
|
||||||
|
this.edit = this.edit.bind(this);
|
||||||
|
this.resetSmartPlug = this.resetSmartPlug.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
edit() {
|
||||||
|
console.log("editing device with id=" + this.props.id);
|
||||||
|
this.modalRef.current.openModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSmartPlug() {
|
||||||
|
this.props
|
||||||
|
.resetSmartPlug(this.props.id)
|
||||||
|
.catch((err) => console.error(`Smart plug reset error`, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDeviceComponent() {
|
||||||
|
switch (this.props.device.kind) {
|
||||||
|
case "regularLight":
|
||||||
|
return <Light id={this.props.id} />;
|
||||||
|
case "sensor":
|
||||||
|
return <Sensor id={this.props.id} />;
|
||||||
|
case "motionSensor":
|
||||||
|
return <Sensor id={this.props.id} />;
|
||||||
|
case "buttonDimmer":
|
||||||
|
return <ButtonDimmer id={this.props.id} />;
|
||||||
|
case "knobDimmer":
|
||||||
|
return <KnobDimmer id={this.props.id} />;
|
||||||
|
case "smartPlug":
|
||||||
|
return <SmartPlug id={this.props.id} />;
|
||||||
|
case "switch":
|
||||||
|
return <Switcher id={this.props.id} />;
|
||||||
|
case "dimmableLight":
|
||||||
|
return <Light id={this.props.id} />;
|
||||||
|
default:
|
||||||
|
throw new Error("Device type unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Segment>
|
||||||
|
{this.renderDeviceComponent()}
|
||||||
|
<h3>{this.props.device.name}</h3>
|
||||||
|
<button onClick={this.edit}>Edit</button>
|
||||||
|
{this.props.device.type === "smartPlug" ? (
|
||||||
|
<button onClick={this.resetSmartPlug}></button>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<DeviceSettingsModal ref={this.modalRef} id={this.props.id} />
|
||||||
|
</Segment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const DeviceContainer = connect(mapStateToProps, RemoteService)(Device);
|
||||||
|
export default DeviceContainer;
|
|
@ -1,26 +0,0 @@
|
||||||
import React, { Component } from "react";
|
|
||||||
import { editModeIconStyle, editModeStyle } from "./styleComponents";
|
|
||||||
|
|
||||||
export default class Settings extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
displayForm: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
displayForm = () => {
|
|
||||||
this.setState((prevState) => ({ displayForm: !prevState.displayForm }));
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const view = (
|
|
||||||
<div onClick={() => this.props.edit.openModal(this.props.deviceId)}>
|
|
||||||
<span style={editModeStyle}>
|
|
||||||
<img src="/img/settings.svg" alt="" style={editModeIconStyle} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return <React.Fragment>{this.props.edit.mode ? view : ""}</React.Fragment>;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { Component, useState } from "react";
|
import React, { Component, useState } from "react";
|
||||||
import { Button, Checkbox, Form, Icon, Header, Modal } from "semantic-ui-react";
|
import { Button, Form, Icon, Header, Modal } from "semantic-ui-react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
|
||||||
const DeleteModal = (props) => (
|
const DeleteModal = (props) => (
|
||||||
<Modal trigger={<Button color="red">Remove</Button>} closeIcon>
|
<Modal trigger={<Button color="red">Remove</Button>} closeIcon>
|
||||||
|
@ -21,11 +23,6 @@ const SettingsForm = (props) => {
|
||||||
setValues({ ...values, [name]: value });
|
setValues({ ...values, [name]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = (e, d) => {
|
|
||||||
const { name, checked } = d;
|
|
||||||
setValues({ ...values, [name]: checked });
|
|
||||||
};
|
|
||||||
|
|
||||||
const [values, setValues] = useState({ name: "" });
|
const [values, setValues] = useState({ name: "" });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -41,18 +38,6 @@ const SettingsForm = (props) => {
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
|
||||||
{props.type === "smart-plug" ? (
|
|
||||||
<Form.Field>
|
|
||||||
<Checkbox
|
|
||||||
slider
|
|
||||||
name={"reset"}
|
|
||||||
onClick={handleCheckboxChange}
|
|
||||||
label="Reset Energy Consumption"
|
|
||||||
/>
|
|
||||||
</Form.Field>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<DeleteModal removeDevice={() => props.removeDevice(values)} />
|
<DeleteModal removeDevice={() => props.removeDevice(values)} />
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
@ -67,41 +52,56 @@ const SettingsForm = (props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class SettingsModal extends Component {
|
class DeviceSettingsModal extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
open: true,
|
open: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.updateDevice = this.updateDevice.bind(this);
|
||||||
|
this.deleteDevice = this.deleteDevice.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
openModal() {
|
||||||
this.setState({ open: false });
|
this.setState({ open: true });
|
||||||
};
|
|
||||||
|
|
||||||
saveSettings = (device) => {
|
|
||||||
// TODO Here there should be all the connections to save the data in the backend
|
|
||||||
console.log("SAVED: ", device);
|
|
||||||
if (device.name.length > 0) {
|
|
||||||
this.props.updateDevice(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.openModal();
|
updateDevice(values) {
|
||||||
};
|
if (values.name.length === 0) return;
|
||||||
|
this.props
|
||||||
|
.saveDevice({ ...this.props.device, name: values.name })
|
||||||
|
.then(() => this.setState({ open: false }))
|
||||||
|
.catch((err) =>
|
||||||
|
console.error(
|
||||||
|
`settings modal for device ${this.props.id} deletion error`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDevice() {
|
||||||
|
// closing is not needed (and actually harmful due to memory leaks)
|
||||||
|
// since this component will be deleted anyways
|
||||||
|
this.props
|
||||||
|
.deleteDevice(this.props.device)
|
||||||
|
.catch((err) =>
|
||||||
|
console.error(
|
||||||
|
`settings modal for device ${this.props.id} deletion error`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const SettingsModal = () => (
|
const SettingsModal = () => (
|
||||||
<Modal
|
<Modal open={this.state.open}>
|
||||||
open={true}
|
|
||||||
onOpen={this.props.openModal}
|
|
||||||
onClose={this.props.openModal}
|
|
||||||
>
|
|
||||||
<Modal.Header>Settings of {this.props.device.name}</Modal.Header>
|
<Modal.Header>Settings of {this.props.device.name}</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<SettingsForm
|
<SettingsForm
|
||||||
type={this.props.device.type}
|
type={this.props.device.type}
|
||||||
removeDevice={this.props.removeDevice}
|
removeDevice={this.deleteDevice}
|
||||||
saveFunction={this.saveSettings}
|
saveFunction={this.updateDevice}
|
||||||
/>
|
/>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -109,3 +109,14 @@ export default class SettingsModal extends Component {
|
||||||
return <SettingsModal />;
|
return <SettingsModal />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const DeviceSettingsModalContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
RemoteService,
|
||||||
|
null,
|
||||||
|
{ forwardRef: true }
|
||||||
|
)(DeviceSettingsModal);
|
||||||
|
export default DeviceSettingsModalContainer;
|
|
@ -1,87 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import Light from "./Light";
|
|
||||||
import SmartPlug from "./SmartPlug";
|
|
||||||
import Sensor from "./Sensor";
|
|
||||||
import { ButtonDimmer, KnobDimmer } from "./Dimmer";
|
|
||||||
import Switcher from "./Switch";
|
|
||||||
|
|
||||||
const DeviceType = (props) => {
|
|
||||||
switch (props.type) {
|
|
||||||
case "regularLight":
|
|
||||||
return (
|
|
||||||
<Light
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "sensor":
|
|
||||||
return (
|
|
||||||
<Sensor
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "motionSensor":
|
|
||||||
return (
|
|
||||||
<Sensor
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "buttonDimmer":
|
|
||||||
return (
|
|
||||||
<ButtonDimmer
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "knobDimmer":
|
|
||||||
return (
|
|
||||||
<KnobDimmer
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "smartPlug":
|
|
||||||
return (
|
|
||||||
<SmartPlug
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "switch":
|
|
||||||
return (
|
|
||||||
<Switcher
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case "dimmableLight":
|
|
||||||
return (
|
|
||||||
<Light
|
|
||||||
updateDev={props.updateDeviceUi}
|
|
||||||
onChangeData={props.changeDeviceData}
|
|
||||||
device={props.device}
|
|
||||||
edit={props.edit}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeviceType;
|
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* Users can add sensors in their rooms.
|
|
||||||
* Sensors typically measure physical quantities in a room.
|
|
||||||
* You must support temperature sensors, humidity sensors, light sensors (which measure luminosity1).
|
|
||||||
* Sensors have an internal state that cannot be changed by the user.
|
|
||||||
* For this story, make the sensors return a constant value with some small random error.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { Component } from "react";
|
|
||||||
import {
|
|
||||||
CircularInput,
|
|
||||||
CircularProgress,
|
|
||||||
CircularTrack,
|
|
||||||
} from "react-circular-input";
|
|
||||||
import { errorStyle, sensorText, style, valueStyle } from "./SensorStyle";
|
|
||||||
import { StyledDiv } from "./styleComponents";
|
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import { Image } from "semantic-ui-react";
|
|
||||||
import { imageStyle, nameStyle } from "./DigitalSensorStyle";
|
|
||||||
|
|
||||||
export default class DigitalSensor extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
value: false, // This value is a boolean, was this type of sensor returns presence/absence
|
|
||||||
};
|
|
||||||
|
|
||||||
this.iconOn = "/img/sensorOn.svg";
|
|
||||||
this.iconOff = "/img/sensorOff.svg";
|
|
||||||
}
|
|
||||||
|
|
||||||
setName = () => {
|
|
||||||
if (this.props.device.name.length > 15) {
|
|
||||||
return this.props.device.name.slice(0, 12) + "...";
|
|
||||||
}
|
|
||||||
return this.props.device.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
getIcon = () => {
|
|
||||||
if (this.state.value) {
|
|
||||||
return this.iconOn;
|
|
||||||
}
|
|
||||||
return this.iconOff;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<StyledDiv>
|
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Image src={this.getIcon()} style={imageStyle} />
|
|
||||||
<h5 style={nameStyle}>{this.props.device.name}</h5>
|
|
||||||
</StyledDiv>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
export const imageStyle = {
|
|
||||||
width: "3.5rem",
|
|
||||||
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 = {
|
|
||||||
color: "black",
|
|
||||||
position: "absolute",
|
|
||||||
top: "40%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
};
|
|
|
@ -19,7 +19,6 @@ import {
|
||||||
PlusPanel,
|
PlusPanel,
|
||||||
ThumbText,
|
ThumbText,
|
||||||
} from "./styleComponents";
|
} from "./styleComponents";
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import {
|
import {
|
||||||
CircularThumbStyle,
|
CircularThumbStyle,
|
||||||
KnobDimmerStyle,
|
KnobDimmerStyle,
|
||||||
|
@ -28,10 +27,11 @@ import {
|
||||||
knobIcon,
|
knobIcon,
|
||||||
knobContainer,
|
knobContainer,
|
||||||
} from "./DimmerStyle";
|
} from "./DimmerStyle";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
import { call } from "../../../client_server";
|
import { call } from "../../../client_server";
|
||||||
|
|
||||||
export class ButtonDimmer extends Component {
|
export class ButtonDimmerComponent extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
|
@ -63,13 +63,6 @@ export class ButtonDimmer extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ButtonDimmerContainer>
|
<ButtonDimmerContainer>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<img alt="icon" src="/img/buttonDimmer.svg" />
|
<img alt="icon" src="/img/buttonDimmer.svg" />
|
||||||
<span className="knob">
|
<span className="knob">
|
||||||
{this.props.device.name} ({this.props.device.id})
|
{this.props.device.name} ({this.props.device.id})
|
||||||
|
@ -85,7 +78,7 @@ export class ButtonDimmer extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KnobDimmer extends Component {
|
export class KnobDimmerComponent extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -118,13 +111,6 @@ export class KnobDimmer extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div style={knobContainer}>
|
<div style={knobContainer}>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CircularInput
|
<CircularInput
|
||||||
style={KnobDimmerStyle}
|
style={KnobDimmerStyle}
|
||||||
value={+(Math.round(this.state.value / 100 + "e+2") + "e-2")}
|
value={+(Math.round(this.state.value / 100 + "e+2") + "e-2")}
|
||||||
|
@ -151,3 +137,11 @@ export class KnobDimmer extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const conn = connect(mapStateToProps, RemoteService);
|
||||||
|
|
||||||
|
export const KnobDimmer = conn(KnobDimmerComponent);
|
||||||
|
export const ButtonDimmer = conn(ButtonDimmerComponent);
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
BottomPanel,
|
BottomPanel,
|
||||||
ThumbText,
|
ThumbText,
|
||||||
} from "./styleComponents";
|
} from "./styleComponents";
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import { Image } from "semantic-ui-react";
|
import { Image } from "semantic-ui-react";
|
||||||
import {
|
import {
|
||||||
CircularInput,
|
CircularInput,
|
||||||
|
@ -32,8 +31,10 @@ import {
|
||||||
knobIcon,
|
knobIcon,
|
||||||
} from "./LightStyle";
|
} from "./LightStyle";
|
||||||
import { call } from "../../../client_server";
|
import { call } from "../../../client_server";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default class Light extends Component {
|
class Light extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -52,7 +53,7 @@ export default class Light extends Component {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
call.socketSubscribe(this.props.device.id, this.stateCallback);
|
// call.socketSubscribe(this.props.device.id, this.stateCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickDevice = () => {
|
onClickDevice = () => {
|
||||||
|
@ -91,13 +92,6 @@ export default class Light extends Component {
|
||||||
render() {
|
render() {
|
||||||
const intensityLightView = (
|
const intensityLightView = (
|
||||||
<div style={LightDimmerContainer}>
|
<div style={LightDimmerContainer}>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CircularInput
|
<CircularInput
|
||||||
style={LightDimmerStyle}
|
style={LightDimmerStyle}
|
||||||
value={+(Math.round(this.intensity / 100 + "e+2") + "e-2")}
|
value={+(Math.round(this.intensity / 100 + "e+2") + "e-2")}
|
||||||
|
@ -128,14 +122,7 @@ export default class Light extends Component {
|
||||||
|
|
||||||
const normalLightView = (
|
const normalLightView = (
|
||||||
<StyledDiv>
|
<StyledDiv>
|
||||||
<Settings
|
<div onClick={this.onClickDevice}>
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div onClick={this.props.edit.mode ? () => {} : this.onClickDevice}>
|
|
||||||
<Image src={this.getIcon()} style={iconStyle} />
|
<Image src={this.getIcon()} style={iconStyle} />
|
||||||
<BottomPanel style={{ backgroundColor: "#ffa41b" }}>
|
<BottomPanel style={{ backgroundColor: "#ffa41b" }}>
|
||||||
<h5 style={nameStyle}>
|
<h5 style={nameStyle}>
|
||||||
|
@ -155,3 +142,9 @@ export default class Light extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const LightContainer = connect(mapStateToProps, RemoteService)(Light);
|
||||||
|
export default LightContainer;
|
||||||
|
|
|
@ -35,11 +35,12 @@ import {
|
||||||
humiditySensorColors,
|
humiditySensorColors,
|
||||||
iconSensorStyle,
|
iconSensorStyle,
|
||||||
} from "./SensorStyle";
|
} from "./SensorStyle";
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import { call } from "../../../client_server";
|
import { call } from "../../../client_server";
|
||||||
import { Image } from "semantic-ui-react";
|
import { Image } from "semantic-ui-react";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default class Sensor extends Component {
|
class Sensor extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -131,13 +132,6 @@ export default class Sensor extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={container}>
|
<div style={container}>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{this.state.motion ? (
|
{this.state.motion ? (
|
||||||
<MotionSensor />
|
<MotionSensor />
|
||||||
) : (
|
) : (
|
||||||
|
@ -185,3 +179,9 @@ export default class Sensor extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const SensorContainer = connect(mapStateToProps, RemoteService)(Sensor);
|
||||||
|
export default SensorContainer;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
editModeIconStyle,
|
editModeIconStyle,
|
||||||
editModeStyleLeft,
|
editModeStyleLeft,
|
||||||
} from "./styleComponents";
|
} from "./styleComponents";
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import { Image } from "semantic-ui-react";
|
import { Image } from "semantic-ui-react";
|
||||||
import {
|
import {
|
||||||
energyConsumedStyle,
|
energyConsumedStyle,
|
||||||
|
@ -20,8 +19,10 @@ import {
|
||||||
nameStyle,
|
nameStyle,
|
||||||
} from "./SmartPlugStyle";
|
} from "./SmartPlugStyle";
|
||||||
import { call } from "../../../client_server";
|
import { call } from "../../../client_server";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default class SmartPlug extends Component {
|
class SmartPlug extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -81,21 +82,7 @@ export default class SmartPlug extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<StyledDiv onClick={this.props.edit.mode ? () => {} : this.onClickDevice}>
|
<StyledDiv onClick={this.onClickDevice}>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{this.props.edit.mode ? (
|
|
||||||
<span style={editModeStyleLeft} onClick={this.resetSmartPlug}>
|
|
||||||
<img src="/img/refresh.svg" alt="" style={editModeIconStyle} />
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
<Image src={this.getIcon()} style={imageStyle} />
|
<Image src={this.getIcon()} style={imageStyle} />
|
||||||
<span style={nameStyle}>
|
<span style={nameStyle}>
|
||||||
{this.props.device.name} ({this.props.device.id})
|
{this.props.device.name} ({this.props.device.id})
|
||||||
|
@ -115,3 +102,9 @@ export default class SmartPlug extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const SmartPlugContainer = connect(mapStateToProps, RemoteService)(SmartPlug);
|
||||||
|
export default SmartPlugContainer;
|
||||||
|
|
|
@ -7,12 +7,13 @@
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { BottomPanel, StyledDiv } from "./styleComponents";
|
import { BottomPanel, StyledDiv } from "./styleComponents";
|
||||||
import Settings from "./DeviceSettings";
|
|
||||||
import { Image } from "semantic-ui-react";
|
import { Image } from "semantic-ui-react";
|
||||||
import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle";
|
import { imageStyle, nameStyle, turnedOnStyle } from "./SwitchStyle";
|
||||||
import { call } from "../../../client_server";
|
import { call } from "../../../client_server";
|
||||||
|
import { RemoteService } from "../../../remote";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
export default class Switch extends Component {
|
class Switch extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -57,15 +58,7 @@ export default class Switch extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<StyledDiv onClick={this.props.edit.mode ? () => {} : this.onClickDevice}>
|
<StyledDiv onClick={this.onClickDevice}>
|
||||||
<Settings
|
|
||||||
deviceId={this.props.device.id}
|
|
||||||
edit={this.props.edit}
|
|
||||||
onChangeData={(id, newSettings) =>
|
|
||||||
this.props.onChangeData(id, newSettings)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Image src={this.getIcon()} style={imageStyle} />
|
<Image src={this.getIcon()} style={imageStyle} />
|
||||||
<span style={nameStyle}>
|
<span style={nameStyle}>
|
||||||
{this.props.device.name} ({this.props.device.id})
|
{this.props.device.name} ({this.props.device.id})
|
||||||
|
@ -86,3 +79,9 @@ export default class Switch extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
device: state.devices[ownProps.id],
|
||||||
|
});
|
||||||
|
const SwitchContainer = connect(mapStateToProps, RemoteService)(Switch);
|
||||||
|
export default SwitchContainer;
|
||||||
|
|
|
@ -60,7 +60,7 @@ const Endpoint = {
|
||||||
Authorization: `Bearer ${Endpoint.token}`,
|
Authorization: `Bearer ${Endpoint.token}`,
|
||||||
},
|
},
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (!res.data) {
|
if (!res.data && method != "delete") {
|
||||||
console.error("Response body is empty");
|
console.error("Response body is empty");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -294,7 +294,7 @@ export const RemoteService = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Endpoint[data.id ? "put" : "post"](url, {}, data)
|
return Endpoint[data.id ? "put" : "post"](url, {}, data)
|
||||||
.then((res) => void dispatch(actions.deviceUpdate(res.data)))
|
.then((res) => void dispatch(actions.deviceSave(res.data)))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.warn("Update device: ", data, "error: ", err);
|
console.warn("Update device: ", data, "error: ", err);
|
||||||
throw new RemoteError(["Network error"]);
|
throw new RemoteError(["Network error"]);
|
||||||
|
@ -396,7 +396,7 @@ export const RemoteService = {
|
||||||
*/
|
*/
|
||||||
deleteRoom: (roomId) => {
|
deleteRoom: (roomId) => {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
Endpoint.delete(`/room/${roomId}`)
|
return Endpoint.delete(`/room/${roomId}`)
|
||||||
.then((_) => dispatch(actions.roomDelete(roomId)))
|
.then((_) => dispatch(actions.roomDelete(roomId)))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.warn("Room deletion error", err);
|
console.warn("Room deletion error", err);
|
||||||
|
@ -407,14 +407,14 @@ export const RemoteService = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a device
|
* Deletes a device
|
||||||
* @param {Number} deviceId the id of the device to delete
|
* @param {Device} device the device to delete
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
deleteDevice: (deviceId) => {
|
deleteDevice: (device) => {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
Endpoint.delete(`/device/${deviceId}`)
|
return Endpoint.delete(`/${device.kind}/${device.id}`)
|
||||||
.then((_) => dispatch(actions.deviceDelete(deviceId)))
|
.then((_) => dispatch(actions.deviceDelete(device.id)))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.warn("Device deletion error", err);
|
console.warn("Device deletion error", err);
|
||||||
throw new RemoteError(["Network error"]);
|
throw new RemoteError(["Network error"]);
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { createStore, applyMiddleware } from "redux";
|
import { createStore, applyMiddleware } from "redux";
|
||||||
import thunk from "redux-thunk";
|
import thunk from "redux-thunk";
|
||||||
import actions from "./storeActions";
|
import action from "./storeActions";
|
||||||
import update from "immutability-helper";
|
import update from "immutability-helper";
|
||||||
|
|
||||||
function reducer(previousState, action) {
|
function reducer(previousState, action) {
|
||||||
let newState;
|
let newState;
|
||||||
const createOrUpdateRoom = (room) => {
|
const createOrUpdateRoom = (room) => {
|
||||||
if (!(room.id in newState.rooms)) {
|
if (!newState.rooms[room.id]) {
|
||||||
newState = update(newState, {
|
newState = update(newState, {
|
||||||
rooms: { [room.id]: { devices: { $set: new Set() } } },
|
rooms: { [room.id]: { $set: { ...room, devices: new Set() } } },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newState = update(newState, {
|
newState = update(newState, {
|
||||||
|
@ -78,7 +78,7 @@ function reducer(previousState, action) {
|
||||||
rooms: {},
|
rooms: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const room of newState.rooms) {
|
for (const room of Object.values(previousState.rooms)) {
|
||||||
change.rooms[room.id].devices = { $set: new Set() };
|
change.rooms[room.id].devices = { $set: new Set() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,14 @@ function reducer(previousState, action) {
|
||||||
case "ROOM_SAVE":
|
case "ROOM_SAVE":
|
||||||
createOrUpdateRoom(action.room);
|
createOrUpdateRoom(action.room);
|
||||||
break;
|
break;
|
||||||
|
case "DEVICE_SAVE":
|
||||||
|
newState = update(previousState, {
|
||||||
|
devices: { [action.device.id]: { $set: action.device } },
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "ROOM_DELETE":
|
case "ROOM_DELETE":
|
||||||
if (!(actions.roomId in previousState.rooms)) {
|
if (!(action.roomId in previousState.rooms)) {
|
||||||
console.warn(`Room to delete ${actions.roomId} does not exist`);
|
console.warn(`Room to delete ${action.roomId} does not exist`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,23 +129,28 @@ function reducer(previousState, action) {
|
||||||
change.devices.$unset.push(id);
|
change.devices.$unset.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
change.rooms = { $unset: actions.roomId };
|
change.rooms = { $unset: [action.roomId] };
|
||||||
newState = update(previousState, change);
|
newState = update(previousState, change);
|
||||||
break;
|
break;
|
||||||
case "DEVICE_DELETE":
|
case "DEVICE_DELETE":
|
||||||
if (!(actions.deviceId in previousState.devices)) {
|
if (!(action.deviceId in previousState.devices)) {
|
||||||
console.warn(`Device to delete ${actions.deviceId} does not exist`);
|
console.warn(`Device to delete ${action.deviceId} does not exist`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
newState = update(previousState, {
|
change = {
|
||||||
devices: { $unset: actions.deviceId },
|
devices: { $unset: [action.deviceId] },
|
||||||
rooms: {
|
};
|
||||||
[previousState.devices[actions.deviceId].roomId]: {
|
|
||||||
devices: { $remove: actions.deviceId },
|
if (previousState.rooms[previousState.devices[action.deviceId].roomId]) {
|
||||||
|
change.rooms = {
|
||||||
|
[previousState.devices[action.deviceId].roomId]: {
|
||||||
|
devices: { $remove: [action.deviceId] },
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
newState = update(previousState, change);
|
||||||
break;
|
break;
|
||||||
case "LOGOUT":
|
case "LOGOUT":
|
||||||
newState = update(initState, {});
|
newState = update(initState, {});
|
||||||
|
@ -149,7 +159,7 @@ function reducer(previousState, action) {
|
||||||
newState = update(previousState, {
|
newState = update(previousState, {
|
||||||
active: {
|
active: {
|
||||||
activeRoom: {
|
activeRoom: {
|
||||||
$set: actions.activeRoom,
|
$set: action.activeRoom,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,12 +17,20 @@ const actions = {
|
||||||
type: "ROOM_SAVE",
|
type: "ROOM_SAVE",
|
||||||
room,
|
room,
|
||||||
}),
|
}),
|
||||||
|
deviceSave: (device) => ({
|
||||||
|
type: "DEVICE_SAVE",
|
||||||
|
device,
|
||||||
|
}),
|
||||||
devicesUpdate: (roomId, devices, partial = false) => ({
|
devicesUpdate: (roomId, devices, partial = false) => ({
|
||||||
type: "DEVICES_UPDATE",
|
type: "DEVICES_UPDATE",
|
||||||
roomId,
|
roomId,
|
||||||
devices,
|
devices,
|
||||||
partial,
|
partial,
|
||||||
}),
|
}),
|
||||||
|
roomsUpdate: (rooms) => ({
|
||||||
|
type: "ROOMS_UPDATE",
|
||||||
|
rooms,
|
||||||
|
}),
|
||||||
roomDelete: (roomId) => ({
|
roomDelete: (roomId) => ({
|
||||||
type: "ROOM_DELETE",
|
type: "ROOM_DELETE",
|
||||||
roomId,
|
roomId,
|
||||||
|
|
|
@ -185,11 +185,13 @@ class Navbar extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const setActiveRoom = (dispatch) => {
|
||||||
return {
|
return (activeRoom) => dispatch(appActions.setActiveRoom(activeRoom));
|
||||||
setActiveRoom: (activeRoom) =>
|
|
||||||
dispatch(appActions.setActiveRoom(activeRoom)),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const NavbarContainer = connect(mapDispatchToProps, RemoteService)(Navbar);
|
|
||||||
|
const mapStateToProps = (state, _) => ({ rooms: state.rooms });
|
||||||
|
const NavbarContainer = connect(mapStateToProps, {
|
||||||
|
...RemoteService,
|
||||||
|
setActiveRoom,
|
||||||
|
})(Navbar);
|
||||||
export default NavbarContainer;
|
export default NavbarContainer;
|
||||||
|
|
Loading…
Reference in a new issue