| @ -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"; | import { LOGIN_GET_STATUS } from "../constants/ActionTypes"; | ||||
| export default { | export default { | ||||
| getStatus: () => ({ | |||||
| type: LOGIN_GET_STATUS | |||||
| }) | |||||
| getStatus: () => ({ | |||||
| type: LOGIN_GET_STATUS, | |||||
| }), | |||||
| }; | }; | ||||
| @ -1,48 +1,48 @@ | |||||
| import { | 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"; | } 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 { | 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"; | } 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 { | 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"; | } from "../constants/ActionTypes"; | ||||
| export default { | 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"; | import { USER_GET_LIST } from "../constants/ActionTypes"; | ||||
| const UserActions = { | const UserActions = { | ||||
| getList: () => ({ | |||||
| type: USER_GET_LIST | |||||
| }) | |||||
| getList: () => ({ | |||||
| type: USER_GET_LIST, | |||||
| }), | |||||
| }; | }; | ||||
| export default UserActions; | 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` | // Use throughout your app instead of plain `useDispatch` and `useSelector` | ||||
| export const useAppDispatch = () => useDispatch<AppDispatch>(); | 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 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 = { | 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({ | export default reduxForm({ | ||||
| form: "chat", | |||||
| fields: ["message"] | |||||
| form: "chat", | |||||
| fields: ["message"], | |||||
| })(RoomChatForm); | })(RoomChatForm); | ||||
| @ -1,20 +1,20 @@ | |||||
| import React, { PropTypes } from "react"; | import React, { PropTypes } from "react"; | ||||
| const RoomListHeader = ({ refresh }) => { | 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 = { | RoomListHeader.propTypes = { | ||||
| refresh: PropTypes.func.isRequired | |||||
| refresh: PropTypes.func.isRequired, | |||||
| }; | }; | ||||
| export default RoomListHeader; | export default RoomListHeader; | ||||
| @ -1,25 +1,23 @@ | |||||
| import React, { PropTypes } from "react"; | import React, { PropTypes } from "react"; | ||||
| const RoomUserList = ({ users }) => { | 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 = { | RoomUserList.propTypes = { | ||||
| users: PropTypes.arrayOf( | |||||
| PropTypes.string.isRequired | |||||
| ).isRequired | |||||
| users: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, | |||||
| }; | }; | ||||
| export default RoomUserList; | 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 Immutable from "immutable"; | ||||
| import { | import { | ||||
| LOGIN_GET_STATUS, | |||||
| SOCKET_RECEIVE_MESSAGE | |||||
| LOGIN_GET_STATUS, | |||||
| SOCKET_RECEIVE_MESSAGE, | |||||
| } from "../constants/ActionTypes"; | } from "../constants/ActionTypes"; | ||||
| import { | 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"; | } from "../constants/login"; | ||||
| const LoginRecord = Immutable.Record({ | 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 initialState = new LoginRecord(); | ||||
| const reduceReceiveMessage = (state, message) => { | 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) => { | 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 */ | /* Styles */ | ||||
| body { | 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 { | 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 { | #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 { | 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 { | header h1 { | ||||
| margin: 0; | |||||
| margin: 0; | |||||
| } | } | ||||
| footer { | footer { | ||||
| display: flex; | |||||
| padding: 0.5em; | |||||
| width: 100%; | |||||
| box-sizing: border-box; | |||||
| display: flex; | |||||
| padding: 0.5em; | |||||
| width: 100%; | |||||
| box-sizing: border-box; | |||||
| } | } | ||||
| main { | main { | ||||
| width: 100%; | |||||
| height: 85%; | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| width: 100%; | |||||
| height: 85%; | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| } | } | ||||
| #rooms-pane { | #rooms-pane { | ||||
| display: flex; | |||||
| border: solid grey 0.1em; | |||||
| height: 100%; | |||||
| display: flex; | |||||
| border: solid grey 0.1em; | |||||
| height: 100%; | |||||
| } | } | ||||
| #room-chat { | #room-chat { | ||||
| border: solid grey 0.1em; | |||||
| border: solid grey 0.1em; | |||||
| } | } | ||||
| #room-list { | #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 { | #room-selected-pane { | ||||
| height: 100%; | |||||
| flex: 3; | |||||
| display: flex; | |||||
| flex-flow: row; | |||||
| height: 100%; | |||||
| flex: 3; | |||||
| display: flex; | |||||
| flex-flow: row; | |||||
| } | } | ||||
| #room-chat { | #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 { | #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 { | #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 { | #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 { | #room-chat-header > button { | ||||
| padding: 0.5em 1em; | |||||
| padding: 0.5em 1em; | |||||
| } | } | ||||
| #room-chat-message-list { | #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 { | .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 { | .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 { | .room-chat-message-user { | ||||
| font-weight: bold; | |||||
| color: blue; | |||||
| font-weight: bold; | |||||
| color: blue; | |||||
| } | } | ||||
| .room-chat-message-text { | .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 { | .room-chat-message-me > .room-chat-message-text { | ||||
| background-color: blue; | |||||
| color: white; | |||||
| background-color: blue; | |||||
| color: white; | |||||
| } | } | ||||
| #room-chat-form { | #room-chat-form { | ||||
| width: 100%; | |||||
| border: solid grey 0.1em; | |||||
| width: 100%; | |||||
| border: solid grey 0.1em; | |||||
| } | } | ||||
| #room-chat-form form { | #room-chat-form form { | ||||
| width: 100%; | |||||
| display: flex; | |||||
| flex-flow: row; | |||||
| width: 100%; | |||||
| display: flex; | |||||
| flex-flow: row; | |||||
| } | } | ||||
| #room-chat-form input { | #room-chat-form input { | ||||
| flex: 1; | |||||
| padding: 0.8em; | |||||
| flex: 1; | |||||
| padding: 0.8em; | |||||
| } | } | ||||
| #room-list-header { | #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 { | #room-list-header > div { | ||||
| flex: 1; | |||||
| border: solid grey 0.1em; | |||||
| flex: 1; | |||||
| border: solid grey 0.1em; | |||||
| } | } | ||||
| #room-list ul { | #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 { | .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 { | .room:hover { | ||||
| background: lightgrey; | |||||
| background: lightgrey; | |||||
| } | } | ||||
| .room:active { | .room:active { | ||||
| background: grey; | |||||
| background: grey; | |||||
| } | } | ||||
| .room-joined { | .room-joined { | ||||
| background: lightgreen; | |||||
| background: lightgreen; | |||||
| } | } | ||||
| .room-selected { | .room-selected { | ||||
| background: lightblue; | |||||
| background: lightblue; | |||||
| } | } | ||||
| #login-status-pane, #socket-status-pane { | |||||
| flex: 1; | |||||
| #login-status-pane, | |||||
| #socket-status-pane { | |||||
| flex: 1; | |||||
| } | } | ||||
| #connect-form { | #connect-form { | ||||
| display: flex; | |||||
| flex-flow: column; | |||||
| align-items: center; | |||||
| display: flex; | |||||
| flex-flow: column; | |||||
| align-items: center; | |||||
| } | } | ||||
| @ -1,34 +1,36 @@ | |||||
| export default { | 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) { | if (props[propName] != null) { | ||||
| return validator(props, propName, componentName, location); | |||||
| return validator(props, propName, componentName, location); | |||||
| } | } | ||||
| return new Error( | return new Error( | ||||
| `Required prop \`${propName}\` was not specified in ` + | |||||
| `Required prop \`${propName}\` was not specified in ` + | |||||
| `\`${componentName}\`.` | `\`${componentName}\`.` | ||||
| ); | ); | ||||
| }; | |||||
| }; | |||||
| export default (validator) => { | export default (validator) => { | ||||
| validator.isRequired = checkRequiredThenValidate(validator); | |||||
| return validator; | |||||
| validator.isRequired = checkRequiredThenValidate(validator); | |||||
| return validator; | |||||
| }; | }; | ||||