Refactor redux almost complete

This commit is contained in:
britea 2020-04-11 18:29:32 +02:00
parent ed68bebe8e
commit b60a050a70
9 changed files with 232 additions and 133 deletions

View file

@ -20,9 +20,6 @@ const TitleImage = () => <Image src="sm_logo.png" size="medium" centered />;
export class MyHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
};
this.getInfo();
this.logout = this.logout.bind(this);
@ -57,7 +54,7 @@ export class MyHeader extends React.Component {
<Grid.Column width={2} heigth={1}>
<Label as="a" image color="black">
<img alt="SmartHut logo" src="smart-home.png" />
{this.state.username}
{this.props.username}
</Label>
<Divider />
<Button onClick={this.logout}>Logout</Button>
@ -79,10 +76,10 @@ export class MyHeader extends React.Component {
<Grid.Column>
<Label as="a" image color="black">
<img alt="SmartHut logo" src="smart-home.png" />
{this.state.username}
{this.props.username}
</Label>
<Divider />
<Button onClick={this.props.logout}>Logout</Button>
<Button onClick={this.logout}>Logout</Button>
</Grid.Column>
</Grid.Row>
</Grid>

View file

@ -32,7 +32,7 @@ class RoomModal extends Component {
get initialState() {
return {
selectedIcon: "home",
selectedIcon: this.type === "new" ? "home" : this.props.room.icon,
name: this.type === "new" ? "New Room" : this.props.room.name,
img: this.type === "new" ? null : this.props.room.image,
openModal: false,
@ -41,9 +41,11 @@ class RoomModal extends Component {
removeImage(e) {
e.preventDefault();
this.setState(update(this.state, {
image: {$set: NO_IMAGE}
}));
this.setState(
update(this.state, {
image: { $set: null },
})
);
}
setInitialState() {
@ -61,36 +63,38 @@ class RoomModal extends Component {
image: this.state.img,
};
this.props.saveRoom(data, null)
this.props
.saveRoom(data, null)
.then(() => {
this.setInitialState();
this.closeModal();
})
.catch((err) => console.error('error in creating room', err));
.catch((err) => console.error("error in creating room", err));
};
modifyRoomModal = (e) => {
let data = {
icon:
this.state.selectedIcon === ""
? this.props.room.icon
: this.state.selectedIcon,
name: this.state.name === "" ? this.props.room.name : this.state.name,
icon: this.state.selectedIcon,
name: this.state.name,
image: this.state.img,
};
this.props.saveRoom(data, this.props.id)
console.log("data", data);
this.props
.saveRoom(data, this.props.id)
.then(() => {
this.setInitialState();
this.closeModal();
})
.catch((err) => console.error('error in updating room', err));
.catch((err) => console.error("error in updating room", err));
};
deleteRoom = (e) => {
// no need to close modal since this room modal instance will be deleted
this.props.deleteRoom(this.props.id)
.catch((err) => console.error('error in deleting room', err));
this.props
.deleteRoom(this.props.id)
.then(() => this.closeModal())
.catch((err) => console.error("error in deleting room", err));
};
changeSomething = (event) => {
@ -101,7 +105,6 @@ class RoomModal extends Component {
closeModal = (e) => {
this.setState({ openModal: false });
this.updateIcon("home");
};
openModal = (e) => {
@ -172,7 +175,7 @@ class RoomModal extends Component {
</div>
) : null}
<Modal onClose={this.closeModal} open={this.state.openModal}>
<Modal closeIcon onClose={this.closeModal} open={this.state.openModal}>
<Header>
{this.type === "new" ? "Add new room" : "Modify room"}
</Header>
@ -192,7 +195,7 @@ class RoomModal extends Component {
<p>Insert an image of the room:</p>
<Form.Field>
<Image
src={this.state.img === null ? NO_IMAGE : this.state.img}
src={!this.state.img ? NO_IMAGE : this.state.img}
size="small"
onClick={() => this.fileInputRef.current.click()}
/>
@ -207,9 +210,9 @@ class RoomModal extends Component {
onChange={this.getBase64.bind(this)}
/>
</Form.Field>
{this.state.img ?
{this.state.img ? (
<Button onClick={this.unsetImage}>Remove image</Button>
: null }
) : null}
</Form>
<div style={spaceDiv}>
@ -244,9 +247,7 @@ class RoomModal extends Component {
<Button
color="green"
onClick={
this.type === "new"
? this.addRoomModal
: this.modifyRoomModal
this.type === "new" ? this.addRoomModal : this.modifyRoomModal
}
>
<Icon name="checkmark" />{" "}
@ -264,7 +265,7 @@ const setActiveRoom = (activeRoom) => {
};
const mapStateToProps = (state, ownProps) => ({
room: ownProps.id ? state.rooms[ownProps.id] : null
room: ownProps.id ? state.rooms[ownProps.id] : null,
});
const RoomModalContainer = connect(
mapStateToProps,

View file

@ -81,10 +81,9 @@ class DeviceSettingsModal extends Component {
}
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)
.then(() => this.setState({ open: false }))
.catch((err) =>
console.error(
`settings modal for device ${this.props.id} deletion error`,

View file

@ -31,24 +31,23 @@ import { RemoteService } from "../../../remote";
import { connect } from "react-redux";
export class ButtonDimmerComponent extends Component {
increaseIntensity = () => {
this.props.device.buttonDimmerDim(this.props.id, "UP")
.catch((err) => console.error('button dimmer increase error', err));
this.props
.buttonDimmerDim(this.props.id, "UP")
.catch((err) => console.error("button dimmer increase error", err));
};
decreaseIntensity = () => {
this.props.device.buttonDimmerDim(this.props.id, "DOWN")
.catch((err) => console.error('button dimmer decrease error', err));
this.props
.buttonDimmerDim(this.props.id, "DOWN")
.catch((err) => console.error("button dimmer decrease error", err));
};
render() {
return (
<ButtonDimmerContainer>
<img alt="icon" src="/img/buttonDimmer.svg" />
<span className="knob">
Button Dimmer
</span>
<span className="knob">Button Dimmer</span>
<PlusPanel name="UP" onClick={this.increaseIntensity}>
<span>&#43;</span>
</PlusPanel>
@ -61,19 +60,23 @@ export class ButtonDimmerComponent extends Component {
}
export class KnobDimmerComponent extends Component {
setIntensity = (newValue) => {
const val = Math.round(newValue * 100);
this.props.device.knobDimmerDimTok(this.props.id, val)
.catch((err) => console.error('knob dimmer set intensity error', err));
this.props
.knobDimmerDimTo(this.props.id, val)
.catch((err) => console.error("knob dimmer set intensity error", err));
};
get intensity() {
return this.props.device.intensity || 0;
}
render() {
return (
<div style={knobContainer}>
<CircularInput
style={KnobDimmerStyle}
value={+(Math.round(this.props.device.intensity / 100 + "e+2") + "e-2")}
value={+(Math.round(this.intensity / 100 + "e+2") + "e-2")}
onChange={this.setIntensity}
>
<text
@ -87,7 +90,7 @@ export class KnobDimmerComponent extends Component {
Knob Dimmer
</text>
<CircularProgress
style={{ ...KnobProgress, opacity: this.props.device.intensity + 0.1 }}
style={{ ...KnobProgress, opacity: this.intensity + 0.1 }}
/>
<CircularThumb style={CircularThumbStyle} />
<ThumbText color={"#1a2849"} />

View file

@ -45,13 +45,14 @@ class Light extends Component {
}
get intensity() {
return this.props.device.intensity;
return this.props.device.intensity || 0;
}
onClickDevice = () => {
this.props.device.on = !this.turnedOn;
this.props.saveDevice({ ...this.props.device, on: !this.turnedOn })
.catch((err) => console.error('regular light update error', err))
const on = !this.turnedOn;
this.props
.saveDevice({ ...this.props.device, on })
.catch((err) => console.error("regular light update error", err));
};
getIcon = () => {
@ -60,8 +61,9 @@ class Light extends Component {
setIntensity = (newValue) => {
const intensity = Math.round(newValue * 100);
this.props.saveDevice({ ...this.props.device, intensity })
.catch((err) => console.error('intensity light update error', err))
this.props
.saveDevice({ ...this.props.device, intensity })
.catch((err) => console.error("intensity light update error", err));
};
render() {
@ -100,9 +102,7 @@ class Light extends Component {
<div onClick={this.onClickDevice}>
<Image src={this.getIcon()} style={iconStyle} />
<BottomPanel style={{ backgroundColor: "#ffa41b" }}>
<h5 style={nameStyle}>
Light
</h5>
<h5 style={nameStyle}>Light</h5>
</BottomPanel>
</div>
</StyledDiv>

View file

@ -88,65 +88,58 @@ class NewDevice extends Component {
this.setState({ lightsAttached: d.value });
};
createDevice() {
async createDevice() {
// Connect to the backend and create device here.
const data = {
params: {
id: null,
roomId: this.props.activeRoom,
name: this.state.deviceName,
},
device: this.state.motion ? "motionSensor" : this.state.typeOfDevice,
kind: this.state.motion ? "motionSensor" : this.state.typeOfDevice,
};
let outputs = null;
const defaultNames = {
regularLight: "New regular light",
dimmableLight: "New intensity light",
smartPlug: "New smart Plug",
sensor: "New sensor",
switch: "New switch",
buttonDimmer: "New button dimmer",
knobDimmer: "New knob dimmer",
};
if (this.state.deviceName === "") {
data.name = defaultNames[this.state.typeOfDevice];
}
switch (this.state.typeOfDevice) {
case "regularLight":
if (this.state.deviceName === "") {
data.params["name"] = "Regular Light";
}
break;
case "smartPlug":
if (this.state.deviceName === "") {
data.params["name"] = "Smart Plug";
}
break;
case "dimmableLight":
if (this.state.deviceName === "") {
data.params["name"] = "Dimmable Light";
}
data.params["intensity"] = 0;
data.intensity = 0;
break;
case "sensor":
if (this.state.deviceName === "") {
data.params["name"] = "Sensor";
}
if (!this.state.motion) {
data.params["sensor"] = this.state.typeOfSensor;
data.params["value"] = 0;
data.sensor = this.state.typeOfSensor;
data.value = 0;
}
break;
case "switch":
if (this.state.deviceName === "") {
data.params["name"] = "Switch";
}
data.params["lights"] = this.state.lightsAttached;
break;
case "buttonDimmer":
if (this.state.deviceName === "") {
data.params["name"] = "Button Dimmer";
}
data.params["lights"] = this.state.lightsAttached;
break;
case "knobDimmer":
if (this.state.deviceName === "") {
data.params["name"] = "Knob Dimmer";
}
data.params["lights"] = this.state.lightsAttached;
outputs = this.state.lightsAttached;
break;
default:
break;
}
this.props.addDevice(data);
try {
let newDevice = await this.props.saveDevice(data);
if (outputs) {
await this.props.connectOutputs(newDevice, outputs);
}
this.resetState();
} catch (e) {
console.error("device creation error: ", e);
}
}
render() {
@ -380,10 +373,8 @@ class NewDevice extends Component {
}
const mapStateToProps = (state, _) => ({
devices: Object.values(state.devices)
devices: Object.values(state.devices),
activeRoom: state.active.activeRoom,
});
const NewDeviceContainer = connect(
mapStateToProps,
RemoteService
)(NewDevice);
const NewDeviceContainer = connect(mapStateToProps, RemoteService)(NewDevice);
export default NewDeviceContainer;

View file

@ -11,7 +11,7 @@ class RemoteError extends Error {
messages;
constructor(messages) {
super("remote error");
super(messages.join(" - "));
this.messages = messages;
}
}
@ -283,7 +283,7 @@ export const RemoteService = {
* is used for updates and the POST "/<device.kind>"
* endpoints are used for creation.
* @param {Device} data the device to update.
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* @returns {Promise<Device, RemoteError>} promise that resolves to the saved device and rejects
* with user-fiendly errors as a RemoteError
*/
saveDevice: (data) => {
@ -294,7 +294,10 @@ export const RemoteService = {
}
return Endpoint[data.id ? "put" : "post"](url, {}, data)
.then((res) => void dispatch(actions.deviceSave(res.data)))
.then((res) => {
dispatch(actions.deviceSave(res.data));
return res.data;
})
.catch((err) => {
console.warn("Update device: ", data, "error: ", err);
throw new RemoteError(["Network error"]);
@ -302,13 +305,51 @@ export const RemoteService = {
};
},
/**
* Connetcs a series of output devices to an input device.
* Output devices for Switch input can be: Normal Light, Dimmable Light, Smart Plug.
* Output devices for Dimmers input can be: Dimmable Light.
*
* @typedef {"switch" | "buttonDimmer" | "knobDimmer"} ConnectableInput
*
* @param {ConnectableInput} newDevice.kind kind of the input device
* @param {Integer} newDevice.id id of the input device
* @param {Integer[]} outputs ids of the output device
* @returns {Promise<Undefined, RemoteError>} promise that resolves to void and rejects
* with user-fiendly errors as a RemoteError
*/
connectOutputs: (newDevice, outputs) => {
return (dispatch) => {
let url = `/${newDevice.kind}/${newDevice.id}/lights`;
return Endpoint.post(url, {}, outputs)
.then((res) => {
dispatch(actions.deviceOperationUpdate(res.data));
return res.data;
})
.catch((err) => {
console.warn(
"ConnectOutputs of ",
newDevice.id,
" with outputs: ",
outputs,
"error: ",
err
);
throw new RemoteError(["Network error"]);
});
};
},
_operateInput: (url, getUrl, action) => {
return (dispatch) => {
return Endpoint.put(url, {}, action)
.then(async (res) => {
const inputDevice = await Endpoint.get(getUrl);
delete inputDevice.outputs;
dispatch(actions.deviceOperationUpdate([...res.data, inputDevice.data]));
dispatch(
actions.deviceOperationUpdate([...res.data, inputDevice.data])
);
})
.catch((err) => {
console.warn(`${url} error`, err);
@ -328,10 +369,14 @@ export const RemoteService = {
* with user-fiendly errors as a RemoteError
*/
switchOperate: (switchId, type) => {
return RemoteService._operateInput("/switch/operate", `/switch/${switchId}`, {
return RemoteService._operateInput(
"/switch/operate",
`/switch/${switchId}`,
{
type: type.toUpperCase(),
id: switchId,
});
}
);
},
/**
@ -343,10 +388,14 @@ export const RemoteService = {
* with user-fiendly errors as a RemoteError
*/
knobDimmerDimTo: (dimmerId, intensity) => {
return RemoteService._operateInput("/knobDimmer/dimTo", `/knobDimmer/${dimmerId}`, {
return RemoteService._operateInput(
"/knobDimmer/dimTo",
`/knobDimmer/${dimmerId}`,
{
intensity,
id: dimmerId,
});
}
);
},
/**

View file

@ -3,7 +3,8 @@ import thunk from "redux-thunk";
import update from "immutability-helper";
function reducer(previousState, action) {
let newState;
let newState, change;
const createOrUpdateRoom = (room) => {
if (!newState.rooms[room.id]) {
newState = update(newState, {
@ -35,13 +36,24 @@ function reducer(previousState, action) {
}
};
let change;
const updateDeviceProps = (device) => {
// In some updates the information regarding a device is incomplete
// due to a fault in the type system and JPA repository management
// in the backend. Therefore to solve this avoid to delete existing
// attributes of this device in the previous state, but just update
// the new ones.
change.devices[device.id] = {};
for (const key in device) {
change.devices[device.id][key] = { $set: device[key] };
}
};
switch (action.type) {
case "LOGIN_UPDATE":
newState = update(previousState, { login: { $set: action.login } });
break;
case "USER_INFO_UPDATE":
newState = update(previousState, { userInfo: { $set: action.user } });
newState = update(previousState, { userInfo: { $set: action.userInfo } });
break;
case "ROOMS_UPDATE":
newState = previousState;
@ -105,7 +117,11 @@ function reducer(previousState, action) {
pendingJoins: { rooms: {} },
};
for (const device of action.devices) {
if (!newState.devices[device.id]) {
change.devices[device.id] = { $set: device };
} else {
updateDeviceProps(device);
}
if (device.roomId in newState.rooms) {
change.rooms[device.roomId] = change.rooms[device.roomId] || {};
@ -135,9 +151,28 @@ function reducer(previousState, action) {
createOrUpdateRoom(action.room);
break;
case "DEVICE_SAVE":
newState = update(previousState, {
change = {
devices: { [action.device.id]: { $set: action.device } },
});
};
if (previousState.rooms[action.device.roomId]) {
change.rooms = {
[action.device.roomId]: {
devices: {
$add: [action.device.id],
},
},
};
} else {
change.pendingJoins = {
rooms: {
[action.device.roomId]: {
$add: [action.device.id],
},
},
};
}
newState = update(previousState, change);
break;
case "ROOM_DELETE":
if (!(action.roomId in previousState.rooms)) {
@ -157,7 +192,7 @@ function reducer(previousState, action) {
change.rooms = { $unset: [action.roomId] };
if (previousState.active.activeRoom === action.roomId) {
change.active = { activeRoom: {$set: -1}};
change.active = { activeRoom: { $set: -1 } };
}
newState = update(previousState, change);
@ -200,6 +235,7 @@ function reducer(previousState, action) {
}
console.log("new state: ", newState);
console.log("active room: ", newState.active.activeRoom);
return newState;
}

View file

@ -22,6 +22,7 @@ class Navbar extends Component {
this.toggleEditMode = this.toggleEditMode.bind(this);
this.selectRoom = this.selectRoom.bind(this);
this.openCurrentModalMobile = this.openCurrentModalMobile.bind(this);
this.getRooms();
}
@ -40,7 +41,13 @@ class Navbar extends Component {
get activeItemName() {
if (this.props.activeRoom === -1) return "Home";
return this.props.rooms[this.props.activeRoom];
return this.props.rooms[this.props.activeRoom].name;
}
openCurrentModalMobile() {
console.log(this.activeItem, this.props.roomModalRefs);
const currentModal = this.props.roomModalRefs[this.activeItem].current;
currentModal.openModal();
}
toggleEditMode(e) {
@ -48,7 +55,7 @@ class Navbar extends Component {
}
selectRoom(e, { id }) {
this.activeItem = id;
this.activeItem = id || -1;
}
render() {
@ -65,9 +72,9 @@ class Navbar extends Component {
<Menu inverted fluid vertical>
<Menu.Item
key={-1}
id={-1}
id={null}
name="Home"
active={this.activeRoom === -1}
active={this.activeItem === -1}
onClick={this.selectRoom}
>
<Grid>
@ -86,7 +93,7 @@ class Navbar extends Component {
id={e.id}
key={i}
name={e.name}
active={this.activeRoom === e.id}
active={this.activeItem === e.id}
onClick={this.selectRoom}
>
<Grid>
@ -124,9 +131,9 @@ class Navbar extends Component {
<Dropdown.Menu>
<Dropdown.Item
key={-1}
id={-1}
id={null}
name="Home"
active={this.activeRoom === -1}
active={this.activeItem === -1}
onClick={this.selectRoom}
>
<Grid>
@ -145,7 +152,7 @@ class Navbar extends Component {
id={e.id}
key={i}
name={e.name}
active={this.activeRoom === e.id}
active={this.activeItem === e.id}
onClick={this.selectRoom}
>
<Grid>
@ -156,7 +163,11 @@ class Navbar extends Component {
<Grid.Column>{e.name}</Grid.Column>
</Grid.Row>
</Grid>
<RoomModal nicolaStop={true} id={e} />
<RoomModal
ref={this.props.roomModalRefs[e.id]}
nicolaStop={true}
id={e.id}
/>
</Dropdown.Item>
);
})}
@ -168,9 +179,14 @@ class Navbar extends Component {
<Grid.Column width={8}>
<RoomModal id={null} />
</Grid.Column>
{this.activeRoom !== -1 ? (
{this.activeItem !== -1 ? (
<Grid.Column width={8}>
<Button icon fluid labelPosition="left">
<Button
icon
fluid
labelPosition="left"
onClick={this.openCurrentModalMobile}
>
<Icon name="pencil" size="small" />
EDIT ROOM
</Button>
@ -188,7 +204,14 @@ const setActiveRoom = (activeRoom) => {
return (dispatch) => dispatch(appActions.setActiveRoom(activeRoom));
};
const mapStateToProps = (state, _) => ({ rooms: state.rooms });
const mapStateToProps = (state, _) => ({
rooms: state.rooms,
activeRoom: state.active.activeRoom,
roomModalRefs: Object.keys(state.rooms).reduce(
(acc, key) => ({ ...acc, [key]: React.createRef() }),
{}
),
});
const NavbarContainer = connect(mapStateToProps, {
...RemoteService,
setActiveRoom,