prepare front to persistable tactics

maxime.batista 1 year ago committed by Override-6
parent 7f2d145e6e
commit 4986247181
Signed by untrusted user who does not match committer: maxime.batista
GPG Key ID: 8002CC4B4DD9ECA5

@ -0,0 +1,13 @@
import {API} from "./Constants";
export function fetchAPI(url: string, payload: object, method = "POST"): Promise<Response> {
return fetch(`${API}/${url}`, {
method,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
}

@ -1,26 +1,27 @@
import CourtSvg from "../../assets/basketball_court.svg?react" import CourtSvg from '../../assets/basketball_court.svg?react';
import "../../style/basket_court.css" import '../../style/basket_court.css';
import { useRef } from "react" import {useRef} from "react";
import CourtPlayer from "./CourtPlayer" import CourtPlayer from "./CourtPlayer";
import { Player } from "../../data/Player" import {Player} from "../../tactic/Player";
export interface BasketCourtProps { export interface BasketCourtProps {
players: Player[] players: Player[],
onPlayerRemove: (p: Player) => void onPlayerRemove: (p: Player) => void,
onPlayerChange: (p: Player) => void
} }
export function BasketCourt({ players, onPlayerRemove }: BasketCourtProps) { export function BasketCourt({players, onPlayerRemove, onPlayerChange}: BasketCourtProps) {
const divRef = useRef<HTMLDivElement>(null);
return ( return (
<div id="court-container" style={{ position: "relative" }}> <div id="court-container" ref={divRef} style={{position: "relative"}}>
<CourtSvg id="court-svg"/> <CourtSvg id="court-svg"/>
{players.map((player) => { {players.map(player => {
return ( return <CourtPlayer key={player.id}
<CourtPlayer
key={player.id}
player={player} player={player}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)} onRemove={() => onPlayerRemove(player)}
/> />
)
})} })}
</div> </div>
) )

@ -1,39 +1,51 @@
import { useRef } from "react"
import "../../style/player.css" import {useRef} from "react";
import RemoveIcon from "../../assets/icon/remove.svg?react" import "../../style/player.css";
import Draggable from "react-draggable" import RemoveIcon from "../../assets/icon/remove.svg?react";
import { PlayerPiece } from "./PlayerPiece" import Draggable from "react-draggable";
import { Player } from "../../data/Player" import {PlayerPiece} from "./PlayerPiece";
import {Player} from "../../tactic/Player";
export interface PlayerProps { export interface PlayerProps {
player: Player player: Player,
onChange: (p: Player) => void,
onRemove: () => void onRemove: () => void
} }
/** /**
* A player that is placed on the court, which can be selected, and moved in the associated bounds * A player that is placed on the court, which can be selected, and moved in the associated bounds
* */ * */
export default function CourtPlayer({ player, onRemove }: PlayerProps) {
const ref = useRef<HTMLDivElement>(null) export default function CourtPlayer({player, onChange, onRemove}: PlayerProps) {
const x = player.rightRatio const x = player.rightRatio
const y = player.bottomRatio const y = player.bottomRatio
return ( return (
<Draggable handle={".player-piece"} nodeRef={ref} bounds="parent"> <Draggable
<div handle={".player-piece"}
ref={ref} bounds="parent"
className={"player"} defaultPosition={{x, y}}
onStop={() => onChange({
id: player.id,
rightRatio: player.rightRatio,
bottomRatio: player.bottomRatio,
team: player.team,
role: player.role
})}
>
<div className={"player"}
style={{ style={{
position: "absolute", position: "absolute",
left: `${x * 100}%`, left: `${x * 100}%`,
top: `${y * 100}%`, top: `${y * 100}%`,
}}> }}>
<div
tabIndex={0} <div tabIndex={0}
className="player-content" className="player-content"
onKeyUp={(e) => { onKeyUp={e => {
if (e.key == "Delete") onRemove() if (e.key == "Delete")
onRemove()
}}> }}>
<div className="player-selection-tab"> <div className="player-selection-tab">
<RemoveIcon <RemoveIcon

@ -13,7 +13,7 @@ export interface Player {
team: Team team: Team
/** /**
* player's position * player's role
* */ * */
role: string role: string

@ -0,0 +1,11 @@
import {Player} from "./Player";
export interface Tactic {
id: number,
name: string,
content: TacticContent
}
export interface TacticContent {
players: Player[]
}

@ -1,18 +1,26 @@
import { CSSProperties, useRef, useState } from "react" import {CSSProperties, useEffect, useRef, useState} from "react";
import "../style/editor.css" import "../style/editor.css";
import TitleInput from "../components/TitleInput" import TitleInput from "../components/TitleInput";
import { API } from "../Constants" import {BasketCourt} from "../components/editor/BasketCourt";
import { BasketCourt } from "../components/editor/BasketCourt"
import { Rack } from "../components/Rack" import {Rack} from "../components/Rack";
import { PlayerPiece } from "../components/editor/PlayerPiece" import {PlayerPiece} from "../components/editor/PlayerPiece";
import { Player } from "../data/Player"
import { Team } from "../data/Team" import {Player} from "../tactic/Player";
import {Tactic, TacticContent} from "../tactic/Tactic";
import {fetchAPI} from "../Fetcher";
import {Team} from "../tactic/Team";
const ERROR_STYLE: CSSProperties = { const ERROR_STYLE: CSSProperties = {
borderColor: "red", borderColor: "red",
} }
export interface EditorViewProps {
tactic: Tactic,
onContentChange: (tactic: TacticContent) => Promise<boolean>,
onNameChange: (name: string) => Promise<boolean>
}
/** /**
* information about a player that is into a rack * information about a player that is into a rack
*/ */
@ -21,8 +29,21 @@ interface RackedPlayer {
key: string key: string
} }
export default function Editor({ id, name }: { id: number; name: string }) { export default function Editor({tactic}: { tactic: Tactic }) {
const [style, setStyle] = useState<CSSProperties>({}) return <EditorView tactic={tactic}
onContentChange={(content: TacticContent) => (
fetchAPI(`tactic/${tactic.id}/save`, {content})
.then((r) => r.ok)
)}
onNameChange={(name: string) => (
fetchAPI(`tactic/${tactic.id}/edit/name`, {name})
.then((r) => r.ok)
)}/>
}
function EditorView({tactic: {name, content}, onContentChange, onNameChange}: EditorViewProps) {
const [style, setStyle] = useState<CSSProperties>({});
const positions = ["1", "2", "3", "4", "5"] const positions = ["1", "2", "3", "4", "5"]
const [allies, setAllies] = useState( const [allies, setAllies] = useState(
positions.map((key) => ({ team: Team.Allies, key })), positions.map((key) => ({ team: Team.Allies, key })),
@ -31,8 +52,17 @@ export default function Editor({ id, name }: { id: number; name: string }) {
positions.map((key) => ({ team: Team.Opponents, key })), positions.map((key) => ({ team: Team.Opponents, key })),
) )
const [players, setPlayers] = useState<Player[]>([])
const courtDivContentRef = useRef<HTMLDivElement>(null) const [players, setPlayers] = useState<Player[]>(content.players);
const courtDivContentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
onContentChange({players})
.then(success => {
if (!success)
alert("error when saving changes.")
})
}, [players])
const canDetach = (ref: HTMLDivElement) => { const canDetach = (ref: HTMLDivElement) => {
const refBounds = ref.getBoundingClientRect() const refBounds = ref.getBoundingClientRect()
@ -75,28 +105,15 @@ export default function Editor({ id, name }: { id: number; name: string }) {
<div id="main-div"> <div id="main-div">
<div id="topbar-div"> <div id="topbar-div">
<div>LEFT</div> <div>LEFT</div>
<TitleInput <TitleInput style={style} default_value={name} on_validated={new_name => {
style={style} onNameChange(new_name).then(success => {
default_value={name} if (success) {
on_validated={(new_name) => {
fetch(`${API}/tactic/${id}/edit/name`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: new_name,
}),
}).then((response) => {
if (response.ok) {
setStyle({}) setStyle({})
} else { } else {
setStyle(ERROR_STYLE) setStyle(ERROR_STYLE)
} }
}) })
}} }}/>
/>
<div>RIGHT</div> <div>RIGHT</div>
</div> </div>
<div id="edit-div"> <div id="edit-div">
@ -126,6 +143,12 @@ export default function Editor({ id, name }: { id: number; name: string }) {
<div id="court-div-bounds" ref={courtDivContentRef}> <div id="court-div-bounds" ref={courtDivContentRef}>
<BasketCourt <BasketCourt
players={players} players={players}
onPlayerChange={(player) => {
setPlayers(players => {
const idx = players.indexOf(player)
return players.toSpliced(idx, 1, player)
})
}}
onPlayerRemove={(player) => { onPlayerRemove={(player) => {
setPlayers((players) => { setPlayers((players) => {
const idx = players.indexOf(player) const idx = players.indexOf(player)

@ -29,8 +29,9 @@ function getRoutes(): AltoRouter {
$router = new AltoRouter(); $router = new AltoRouter();
$router->setBasePath(get_public_path() . "/api"); $router->setBasePath(get_public_path() . "/api");
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize())); $router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize()));
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id) => getTacticController()->saveContent($id)));
return $router; return $router;
} }

@ -2,7 +2,7 @@
namespace IQBall\Api\Controller; namespace IQBall\Api\Controller;
use IQBall\Core\Route\Control; use IQBall\App\Control;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpRequest;
@ -45,4 +45,13 @@ class APITacticController {
return HttpResponse::fromCode(HttpCodes::OK); return HttpResponse::fromCode(HttpCodes::OK);
}); });
} }
/**
* @param int $id
* @param Account $account
* @return HttpResponse
*/
public function saveContent(int $id, Account $account): HttpResponse {
return HttpResponse::fromCode(HttpCodes::OK);
}
} }

@ -1,12 +1,10 @@
<?php <?php
namespace IQBall\Core\Route; namespace IQBall\App;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\ValidationFail;
use IQBall\Core\Validation\Validator; use IQBall\Core\Validation\Validator;

Loading…
Cancel
Save