diff --git a/frontend/static/css/gameHistory.css b/frontend/static/css/gameHistory.css new file mode 100644 index 0000000..465b115 --- /dev/null +++ b/frontend/static/css/gameHistory.css @@ -0,0 +1,15 @@ + +#game-list { + justify-content: flex-start; + display: flex; + flex-wrap: wrap; +} + +#game-list .game-item { + display: flex; + flex-direction: column; + height: 160px; + width: 160px; + margin: 10px; + border-radius: 5%; +} \ No newline at end of file diff --git a/frontend/static/js/api/Profile.js b/frontend/static/js/api/Profile.js index 75d0a68..b055423 100644 --- a/frontend/static/js/api/Profile.js +++ b/frontend/static/js/api/Profile.js @@ -1,4 +1,5 @@ import { Client } from "./Client.js"; +import { Game } from "./game/Game.js"; class Profile { @@ -59,6 +60,36 @@ class Profile } + /** + * @returns {[Game]} + */ + async getGameHistory() + { + let response = await this.client._get(`/api/games/history/${this.id}`); + let response_data = await response.json(); + + let games = []; + + response_data.forEach(game_data => { + games.push(new Game(this.client, + game_data.id, + null, + null, + null, + game_data.winner_id, + game_data.state, + game_data.started, + game_data.finished, + game_data.players, + game_data.start_timestamp, + game_data.stop_timestamp + ) + ); + }); + + return games; + } + async getBlock() { let block_response = await this.client._get("/api/profiles/block"); diff --git a/frontend/static/js/api/Profiles.js b/frontend/static/js/api/Profiles.js index ceddad6..7c0de7e 100644 --- a/frontend/static/js/api/Profiles.js +++ b/frontend/static/js/api/Profiles.js @@ -42,6 +42,11 @@ class Profiles return profile; } + /** + * + * @param {Number} id + * @returns {Profile} + */ async getProfileId(id) { let profile = new Profile(this.client, undefined, id); diff --git a/frontend/static/js/api/game/Game.js b/frontend/static/js/api/game/Game.js index 4c1b830..6f1a4d8 100644 --- a/frontend/static/js/api/game/Game.js +++ b/frontend/static/js/api/game/Game.js @@ -13,8 +13,16 @@ class Game * @param {CallableFunction} goal_handler * @param {CallableFunction} finish_handler * @param {CallableFunction} disconnect_handler + * @param {Boolean} finished + * @param {Number} id + * @param {[Object]} players_data + * @param {Number} start_timestamp + * @param {Number} stop_timestamp + * @param {Boolean} started + * @param {Number} winner_id + * @param {String} state */ - constructor(client, id, disconnect_handler, goal_handler, finish_handler) + constructor(client, id, disconnect_handler, goal_handler, finish_handler, winner_id, state, started, finished, players_data, start_timestamp, stop_timestamp) { /** * @type {Client} @@ -40,6 +48,50 @@ class Game * @type {CallableFunction} */ this.disconnect_handler = disconnect_handler; + + /** + * @type {String} + */ + this.state = state; + + /** + * @type {Boolean} + */ + this.started = started; + + /** + * @type {Boolean} + */ + this.finished = finished; + + /** + * @type {Number} + */ + this.winner_id = this.finished ? winner_id : undefined; + + /** + * @type {Number} + */ + this.start_timestamp = start_timestamp; + + /** + * @type {Number} + */ + this.stop_timestamp = stop_timestamp; + + /** + * @type {[Player]} + */ + this.players = []; + + players_data.forEach(player_data => { + this.players.push(new Player(this, + player_data.player_id, + player_data.username, + player_data.score + ) + ); + }); } /** @@ -55,9 +107,6 @@ class Game let response_data = await response.json(); - /** - * @type {[Player]} - */ this.players = []; response_data.players.forEach(player_data => { @@ -69,34 +118,14 @@ class Game ); }); - /** - * @type {String} - */ this.state = response_data.state; - /** - * @type {Boolean} - */ this.started = response_data.started; - - /** - * @type {Boolean} - */ this.finished = response_data.finished; - /** - * @type {Number} - */ this.winner_id = this.finished ? response_data.winner_id : undefined; - /** - * @type {Number} - */ this.start_timestamp = response_data.start_timestamp; - - /** - * @type {Number} - */ this.stop_timestamp = response_data.stop_timestamp; if (this.finished === true) diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index c6491a8..1bf21fe 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -19,6 +19,7 @@ import TournamentsView from "./views/tournament/TournamentsListView.js"; import TournamentCreateView from "./views/tournament/TournamentCreateView.js"; import AuthenticationView from "./views/accounts/AuthenticationView.js"; import TicTacToeView from "./views/TicTacToeView.js"; +import GameHistoryView from "./views/GameHistoryView.js"; let client = new Client(location.origin); let lang = client.lang; @@ -77,6 +78,7 @@ const router = async(uri) => { const routes = [ { path: "/", view: Dashboard }, + { path: "/profiles/:id/history", view: GameHistoryView }, { path: "/profiles/:username", view: ProfilePageView }, { path: "/tournaments/create", view: TournamentCreateView }, { path: "/tournaments/:id", view: TournamentPageView }, diff --git a/frontend/static/js/views/GameHistoryView.js b/frontend/static/js/views/GameHistoryView.js new file mode 100644 index 0000000..60e7850 --- /dev/null +++ b/frontend/static/js/views/GameHistoryView.js @@ -0,0 +1,66 @@ +import { client, lang } from "../index.js"; +import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js"; + +export default class extends AbstractAuthenticatedView +{ + constructor(params) + { + super(params, 'homeWindowTitle'); + this.id = params.id; + } + + async postInit() + { + this.profile = await client.profiles.getProfileId(this.id); + + if (this.profile === null) + return 404; + + await this.fillHistory(); + } + + async fillHistory() + { + let games = await this.profile.getGameHistory(); + + let game_list = document.getElementById("game-list"); + + games.forEach(game => { + + let a = document.createElement("a"); + a.href = `/games/${game.id}/0`; + a.setAttribute("data-link", true); + + let game_item = document.createElement("div"); + game_item.className = "game-item"; + game_item.style.backgroundColor = "grey"; + if (game.started) + game_item.style.backgroundColor = "yellow"; + if (game.finished) + game_item.style.backgroundColor = this.profile.id === game.winner_id ? "green" : "red"; + + game.players.forEach(player => { + let player_score = document.createElement("a"); + + player_score.href = `/profiles/${player.username}`; + player_score.innerText = `${player.username}: ${player.score.length}`; + player_score.setAttribute("data-link", true); + + game_item.appendChild(player_score); + }); + + a.appendChild(game_item); + game_list.appendChild(a); + }); + } + + async getHtml() + { + return /* HTML */ ` + +

