fix guest mode editor, avoid some illegal actions
continuous-integration/drone/push Build is passing Details

maxime 1 year ago
parent e9d6640171
commit 73d167e467

@ -1,34 +1,31 @@
import { useRef } from "react" import { KeyboardEventHandler, RefObject, useRef } from "react"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import { BallPiece } from "./BallPiece" import { BallPiece } from "./BallPiece"
import { NULL_POS } from "../../geo/Pos" import { NULL_POS, Pos } from "../../geo/Pos"
import { Ball } from "../../model/tactic/CourtObjects" import { Ball } from "../../model/tactic/CourtObjects"
export interface CourtBallProps { export interface CourtBallProps {
ball: Ball
}
export interface EditableCourtBallProps extends CourtBallProps {
onPosValidated: (rect: DOMRect) => void onPosValidated: (rect: DOMRect) => void
onRemove: () => void onRemove: () => void
ball: Ball
} }
export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) {
const pieceRef = useRef<HTMLDivElement>(null)
const { x, y } = ball.pos export function CourtBall({ onPosValidated, ball, onRemove }: EditableCourtBallProps) {
const pieceRef = useRef<HTMLDivElement>(null)
return ( function courtBallPiece({ x, y }: Pos,
<Draggable pieceRef?: RefObject<HTMLDivElement>,
onStop={() => onKeyUp?: KeyboardEventHandler) {
onPosValidated(pieceRef.current!.getBoundingClientRect()) return (
}
position={NULL_POS}
nodeRef={pieceRef}>
<div <div
className={"ball-div"} className={"ball-div"}
ref={pieceRef} ref={pieceRef}
tabIndex={0} tabIndex={0}
onKeyUp={(e) => { onKeyUp={onKeyUp}
if (e.key == "Delete") onRemove()
}}
style={{ style={{
position: "absolute", position: "absolute",
left: `${x * 100}%`, left: `${x * 100}%`,
@ -36,6 +33,30 @@ export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) {
}}> }}>
<BallPiece /> <BallPiece />
</div> </div>
)
}
if (ball.frozen) {
return courtBallPiece(ball.pos)
}
return (
<Draggable
onStop={() =>
onPosValidated(pieceRef.current!.getBoundingClientRect())
}
position={NULL_POS}
nodeRef={pieceRef}>
{courtBallPiece(
ball.pos,
pieceRef,
(e) => {
if (e.key == "Delete") onRemove()
},
)}
</Draggable> </Draggable>
) )
} }

