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 "../../style/basket_court.css"
import { useRef } from "react"
import CourtPlayer from "./CourtPlayer"
import { Player } from "../../data/Player"
import CourtSvg from '../../assets/basketball_court.svg?react';
import '../../style/basket_court.css';
import {useRef} from "react";
import CourtPlayer from "./CourtPlayer";
import {Player} from "../../tactic/Player";
export interface BasketCourtProps {
players: Player[]
onPlayerRemove: (p: Player) => void
players: Player[],
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 (
<div id="court-container" style={{ position: "relative" }}>
<CourtSvg id="court-svg" />
{players.map((player) => {
return (
<CourtPlayer
key={player.id}
player={player}
onRemove={() => onPlayerRemove(player)}
/>
)
<div id="court-container" ref={divRef} style={{position: "relative"}}>
<CourtSvg id="court-svg"/>
{players.map(player => {
return <CourtPlayer key={player.id}
player={player}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)}
/>
})}
</div>
)

@ -1,40 +1,52 @@
import { useRef } from "react"
import "../../style/player.css"
import RemoveIcon from "../../assets/icon/remove.svg?react"
import Draggable from "react-draggable"
import { PlayerPiece } from "./PlayerPiece"
import { Player } from "../../data/Player"
import {useRef} from "react";
import "../../style/player.css";
import RemoveIcon from "../../assets/icon/remove.svg?react";
import Draggable from "react-draggable";
import {PlayerPiece} from "./PlayerPiece";
import {Player} from "../../tactic/Player";
export interface PlayerProps {
player: Player
player: Player,
onChange: (p: Player) => void,
onRemove: () => void
}
/**
* 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 y = player.bottomRatio
return (
<Draggable handle={".player-piece"} nodeRef={ref} bounds="parent">
<div
ref={ref}
className={"player"}
style={{
position: "absolute",
left: `${x * 100}%`,
top: `${y * 100}%`,
}}>
<div
tabIndex={0}
className="player-content"
onKeyUp={(e) => {
if (e.key == "Delete") onRemove()
}}>
<Draggable
handle={".player-piece"}
bounds="parent"
defaultPosition={{x, y}}
onStop={() => onChange({
id: player.id,
rightRatio: player.rightRatio,
bottomRatio: player.bottomRatio,
team: player.team,
role: player.role
})}
>
<div className={"player"}
style={{
position: "absolute",
left: `${x * 100}%`,
top: `${y * 100}%`,
}}>
<div tabIndex={0}
className="player-content"
onKeyUp={e => {
if (e.key == "Delete")
onRemove()
}}>
<div className="player-selection-tab">
<RemoveIcon
className="player-selection-tab-remove"

@ -13,7 +13,7 @@ export interface Player {
team: Team
/**
* player's position
* player's role
* */
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 "../style/editor.css"
import TitleInput from "../components/TitleInput"
import { API } from "../Constants"
import { BasketCourt } from "../components/editor/BasketCourt"
import {CSSProperties, useEffect, useRef, useState} from "react";
import "../style/editor.css";
import TitleInput from "../components/TitleInput";
import {BasketCourt} from "../components/editor/BasketCourt";
import { Rack } from "../components/Rack"
import { PlayerPiece } from "../components/editor/PlayerPiece"
import { Player } from "../data/Player"
import { Team } from "../data/Team"
import {Rack} from "../components/Rack";
import {PlayerPiece} from "../components/editor/PlayerPiece";
import {Player} from "../tactic/Player";
import {Tactic, TacticContent} from "../tactic/Tactic";
import {fetchAPI} from "../Fetcher";
import {Team} from "../tactic/Team";
const ERROR_STYLE: CSSProperties = {
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
*/
@ -21,8 +29,21 @@ interface RackedPlayer {
key: string
}
export default function Editor({ id, name }: { id: number; name: string }) {
const [style, setStyle] = useState<CSSProperties>({})
export default function Editor({tactic}: { tactic: Tactic }) {
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 [allies, setAllies] = useState(
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 })),
)
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 refBounds = ref.getBoundingClientRect()
@ -75,28 +105,15 @@ export default function Editor({ id, name }: { id: number; name: string }) {
<div id="main-div">
<div id="topbar-div">
<div>LEFT</div>
<TitleInput
style={style}
default_value={name}
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({})
} else {
setStyle(ERROR_STYLE)
}
})
}}
/>
<TitleInput style={style} default_value={name} on_validated={new_name => {
onNameChange(new_name).then(success => {
if (success) {
setStyle({})
} else {
setStyle(ERROR_STYLE)
}
})
}}/>
<div>RIGHT</div>
</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}>
<BasketCourt
players={players}
onPlayerChange={(player) => {
setPlayers(players => {
const idx = players.indexOf(player)
return players.toSpliced(idx, 1, player)
})
}}
onPlayerRemove={(player) => {
setPlayers((players) => {
const idx = players.indexOf(player)

@ -29,8 +29,9 @@ function getRoutes(): AltoRouter {
$router = new AltoRouter();
$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", "/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;
}

@ -2,7 +2,7 @@
namespace IQBall\Api\Controller;
use IQBall\Core\Route\Control;
use IQBall\App\Control;
use IQBall\Core\Data\Account;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest;
@ -45,4 +45,13 @@ class APITacticController {
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
namespace IQBall\Core\Route;
namespace IQBall\App;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\ValidationFail;
use IQBall\Core\Validation\Validator;

Loading…
Cancel
Save