Game History

+
+
+ `; + } +} diff --git a/games/GameHistoryView.py b/games/GameHistoryView.py new file mode 100644 index 0000000..b7a1a10 --- /dev/null +++ b/games/GameHistoryView.py @@ -0,0 +1,27 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import permissions, status + +from django.http import HttpRequest + +from .models import GameMembersModel, GameModel +from .serializers import GameSerializer + +from . import config + +class GameHistoryView(APIView): + + permission_classes = (permissions.AllowAny,) + + def get(self, request: HttpRequest, pk: int = None): + + member_game_model_list: list[GameMembersModel] = GameMembersModel.objects.filter(player_id = pk) + + game_model_list: list[GameModel] = [] + + for member_game_model in member_game_model_list: + game_model_list.append(GameModel.objects.get(pk = member_game_model.game_id)) + + games_data: list[dict] = [GameSerializer(game_model).data for game_model in game_model_list] + + return Response(games_data) \ No newline at end of file diff --git a/games/urls.py b/games/urls.py index f27cdd2..34e28c2 100644 --- a/games/urls.py +++ b/games/urls.py @@ -3,9 +3,11 @@ from django.conf import settings from django.conf.urls.static import static from .GameViewSet import GameViewSet +from .GameHistoryView import GameHistoryView from .GameConfigView import GameConfigView urlpatterns = [ path("", GameViewSet.as_view({"get": "retrieve"}), name="game_page"), + path("history/", GameHistoryView.as_view(), name="history_page"), path("", GameConfigView.as_view(), name = "game_config") ] \ No newline at end of file