diff --git a/src/components/Header.js b/src/components/Header.js index b09de8e..70c1dfc 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,9 +1,13 @@ import React, { PropTypes } from "react"; +import { Link } from "react-router"; const Header = (props) => { return (

Solstice web UI

+ + Rooms +
); }; diff --git a/src/components/Room.js b/src/components/Room.js index bca9fc1..0d77da5 100644 --- a/src/components/Room.js +++ b/src/components/Room.js @@ -1,14 +1,16 @@ import React, { PropTypes } from "react"; import { Link } from "react-router"; +import md5 from "md5"; + const Room = ({ name, membership, userCount }) => { const classes = ["room"]; if (membership == "Member") { classes.push("room-joined"); } - const base64Name = btoa(encodeURIComponent(name)); - const path = `/app/rooms/${base64Name}`; + const hash = md5(name); + const path = `/app/rooms/${hash}`; return (
@@ -60,15 +60,15 @@ class RoomsPane extends React.Component { RoomsPane.propTypes = { loginUserName: PropTypes.string.isRequired, params: PropTypes.shape({ - roomName: PropTypes.string + roomNameHash: PropTypes.string }), - roomMap: ImmutablePropTypes.orderedMap.isRequired, + rooms: ImmutablePropTypes.record.isRequired, roomActions: PropTypes.object.isRequired }; const mapStateToProps = (state) => ({ - roomMap: state.rooms.get("roomMap"), - loginUserName: state.login.get("username") + loginUserName: state.login.get("username"), + rooms: state.rooms }); const mapDispatchToProps = (dispatch) => ({ diff --git a/src/createRoutes.js b/src/createRoutes.js index 1fc3c2c..2865873 100644 --- a/src/createRoutes.js +++ b/src/createRoutes.js @@ -24,7 +24,7 @@ const createRoutes = (store) => { - + ); diff --git a/src/reducers/rooms.js b/src/reducers/rooms.js index 88f7476..0d036b1 100644 --- a/src/reducers/rooms.js +++ b/src/reducers/rooms.js @@ -1,5 +1,7 @@ import Immutable from "immutable"; +import OrderedMap from "../utils/OrderedMap"; + import { ROOM_JOIN, ROOM_LEAVE, @@ -9,48 +11,9 @@ import { SOCKET_RECEIVE_MESSAGE } from "../constants/ActionTypes"; -const initialState = Immutable.Map({ - roomMap: Immutable.OrderedMap(), - roomNameByHash: Immutable.Map() -}); +const initialState = OrderedMap(); const reduceRoomList = (state, roomList) => { - const roomMap = state.get("roomMap"); - const roomNameByHash = state.get("roomNameByHash"); - - // First sort the room list by room name - roomList.sort(([ roomName1 ], [ roomName2 ]) => { - if (roomName1 < roomName2) { - return -1; - } else if (roomName1 > roomName2) { - return 1; - } - return 0; - }); - - // Then build the new rooms map - let newRoomMap = Immutable.OrderedMap(); - - for (const [ roomName, newRoomData ] of roomList) { - // Get the old room data. - let roomData = roomMap.get(roomName); - if (roomData) { - // Scrap the old message list, we only want the new message list. - roomData.remove("messages"); - } else { - // If the room did not exist, make up an empty one. - roomData = Immutable.Map(); - } - // Merge the old data and the new data, overwriting with new data if - // conflicting. - const mergedRoomData = roomData.merge(newRoomData); - // Insert that in the new room map. - newRoomMap = newRoomMap.set(roomName, mergedRoomData); - } - - return state - .set("roomMap", newRoomMap) - .set("roomNameByHash", roomNameByHash); }; const reduceReceiveMessageRoom = (roomData, { variant, data }) => { @@ -79,7 +42,7 @@ const reduceReceiveMessage = (state, message) => { case "RoomMessageResponse": { const { room_name } = data; - return state.updateIn(["roomMap", data.room_name], (roomData) => { + return state.updateByName(data.room_name, (roomData) => { if (roomData) { return reduceReceiveMessageRoom(roomData, message); } else { @@ -90,7 +53,16 @@ const reduceReceiveMessage = (state, message) => { } case "RoomListResponse": - return reduceRoomList(state, data.rooms); + return state.updateAll(data.rooms, (newData, oldData) => { + if (oldData) { + // Remove the messages array, we want to overwrite it + // completely. + oldData.remove("messages"); + } else { + oldData = Immutable.Map(); + } + return oldData.merge(newData); + }); default: return state; @@ -125,7 +97,7 @@ export default (state = initialState, action) => { case ROOM_SHOW_USERS: case ROOM_HIDE_USERS: { - return state.updateIn(["roomMap", payload], (roomData) => { + return state.updateByName(payload, (roomData) => { if (roomData) { return reduceRoom(roomData, action); } else { diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 717199e..b9c1a7c 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -33,6 +33,10 @@ html { header { margin: 0; padding: 1.5em 2em; + display: flex; + flex-flow: row; + justify-content: space-around; + width: 100%; } header h1 { diff --git a/src/utils/OrderedMap.js b/src/utils/OrderedMap.js new file mode 100644 index 0000000..b370a0f --- /dev/null +++ b/src/utils/OrderedMap.js @@ -0,0 +1,70 @@ +import Immutable from "immutable"; +import md5 from "md5"; + +const MapRecord = Immutable.Record({ + byName: Immutable.OrderedMap(), + byHash: Immutable.Map() +}); + +class OrderedMap extends MapRecord { + constructor(arg) { + super(arg); + } + + getByName(name) { + return this.getIn(["byName", name]); + } + + setByName(name, data) { + let thisModified = this; + if (!this.getByName(name)) { + // That key was not there yet, add hash -> name mapping. + thisModified = this.setIn(["byHash", md5(name)], name); + } + // Add data to map. + return thisModified.setIn(["byName", name], data); + } + + updateByName(name, updater) { + return this.updateIn(["byName", name], updater); + } + + getNameByHash(hash) { + return this.getIn(["byHash", hash]); + } + + updateAll(nameAndDataList, merger) { + const { byName } = this; + let { byHash } = this; + + // First sort the room list by room name + nameAndDataList.sort(([ name1 ], [ name2 ]) => { + if (name1 < name2) { + return -1; + } else if (name1 > name2) { + return 1; + } + return 0; + }); + + // Then build the new map. + let newByName = Immutable.OrderedMap(); + + for (const [ name, newData ] of nameAndDataList) { + // Get the old room data. + let data = byName.get(name); + if (!data) { + // Add the hash -> name mapping. + byHash = byHash.set(md5(name), name); + } + // Merge the old data and the new data using the provided function. + const mergedData = merger(newData, data); + // Insert that in the new room map. + newByName = newByName.set(name, mergedData); + } + + return new OrderedMap({ byName: newByName, byHash }); + } +} + +export default () => new OrderedMap();