onAddChildren(node)}
+ onAddButtonClicked={() => {
+ if (onAddChildren) onAddChildren(node)
+ }}
onRemoveButtonClicked={
rootNode.id === node.id
? undefined
- : () => onRemoveNode(node)
+ : () => {
+ if (onRemoveNode) onRemoveNode(node)
+ }
}
- onSelected={() => onStepSelected(node)}>
+ onSelected={() => {
+ if (onStepSelected) onStepSelected(node)
+ }}>
{useMemo(
() => getStepName(rootNode, node.id),
diff --git a/src/editor/ActionsDomains.ts b/src/domains/ActionsDomains.ts
similarity index 98%
rename from src/editor/ActionsDomains.ts
rename to src/domains/ActionsDomains.ts
index 8ad9e5a..2fa3229 100644
--- a/src/editor/ActionsDomains.ts
+++ b/src/domains/ActionsDomains.ts
@@ -3,16 +3,16 @@ import {
Player,
PlayerLike,
PlayerPhantom,
-} from "../model/tactic/Player"
-import { ratioWithinBase } from "../geo/Pos"
+} from "../model/tactic/Player.ts"
+import { ratioWithinBase } from "../geo/Pos.ts"
import {
ComponentId,
StepContent,
TacticComponent,
-} from "../model/tactic/Tactic"
-import { overlaps } from "../geo/Box"
-import { Action, ActionKind, moves } from "../model/tactic/Action"
-import { removeBall, updateComponent } from "./TacticContentDomains"
+} from "../model/tactic/Tactic.ts"
+import { overlaps } from "../geo/Box.ts"
+import { Action, ActionKind, moves } from "../model/tactic/Action.ts"
+import { removeBall, updateComponent } from "./TacticContentDomains.ts"
import {
areInSamePath,
getComponent,
@@ -20,8 +20,8 @@ import {
getPlayerNextTo,
isNextInPath,
removePlayer,
-} from "./PlayerDomains"
-import { BALL_TYPE } from "../model/tactic/CourtObjects"
+} from "./PlayerDomains.ts"
+import { BALL_TYPE } from "../model/tactic/CourtObjects.ts"
export function getActionKind(
target: TacticComponent | null,
diff --git a/src/editor/PlayerDomains.ts b/src/domains/PlayerDomains.ts
similarity index 88%
rename from src/editor/PlayerDomains.ts
rename to src/domains/PlayerDomains.ts
index 2a7e28d..8c13c64 100644
--- a/src/editor/PlayerDomains.ts
+++ b/src/domains/PlayerDomains.ts
@@ -1,21 +1,22 @@
import {
BallState,
Player,
+ PlayerInfo,
PlayerLike,
PlayerPhantom,
-} from "../model/tactic/Player"
+} from "../model/tactic/Player.ts"
import {
ComponentId,
StepContent,
TacticComponent,
-} from "../model/tactic/Tactic"
+} from "../model/tactic/Tactic.ts"
-import { removeComponent, updateComponent } from "./TacticContentDomains"
+import { removeComponent, updateComponent } from "./TacticContentDomains.ts"
import {
removeAllActionsTargeting,
spreadNewStateFromOriginStateChange,
-} from "./ActionsDomains"
-import { ActionKind } from "../model/tactic/Action"
+} from "./ActionsDomains.ts"
+import { ActionKind } from "../model/tactic/Action.ts"
import {
add,
minus,
@@ -311,7 +312,7 @@ export function truncatePlayerPath(
for (let i = truncateStartIdx; i < path.items.length; i++) {
const pathPhantomId = path.items[i]
- //remove the phantom from the tactic
+ //remove the phantom from the domains
content = removeComponent(pathPhantomId, content)
content = removeAllActionsTargeting(pathPhantomId, content)
}
@@ -330,3 +331,46 @@ export function truncatePlayerPath(
content,
)
}
+
+export function getPhantomInfo(
+ phantom: PlayerPhantom,
+ content: StepContent,
+ relativePositions: ComputedRelativePositions,
+ courtBounds: DOMRect,
+): PlayerInfo {
+ const origin = getOrigin(phantom, content.components)
+
+ return {
+ id: phantom.id,
+ team: origin.team,
+ role: origin.role,
+ pos: computePhantomPositioning(
+ phantom,
+ content,
+ relativePositions,
+ courtBounds,
+ ),
+ ballState: phantom.ballState,
+ }
+}
+
+export type ComputedRelativePositions = Map
+
+export function computeRelativePositions(
+ courtBounds: DOMRect,
+ content: StepContent,
+) {
+ const relativePositionsCache: ComputedRelativePositions = new Map()
+
+ for (const component of content.components) {
+ if (component.type !== "phantom") continue
+ computePhantomPositioning(
+ component,
+ content,
+ relativePositionsCache,
+ courtBounds,
+ )
+ }
+
+ return relativePositionsCache
+}
diff --git a/src/editor/StepsDomain.ts b/src/domains/StepsDomain.ts
similarity index 97%
rename from src/editor/StepsDomain.ts
rename to src/domains/StepsDomain.ts
index a2b739a..a120c23 100644
--- a/src/editor/StepsDomain.ts
+++ b/src/domains/StepsDomain.ts
@@ -1,4 +1,4 @@
-import { StepInfoNode } from "../model/tactic/Tactic"
+import { StepInfoNode } from "../model/tactic/Tactic.ts"
export function addStepNode(
root: StepInfoNode,
diff --git a/src/editor/TacticContentDomains.ts b/src/domains/TacticContentDomains.ts
similarity index 97%
rename from src/editor/TacticContentDomains.ts
rename to src/domains/TacticContentDomains.ts
index c7b2965..07fba44 100644
--- a/src/editor/TacticContentDomains.ts
+++ b/src/domains/TacticContentDomains.ts
@@ -1,4 +1,4 @@
-import { equals, Pos, ratioWithinBase } from "../geo/Pos"
+import { equals, Pos, ratioWithinBase } from "../geo/Pos.ts"
import {
BallState,
@@ -7,28 +7,28 @@ import {
PlayerLike,
PlayerPhantom,
PlayerTeam,
-} from "../model/tactic/Player"
+} from "../model/tactic/Player.ts"
import {
Ball,
BALL_ID,
BALL_TYPE,
CourtObject,
-} from "../model/tactic/CourtObjects"
+} from "../model/tactic/CourtObjects.ts"
import {
ComponentId,
StepContent,
TacticComponent,
-} from "../model/tactic/Tactic"
+} from "../model/tactic/Tactic.ts"
-import { overlaps } from "../geo/Box"
-import { RackedCourtObject, RackedPlayer } from "./RackedItems"
+import { overlaps } from "../geo/Box.ts"
+import { RackedCourtObject, RackedPlayer } from "../editor/RackedItems.ts"
import {
getComponent,
getOrigin,
getPrecomputedPosition,
removePlayer,
tryGetComponent,
-} from "./PlayerDomains"
+} from "./PlayerDomains.ts"
import { Action, ActionKind } from "../model/tactic/Action.ts"
import { spreadNewStateFromOriginStateChange } from "./ActionsDomains.ts"
diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx
index 4b1f2f7..063b120 100644
--- a/src/pages/Editor.tsx
+++ b/src/pages/Editor.tsx
@@ -1,5 +1,6 @@
import {
CSSProperties,
+ ReactNode,
RefObject,
SetStateAction,
useCallback,
@@ -10,8 +11,6 @@ import {
} from "react"
import "../style/editor.css"
import TitleInput from "../components/TitleInput"
-import PlainCourt from "../assets/court/full_court.svg?react"
-import HalfCourt from "../assets/court/half_court.svg?react"
import { BallPiece } from "../components/editor/BallPiece"
@@ -19,7 +18,6 @@ import { Rack } from "../components/Rack"
import { PlayerPiece } from "../components/editor/PlayerPiece"
import {
- ComponentId,
CourtType,
StepContent,
StepInfoNode,
@@ -33,7 +31,11 @@ import SavingState, {
import { BALL_TYPE } from "../model/tactic/CourtObjects"
import { CourtAction } from "../components/editor/CourtAction"
-import { ActionPreview, BasketCourt } from "../components/editor/BasketCourt"
+import {
+ ActionPreview,
+ BasketCourt,
+ Court,
+} from "../components/editor/BasketCourt"
import { overlaps } from "../geo/Box"
import {
@@ -50,7 +52,7 @@ import {
removeBall,
selectContent,
updateComponent,
-} from "../editor/TacticContentDomains"
+} from "../domains/TacticContentDomains.ts"
import {
BallState,
@@ -71,16 +73,18 @@ import {
isActionValid,
removeAction,
spreadNewStateFromOriginStateChange,
-} from "../editor/ActionsDomains"
+} from "../domains/ActionsDomains.ts"
import ArrowAction from "../components/actions/ArrowAction"
import { middlePos, Pos, ratioWithinBase } from "../geo/Pos"
import { Action, ActionKind } from "../model/tactic/Action"
import BallAction from "../components/actions/BallAction"
import {
- computePhantomPositioning,
+ ComputedRelativePositions,
+ computeRelativePositions,
getOrigin,
+ getPhantomInfo,
removePlayer,
-} from "../editor/PlayerDomains"
+} from "../domains/PlayerDomains.ts"
import { CourtBall } from "../components/editor/CourtBall"
import StepsTree from "../components/editor/StepsTree"
import {
@@ -88,33 +92,30 @@ import {
getParent,
getStepNode,
removeStepNode,
-} from "../editor/StepsDomain"
+} from "../domains/StepsDomain.ts"
import SplitLayout from "../components/SplitLayout.tsx"
-import { ServiceError, TacticService } from "../service/TacticService.ts"
+import {
+ MutableTacticService,
+ ServiceError,
+} from "../service/MutableTacticService.ts"
import { LocalStorageTacticService } from "../service/LocalStorageTacticService.ts"
import { APITacticService } from "../service/APITacticService.ts"
-import { useParams } from "react-router-dom"
+import { useNavigate, useParams } from "react-router-dom"
import { ContentVersions } from "../editor/ContentVersions.ts"
const ERROR_STYLE: CSSProperties = {
borderColor: "red",
}
-type ComputedRelativePositions = Map
-
type ComputedStepContent = {
content: StepContent
relativePositions: ComputedRelativePositions
}
-export interface EditorPageProps {
+export interface EditorProps {
guestMode: boolean
}
-export default function Editor({ guestMode }: EditorPageProps) {
- return
-}
-
interface EditorService {
addStep(
parent: StepInfoNode,
@@ -128,24 +129,47 @@ interface EditorService {
setContent(content: SetStateAction): void
setName(name: string): Promise
+
+ openVisualizer(): Promise
}
-function EditorPortal({ guestMode }: EditorPageProps) {
+export default function Editor({ guestMode }: EditorProps) {
const { tacticId: idStr } = useParams()
+ const navigate = useNavigate()
+
if (guestMode || !idStr) {
- return
+ return (
+ navigate("/tactic/view-guest")}
+ />
+ )
}
- return
+ return (
+ navigate(`/tactic/${idStr}/view`)}
+ />
+ )
+}
+
+interface EditorPageWrapperProps {
+ service: MutableTacticService
+ openVisualizer(): void
}
-function EditorPageWrapper({ service }: { service: TacticService }) {
+function EditorPageWrapper({
+ service,
+ openVisualizer,
+}: EditorPageWrapperProps) {
const [panicMessage, setPanicMessage] = useState()
const [stepId, setStepId] = useState()
const [tacticName, setTacticName] = useState()
const [courtType, setCourtType] = useState()
const [stepsTree, setStepsTree] = useState()
+ const [parentContent, setParentContent] = useState(null)
const courtRef = useRef(null)
@@ -204,8 +228,6 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
[stepsVersions, service, stepId, stepsTree],
)
- const [parentContent, setParentContent] = useState(null)
-
const [stepContent, setStepContent, saveState] =
useContentState(
{ components: [] },
@@ -329,6 +351,10 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
setStepId(step)
setStepContent(result, false)
},
+
+ async openVisualizer(): Promise {
+ openVisualizer()
+ },
}
}, [stepsVersions, service, setStepContent, stepsTree])
@@ -538,6 +564,7 @@ function EditorPage({
content,
courtRef,
doMoveBall,
+ parentContent,
previewAction?.isInvalid,
service.setContent,
],
@@ -553,22 +580,14 @@ function EditorPage({
const usedContent = isFromParent ? parentContent! : content
if (isPhantom) {
- const origin = getOrigin(component, usedContent.components)
- info = {
- id: component.id,
- team: origin.team,
- role: origin.role,
- pos: computePhantomPositioning(
- component,
- usedContent,
- relativePositions,
- courtBounds(),
- ),
- ballState: component.ballState,
- }
+ info = getPhantomInfo(
+ component,
+ usedContent,
+ relativePositions,
+ courtBounds(),
+ )
} else {
info = component
-
forceFreeze ||= component.frozen
}
@@ -607,8 +626,9 @@ function EditorPage({
)
},
[
- courtRef,
+ parentContent,
content,
+ courtRef,
relativePositions,
courtBounds,
renderAvailablePlayerActions,
@@ -644,7 +664,7 @@ function EditorPage({
)
const renderComponent = useCallback(
- (component: TacticComponent, isFromParent: boolean) => {
+ (component: TacticComponent, isFromParent: boolean): ReactNode => {
if (component.type === "player" || component.type === "phantom") {
return renderPlayer(component, isFromParent)
}
@@ -664,7 +684,7 @@ function EditorPage({
/>
)
}
- throw new Error("unknown tactic component " + component)
+ return <>>
},
[service, renderPlayer, doMoveBall],
)
@@ -794,25 +814,26 @@ function EditorPage({
-
- {
- service.setName(new_name).then((state) => {
- setTitleStyle(
- state == SaveStates.Ok
- ? {}
- : ERROR_STYLE,
- )
- })
- },
- [service],
- )}
- />
-
+ {
+ service.setName(new_name).then((state) => {
+ setTitleStyle(
+ state == SaveStates.Ok ? {} : ERROR_STYLE,
+ )
+ })
+ },
+ [service],
+ )}
+ />
+