前回は、親が場を開くと場の画面へ遷移し、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行目)。
これで新たに参加したプレイヤーには、自分とそれ以前に参加済みのプレイヤー(親を含む)が表示される。このタイミングで、その他のプレイヤーに対しても画面更新をして最新の状態が見えるようにしたい。
次回はそのあたりを実装したい。
コメント