Merge branch 'master' of https://codefirst.iut.uca.fr/git/IQBall/Application-Web into connexion/bootstrap
commit
e0e57b4d65
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
$finder = (new PhpCsFixer\Finder())->in(__DIR__);
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@PER-CS' => true,
|
||||
'@PHP74Migration' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'braces_position' => [
|
||||
'classes_opening_brace' => 'same_line',
|
||||
'functions_opening_brace' => 'same_line'
|
||||
]
|
||||
])
|
||||
->setIndent(" ")
|
||||
->setFinder($finder);
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"bracketSameLine": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 4,
|
||||
"semi": false
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
@startuml
|
||||
|
||||
class Connexion
|
||||
|
||||
class Modele
|
||||
|
||||
class Account
|
||||
|
||||
class AccountGateway
|
||||
|
||||
|
||||
@enduml
|
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## verify php and typescript types
|
||||
|
||||
echo "formatting php typechecking"
|
||||
vendor/bin/php-cs-fixer fix
|
||||
|
||||
echo "formatting typescript typechecking"
|
||||
npm run format
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
* This constant defines the API endpoint.
|
||||
*/
|
||||
export const API = import.meta.env.VITE_API_ENDPOINT;
|
||||
export const API = import.meta.env.VITE_API_ENDPOINT
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 427 B |
@ -0,0 +1,72 @@
|
||||
import { ReactElement, useRef } from "react"
|
||||
import Draggable from "react-draggable"
|
||||
|
||||
export interface RackProps<E extends { key: string | number }> {
|
||||
id: string
|
||||
objects: E[]
|
||||
onChange: (objects: E[]) => void
|
||||
canDetach: (ref: HTMLDivElement) => boolean
|
||||
onElementDetached: (ref: HTMLDivElement, el: E) => void
|
||||
render: (e: E) => ReactElement
|
||||
}
|
||||
|
||||
interface RackItemProps<E extends { key: string | number }> {
|
||||
item: E
|
||||
onTryDetach: (ref: HTMLDivElement, el: E) => void
|
||||
render: (e: E) => ReactElement
|
||||
}
|
||||
|
||||
/**
|
||||
* A container of draggable objects
|
||||
* */
|
||||
export function Rack<E extends { key: string | number }>({
|
||||
id,
|
||||
objects,
|
||||
onChange,
|
||||
canDetach,
|
||||
onElementDetached,
|
||||
render,
|
||||
}: RackProps<E>) {
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
style={{
|
||||
display: "flex",
|
||||
}}>
|
||||
{objects.map((element) => (
|
||||
<RackItem
|
||||
key={element.key}
|
||||
item={element}
|
||||
render={render}
|
||||
onTryDetach={(ref, element) => {
|
||||
if (!canDetach(ref)) return
|
||||
|
||||
const index = objects.findIndex(
|
||||
(o) => o.key === element.key,
|
||||
)
|
||||
onChange(objects.toSpliced(index, 1))
|
||||
|
||||
onElementDetached(ref, element)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RackItem<E extends { key: string | number }>({
|
||||
item,
|
||||
onTryDetach,
|
||||
render,
|
||||
}: RackItemProps<E>) {
|
||||
const divRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
position={{ x: 0, y: 0 }}
|
||||
nodeRef={divRef}
|
||||
onStop={() => onTryDetach(divRef.current!, item)}>
|
||||
<div ref={divRef}>{render(item)}</div>
|
||||
</Draggable>
|
||||
)
|
||||
}
|
@ -1,28 +1,32 @@
|
||||
import React, {CSSProperties, useRef, useState} from "react";
|
||||
import "../style/title_input.css";
|
||||
import React, { CSSProperties, useRef, useState } from "react"
|
||||
import "../style/title_input.css"
|
||||
|
||||
export interface TitleInputOptions {
|
||||
style: CSSProperties,
|
||||
default_value: string,
|
||||
style: CSSProperties
|
||||
default_value: string
|
||||
on_validated: (a: string) => void
|
||||
}
|
||||
|
||||
export default function TitleInput({style, default_value, on_validated}: TitleInputOptions) {
|
||||
const [value, setValue] = useState(default_value);
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
export default function TitleInput({
|
||||
style,
|
||||
default_value,
|
||||
on_validated,
|
||||
}: TitleInputOptions) {
|
||||
const [value, setValue] = useState(default_value)
|
||||
const ref = useRef<HTMLInputElement>(null)
|
||||
|
||||
return (
|
||||
<input className="title_input"
|
||||
ref={ref}
|
||||
style={style}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={event => setValue(event.target.value)}
|
||||
onBlur={_ => on_validated(value)}
|
||||
onKeyDown={event => {
|
||||
if (event.key == 'Enter')
|
||||
ref.current?.blur();
|
||||
}}
|
||||
<input
|
||||
className="title_input"
|
||||
ref={ref}
|
||||
style={style}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
onBlur={(_) => on_validated(value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key == "Enter") ref.current?.blur()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import CourtSvg from "../../assets/basketball_court.svg?react"
|
||||
import "../../style/basket_court.css"
|
||||
import { useRef } from "react"
|
||||
import CourtPlayer from "./CourtPlayer"
|
||||
import { Player } from "../../data/Player"
|
||||
|
||||
export interface BasketCourtProps {
|
||||
players: Player[]
|
||||
onPlayerRemove: (p: Player) => void
|
||||
}
|
||||
|
||||
export function BasketCourt({ players, onPlayerRemove }: BasketCourtProps) {
|
||||
return (
|
||||
<div id="court-container" style={{ position: "relative" }}>
|
||||
<CourtSvg id="court-svg" />
|
||||
{players.map((player) => {
|
||||
return (
|
||||
<CourtPlayer
|
||||
key={player.id}
|
||||
player={player}
|
||||
onRemove={() => onPlayerRemove(player)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { useRef } from "react"
|
||||
import "../../style/player.css"
|
||||
import RemoveIcon from "../../assets/icon/remove.svg?react"
|
||||
import Draggable from "react-draggable"
|
||||
import { PlayerPiece } from "./PlayerPiece"
|
||||
import { Player } from "../../data/Player"
|
||||
|
||||
export interface PlayerProps {
|
||||
player: Player
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* A player that is placed on the court, which can be selected, and moved in the associated bounds
|
||||
* */
|
||||
export default function CourtPlayer({ player, onRemove }: PlayerProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const x = player.rightRatio
|
||||
const y = player.bottomRatio
|
||||
|
||||
return (
|
||||
<Draggable handle={".player-piece"} nodeRef={ref} bounds="parent">
|
||||
<div
|
||||
ref={ref}
|
||||
className={"player"}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: `${x * 100}%`,
|
||||
top: `${y * 100}%`,
|
||||
}}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
className="player-content"
|
||||
onKeyUp={(e) => {
|
||||
if (e.key == "Delete") onRemove()
|
||||
}}>
|
||||
<div className="player-selection-tab">
|
||||
<RemoveIcon
|
||||
className="player-selection-tab-remove"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
</div>
|
||||
<PlayerPiece team={player.team} text={player.role} />
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import React from "react"
|
||||
import "../../style/player.css"
|
||||
import { Team } from "../../data/Team"
|
||||
|
||||
export function PlayerPiece({ team, text }: { team: Team; text: string }) {
|
||||
return (
|
||||
<div className={`player-piece ${team}`}>
|
||||
<p>{text}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { Team } from "./Team"
|
||||
|
||||
export interface Player {
|
||||
/**
|
||||
* unique identifier of the player.
|
||||
* This identifier must be unique to the associated court.
|
||||
*/
|
||||
id: number
|
||||
|
||||
/**
|
||||
* the player's team
|
||||
* */
|
||||
team: Team
|
||||
|
||||
/**
|
||||
* player's position
|
||||
* */
|
||||
role: string
|
||||
|
||||
/**
|
||||
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
|
||||
*/
|
||||
bottomRatio: number
|
||||
|
||||
/**
|
||||
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
|
||||
*/
|
||||
rightRatio: number
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export enum Team {
|
||||
Allies = "allies",
|
||||
Opponents = "opponents",
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
#court-container {
|
||||
display: flex;
|
||||
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
#court-svg {
|
||||
margin: 5%;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
#court-svg * {
|
||||
stroke: var(--selected-team-secondarycolor);
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
|
||||
|
||||
:root {
|
||||
--main-color: #ffffff;
|
||||
--second-color: #ccde54;
|
||||
|
||||
--background-color: #d2cdd3;
|
||||
|
||||
--selected-team-primarycolor: #ffffff;
|
||||
--selected-team-secondarycolor: #000000;
|
||||
|
||||
--selection-color: #3f7fc4;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
as the .player div content is translated,
|
||||
the real .player div position is not were the user can expect.
|
||||
Disable pointer events to this div as it may overlap on other components
|
||||
on the court.
|
||||
*/
|
||||
.player {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.player-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.player-piece {
|
||||
font-family: monospace;
|
||||
pointer-events: all;
|
||||
|
||||
background-color: var(--selected-team-primarycolor);
|
||||
color: var(--selected-team-secondarycolor);
|
||||
|
||||
border-width: 2px;
|
||||
border-radius: 100px;
|
||||
border-style: solid;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.player-selection-tab {
|
||||
display: flex;
|
||||
|
||||
position: absolute;
|
||||
margin-bottom: 10%;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
|
||||
width: 100%;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.player-selection-tab-remove {
|
||||
pointer-events: all;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.player-selection-tab-remove * {
|
||||
stroke: red;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.player-selection-tab-remove:hover * {
|
||||
fill: #f1dbdb;
|
||||
stroke: #ff331a;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.player:focus-within .player-selection-tab {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.player:focus-within .player-piece {
|
||||
color: var(--selection-color);
|
||||
}
|
||||
|
||||
.player:focus-within {
|
||||
z-index: 1000;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
#main {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#topbar {
|
||||
display: flex;
|
||||
background-color: var(--main-color);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#court-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
|
||||
#court {
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
}
|
@ -1,19 +1,13 @@
|
||||
|
||||
interface DisplayResultsProps {
|
||||
results: readonly { name: string, description: string}[]
|
||||
results: readonly { name: string; description: string }[]
|
||||
}
|
||||
|
||||
export default function DisplayResults({results}: DisplayResultsProps) {
|
||||
const list = results
|
||||
.map(({name, description}) =>
|
||||
export default function DisplayResults({ results }: DisplayResultsProps) {
|
||||
const list = results.map(({ name, description }) => (
|
||||
<div>
|
||||
<p>username: {name}</p>
|
||||
<p>description: {description}</p>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
{list}
|
||||
</div>
|
||||
)
|
||||
))
|
||||
return <div>{list}</div>
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import React, { CSSProperties, useState } from "react"
|
||||
import "../style/visualizer.css"
|
||||
import Court from "../assets/basketball_court.svg"
|
||||
|
||||
|
||||
export default function Visualizer({id, name}: { id: number; name: string }) {
|
||||
const [style, setStyle] = useState<CSSProperties>({});
|
||||
|
||||
return (
|
||||
<div id="main">
|
||||
<div id="topbar">
|
||||
<h1>{name}</h1>
|
||||
</div>
|
||||
<div id="court-container">
|
||||
<img
|
||||
id="court"
|
||||
src={Court}
|
||||
style={style}
|
||||
alt="Basketball Court"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
parameters:
|
||||
phpVersion: 70400
|
||||
level: 6
|
||||
paths:
|
||||
- src
|
||||
- public
|
||||
scanFiles:
|
||||
- config.php
|
||||
- sql/database.php
|
||||
- profiles/dev-config-profile.php
|
||||
- profiles/prod-config-profile.php
|
||||
excludePaths:
|
||||
- src/react-display-file.php
|
@ -0,0 +1 @@
|
||||
../front
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
require_once __DIR__ . "/../react-display.php";
|
||||
|
||||
use App\Validation\ValidationFail;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
class ErrorController {
|
||||
/**
|
||||
* @param ValidationFail[] $failures
|
||||
* @param Environment $twig
|
||||
* @return void
|
||||
*/
|
||||
public static function displayFailures(array $failures, Environment $twig): void {
|
||||
try {
|
||||
$twig->display("error.html.twig", ['failures' => $failures]);
|
||||
} catch (LoaderError|RuntimeError|SyntaxError $e) {
|
||||
echo "Twig error: $e";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Http\HttpCodes;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\JsonHttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Model\TacticModel;
|
||||
|
||||
class VisualizerController {
|
||||
private TacticModel $tacticModel;
|
||||
|
||||
/**
|
||||
* @param TacticModel $tacticModel
|
||||
*/
|
||||
|
||||
public function __construct(TacticModel $tacticModel) {
|
||||
$this->tacticModel = $tacticModel;
|
||||
}
|
||||
|
||||
public function openVisualizer(int $id): HttpResponse {
|
||||
$tactic = $this->tacticModel->get($id);
|
||||
|
||||
if ($tactic == null) {
|
||||
return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Error</title>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #da6110;
|
||||
text-align: center;
|
||||
margin-bottom: 15px
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
cursor : pointer;
|
||||
background-color: white;
|
||||
color : black;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
border-radius: 12px;
|
||||
border : 2px solid #da6110;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #da6110
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>IQBall</h1>
|
||||
|
||||
{% for fail in failures %}
|
||||
<h2>{{ fail.getKind() }} : {{ fail.getMessage() }}</h2>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<button class="button" onclick="location.href='/'" type="button">Retour à la page d'accueil</button>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## verify php and typescript types
|
||||
|
||||
echo "running php typechecking"
|
||||
vendor/bin/phpstan analyze && echo "php types are respected"
|
||||
|
||||
echo "running typescript typechecking"
|
||||
npm run tsc && echo "typescript types are respected"
|
Loading…
Reference in new issue