|
|
|
@ -42,6 +42,7 @@ import {
|
|
|
|
|
dropBallOnComponent,
|
|
|
|
|
getComponentCollided,
|
|
|
|
|
getRackPlayers,
|
|
|
|
|
getTerminalState,
|
|
|
|
|
moveComponent,
|
|
|
|
|
placeBallAt,
|
|
|
|
|
placeObjectAt,
|
|
|
|
@ -79,7 +80,12 @@ import {
|
|
|
|
|
import { CourtBall } from "../components/editor/CourtBall"
|
|
|
|
|
import { useNavigate, useParams } from "react-router-dom"
|
|
|
|
|
import StepsTree from "../components/editor/StepsTree"
|
|
|
|
|
import { addStepNode, getAvailableId, getParent, removeStepNode } from "../editor/StepsDomain"
|
|
|
|
|
import {
|
|
|
|
|
addStepNode,
|
|
|
|
|
getAvailableId,
|
|
|
|
|
getParent,
|
|
|
|
|
removeStepNode,
|
|
|
|
|
} from "../editor/StepsDomain"
|
|
|
|
|
|
|
|
|
|
const ERROR_STYLE: CSSProperties = {
|
|
|
|
|
borderColor: "red",
|
|
|
|
@ -96,7 +102,6 @@ interface TacticDto {
|
|
|
|
|
id: number
|
|
|
|
|
name: string
|
|
|
|
|
courtType: CourtType
|
|
|
|
|
content: { components: TacticComponent[] }
|
|
|
|
|
root: StepInfoNode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -108,78 +113,124 @@ export default function Editor({ guestMode }: EditorPageProps) {
|
|
|
|
|
return <EditorPortal guestMode={guestMode} />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function EditorPortal({ guestMode }: EditorPageProps) {
|
|
|
|
|
return guestMode ? <GuestModeEditor /> : <UserModeEditor />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function GuestModeEditor() {
|
|
|
|
|
const storageContent = localStorage.getItem(GUEST_MODE_STEP_CONTENT_STORAGE_KEY + "0")
|
|
|
|
|
const storageContent = localStorage.getItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + "0",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const stepInitialContent = ({
|
|
|
|
|
...(storageContent == null ? { components: [] } : JSON.parse(storageContent)),
|
|
|
|
|
const stepInitialContent = {
|
|
|
|
|
...(storageContent == null
|
|
|
|
|
? { components: [] }
|
|
|
|
|
: JSON.parse(storageContent)),
|
|
|
|
|
stepId: 0,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize local storage if we launch in guest mode
|
|
|
|
|
if (storageContent == null) {
|
|
|
|
|
localStorage.setItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, JSON.stringify({ id: 0, children: [] }))
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
JSON.stringify({ id: 0, children: [] }),
|
|
|
|
|
)
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + stepInitialContent.stepId,
|
|
|
|
|
JSON.stringify(stepInitialContent),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [stepId, setStepId] = useState(DEFAULT_STEP_ID)
|
|
|
|
|
const [stepContent, setStepContent, saveState] = useContentState(
|
|
|
|
|
stepInitialContent,
|
|
|
|
|
SaveStates.Guest,
|
|
|
|
|
useMemo(() => debounceAsync(async (content: StepContent) => {
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + stepId,
|
|
|
|
|
JSON.stringify(content),
|
|
|
|
|
)
|
|
|
|
|
return SaveStates.Guest
|
|
|
|
|
}, 250), [stepId]),
|
|
|
|
|
useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
debounceAsync(async (content: StepContent) => {
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + stepId,
|
|
|
|
|
JSON.stringify(content),
|
|
|
|
|
)
|
|
|
|
|
return SaveStates.Guest
|
|
|
|
|
}, 250),
|
|
|
|
|
[stepId],
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return <EditorPage
|
|
|
|
|
tactic={({
|
|
|
|
|
id: -1,
|
|
|
|
|
rootStepNode: JSON.parse(localStorage.getItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY)!),
|
|
|
|
|
name: localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) ?? "Nouvelle Tactique",
|
|
|
|
|
courtType: "PLAIN",
|
|
|
|
|
})}
|
|
|
|
|
currentStepContent={stepContent}
|
|
|
|
|
setCurrentStepContent={(content) => setStepContent(content, true)}
|
|
|
|
|
saveState={saveState}
|
|
|
|
|
currentStepId={stepId}
|
|
|
|
|
onNameChange={useCallback(async name => {
|
|
|
|
|
localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name)
|
|
|
|
|
return true //simulate that the name has been changed
|
|
|
|
|
}, [])}
|
|
|
|
|
selectStep={useCallback(step => {
|
|
|
|
|
setStepId(step)
|
|
|
|
|
setStepContent({ ...JSON.parse(localStorage.getItem(GUEST_MODE_STEP_CONTENT_STORAGE_KEY + step)!) }, false)
|
|
|
|
|
return
|
|
|
|
|
}, [setStepContent])}
|
|
|
|
|
onAddStep={useCallback(async (parent, content) => {
|
|
|
|
|
const root: StepInfoNode = JSON.parse(localStorage.getItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY)!)
|
|
|
|
|
|
|
|
|
|
const nodeId = getAvailableId(root)
|
|
|
|
|
const node = { id: nodeId, children: [] }
|
|
|
|
|
|
|
|
|
|
localStorage.setItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, JSON.stringify(addStepNode(root, parent, node)))
|
|
|
|
|
localStorage.setItem(GUEST_MODE_STEP_CONTENT_STORAGE_KEY + node.id, JSON.stringify(content))
|
|
|
|
|
return node
|
|
|
|
|
}, [])}
|
|
|
|
|
onRemoveStep={useCallback(async step => {
|
|
|
|
|
const root: StepInfoNode = JSON.parse(localStorage.getItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY)!)
|
|
|
|
|
localStorage.setItem(GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, JSON.stringify(removeStepNode(root, step)))
|
|
|
|
|
return true
|
|
|
|
|
}, [])}
|
|
|
|
|
/>
|
|
|
|
|
return (
|
|
|
|
|
<EditorPage
|
|
|
|
|
tactic={{
|
|
|
|
|
id: -1,
|
|
|
|
|
rootStepNode: JSON.parse(
|
|
|
|
|
localStorage.getItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
)!,
|
|
|
|
|
),
|
|
|
|
|
name:
|
|
|
|
|
localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) ??
|
|
|
|
|
"Nouvelle Tactique",
|
|
|
|
|
courtType: "PLAIN",
|
|
|
|
|
}}
|
|
|
|
|
currentStepContent={stepContent}
|
|
|
|
|
setCurrentStepContent={(content) => setStepContent(content, true)}
|
|
|
|
|
saveState={saveState}
|
|
|
|
|
currentStepId={stepId}
|
|
|
|
|
onNameChange={useCallback(async (name) => {
|
|
|
|
|
localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name)
|
|
|
|
|
return true //simulate that the name has been changed
|
|
|
|
|
}, [])}
|
|
|
|
|
selectStep={useCallback(
|
|
|
|
|
(step) => {
|
|
|
|
|
setStepId(step)
|
|
|
|
|
setStepContent(
|
|
|
|
|
{
|
|
|
|
|
...JSON.parse(
|
|
|
|
|
localStorage.getItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + step,
|
|
|
|
|
)!,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
},
|
|
|
|
|
[setStepContent],
|
|
|
|
|
)}
|
|
|
|
|
onAddStep={useCallback(async (parent, content) => {
|
|
|
|
|
const root: StepInfoNode = JSON.parse(
|
|
|
|
|
localStorage.getItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
)!,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const nodeId = getAvailableId(root)
|
|
|
|
|
const node = { id: nodeId, children: [] }
|
|
|
|
|
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
JSON.stringify(addStepNode(root, parent, node)),
|
|
|
|
|
)
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_CONTENT_STORAGE_KEY + node.id,
|
|
|
|
|
JSON.stringify(content),
|
|
|
|
|
)
|
|
|
|
|
return node
|
|
|
|
|
}, [])}
|
|
|
|
|
onRemoveStep={useCallback(async (step) => {
|
|
|
|
|
const root: StepInfoNode = JSON.parse(
|
|
|
|
|
localStorage.getItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
)!,
|
|
|
|
|
)
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY,
|
|
|
|
|
JSON.stringify(removeStepNode(root, step)),
|
|
|
|
|
)
|
|
|
|
|
return true
|
|
|
|
|
}, [])}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function UserModeEditor() {
|
|
|
|
@ -188,12 +239,36 @@ function UserModeEditor() {
|
|
|
|
|
const id = parseInt(idStr!)
|
|
|
|
|
const navigation = useNavigate()
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const [stepId, setStepId] = useState(1)
|
|
|
|
|
const [stepContent, setStepContent, saveState] =
|
|
|
|
|
useContentState<StepContent>(
|
|
|
|
|
{ components: [] },
|
|
|
|
|
SaveStates.Ok,
|
|
|
|
|
useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
debounceAsync(async (content: StepContent) => {
|
|
|
|
|
const response = await fetchAPI(
|
|
|
|
|
`tactics/${id}/steps/${stepId}`,
|
|
|
|
|
{
|
|
|
|
|
content: {
|
|
|
|
|
components: content.components,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"PUT",
|
|
|
|
|
)
|
|
|
|
|
return response.ok ? SaveStates.Ok : SaveStates.Err
|
|
|
|
|
}, 250),
|
|
|
|
|
[id, stepId],
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
async function initialize() {
|
|
|
|
|
const infoResponsePromise = fetchAPIGet(`tactics/${id}`)
|
|
|
|
|
const treeResponsePromise = fetchAPIGet(`tactics/${id}/tree`)
|
|
|
|
|
const contentResponsePromise = fetchAPIGet(`tactics/${id}/steps/${DEFAULT_STEP_ID}`)
|
|
|
|
|
const contentResponsePromise = fetchAPIGet(
|
|
|
|
|
`tactics/${id}/steps/${DEFAULT_STEP_ID}`,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const infoResponse = await infoResponsePromise
|
|
|
|
|
const treeResponse = await treeResponsePromise
|
|
|
|
@ -212,82 +287,70 @@ function UserModeEditor() {
|
|
|
|
|
const content = await contentResponse.json()
|
|
|
|
|
const { root } = await treeResponse.json()
|
|
|
|
|
|
|
|
|
|
setTactic({ id, name, courtType, content, root })
|
|
|
|
|
setTactic({ id, name, courtType, root })
|
|
|
|
|
setStepContent(content, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initialize()
|
|
|
|
|
}, [id, idStr, navigation])
|
|
|
|
|
|
|
|
|
|
const onNameChange = useCallback(
|
|
|
|
|
(name: string) =>
|
|
|
|
|
fetchAPI(`tactics/${id}/name`, { name }, "PUT").then((r) => r.ok),
|
|
|
|
|
[id],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const [stepId, setStepId] = useState(1)
|
|
|
|
|
const [stepContent, setStepContent, saveState] = useContentState(
|
|
|
|
|
tactic?.content ?? { components: [] },
|
|
|
|
|
SaveStates.Ok,
|
|
|
|
|
useMemo(() => debounceAsync(async (content: StepContent) => {
|
|
|
|
|
const response = await fetchAPI(
|
|
|
|
|
`tactics/${id}/steps/${stepId}`,
|
|
|
|
|
{
|
|
|
|
|
content: {
|
|
|
|
|
components: content.components,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"PUT",
|
|
|
|
|
)
|
|
|
|
|
return response.ok ? SaveStates.Ok : SaveStates.Err
|
|
|
|
|
}, 250), [id, stepId]),
|
|
|
|
|
const selectStep = useCallback(
|
|
|
|
|
async (step: number) => {
|
|
|
|
|
const response = await fetchAPIGet(`tactics/${id}/steps/${step}`)
|
|
|
|
|
if (!response.ok) return
|
|
|
|
|
setStepId(step)
|
|
|
|
|
setStepContent({ ...(await response.json()) }, false)
|
|
|
|
|
},
|
|
|
|
|
[id, setStepContent],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const onAddStep = useCallback(
|
|
|
|
|
async (parent: StepInfoNode, content: StepContent) => {
|
|
|
|
|
const response = await fetchAPI(`tactics/${id}/steps`, {
|
|
|
|
|
parentId: parent.id,
|
|
|
|
|
content,
|
|
|
|
|
})
|
|
|
|
|
if (!response.ok) return null
|
|
|
|
|
const { stepId } = await response.json()
|
|
|
|
|
return { id: stepId, children: [] }
|
|
|
|
|
},
|
|
|
|
|
[id],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const onRemoveStep = useCallback(
|
|
|
|
|
(step: StepInfoNode) =>
|
|
|
|
|
fetchAPI(`tactics/${id}/steps/${step.id}`, {}, "DELETE").then(
|
|
|
|
|
(r) => r.ok,
|
|
|
|
|
),
|
|
|
|
|
[id],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!tactic) return <EditorLoadingScreen />
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<EditorPage
|
|
|
|
|
tactic={{
|
|
|
|
|
id,
|
|
|
|
|
name: tactic?.name ?? "",
|
|
|
|
|
rootStepNode: tactic?.root ?? { id: stepId, children: [] },
|
|
|
|
|
courtType: tactic?.courtType,
|
|
|
|
|
}}
|
|
|
|
|
currentStepId={stepId}
|
|
|
|
|
currentStepContent={stepContent}
|
|
|
|
|
setCurrentStepContent={(content) => setStepContent(content, true)}
|
|
|
|
|
saveState={saveState}
|
|
|
|
|
onNameChange={onNameChange}
|
|
|
|
|
selectStep={selectStep}
|
|
|
|
|
onAddStep={onAddStep}
|
|
|
|
|
onRemoveStep={onRemoveStep}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
const onNameChange = useCallback((name: string) =>
|
|
|
|
|
fetchAPI(`tactics/${id}/name`, { name }, "PUT")
|
|
|
|
|
.then((r) => r.ok)
|
|
|
|
|
, [id])
|
|
|
|
|
|
|
|
|
|
const selectStep = useCallback(async (step: number) => {
|
|
|
|
|
const response = await fetchAPIGet(`tactics/${id}/steps/${step}`)
|
|
|
|
|
if (!response.ok)
|
|
|
|
|
return
|
|
|
|
|
setStepId(step)
|
|
|
|
|
setStepContent({ ...await response.json() }, false)
|
|
|
|
|
}, [id, setStepContent])
|
|
|
|
|
|
|
|
|
|
const onAddStep = useCallback(async (parent: StepInfoNode, content: StepContent) => {
|
|
|
|
|
const response = await fetchAPI(`tactics/${id}/steps`, {
|
|
|
|
|
parentId: parent.id,
|
|
|
|
|
content
|
|
|
|
|
})
|
|
|
|
|
if (!response.ok)
|
|
|
|
|
return null
|
|
|
|
|
const { stepId } = await response.json()
|
|
|
|
|
return { id: stepId, children: [] }
|
|
|
|
|
}, [id])
|
|
|
|
|
|
|
|
|
|
const onRemoveStep = useCallback((step: StepInfoNode) =>
|
|
|
|
|
fetchAPI(
|
|
|
|
|
`tactics/${id}/steps/${step.id}`,
|
|
|
|
|
{},
|
|
|
|
|
"DELETE",
|
|
|
|
|
).then(r => r.ok)
|
|
|
|
|
, [id])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!tactic)
|
|
|
|
|
return <EditorLoadingScreen />
|
|
|
|
|
|
|
|
|
|
return <EditorPage
|
|
|
|
|
tactic={({
|
|
|
|
|
id,
|
|
|
|
|
name: tactic?.name ?? "",
|
|
|
|
|
rootStepNode: tactic?.root ?? { id: stepId, children: [] },
|
|
|
|
|
courtType: tactic?.courtType,
|
|
|
|
|
})}
|
|
|
|
|
currentStepId={stepId}
|
|
|
|
|
currentStepContent={stepContent}
|
|
|
|
|
setCurrentStepContent={(content) => setStepContent(content, true)}
|
|
|
|
|
saveState={saveState}
|
|
|
|
|
onNameChange={onNameChange}
|
|
|
|
|
selectStep={selectStep}
|
|
|
|
|
onAddStep={onAddStep}
|
|
|
|
|
onRemoveStep={onRemoveStep}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function EditorLoadingScreen() {
|
|
|
|
@ -304,26 +367,23 @@ export interface EditorViewProps {
|
|
|
|
|
selectStep: (stepId: number) => void
|
|
|
|
|
onNameChange: (name: string) => Promise<boolean>
|
|
|
|
|
onRemoveStep: (step: StepInfoNode) => Promise<boolean>
|
|
|
|
|
onAddStep: (parent: StepInfoNode, content: StepContent) => Promise<StepInfoNode | null>
|
|
|
|
|
onAddStep: (
|
|
|
|
|
parent: StepInfoNode,
|
|
|
|
|
content: StepContent,
|
|
|
|
|
) => Promise<StepInfoNode | null>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function EditorPage({
|
|
|
|
|
tactic: {
|
|
|
|
|
name,
|
|
|
|
|
rootStepNode: initialStepsNode,
|
|
|
|
|
courtType,
|
|
|
|
|
},
|
|
|
|
|
setCurrentStepContent: setContent,
|
|
|
|
|
currentStepContent: content,
|
|
|
|
|
saveState,
|
|
|
|
|
onNameChange,
|
|
|
|
|
selectStep,
|
|
|
|
|
onRemoveStep,
|
|
|
|
|
onAddStep,
|
|
|
|
|
}: EditorViewProps) {
|
|
|
|
|
|
|
|
|
|
tactic: { name, rootStepNode: initialStepsNode, courtType },
|
|
|
|
|
currentStepId,
|
|
|
|
|
setCurrentStepContent: setContent,
|
|
|
|
|
currentStepContent: content,
|
|
|
|
|
saveState,
|
|
|
|
|
onNameChange,
|
|
|
|
|
selectStep,
|
|
|
|
|
onRemoveStep,
|
|
|
|
|
onAddStep,
|
|
|
|
|
}: EditorViewProps) {
|
|
|
|
|
const [titleStyle, setTitleStyle] = useState<CSSProperties>({})
|
|
|
|
|
|
|
|
|
|
const [rootStepsNode, setRootStepsNode] = useState(initialStepsNode)
|
|
|
|
@ -693,29 +753,35 @@ function EditorPage({
|
|
|
|
|
</div>
|
|
|
|
|
<EditorStepsTree
|
|
|
|
|
isVisible={isStepsTreeVisible}
|
|
|
|
|
selectedStepId={currentStepId}
|
|
|
|
|
root={rootStepsNode}
|
|
|
|
|
onAddChildren={useCallback(
|
|
|
|
|
async (parent) => {
|
|
|
|
|
const addedNode = await onAddStep(parent, content)
|
|
|
|
|
const addedNode = await onAddStep(
|
|
|
|
|
parent,
|
|
|
|
|
getTerminalState(content, courtBounds()),
|
|
|
|
|
)
|
|
|
|
|
if (addedNode == null) {
|
|
|
|
|
console.error(
|
|
|
|
|
"could not add step : onAddStep returned null node",
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
selectStep(addedNode.id)
|
|
|
|
|
setRootStepsNode((root) =>
|
|
|
|
|
addStepNode(root, parent, addedNode),
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
[content, onAddStep],
|
|
|
|
|
[content, courtBounds, onAddStep, selectStep],
|
|
|
|
|
)}
|
|
|
|
|
onRemoveNode={useCallback(
|
|
|
|
|
async (removed) => {
|
|
|
|
|
const isOk = await onRemoveStep(removed)
|
|
|
|
|
selectStep(getParent(rootStepsNode, removed)!.id)
|
|
|
|
|
if (isOk) setRootStepsNode(
|
|
|
|
|
(root) => removeStepNode(root, removed)!,
|
|
|
|
|
)
|
|
|
|
|
if (isOk)
|
|
|
|
|
setRootStepsNode(
|
|
|
|
|
(root) => removeStepNode(root, removed)!,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
[rootStepsNode, onRemoveStep, selectStep],
|
|
|
|
|
)}
|
|
|
|
@ -731,6 +797,7 @@ function EditorPage({
|
|
|
|
|
|
|
|
|
|
interface EditorStepsTreeProps {
|
|
|
|
|
isVisible: boolean
|
|
|
|
|
selectedStepId: number
|
|
|
|
|
root: StepInfoNode
|
|
|
|
|
onAddChildren: (parent: StepInfoNode) => void
|
|
|
|
|
onRemoveNode: (node: StepInfoNode) => void
|
|
|
|
@ -739,6 +806,7 @@ interface EditorStepsTreeProps {
|
|
|
|
|
|
|
|
|
|
function EditorStepsTree({
|
|
|
|
|
isVisible,
|
|
|
|
|
selectedStepId,
|
|
|
|
|
root,
|
|
|
|
|
onAddChildren,
|
|
|
|
|
onRemoveNode,
|
|
|
|
@ -752,6 +820,7 @@ function EditorStepsTree({
|
|
|
|
|
}}>
|
|
|
|
|
<StepsTree
|
|
|
|
|
root={root}
|
|
|
|
|
selectedStepId={selectedStepId}
|
|
|
|
|
onStepSelected={onStepSelected}
|
|
|
|
|
onAddChildren={onAddChildren}
|
|
|
|
|
onRemoveNode={onRemoveNode}
|
|
|
|
@ -869,7 +938,7 @@ function CourtPlayerArrowAction({
|
|
|
|
|
}))
|
|
|
|
|
}}
|
|
|
|
|
onHeadPicked={(headPos) => {
|
|
|
|
|
(document.activeElement as HTMLElement).blur()
|
|
|
|
|
;(document.activeElement as HTMLElement).blur()
|
|
|
|
|
|
|
|
|
|
setPreviewAction({
|
|
|
|
|
origin: playerInfo.id,
|
|
|
|
|