import { createStore, applyMiddleware } from "redux"; import thunk from "redux-thunk"; import action from "./storeActions"; import update from "immutability-helper"; function reducer(previousState, action) { let newState; const createOrUpdateRoom = (room) => { if (!newState.rooms[room.id]) { newState = update(newState, { rooms: { [room.id]: { $set: { ...room, devices: new Set() } } }, }); } else { newState = update(newState, { rooms: { [room.id]: { name: { $set: room.name }, image: { $set: room.image }, icon: { $set: room.icon }, }, }, }); } }; let change; 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 } }); break; case "ROOMS_UPDATE": newState = previousState; for (const room of action.rooms) { createOrUpdateRoom(room); } break; case "DEVICES_UPDATE": change = null; // if room is given, delete all devices in that room // and remove any join between that room and deleted // devices if (action.roomId) { change = { rooms: { [action.roomId]: { devices: { $set: new Set() } } }, devices: { $unset: [] }, }; const room = newState.rooms[action.roomId]; for (const deviceId of room.devices) { change.devices.$unset.push(deviceId); } } else if (action.partial) { // if the update is partial and caused by an operation on an input // device (like /switch/operate), iteratively remove deleted // devices and their join with their corresponding room. change = { devices: { $unset: [] }, rooms: {}, }; for (const device of action.devices) { const roomId = previousState.devices[device.id].roomId; change.rooms[roomId] = change.rooms[roomId] || { devices: { $remove: [] }, }; change.rooms[roomId].devices.$remove.push(device.id); change.devices.$unset.push(device.id); } } else { // otherwise, just delete all devices and all joins // between rooms and devices change = { devices: { $set: {} }, rooms: {}, }; for (const room of Object.values(previousState.rooms)) { change.rooms[room.id].devices = { $set: new Set() }; } } newState = update(previousState, change); change = { devices: {}, rooms: {}, }; for (const device of action.devices) { change.devices[device.id] = { $set: device }; if (device.roomId in newState.rooms) { const devices = change.rooms[device.roomId].devices; devices.$add = devices.$add || []; devices.$add.push(device.id); } else { console.warn( "Cannot join device", device, `in room ${device.roomId} since that room has not been fetched` ); } } newState = update(newState, change); break; case "ROOM_SAVE": createOrUpdateRoom(action.room); break; case "DEVICE_SAVE": newState = update(previousState, { devices: { [action.device.id]: { $set: action.device } }, }); break; case "ROOM_DELETE": if (!(action.roomId in previousState.rooms)) { console.warn(`Room to delete ${action.roomId} does not exist`); break; } // This update does not ensure the consistent update of switchId/dimmerId properties // on output devices connected to an input device in this room. Please manually request // all devices again if consistent update is desired change = { devices: { $unset: [] } }; for (const id of previousState.rooms[action.roomId].devices) { change.devices.$unset.push(id); } change.rooms = { $unset: [action.roomId] }; newState = update(previousState, change); break; case "DEVICE_DELETE": if (!(action.deviceId in previousState.devices)) { console.warn(`Device to delete ${action.deviceId} does not exist`); break; } change = { devices: { $unset: [action.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; case "LOGOUT": newState = update(initState, {}); break; case "SET_ACTIVE_ROOM": newState = update(previousState, { active: { activeRoom: { $set: action.activeRoom, }, }, }); break; default: console.warn(`Action type ${action.type} unknown`, action); return previousState; } console.log("new state: ", newState); return newState; } const initState = { errors: {}, active: { activeRoom: -1, }, login: { loggedIn: false, token: null, }, userInfo: null, /** @type {[integer]Room} */ rooms: {}, /** @type {[integer]Device} */ devices: {}, }; function createSmartHutStore() { const token = localStorage.getItem("token"); const exp = localStorage.getItem("exp"); const initialState = update(initState, { login: { token: { $set: token }, loggedIn: { $set: !!(token && exp > new Date().getTime()) }, }, }); if (!initialState.login.loggedIn) { localStorage.removeItem("token"); localStorage.removeItem("exp"); initialState.login.token = null; } return createStore(reducer, initialState, applyMiddleware(thunk)); } const smartHutStore = createSmartHutStore(); export default smartHutStore;