Reactサンプルアプリ開発 プラニングポーカー (10) Bidの通知

Poker サンプル開発
<!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 tableProps = {
            tableId: "{{ table_id }}",
            tableName: "{{ table_name }}",
            playerId: Number("{{ player_id }}"),
        };
    </script>
    <script src="/js/table.bundle.js"></script>
</body>

</html>
import React from 'react';
import ReactDOM from 'react-dom';
import { TablePage } from './TablePage';

declare var tableProps: any;

ReactDOM.render(
  <React.StrictMode>
    <TablePage {...tableProps} />
  </React.StrictMode>,
  document.getElementById('app')
);
import React, { FunctionComponent, useState, useEffect } from "react";
import { ParentOperations } from "./ParentOperations";
import { GameResults } from "./GameResults";
import { Players } from "./Players";
import { PlayerProps } from "./Player";
import axios from 'axios';
import io from 'socket.io-client';

type TableProps = {
  tableId: string;
  tableName: string;
  playerId: number;
}

const socket: SocketIOClient.Socket = io();

const mapPlayers = (names: string[], myId: number) => {
  const players: PlayerProps[] =
    names.map((n: string, idx: number) => mapPlayer(n, idx === myId, idx));
  return players;
}

const mapPlayer = (name: string, isMe: boolean, pos: number) => {
  return {
    name: name,
    isMe: isMe,
    icon: (pos + 1) % 4,
    bid: "",
    open: false,
    onBidChange: (x: string) => { },
  }
}

export const TablePage: FunctionComponent<TableProps> = (props) => {
  const [bidding, setBidding] = useState(true);
  const [players, setPlayers] = useState<PlayerProps[]>([]);

  useEffect(() => {
    axios.get(`/table/${props.tableId}/players`)
      .then((res) => {
        const players = mapPlayers(res.data.players, props.playerId);
        console.log(`Got ${players.length} players by ajax call.`)
        setPlayers(players);
      }).catch((error) => {
        console.log("通信失敗")
        console.log(error.status);
      });
    return () => {
      socket.close();
    }
  }, []);

  useEffect(() => {
    socket.on("joined", (data: any) => {
      console.log(`${data.playerName} has joined (ID=${data.playerId})`);
      const joined = mapPlayer(data.playerName, false, data.playerId);
      const updatedPlayers = players.slice();
      updatedPlayers.push(joined);
      setPlayers(updatedPlayers);
    });
    return () => {
      socket.off("joined");
    }
  }, [players]);

  useEffect(() => {
    socket.on("bidded", (data: any) => {
      console.log(`Player ${data.playerId} has changed the bid: ${data.bid}`);
      const idx: number = data.playerId;
      const updatedPlayer = { ...players[idx], ...{ bid: data.bid } };
      const updatedPlayers = players.map((p, i) => i === idx ? updatedPlayer : p);
      setPlayers(updatedPlayers);
    });
    return () => {
      socket.off("bidded");
    }
  }, [players]);

  const handleOpen = () => {
    const ps = players.slice();
    ps.forEach(p => p.open = true);
    setPlayers(ps);
    setBidding(false);
  }

  const handleNewGame = () => {
    const ps = players.slice();
    ps.forEach(p => {
      p.open = false;
      p.bid = "";
    });
    setPlayers(ps);
    setBidding(true);
  }

  const handleBidChange = (bid: string, idx: number) => {
    const ps = players.slice();
    console.log('handleBidChange was called.')
    ps[idx].bid = bid;
    setPlayers(ps);
    setTimeout(() => {
      socket.emit("bidding", idx, bid);
    }, 0);
  }

  return (
    <>
      <header className="header">
        <span className="table-name">{props.tableName}</span>
      </header>
      <main>
        <ParentOperations bidding={bidding}
          onOpen={handleOpen} onNewGame={handleNewGame} />
        <GameResults />
        <Players players={
          players.map((p, idx) => Object.assign(p,
            { onBidChange: (bid: string) => handleBidChange(bid, idx) }))
        } />
      </main>
    </>
  )
}
import React, { FunctionComponent, useState } from "react";
import { Player, PlayerProps } from "./Player";

type PlayersProps = {
  players: PlayerProps[];
}

export const Players: FunctionComponent<PlayersProps> = (props) => {
  const players = props.players.map((player) => {
    return (
      <Player {...player} />
    );
  });
  return (
    <div className="players">
      {players}
    </div>
  )
}
import React, { FunctionComponent, useState } from "react";
import Modal from 'react-modal';
import { Deck } from "./Deck";

Modal.setAppElement('#app');
const customStyles = {
  overlay: {
    backgroundColor: "rgba(0, 0, 0, 0.7)"
  },
  content: {
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    padding: 0,
    transform: 'translate(-50%, -50%)'
  }
};

export type PlayerProps = {
  name: string;
  icon: number;
  bid: string;
  open: boolean;
  isMe: boolean;
  onBidChange: (bid: string) => void;
}

export const Player: FunctionComponent<PlayerProps> = (props) => {
  const [modalIsOpen, setModalIsOpen] = useState(false);
  //const [bid, setBid] = useState(props.bid);
  const points = ["0", "1", "2", "3", "5", "8"];

  const handleModalOpen = () => {
    setModalIsOpen(true);
  }

  const handleModalClose = () => {
    setModalIsOpen(false);
  }

  const handleCardSelect = (point: string) => {
    //setBid(point);
    setModalIsOpen(false);
    props.onBidChange(point);
  }

  const playerIsMe = props.isMe ? "player-me" : "";
  const playerCardOpen = props.open ? "player-card-open" : "";
  // オープン前で入力済みの他ユーザーは?を表示する
  const point = (!props.open && !props.isMe && props.bid) ? "?" : props.bid;

  return (
    <div className={`player ${playerIsMe}`}>
      <div className="player-info">
        <div className={"player-icon-" + props.icon}>
        </div>
        <div className="player-name">{props.name}</div>
      </div>
      <div className={`player-card ${playerCardOpen}`} onClick={props.isMe ? handleModalOpen : undefined}>
        <span className="point">{point}</span>
      </div>
      <Modal isOpen={modalIsOpen} onRequestClose={handleModalClose}
        contentLabel="test" style={customStyles} >
        <Deck points={points} onCardSelected={handleCardSelect} />
      </Modal>
    </div>
  )
}
import React, { FunctionComponent, useState } from "react";

type DeckProps = {
  points: string[];
  onCardSelected: (point: string) => void;
}

export const Deck: FunctionComponent<DeckProps> = (props) => {

  const handleCardSelect = (point: string) => {
    props.onCardSelected(point);
  }

  const cards = props.points.map((point) => {
    return (
      <div className="deck-card" onClick={() => handleCardSelect(point)} >
        <span className="point">{point}</span>
      </div>
    );
  });
  return (
    <div className="deck">
      {cards}
    </div>
  );
}

コメント

  1. […] 【2020-5-9追記】gunicornで複数ワーカープロセス設定をすると、Flask-SocketIOで400 Bad Requestエラーが起こることが判明。詳細と対処方法は次回記事に記載。 […]

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