|
|
@ -42,6 +42,7 @@ import {
|
|
|
|
dropBallOnComponent,
|
|
|
|
dropBallOnComponent,
|
|
|
|
getComponentCollided,
|
|
|
|
getComponentCollided,
|
|
|
|
getRackPlayers,
|
|
|
|
getRackPlayers,
|
|
|
|
|
|
|
|
mapToParentContent,
|
|
|
|
moveComponent,
|
|
|
|
moveComponent,
|
|
|
|
placeBallAt,
|
|
|
|
placeBallAt,
|
|
|
|
placeObjectAt,
|
|
|
|
placeObjectAt,
|
|
|
@ -201,6 +202,8 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
|
|
|
|
[stepsVersions, service, stepId, stepsTree],
|
|
|
|
[stepsVersions, service, stepId, stepsTree],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [parentContent, setParentContent] = useState<StepContent | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
const [stepContent, setStepContent, saveState] =
|
|
|
|
const [stepContent, setStepContent, saveState] =
|
|
|
|
useContentState<StepContent>(
|
|
|
|
useContentState<StepContent>(
|
|
|
|
{ components: [] },
|
|
|
|
{ components: [] },
|
|
|
@ -270,21 +273,30 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
|
|
|
|
if (isNotInit) init()
|
|
|
|
if (isNotInit) init()
|
|
|
|
}, [isNotInit, service, setStepContent, stepsVersions])
|
|
|
|
}, [isNotInit, service, setStepContent, stepsVersions])
|
|
|
|
|
|
|
|
|
|
|
|
const editorService: EditorService = useMemo(
|
|
|
|
const editorService: EditorService = useMemo(() => {
|
|
|
|
() => ({
|
|
|
|
let internalStepsTree = stepsTree
|
|
|
|
|
|
|
|
return {
|
|
|
|
async addStep(
|
|
|
|
async addStep(
|
|
|
|
parent: StepInfoNode,
|
|
|
|
parent: StepInfoNode,
|
|
|
|
content: StepContent,
|
|
|
|
content: StepContent,
|
|
|
|
): Promise<StepInfoNode | ServiceError> {
|
|
|
|
): Promise<StepInfoNode | ServiceError> {
|
|
|
|
const result = await service.addStep(parent, content)
|
|
|
|
const result = await service.addStep(parent, content)
|
|
|
|
if (typeof result !== "string")
|
|
|
|
if (typeof result !== "string") {
|
|
|
|
setStepsTree(addStepNode(stepsTree!, parent, result))
|
|
|
|
internalStepsTree = addStepNode(
|
|
|
|
|
|
|
|
internalStepsTree!,
|
|
|
|
|
|
|
|
parent,
|
|
|
|
|
|
|
|
result,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
setStepsTree(internalStepsTree)
|
|
|
|
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
},
|
|
|
|
},
|
|
|
|
async removeStep(step: number): Promise<void | ServiceError> {
|
|
|
|
async removeStep(step: number): Promise<void | ServiceError> {
|
|
|
|
const result = await service.removeStep(step)
|
|
|
|
const result = await service.removeStep(step)
|
|
|
|
if (typeof result !== "string")
|
|
|
|
if (typeof result !== "string") {
|
|
|
|
setStepsTree(removeStepNode(stepsTree!, step))
|
|
|
|
internalStepsTree = removeStepNode(internalStepsTree!, step)
|
|
|
|
|
|
|
|
setStepsTree(internalStepsTree)
|
|
|
|
|
|
|
|
}
|
|
|
|
stepsVersions.delete(step)
|
|
|
|
stepsVersions.delete(step)
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
},
|
|
|
|
},
|
|
|
@ -303,12 +315,19 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
|
|
|
|
async selectStep(step: number): Promise<void | ServiceError> {
|
|
|
|
async selectStep(step: number): Promise<void | ServiceError> {
|
|
|
|
const result = await service.getContent(step)
|
|
|
|
const result = await service.getContent(step)
|
|
|
|
if (typeof result === "string") return result
|
|
|
|
if (typeof result === "string") return result
|
|
|
|
|
|
|
|
const stepParent = getParent(internalStepsTree!, step)?.id
|
|
|
|
|
|
|
|
if (stepParent) {
|
|
|
|
|
|
|
|
const parentResult = await service.getContent(stepParent)
|
|
|
|
|
|
|
|
if (typeof parentResult === "string") return parentResult
|
|
|
|
|
|
|
|
setParentContent(mapToParentContent(parentResult))
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
setParentContent(null)
|
|
|
|
|
|
|
|
}
|
|
|
|
setStepId(step)
|
|
|
|
setStepId(step)
|
|
|
|
setStepContent(result, false)
|
|
|
|
setStepContent(result, false)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
[stepsVersions, service, setStepContent, stepsTree],
|
|
|
|
}, [stepsVersions, service, setStepContent, stepsTree])
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (panicMessage) {
|
|
|
|
if (panicMessage) {
|
|
|
|
return <p>{panicMessage}</p>
|
|
|
|
return <p>{panicMessage}</p>
|
|
|
@ -325,6 +344,7 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
|
|
|
|
stepId={stepId}
|
|
|
|
stepId={stepId}
|
|
|
|
stepsTree={stepsTree}
|
|
|
|
stepsTree={stepsTree}
|
|
|
|
contentSaveState={saveState}
|
|
|
|
contentSaveState={saveState}
|
|
|
|
|
|
|
|
parentContent={parentContent}
|
|
|
|
content={stepContent}
|
|
|
|
content={stepContent}
|
|
|
|
service={editorService}
|
|
|
|
service={editorService}
|
|
|
|
courtRef={courtRef}
|
|
|
|
courtRef={courtRef}
|
|
|
@ -336,10 +356,12 @@ export interface EditorViewProps {
|
|
|
|
stepsTree: StepInfoNode
|
|
|
|
stepsTree: StepInfoNode
|
|
|
|
name: string
|
|
|
|
name: string
|
|
|
|
courtType: CourtType
|
|
|
|
courtType: CourtType
|
|
|
|
content: StepContent
|
|
|
|
|
|
|
|
contentSaveState: SaveState
|
|
|
|
contentSaveState: SaveState
|
|
|
|
stepId: number
|
|
|
|
stepId: number
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parentContent: StepContent | null
|
|
|
|
|
|
|
|
content: StepContent
|
|
|
|
|
|
|
|
|
|
|
|
courtRef: RefObject<HTMLDivElement>
|
|
|
|
courtRef: RefObject<HTMLDivElement>
|
|
|
|
|
|
|
|
|
|
|
|
service: EditorService
|
|
|
|
service: EditorService
|
|
|
@ -348,6 +370,7 @@ export interface EditorViewProps {
|
|
|
|
function EditorPage({
|
|
|
|
function EditorPage({
|
|
|
|
name,
|
|
|
|
name,
|
|
|
|
courtType,
|
|
|
|
courtType,
|
|
|
|
|
|
|
|
parentContent,
|
|
|
|
content,
|
|
|
|
content,
|
|
|
|
stepId,
|
|
|
|
stepId,
|
|
|
|
contentSaveState,
|
|
|
|
contentSaveState,
|
|
|
@ -515,9 +538,12 @@ function EditorPage({
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const renderPlayer = useCallback(
|
|
|
|
const renderPlayer = useCallback(
|
|
|
|
(component: PlayerLike) => {
|
|
|
|
(component: PlayerLike, isFromParent: boolean) => {
|
|
|
|
let info: PlayerInfo
|
|
|
|
let info: PlayerInfo
|
|
|
|
const isPhantom = component.type == "phantom"
|
|
|
|
const isPhantom = component.type == "phantom"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let forceFreeze = isFromParent
|
|
|
|
|
|
|
|
|
|
|
|
if (isPhantom) {
|
|
|
|
if (isPhantom) {
|
|
|
|
const origin = getOrigin(component, content.components)
|
|
|
|
const origin = getOrigin(component, content.components)
|
|
|
|
info = {
|
|
|
|
info = {
|
|
|
@ -535,24 +561,31 @@ function EditorPage({
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
info = component
|
|
|
|
info = component
|
|
|
|
|
|
|
|
|
|
|
|
if (component.frozen) {
|
|
|
|
forceFreeze ||= component.frozen
|
|
|
|
return (
|
|
|
|
}
|
|
|
|
<CourtPlayer
|
|
|
|
|
|
|
|
key={component.id}
|
|
|
|
const className =
|
|
|
|
playerInfo={info}
|
|
|
|
(isPhantom ? "phantom" : "player") +
|
|
|
|
className={"player"}
|
|
|
|
" " +
|
|
|
|
availableActions={() =>
|
|
|
|
(isFromParent ? "from-parent" : "")
|
|
|
|
renderAvailablePlayerActions(info, component)
|
|
|
|
|
|
|
|
}
|
|
|
|
if (forceFreeze) {
|
|
|
|
/>
|
|
|
|
return (
|
|
|
|
)
|
|
|
|
<CourtPlayer
|
|
|
|
}
|
|
|
|
key={component.id}
|
|
|
|
|
|
|
|
playerInfo={info}
|
|
|
|
|
|
|
|
className={className}
|
|
|
|
|
|
|
|
availableActions={() =>
|
|
|
|
|
|
|
|
renderAvailablePlayerActions(info, component)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<EditableCourtPlayer
|
|
|
|
<EditableCourtPlayer
|
|
|
|
key={component.id}
|
|
|
|
key={component.id}
|
|
|
|
className={isPhantom ? "phantom" : "player"}
|
|
|
|
className={className}
|
|
|
|
playerInfo={info}
|
|
|
|
playerInfo={info}
|
|
|
|
onPositionValidated={(newPos) =>
|
|
|
|
onPositionValidated={(newPos) =>
|
|
|
|
validatePlayerPosition(component, info, newPos)
|
|
|
|
validatePlayerPosition(component, info, newPos)
|
|
|
@ -603,11 +636,11 @@ function EditorPage({
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const renderComponent = useCallback(
|
|
|
|
const renderComponent = useCallback(
|
|
|
|
(component: TacticComponent) => {
|
|
|
|
(component: TacticComponent, isFromParent: boolean) => {
|
|
|
|
if (component.type === "player" || component.type === "phantom") {
|
|
|
|
if (component.type === "player" || component.type === "phantom") {
|
|
|
|
return renderPlayer(component)
|
|
|
|
return renderPlayer(component, isFromParent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (component.type === BALL_TYPE) {
|
|
|
|
if (component.type === BALL_TYPE && !isFromParent) {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<CourtBall
|
|
|
|
<CourtBall
|
|
|
|
key="ball"
|
|
|
|
key="ball"
|
|
|
@ -629,7 +662,7 @@ function EditorPage({
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const renderActions = useCallback(
|
|
|
|
const renderActions = useCallback(
|
|
|
|
(component: TacticComponent) =>
|
|
|
|
(component: TacticComponent, isFromParent: boolean) =>
|
|
|
|
component.actions.map((action, i) => {
|
|
|
|
component.actions.map((action, i) => {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<CourtAction
|
|
|
|
<CourtAction
|
|
|
@ -637,13 +670,16 @@ function EditorPage({
|
|
|
|
action={action}
|
|
|
|
action={action}
|
|
|
|
origin={component.id}
|
|
|
|
origin={component.id}
|
|
|
|
courtRef={courtRef}
|
|
|
|
courtRef={courtRef}
|
|
|
|
isInvalid={false}
|
|
|
|
color={isFromParent ? "gray" : "black"}
|
|
|
|
|
|
|
|
isEditable={!isFromParent}
|
|
|
|
onActionDeleted={() => {
|
|
|
|
onActionDeleted={() => {
|
|
|
|
doDeleteAction(action, i, component)
|
|
|
|
if (!isFromParent)
|
|
|
|
|
|
|
|
doDeleteAction(action, i, component)
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onActionChanges={(action) => {
|
|
|
|
|
|
|
|
if (!isFromParent)
|
|
|
|
|
|
|
|
doUpdateAction(component, action, i)
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
onActionChanges={(action) =>
|
|
|
|
|
|
|
|
doUpdateAction(component, action, i)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
}),
|
|
|
@ -697,6 +733,7 @@ function EditorPage({
|
|
|
|
<div id="court-div">
|
|
|
|
<div id="court-div">
|
|
|
|
<div id="court-div-bounds">
|
|
|
|
<div id="court-div-bounds">
|
|
|
|
<BasketCourt
|
|
|
|
<BasketCourt
|
|
|
|
|
|
|
|
parentComponents={parentContent?.components ?? null}
|
|
|
|
components={content.components}
|
|
|
|
components={content.components}
|
|
|
|
courtImage={<Court courtType={courtType} />}
|
|
|
|
courtImage={<Court courtType={courtType} />}
|
|
|
|
courtRef={courtRef}
|
|
|
|
courtRef={courtRef}
|
|
|
@ -730,7 +767,9 @@ function EditorPage({
|
|
|
|
onRemoveNode={useCallback(
|
|
|
|
onRemoveNode={useCallback(
|
|
|
|
async (removed) => {
|
|
|
|
async (removed) => {
|
|
|
|
await service.removeStep(removed.id)
|
|
|
|
await service.removeStep(removed.id)
|
|
|
|
await service.selectStep(getParent(stepsTree, removed)!.id)
|
|
|
|
await service.selectStep(
|
|
|
|
|
|
|
|
getParent(stepsTree, removed.id)!.id,
|
|
|
|
|
|
|
|
)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
[service, stepsTree],
|
|
|
|
[service, stepsTree],
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|