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

pull/119/head
maxime 1 year ago
parent cab6fc43ca
commit f9e436ea12

@ -147,8 +147,9 @@ export default function App() {
path={"/tactic/:tacticId/view"} path={"/tactic/:tacticId/view"}
element={suspense( element={suspense(
<LoggedInPage> <LoggedInPage>
<VisualizerPage guestMode={false} />, <VisualizerPage guestMode={false} />
</LoggedInPage> ,
</LoggedInPage>,
)} )}
/> />
<Route <Route

@ -68,9 +68,14 @@ export class Fetcher {
} }
const nextToken = response.headers.get("Next-Authorization")! const nextToken = response.headers.get("Next-Authorization")!
const expirationDate = response.headers.get("Next-Authorization-Expiration-Date")! const expirationDate = response.headers.get(
"Next-Authorization-Expiration-Date",
)!
if (nextToken && expirationDate) { if (nextToken && expirationDate) {
this.auth = { token: nextToken, expirationDate: new Date(expirationDate) } this.auth = {
token: nextToken,
expirationDate: new Date(expirationDate),
}
} }
return response return response

@ -1,8 +1,23 @@
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react" import {
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react"
import { APITacticService } from "../service/APITacticService.ts" import { APITacticService } from "../service/APITacticService.ts"
import { CourtType, StepContent, StepInfoNode, TacticComponent } from "../model/tactic/Tactic.ts" import {
CourtType,
StepContent,
StepInfoNode,
TacticComponent,
} from "../model/tactic/Tactic.ts"
import { getParent } from "../domains/StepsDomain.ts" import { getParent } from "../domains/StepsDomain.ts"
import { computeRelativePositions, getPhantomInfo } from "../domains/PlayerDomains.ts" import {
computeRelativePositions,
getPhantomInfo,
} from "../domains/PlayerDomains.ts"
import { PlayerInfo, PlayerLike } from "../model/tactic/Player.ts" import { PlayerInfo, PlayerLike } from "../model/tactic/Player.ts"
import { CourtPlayer } from "./editor/CourtPlayer.tsx" import { CourtPlayer } from "./editor/CourtPlayer.tsx"
import { BALL_TYPE } from "../model/tactic/CourtObjects.ts" import { BALL_TYPE } from "../model/tactic/CourtObjects.ts"
@ -12,7 +27,6 @@ import { BasketCourt, Court } from "./editor/BasketCourt.tsx"
import { TacticService } from "../service/MutableTacticService.ts" import { TacticService } from "../service/MutableTacticService.ts"
import { useAppFetcher } from "../App.tsx" import { useAppFetcher } from "../App.tsx"
export interface VisualizerProps { export interface VisualizerProps {
tacticId: number tacticId: number
stepId?: number stepId?: number
@ -23,7 +37,10 @@ export function Visualizer({ tacticId, stepId }: VisualizerProps) {
const [courtType, setCourtType] = useState<CourtType | null>() const [courtType, setCourtType] = useState<CourtType | null>()
const [stepsTree, setStepsTree] = useState<StepInfoNode | null>() const [stepsTree, setStepsTree] = useState<StepInfoNode | null>()
const fetcher = useAppFetcher() const fetcher = useAppFetcher()
const service = useMemo(() => new APITacticService(fetcher, tacticId), [tacticId]) const service = useMemo(
() => new APITacticService(fetcher, tacticId),
[tacticId],
)
const isNotInit = !stepsTree || !courtType const isNotInit = !stepsTree || !courtType
@ -35,13 +52,11 @@ export function Visualizer({ tacticId, stepId }: VisualizerProps) {
return return
} }
const rootStep = contextResult.stepsTree const rootStep = contextResult.stepsTree
setStepsTree(rootStep) setStepsTree(rootStep)
setCourtType(contextResult.courtType) setCourtType(contextResult.courtType)
} }
if (isNotInit) if (isNotInit) init()
init()
}, [isNotInit, service]) }, [isNotInit, service])
if (panicMessage) { if (panicMessage) {
@ -51,12 +66,14 @@ export function Visualizer({ tacticId, stepId }: VisualizerProps) {
return <p>Loading...</p> return <p>Loading...</p>
} }
return <StepVisualizer return (
courtType={courtType} <StepVisualizer
stepsTree={stepsTree} courtType={courtType}
stepId={stepId} stepsTree={stepsTree}
service={service} stepId={stepId}
/> service={service}
/>
)
} }
export interface StepVisualizerProps { export interface StepVisualizerProps {
@ -66,7 +83,12 @@ export interface StepVisualizerProps {
service: TacticService service: TacticService
} }
export function StepVisualizer({stepId, stepsTree, courtType, service}: StepVisualizerProps) { export function StepVisualizer({
stepId,
stepsTree,
courtType,
service,
}: StepVisualizerProps) {
const [panicMessage, setPanicMessage] = useState<string | null>(null) const [panicMessage, setPanicMessage] = useState<string | null>(null)
const [content, setContent] = useState<StepContent | null>(null) const [content, setContent] = useState<StepContent | null>(null)
const [parentContent, setParentContent] = useState<StepContent | null>() const [parentContent, setParentContent] = useState<StepContent | null>()
@ -75,7 +97,6 @@ export function StepVisualizer({stepId, stepsTree, courtType, service}: StepVisu
useEffect(() => { useEffect(() => {
async function init() { async function init() {
const contentStepId = stepId ?? stepsTree.id const contentStepId = stepId ?? stepsTree.id
const contentResult = await service.getContent(contentStepId) const contentResult = await service.getContent(contentStepId)
@ -126,11 +147,10 @@ export interface VisualizerFrameProps {
} }
export function VisualizerFrame({ export function VisualizerFrame({
content, content,
parentContent, parentContent,
courtType, courtType,
}: VisualizerFrameProps) { }: VisualizerFrameProps) {
const courtRef = useRef<HTMLDivElement>(null) const courtRef = useRef<HTMLDivElement>(null)
const courtBounds = useCallback( const courtBounds = useCallback(

@ -2,7 +2,11 @@ import { ReactElement, ReactNode, RefObject, useEffect, useState } from "react"
import { Action } from "../../model/tactic/Action" import { Action } from "../../model/tactic/Action"
import { CourtAction } from "./CourtAction" import { CourtAction } from "./CourtAction"
import { ComponentId, TacticComponent } from "../../model/tactic/Tactic" import {
ComponentId,
CourtType,
TacticComponent,
} from "../../model/tactic/Tactic"
import PlainCourt from "../../assets/court/full_court.svg?react" import PlainCourt from "../../assets/court/full_court.svg?react"
import HalfCourt from "../../assets/court/half_court.svg?react" import HalfCourt from "../../assets/court/half_court.svg?react"
@ -34,7 +38,6 @@ export function BasketCourt({
courtImage, courtImage,
courtRef, courtRef,
}: BasketCourtProps) { }: BasketCourtProps) {
const [court, setCourt] = useState(courtRef.current) const [court, setCourt] = useState(courtRef.current)
//force update once the court reference is set //force update once the court reference is set
@ -49,15 +52,11 @@ export function BasketCourt({
style={{ position: "relative" }}> style={{ position: "relative" }}>
{courtImage} {courtImage}
{court && {court && parentComponents?.map((i) => renderComponent(i, true))}
parentComponents?.map((i) => renderComponent(i, true))} {court && parentComponents?.flatMap((i) => renderActions(i, true))}
{court &&
parentComponents?.flatMap((i) => renderActions(i, true))}
{court && {court && components.map((i) => renderComponent(i, false))}
components.map((i) => renderComponent(i, false))} {court && components.flatMap((i) => renderActions(i, false))}
{court &&
components.flatMap((i) => renderActions(i, false))}
{previewAction && ( {previewAction && (
<CourtAction <CourtAction
@ -75,14 +74,11 @@ export function BasketCourt({
) )
} }
export function Court({ courtType }: { courtType: string }) { export function Court({ courtType }: { courtType: CourtType }) {
const CourtSvg = courtType === "PLAIN" ? PlainCourt : HalfCourt
return ( return (
<div className="court-image-div"> <div className="court-image-div">
{courtType == "PLAIN" ? ( <CourtSvg className="court-image" />
<PlainCourt className="court-image" />
) : (
<HalfCourt className="court-image" />
)}
</div> </div>
) )
} }

@ -15,12 +15,12 @@ export interface StepsTreeProps {
} }
export default function StepsTree({ export default function StepsTree({
root, root,
selectedStepId, selectedStepId,
onAddChildren, onAddChildren,
onRemoveNode, onRemoveNode,
onStepSelected, onStepSelected,
}: StepsTreeProps) { }: StepsTreeProps) {
return ( return (
<div className="steps-tree"> <div className="steps-tree">
<StepsTreeNode <StepsTreeNode
@ -46,13 +46,13 @@ interface StepsTreeContentProps {
} }
function StepsTreeNode({ function StepsTreeNode({
node, node,
rootNode, rootNode,
selectedStepId, selectedStepId,
onAddChildren, onAddChildren,
onRemoveNode, onRemoveNode,
onStepSelected, onStepSelected,
}: StepsTreeContentProps) { }: StepsTreeContentProps) {
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
return ( return (
@ -67,8 +67,7 @@ function StepsTreeNode({
next: "step-piece-" + child.id, next: "step-piece-" + child.id,
}, },
]} ]}
onSegmentsChanges={() => { onSegmentsChanges={() => {}}
}}
forceStraight={true} forceStraight={true}
wavy={false} wavy={false}
readOnly={true} readOnly={true}
@ -80,9 +79,8 @@ function StepsTreeNode({
<StepPiece <StepPiece
id={node.id} id={node.id}
isSelected={selectedStepId === node.id} isSelected={selectedStepId === node.id}
onAddButtonClicked={onAddChildren onAddButtonClicked={
? () => onAddChildren(node) onAddChildren ? () => onAddChildren(node) : undefined
: undefined
} }
onRemoveButtonClicked={ onRemoveButtonClicked={
rootNode.id === node.id || !onRemoveNode rootNode.id === node.id || !onRemoveNode
@ -126,13 +124,13 @@ interface StepPieceProps {
} }
function StepPiece({ function StepPiece({
id, id,
isSelected, isSelected,
onAddButtonClicked, onAddButtonClicked,
onRemoveButtonClicked, onRemoveButtonClicked,
onSelected, onSelected,
children, children,
}: StepPieceProps) { }: StepPieceProps) {
return ( return (
<div <div
id={"step-piece-" + id} id={"step-piece-" + id}

@ -408,7 +408,6 @@ export function drainTerminalStateOnChildContent(
childContent.components, childContent.components,
) )
if (!childComponent) { if (!childComponent) {
//if the child does not contain the parent's component, add it to the children's content. //if the child does not contain the parent's component, add it to the children's content.
childContent = { childContent = {
@ -432,7 +431,6 @@ export function drainTerminalStateOnChildContent(
}, },
childContent, childContent,
) )
} }
// ensure that the component is a player // ensure that the component is a player

@ -716,7 +716,7 @@ function EditorPage({
const contentNode = ( const contentNode = (
<div id="content-div"> <div id="content-div">
<div id="racks" > <div id="racks">
<PlayerRack <PlayerRack
id={"allies"} id={"allies"}
objects={allies} objects={allies}

@ -52,7 +52,9 @@ export default function ProfileSettings() {
useEffect(() => { useEffect(() => {
passwordConfirmRef.current!.setCustomValidity( passwordConfirmRef.current!.setCustomValidity(
password === confirmPassword ? "" : "Les mots de passe ne correspondent pas !" password === confirmPassword
? ""
: "Les mots de passe ne correspondent pas !",
) )
}, [confirmPassword, password]) }, [confirmPassword, password])
@ -110,9 +112,8 @@ export default function ProfileSettings() {
autoComplete="username" autoComplete="username"
required required
placeholder="Nom d'utilisateur" placeholder="Nom d'utilisateur"
value={name} value={name}
onChange={e => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> />
<label htmlFor="email">Adresse email</label> <label htmlFor="email">Adresse email</label>
@ -124,9 +125,8 @@ export default function ProfileSettings() {
placeholder={"Adresse email"} placeholder={"Adresse email"}
autoComplete="email" autoComplete="email"
required required
value={email} value={email}
onChange={e => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
/> />
<label htmlFor="password">Mot de passe</label> <label htmlFor="password">Mot de passe</label>
@ -137,12 +137,13 @@ export default function ProfileSettings() {
type="password" type="password"
placeholder={"Mot de passe"} placeholder={"Mot de passe"}
autoComplete="new-password" autoComplete="new-password"
value={password} value={password}
onChange={e => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
/> />
<label htmlFor="confirmPassword">Confirmez le mot de passe</label> <label htmlFor="confirmPassword">
Confirmez le mot de passe
</label>
<input <input
ref={passwordConfirmRef} ref={passwordConfirmRef}
className="settings-input" className="settings-input"
@ -151,14 +152,13 @@ export default function ProfileSettings() {
type="password" type="password"
autoComplete="new-password" autoComplete="new-password"
placeholder={"Confirmation du mot de passe"} placeholder={"Confirmation du mot de passe"}
value={confirmPassword} value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)} onChange={(e) =>
setConfirmPassword(e.target.value)
}
/> />
<button <button className="settings-button" type="submit">
className="settings-button"
type="submit">
Mettre à jour Mettre à jour
</button> </button>
</form> </form>
@ -207,25 +207,28 @@ function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
if (e.key === "Escape") onHide() if (e.key === "Escape") onHide()
} }
window.addEventListener('keyup', onKeyUp) window.addEventListener("keyup", onKeyUp)
return () => window.removeEventListener('keyup', onKeyUp) return () => window.removeEventListener("keyup", onKeyUp)
}, [onHide]) }, [onHide])
const handleForm = useCallback(async (e: FormEvent) => { const handleForm = useCallback(
e.preventDefault() async (e: FormEvent) => {
e.preventDefault()
const url = urlRef.current!.value const url = urlRef.current!.value
const errors = await updateAccount(fetcher, { const errors = await updateAccount(fetcher, {
profilePicture: url, profilePicture: url,
}) })
if (errors.length !== 0) { if (errors.length !== 0) {
setErrorMessages(errors) setErrorMessages(errors)
return return
} }
setUser({ ...user!, profilePicture: url }) setUser({ ...user!, profilePicture: url })
setErrorMessages([]) setErrorMessages([])
onHide() onHide()
}, [fetcher, onHide, setUser, user]) },
[fetcher, onHide, setUser, user],
)
if (!show) return <></> if (!show) return <></>
@ -243,7 +246,9 @@ function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
{msg} {msg}
</div> </div>
))} ))}
<label id="profile-picture-popup-subtitle" htmlFor="profile-picture"> <label
id="profile-picture-popup-subtitle"
htmlFor="profile-picture">
Saisissez le lien vers votre nouvelle photo de profil Saisissez le lien vers votre nouvelle photo de profil
</label> </label>
<input <input
@ -260,7 +265,7 @@ function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
required required
placeholder={"lien vers une image"} placeholder={"lien vers une image"}
value={link} value={link}
onChange={e => setLink(e.target.value)} onChange={(e) => setLink(e.target.value)}
/> />
<div id="profile-picture-popup-footer"> <div id="profile-picture-popup-footer">
<button className={"settings-button"} onClick={onHide}> <button className={"settings-button"} onClick={onHide}>
@ -270,7 +275,7 @@ function ProfileImageInputPopup({ show, onHide }: ProfileImageInputPopupProps) {
type="submit" type="submit"
className={"settings-button"} className={"settings-button"}
value="Valider" value="Valider"
/> />
</div> </div>
</form> </form>
</dialog> </dialog>

@ -1,7 +1,11 @@
import { ServiceError, TacticService } from "../service/MutableTacticService.ts" import { ServiceError, TacticService } from "../service/MutableTacticService.ts"
import { useNavigate, useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { useCallback, useEffect, useMemo, useReducer, useState } from "react" import { useCallback, useEffect, useMemo, useState } from "react"
import { VisualizerState, VisualizerStateActionKind, visualizerStateReducer } from "../visualizer/VisualizerState.ts" import {
useVisualizer,
VisualizerState,
VisualizerStateActionKind,
} from "../visualizer/VisualizerState.ts"
import { getParent } from "../domains/StepsDomain.ts" import { getParent } from "../domains/StepsDomain.ts"
import { mapToParentContent } from "../domains/TacticContentDomains.ts" import { mapToParentContent } from "../domains/TacticContentDomains.ts"
import StepsTree from "../components/editor/StepsTree.tsx" import StepsTree from "../components/editor/StepsTree.tsx"
@ -25,14 +29,18 @@ export function VisualizerPage({ guestMode }: VisualizerPageProps) {
const fetcher = useAppFetcher() const fetcher = useAppFetcher()
if (guestMode || !idStr) { if (guestMode || !idStr) {
return ( return (
<ServedVisualizerPage service={LocalStorageTacticService.init()} <ServedVisualizerPage
openEditor={() => navigate("/tactic/edit-guest")} /> service={LocalStorageTacticService.init()}
openEditor={() => navigate("/tactic/edit-guest")}
/>
) )
} }
return ( return (
<ServedVisualizerPage service={new APITacticService(fetcher, parseInt(idStr))} <ServedVisualizerPage
openEditor={() => navigate(`/tactic/${idStr}/edit`)} /> service={new APITacticService(fetcher, parseInt(idStr))}
openEditor={() => navigate(`/tactic/${idStr}/edit`)}
/>
) )
} }
@ -47,9 +55,12 @@ interface ServedVisualizerPageProps {
openEditor: () => void openEditor: () => void
} }
function ServedVisualizerPage({ service, openEditor }: ServedVisualizerPageProps) { function ServedVisualizerPage({
service,
openEditor,
}: ServedVisualizerPageProps) {
const [panicMessage, setPanicMessage] = useState<string>() const [panicMessage, setPanicMessage] = useState<string>()
const [state, dispatch] = useReducer(visualizerStateReducer, null) const [state, dispatch] = useVisualizer(null)
const [canEdit, setCanEdit] = useState(false) const [canEdit, setCanEdit] = useState(false)
useEffect(() => { useEffect(() => {
@ -58,7 +69,7 @@ function ServedVisualizerPage({ service, openEditor }: ServedVisualizerPageProps
if (typeof contextResult === "string") { if (typeof contextResult === "string") {
setPanicMessage( setPanicMessage(
"There has been an error retrieving the editor initial context : " + "There has been an error retrieving the editor initial context : " +
contextResult, contextResult,
) )
return return
} }
@ -69,7 +80,7 @@ function ServedVisualizerPage({ service, openEditor }: ServedVisualizerPageProps
if (typeof contentResult === "string") { if (typeof contentResult === "string") {
setPanicMessage( setPanicMessage(
"There has been an error retrieving the tactic's root step content : " + "There has been an error retrieving the tactic's root step content : " +
contentResult, contentResult,
) )
return return
} }
@ -118,7 +129,6 @@ function ServedVisualizerPage({ service, openEditor }: ServedVisualizerPageProps
[openEditor, service, state], [openEditor, service, state],
) )
if (panicMessage) { if (panicMessage) {
return <p>{panicMessage}</p> return <p>{panicMessage}</p>
} }
@ -127,7 +137,13 @@ function ServedVisualizerPage({ service, openEditor }: ServedVisualizerPageProps
return <p>Retrieving tactic context. Please wait...</p> return <p>Retrieving tactic context. Please wait...</p>
} }
return <VisualizerPageContent state={state} service={visualizerService} showEditButton={canEdit} /> return (
<VisualizerPageContent
state={state}
service={visualizerService}
showEditButton={canEdit}
/>
)
} }
interface VisualizerPageContentProps { interface VisualizerPageContentProps {
@ -137,10 +153,10 @@ interface VisualizerPageContentProps {
} }
function VisualizerPageContent({ function VisualizerPageContent({
state: { content, parentContent, stepId, stepsTree, courtType, tacticName }, state: { content, parentContent, stepId, stepsTree, courtType, tacticName },
service, service,
showEditButton, showEditButton,
}: VisualizerPageContentProps) { }: VisualizerPageContentProps) {
const [isStepsTreeVisible, setStepsTreeVisible] = useState(true) const [isStepsTreeVisible, setStepsTreeVisible] = useState(true)
const [editorContentCurtainWidth, setEditorContentCurtainWidth] = const [editorContentCurtainWidth, setEditorContentCurtainWidth] =
@ -179,12 +195,12 @@ function VisualizerPageContent({
onClick={() => setStepsTreeVisible((b) => !b)}> onClick={() => setStepsTreeVisible((b) => !b)}>
ETAPES ETAPES
</button> </button>
{showEditButton && <button {showEditButton && (
onClick={() => service.openEditor()}> <button onClick={() => service.openEditor()}>
EDITER EDITER
</button>} </button>
)}
</div> </div>
</div> </div>
<div id="editor-div"> <div id="editor-div">

@ -16,7 +16,9 @@ export class APITacticService implements MutableTacticService {
} }
async canBeEdited(): Promise<boolean> { async canBeEdited(): Promise<boolean> {
const response = await this.fetcher.fetchAPIGet(`tactics/${this.tacticId}/can-edit`) const response = await this.fetcher.fetchAPIGet(
`tactics/${this.tacticId}/can-edit`,
)
const { canEdit } = await response.json() const { canEdit } = await response.json()
return canEdit return canEdit
} }

@ -33,7 +33,7 @@ export class LocalStorageTacticService implements MutableTacticService {
) )
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + 1, GUEST_MODE_STEP_CONTENT_STORAGE_KEY + 1,
JSON.stringify(<StepContent>{components: []}) JSON.stringify(<StepContent>{ components: [] }),
) )
} }

@ -1,4 +1,5 @@
import { CourtType, StepContent, StepInfoNode } from "../model/tactic/Tactic.ts" import { CourtType, StepContent, StepInfoNode } from "../model/tactic/Tactic.ts"
import { useReducer } from "react"
export interface VisualizerState { export interface VisualizerState {
stepId: number stepId: number
@ -9,7 +10,7 @@ export interface VisualizerState {
parentContent: StepContent | null parentContent: StepContent | null
} }
export enum VisualizerStateActionKind { export const enum VisualizerStateActionKind {
INIT, INIT,
SET_CONTENTS, SET_CONTENTS,
} }
@ -26,7 +27,11 @@ export type VisualizerStateAction =
stepId: number stepId: number
} }
export function visualizerStateReducer( export function useVisualizer(initialState: VisualizerState | null) {
return useReducer(visualizerStateReducer, initialState)
}
function visualizerStateReducer(
state: VisualizerState | null, state: VisualizerState | null,
action: VisualizerStateAction, action: VisualizerStateAction,
): VisualizerState | null { ): VisualizerState | null {

Loading…
Cancel
Save