Compare commits
No commits in common. 'b3490bd9c2999002e3d7db715f80a991cafc3be1' and 'c87c86f7a6635df1e393235933aa213dab64717c' have entirely different histories.
b3490bd9c2
...
c87c86f7a6
@ -1,2 +1,2 @@
|
|||||||
VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-master
|
VITE_API_ENDPOINT=/api
|
||||||
#VITE_API_ENDPOINT=http://localhost:5254
|
VITE_BASE=
|
@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: { browser: true, es2021: true },
|
|
||||||
extends: [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react/jsx-runtime",
|
|
||||||
],
|
|
||||||
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
plugins: ["react-refresh"],
|
|
||||||
rules: {
|
|
||||||
"react-refresh/only-export-components": [
|
|
||||||
"warn",
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,25 +1,44 @@
|
|||||||
# Logs
|
.vs
|
||||||
logs
|
.vscode
|
||||||
*.log
|
.idea
|
||||||
npm-debug.log*
|
.code
|
||||||
yarn-debug.log*
|
.vite
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
vendor
|
||||||
dist
|
.nfs*
|
||||||
dist-ssr
|
composer.lock
|
||||||
*.local
|
*.phar
|
||||||
|
/dist
|
||||||
|
.guard
|
||||||
|
|
||||||
# Editor directories and files
|
# sqlite database files
|
||||||
.vscode/*
|
*.sqlite
|
||||||
.idea
|
|
||||||
|
views-mappings.php
|
||||||
|
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
.env.local
|
||||||
*.ntvs*
|
.env.development.local
|
||||||
*.njsproj
|
.env.test.local
|
||||||
*.sln
|
.env.production.local
|
||||||
*.sw?
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
package-lock.json
|
.php-cs-fixer.cache
|
@ -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);
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"semi": false
|
"semi": false
|
||||||
}
|
}
|
@ -1 +0,0 @@
|
|||||||
master
|
|
@ -1,90 +0,0 @@
|
|||||||
# Welcome on the documentation's description
|
|
||||||
|
|
||||||
## Let's get started with the architecture diagram.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
As you can see our entire application is build around three main package.
|
|
||||||
All of them contained in "src" package.
|
|
||||||
The core represent the main code of the web application.
|
|
||||||
It contains all the validation protocol, detailed below, the model of the imposed MVC architecture.
|
|
||||||
It also has a package named "data", it is a package of the structure of all the data we use in our application.
|
|
||||||
Of course there is package containing all the gateways as its name indicates. It is where we use the connection to our database.
|
|
||||||
Allowing to operate on it.
|
|
||||||
|
|
||||||
The App now is more about the web application itself.
|
|
||||||
Having all the controllers of the MVC architecture the use the model, the validation system and the http system in the core.
|
|
||||||
It also calls the twig's views inside of App. Finally, it uses the package Session. This one replace the $\_SESSION we all know in PHP.
|
|
||||||
Thanks to this we have a way cleaner use of all session's data.
|
|
||||||
Nevertheless, all the controllers call not only twig views but also react ones.
|
|
||||||
Those are present in the package "front", dispatched in several other packages.
|
|
||||||
Such as assets having all the image and stuff, model containing all the data's structure, style centralizing all css file and eventually components the last package used for the editor.
|
|
||||||
|
|
||||||
Finally, we have the package "Api" that allows to share code and bind all the different third-hand application such as the web admin one.
|
|
||||||
|
|
||||||
## Main data class diagram.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
You can see how our data is structured contained in the package "data" as explained right above.
|
|
||||||
There is two clear part.
|
|
||||||
First of all, the Tactic one.
|
|
||||||
We got a nice class named TacticInfo representing as it says the information about a tactic, nothing to discuss more about.
|
|
||||||
It associates an attribute of type "CourtType". This last is just an "evoluated" type of enum with some more features.
|
|
||||||
We had to do it this way because of the language PHP that doesn't implement such a thing as an enum.
|
|
||||||
|
|
||||||
Now, let's discuss a much bigger part of the diagram.
|
|
||||||
In this part we find all the team logic. Actually, a team only have an array of members and a "TeamInfo".
|
|
||||||
The class "TeamInfo" only exists to split the team's information data (name, id etc) from the members.
|
|
||||||
The type Team does only link the information about a team and its members.
|
|
||||||
Talking about them, their class indicate what role they have (either Coach or Player) in the team.
|
|
||||||
Because a member is registered in the app, therefore he is a user of it. Represented by the type of the same name.
|
|
||||||
This class does only contain all the user's basic information.
|
|
||||||
The last class we have is the Account. It could directly be incorporated in User but we decided to split it the same way we did for the team.
|
|
||||||
Then, Account only has a user and a token which is an identifier.
|
|
||||||
|
|
||||||
## Validation's class diagram
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
We implemented our own validation system, here it is!
|
|
||||||
For the validation methods (for instance those in DefaultValidators) we use lambda to instantiate a Validator.
|
|
||||||
In general, we use the implementation "SimpleFunctionValidator".
|
|
||||||
We reconize the strategy pattern. Indeed, we need a family of algorithms because we have many classes that only differ by the way they validate.
|
|
||||||
Futhermore, you may have notices the ComposedValidator that allows to chain several Validator.
|
|
||||||
We can see that this system uses the composite pattern
|
|
||||||
The other part of the diagram is about the failure a specific field's validation.
|
|
||||||
We have a concrete class to return a something more general. All the successors are just more precise about the failure.
|
|
||||||
|
|
||||||
## Http's class diagram
|
|
||||||
|
|
||||||

|
|
||||||
It were we centralize what the app can render, and what the api can receive.
|
|
||||||
Then, we got the "basic" response (HttpResponse) that just render a HttpCodes.
|
|
||||||
We have two successors for now. ViewHttpResponse render not only a code but also a view, either react or twig ones.
|
|
||||||
Finally, we have the JsonHttpResponse that renders, as it's name says, some Json.
|
|
||||||
|
|
||||||
## Session's class diagram
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
It encapsulates the PHP's array "$\_SESSION". With two interfaces that dictate how a session should be handled, and same for a mutable one.
|
|
||||||
|
|
||||||
## Model View Controller
|
|
||||||
|
|
||||||
All class diagram, separated by their range of action, of the imposed MVC architecture.
|
|
||||||
All of them have a controller that validates entries with the validation system and check the permission the user has,and whether or not actually do the action.
|
|
||||||
These controllers are composed by a Model that handle the pure data and is the point of contact between these and the gateways.
|
|
||||||
Speaking of which, Gateways are composing Models. They use the connection class to access the database and send their query.
|
|
||||||
|
|
||||||
### Team
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Editor
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Authentification
|
|
||||||
|
|
||||||

|
|
@ -1,3 +1,3 @@
|
|||||||
- [Description.md](Description.md)
|
# The wiki also exists
|
||||||
- [Conception.md](Conception.md)
|
|
||||||
- [how-to-dev.md](how-to-dev.md)
|
Some of our explanation are contained in the [wiki](https://codefirst.iut.uca.fr/git/IQBall/Application-Web/wiki)
|
@ -1,60 +0,0 @@
|
|||||||
@startuml
|
|
||||||
'https://plantuml.com/component-diagram
|
|
||||||
|
|
||||||
package front{
|
|
||||||
package assets
|
|
||||||
package components
|
|
||||||
package model
|
|
||||||
package style
|
|
||||||
package views
|
|
||||||
}
|
|
||||||
|
|
||||||
database sql{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
package src {
|
|
||||||
|
|
||||||
package "Api"{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
package "App" {
|
|
||||||
package Controller
|
|
||||||
package Session
|
|
||||||
package Views
|
|
||||||
}
|
|
||||||
|
|
||||||
package Core{
|
|
||||||
package Data
|
|
||||||
package Gateway
|
|
||||||
package Http
|
|
||||||
package Model
|
|
||||||
package Validation
|
|
||||||
[Connection]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[sql] -- [Connection]
|
|
||||||
|
|
||||||
[views] -- [style]
|
|
||||||
[views] -- [components]
|
|
||||||
[views] -- [assets]
|
|
||||||
[views] -- [model]
|
|
||||||
|
|
||||||
[Gateway] -- [Connection]
|
|
||||||
|
|
||||||
[Validation] -- [Controller]
|
|
||||||
[Controller] -- [Session]
|
|
||||||
[Controller] -- [Http]
|
|
||||||
[Controller] -- [Views]
|
|
||||||
[Controller] -- [views]
|
|
||||||
[Controller] -- [Model]
|
|
||||||
[Model] -- [Gateway]
|
|
||||||
|
|
||||||
[Api] -- [Validation]
|
|
||||||
[Api] -- [Model]
|
|
||||||
[Api] -- [Http]
|
|
||||||
|
|
||||||
@enduml
|
|
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 32 KiB |
@ -1,44 +0,0 @@
|
|||||||
@startuml
|
|
||||||
class EditorController {
|
|
||||||
+__construct (model : TacticModel)
|
|
||||||
+ openEditorFor(tactic:TacticInfo): ViewHttpResponse
|
|
||||||
+ createNew(): ViewHttpResponse
|
|
||||||
+ openTestEditor(courtType:CourtType): ViewHttpResponse
|
|
||||||
+ createNewOfKind(type:CourtType, session:SessionHandle): ViewHttpResponse
|
|
||||||
+ openEditor(id:int, session:SessionHandle): ViewHttpResponse
|
|
||||||
}
|
|
||||||
EditorController *-- "- model" TacticModel
|
|
||||||
|
|
||||||
class TacticModel {
|
|
||||||
+ TACTIC_DEFAULT_NAME:int {static}{frozen}
|
|
||||||
+ __construct(tactics : TacticInfoGateway)
|
|
||||||
+ makeNew(name:string, ownerId:int, type:CourtType): TacticInfo
|
|
||||||
+ makeNewDefault(ownerId:int, type:CourtType): ?TacticInfo
|
|
||||||
+ get(id:int): ?TacticInfo
|
|
||||||
+ getLast(nb:int, ownerId:int): array
|
|
||||||
+ getAll(ownerId:int): ?array
|
|
||||||
+ updateName(id:int, name:string, authId:int): array
|
|
||||||
+ updateContent(id:int, json:string): ?ValidationFail
|
|
||||||
}
|
|
||||||
|
|
||||||
TacticModel *-- "- tactics" TacticInfoGateway
|
|
||||||
|
|
||||||
class TacticInfoGateway{
|
|
||||||
+ __construct(con : Connexion)
|
|
||||||
+ get(id:int): ?TacticInfo
|
|
||||||
+ getLast(nb:int, ownerId:int): ?array
|
|
||||||
+ getAll(ownerId:int): ?array
|
|
||||||
+ insert(name:string, owner:int, type:CourtType): int
|
|
||||||
+ updateName(id:int, name:string): bool
|
|
||||||
+ updateContent(id:int, json:string): bool
|
|
||||||
}
|
|
||||||
|
|
||||||
TacticInfoGateway *--"- con" Connexion
|
|
||||||
|
|
||||||
class TacticValidator{
|
|
||||||
+ validateAccess(tacticId:int, tactic:?TacticInfo, ownerId:int): ?ValidationFail {static}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorController ..> TacticValidator
|
|
||||||
|
|
||||||
@enduml
|
|
@ -1,87 +1,63 @@
|
|||||||
@startuml
|
@startuml
|
||||||
|
class Team {
|
||||||
|
- name: string
|
||||||
|
- picture: Url
|
||||||
|
- members: array<int, MemberRole>
|
||||||
|
|
||||||
|
+ __construct(name : string, picture : string, mainColor : Colo, secondColor : Color)
|
||||||
|
+ getName(): string
|
||||||
|
+ getPicture(): Url
|
||||||
|
+ getMainColor(): Color
|
||||||
|
+ getSecondColor(): Color
|
||||||
|
+ listMembers(): array<Member>
|
||||||
|
}
|
||||||
|
|
||||||
|
Team --> "- mainColor" Color
|
||||||
|
Team --> "- secondColor" Color
|
||||||
|
|
||||||
|
class Color {
|
||||||
|
- value: string
|
||||||
|
- __construct(value : string)
|
||||||
|
+ getValue(): string
|
||||||
|
+ from(value: string): Color
|
||||||
|
+ tryFrom(value : string) : ?Color
|
||||||
|
}
|
||||||
|
|
||||||
class TeamGateway{
|
class TeamGateway{
|
||||||
|
--
|
||||||
+ __construct(con : Connexion)
|
+ __construct(con : Connexion)
|
||||||
+ insert(name : string ,picture : string, mainColor : Color, secondColor : Color)
|
+ insert(name : string ,picture : string, mainColor : Color, secondColor : Color)
|
||||||
+ listByName(name : string): array
|
+ listByName(name : string): array
|
||||||
+ getTeamById(id:int): ?TeamInfo
|
|
||||||
+ getTeamIdByName(name:string): ?int
|
|
||||||
+ deleteTeam(idTeam:int): void
|
|
||||||
+ editTeam(idTeam:int, newName:string, newPicture:string, newMainColor:string, newSecondColor:string)
|
|
||||||
+ getAll(user:int): array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamGateway *--"- con" Connexion
|
TeamGateway *--"- con" Connexion
|
||||||
|
TeamGateway ..> Color
|
||||||
|
|
||||||
class MemberGateway{
|
|
||||||
|
|
||||||
+ __construct(con : Connexion)
|
|
||||||
+ insert(idTeam:int, userId:int, role:string): void
|
|
||||||
+ getMembersOfTeam(teamId:int): array
|
|
||||||
+ remove(idTeam:int, idMember:int): void
|
|
||||||
+ isCoach(email:string, idTeam:int): bool
|
|
||||||
+ isMemberOfTeam(idTeam:int, idCurrentUser:int): bool
|
|
||||||
}
|
|
||||||
|
|
||||||
MemberGateway *--"- con" Connexion
|
|
||||||
|
|
||||||
class AccountGateway{
|
|
||||||
+ __construct(con : Connexion)
|
|
||||||
+ insertAccount(name:string, email:string, token:string, hash:string, profilePicture:string): int
|
|
||||||
+ getRowsFromMail(email:string): ?array
|
|
||||||
+ getHash(email:string): ?string
|
|
||||||
+ exists(email:string): bool
|
|
||||||
+ getAccountFromMail(email:string): ?Account
|
|
||||||
+ getAccountFromToken(token:string): ?Account
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountGateway *--"- con" Connexion
|
|
||||||
|
|
||||||
class TeamModel{
|
class TeamModel{
|
||||||
|
---
|
||||||
+ __construct(gateway : TeamGateway)
|
+ __construct(gateway : TeamGateway)
|
||||||
+ createTeam(name : string,picture : string, mainColorValue : int, secondColorValue : int, errors : array)
|
+ createTeam(name : string,picture : string, mainColorValue : int, secondColorValue : int, errors : array)
|
||||||
+ addMember(mail:string, teamId:int, role:string): int
|
|
||||||
+ listByName(name : string ,errors : array) : ?array
|
+ listByName(name : string ,errors : array) : ?array
|
||||||
+ getTeam(idTeam:int, idCurrentUser:int): ?Team
|
+ displayTeam(id : int): Team
|
||||||
+ deleteMember(idMember:int, teamId:int): int
|
|
||||||
+ deleteTeam(email:string, idTeam:int): int
|
|
||||||
+ isCoach(idTeam:int, email:string): bool
|
|
||||||
+ editTeam(idTeam:int, newName:string, newPicture:string, newMainColor:string, newSecondColor:string)
|
|
||||||
+ getAll(user:int): array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamModel *--"- members" MemberGateway
|
TeamModel *--"- gateway" TeamGateway
|
||||||
TeamModel *--"- teams" TeamGateway
|
TeamModel ..> Team
|
||||||
TeamModel *--"- teams" AccountGateway
|
TeamModel ..> Color
|
||||||
|
|
||||||
|
|
||||||
class TeamController{
|
class TeamController{
|
||||||
+ __construct( model : TeamModel)
|
- twig : Environement
|
||||||
+ displayCreateTeam(session:SessionHandle): ViewHttpResponse
|
--
|
||||||
+ displayDeleteMember(session:SessionHandle): ViewHttpResponse
|
+ __construct( model : TeamModel, twig : Environement)
|
||||||
+ submitTeam(request:array, session:SessionHandle): HttpResponse
|
+ displaySubmitTeam() : HttpResponse
|
||||||
+ displayListTeamByName(session:SessionHandle): ViewHttpResponse
|
+ submitTeam(request : array) : HttpResponse
|
||||||
+ listTeamByName(request:array, session:SessionHandle): HttpResponse
|
+ displayListTeamByName(): HttpResponse
|
||||||
+ deleteTeamById(id:int, session:SessionHandle): HttpResponse
|
+ listTeamByName(request : array) : HttpResponse
|
||||||
+ displayTeam(id:int, session:SessionHandle): ViewHttpResponse
|
+ displayTeam(id : int): HttpResponse
|
||||||
+ displayAddMember(idTeam:int, session:SessionHandle): ViewHttpResponse
|
|
||||||
+ addMember(idTeam:int, request:array, session:SessionHandle): HttpResponse
|
|
||||||
+ deleteMember(idTeam:int, idMember:int, session:SessionHandle): HttpResponse
|
|
||||||
+ displayEditTeam(idTeam:int, session:SessionHandle): ViewHttpResponse
|
|
||||||
+ editTeam(idTeam:int, request:array, session:SessionHandle): HttpResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamController *--"- model" TeamModel
|
TeamController *--"- model" TeamModel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Connexion { }
|
class Connexion { }
|
||||||
|
|
||||||
@enduml
|
@enduml
|
@ -1,27 +0,0 @@
|
|||||||
@startuml
|
|
||||||
|
|
||||||
interface SessionHandle{
|
|
||||||
+ getInitialTarget(): ?string {abstract}
|
|
||||||
+ getAccount(): ?Account {abstract}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MutableSessionHandle{
|
|
||||||
+ setInitialTarget(url:?string): void
|
|
||||||
+ setAccount(account:Account): void
|
|
||||||
+ destroy(): void
|
|
||||||
}
|
|
||||||
|
|
||||||
class PhpSessionHandle{
|
|
||||||
+ init(): self {static}
|
|
||||||
+ getAccount(): ?Account
|
|
||||||
+ getInitialTarget(): ?string
|
|
||||||
+ setAccount(account:Account): void
|
|
||||||
+ setInitialTarget(url:?string): void
|
|
||||||
+ destroy(): void
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PhpSessionHandle ..|> MutableSessionHandle
|
|
||||||
MutableSessionHandle ..|> SessionHandle
|
|
||||||
|
|
||||||
@enduml
|
|
@ -1,8 +1,7 @@
|
|||||||
# IQBall - Web Application
|
# IQBall - Web Application
|
||||||
|
|
||||||
This repository hosts the IQBall application for web
|
This repository hosts the IQBall application for web
|
||||||
|
|
||||||
## Read the docs !
|
## Read the docs !
|
||||||
|
|
||||||
You can find some additional documentation in the [Documentation](Documentation) folder,
|
You can find some additional documentation in the [Documentation](Documentation) folder,
|
||||||
and in the [wiki](https://codefirst.iut.uca.fr/git/IQBall/Application-Web/wiki).
|
and in the [wiki](https://codefirst.iut.uca.fr/git/IQBall/Application-Web/wiki).
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
set -e
|
|
||||||
|
|
||||||
export OUTPUT=$1
|
|
||||||
export BASE=$2
|
|
||||||
|
|
||||||
rm -rf "$OUTPUT"/*
|
|
||||||
|
|
||||||
echo "VITE_API_ENDPOINT=$BASE/api" >> .env.PROD
|
|
||||||
echo "VITE_BASE=$BASE" >> .env.PROD
|
|
||||||
|
|
||||||
ci/build_react.msh
|
|
||||||
|
|
||||||
mkdir -p $OUTPUT/profiles/
|
|
||||||
|
|
||||||
sed -E 's/\/\*PROFILE_FILE\*\/\s*".*"/"profiles\/prod-config-profile.php"/' config.php > $OUTPUT/config.php
|
|
||||||
sed -E "s/const BASE_PATH = .*;/const BASE_PATH = \"$(sed s/\\//\\\\\\//g <<< "$BASE")\";/" profiles/prod-config-profile.php > $OUTPUT/profiles/prod-config-profile.php
|
|
||||||
|
|
||||||
cp -r vendor sql src public $OUTPUT
|
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"IQBall\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"altorouter/altorouter": "1.2.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pdo_sqlite": "*",
|
||||||
|
"twig/twig":"^2.0",
|
||||||
|
"phpstan/phpstan": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.38"
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
@ -0,0 +1,19 @@
|
|||||||
|
import ReactDOM from "react-dom/client"
|
||||||
|
import React, { FunctionComponent } from "react"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically renders a React component, with given arguments
|
||||||
|
* @param Component the react component to render
|
||||||
|
* @param args the arguments to pass to the react component.
|
||||||
|
*/
|
||||||
|
export function renderView(Component: FunctionComponent, args: {}) {
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById("root") as HTMLElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<Component {...args} />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
}
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 507 B |
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
@ -1,19 +1,15 @@
|
|||||||
import { BallPiece } from "../editor/BallPiece"
|
import { BallPiece } from "../editor/BallPiece"
|
||||||
import Draggable from "react-draggable"
|
import Draggable from "react-draggable"
|
||||||
import { useRef } from "react"
|
import { useRef } from "react"
|
||||||
import { NULL_POS } from "../../geo/Pos"
|
|
||||||
|
|
||||||
export interface BallActionProps {
|
export interface BallActionProps {
|
||||||
onDrop: (el: DOMRect) => void
|
onDrop: (el: HTMLElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BallAction({ onDrop }: BallActionProps) {
|
export default function BallAction({ onDrop }: BallActionProps) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable onStop={() => onDrop(ref.current!)} nodeRef={ref}>
|
||||||
nodeRef={ref}
|
|
||||||
onStop={() => onDrop(ref.current!.getBoundingClientRect())}
|
|
||||||
position={NULL_POS}>
|
|
||||||
<div ref={ref}>
|
<div ref={ref}>
|
||||||
<BallPiece />
|
<BallPiece />
|
||||||
</div>
|
</div>
|
@ -1,8 +1,7 @@
|
|||||||
import "../../style/ball.css"
|
import "../../style/ball.css"
|
||||||
|
|
||||||
import BallSvg from "../../assets/icon/ball.svg?react"
|
import BallSvg from "../../assets/icon/ball.svg?react"
|
||||||
import { BALL_ID } from "../../model/tactic/CourtObjects"
|
|
||||||
|
|
||||||
export function BallPiece() {
|
export function BallPiece() {
|
||||||
return <BallSvg id={BALL_ID} className={"ball"} />
|
return <BallSvg className={"ball"} />
|
||||||
}
|
}
|
@ -0,0 +1,272 @@
|
|||||||
|
import { CourtBall } from "./CourtBall"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ReactElement,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useLayoutEffect,
|
||||||
|
useState,
|
||||||
|
} from "react"
|
||||||
|
import CourtPlayer from "./CourtPlayer"
|
||||||
|
|
||||||
|
import { Player } from "../../model/tactic/Player"
|
||||||
|
import { Action, ActionKind } from "../../model/tactic/Action"
|
||||||
|
import ArrowAction from "../actions/ArrowAction"
|
||||||
|
import { middlePos, ratioWithinBase } from "../arrows/Pos"
|
||||||
|
import BallAction from "../actions/BallAction"
|
||||||
|
import { CourtObject } from "../../model/tactic/Ball"
|
||||||
|
import { contains } from "../arrows/Box"
|
||||||
|
import { CourtAction } from "../../views/editor/CourtAction"
|
||||||
|
|
||||||
|
export interface BasketCourtProps {
|
||||||
|
players: Player[]
|
||||||
|
actions: Action[]
|
||||||
|
objects: CourtObject[]
|
||||||
|
|
||||||
|
renderAction: (a: Action, key: number) => ReactElement
|
||||||
|
setActions: (f: (a: Action[]) => Action[]) => void
|
||||||
|
|
||||||
|
onPlayerRemove: (p: Player) => void
|
||||||
|
onPlayerChange: (p: Player) => void
|
||||||
|
|
||||||
|
onBallRemove: () => void
|
||||||
|
onBallMoved: (ball: DOMRect) => void
|
||||||
|
|
||||||
|
courtImage: ReactElement
|
||||||
|
courtRef: RefObject<HTMLDivElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BasketCourt({
|
||||||
|
players,
|
||||||
|
actions,
|
||||||
|
objects,
|
||||||
|
renderAction,
|
||||||
|
setActions,
|
||||||
|
onPlayerRemove,
|
||||||
|
onPlayerChange,
|
||||||
|
|
||||||
|
onBallMoved,
|
||||||
|
onBallRemove,
|
||||||
|
|
||||||
|
courtImage,
|
||||||
|
courtRef,
|
||||||
|
}: BasketCourtProps) {
|
||||||
|
function placeArrow(origin: Player, arrowHead: DOMRect) {
|
||||||
|
const originRef = document.getElementById(origin.id)!
|
||||||
|
const courtBounds = courtRef.current!.getBoundingClientRect()
|
||||||
|
const start = ratioWithinBase(
|
||||||
|
middlePos(originRef.getBoundingClientRect()),
|
||||||
|
courtBounds,
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const player of players) {
|
||||||
|
if (player.id == origin.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerBounds = document
|
||||||
|
.getElementById(player.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
playerBounds.top > arrowHead.bottom ||
|
||||||
|
playerBounds.right < arrowHead.left ||
|
||||||
|
playerBounds.bottom < arrowHead.top ||
|
||||||
|
playerBounds.left > arrowHead.right
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const targetPos = document
|
||||||
|
.getElementById(player.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
const end = ratioWithinBase(middlePos(targetPos), courtBounds)
|
||||||
|
|
||||||
|
const action: Action = {
|
||||||
|
fromPlayerId: originRef.id,
|
||||||
|
toPlayerId: player.id,
|
||||||
|
type: origin.hasBall ? ActionKind.SHOOT : ActionKind.SCREEN,
|
||||||
|
moveFrom: start,
|
||||||
|
segments: [{ next: end }],
|
||||||
|
}
|
||||||
|
setActions((actions) => [...actions, action])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const action: Action = {
|
||||||
|
fromPlayerId: originRef.id,
|
||||||
|
type: origin.hasBall ? ActionKind.DRIBBLE : ActionKind.MOVE,
|
||||||
|
moveFrom: ratioWithinBase(
|
||||||
|
middlePos(originRef.getBoundingClientRect()),
|
||||||
|
courtBounds,
|
||||||
|
),
|
||||||
|
segments: [
|
||||||
|
{ next: ratioWithinBase(middlePos(arrowHead), courtBounds) },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
setActions((actions) => [...actions, action])
|
||||||
|
}
|
||||||
|
|
||||||
|
const [previewAction, setPreviewAction] = useState<Action | null>(null)
|
||||||
|
|
||||||
|
const updateActionsRelatedTo = useCallback((player: Player) => {
|
||||||
|
const newPos = ratioWithinBase(
|
||||||
|
middlePos(
|
||||||
|
document.getElementById(player.id)!.getBoundingClientRect(),
|
||||||
|
),
|
||||||
|
courtRef.current!.getBoundingClientRect(),
|
||||||
|
)
|
||||||
|
setActions((actions) =>
|
||||||
|
actions.map((a) => {
|
||||||
|
if (a.fromPlayerId == player.id) {
|
||||||
|
return { ...a, moveFrom: newPos }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.toPlayerId == player.id) {
|
||||||
|
const segments = a.segments.toSpliced(
|
||||||
|
a.segments.length - 1,
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
...a.segments[a.segments.length - 1],
|
||||||
|
next: newPos,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return { ...a, segments }
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const [internActions, setInternActions] = useState<Action[]>([])
|
||||||
|
|
||||||
|
useLayoutEffect(() => setInternActions(actions), [actions])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="court-container"
|
||||||
|
ref={courtRef}
|
||||||
|
style={{ position: "relative" }}>
|
||||||
|
{courtImage}
|
||||||
|
|
||||||
|
{players.map((player) => (
|
||||||
|
<CourtPlayer
|
||||||
|
key={player.id}
|
||||||
|
player={player}
|
||||||
|
onDrag={() => updateActionsRelatedTo(player)}
|
||||||
|
onChange={onPlayerChange}
|
||||||
|
onRemove={() => onPlayerRemove(player)}
|
||||||
|
courtRef={courtRef}
|
||||||
|
availableActions={(pieceRef) => [
|
||||||
|
<ArrowAction
|
||||||
|
key={1}
|
||||||
|
onHeadMoved={(headPos) => {
|
||||||
|
const baseBounds =
|
||||||
|
courtRef.current!.getBoundingClientRect()
|
||||||
|
|
||||||
|
const arrowHeadPos = middlePos(headPos)
|
||||||
|
|
||||||
|
const target = players.find(
|
||||||
|
(p) =>
|
||||||
|
p != player &&
|
||||||
|
contains(
|
||||||
|
document
|
||||||
|
.getElementById(p.id)!
|
||||||
|
.getBoundingClientRect(),
|
||||||
|
arrowHeadPos,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
setPreviewAction((action) => ({
|
||||||
|
...action!,
|
||||||
|
segments: [
|
||||||
|
{
|
||||||
|
next: ratioWithinBase(
|
||||||
|
arrowHeadPos,
|
||||||
|
baseBounds,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: player.hasBall
|
||||||
|
? target
|
||||||
|
? ActionKind.SHOOT
|
||||||
|
: ActionKind.DRIBBLE
|
||||||
|
: target
|
||||||
|
? ActionKind.SCREEN
|
||||||
|
: ActionKind.MOVE,
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
onHeadPicked={(headPos) => {
|
||||||
|
;(document.activeElement as HTMLElement).blur()
|
||||||
|
const baseBounds =
|
||||||
|
courtRef.current!.getBoundingClientRect()
|
||||||
|
|
||||||
|
setPreviewAction({
|
||||||
|
type: player.hasBall
|
||||||
|
? ActionKind.DRIBBLE
|
||||||
|
: ActionKind.MOVE,
|
||||||
|
fromPlayerId: player.id,
|
||||||
|
toPlayerId: undefined,
|
||||||
|
moveFrom: ratioWithinBase(
|
||||||
|
middlePos(
|
||||||
|
pieceRef.getBoundingClientRect(),
|
||||||
|
),
|
||||||
|
baseBounds,
|
||||||
|
),
|
||||||
|
segments: [
|
||||||
|
{
|
||||||
|
next: ratioWithinBase(
|
||||||
|
middlePos(headPos),
|
||||||
|
baseBounds,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onHeadDropped={(headRect) => {
|
||||||
|
placeArrow(player, headRect)
|
||||||
|
setPreviewAction(null)
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
player.hasBall && (
|
||||||
|
<BallAction
|
||||||
|
key={2}
|
||||||
|
onDrop={(ref) =>
|
||||||
|
onBallMoved(ref.getBoundingClientRect())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{internActions.map((action, idx) => renderAction(action, idx))}
|
||||||
|
|
||||||
|
{objects.map((object) => {
|
||||||
|
if (object.type == "ball") {
|
||||||
|
return (
|
||||||
|
<CourtBall
|
||||||
|
onMoved={onBallMoved}
|
||||||
|
ball={object}
|
||||||
|
onRemove={onBallRemove}
|
||||||
|
key="ball"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw new Error("unknown court object" + object.type)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{previewAction && (
|
||||||
|
<CourtAction
|
||||||
|
courtRef={courtRef}
|
||||||
|
action={previewAction}
|
||||||
|
//do nothing on change, not really possible as it's a preview arrow
|
||||||
|
onActionDeleted={() => {}}
|
||||||
|
onActionChanges={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
export type CourtObject = { type: "ball" } & Ball
|
||||||
|
|
||||||
|
export interface Ball {
|
||||||
|
/**
|
||||||
|
* The ball is a "ball" court object
|
||||||
|
*/
|
||||||
|
readonly type: "ball"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
|
||||||
|
*/
|
||||||
|
readonly bottomRatio: number
|
||||||
|
/**
|
||||||
|
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
|
||||||
|
*/
|
||||||
|
readonly rightRatio: number
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
export type PlayerId = string
|
||||||
|
|
||||||
|
export enum PlayerTeam {
|
||||||
|
Allies = "allies",
|
||||||
|
Opponents = "opponents",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Player {
|
||||||
|
readonly id: PlayerId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the player's team
|
||||||
|
* */
|
||||||
|
readonly team: PlayerTeam
|
||||||
|
|
||||||
|
/**
|
||||||
|
* player's role
|
||||||
|
* */
|
||||||
|
readonly role: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
|
||||||
|
*/
|
||||||
|
readonly bottomRatio: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
|
||||||
|
*/
|
||||||
|
readonly rightRatio: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the player has a basketball
|
||||||
|
*/
|
||||||
|
readonly hasBall: boolean
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Player } from "./Player"
|
||||||
|
import { CourtObject } from "./Ball"
|
||||||
|
import { Action } from "./Action"
|
||||||
|
|
||||||
|
export interface Tactic {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
content: TacticContent
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TacticContent {
|
||||||
|
players: Player[]
|
||||||
|
objects: CourtObject[]
|
||||||
|
actions: Action[]
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
:root {
|
||||||
|
--main-color: #191a21;
|
||||||
|
--second-color: #282a36;
|
||||||
|
--third-color: #303341;
|
||||||
|
--accent-color: #ffa238;
|
||||||
|
--main-contrast-color: #e6edf3;
|
||||||
|
--font-title: Helvetica;
|
||||||
|
--font-content: Helvetica;
|
||||||
|
}
|
@ -1,75 +1,26 @@
|
|||||||
import "../style/home/home.css"
|
import "../style/home/home.css"
|
||||||
|
|
||||||
|
// import AccountSvg from "../assets/account.svg?react"
|
||||||
|
import { Header } from "./template/Header"
|
||||||
import { BASE } from "../Constants"
|
import { BASE } from "../Constants"
|
||||||
import { MainTitle } from "./component/Title"
|
import { MainTitle } from "./component/Title"
|
||||||
import { Tactic } from "./model/Tactic"
|
import { Tactic } from "./model/Tactic"
|
||||||
import { Team } from "./model/Team"
|
import { Team } from "./model/Team"
|
||||||
import { getSession } from "../api/session.ts"
|
|
||||||
import { useNavigate } from "react-router-dom"
|
|
||||||
import { startTransition, useLayoutEffect, useState } from "react"
|
|
||||||
import { User } from "../model/User.ts"
|
|
||||||
import { fetchAPIGet } from "../Fetcher.ts"
|
|
||||||
|
|
||||||
interface Tactic {
|
export default function Home({
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
creationDate: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Team {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
picture: string
|
|
||||||
main_color: string
|
|
||||||
second_color: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function HomePage() {
|
|
||||||
type UserDataResponse = { user?: User; tactics: Tactic[]; teams: Team[] }
|
|
||||||
const [{ tactics, teams }, setInfo] = useState<UserDataResponse>({
|
|
||||||
tactics: [],
|
|
||||||
teams: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const session = getSession()
|
|
||||||
|
|
||||||
if (!session.auth) {
|
|
||||||
startTransition(() => {
|
|
||||||
navigate("/login")
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getUser() {
|
|
||||||
const response = await fetchAPIGet("user-data")
|
|
||||||
setInfo(await response.json())
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser()
|
|
||||||
}, [navigate])
|
|
||||||
|
|
||||||
tactics!.sort((a, b) => b.creationDate - a.creationDate)
|
|
||||||
|
|
||||||
const lastTactics = tactics.slice(0, 5)
|
|
||||||
return (
|
|
||||||
<Home teams={teams!} allTactics={tactics!} lastTactics={lastTactics} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Home({
|
|
||||||
lastTactics,
|
lastTactics,
|
||||||
allTactics,
|
allTactics,
|
||||||
teams,
|
teams,
|
||||||
|
username,
|
||||||
}: {
|
}: {
|
||||||
lastTactics: Tactic[]
|
lastTactics: Tactic[]
|
||||||
allTactics: Tactic[]
|
allTactics: Tactic[]
|
||||||
teams: Team[]
|
teams: Team[]
|
||||||
|
username: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div id="main">
|
<div id="main">
|
||||||
|
<Header username={username} />
|
||||||
<Body
|
<Body
|
||||||
lastTactics={lastTactics}
|
lastTactics={lastTactics}
|
||||||
allTactics={allTactics}
|
allTactics={allTactics}
|
@ -1,28 +1,9 @@
|
|||||||
import "../style/team_panel.css"
|
import "../style/team_panel.css"
|
||||||
import { BASE } from "../Constants"
|
import { BASE } from "../Constants"
|
||||||
import { Member, Team, TeamInfo } from "../model/Team"
|
import { Team, TeamInfo, Member } from "../model/Team"
|
||||||
import { useParams } from "react-router-dom"
|
import { User } from "../model/User"
|
||||||
|
|
||||||
export default function TeamPanelPage() {
|
export default function TeamPanel({
|
||||||
const { teamId } = useParams()
|
|
||||||
const teamInfo = {
|
|
||||||
id: parseInt(teamId!),
|
|
||||||
name: teamId!,
|
|
||||||
mainColor: "#FFFFFF",
|
|
||||||
secondColor: "#000000",
|
|
||||||
picture:
|
|
||||||
"https://a.espncdn.com/combiner/i?img=/i/teamlogos/nba/500/lal.png",
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TeamPanel
|
|
||||||
team={{ info: teamInfo, members: [] }}
|
|
||||||
currentUserId={0}
|
|
||||||
isCoach={false}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TeamPanel({
|
|
||||||
isCoach,
|
isCoach,
|
||||||
team,
|
team,
|
||||||
currentUserId,
|
currentUserId,
|
@ -0,0 +1,23 @@
|
|||||||
|
import React, { CSSProperties, useState } from "react"
|
||||||
|
import "../style/visualizer.css"
|
||||||
|
import Court from "../assets/court/full_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>
|
||||||
|
)
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/src/assets/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>IQBall</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||||||
|
parameters:
|
||||||
|
phpVersion: 70400
|
||||||
|
level: 6
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
scanFiles:
|
||||||
|
- config.php
|
||||||
|
- sql/database.php
|
||||||
|
- profiles/dev-config-profile.php
|
||||||
|
- profiles/prod-config-profile.php
|
||||||
|
excludePaths:
|
||||||
|
- src/App/react-display-file.php
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$hostname = getHostName();
|
||||||
|
$front_url = "http://$hostname:5173";
|
||||||
|
|
||||||
|
const _SUPPORTS_FAST_REFRESH = true;
|
||||||
|
$_data_source_name = "sqlite:${_SERVER['DOCUMENT_ROOT']}/../dev-database.sqlite";
|
||||||
|
|
||||||
|
// no user and password needed for sqlite databases
|
||||||
|
const _DATABASE_USER = null;
|
||||||
|
const _DATABASE_PASSWORD = null;
|
||||||
|
|
||||||
|
function _asset(string $assetURI): string {
|
||||||
|
global $front_url;
|
||||||
|
return $front_url . "/" . $assetURI;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// This file only exists on production servers, and defines the available assets mappings
|
||||||
|
// in an `ASSETS` array constant.
|
||||||
|
require __DIR__ . "/../views-mappings.php";
|
||||||
|
|
||||||
|
const _SUPPORTS_FAST_REFRESH = false;
|
||||||
|
$database_file = __DIR__ . "/../database.sqlite";
|
||||||
|
$_data_source_name = "sqlite:/$database_file";
|
||||||
|
|
||||||
|
// no user and password needed for sqlite databases
|
||||||
|
const _DATABASE_USER = null;
|
||||||
|
const _DATABASE_PASSWORD = null;
|
||||||
|
|
||||||
|
|
||||||
|
function _asset(string $assetURI): string {
|
||||||
|
// use index.php's base path
|
||||||
|
global $basePath;
|
||||||
|
// If the asset uri does not figure in the available assets array,
|
||||||
|
// fallback to the uri itself.
|
||||||
|
return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI);
|
||||||
|
}
|
After Width: | Height: | Size: 747 B |