@ -258,7 +258,7 @@ export function removePlayer(
content.components, content.components,
)! )!
const actions = playerBefore.actions.filter( const actions = playerBefore.actions.filter(
(a) => a.target === pos.attach, (a) => a.target !== pos.attach,
) )
content = updateComponent( content = updateComponent(
{ {

@ -77,6 +77,7 @@ export function placeObjectAt(
id: BALL_ID, id: BALL_ID,
pos, pos,
actions: [], actions: [],
frozen: false
} }
break break
} }
@ -153,6 +154,7 @@ export function placeBallAt(
id: BALL_ID, id: BALL_ID,
pos, pos,
actions: [], actions: [],
frozen: false,
} }
let components = content.components let components = content.components
@ -198,9 +200,9 @@ export function moveComponent(
phantomIdx == 0 phantomIdx == 0
? origin ? origin
: getComponent( : getComponent(
originPathItems[phantomIdx - 1], originPathItems[phantomIdx - 1],
content.components, content.components,
) )
// detach the action from the screen target and transform it to a regular move action to the phantom. // detach the action from the screen target and transform it to a regular move action to the phantom.
content = updateComponent( content = updateComponent(
{ {
@ -208,18 +210,18 @@ export function moveComponent(
actions: playerBeforePhantom.actions.map((a) => actions: playerBeforePhantom.actions.map((a) =>
a.target === referent a.target === referent
? { ? {
...a, ...a,
segments: a.segments.toSpliced( segments: a.segments.toSpliced(
a.segments.length - 2, a.segments.length - 2,
1, 1,
{ {
...a.segments[a.segments.length - 1], ...a.segments[a.segments.length - 1],
next: component.id, next: component.id,
}, },
), ),
target: component.id, target: component.id,
type: ActionKind.MOVE, type: ActionKind.MOVE,
} }
: a, : a,
), ),
}, },
@ -232,9 +234,9 @@ export function moveComponent(
...component, ...component,
pos: isPhantom pos: isPhantom
? { ? {
type: "fixed", type: "fixed",
...newPos, ...newPos,
} }
: newPos, : newPos,
}, },
content, content,
@ -313,12 +315,15 @@ export function computeTerminalState(
content.components.filter((c) => c.type !== "phantom") as ( content.components.filter((c) => c.type !== "phantom") as (
| Player | Player
| CourtObject | CourtObject
)[] )[]
const componentsTargetedState = nonPhantomComponents.map((comp) => const componentsTargetedState = nonPhantomComponents.map((comp) =>
comp.type === "player" comp.type === "player"
? getPlayerTerminalState(comp, content, computedPositions) ? getPlayerTerminalState(comp, content, computedPositions)
: comp, : {
...comp,
frozen: true,
},
) )
return { return {
@ -396,6 +401,11 @@ export function drainTerminalStateOnChildContent(
): StepContent | null { ): StepContent | null {
let gotUpdated = false let gotUpdated = false
//filter out all frozen components that are not present on the parent's terminal state anymore
childContent = {
components: childContent.components.filter(comp => comp.type === "phantom" || (comp.frozen && tryGetComponent(comp.id, parentTerminalState.components)))
}
for (const parentComponent of parentTerminalState.components) { for (const parentComponent of parentTerminalState.components) {
const childComponent = tryGetComponent( const childComponent = tryGetComponent(
parentComponent.id, parentComponent.id,

@ -7,4 +7,6 @@ export const BALL_TYPE = "ball"
//place here all different kinds of objects //place here all different kinds of objects
export type CourtObject = Ball export type CourtObject = Ball
export type Ball = Component<typeof BALL_TYPE, Pos> export interface Ball extends Component<typeof BALL_TYPE, Pos> {
readonly frozen: boolean
}

@ -131,7 +131,7 @@ function EditorPortal({ guestMode }: EditorPageProps) {
function GuestModeEditor() { function GuestModeEditor() {
const storageContent = localStorage.getItem( const storageContent = localStorage.getItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + "0", GUEST_MODE_STEP_CONTENT_STORAGE_KEY + ROOT_STEP_ID,
) )
const stepInitialContent: StepContent = { const stepInitialContent: StepContent = {
@ -148,7 +148,7 @@ function GuestModeEditor() {
if (storageContent == null) { if (storageContent == null) {
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
JSON.stringify({ id: 0, children: [] }), JSON.stringify({ id: ROOT_STEP_ID, children: [] }),
) )
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + ROOT_STEP_ID, GUEST_MODE_STEP_CONTENT_STORAGE_KEY + ROOT_STEP_ID,
@ -156,6 +156,8 @@ function GuestModeEditor() {
) )
} }
const tacticName = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) ?? "Nouvelle Tactique"
const courtRef = useRef<HTMLDivElement>(null) const courtRef = useRef<HTMLDivElement>(null)
const [stepId, setStepId] = useState(ROOT_STEP_ID) const [stepId, setStepId] = useState(ROOT_STEP_ID)
const [stepContent, setStepContent, saveState] = useContentState( const [stepContent, setStepContent, saveState] = useContentState(
@ -212,9 +214,7 @@ function GuestModeEditor() {
tactic={{ tactic={{
id: -1, id: -1,
rootStepNode, rootStepNode,
name: name: tacticName,
localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) ??
"Nouvelle Tactique",
courtType: "PLAIN", courtType: "PLAIN",
}} }}
courtRef={courtRef} courtRef={courtRef}
@ -244,9 +244,10 @@ function GuestModeEditor() {
const nodeId = getAvailableId(root) const nodeId = getAvailableId(root)
const node = { id: nodeId, children: [] } const node = { id: nodeId, children: [] }
const resultTree = addStepNode(root, parent, node)
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
JSON.stringify(addStepNode(root, parent, node)), JSON.stringify(resultTree),
) )
localStorage.setItem( localStorage.setItem(
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + node.id, GUEST_MODE_STEP_CONTENT_STORAGE_KEY + node.id,

Loading…
Cancel
Save