Apply suggestions
continuous-integration/drone/push Build is passing Details

pull/23/head
maxime.batista 1 year ago
parent 44513a5049
commit cb24dd53a9

@ -2,7 +2,7 @@ import { API } from "./Constants"
export function fetchAPI(
url: string,
payload: object,
payload: unknown,
method = "POST",
): Promise<Response> {
return fetch(`${API}/${url}`, {

@ -1,4 +1,11 @@
import React, { CSSProperties, useEffect, useRef, useState } from "react"
import {
CSSProperties,
Dispatch,
SetStateAction,
useCallback,
useRef,
useState,
} from "react"
import "../style/editor.css"
import TitleInput from "../components/TitleInput"
import { BasketCourt } from "../components/editor/BasketCourt"
@ -11,7 +18,10 @@ import { Tactic, TacticContent } from "../tactic/Tactic"
import { fetchAPI } from "../Fetcher"
import { Team } from "../tactic/Team"
import { calculateRatio } from "../Utils"
import SavingState, { SaveStates } from "../components/editor/SavingState"
import SavingState, {
SaveState,
SaveStates,
} from "../components/editor/SavingState"
const ERROR_STYLE: CSSProperties = {
borderColor: "red",
@ -54,57 +64,27 @@ export default function Editor({
}
function EditorView({
tactic: { name, content },
tactic: { name, content: initialContent },
onContentChange,
onNameChange,
}: EditorViewProps) {
const [style, setStyle] = useState<CSSProperties>({})
const [saveState, setSaveState] = useState(SaveStates.Ok)
const positions = ["1", "2", "3", "4", "5"]
const [content, setContent, saveState] = useContentState(
initialContent,
(content) =>
onContentChange(content).then((success) =>
success ? SaveStates.Ok : SaveStates.Err,
),
)
const [allies, setAllies] = useState(
positions
.filter(
(role) =>
content.players.findIndex(
(p) => p.team == Team.Allies && p.role == role,
) == -1,
)
.map((key) => ({ team: Team.Allies, key })),
getRackPlayers(Team.Allies, content.players),
)
const [opponents, setOpponents] = useState(
positions
.filter(
(role) =>
content.players.findIndex(
(p) => p.team == Team.Opponents && p.role == role,
) == -1,
)
.map((key) => ({ team: Team.Opponents, key })),
getRackPlayers(Team.Opponents, content.players),
)
const [players, setPlayers] = useState<Player[]>(content.players)
const courtDivContentRef = useRef<HTMLDivElement>(null)
// The didMount ref is used to store a boolean flag in order to avoid calling 'onChange' when the editor is first rendered.
const didMount = useRef(false)
useEffect(() => {
if (!didMount.current) {
didMount.current = true
return
}
setSaveState(SaveStates.Saving)
onContentChange({ players })
.then((success) => {
if (success) {
setSaveState(SaveStates.Ok)
} else {
setSaveState(SaveStates.Err)
}
})
.catch(() => setSaveState(SaveStates.Err))
}, [players])
const canDetach = (ref: HTMLDivElement) => {
const refBounds = ref.getBoundingClientRect()
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
@ -124,16 +104,18 @@ function EditorView({
const { x, y } = calculateRatio(refBounds, courtBounds)
setPlayers((players) => {
return [
...players,
{
team: element.team,
role: element.key,
rightRatio: x,
bottomRatio: y,
},
]
setContent((content) => {
return {
players: [
...content.players,
{
team: element.team,
role: element.key,
rightRatio: x,
bottomRatio: y,
},
],
}
})
}
@ -183,47 +165,40 @@ function EditorView({
<div id="court-div">
<div id="court-div-bounds" ref={courtDivContentRef}>
<BasketCourt
players={players}
players={content.players}
onPlayerChange={(player) => {
setPlayers((players) => {
const idx = players.findIndex(
(p) =>
p.team === player.team &&
p.role === player.role,
)
return players.toSpliced(idx, 1, player)
})
setContent((content) => ({
players: toSplicedPlayers(
content.players,
player,
true,
),
}))
}}
onPlayerRemove={(player) => {
setPlayers((players) => {
const idx = players.findIndex(
(p) =>
p.team === player.team &&
p.role === player.role,
)
return players.toSpliced(idx, 1)
})
setContent((content) => ({
players: toSplicedPlayers(
content.players,
player,
false,
),
}))
let setter
switch (player.team) {
case Team.Opponents:
setOpponents((opponents) => [
...opponents,
{
team: player.team,
pos: player.role,
key: player.role,
},
])
setter = setOpponents
break
case Team.Allies:
setAllies((allies) => [
...allies,
{
team: player.team,
pos: player.role,
key: player.role,
},
])
setter = setAllies
}
setter((players) => [
...players,
{
team: player.team,
pos: player.role,
key: player.role,
},
])
}}
/>
</div>
@ -232,3 +207,51 @@ function EditorView({
</div>
)
}
function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] {
return ["1", "2", "3", "4", "5"]
.filter(
(role) =>
players.findIndex((p) => p.team == team && p.role == role) ==
-1,
)
.map((key) => ({ team, key }))
}
function useContentState<S>(
initialContent: S,
saveStateCallback: (s: S) => Promise<SaveState>,
): [S, Dispatch<SetStateAction<S>>, SaveState] {
const [content, setContent] = useState(initialContent)
const [savingState, setSavingState] = useState(SaveStates.Ok)
const setContentSynced = useCallback((newState: SetStateAction<S>) => {
setContent((content) => {
const state =
typeof newState === "function"
? (newState as (state: S) => S)(content)
: newState
if (state !== content) {
setSavingState(SaveStates.Saving)
saveStateCallback(state)
.then(setSavingState)
.catch(() => setSavingState(SaveStates.Err))
}
return state
})
}, [])
return [content, setContentSynced, savingState]
}
function toSplicedPlayers(
players: Player[],
player: Player,
replace: boolean,
): Player[] {
const idx = players.findIndex(
(p) => p.team === player.team && p.role === player.role,
)
return players.toSpliced(idx, 1, ...(replace ? [player] : []))
}

@ -44,7 +44,7 @@ CREATE TABLE Member
(
id_team integer,
id_user integer,
role char(1) CHECK (role IN ('Coach', 'Player')),
role text CHECK (role IN ('Coach', 'Player')),
FOREIGN KEY (id_team) REFERENCES Team (id),
FOREIGN KEY (id_user) REFERENCES User (id)
);

@ -9,6 +9,7 @@ use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\Validators;
/**
@ -55,7 +56,9 @@ class APITacticController {
return Control::runChecked([
"content" => [],
], function (HttpRequest $req) use ($id) {
$this->model->updateContent($id, json_encode($req["content"]));
if ($fail = $this->model->updateContent($id, json_encode($req["content"]))) {
return new JsonHttpResponse([$fail]);
}
return HttpResponse::fromCode(HttpCodes::OK);
});
}

@ -25,7 +25,7 @@ class Connection {
* @return void
*/
public function exec(string $query, array $args) {
$stmnt = $this->prepare($query, $args);
$stmnt = $this->prep($query, $args);
$stmnt->execute();
}
@ -36,7 +36,7 @@ class Connection {
* @return array<string, mixed>[] the returned rows of the request
*/
public function fetch(string $query, array $args): array {
$stmnt = $this->prepare($query, $args);
$stmnt = $this->prep($query, $args);
$stmnt->execute();
return $stmnt->fetchAll(PDO::FETCH_ASSOC);
}
@ -46,7 +46,7 @@ class Connection {
* @param array<string, array<mixed, int>> $args
* @return \PDOStatement
*/
private function prepare(string $query, array $args): \PDOStatement {
private function prep(string $query, array $args): \PDOStatement {
$stmnt = $this->pdo->prepare($query);
foreach ($args as $name => $value) {
$stmnt->bindValue($name, $value[0], $value[1]);
@ -54,4 +54,8 @@ class Connection {
return $stmnt;
}
public function prepare(string $query): \PDOStatement {
return $this->pdo->prepare($query);
}
}

@ -74,26 +74,30 @@ class TacticInfoGateway {
* update name of given tactic identifier
* @param int $id
* @param string $name
* @return void
* @return bool
*/
public function updateName(int $id, string $name): void {
$this->con->exec(
"UPDATE Tactic SET name = :name WHERE id = :id",
[
":name" => [$name, PDO::PARAM_STR],
":id" => [$id, PDO::PARAM_INT],
]
);
public function updateName(int $id, string $name): bool {
$stmnt = $this->con->prepare("UPDATE Tactic SET name = :name WHERE id = :id");
$stmnt->execute([
":name" => $name,
":id" => $id,
]);
return $stmnt->rowCount() == 1;
}
public function updateContent(int $id, string $json): void {
$this->con->exec(
"UPDATE Tactic SET content = :content WHERE id = :id",
[
":content" => [$json, PDO::PARAM_STR],
":id" => [$id, PDO::PARAM_INT],
]
);
/***
* Updates a given tactics content
* @param int $id
* @param string $json
* @return bool
*/
public function updateContent(int $id, string $json): bool {
$stmnt = $this->con->prepare("UPDATE Tactic SET content = :content WHERE id = :id");
$stmnt->execute([
":content" => $json,
":id" => $id,
]);
return $stmnt->rowCount() == 1;
}
}

@ -76,12 +76,17 @@ class TacticModel {
return [ValidationFail::unauthorized()];
}
$this->tactics->updateName($id, $name);
if (!$this->tactics->updateName($id, $name)) {
return [ValidationFail::error("Could not update name")];
}
return [];
}
public function updateContent(int $id, string $json): void {
$this->tactics->updateContent($id, $json);
public function updateContent(int $id, string $json): ?ValidationFail {
if (!$this->tactics->updateContent($id, $json)) {
return ValidationFail::error("Could not update content");
}
return null;
}
}

@ -49,4 +49,8 @@ class ValidationFail implements JsonSerializable {
return new ValidationFail("Unauthorized", $message);
}
public static function error(string $message): ValidationFail {
return new ValidationFail("Error", $message);
}
}

Loading…
Cancel
Save