--- /dev/null
+function undisplay(element) {
+ element.style.display="none";
+}
+
+function add_message(severity, message) {
+ message = `<div class="message ${severity}" onclick="undisplay(this)">
+<span class="hide-button" onclick="undisplay(this.parentElement)">×</span>
+${message}
+</div>`;
+ const message_area = document.getElementById('message-area');
+ message_area.insertAdjacentHTML('beforeend', message);
+}
+
+/*********************************************************
+ * Handling server-sent event stream *
+ *********************************************************/
+
+const events = new EventSource("events");
+
+events.onerror = function(event) {
+ if (event.target.readyState === EventSource.CLOSED) {
+ setTimeout(() => {
+ add_message("danger", "Connection to server lost.");
+ }, 1000);
+ }
+};
+
+events.addEventListener("game-info", event => {
+ const info = JSON.parse(event.data);
+
+ window.game.set_game_info(info);
+});
+
+events.addEventListener("player-info", event => {
+ const info = JSON.parse(event.data);
+
+ window.game.set_player_info(info);
+});
+
+events.addEventListener("player-enter", event => {
+ const info = JSON.parse(event.data);
+
+ window.game.set_other_player_info(info);
+});
+
+events.addEventListener("player-update", event => {
+ const info = JSON.parse(event.data);
+
+ if (info.id === window.game.state.player_info.id)
+ window.game.set_player_info(info);
+ else
+ window.game.set_other_player_info(info);
+});
+
+/*********************************************************
+ * Game and supporting classes *
+ *********************************************************/
+
+function copy_to_clipboard(id)
+{
+ const tmp = document.createElement("input");
+ tmp.setAttribute("value", document.getElementById(id).innerHTML);
+ document.body.appendChild(tmp);
+ tmp.select();
+ document.execCommand("copy");
+ document.body.removeChild(tmp);
+}
+
+function GameInfo(props) {
+ if (! props.id)
+ return null;
+
+ return (
+ <div className="game-info">
+ <span className="game-id">{props.id}</span>
+ {" "}
+ Share this link to invite friends:{" "}
+ <span id="game-share-url">{props.url}</span>
+ {" "}
+ <button
+ className="inline"
+ onClick={() => copy_to_clipboard('game-share-url')}
+ >Copy Link</button>
+ </div>
+ );
+}
+
+function PlayerInfo(props) {
+ if (! props.player.id)
+ return null;
+
+ return (
+ <div className="player-info">
+ <span className="players-header">Players: </span>
+ {props.player.name}
+ {props.other_players.map(other => (
+ <span key={other.id}>
+ {", "}
+ {other.name}
+ </span>
+ ))}
+ </div>
+ );
+}
+
+function fetch_method_json(method, api = '', data = {}) {
+ const response = fetch(api, {
+ method: method,
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+ });
+ return response;
+}
+
+function fetch_post_json(api = '', data = {}) {
+ return fetch_method_json('POST', api, data);
+}
+
+async function fetch_put_json(api = '', data = {}) {
+ return fetch_method_json('PUT', api, data);
+}
+
+function CategoryRequest(props) {
+ return (
+ <div className="category-request">
+ <h2>Submit a Category</h2>
+ <p>
+ Suggest a category to play with your friends. Don't forget to
+ include the number of items for each person to submit.
+ </p>
+
+ <form>
+ <div className="form-field large">
+ <input
+ type="text"
+ id="category"
+ placeholder="6 things at the beach"
+ required pattern=".*[0-9]+.*"
+ title="Category must contain a number"
+ >
+ </input>
+ </div>
+
+ <div className="form-field large">
+ <button type="submit">
+ Send
+ </button>
+ </div>
+
+ </form>
+ </div>
+ );
+}
+
+class Game extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ game_info: {},
+ player_info: {},
+ other_players: [],
+ };
+ }
+
+ set_game_info(info) {
+ this.setState({
+ game_info: info
+ });
+ }
+
+ set_player_info(info) {
+ this.setState({
+ player_info: info
+ });
+ }
+
+ set_other_player_info(info) {
+ const other_players_copy = [...this.state.other_players];
+ const idx = other_players_copy.findIndex(o => o.id === info.id);
+ if (idx >= 0) {
+ other_players_copy[idx] = info;
+ } else {
+ other_players_copy.push(info);
+ }
+ this.setState({
+ other_players: other_players_copy
+ });
+ }
+
+ render() {
+ const state = this.state;
+
+ return [
+ <GameInfo
+ key="game-info"
+ id={state.game_info.id}
+ url={state.game_info.url}
+ />,
+ <PlayerInfo
+ key="player-info"
+ game={this}
+ player={state.player_info}
+ other_players={state.other_players}
+ />,
+ <p key="spacer"></p>,
+ <CategoryRequest
+ key="category-request"
+ />
+ ];
+ }
+}
+
+ReactDOM.render(<Game
+ ref={(me) => window.game = me}
+ />, document.getElementById("empathy"));