Reactサンプルアプリ開発 プラニングポーカー (7) 他のプレイヤーが参加できるようにする

Poker サンプル開発

前回は、親が場を開くと場の画面へ遷移し、Ajaxで取得したプレイヤーリストを表示するところを実装した。

今回は、この場に対して他のプレイヤーが参加できるようにする。開いた場のURLは親が何らかの手段で伝達済みであるとし、そのURLにアクセスすると以下の画面が開く。

ニックネームを入れて「参加する」をクリックすると場の画面へ遷移する。

まず、参加しているユーザーの状態はセッションで管理することにする。

from flask import Flask, render_template, request, redirect, jsonify, session
import uuid

app = Flask(__name__, static_folder="public", static_url_path="/")
app.secret_key = "hogehogehoge"

flaskからsessionをインポートする(1行目)。
app.secret_keyにキーフレーズを設定する必要がある(5行目)。

親が場を開いた時点で、親ユーザーのセッションにプレイヤーID(親は1)を保管する。

@app.route("/table/open", methods=["POST"])
def open():
    table_name = request.form["tablename"]
    parent_name = request.form["nickname"]
    table_id = str(uuid.uuid4())
    player_id = 1  # 親を1とする
    players = [(player_id, parent_name)]
    table = {
        "ID": table_id,
        "NAME": table_name,
        "PLAYERS": players,
    }
    tables[table_id] = table
    session["ses_player_id"] = table_id + "." + str(player_id)
    return redirect(f"/table/{table_id}")

開かれた場のURL /table/<table_id> へのアクセスは以下の関数が受け付ける。

@app.route("/table/<table_id>", methods=["GET"])
def table(table_id):
    if not table_id in tables:
        return redirect("/")
    if not "ses_player_id" in session:
        return redirect(f"/table/{table_id}/join")  # 新しいプレイヤー
    ses_table_id, ses_player_id = session["ses_player_id"].split(".")
    if ses_table_id != table_id:
        return redirect(f"/table/{table_id}/join")  # 別の場に新しいプレイヤーとして参加
    player_id = int(ses_player_id)

    table = tables[table_id]  # 場の情報

    table_name = table["NAME"]
    players = table["PLAYERS"]
    player_name = next(name for (id, name) in players if id == player_id)
    if not player_name:
        return redirect("/")  # ここに到達することはないはず

    html = render_template(
        "table.html", table_id=table_id, table_name=table_name, player_name=player_name
    )
    return html

親に招待された子のユーザーの場合、まだセッションにプレイヤーIDが存在しないので、場に参加するための画面へリダイレクトされる(5-6行目)。
もし参加済みの場合は、場の画面が表示される(20-23行目)。

場に参加するURLへのアクセスは以下の関数で処理される。

@app.route("/table/<table_id>/join", methods=["GET"])
def join_init(table_id):
    table = tables[table_id]  # 場の情報

    table_name = table["NAME"]
    html = render_template("join.html", table_id=table_id, table_name=table_name)
    return html

join.htmlは以下。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title>Planning Poker</title>
    <link rel="stylesheet" href="/css/reset.css">
    <link rel="stylesheet" href="/css/style.css">
</head>

<body>
    <div id="app"></div>
    <script>
        var joinProps = {
            tableId: "{{ table_id }}",
            tableName: "{{ table_name }}",
        };
    </script>
    <script src="/js/join.bundle.js"></script>
</body>

</html>

join.tsx。このファイルを起点にwebpackでトランスパイルを行い、join.bundle.jsというファイル名でバンドルされる。

import React from 'react';
import ReactDOM from 'react-dom';
import { JoinPage } from './JoinPage';
//import * as serviceWorker from './serviceWorker';

declare var joinProps: any;

ReactDOM.render(
  <React.StrictMode>
    <JoinPage {...joinProps} />
  </React.StrictMode>,
  document.getElementById('app')
);

JoinPage.tsxではフォームを表示する。

import React, { FunctionComponent, useState } from "react";

type JoinPageProps = {
  tableId: string;
  tableName: string;
}

export const JoinPage: FunctionComponent<JoinPageProps> = (props) => {
  const action = `/table/${props.tableId}/join`;
  return (
    <>
      <header className="header">
        <h1 className="title">Planning Poker</h1>
        <p className="description">
          「{props.tableName}」に参加してプラニング・ポーカーを行いましょう!<br />
            ニックネームを入力してください
        </p>
      </header>
      <main>
        <form className="openForm" action={action} method="POST">
          <input name="nickname" type="text" required placeholder="ニックネーム" />
          <input type="submit" value="参加する" />
        </form>
      </main>
    </>
  )
}

このフォームをサブミットしたときのPOSTリクエストの受け口。

@app.route("/table/<table_id>/join", methods=["POST"])
def join_do(table_id):
    player_name = request.form["nickname"]

    table = tables[table_id]  # 場の情報
    players = table["PLAYERS"]
    player_id = len(players) + 1  # 現在の人数+1
    player = (player_id, player_name)
    players.append(player)
    session["ses_player_id"] = table_id + "." + str(player_id)
    return redirect(f"/table/{table_id}")

この時点で参加が確定するので、プレイヤーIDを振ってセッションに格納する(7、10行目)。
プレイヤーリストに追加(8-9行目)した後、場の画面へリダイレクトさせる(11行目)。

これで新たに参加したプレイヤーには、自分とそれ以前に参加済みのプレイヤー(親を含む)が表示される。このタイミングで、その他のプレイヤーに対しても画面更新をして最新の状態が見えるようにしたい。

次回はそのあたりを実装したい。

コメント

タイトルとURLをコピーしました