From a6cceb31ea41a7bd60d5ff91c1d2ceb73ad17fc6 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Fri, 17 Nov 2023 14:34:38 +0100 Subject: [PATCH 01/20] apply suggestions --- front/components/Rack.tsx | 39 ++++++++++---------- front/components/editor/BasketCourt.tsx | 6 +-- front/components/editor/CourtPlayer.tsx | 8 ++-- front/data/Player.ts | 4 +- front/views/Editor.tsx | 49 +++++++++++++++---------- 5 files changed, 58 insertions(+), 48 deletions(-) diff --git a/front/components/Rack.tsx b/front/components/Rack.tsx index ad8f354..415ba2f 100644 --- a/front/components/Rack.tsx +++ b/front/components/Rack.tsx @@ -1,40 +1,39 @@ -import {Dispatch, ReactElement, RefObject, SetStateAction, useRef} from "react"; +import {ReactElement, useRef} from "react"; import Draggable from "react-draggable"; -export interface RackInput { +export interface RackProps { id: string, - objects: [ReactElement[], Dispatch>], - canDetach: (ref: RefObject) => boolean, - onElementDetached: (ref: RefObject, el: ReactElement) => void, + objects: E[], + onChange: (objects: E[]) => void, + canDetach: (ref: HTMLDivElement) => boolean, + onElementDetached: (ref: HTMLDivElement, el: E) => void, + render: (e: E) => ReactElement, } -interface RackItemInput { - item: ReactElement, - onTryDetach: (ref: RefObject, el: ReactElement) => void +interface RackItemProps { + item: E, + onTryDetach: (ref: HTMLDivElement, el: E) => void, + render: (e: E) => ReactElement, } /** * A container of draggable objects * */ -export function Rack({id, objects, canDetach, onElementDetached}: RackInput) { - - const [rackObjects, setRackObjects] = objects - +export function Rack({id, objects, onChange, canDetach, onElementDetached, render}: RackProps) { return (
- {rackObjects.map(element => ( + {objects.map(element => ( { if (!canDetach(ref)) return - setRackObjects(objects => { - const index = objects.findIndex(o => o.key === element.key) - return objects.toSpliced(index, 1); - }) + const index = objects.findIndex(o => o.key === element.key) + onChange(objects.toSpliced(index, 1)) onElementDetached(ref, element) }}/> @@ -43,16 +42,16 @@ export function Rack({id, objects, canDetach, onElementDetached}: RackInput) { ) } -function RackItem({item, onTryDetach}: RackItemInput) { +function RackItem({item, onTryDetach, render}: RackItemProps) { const divRef = useRef(null); return ( onTryDetach(divRef, item)}> + onStop={() => onTryDetach(divRef.current!, item)}>
- {item} + {render(item)}
) diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index 0819633..607ad38 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,6 +1,6 @@ import CourtSvg from '../../assets/basketball_court.svg'; import '../../style/basket_court.css'; -import {MouseEvent, ReactElement, useEffect, useRef, useState} from "react"; +import {ReactElement, useEffect, useRef, useState} from "react"; import CourtPlayer from "./CourtPlayer"; import {Player} from "../../data/Player"; @@ -15,8 +15,8 @@ export function BasketCourt({players, onPlayerRemove}: { players: Player[], onPl onPlayerRemove(player)} /> diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 08323f4..5f05e75 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,10 +1,10 @@ -import React, {useRef} from "react"; +import {useRef} from "react"; import "../../style/player.css"; import RemoveIcon from "../../assets/icon/remove.svg"; import Draggable, {DraggableBounds} from "react-draggable"; import {PlayerPiece} from "./PlayerPiece"; -export interface PlayerOptions { +export interface PlayerProps { pos: string, team: string, x: number, @@ -16,7 +16,7 @@ export interface PlayerOptions { /** * A player that is placed on the court, which can be selected, and moved in the associated bounds * */ -export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerOptions) { +export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerProps) { const ref = useRef(null); return ( @@ -24,7 +24,7 @@ export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerO handle={".player-piece"} nodeRef={ref} bounds={bounds} - defaultPosition={{x: x, y: y}} + defaultPosition={{x, y}} >
({}); const positions = ["1", "2", "3", "4", "5"] const [allies, setAllies] = useState( - positions.map(pos => ) + positions.map(key => ({team: "allies", key})) ) const [opponents, setOpponents] = useState( - positions.map(pos => ) + positions.map(key => ({team: "opponents", key})) ) const [players, setPlayers] = useState([]); const courtDivContentRef = useRef(null); - const canDetach = (ref: RefObject) => { - const refBounds = ref.current!.getBoundingClientRect(); + const canDetach = (ref: HTMLDivElement) => { + const refBounds = ref.getBoundingClientRect(); const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); // check if refBounds overlaps courtBounds @@ -39,23 +47,23 @@ export default function Editor({id, name}: { id: number, name: string }) { ); } - const onElementDetach = (ref: RefObject, element: ReactElement) => { - const refBounds = ref.current!.getBoundingClientRect(); + const onElementDetach = (ref: HTMLDivElement, element: RackedPlayer) => { + const refBounds = ref.getBoundingClientRect(); const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); const relativeXPixels = refBounds.x - courtBounds.x; const relativeYPixels = refBounds.y - courtBounds.y; - const xPercent = relativeXPixels / courtBounds.width; - const yPercent = relativeYPixels / courtBounds.height; + const xRatio = relativeXPixels / courtBounds.width; + const yRatio = relativeYPixels / courtBounds.height; setPlayers(players => { return [...players, { id: players.length, - team: element.props.team, - position: element.props.text, - right_percentage: xPercent, - bottom_percentage: yPercent + team: element.team, + position: element.key, + rightRatio: xRatio, + bottomRatio: yRatio }] }) } @@ -87,13 +95,17 @@ export default function Editor({id, name}: { id: number, name: string }) {
+ onElementDetached={onElementDetach} + render={({team, key}) => }/> + onElementDetached={onElementDetach} + render={({team, key}) => }/>
@@ -104,16 +116,15 @@ export default function Editor({id, name}: { id: number, name: string }) { const idx = players.indexOf(player) return players.toSpliced(idx, 1) }) - const piece = switch (player.team) { case "opponents": setOpponents(opponents => ( - [...opponents, piece] + [...opponents, {team: player.team, pos: player.position, key: player.position}] )) break case "allies": setAllies(allies => ( - [...allies, piece] + [...allies, {team: player.team, pos: player.position, key: player.position}] )) } }}/> From eb0f270f0376061c99c94ac650c34456bf66b71f Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Mon, 20 Nov 2023 08:04:04 +0100 Subject: [PATCH 02/20] add ball on screen --- front/assets/icon/ball.svg | 62 +++++++++++++++++++++++++ front/components/editor/BallPiece.tsx | 13 ++++++ front/components/editor/CourtPlayer.tsx | 6 ++- front/data/Ball.ts | 15 ++++++ front/data/Player.ts | 7 ++- front/style/ball.css | 8 ++++ front/style/colors.css | 2 +- front/style/player.css | 4 +- front/views/Editor.tsx | 50 +++++++++++++++++++- 9 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 front/assets/icon/ball.svg create mode 100644 front/components/editor/BallPiece.tsx create mode 100644 front/data/Ball.ts create mode 100644 front/style/ball.css diff --git a/front/assets/icon/ball.svg b/front/assets/icon/ball.svg new file mode 100644 index 0000000..6351088 --- /dev/null +++ b/front/assets/icon/ball.svg @@ -0,0 +1,62 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + + + + diff --git a/front/components/editor/BallPiece.tsx b/front/components/editor/BallPiece.tsx new file mode 100644 index 0000000..79e8148 --- /dev/null +++ b/front/components/editor/BallPiece.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import "../../style/ball.css"; + +import Ball from "../../assets/icon/ball.svg"; + +export function BallPiece() { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 5f05e75..21a088e 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,6 +1,7 @@ import {useRef} from "react"; import "../../style/player.css"; import RemoveIcon from "../../assets/icon/remove.svg"; +import AssignBallIcon from "../../assets/icon/ball.svg"; import Draggable, {DraggableBounds} from "react-draggable"; import {PlayerPiece} from "./PlayerPiece"; @@ -10,13 +11,14 @@ export interface PlayerProps { x: number, y: number, bounds: DraggableBounds, - onRemove: () => void + onRemove: () => void, + hasBall: boolean } /** * A player that is placed on the court, which can be selected, and moved in the associated bounds * */ -export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerProps) { +export default function CourtPlayer({pos, team, x, y, bounds, onRemove, hasBall}: PlayerProps) { const ref = useRef(null); return ( diff --git a/front/data/Ball.ts b/front/data/Ball.ts new file mode 100644 index 0000000..c312fdf --- /dev/null +++ b/front/data/Ball.ts @@ -0,0 +1,15 @@ +export interface Ball { + + position: string, + + /** + * Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle) + */ + bottom_percentage: number, + + /** + * Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle) + */ + right_percentage: number, + +} \ No newline at end of file diff --git a/front/data/Player.ts b/front/data/Player.ts index 9484dd3..983b650 100644 --- a/front/data/Player.ts +++ b/front/data/Player.ts @@ -18,10 +18,15 @@ export interface Player { /** * Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle) */ - bottomRatio: number + bottomRatio: number, + /** * Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle) */ rightRatio: number, + + + hasBall: boolean, + } \ No newline at end of file diff --git a/front/style/ball.css b/front/style/ball.css new file mode 100644 index 0000000..5669b07 --- /dev/null +++ b/front/style/ball.css @@ -0,0 +1,8 @@ +#ball * { + fill: #c5520d; +} + +#ball { + width: 20px; + height: 20px; +} diff --git a/front/style/colors.css b/front/style/colors.css index f3287cb..54ee221 100644 --- a/front/style/colors.css +++ b/front/style/colors.css @@ -6,7 +6,7 @@ --background-color: #d2cdd3; - --selected-team-primarycolor: #ffffff; + --selected-team-primarycolor: #50b63a; --selected-team-secondarycolor: #000000; --selection-color: #3f7fc4 diff --git a/front/style/player.css b/front/style/player.css index ebd0462..d22a35f 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -27,9 +27,11 @@ on the court. background-color: var(--selected-team-primarycolor); color: var(--selected-team-secondarycolor); - border-width: 2px; border-radius: 100px; + /* + border-width: 2px; border-style: solid; + */ width: 20px; height: 20px; diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 05dc4a1..487ee51 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -7,6 +7,8 @@ import {BasketCourt} from "../components/editor/BasketCourt"; import {Rack} from "../components/Rack"; import {PlayerPiece} from "../components/editor/PlayerPiece"; import {Player} from "../data/Player"; +import {BallPiece} from "../components/editor/BallPiece"; +import {Ball} from "../data/Ball"; const ERROR_STYLE: CSSProperties = { borderColor: "red" @@ -24,6 +26,7 @@ export default function Editor({id, name}: { id: number, name: string }) { const [style, setStyle] = useState({}); const positions = ["1", "2", "3", "4", "5"] + const positionBall = ["1"] const [allies, setAllies] = useState( positions.map(key => ({team: "allies", key})) ) @@ -31,6 +34,9 @@ export default function Editor({id, name}: { id: number, name: string }) { positions.map(key => ({team: "opponents", key})) ) + const [ballPiece, setBallPiece] = useState(positionBall) + const [ball, setBall] = useState([]); + const [players, setPlayers] = useState([]); const courtDivContentRef = useRef(null); @@ -63,7 +69,42 @@ export default function Editor({id, name}: { id: number, name: string }) { team: element.team, position: element.key, rightRatio: xRatio, - bottomRatio: yRatio + bottomRatio: yRatio, + hasBall:false + }] + }) + } + + const canDetachBall = (ref: HTMLDivElement) => { + const refBounds = ref.getBoundingClientRect(); + const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); + + //check if we give the ball to a player on the court + if (!canDetach(ref)) { + return false; + } + for(const player in players) { + const rightRatio = player + } + return false; + } + + const onElementDetachBall = (ref: RefObject, element: ReactElement) => { + const refBounds = ref.current!.getBoundingClientRect(); + const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); + + const relativeXPixels = refBounds.x - courtBounds.x; + const relativeYPixels = refBounds.y - courtBounds.y; + + const xPercent = relativeXPixels / courtBounds.width; + const yPercent = relativeYPixels / courtBounds.height; + + + setBall(ball => { + return [...ball, { + position: element.props.text, + right_percentage: xPercent, + bottom_percentage: yPercent, }] }) } @@ -100,6 +141,12 @@ export default function Editor({id, name}: { id: number, name: string }) { canDetach={canDetach} onElementDetached={onElementDetach} render={({team, key}) => }/> + }/> { setPlayers(players => { const idx = players.indexOf(player) From d1564a3549f5845c464fe5b82baef2f9de9156a2 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 26 Nov 2023 14:00:30 +0100 Subject: [PATCH 03/20] add a gest mode for editor if user is not connected --- front/components/editor/SavingState.tsx | 4 ++ front/style/editor.css | 3 +- front/views/Editor.tsx | 72 +++++++++++++-------- public/index.php | 4 +- src/Api/API.php | 2 +- src/App/Controller/EditorController.php | 22 ++++++- src/App/Controller/VisualizerController.php | 2 +- src/Core/Gateway/TacticInfoGateway.php | 1 - 8 files changed, 74 insertions(+), 36 deletions(-) diff --git a/front/components/editor/SavingState.tsx b/front/components/editor/SavingState.tsx index df03628..68c2285 100644 --- a/front/components/editor/SavingState.tsx +++ b/front/components/editor/SavingState.tsx @@ -4,6 +4,10 @@ export interface SaveState { } export class SaveStates { + static readonly Guest: SaveState = { + className: "save-state-guest", + message: "you are not connected, your changes will not be saved.", + } static readonly Ok: SaveState = { className: "save-state-ok", message: "saved", diff --git a/front/style/editor.css b/front/style/editor.css index d832fc3..eefa561 100644 --- a/front/style/editor.css +++ b/front/style/editor.css @@ -85,6 +85,7 @@ color: green; } -.save-state-saving { +.save-state-saving, +.save-state-guest { color: gray; } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index dddf5cc..13f7684 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -29,7 +29,7 @@ const ERROR_STYLE: CSSProperties = { export interface EditorViewProps { tactic: Tactic - onContentChange: (tactic: TacticContent) => Promise + onContentChange: (tactic: TacticContent) => Promise onNameChange: (name: string) => Promise } @@ -50,31 +50,43 @@ export default function Editor({ name: string content: string }) { + const isInGuestMode = id == -1 + return ( - fetchAPI(`tactic/${id}/save`, { content }).then((r) => r.ok) - } - onNameChange={(name: string) => - fetchAPI(`tactic/${id}/edit/name`, { name }).then((r) => r.ok) - } + onContentChange={async (content: TacticContent) => { + if (isInGuestMode) { + return SaveStates.Guest + } + return fetchAPI(`tactic/${id}/save`, { content }).then((r) => + r.ok ? SaveStates.Ok : SaveStates.Err, + ) + }} + onNameChange={async (name: string) => { + if (isInGuestMode) { + return true //simulate that the name has been changed + } + return fetchAPI(`tactic/${id}/edit/name`, { name }).then( + (r) => r.ok, + ) + }} /> ) } function EditorView({ - tactic: { name, content: initialContent }, + tactic: { id, name, content: initialContent }, onContentChange, onNameChange, }: EditorViewProps) { + const isInGuestMode = id == -1 + const [style, setStyle] = useState({}) const [content, setContent, saveState] = useContentState( initialContent, - (content) => - onContentChange(content).then((success) => - success ? SaveStates.Ok : SaveStates.Err, - ), + isInGuestMode ? SaveStates.Guest : SaveStates.Ok, + onContentChange, ) const [allies, setAllies] = useState( getRackPlayers(Team.Allies, content.players), @@ -220,26 +232,30 @@ function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] { function useContentState( initialContent: S, + initialSaveState: SaveState, saveStateCallback: (s: S) => Promise, ): [S, Dispatch>, SaveState] { const [content, setContent] = useState(initialContent) - const [savingState, setSavingState] = useState(SaveStates.Ok) + const [savingState, setSavingState] = useState(initialSaveState) - const setContentSynced = useCallback((newState: SetStateAction) => { - 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 - }) - }, [saveStateCallback]) + const setContentSynced = useCallback( + (newState: SetStateAction) => { + 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 + }) + }, + [saveStateCallback], + ) return [content, setContentSynced, savingState] } diff --git a/public/index.php b/public/index.php index c31e289..78ee4d6 100644 --- a/public/index.php +++ b/public/index.php @@ -88,7 +88,9 @@ function getRoutes(): AltoRouter { //tactic-related $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s))); $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s))); - $ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + // don't require an authentication to run this action. + // If the user is not connected, the tactic will never save. + $ar->map("GET", "/tactic/new", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related $ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); diff --git a/src/Api/API.php b/src/Api/API.php index f79e1b5..da00749 100644 --- a/src/Api/API.php +++ b/src/Api/API.php @@ -19,7 +19,7 @@ class API { if ($response instanceof JsonHttpResponse) { header('Content-type: application/json'); echo $response->getJson(); - } else if (get_class($response) != HttpResponse::class) { + } elseif (get_class($response) != HttpResponse::class) { throw new Exception("API returned unknown Http Response"); } } diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index d2a2bc4..3994093 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -28,13 +28,31 @@ class EditorController { ]); } + /** + * @return ViewHttpResponse the editor view for a test tactic. + */ + private function openTestEditor(): ViewHttpResponse { + return ViewHttpResponse::react("views/Editor.tsx", [ + "id" => -1, //-1 id means that the editor will not support saves + "content" => '{"players": []}', + "name" => TacticModel::TACTIC_DEFAULT_NAME, + ]); + } + /** * creates a new empty tactic, with default name + * If the given session does not contain a connected account, + * open a test editor. * @param SessionHandle $session * @return ViewHttpResponse the editor view */ public function createNew(SessionHandle $session): ViewHttpResponse { - $tactic = $this->model->makeNewDefault($session->getAccount()->getId()); + $account = $session->getAccount(); + + if ($account == null) { + return $this->openTestEditor(); + } + $tactic = $this->model->makeNewDefault($account->getId()); return $this->openEditorFor($tactic); } @@ -55,6 +73,4 @@ class EditorController { return $this->openEditorFor($tactic); } - - } diff --git a/src/App/Controller/VisualizerController.php b/src/App/Controller/VisualizerController.php index 271c4e9..631468e 100644 --- a/src/App/Controller/VisualizerController.php +++ b/src/App/Controller/VisualizerController.php @@ -20,7 +20,7 @@ class VisualizerController { } /** - * opens a visualisation page for the tactic specified by its identifier in the url. + * Opens a visualisation page for the tactic specified by its identifier in the url. * @param int $id * @param SessionHandle $session * @return HttpResponse diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index 6b66f2c..447c7a5 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -99,5 +99,4 @@ class TacticInfoGateway { ]); return $stmnt->rowCount() == 1; } - } From 31c3525b1a5743c40c16fa4119abbec8df6f5e08 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 28 Nov 2023 20:57:37 +0100 Subject: [PATCH 04/20] persist in local storage if not connected --- front/views/Editor.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 13f7684..04362b5 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -27,6 +27,9 @@ const ERROR_STYLE: CSSProperties = { borderColor: "red", } +const GUEST_MODE_CONTENT_STORAGE_KEY = "guest_mode_content" +const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title" + export interface EditorViewProps { tactic: Tactic onContentChange: (tactic: TacticContent) => Promise @@ -52,11 +55,22 @@ export default function Editor({ }) { const isInGuestMode = id == -1 + const storage_content = localStorage.getItem(GUEST_MODE_CONTENT_STORAGE_KEY) + const editorContent = + isInGuestMode && storage_content != null ? storage_content : content + + const storage_name = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) + const editorName = isInGuestMode && storage_name != null ? storage_name : name + return ( { if (isInGuestMode) { + localStorage.setItem( + GUEST_MODE_CONTENT_STORAGE_KEY, + JSON.stringify(content), + ) return SaveStates.Guest } return fetchAPI(`tactic/${id}/save`, { content }).then((r) => @@ -65,6 +79,7 @@ export default function Editor({ }} onNameChange={async (name: string) => { if (isInGuestMode) { + localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name) return true //simulate that the name has been changed } return fetchAPI(`tactic/${id}/edit/name`, { name }).then( From 76c0eb28af238876489fe4d5eedc49e5e0054e76 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Fri, 17 Nov 2023 14:34:38 +0100 Subject: [PATCH 05/20] prepare front to persistable tactics --- front/components/editor/BasketCourt.tsx | 43 +++++++++++-------------- front/components/editor/CourtPlayer.tsx | 2 +- front/views/Editor.tsx | 34 ++++++++----------- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index 115c41b..ccdc382 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,35 +1,28 @@ -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" +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, - onPlayerChange, -}: BasketCourtProps) { - const divRef = useRef(null) +export function BasketCourt({players, onPlayerRemove, onPlayerChange}: BasketCourtProps) { + const divRef = useRef(null); return ( -
- - {players.map((player) => { - return ( - onPlayerRemove(player)} - parentRef={divRef} - /> - ) +
+ + {players.map(player => { + return onPlayerRemove(player)} + parentRef={divRef} + /> })}
) diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 12083d3..1709599 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,4 +1,4 @@ -import { RefObject, useRef, useState } from "react" +import { RefObject, useRef } from "react" import "../../style/player.css" import RemoveIcon from "../../assets/icon/remove.svg?react" import Draggable from "react-draggable" diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 04362b5..15bb5b2 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -1,27 +1,18 @@ -import { - CSSProperties, - Dispatch, - SetStateAction, - useCallback, - 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 { CSSProperties, Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react" +import "../style/editor.css"; +import TitleInput from "../components/TitleInput"; +import {BasketCourt} from "../components/editor/BasketCourt"; -import { Player } from "../tactic/Player" -import { Tactic, TacticContent } from "../tactic/Tactic" -import { fetchAPI } from "../Fetcher" -import { Team } from "../tactic/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"; +import SavingState, { SaveState, SaveStates } from "../components/editor/SavingState" import { calculateRatio } from "../Utils" -import SavingState, { - SaveState, - SaveStates, -} from "../components/editor/SavingState" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -103,6 +94,7 @@ function EditorView({ isInGuestMode ? SaveStates.Guest : SaveStates.Ok, onContentChange, ) + const [allies, setAllies] = useState( getRackPlayers(Team.Allies, content.players), ) From ce832a8510ab26440c5add516af7b6fd63cebc15 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 20 Nov 2023 09:56:05 +0100 Subject: [PATCH 06/20] add save state information in topbar --- front/components/editor/BasketCourt.tsx | 22 +++++++++++++--------- front/components/editor/CourtPlayer.tsx | 2 +- front/views/Editor.tsx | 16 +++++++++------- sql/setup-tables.sql | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index ccdc382..23c4a3f 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,17 +1,21 @@ -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"; +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, onPlayerChange}: BasketCourtProps) { - const divRef = useRef(null); +export function BasketCourt({ + players, + onPlayerRemove, + onPlayerChange, +}: BasketCourtProps) { + const divRef = useRef(null) return (
diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 1709599..12083d3 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,4 +1,4 @@ -import { RefObject, useRef } from "react" +import { RefObject, useRef, useState } from "react" import "../../style/player.css" import RemoveIcon from "../../assets/icon/remove.svg?react" import Draggable from "react-draggable" diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 15bb5b2..380b024 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -4,15 +4,17 @@ 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 "../tactic/Player"; -import {Tactic, TacticContent} from "../tactic/Tactic"; -import {fetchAPI} from "../Fetcher"; -import {Team} from "../tactic/Team"; -import SavingState, { SaveState, SaveStates } from "../components/editor/SavingState" +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" import { calculateRatio } from "../Utils" +import SavingState, { SaveState, SaveStates } from "../components/editor/SavingState" const ERROR_STYLE: CSSProperties = { borderColor: "red", diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index eb74877..7c012eb 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -46,5 +46,5 @@ CREATE TABLE Member id_user integer, role text CHECK (role IN ('Coach', 'Player')), FOREIGN KEY (id_team) REFERENCES Team (id), - FOREIGN KEY (id_user) REFERENCES User (id) + FOREIGN KEY (id_user) REFERENCES Account (id) ); From 8546206bc19ae705b9aacf783233d73c3f422b27 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 26 Nov 2023 21:35:23 +0100 Subject: [PATCH 07/20] add view to select between half and plain court --- .../{basketball_court.svg => court/court.svg} | 1 + front/assets/court/half_court.svg | 22 ++++ front/components/editor/BasketCourt.tsx | 24 ++-- front/style/basket_court.css | 8 +- front/style/colors.css | 11 -- front/style/editor.css | 5 +- front/style/new_tactic_panel.css | 117 ++++++++++++++++++ front/style/theme/default.css | 21 ++++ front/views/Editor.tsx | 42 +++++-- front/views/NewTacticPanel.tsx | 63 ++++++++++ front/views/Visualizer.tsx | 2 +- public/index.php | 5 +- sql/setup-tables.sql | 29 ++--- src/App/Controller/EditorController.php | 17 +-- src/Core/Data/CourtType.php | 61 +++++++++ src/Core/Data/MemberRole.php | 8 +- src/Core/Data/TacticInfo.php | 10 +- src/Core/Gateway/TacticInfoGateway.php | 10 +- src/Core/Model/TacticModel.php | 11 +- 19 files changed, 397 insertions(+), 70 deletions(-) rename front/assets/{basketball_court.svg => court/court.svg} (99%) create mode 100644 front/assets/court/half_court.svg delete mode 100644 front/style/colors.css create mode 100644 front/style/new_tactic_panel.css create mode 100644 front/style/theme/default.css create mode 100644 front/views/NewTacticPanel.tsx create mode 100755 src/Core/Data/CourtType.php diff --git a/front/assets/basketball_court.svg b/front/assets/court/court.svg similarity index 99% rename from front/assets/basketball_court.svg rename to front/assets/court/court.svg index e0df003..476a96f 100644 --- a/front/assets/basketball_court.svg +++ b/front/assets/court/court.svg @@ -2,6 +2,7 @@ xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" + height="100%" viewBox="7.5 18.5 85.5 56" style="enable-background:new 7.5 18.5 85.5 56;" xml:space="preserve"> diff --git a/front/assets/court/half_court.svg b/front/assets/court/half_court.svg new file mode 100644 index 0000000..8c06d6d --- /dev/null +++ b/front/assets/court/half_court.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index 23c4a3f..5305f6f 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,4 +1,3 @@ -import CourtSvg from "../../assets/basketball_court.svg?react" import "../../style/basket_court.css" import { useRef } from "react" import CourtPlayer from "./CourtPlayer" @@ -8,25 +7,30 @@ export interface BasketCourtProps { players: Player[] onPlayerRemove: (p: Player) => void onPlayerChange: (p: Player) => void + courtImage: string, } export function BasketCourt({ players, onPlayerRemove, onPlayerChange, + courtImage }: BasketCourtProps) { const divRef = useRef(null) return ( -
- - {players.map(player => { - return onPlayerRemove(player)} - parentRef={divRef} - /> +
+ {"court"} + {players.map((player) => { + return ( + onPlayerRemove(player)} + parentRef={divRef} + /> + ) })}
) diff --git a/front/style/basket_court.css b/front/style/basket_court.css index c001cc0..353d519 100644 --- a/front/style/basket_court.css +++ b/front/style/basket_court.css @@ -1,11 +1,17 @@ #court-container { display: flex; + align-content: center; + align-items: center; + justify-content: center; + height: 100%; + + padding: 20px 40px 20px 40px; background-color: var(--main-color); } #court-svg { - margin: 5%; + height: 100%; user-select: none; -webkit-user-drag: none; } diff --git a/front/style/colors.css b/front/style/colors.css deleted file mode 100644 index 3c17a25..0000000 --- a/front/style/colors.css +++ /dev/null @@ -1,11 +0,0 @@ -:root { - --main-color: #ffffff; - --second-color: #ccde54; - - --background-color: #d2cdd3; - - --selected-team-primarycolor: #ffffff; - --selected-team-secondarycolor: #000000; - - --selection-color: #3f7fc4; -} diff --git a/front/style/editor.css b/front/style/editor.css index eefa561..45ce43d 100644 --- a/front/style/editor.css +++ b/front/style/editor.css @@ -1,4 +1,4 @@ -@import "colors.css"; +@import "theme/default.css"; #main-div { display: flex; @@ -63,7 +63,8 @@ } #court-div-bounds { - width: 60%; + padding: 20px 20px 20px 20px; + height: 75%; } .react-draggable { diff --git a/front/style/new_tactic_panel.css b/front/style/new_tactic_panel.css new file mode 100644 index 0000000..e5640b6 --- /dev/null +++ b/front/style/new_tactic_panel.css @@ -0,0 +1,117 @@ + +#panel-root { + width: 100%; + height: 100%; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + align-content: center; +} + +#panel-top { + font-family: var(--text-main-font); +} + +#panel-choices { + width: 100%; + height: 100%; + + display: flex; + justify-content: center; + align-items: center; + align-content: center; + + background-color: var(--editor-court-selection-background); +} + +#panel-buttons { + width: 75%; + height: 30%; + display: flex; + justify-content: space-evenly; + align-items: stretch; + align-content: center; +} + +.court-kind-button { + position: relative; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + align-content: center; + + cursor: pointer; + + transition: scale 1s; +} + +.court-kind-button-bottom, .court-kind-button-top { + width: 100%; + height: 100%; + border: solid; + border-color: var(--border-color); +} + +.court-kind-button-bottom { + display: flex; + justify-content: center; + align-items: center; + align-content: center; + + height: 25%; + background-color: var(--editor-court-selection-buttons); + border-radius: 0 0 20px 20px; + border-width: 3px; +} + +.court-kind-button-top { + height: 75%; + overflow: hidden; + background-color: var(--main-color); + border-radius: 20px 20px 0 0; + border-width: 3px 3px 0 3px; +} + +.court-kind-button:hover { + scale: 1.1; +} + +.court-kind-button-top, .court-kind-button-image-div { + display: flex; + justify-content: center; + align-items: stretch; + align-content: stretch; +} + +.court-kind-button-image { + height: 100%; + user-select: none; + -webkit-user-drag: none; +} + +.court-kind-button-image-div { + width: 100%; + height: 100%; + padding: 0 10px 0 10px; + background-color: var(--second-color); +} + +.court-kind-button-name, .court-kind-button-details { + user-select: none; + font-family: var(--text-main-font); +} + +.court-kind-button-details { + position: absolute; + z-index: -1; + top: 0; + transition: top 1s; +} + +.court-kind-button:hover .court-kind-button-details { + top: -20px; +} \ No newline at end of file diff --git a/front/style/theme/default.css b/front/style/theme/default.css new file mode 100644 index 0000000..0baa9b9 --- /dev/null +++ b/front/style/theme/default.css @@ -0,0 +1,21 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400&display=swap'); +:root { + --main-color: #ffffff; + --second-color: #e8e8e8; + + --background-color: #d2cdd3; + + --selected-team-primarycolor: #ffffff; + --selected-team-secondarycolor: #000000; + + --buttons-shadow-color: #a8a8a8; + + --selection-color: #3f7fc4; + + --border-color: #FFFFFF; + + --editor-court-selection-background: #5f8fee; + --editor-court-selection-buttons: #acc4f3; + + --text-main-font: 'Roboto', sans-serif; +} diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 380b024..4c4c725 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -1,20 +1,30 @@ +import { + CSSProperties, + Dispatch, + SetStateAction, + useCallback, + useRef, + useState, +} from "react" +import "../style/editor.css" +import TitleInput from "../components/TitleInput" +import { BasketCourt } from "../components/editor/BasketCourt" -import { CSSProperties, Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from "react" -import "../style/editor.css"; -import TitleInput from "../components/TitleInput"; -import {BasketCourt} from "../components/editor/BasketCourt"; - +import plainCourt from "../assets/court/court.svg" +import halfCourt from "../assets/court/half_court.svg" 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" import { calculateRatio } from "../Utils" -import SavingState, { SaveState, SaveStates } from "../components/editor/SavingState" +import SavingState, { + SaveState, + SaveStates, +} from "../components/editor/SavingState" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -27,6 +37,14 @@ export interface EditorViewProps { tactic: Tactic onContentChange: (tactic: TacticContent) => Promise onNameChange: (name: string) => Promise + courtType: string +} + +export interface EditorProps { + id: number + name: string + content: string + courtType: string } /** @@ -87,10 +105,11 @@ function EditorView({ tactic: { id, name, content: initialContent }, onContentChange, onNameChange, + courtType, }: EditorViewProps) { const isInGuestMode = id == -1 - const [style, setStyle] = useState({}) + const [titleStyle, setTitleStyle] = useState({}) const [content, setContent, saveState] = useContentState( initialContent, isInGuestMode ? SaveStates.Guest : SaveStates.Ok, @@ -149,11 +168,11 @@ function EditorView({
{ onNameChange(new_name).then((success) => { - setStyle(success ? {} : ERROR_STYLE) + setTitleStyle(success ? {} : ERROR_STYLE) }) }} /> @@ -187,6 +206,9 @@ function EditorView({
{ setContent((content) => ({ players: toSplicedPlayers( diff --git a/front/views/NewTacticPanel.tsx b/front/views/NewTacticPanel.tsx new file mode 100644 index 0000000..0f72c37 --- /dev/null +++ b/front/views/NewTacticPanel.tsx @@ -0,0 +1,63 @@ +import "../style/theme/default.css" +import "../style/new_tactic_panel.css" + +import plainCourt from "../assets/court/court.svg" +import halfCourt from "../assets/court/half_court.svg" + +export default function NewTacticPanel() { + return ( +
+
+

Select a basket court

+
+
+
+ + +
+
+
+ ) +} + +function CourtKindButton({ + name, + image, + details, + redirect, +}: { + name: string + image: string + details: string + redirect: string +}) { + return ( +
location.href = redirect} + > +
{details}
+
+
+ {name} +
+
+
+

{name}

+
+
+ ) +} diff --git a/front/views/Visualizer.tsx b/front/views/Visualizer.tsx index 541da09..5b09115 100644 --- a/front/views/Visualizer.tsx +++ b/front/views/Visualizer.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, useState } from "react" import "../style/visualizer.css" -import Court from "../assets/basketball_court.svg" +import Court from "../assets/court/court.svg" export default function Visualizer({ id, name }: { id: number; name: string }) { const [style, setStyle] = useState({}) diff --git a/public/index.php b/public/index.php index 78ee4d6..cd777ab 100644 --- a/public/index.php +++ b/public/index.php @@ -19,6 +19,7 @@ use IQBall\App\Session\SessionHandle; use IQBall\App\ViewHttpResponse; use IQBall\Core\Action; use IQBall\Core\Connection; +use IQBall\Core\Data\CourtType; use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\MemberGateway; use IQBall\Core\Gateway\TacticInfoGateway; @@ -90,7 +91,9 @@ function getRoutes(): AltoRouter { $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s))); // don't require an authentication to run this action. // If the user is not connected, the tactic will never save. - $ar->map("GET", "/tactic/new", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + $ar->map("GET", "/tactic/new", Action::noAuth(fn() => getEditorController()->createNew())); + $ar->map("GET", "/tactic/new/plain", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNewOfKind(CourtType::plain(), $s))); + $ar->map("GET", "/tactic/new/half", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNewOfKind(CourtType::half(), $s))); //team-related $ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 7c012eb..efb20c6 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -16,35 +16,36 @@ CREATE TABLE Account CREATE TABLE Tactic ( id integer PRIMARY KEY AUTOINCREMENT, - name varchar NOT NULL, - creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - owner integer NOT NULL, - content varchar DEFAULT '{"players": []}' NOT NULL, + name varchar NOT NULL, + creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, + owner integer NOT NULL, + content varchar DEFAULT '{"players": []}' NOT NULL, + court_type varchar CHECK ( court_type IN ('HALF', 'PLAIN')) NOT NULL, FOREIGN KEY (owner) REFERENCES Account ); CREATE TABLE FormEntries ( - name varchar, - description varchar + name varchar NOT NULL, + description varchar NOT NULL ); CREATE TABLE Team ( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar, - picture varchar, - main_color varchar, - second_color varchar + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + name varchar NOT NULL, + picture varchar NOT NULL, + main_color varchar NOT NULL, + second_color varchar NOT NULL ); CREATE TABLE Member ( - id_team integer, - id_user integer, - role text CHECK (role IN ('Coach', 'Player')), + id_team integer NOT NULL, + id_user integer NOT NULL, + role text CHECK (role IN ('COACH', 'PLAYER')) NOT NULL, FOREIGN KEY (id_team) REFERENCES Team (id), FOREIGN KEY (id_user) REFERENCES Account (id) ); diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 3994093..238ae83 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -5,9 +5,11 @@ namespace IQBall\App\Controller; use IQBall\App\Session\SessionHandle; use IQBall\App\Validator\TacticValidator; use IQBall\App\ViewHttpResponse; +use IQBall\Core\Data\CourtType; use IQBall\Core\Data\TacticInfo; use IQBall\Core\Http\HttpCodes; use IQBall\Core\Model\TacticModel; +use IQBall\Core\Validation\ValidationFail; class EditorController { private TacticModel $model; @@ -25,9 +27,14 @@ class EditorController { "id" => $tactic->getId(), "name" => $tactic->getName(), "content" => $tactic->getContent(), + "courtType" => $tactic->getCourtType()->name() ]); } + public function createNew(): ViewHttpResponse { + return ViewHttpResponse::react("views/NewTacticPanel.tsx", []); + } + /** * @return ViewHttpResponse the editor view for a test tactic. */ @@ -44,15 +51,11 @@ class EditorController { * If the given session does not contain a connected account, * open a test editor. * @param SessionHandle $session + * @param CourtType $type * @return ViewHttpResponse the editor view */ - public function createNew(SessionHandle $session): ViewHttpResponse { - $account = $session->getAccount(); - - if ($account == null) { - return $this->openTestEditor(); - } - $tactic = $this->model->makeNewDefault($account->getId()); + public function createNewOfKind(CourtType $type, SessionHandle $session): ViewHttpResponse { + $tactic = $this->model->makeNewDefault($session->getAccount()->getId(), $type); return $this->openEditorFor($tactic); } diff --git a/src/Core/Data/CourtType.php b/src/Core/Data/CourtType.php new file mode 100755 index 0000000..caad45c --- /dev/null +++ b/src/Core/Data/CourtType.php @@ -0,0 +1,61 @@ + self::COURT_HALF) { + throw new InvalidArgumentException("Valeur du rôle invalide"); + } + $this->value = $val; + } + + public static function plain(): CourtType { + return new CourtType(CourtType::COURT_PLAIN); + } + + public static function half(): CourtType { + return new CourtType(CourtType::COURT_HALF); + } + + public function name(): string { + switch ($this->value) { + case self::COURT_HALF: + return "HALF"; + case self::COURT_PLAIN: + return "PLAIN"; + } + die("unreachable"); + } + + public static function fromName(string $name): ?CourtType { + switch ($name) { + case "HALF": + return CourtType::half(); + case "PLAIN": + return CourtType::plain(); + default: + return null; + } + } + + public function isPlain(): bool { + return ($this->value == self::COURT_PLAIN); + } + + public function isHalf(): bool { + return ($this->value == self::COURT_HALF); + } + +} diff --git a/src/Core/Data/MemberRole.php b/src/Core/Data/MemberRole.php index 41b6b71..9606c0b 100755 --- a/src/Core/Data/MemberRole.php +++ b/src/Core/Data/MemberRole.php @@ -35,18 +35,18 @@ final class MemberRole { public function name(): string { switch ($this->value) { case self::ROLE_COACH: - return "Coach"; + return "COACH"; case self::ROLE_PLAYER: - return "Player"; + return "PLAYER"; } die("unreachable"); } public static function fromName(string $name): ?MemberRole { switch ($name) { - case "Coach": + case "COACH": return MemberRole::coach(); - case "Player": + case "PLAYER": return MemberRole::player(); default: return null; diff --git a/src/Core/Data/TacticInfo.php b/src/Core/Data/TacticInfo.php index 2565f93..c3b8667 100644 --- a/src/Core/Data/TacticInfo.php +++ b/src/Core/Data/TacticInfo.php @@ -7,7 +7,7 @@ class TacticInfo { private string $name; private int $creationDate; private int $ownerId; - + private CourtType $courtType; private string $content; /** @@ -15,13 +15,15 @@ class TacticInfo { * @param string $name * @param int $creationDate * @param int $ownerId + * @param CourtType $type * @param string $content */ - public function __construct(int $id, string $name, int $creationDate, int $ownerId, string $content) { + public function __construct(int $id, string $name, int $creationDate, int $ownerId, CourtType $type, string $content) { $this->id = $id; $this->name = $name; $this->ownerId = $ownerId; $this->creationDate = $creationDate; + $this->courtType = $type; $this->content = $content; } @@ -47,6 +49,10 @@ class TacticInfo { return $this->ownerId; } + public function getCourtType(): CourtType { + return $this->courtType; + } + /** * @return int */ diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index 447c7a5..abd0541 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -3,6 +3,7 @@ namespace IQBall\Core\Gateway; use IQBall\Core\Connection; +use IQBall\Core\Data\CourtType; use IQBall\Core\Data\TacticInfo; use PDO; @@ -33,7 +34,8 @@ class TacticInfoGateway { $row = $res[0]; - return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"], $row['content']); + $type = CourtType::fromName($row['court_type']); + return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"], $type, $row['content']); } @@ -57,14 +59,16 @@ class TacticInfoGateway { /** * @param string $name * @param int $owner + * @param CourtType $type * @return int inserted tactic id */ - public function insert(string $name, int $owner): int { + public function insert(string $name, int $owner, CourtType $type): int { $this->con->exec( - "INSERT INTO Tactic(name, owner) VALUES(:name, :owner)", + "INSERT INTO Tactic(name, owner, court_type) VALUES(:name, :owner, :court_type)", [ ":name" => [$name, PDO::PARAM_STR], ":owner" => [$owner, PDO::PARAM_INT], + ":court_type" => [$type->name(), PDO::PARAM_STR] ] ); return intval($this->con->lastInsertId()); diff --git a/src/Core/Model/TacticModel.php b/src/Core/Model/TacticModel.php index 136b27d..51e5eb8 100644 --- a/src/Core/Model/TacticModel.php +++ b/src/Core/Model/TacticModel.php @@ -2,6 +2,7 @@ namespace IQBall\Core\Model; +use IQBall\Core\Data\CourtType; use IQBall\Core\Data\TacticInfo; use IQBall\Core\Gateway\TacticInfoGateway; use IQBall\Core\Validation\ValidationFail; @@ -23,20 +24,22 @@ class TacticModel { * creates a new empty tactic, with given name * @param string $name * @param int $ownerId + * @param CourtType $type * @return TacticInfo */ - public function makeNew(string $name, int $ownerId): TacticInfo { - $id = $this->tactics->insert($name, $ownerId); + public function makeNew(string $name, int $ownerId, CourtType $type): TacticInfo { + $id = $this->tactics->insert($name, $ownerId, $type); return $this->tactics->get($id); } /** * creates a new empty tactic, with a default name * @param int $ownerId + * @param CourtType $type * @return TacticInfo|null */ - public function makeNewDefault(int $ownerId): ?TacticInfo { - return $this->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId); + public function makeNewDefault(int $ownerId, CourtType $type): ?TacticInfo { + return $this->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId, $type); } /** From 208d835a1f05139b7c9a4a99e275a280740022d2 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 26 Nov 2023 21:46:31 +0100 Subject: [PATCH 08/20] fix BasketCourt margins --- front/components/editor/BasketCourt.tsx | 19 +++++++++++-------- front/style/basket_court.css | 5 ++--- front/style/new_tactic_panel.css | 12 +++++++----- front/style/player.css | 5 ++--- front/style/theme/default.css | 6 +++--- front/views/Editor.tsx | 3 ++- front/views/NewTacticPanel.tsx | 3 +-- src/App/Controller/EditorController.php | 2 +- src/Core/Gateway/TacticInfoGateway.php | 2 +- 9 files changed, 30 insertions(+), 27 deletions(-) diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index 5305f6f..cd4774e 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,5 +1,5 @@ import "../../style/basket_court.css" -import { useRef } from "react" +import { RefObject, useRef } from "react" import CourtPlayer from "./CourtPlayer" import { Player } from "../../tactic/Player" @@ -7,20 +7,23 @@ export interface BasketCourtProps { players: Player[] onPlayerRemove: (p: Player) => void onPlayerChange: (p: Player) => void - courtImage: string, + courtImage: string + courtRef: RefObject } export function BasketCourt({ players, onPlayerRemove, onPlayerChange, - courtImage + courtImage, + courtRef, }: BasketCourtProps) { - const divRef = useRef(null) - return ( -
- {"court"} +
+ {"court"} {players.map((player) => { return ( onPlayerRemove(player)} - parentRef={divRef} + parentRef={courtRef} /> ) })} diff --git a/front/style/basket_court.css b/front/style/basket_court.css index 353d519..859d00f 100644 --- a/front/style/basket_court.css +++ b/front/style/basket_court.css @@ -5,13 +5,12 @@ justify-content: center; height: 100%; - padding: 20px 40px 20px 40px; - background-color: var(--main-color); } #court-svg { - height: 100%; + margin: 35px; + height: 87%; user-select: none; -webkit-user-drag: none; } diff --git a/front/style/new_tactic_panel.css b/front/style/new_tactic_panel.css index e5640b6..5f151d9 100644 --- a/front/style/new_tactic_panel.css +++ b/front/style/new_tactic_panel.css @@ -1,4 +1,3 @@ - #panel-root { width: 100%; height: 100%; @@ -49,7 +48,8 @@ transition: scale 1s; } -.court-kind-button-bottom, .court-kind-button-top { +.court-kind-button-bottom, +.court-kind-button-top { width: 100%; height: 100%; border: solid; @@ -80,7 +80,8 @@ scale: 1.1; } -.court-kind-button-top, .court-kind-button-image-div { +.court-kind-button-top, +.court-kind-button-image-div { display: flex; justify-content: center; align-items: stretch; @@ -100,7 +101,8 @@ background-color: var(--second-color); } -.court-kind-button-name, .court-kind-button-details { +.court-kind-button-name, +.court-kind-button-details { user-select: none; font-family: var(--text-main-font); } @@ -114,4 +116,4 @@ .court-kind-button:hover .court-kind-button-details { top: -20px; -} \ No newline at end of file +} diff --git a/front/style/player.css b/front/style/player.css index 7bea36e..71baddb 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -39,12 +39,11 @@ on the court. } .player-selection-tab { - display: flex; + display: none; position: absolute; margin-bottom: 10%; justify-content: center; - visibility: hidden; width: 100%; transform: translateY(-20px); @@ -67,7 +66,7 @@ on the court. } .player:focus-within .player-selection-tab { - visibility: visible; + display: flex; } .player:focus-within .player-piece { diff --git a/front/style/theme/default.css b/front/style/theme/default.css index 0baa9b9..a2894ee 100644 --- a/front/style/theme/default.css +++ b/front/style/theme/default.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400&display=swap"); :root { --main-color: #ffffff; --second-color: #e8e8e8; @@ -12,10 +12,10 @@ --selection-color: #3f7fc4; - --border-color: #FFFFFF; + --border-color: #ffffff; --editor-court-selection-background: #5f8fee; --editor-court-selection-buttons: #acc4f3; - --text-main-font: 'Roboto', sans-serif; + --text-main-font: "Roboto", sans-serif; } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 4c4c725..6b5dbd7 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -203,12 +203,13 @@ function EditorView({ />
-
+
{ setContent((content) => ({ players: toSplicedPlayers( diff --git a/front/views/NewTacticPanel.tsx b/front/views/NewTacticPanel.tsx index 0f72c37..2b7165b 100644 --- a/front/views/NewTacticPanel.tsx +++ b/front/views/NewTacticPanel.tsx @@ -44,8 +44,7 @@ function CourtKindButton({ return (
location.href = redirect} - > + onClick={() => (location.href = redirect)}>
{details}
diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 238ae83..64de063 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -27,7 +27,7 @@ class EditorController { "id" => $tactic->getId(), "name" => $tactic->getName(), "content" => $tactic->getContent(), - "courtType" => $tactic->getCourtType()->name() + "courtType" => $tactic->getCourtType()->name(), ]); } diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index abd0541..67cffc4 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -68,7 +68,7 @@ class TacticInfoGateway { [ ":name" => [$name, PDO::PARAM_STR], ":owner" => [$owner, PDO::PARAM_INT], - ":court_type" => [$type->name(), PDO::PARAM_STR] + ":court_type" => [$type->name(), PDO::PARAM_STR], ] ); return intval($this->con->lastInsertId()); From 4c684e27a8e2daba8554dee965a95fffee331ec1 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Thu, 30 Nov 2023 08:50:06 +0100 Subject: [PATCH 09/20] fix staging server --- .env | 3 ++- ci/.drone.yml | 1 + ci/build_react.msh | 2 -- front/Constants.ts | 5 +++++ front/views/Editor.tsx | 4 ++-- front/views/NewTacticPanel.tsx | 15 ++++++++------- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.env b/.env index 951db6b..98ae12d 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -VITE_API_ENDPOINT=/api \ No newline at end of file +VITE_API_ENDPOINT=/api +VITE_BASE= \ No newline at end of file diff --git a/ci/.drone.yml b/ci/.drone.yml index 8b7058d..a282766 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -36,6 +36,7 @@ steps: - chmod +x /tmp/moshell_setup.sh - echo n | /tmp/moshell_setup.sh - echo "VITE_API_ENDPOINT=/IQBall/$DRONE_BRANCH/public/api" >> .env.PROD + - echo "VITE_BASE=/IQBall/$DRONE_BRANCH/public" >> .env.PROD - - /root/.local/bin/moshell ci/build_react.msh diff --git a/ci/build_react.msh b/ci/build_react.msh index 32a5923..3d3a8f0 100755 --- a/ci/build_react.msh +++ b/ci/build_react.msh @@ -9,8 +9,6 @@ val drone_branch = std::env("DRONE_BRANCH").unwrap() val base = "/IQBall/$drone_branch/public" npm run build -- --base=$base --mode PROD -npm run build -- --base=/IQBall/public --mode PROD - // Read generated mappings from build val result = $(jq -r 'to_entries|map(.key + " " +.value.file)|.[]' dist/manifest.json) val mappings = $result.split('\n') diff --git a/front/Constants.ts b/front/Constants.ts index 76b37c2..013db50 100644 --- a/front/Constants.ts +++ b/front/Constants.ts @@ -2,3 +2,8 @@ * This constant defines the API endpoint. */ export const API = import.meta.env.VITE_API_ENDPOINT + +/** + * This constant defines the base app's endpoint. + */ +export const BASE = import.meta.env.VITE_BASE diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 6b5dbd7..3b6ad4f 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -37,14 +37,14 @@ export interface EditorViewProps { tactic: Tactic onContentChange: (tactic: TacticContent) => Promise onNameChange: (name: string) => Promise - courtType: string + courtType: "PLAIN" | "HALF" } export interface EditorProps { id: number name: string content: string - courtType: string + courtType: "PLAIN" | "HALF" } /** diff --git a/front/views/NewTacticPanel.tsx b/front/views/NewTacticPanel.tsx index 2b7165b..9733c91 100644 --- a/front/views/NewTacticPanel.tsx +++ b/front/views/NewTacticPanel.tsx @@ -3,6 +3,7 @@ import "../style/new_tactic_panel.css" import plainCourt from "../assets/court/court.svg" import halfCourt from "../assets/court/half_court.svg" +import {BASE} from "../Constants"; export default function NewTacticPanel() { return ( @@ -31,11 +32,11 @@ export default function NewTacticPanel() { } function CourtKindButton({ - name, - image, - details, - redirect, -}: { + name, + image, + details, + redirect, + }: { name: string image: string details: string @@ -44,14 +45,14 @@ function CourtKindButton({ return (
(location.href = redirect)}> + onClick={() => location.href = BASE + redirect}>
{details}
{name} + className="court-kind-button-image"/>
From 940d87930adb17917b1fe38cb19b7de8183859ba Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 4 Dec 2023 14:28:20 +0100 Subject: [PATCH 10/20] fix new tactic panel --- front/assets/court/court.svg | 4 ++-- front/assets/court/half_court.svg | 2 +- front/style/basket_court.css | 2 +- front/style/new_tactic_panel.css | 23 +++++++++++++---------- front/views/Editor.tsx | 9 +++------ front/views/NewTacticPanel.tsx | 2 +- src/App/Controller/EditorController.php | 12 ++++++++++-- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/front/assets/court/court.svg b/front/assets/court/court.svg index 476a96f..e01fd58 100644 --- a/front/assets/court/court.svg +++ b/front/assets/court/court.svg @@ -1,8 +1,8 @@ diff --git a/front/assets/court/half_court.svg b/front/assets/court/half_court.svg index 8c06d6d..8e7640e 100644 --- a/front/assets/court/half_court.svg +++ b/front/assets/court/half_court.svg @@ -1,4 +1,4 @@ - + diff --git a/front/style/basket_court.css b/front/style/basket_court.css index 859d00f..92a520c 100644 --- a/front/style/basket_court.css +++ b/front/style/basket_court.css @@ -9,7 +9,7 @@ } #court-svg { - margin: 35px; + margin: 35px 0 35px 0; height: 87%; user-select: none; -webkit-user-drag: none; diff --git a/front/style/new_tactic_panel.css b/front/style/new_tactic_panel.css index 5f151d9..ff6a07e 100644 --- a/front/style/new_tactic_panel.css +++ b/front/style/new_tactic_panel.css @@ -27,7 +27,7 @@ #panel-buttons { width: 75%; - height: 30%; + height: 20%; display: flex; justify-content: space-evenly; align-items: stretch; @@ -45,17 +45,15 @@ cursor: pointer; - transition: scale 1s; + transition: scale 0.5s ease-out; + width: auto; } .court-kind-button-bottom, .court-kind-button-top { - width: 100%; - height: 100%; border: solid; border-color: var(--border-color); } - .court-kind-button-bottom { display: flex; justify-content: center; @@ -63,14 +61,15 @@ align-content: center; height: 25%; + width: 100%; + background-color: var(--editor-court-selection-buttons); border-radius: 0 0 20px 20px; border-width: 3px; } .court-kind-button-top { - height: 75%; - overflow: hidden; + height: 30%; background-color: var(--main-color); border-radius: 20px 20px 0 0; border-width: 3px 3px 0 3px; @@ -82,21 +81,25 @@ .court-kind-button-top, .court-kind-button-image-div { + overflow: hidden; display: flex; + height: 100%; + width: 100%; justify-content: center; - align-items: stretch; - align-content: stretch; + align-items: center; + align-content: center; } .court-kind-button-image { height: 100%; + width: 150px; user-select: none; -webkit-user-drag: none; } .court-kind-button-image-div { - width: 100%; height: 100%; + padding: 0 10px 0 10px; background-color: var(--second-color); } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 3b6ad4f..14ebbf6 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -58,12 +58,9 @@ interface RackedPlayer { export default function Editor({ id, name, + courtType, content, -}: { - id: number - name: string - content: string -}) { +}: EditorProps) { const isInGuestMode = id == -1 const storage_content = localStorage.getItem(GUEST_MODE_CONTENT_STORAGE_KEY) @@ -97,7 +94,7 @@ export default function Editor({ (r) => r.ok, ) }} - /> + courtType={courtType}/> ) } diff --git a/front/views/NewTacticPanel.tsx b/front/views/NewTacticPanel.tsx index 9733c91..d3d5528 100644 --- a/front/views/NewTacticPanel.tsx +++ b/front/views/NewTacticPanel.tsx @@ -56,7 +56,7 @@ function CourtKindButton({
-

{name}

+

{name}

) diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 64de063..855d8d9 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -38,11 +38,12 @@ class EditorController { /** * @return ViewHttpResponse the editor view for a test tactic. */ - private function openTestEditor(): ViewHttpResponse { + private function openTestEditor(CourtType $courtType): ViewHttpResponse { return ViewHttpResponse::react("views/Editor.tsx", [ "id" => -1, //-1 id means that the editor will not support saves - "content" => '{"players": []}', "name" => TacticModel::TACTIC_DEFAULT_NAME, + "content" => '{"players": []}', + "courtType" => $courtType->name() ]); } @@ -55,6 +56,13 @@ class EditorController { * @return ViewHttpResponse the editor view */ public function createNewOfKind(CourtType $type, SessionHandle $session): ViewHttpResponse { + + $action = $session->getAccount(); + + if ($action == null) { + return $this->openTestEditor($type); + } + $tactic = $this->model->makeNewDefault($session->getAccount()->getId(), $type); return $this->openEditorFor($tactic); } From 7ab18786ccbc35ca158f5bce869ffaf340319258 Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Mon, 4 Dec 2023 17:38:30 +0100 Subject: [PATCH 11/20] can assign ball to player in the code (need render and destroy ball now) --- front/components/editor/BallPiece.tsx | 2 +- front/components/editor/CourtPlayer.tsx | 6 +- front/tactic/Player.ts | 2 + front/views/Editor.tsx | 126 +++++++++++------------- 4 files changed, 63 insertions(+), 73 deletions(-) diff --git a/front/components/editor/BallPiece.tsx b/front/components/editor/BallPiece.tsx index 79e8148..a99abf0 100644 --- a/front/components/editor/BallPiece.tsx +++ b/front/components/editor/BallPiece.tsx @@ -2,7 +2,7 @@ import React from "react"; import "../../style/ball.css"; -import Ball from "../../assets/icon/ball.svg"; +import Ball from "../../assets/icon/ball.svg?react"; export function BallPiece() { return ( diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 6431a50..9ca517a 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -21,7 +21,6 @@ export default function CourtPlayer({ player, onChange, onRemove, - assignBall, parentRef, }: PlayerProps) { const pieceRef = useRef(null) @@ -42,11 +41,12 @@ export default function CourtPlayer({ const { x, y } = calculateRatio(pieceBounds, parentBounds) onChange({ + id : player.id, rightRatio: x, bottomRatio: y, team: player.team, role: player.role, - hasBall: false + hasBall: player.hasBall }) }}>
-
{ diff --git a/front/tactic/Player.ts b/front/tactic/Player.ts index 13d79fe..32ed02d 100644 --- a/front/tactic/Player.ts +++ b/front/tactic/Player.ts @@ -1,6 +1,8 @@ import { Team } from "./Team" export interface Player { + + id : string /** * the player's team * */ diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index c3f1a29..40cb9ea 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -8,22 +8,23 @@ import { } from "react" import "../style/editor.css" import TitleInput from "../components/TitleInput" -import { BasketCourt } from "../components/editor/BasketCourt" - - -import { BallPiece } from "../components/editor/BallPiece"; -import { Ball } from "../tactic/Ball"; -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" -import { calculateRatio } from "../Utils" +import {BasketCourt} from "../components/editor/BasketCourt" + + +import {BallPiece} from "../components/editor/BallPiece"; +import {Ball} from "../tactic/Ball"; +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" +import {calculateRatio} from "../Utils" import SavingState, { SaveState, SaveStates, } from "../components/editor/SavingState" +import Draggable from "react-draggable"; const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -47,10 +48,10 @@ interface RackedPlayer { } export default function Editor({ - id, - name, - content, -}: { + id, + name, + content, + }: { id: number name: string content: string @@ -66,7 +67,7 @@ export default function Editor({ return ( { if (isInGuestMode) { localStorage.setItem( @@ -75,7 +76,7 @@ export default function Editor({ ) return SaveStates.Guest } - return fetchAPI(`tactic/${id}/save`, { content }).then((r) => + return fetchAPI(`tactic/${id}/save`, {content}).then((r) => r.ok ? SaveStates.Ok : SaveStates.Err, ) }} @@ -84,7 +85,7 @@ export default function Editor({ localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name) return true //simulate that the name has been changed } - return fetchAPI(`tactic/${id}/edit/name`, { name }).then( + return fetchAPI(`tactic/${id}/edit/name`, {name}).then( (r) => r.ok, ) }} @@ -94,10 +95,10 @@ export default function Editor({ function EditorView({ - tactic: { id, name, content: initialContent }, - onContentChange, - onNameChange, -}: EditorViewProps) { + tactic: {id, name, content: initialContent}, + onContentChange, + onNameChange, + }: EditorViewProps) { const isInGuestMode = id == -1 const [style, setStyle] = useState({}) @@ -113,8 +114,7 @@ function EditorView({ getRackPlayers(Team.Opponents, content.players), ) - - const [ball, setBall] = useState([]); + const ballPiece = useRef(null) const courtDivContentRef = useRef(null) @@ -136,13 +136,14 @@ function EditorView({ const refBounds = ref.getBoundingClientRect() const courtBounds = courtDivContentRef.current!.getBoundingClientRect() - const { x, y } = calculateRatio(refBounds, courtBounds) + const {x, y} = calculateRatio(refBounds, courtBounds) setContent((content) => { return { players: [ ...content.players, { + id: "player-" + content.players.length, team: element.team, role: element.key, rightRatio: x, @@ -154,37 +155,22 @@ function EditorView({ }) } - const canDetachBall = (ref: HTMLDivElement) => { - const refBounds = ref.getBoundingClientRect(); - const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); - - //check if we give the ball to a player on the court - if (!canDetach(ref)) { - return false; + const onElementDetachBall = () => { + const ballBounds = ballPiece.current!.getBoundingClientRect() + + for (const player of content.players) { + const playerBounds = document.getElementById(player.id)!.getBoundingClientRect() + const doesNotOverlap = ( + ballBounds.top > playerBounds.bottom || + ballBounds.right < playerBounds.left || + ballBounds.bottom < playerBounds.top || + ballBounds.left > playerBounds.right + ) + if (doesNotOverlap) { + continue + } + player.hasBall = true } - /*for(const player in players) { - const rightRatio = player - }*/ - return false; - } - - const onElementDetachBall = (ref: HTMLDivElement) => { - const refBounds = ref.getBoundingClientRect() - const courtBounds = courtDivContentRef.current!.getBoundingClientRect(); - - const relativeXPixels = refBounds.x - courtBounds.x; - const relativeYPixels = refBounds.y - courtBounds.y; - - const xPercent = relativeXPixels / courtBounds.width; - const yPercent = relativeYPixels / courtBounds.height; - - - setBall(ball => { - return [...ball, { - right_percentage: xPercent, - bottom_percentage: yPercent, - }] - }) } @@ -193,7 +179,7 @@ function EditorView({
LEFT - +
( - + render={({team, key}) => ( + )} /> - }/> + + +
+ +
+
+ ( - + render={({team, key}) => ( + )} />
@@ -291,7 +279,7 @@ function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] { players.findIndex((p) => p.team == team && p.role == role) == -1, ) - .map((key) => ({ team, key })) + .map((key) => ({team, key})) } function useContentState( From 7177c07ca049a391b8854ebce67bfd868afa97de Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Tue, 5 Dec 2023 18:02:26 +0100 Subject: [PATCH 12/20] can assign ball to a player, player with ball is surrounded --- front/components/editor/BallPiece.tsx | 23 ++++++++--- front/components/editor/BasketCourt.tsx | 3 ++ front/components/editor/CourtPlayer.tsx | 10 ++++- front/components/editor/PlayerPiece.tsx | 15 ++++++- front/style/ball.css | 6 ++- front/style/player.css | 9 ++-- front/views/Editor.tsx | 55 ++++++++++++++----------- 7 files changed, 81 insertions(+), 40 deletions(-) diff --git a/front/components/editor/BallPiece.tsx b/front/components/editor/BallPiece.tsx index a99abf0..054f83c 100644 --- a/front/components/editor/BallPiece.tsx +++ b/front/components/editor/BallPiece.tsx @@ -1,13 +1,26 @@ -import React from "react"; +import React, {RefObject} from "react"; import "../../style/ball.css"; import Ball from "../../assets/icon/ball.svg?react"; +import Draggable from "react-draggable"; -export function BallPiece() { +export interface BallPieceProps { + onDrop: () => void + pieceRef: RefObject +} + + +export function BallPiece({onDrop, pieceRef}: BallPieceProps) { return ( -
- -
+ +
+ +
+
) } \ No newline at end of file diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index b583f61..c15f02c 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -8,12 +8,14 @@ import { Player } from "../../tactic/Player" export interface BasketCourtProps { players: Player[] onPlayerRemove: (p: Player) => void + onBallDrop: (ref : HTMLDivElement) => void onPlayerChange: (p: Player) => void } export function BasketCourt({ players, onPlayerRemove, + onBallDrop, onPlayerChange, }: BasketCourtProps) { const divRef = useRef(null) @@ -28,6 +30,7 @@ export function BasketCourt({ player={player} onChange={onPlayerChange} onRemove={() => onPlayerRemove(player)} + onBallDrop={onBallDrop} parentRef={divRef} /> ) diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 9ca517a..1d4961a 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,7 +1,7 @@ import { RefObject, useRef, useState } from "react" import "../../style/player.css" import RemoveIcon from "../../assets/icon/remove.svg?react" -import BallIcon from "../../assets/icon/ball.svg?react" +import {BallPiece} from "./BallPiece"; import Draggable from "react-draggable" import { PlayerPiece } from "./PlayerPiece" import { Player } from "../../tactic/Player" @@ -11,6 +11,7 @@ export interface PlayerProps { player: Player onChange: (p: Player) => void onRemove: () => void + onBallDrop: (ref: HTMLDivElement) => void parentRef: RefObject } @@ -21,12 +22,15 @@ export default function CourtPlayer({ player, onChange, onRemove, + onBallDrop, parentRef, }: PlayerProps) { const pieceRef = useRef(null) + const ballPiece = useRef(null) const x = player.rightRatio const y = player.bottomRatio + const hasBall = player.hasBall return ( + {hasBall && onBallDrop(ballPiece.current!)} pieceRef={ballPiece}/>}
- +
diff --git a/front/components/editor/PlayerPiece.tsx b/front/components/editor/PlayerPiece.tsx index 69b38c2..5756afb 100644 --- a/front/components/editor/PlayerPiece.tsx +++ b/front/components/editor/PlayerPiece.tsx @@ -2,9 +2,20 @@ import React from "react" import "../../style/player.css" import { Team } from "../../tactic/Team" -export function PlayerPiece({ team, text }: { team: Team; text: string }) { +export interface PlayerPieceProps { + team: Team + text: string + hasBall : boolean +} + +export function PlayerPiece({ team, text, hasBall}: PlayerPieceProps) { + let className = `player-piece ${team}` + if (hasBall) { + className += ` player-piece-has-ball` + } + return ( -
+

{text}

) diff --git a/front/style/ball.css b/front/style/ball.css index 5669b07..d79ac89 100644 --- a/front/style/ball.css +++ b/front/style/ball.css @@ -1,8 +1,10 @@ -#ball * { +.ball * { fill: #c5520d; } -#ball { +.ball { + pointer-events: all; width: 20px; height: 20px; + cursor: pointer; } diff --git a/front/style/player.css b/front/style/player.css index 166b449..be468e6 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -24,10 +24,6 @@ on the court. color: var(--selected-team-secondarycolor); border-radius: 100px; - /* - border-width: 2px; - border-style: solid; - */ width: 20px; height: 20px; @@ -40,6 +36,11 @@ on the court. user-select: none; } +.player-piece-has-ball { + border-width: 2px; + border-style: solid; +} + .player-selection-tab { display: flex; diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 40cb9ea..0923a67 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -114,6 +114,8 @@ function EditorView({ getRackPlayers(Team.Opponents, content.players), ) + const [showBall, setShowBall] = useState(true) + const ballPiece = useRef(null) const courtDivContentRef = useRef(null) @@ -155,22 +157,29 @@ function EditorView({ }) } - const onElementDetachBall = () => { - const ballBounds = ballPiece.current!.getBoundingClientRect() - - for (const player of content.players) { - const playerBounds = document.getElementById(player.id)!.getBoundingClientRect() - const doesNotOverlap = ( - ballBounds.top > playerBounds.bottom || - ballBounds.right < playerBounds.left || - ballBounds.bottom < playerBounds.top || - ballBounds.left > playerBounds.right - ) - if (doesNotOverlap) { - continue - } - player.hasBall = true - } + const onBallDrop = (ref : HTMLDivElement) => { + const ballBounds = ref.getBoundingClientRect() + let showBall = true + + setContent(content => { + const players = content.players.map(player => { + const playerBounds = document.getElementById(player.id)!.getBoundingClientRect() + const doesOverlap = !( + ballBounds.top > playerBounds.bottom || + ballBounds.right < playerBounds.left || + ballBounds.bottom < playerBounds.top || + ballBounds.left > playerBounds.right + ) + if(doesOverlap) { + showBall = false + + } + + return {...player, hasBall: doesOverlap} + }) + setShowBall(showBall) + return {players: players} + }) } @@ -203,17 +212,11 @@ function EditorView({ canDetach={canDetach} onElementDetached={onPieceDetach} render={({team, key}) => ( - + )} /> - -
- -
-
+ {showBall && onBallDrop(ballPiece.current!)} pieceRef={ballPiece}/>} ( - + )} />
@@ -230,6 +233,7 @@ function EditorView({
{ setContent((content) => ({ players: toSplicedPlayers( @@ -264,6 +268,7 @@ function EditorView({ }, ]) }} + />
From f0faa9b2d2ce8ffe209f19fc1cc014b9b5df3167 Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 5 Dec 2023 18:07:49 +0100 Subject: [PATCH 13/20] deconnection and fix connection --- front/views/Editor.tsx | 19 +++++----- front/views/NewTacticPanel.tsx | 17 +++++---- public/index.php | 2 + src/App/Controller/EditorController.php | 2 +- src/App/Controller/UserController.php | 7 ++++ src/App/Session/MutableSessionHandle.php | 2 + src/App/Session/PhpSessionHandle.php | 4 ++ src/App/Views/display_login.html.twig | 47 ++++++++++++++++-------- src/App/Views/home.twig | 1 + 9 files changed, 67 insertions(+), 34 deletions(-) diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 14ebbf6..99e5177 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -55,12 +55,7 @@ interface RackedPlayer { key: string } -export default function Editor({ - id, - name, - courtType, - content, -}: EditorProps) { +export default function Editor({ id, name, courtType, content }: EditorProps) { const isInGuestMode = id == -1 const storage_content = localStorage.getItem(GUEST_MODE_CONTENT_STORAGE_KEY) @@ -68,11 +63,16 @@ export default function Editor({ isInGuestMode && storage_content != null ? storage_content : content const storage_name = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) - const editorName = isInGuestMode && storage_name != null ? storage_name : name + const editorName = + isInGuestMode && storage_name != null ? storage_name : name return ( { if (isInGuestMode) { localStorage.setItem( @@ -94,7 +94,8 @@ export default function Editor({ (r) => r.ok, ) }} - courtType={courtType}/> + courtType={courtType} + /> ) } diff --git a/front/views/NewTacticPanel.tsx b/front/views/NewTacticPanel.tsx index d3d5528..bd9badb 100644 --- a/front/views/NewTacticPanel.tsx +++ b/front/views/NewTacticPanel.tsx @@ -3,7 +3,7 @@ import "../style/new_tactic_panel.css" import plainCourt from "../assets/court/court.svg" import halfCourt from "../assets/court/half_court.svg" -import {BASE} from "../Constants"; +import { BASE } from "../Constants" export default function NewTacticPanel() { return ( @@ -32,11 +32,11 @@ export default function NewTacticPanel() { } function CourtKindButton({ - name, - image, - details, - redirect, - }: { + name, + image, + details, + redirect, +}: { name: string image: string details: string @@ -45,14 +45,15 @@ function CourtKindButton({ return (
location.href = BASE + redirect}> + onClick={() => (location.href = BASE + redirect)}>
{details}
{name} + className="court-kind-button-image" + />
diff --git a/public/index.php b/public/index.php index cd777ab..8c9a62b 100644 --- a/public/index.php +++ b/public/index.php @@ -85,6 +85,8 @@ function getRoutes(): AltoRouter { $ar->map("GET", "/", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); $ar->map("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); $ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); + $ar->map("GET", "/disconnect", Action::auth(fn(MutableSessionHandle $s) => getUserController()->disconnect($s))); + //tactic-related $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s))); diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 855d8d9..3bbbe61 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -43,7 +43,7 @@ class EditorController { "id" => -1, //-1 id means that the editor will not support saves "name" => TacticModel::TACTIC_DEFAULT_NAME, "content" => '{"players": []}', - "courtType" => $courtType->name() + "courtType" => $courtType->name(), ]); } diff --git a/src/App/Controller/UserController.php b/src/App/Controller/UserController.php index d6f9f89..5ce1318 100644 --- a/src/App/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -2,8 +2,10 @@ namespace IQBall\App\Controller; +use IQBall\App\Session\MutableSessionHandle; use IQBall\App\Session\SessionHandle; use IQBall\App\ViewHttpResponse; +use IQBall\Core\Http\HttpResponse; use IQBall\Core\Model\TacticModel; class UserController { @@ -33,4 +35,9 @@ class UserController { return ViewHttpResponse::twig("account_settings.twig", []); } + public function disconnect(MutableSessionHandle $session): HttpResponse { + $session->destroy(); + return HttpResponse::redirect("/"); + } + } diff --git a/src/App/Session/MutableSessionHandle.php b/src/App/Session/MutableSessionHandle.php index 9ef23c0..14871b6 100644 --- a/src/App/Session/MutableSessionHandle.php +++ b/src/App/Session/MutableSessionHandle.php @@ -17,4 +17,6 @@ interface MutableSessionHandle extends SessionHandle { * @param Account $account update the session's account */ public function setAccount(Account $account): void; + + public function destroy(): void; } diff --git a/src/App/Session/PhpSessionHandle.php b/src/App/Session/PhpSessionHandle.php index 9a08e47..cea20a6 100644 --- a/src/App/Session/PhpSessionHandle.php +++ b/src/App/Session/PhpSessionHandle.php @@ -31,4 +31,8 @@ class PhpSessionHandle implements MutableSessionHandle { public function setInitialTarget(?string $url): void { $_SESSION["target"] = $url; } + + public function destroy(): void { + session_destroy(); + } } diff --git a/src/App/Views/display_login.html.twig b/src/App/Views/display_login.html.twig index cdc11a5..8b61da1 100644 --- a/src/App/Views/display_login.html.twig +++ b/src/App/Views/display_login.html.twig @@ -40,18 +40,6 @@ border-radius: 5px; } - input[type="submit"] { - background-color: #007bff; - color: #fff; - padding: 10px 20px; - border: none; - border-radius: 5px; - cursor: pointer; - } - - input[type="submit"]:hover { - background-color: #0056b3; - } .error-messages { color: #ff331a; @@ -68,6 +56,32 @@ ; } {% endfor %} + + .inscr{ + font-size: small; + text-align: right; + } + + #buttons{ + display: flex; + justify-content: space-between; + padding: 10px 20px; + + } + + .button{ + background-color: #007bff; + color: #fff; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + } + + .button:hover{ + background-color: #0056b3; + } +
@@ -82,11 +96,12 @@ - - +

+

Vous n'avez pas de compte ?

-
- +
+ +
diff --git a/src/App/Views/home.twig b/src/App/Views/home.twig index 5d9e8ae..7d8430e 100644 --- a/src/App/Views/home.twig +++ b/src/App/Views/home.twig @@ -50,6 +50,7 @@ +

IQ Ball

From b653ff4ea299f465d2ffe15cc9d58b43d4860453 Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Tue, 5 Dec 2023 18:25:53 +0100 Subject: [PATCH 14/20] fix collision problem where more than 1 player can have the ball --- front/style/player.css | 6 ++++-- front/views/Editor.tsx | 14 +++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/front/style/player.css b/front/style/player.css index be468e6..facdaa2 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -44,8 +44,8 @@ on the court. .player-selection-tab { display: flex; - position: absolute; - margin-bottom: 10%; + position: relative; + margin-bottom: -20%; justify-content: center; visibility: hidden; @@ -56,6 +56,8 @@ on the court. .player-selection-tab-remove { pointer-events: all; height: 25%; + width: 25%; + justify-content: center; } .player-selection-tab-remove * { diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 0923a67..17276c0 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -159,10 +159,13 @@ function EditorView({ const onBallDrop = (ref : HTMLDivElement) => { const ballBounds = ref.getBoundingClientRect() - let showBall = true + let ballAssigned = false setContent(content => { const players = content.players.map(player => { + if(ballAssigned) { + return {...player, hasBall: false} + } const playerBounds = document.getElementById(player.id)!.getBoundingClientRect() const doesOverlap = !( ballBounds.top > playerBounds.bottom || @@ -171,13 +174,11 @@ function EditorView({ ballBounds.left > playerBounds.right ) if(doesOverlap) { - showBall = false - + ballAssigned = true } - return {...player, hasBall: doesOverlap} }) - setShowBall(showBall) + setShowBall(!ballAssigned) return {players: players} }) } @@ -259,6 +260,9 @@ function EditorView({ case Team.Allies: setter = setAllies } + if(player.hasBall) { + setShowBall(true) + } setter((players) => [ ...players, { From b81346e31c01dff992c288a26303bf558b6dd715 Mon Sep 17 00:00:00 2001 From: samuel Date: Tue, 5 Dec 2023 18:27:26 +0100 Subject: [PATCH 15/20] add login from register --- src/App/Views/display_login.html.twig | 2 +- src/App/Views/display_register.html.twig | 44 ++++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/App/Views/display_login.html.twig b/src/App/Views/display_login.html.twig index 8b61da1..a9fe349 100644 --- a/src/App/Views/display_login.html.twig +++ b/src/App/Views/display_login.html.twig @@ -96,7 +96,7 @@ -

+

Vous n'avez pas de compte ?

diff --git a/src/App/Views/display_register.html.twig b/src/App/Views/display_register.html.twig index 8649de8..bd17ec6 100644 --- a/src/App/Views/display_register.html.twig +++ b/src/App/Views/display_register.html.twig @@ -40,23 +40,11 @@ border-radius: 5px; } - input[type="submit"] { - background-color: #007bff; - color: #fff; - padding: 10px 20px; - border: none; - border-radius: 5px; - cursor: pointer; - } - .error-messages { color: #ff331a; font-style: italic; } - input[type="submit"]:hover { - background-color: #0056b3; - } {% for err in fails %} .form-group @@ -69,6 +57,31 @@ } {% endfor %} + .inscr{ + font-size: small; + text-align: right; + } + + #buttons{ + display: flex; + justify-content: space-between; + padding: 10px 20px; + + } + + .button{ + background-color: #007bff; + color: #fff; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + } + + .button:hover{ + background-color: #0056b3; + } + @@ -87,12 +100,13 @@ - +

Vous avez déja un compte ?

-
- +
+ +
From 31fad085ebeb32d1e7c2de00052fe55ddfd35410 Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Tue, 5 Dec 2023 18:47:42 +0100 Subject: [PATCH 16/20] fix css --- front/style/ball.css | 2 +- front/style/colors.css | 2 ++ front/style/player.css | 9 +++++---- front/views/Editor.tsx | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/front/style/ball.css b/front/style/ball.css index d79ac89..5bf15d1 100644 --- a/front/style/ball.css +++ b/front/style/ball.css @@ -2,7 +2,7 @@ fill: #c5520d; } -.ball { +.ball-div, .ball { pointer-events: all; width: 20px; height: 20px; diff --git a/front/style/colors.css b/front/style/colors.css index 1ab3f01..53a9f65 100644 --- a/front/style/colors.css +++ b/front/style/colors.css @@ -8,4 +8,6 @@ --selected-team-secondarycolor: #000000; --selection-color: #3f7fc4; + + --player-piece-ball-border-color: #000000; } diff --git a/front/style/player.css b/front/style/player.css index facdaa2..d79cf46 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -39,24 +39,25 @@ on the court. .player-piece-has-ball { border-width: 2px; border-style: solid; + border-color: var(--player-piece-ball-border-color); } .player-selection-tab { display: flex; - position: relative; + position: absolute; margin-bottom: -20%; justify-content: center; visibility: hidden; - width: 100%; + width: fit-content; transform: translateY(-20px); } .player-selection-tab-remove { pointer-events: all; - height: 25%; - width: 25%; + width: 25px; + height: 17px; justify-content: center; } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 17276c0..19b8a15 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -114,7 +114,7 @@ function EditorView({ getRackPlayers(Team.Opponents, content.players), ) - const [showBall, setShowBall] = useState(true) + const [showBall, setShowBall] = useState(content.players.find(p => p.hasBall) == undefined) const ballPiece = useRef(null) From ee21fb79aade8d87a16598e0fd06387ce2f3488b Mon Sep 17 00:00:00 2001 From: Override-6 Date: Wed, 6 Dec 2023 08:30:27 +0100 Subject: [PATCH 17/20] remove rm button, remove LEFT/RIGHT indicators --- front/style/colors.css | 13 ------------- front/style/editor.css | 6 +++++- front/style/player.css | 1 + front/style/theme/default.css | 2 ++ front/views/Editor.tsx | 4 ++-- 5 files changed, 10 insertions(+), 16 deletions(-) delete mode 100644 front/style/colors.css diff --git a/front/style/colors.css b/front/style/colors.css deleted file mode 100644 index 53a9f65..0000000 --- a/front/style/colors.css +++ /dev/null @@ -1,13 +0,0 @@ -:root { - --main-color: #ffffff; - --second-color: #ccde54; - - --background-color: #d2cdd3; - - --selected-team-primarycolor: #50b63a; - --selected-team-secondarycolor: #000000; - - --selection-color: #3f7fc4; - - --player-piece-ball-border-color: #000000; -} diff --git a/front/style/editor.css b/front/style/editor.css index 45ce43d..8f0adf4 100644 --- a/front/style/editor.css +++ b/front/style/editor.css @@ -49,7 +49,11 @@ } .player-piece.opponents { - background-color: #f59264; + background-color: var(--player-opponents-color); +} + +.player-piece.allies { + background-color: var(--player-allies-color); } #court-div { diff --git a/front/style/player.css b/front/style/player.css index 6fa31a9..81a6b7e 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -54,6 +54,7 @@ on the court. } .player-selection-tab-remove { + visibility: hidden; pointer-events: all; width: 25px; height: 17px; diff --git a/front/style/theme/default.css b/front/style/theme/default.css index a2894ee..12c4452 100644 --- a/front/style/theme/default.css +++ b/front/style/theme/default.css @@ -7,6 +7,8 @@ --selected-team-primarycolor: #ffffff; --selected-team-secondarycolor: #000000; + --player-allies-color: #64e4f5; + --player-opponents-color: #f59264; --buttons-shadow-color: #a8a8a8; diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 213607f..5c10f64 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -198,7 +198,7 @@ function EditorView({
- LEFT +
@@ -212,7 +212,7 @@ function EditorView({ }} />
-
RIGHT
+
From d3a103f95a3da80eb6880c5d1a201d0907d0a3e1 Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 11 Dec 2023 15:49:07 +0100 Subject: [PATCH 18/20] fix bugs and requests of PO : less informations for login --- src/App/Controller/AuthController.php | 8 -------- src/App/Views/display_login.html.twig | 9 ++++----- src/App/Views/display_register.html.twig | 8 ++++---- src/Core/Model/AuthModel.php | 10 ++-------- 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/App/Controller/AuthController.php b/src/App/Controller/AuthController.php index dfc5f52..cd89d11 100644 --- a/src/App/Controller/AuthController.php +++ b/src/App/Controller/AuthController.php @@ -64,14 +64,6 @@ class AuthController { */ public function login(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; - $request = HttpRequest::from($request, $fails, [ - "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::email(), Validators::lenBetween(5, 256)], - ]); - if (!empty($fails)) { - return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]); - } - $account = $this->model->login($request['email'], $request['password'], $fails); if (!empty($fails)) { return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]); diff --git a/src/App/Views/display_login.html.twig b/src/App/Views/display_login.html.twig index a9fe349..8f10529 100644 --- a/src/App/Views/display_login.html.twig +++ b/src/App/Views/display_login.html.twig @@ -64,7 +64,7 @@ #buttons{ display: flex; - justify-content: space-between; + justify-content: center; padding: 10px 20px; } @@ -90,18 +90,17 @@
{% for name in fails %} - + {% endfor %} -

Vous n'avez pas de compte ?

+ Vous n'avez pas de compte ?
- - +
diff --git a/src/App/Views/display_register.html.twig b/src/App/Views/display_register.html.twig index bd17ec6..38bdb43 100644 --- a/src/App/Views/display_register.html.twig +++ b/src/App/Views/display_register.html.twig @@ -64,7 +64,7 @@ #buttons{ display: flex; - justify-content: space-between; + justify-content: center; padding: 10px 20px; } @@ -102,11 +102,11 @@ -

Vous avez déja un compte ?

+ Vous avez déjà un compte ? +
- - +
diff --git a/src/Core/Model/AuthModel.php b/src/Core/Model/AuthModel.php index a6ada53..e8710c0 100644 --- a/src/Core/Model/AuthModel.php +++ b/src/Core/Model/AuthModel.php @@ -63,16 +63,10 @@ class AuthModel { */ public function login(string $email, string $password, array &$failures): ?Account { $hash = $this->gateway->getHash($email); - if ($hash == null) { - $failures[] = new FieldValidationFail("email", "l'addresse email n'est pas connue."); + if ($hash == null or (!password_verify($password, $hash))) { + $failures[] = new ValidationFail("email","Adresse email ou mot de passe invalide"); return null; } - - if (!password_verify($password, $hash)) { - $failures[] = new FieldValidationFail("password", "Mot de passe invalide."); - return null; - } - return $this->gateway->getAccountFromMail($email); } From 0b45c761d0283002601d8b9833d5c0bb31af74a5 Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 11 Dec 2023 16:56:20 +0100 Subject: [PATCH 19/20] fix the view --- src/App/Views/display_login.html.twig | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/App/Views/display_login.html.twig b/src/App/Views/display_login.html.twig index 8f10529..c609537 100644 --- a/src/App/Views/display_login.html.twig +++ b/src/App/Views/display_login.html.twig @@ -57,9 +57,8 @@ } {% endfor %} - .inscr{ + .inscr { font-size: small; - text-align: right; } #buttons{ @@ -98,13 +97,11 @@ Vous n'avez pas de compte ? -
-
- -
+

+
+ +
- - \ No newline at end of file From 165c754fa5a60496da4dc068745559d6200762c6 Mon Sep 17 00:00:00 2001 From: maxime Date: Sun, 17 Dec 2023 01:14:31 +0100 Subject: [PATCH 20/20] fix new server configuration --- ci/deploy.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ci/deploy.sh b/ci/deploy.sh index b870658..19aa768 100644 --- a/ci/deploy.sh +++ b/ci/deploy.sh @@ -4,6 +4,9 @@ mkdir ~/.ssh echo "$SERVER_PRIVATE_KEY" > ~/.ssh/id_rsa chmod 0600 ~/.ssh chmod 0500 ~/.ssh/id_rsa* -ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev mkdir -p /server/nginx/IQBall/$DRONE_BRANCH -rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH -ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev "chmod 770 /server/nginx/IQBall/$DRONE_BRANCH; chgrp www-data /server/nginx/IQBall/$DRONE_BRANCH" \ No newline at end of file + +SERVER_ROOT=/srv/www/IQBall + +ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev mkdir -p $SERVER_ROOT/$DRONE_BRANCH +rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:$SERVER_ROOT/$DRONE_BRANCH +ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev "chmod 777 $SERVER_ROOT/$DRONE_BRANCH" \ No newline at end of file