| @ -0,0 +1,4 @@ | |||
| build | |||
| node_modules | |||
| package.json | |||
| package-lock.json | |||
| @ -0,0 +1 @@ | |||
| {} | |||
| @ -1,4 +1,3 @@ | |||
| Things to do: | |||
| ------------- | |||
| ## Things to do: | |||
| - actually join rooms, display and send messages | |||
| - actually join rooms, display and send messages | |||
| @ -1,7 +1,7 @@ | |||
| import { LOGIN_GET_STATUS } from "../constants/ActionTypes"; | |||
| export default { | |||
| getStatus: () => ({ | |||
| type: LOGIN_GET_STATUS | |||
| }) | |||
| getStatus: () => ({ | |||
| type: LOGIN_GET_STATUS, | |||
| }), | |||
| }; | |||
| @ -1,48 +1,48 @@ | |||
| import { | |||
| ROOM_GET_LIST, | |||
| ROOM_JOIN, | |||
| ROOM_LEAVE, | |||
| ROOM_MESSAGE, | |||
| ROOM_SELECT, | |||
| ROOM_SHOW_USERS, | |||
| ROOM_HIDE_USERS | |||
| ROOM_GET_LIST, | |||
| ROOM_JOIN, | |||
| ROOM_LEAVE, | |||
| ROOM_MESSAGE, | |||
| ROOM_SELECT, | |||
| ROOM_SHOW_USERS, | |||
| ROOM_HIDE_USERS, | |||
| } from "../constants/ActionTypes"; | |||
| export default ({ | |||
| getList: () => ({ | |||
| type: ROOM_GET_LIST | |||
| }), | |||
| export default { | |||
| getList: () => ({ | |||
| type: ROOM_GET_LIST, | |||
| }), | |||
| join: (room_name) => ({ | |||
| type: ROOM_JOIN, | |||
| payload: room_name | |||
| }), | |||
| join: (room_name) => ({ | |||
| type: ROOM_JOIN, | |||
| payload: room_name, | |||
| }), | |||
| leave: (room_name) => ({ | |||
| type: ROOM_LEAVE, | |||
| payload: room_name | |||
| }), | |||
| leave: (room_name) => ({ | |||
| type: ROOM_LEAVE, | |||
| payload: room_name, | |||
| }), | |||
| select: (room_name) => ({ | |||
| type: ROOM_SELECT, | |||
| payload: room_name | |||
| }), | |||
| select: (room_name) => ({ | |||
| type: ROOM_SELECT, | |||
| payload: room_name, | |||
| }), | |||
| sendMessage: (room_name, message) => ({ | |||
| type: ROOM_MESSAGE, | |||
| payload: { | |||
| room_name, | |||
| message | |||
| } | |||
| }), | |||
| sendMessage: (room_name, message) => ({ | |||
| type: ROOM_MESSAGE, | |||
| payload: { | |||
| room_name, | |||
| message, | |||
| }, | |||
| }), | |||
| showUsers: (room_name) => ({ | |||
| type: ROOM_SHOW_USERS, | |||
| payload: room_name | |||
| }), | |||
| showUsers: (room_name) => ({ | |||
| type: ROOM_SHOW_USERS, | |||
| payload: room_name, | |||
| }), | |||
| hideUsers: (room_name) => ({ | |||
| type: ROOM_HIDE_USERS, | |||
| payload: room_name | |||
| }) | |||
| }); | |||
| hideUsers: (room_name) => ({ | |||
| type: ROOM_HIDE_USERS, | |||
| payload: room_name, | |||
| }), | |||
| }; | |||
| @ -1,29 +1,29 @@ | |||
| import { | |||
| SOCKET_RECEIVE_MESSAGE, | |||
| SOCKET_SEND_MESSAGE, | |||
| SOCKET_SET_CLOSED, | |||
| SOCKET_SET_CLOSING, | |||
| SOCKET_SET_ERROR, | |||
| SOCKET_SET_OPEN, | |||
| SOCKET_SET_OPENING | |||
| SOCKET_RECEIVE_MESSAGE, | |||
| SOCKET_SEND_MESSAGE, | |||
| SOCKET_SET_CLOSED, | |||
| SOCKET_SET_CLOSING, | |||
| SOCKET_SET_ERROR, | |||
| SOCKET_SET_OPEN, | |||
| SOCKET_SET_OPENING, | |||
| } from "../constants/ActionTypes"; | |||
| export default ({ | |||
| open: (url, handlers) => ({ | |||
| type: SOCKET_SET_OPENING, | |||
| payload: { | |||
| url, | |||
| onclose: handlers.onclose, | |||
| onerror: handlers.onerror, | |||
| onopen: handlers.onopen, | |||
| onmessage: handlers.onmessage | |||
| } | |||
| }), | |||
| export default { | |||
| open: (url, handlers) => ({ | |||
| type: SOCKET_SET_OPENING, | |||
| payload: { | |||
| url, | |||
| onclose: handlers.onclose, | |||
| onerror: handlers.onerror, | |||
| onopen: handlers.onopen, | |||
| onmessage: handlers.onmessage, | |||
| }, | |||
| }), | |||
| close: () => ({ type: SOCKET_SET_CLOSING }), | |||
| close: () => ({ type: SOCKET_SET_CLOSING }), | |||
| send: (message) => ({ | |||
| type: SOCKET_SEND_MESSAGE, | |||
| payload: message | |||
| }) | |||
| }); | |||
| send: (message) => ({ | |||
| type: SOCKET_SEND_MESSAGE, | |||
| payload: message, | |||
| }), | |||
| }; | |||
| @ -1,33 +1,36 @@ | |||
| import { | |||
| SOCKET_SET_CLOSED, | |||
| SOCKET_SET_ERROR, | |||
| SOCKET_SET_OPEN, | |||
| SOCKET_RECEIVE_MESSAGE | |||
| SOCKET_SET_CLOSED, | |||
| SOCKET_SET_ERROR, | |||
| SOCKET_SET_OPEN, | |||
| SOCKET_RECEIVE_MESSAGE, | |||
| } from "../constants/ActionTypes"; | |||
| export default { | |||
| onclose: event => ({ | |||
| type: SOCKET_SET_CLOSED, | |||
| payload: event.code | |||
| }), | |||
| onclose: (event) => ({ | |||
| type: SOCKET_SET_CLOSED, | |||
| payload: event.code, | |||
| }), | |||
| onerror: event => ({ type: SOCKET_SET_ERROR }), | |||
| onerror: (event) => ({ type: SOCKET_SET_ERROR }), | |||
| onopen: event => ({ type: SOCKET_SET_OPEN }), | |||
| onopen: (event) => ({ type: SOCKET_SET_OPEN }), | |||
| onmessage: event => { | |||
| console.log(`Received message: ${event.data}`); | |||
| const action = { type: SOCKET_RECEIVE_MESSAGE }; | |||
| try { | |||
| const { variant, fields: [data] } = JSON.parse(event.data); | |||
| if (typeof variant === "undefined") { | |||
| throw new Error('Missing "variant" field in control response'); | |||
| } | |||
| action.payload = { variant, data }; | |||
| } catch (err) { | |||
| action.error = true; | |||
| action.payload = err; | |||
| } | |||
| return action; | |||
| onmessage: (event) => { | |||
| console.log(`Received message: ${event.data}`); | |||
| const action = { type: SOCKET_RECEIVE_MESSAGE }; | |||
| try { | |||
| const { | |||
| variant, | |||
| fields: [data], | |||
| } = JSON.parse(event.data); | |||
| if (typeof variant === "undefined") { | |||
| throw new Error('Missing "variant" field in control response'); | |||
| } | |||
| action.payload = { variant, data }; | |||
| } catch (err) { | |||
| action.error = true; | |||
| action.payload = err; | |||
| } | |||
| return action; | |||
| }, | |||
| }; | |||
| @ -1,9 +1,9 @@ | |||
| import { USER_GET_LIST } from "../constants/ActionTypes"; | |||
| const UserActions = { | |||
| getList: () => ({ | |||
| type: USER_GET_LIST | |||
| }) | |||
| getList: () => ({ | |||
| type: USER_GET_LIST, | |||
| }), | |||
| }; | |||
| export default UserActions; | |||
| @ -1,5 +1,5 @@ | |||
| import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; | |||
| import type { RootState, AppDispatch } from './store'; | |||
| import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; | |||
| import type { RootState, AppDispatch } from "./store"; | |||
| // Use throughout your app instead of plain `useDispatch` and `useSelector` | |||
| export const useAppDispatch = () => useDispatch<AppDispatch>(); | |||
| @ -1,43 +1,41 @@ | |||
| import React, {PropTypes} from "react"; | |||
| import {reduxForm} from "redux-form"; | |||
| import React, { PropTypes } from "react"; | |||
| import { reduxForm } from "redux-form"; | |||
| const RoomChatForm = (props) => { | |||
| const { | |||
| fields: { message }, | |||
| handleSubmit, | |||
| resetForm, | |||
| roomName, | |||
| sendMessage | |||
| } = props; | |||
| const { | |||
| fields: { message }, | |||
| handleSubmit, | |||
| resetForm, | |||
| roomName, | |||
| sendMessage, | |||
| } = props; | |||
| const onSubmit = handleSubmit((values) => { | |||
| sendMessage(roomName, values.message); | |||
| resetForm(); | |||
| }); | |||
| const onSubmit = handleSubmit((values) => { | |||
| sendMessage(roomName, values.message); | |||
| resetForm(); | |||
| }); | |||
| return ( | |||
| <div id="room-chat-form"> | |||
| <form onSubmit={onSubmit}> | |||
| <input type="text" placeholder="Type a message..." | |||
| {...message} /> | |||
| <button type="submit">Send</button> | |||
| </form> | |||
| </div> | |||
| ); | |||
| return ( | |||
| <div id="room-chat-form"> | |||
| <form onSubmit={onSubmit}> | |||
| <input type="text" placeholder="Type a message..." {...message} /> | |||
| <button type="submit">Send</button> | |||
| </form> | |||
| </div> | |||
| ); | |||
| }; | |||
| RoomChatForm.propTypes = { | |||
| fields: PropTypes.shape({ | |||
| message: PropTypes.object.isRequired | |||
| }).isRequired, | |||
| handleSubmit: PropTypes.func.isRequired, | |||
| resetForm: PropTypes.func.isRequired, | |||
| roomName: PropTypes.string, | |||
| sendMessage: PropTypes.func.isRequired | |||
| fields: PropTypes.shape({ | |||
| message: PropTypes.object.isRequired, | |||
| }).isRequired, | |||
| handleSubmit: PropTypes.func.isRequired, | |||
| resetForm: PropTypes.func.isRequired, | |||
| roomName: PropTypes.string, | |||
| sendMessage: PropTypes.func.isRequired, | |||
| }; | |||
| export default reduxForm({ | |||
| form: "chat", | |||
| fields: ["message"] | |||
| form: "chat", | |||
| fields: ["message"], | |||
| })(RoomChatForm); | |||
| @ -1,20 +1,20 @@ | |||
| import React, { PropTypes } from "react"; | |||
| const RoomListHeader = ({ refresh }) => { | |||
| return ( | |||
| <div id="room-list-header"> | |||
| <div> | |||
| <h2>Room List</h2> | |||
| </div> | |||
| <div> | |||
| <button onClick={refresh}>Refresh</button> | |||
| </div> | |||
| </div> | |||
| ); | |||
| return ( | |||
| <div id="room-list-header"> | |||
| <div> | |||
| <h2>Room List</h2> | |||
| </div> | |||
| <div> | |||
| <button onClick={refresh}>Refresh</button> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| RoomListHeader.propTypes = { | |||
| refresh: PropTypes.func.isRequired | |||
| refresh: PropTypes.func.isRequired, | |||
| }; | |||
| export default RoomListHeader; | |||
| @ -1,25 +1,23 @@ | |||
| import React, { PropTypes } from "react"; | |||
| const RoomUserList = ({ users }) => { | |||
| // Append all users | |||
| const children = []; | |||
| let i = 0; | |||
| for (const user of users) { | |||
| children.push( | |||
| <li key={i} className="room-user"> | |||
| {user} | |||
| </li> | |||
| ); | |||
| i++; | |||
| } | |||
| // Append all users | |||
| const children = []; | |||
| let i = 0; | |||
| for (const user of users) { | |||
| children.push( | |||
| <li key={i} className="room-user"> | |||
| {user} | |||
| </li> | |||
| ); | |||
| i++; | |||
| } | |||
| return <ul id="room-user-list">{children}</ul>; | |||
| return <ul id="room-user-list">{children}</ul>; | |||
| }; | |||
| RoomUserList.propTypes = { | |||
| users: PropTypes.arrayOf( | |||
| PropTypes.string.isRequired | |||
| ).isRequired | |||
| users: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, | |||
| }; | |||
| export default RoomUserList; | |||
| @ -1,5 +1,5 @@ | |||
| export const wsConnect = host => ({ type: 'WS_CONNECT', host }); | |||
| export const wsConnecting = host => ({ type: 'WS_CONNECTING', host }); | |||
| export const wsConnected = host => ({ type: 'WS_CONNECTED', host }); | |||
| export const wsDisconnect = () => ({ type: 'WS_DISCONNECT' }); | |||
| export const wsDisconnected = () => ({ type: 'WS_DISCONNECTED' }); | |||
| export const wsConnect = (host) => ({ type: "WS_CONNECT", host }); | |||
| export const wsConnecting = (host) => ({ type: "WS_CONNECTING", host }); | |||
| export const wsConnected = (host) => ({ type: "WS_CONNECTED", host }); | |||
| export const wsDisconnect = () => ({ type: "WS_DISCONNECT" }); | |||
| export const wsDisconnected = () => ({ type: "WS_DISCONNECTED" }); | |||
| @ -1,75 +1,75 @@ | |||
| import Immutable from "immutable"; | |||
| import { | |||
| LOGIN_GET_STATUS, | |||
| SOCKET_RECEIVE_MESSAGE | |||
| LOGIN_GET_STATUS, | |||
| SOCKET_RECEIVE_MESSAGE, | |||
| } from "../constants/ActionTypes"; | |||
| import { | |||
| LOGIN_STATUS_UNKNOWN, | |||
| LOGIN_STATUS_GETTING, | |||
| LOGIN_STATUS_PENDING, | |||
| LOGIN_STATUS_SUCCESS, | |||
| LOGIN_STATUS_FAILURE | |||
| LOGIN_STATUS_UNKNOWN, | |||
| LOGIN_STATUS_GETTING, | |||
| LOGIN_STATUS_PENDING, | |||
| LOGIN_STATUS_SUCCESS, | |||
| LOGIN_STATUS_FAILURE, | |||
| } from "../constants/login"; | |||
| const LoginRecord = Immutable.Record({ | |||
| status: LOGIN_STATUS_UNKNOWN, | |||
| username: undefined, | |||
| motd: undefined, | |||
| reason: undefined | |||
| status: LOGIN_STATUS_UNKNOWN, | |||
| username: undefined, | |||
| motd: undefined, | |||
| reason: undefined, | |||
| }); | |||
| const initialState = new LoginRecord(); | |||
| const reduceReceiveMessage = (state, message) => { | |||
| const { variant, data } = message; | |||
| const { variant, data } = message; | |||
| if (variant !== "LoginStatusResponse") { | |||
| return state; | |||
| } | |||
| switch (data.variant) { | |||
| case "Pending": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username ] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_PENDING) | |||
| .set("username", username); | |||
| } | |||
| case "Success": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username, motd ] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_SUCCESS) | |||
| .set("username", username) | |||
| .set("motd", motd); | |||
| } | |||
| if (variant !== "LoginStatusResponse") { | |||
| return state; | |||
| } | |||
| case "Failure": | |||
| { // sub-block required otherwise const username declarations clash | |||
| const [ username, reason ] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_FAILURE) | |||
| .set("username", username) | |||
| .set("reason", reason); | |||
| } | |||
| switch (data.variant) { | |||
| case "Pending": { | |||
| // sub-block required otherwise const username declarations clash | |||
| const [username] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_PENDING) | |||
| .set("username", username); | |||
| } | |||
| case "Success": { | |||
| // sub-block required otherwise const username declarations clash | |||
| const [username, motd] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_SUCCESS) | |||
| .set("username", username) | |||
| .set("motd", motd); | |||
| } | |||
| default: | |||
| return state; | |||
| case "Failure": { | |||
| // sub-block required otherwise const username declarations clash | |||
| const [username, reason] = data.fields; | |||
| return state | |||
| .set("status", LOGIN_STATUS_FAILURE) | |||
| .set("username", username) | |||
| .set("reason", reason); | |||
| } | |||
| default: | |||
| return state; | |||
| } | |||
| }; | |||
| export default (state = initialState, action) => { | |||
| const { type, payload } = action; | |||
| switch (type) { | |||
| case LOGIN_GET_STATUS: | |||
| return state.set("status", LOGIN_STATUS_GETTING); | |||
| const { type, payload } = action; | |||
| switch (type) { | |||
| case LOGIN_GET_STATUS: | |||
| return state.set("status", LOGIN_STATUS_GETTING); | |||
| case SOCKET_RECEIVE_MESSAGE: | |||
| return reduceReceiveMessage(state, payload); | |||
| case SOCKET_RECEIVE_MESSAGE: | |||
| return reduceReceiveMessage(state, payload); | |||
| default: | |||
| return state; | |||
| } | |||
| default: | |||
| return state; | |||
| } | |||
| }; | |||
| @ -1,236 +1,238 @@ | |||
| /* Styles */ | |||
| body { | |||
| font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; | |||
| line-height: 1.4em; | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-font-smoothing: antialiased; | |||
| font-smoothing: antialiased; | |||
| font-weight: 300; | |||
| padding: 0 !important; | |||
| margin: 0 !important; | |||
| height: 100%; | |||
| font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; | |||
| line-height: 1.4em; | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-font-smoothing: antialiased; | |||
| font-smoothing: antialiased; | |||
| font-weight: 300; | |||
| padding: 0 !important; | |||
| margin: 0 !important; | |||
| height: 100%; | |||
| } | |||
| html { | |||
| padding: 0; | |||
| margin: 0; | |||
| height: 100vh; | |||
| padding: 0; | |||
| margin: 0; | |||
| height: 100vh; | |||
| } | |||
| #app, #solstice-app { | |||
| height: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| #app, | |||
| #solstice-app { | |||
| height: 100%; | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| #solstice-app { | |||
| display: flex; | |||
| flex-flow: column; | |||
| justify-content: center; | |||
| align-items: center; | |||
| display: flex; | |||
| flex-flow: column; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| header { | |||
| margin: 0; | |||
| padding: 1.5em 2em; | |||
| display: flex; | |||
| flex-flow: row; | |||
| justify-content: space-around; | |||
| width: 100%; | |||
| margin: 0; | |||
| padding: 1.5em 2em; | |||
| display: flex; | |||
| flex-flow: row; | |||
| justify-content: space-around; | |||
| width: 100%; | |||
| } | |||
| header h1 { | |||
| margin: 0; | |||
| margin: 0; | |||
| } | |||
| footer { | |||
| display: flex; | |||
| padding: 0.5em; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| display: flex; | |||
| padding: 0.5em; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| } | |||
| main { | |||
| width: 100%; | |||
| height: 85%; | |||
| margin: 0; | |||
| padding: 0; | |||
| width: 100%; | |||
| height: 85%; | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| #rooms-pane { | |||
| display: flex; | |||
| border: solid grey 0.1em; | |||
| height: 100%; | |||
| display: flex; | |||
| border: solid grey 0.1em; | |||
| height: 100%; | |||
| } | |||
| #room-chat { | |||
| border: solid grey 0.1em; | |||
| border: solid grey 0.1em; | |||
| } | |||
| #room-list { | |||
| flex: 1; | |||
| display: flex; | |||
| flex-flow: column; | |||
| border: solid grey 0.1em; | |||
| height: 100%; | |||
| flex: 1; | |||
| display: flex; | |||
| flex-flow: column; | |||
| border: solid grey 0.1em; | |||
| height: 100%; | |||
| } | |||
| #room-selected-pane { | |||
| height: 100%; | |||
| flex: 3; | |||
| display: flex; | |||
| flex-flow: row; | |||
| height: 100%; | |||
| flex: 3; | |||
| display: flex; | |||
| flex-flow: row; | |||
| } | |||
| #room-chat { | |||
| height: 100%; | |||
| max-width: 100%; | |||
| flex: 2; | |||
| display: flex; | |||
| flex-flow: column; | |||
| justify-content: space-between; | |||
| height: 100%; | |||
| max-width: 100%; | |||
| flex: 2; | |||
| display: flex; | |||
| flex-flow: column; | |||
| justify-content: space-between; | |||
| } | |||
| #room-user-list { | |||
| flex: 1; | |||
| display: block; | |||
| list-style: none; | |||
| margin: 0; | |||
| padding: 1em; | |||
| flex: 1; | |||
| display: block; | |||
| list-style: none; | |||
| margin: 0; | |||
| padding: 1em; | |||
| } | |||
| #room-chat-header { | |||
| flex: 1; | |||
| padding: 0.8em; | |||
| border: solid grey 0.1em; | |||
| flex: 1; | |||
| padding: 0.8em; | |||
| border: solid grey 0.1em; | |||
| display: flex; | |||
| flex-flow: row; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| display: flex; | |||
| flex-flow: row; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| } | |||
| #room-chat-header-title { | |||
| font-size: 1.3em; | |||
| text-align: center; | |||
| flex: 1; | |||
| font-size: 1.3em; | |||
| text-align: center; | |||
| flex: 1; | |||
| } | |||
| #room-chat-header > button { | |||
| padding: 0.5em 1em; | |||
| padding: 0.5em 1em; | |||
| } | |||
| #room-chat-message-list { | |||
| display: block; | |||
| height: 83%; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| margin: 0; | |||
| padding: 1em; | |||
| display: block; | |||
| height: 83%; | |||
| width: 100%; | |||
| box-sizing: border-box; | |||
| margin: 0; | |||
| padding: 1em; | |||
| overflow: auto; | |||
| overflow: auto; | |||
| list-style: none; | |||
| list-style: none; | |||
| } | |||
| .room-chat-message { | |||
| display: flex; | |||
| flex-flow: column; | |||
| align-items: flex-start; | |||
| margin-right: 3em; | |||
| display: flex; | |||
| flex-flow: column; | |||
| align-items: flex-start; | |||
| margin-right: 3em; | |||
| } | |||
| .room-chat-message-me { | |||
| align-items: flex-end; | |||
| margin-left: 3em; | |||
| margin-right: 0; | |||
| align-items: flex-end; | |||
| margin-left: 3em; | |||
| margin-right: 0; | |||
| } | |||
| .room-chat-message-user { | |||
| font-weight: bold; | |||
| color: blue; | |||
| font-weight: bold; | |||
| color: blue; | |||
| } | |||
| .room-chat-message-text { | |||
| padding: 0.5em 0.7em; | |||
| margin: 0.2em 0.5em; | |||
| border-radius: 0.8em; | |||
| background-color: lightgrey; | |||
| padding: 0.5em 0.7em; | |||
| margin: 0.2em 0.5em; | |||
| border-radius: 0.8em; | |||
| background-color: lightgrey; | |||
| } | |||
| .room-chat-message-me > .room-chat-message-text { | |||
| background-color: blue; | |||
| color: white; | |||
| background-color: blue; | |||
| color: white; | |||
| } | |||
| #room-chat-form { | |||
| width: 100%; | |||
| border: solid grey 0.1em; | |||
| width: 100%; | |||
| border: solid grey 0.1em; | |||
| } | |||
| #room-chat-form form { | |||
| width: 100%; | |||
| display: flex; | |||
| flex-flow: row; | |||
| width: 100%; | |||
| display: flex; | |||
| flex-flow: row; | |||
| } | |||
| #room-chat-form input { | |||
| flex: 1; | |||
| padding: 0.8em; | |||
| flex: 1; | |||
| padding: 0.8em; | |||
| } | |||
| #room-list-header { | |||
| flex: 1; | |||
| display: flex; | |||
| flex-flow: row; | |||
| border: solid grey 0.1em; | |||
| flex: 1; | |||
| display: flex; | |||
| flex-flow: row; | |||
| border: solid grey 0.1em; | |||
| } | |||
| #room-list-header > div { | |||
| flex: 1; | |||
| border: solid grey 0.1em; | |||
| flex: 1; | |||
| border: solid grey 0.1em; | |||
| } | |||
| #room-list ul { | |||
| display: block; | |||
| list-style: none; | |||
| padding: 0; | |||
| margin: 0; | |||
| height: 84%; | |||
| overflow-y: auto; | |||
| display: block; | |||
| list-style: none; | |||
| padding: 0; | |||
| margin: 0; | |||
| height: 84%; | |||
| overflow-y: auto; | |||
| } | |||
| .room { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| color: inherit; | |||
| text-decoration: inherit; | |||
| padding: .5em 1em; | |||
| border: solid grey 0.1em; | |||
| display: flex; | |||
| justify-content: space-between; | |||
| color: inherit; | |||
| text-decoration: inherit; | |||
| padding: 0.5em 1em; | |||
| border: solid grey 0.1em; | |||
| } | |||
| .room:hover { | |||
| background: lightgrey; | |||
| background: lightgrey; | |||
| } | |||
| .room:active { | |||
| background: grey; | |||
| background: grey; | |||
| } | |||
| .room-joined { | |||
| background: lightgreen; | |||
| background: lightgreen; | |||
| } | |||
| .room-selected { | |||
| background: lightblue; | |||
| background: lightblue; | |||
| } | |||
| #login-status-pane, #socket-status-pane { | |||
| flex: 1; | |||
| #login-status-pane, | |||
| #socket-status-pane { | |||
| flex: 1; | |||
| } | |||
| #connect-form { | |||
| display: flex; | |||
| flex-flow: column; | |||
| align-items: center; | |||
| display: flex; | |||
| flex-flow: column; | |||
| align-items: center; | |||
| } | |||
| @ -1,34 +1,36 @@ | |||
| export default { | |||
| loginStatus: () => ({ | |||
| variant: "LoginStatusRequest", | |||
| fields: [] | |||
| }), | |||
| loginStatus: () => ({ | |||
| variant: "LoginStatusRequest", | |||
| fields: [], | |||
| }), | |||
| roomJoin: (room_name) => ({ | |||
| variant: "RoomJoinRequest", | |||
| fields: [room_name] | |||
| }), | |||
| roomJoin: (room_name) => ({ | |||
| variant: "RoomJoinRequest", | |||
| fields: [room_name], | |||
| }), | |||
| roomLeave: (room_name) => ({ | |||
| variant: "RoomLeaveRequest", | |||
| fields: [room_name] | |||
| }), | |||
| roomLeave: (room_name) => ({ | |||
| variant: "RoomLeaveRequest", | |||
| fields: [room_name], | |||
| }), | |||
| roomList: () => ({ | |||
| variant: "RoomListRequest", | |||
| fields: [] | |||
| }), | |||
| roomList: () => ({ | |||
| variant: "RoomListRequest", | |||
| fields: [], | |||
| }), | |||
| roomMessage: (room_name, message) => ({ | |||
| variant: "RoomMessageRequest", | |||
| fields: [{ | |||
| room_name, | |||
| message | |||
| }] | |||
| }), | |||
| roomMessage: (room_name, message) => ({ | |||
| variant: "RoomMessageRequest", | |||
| fields: [ | |||
| { | |||
| room_name, | |||
| message, | |||
| }, | |||
| ], | |||
| }), | |||
| userList: () =>({ | |||
| variant: "UserListRequest", | |||
| fields: [] | |||
| }) | |||
| userList: () => ({ | |||
| variant: "UserListRequest", | |||
| fields: [], | |||
| }), | |||
| }; | |||
| @ -1,17 +1,16 @@ | |||
| const checkRequiredThenValidate = (validator) => | |||
| (props, propName, componentName, location) => | |||
| { | |||
| const checkRequiredThenValidate = | |||
| (validator) => (props, propName, componentName, location) => { | |||
| if (props[propName] != null) { | |||
| return validator(props, propName, componentName, location); | |||
| return validator(props, propName, componentName, location); | |||
| } | |||
| return new Error( | |||
| `Required prop \`${propName}\` was not specified in ` + | |||
| `Required prop \`${propName}\` was not specified in ` + | |||
| `\`${componentName}\`.` | |||
| ); | |||
| }; | |||
| }; | |||
| export default (validator) => { | |||
| validator.isRequired = checkRequiredThenValidate(validator); | |||
| return validator; | |||
| validator.isRequired = checkRequiredThenValidate(validator); | |||
| return validator; | |||
| }; | |||