add parent's content view in children

pull/117/head
maxime 1 year ago
parent c544f88de7
commit 80c94d733f

@ -36,6 +36,7 @@ export interface BendableArrowProps {
onSegmentsChanges: (edges: Segment[]) => void
forceStraight: boolean
wavy: boolean
readOnly: boolean
startRadius?: number
endRadius?: number
@ -87,6 +88,7 @@ function constraintInCircle(center: Pos, reference: Pos, radius: number): Pos {
* @param segments
* @param onSegmentsChanges
* @param wavy
* @param readOnly
* @param forceStraight
* @param style
* @param startRadius
@ -103,6 +105,7 @@ export default function BendableArrow({
forceStraight,
wavy,
readOnly,
style,
startRadius = 0,
@ -531,7 +534,7 @@ export default function BendableArrow({
}
fill="none"
tabIndex={0}
onDoubleClick={addSegment}
onDoubleClick={readOnly ? undefined : addSegment}
onKeyUp={(e) => {
if (onDeleteRequested && e.key == "Delete")
onDeleteRequested()
@ -555,6 +558,7 @@ export default function BendableArrow({
{!forceStraight &&
isSelected &&
!readOnly &&
computePoints(area.current!.getBoundingClientRect())}
</div>
)

@ -6,10 +6,11 @@ import { ComponentId, TacticComponent } from "../../model/tactic/Tactic"
export interface BasketCourtProps {
components: TacticComponent[]
parentComponents: TacticComponent[] | null
previewAction: ActionPreview | null
renderComponent: (comp: TacticComponent) => ReactNode
renderActions: (comp: TacticComponent) => ReactNode[]
renderComponent: (comp: TacticComponent, isFromParent: boolean) => ReactNode
renderActions: (comp: TacticComponent, isFromParent: boolean) => ReactNode[]
courtImage: ReactElement
courtRef: RefObject<HTMLDivElement>
@ -22,6 +23,7 @@ export interface ActionPreview extends Action {
export function BasketCourt({
components,
parentComponents,
previewAction,
renderComponent,
@ -37,15 +39,25 @@ export function BasketCourt({
style={{ position: "relative" }}>
{courtImage}
{courtRef.current && components.map(renderComponent)}
{courtRef.current && components.flatMap(renderActions)}
{courtRef.current &&
parentComponents &&
parentComponents.map((i) => renderComponent(i, true))}
{courtRef.current &&
parentComponents &&
parentComponents.flatMap((i) => renderActions(i, true))}
{courtRef.current &&
components.map((i) => renderComponent(i, false))}
{courtRef.current &&
components.flatMap((i) => renderActions(i, false))}
{previewAction && (
<CourtAction
courtRef={courtRef}
action={previewAction}
origin={previewAction.origin}
isInvalid={previewAction.isInvalid}
color={previewAction.isInvalid ? "red" : "black"}
isEditable={true}
//do nothing on interacted, not really possible as it's a preview arrow
onActionDeleted={() => {}}
onActionChanges={() => {}}

@ -7,22 +7,22 @@ import { ComponentId } from "../../model/tactic/Tactic"
export interface CourtActionProps {
origin: ComponentId
action: Action
onActionChanges: (a: Action) => void
onActionDeleted: () => void
color: string
courtRef: RefObject<HTMLElement>
isInvalid: boolean
isEditable: boolean
onActionChanges?: (a: Action) => void
onActionDeleted?: () => void
}
export function CourtAction({
origin,
action,
color,
onActionChanges,
onActionDeleted,
courtRef,
isInvalid,
isEditable,
}: CourtActionProps) {
const color = isInvalid ? "red" : "black"
let head
switch (action.type) {
case ActionKind.DRIBBLE:
@ -49,9 +49,11 @@ export function CourtAction({
startPos={origin}
segments={action.segments}
onSegmentsChanges={(edges) => {
onActionChanges({ ...action, segments: edges })
if (onActionChanges)
onActionChanges({ ...action, segments: edges })
}}
wavy={action.type == ActionKind.DRIBBLE}
readOnly={!isEditable}
//TODO place those magic values in constants
endRadius={action.target ? 26 : 17}
startRadius={10}

@ -70,6 +70,7 @@ function StepsTreeNode({
onSegmentsChanges={() => {}}
forceStraight={true}
wavy={false}
readOnly={true}
//TODO remove magic constants
startRadius={10}
endRadius={10}

@ -98,9 +98,9 @@ export function getAvailableId(root: StepInfoNode): number {
export function getParent(
root: StepInfoNode,
node: StepInfoNode,
node: number,
): StepInfoNode | null {
if (root.children.find((n) => n.id === node.id)) return root
if (root.children.find((n) => n.id === node)) return root
for (const child of root.children) {
const result = getParent(child, node)

@ -472,3 +472,27 @@ export function drainTerminalStateOnChildContent(
return gotUpdated ? childContent : null
}
export function mapToParentContent(content: StepContent): StepContent {
return {
...content,
components: content.components.map((p) => {
if (p.type == "ball") return p
return {
...p,
id: p.id + "-parent",
actions: p.actions.map((a) => ({
...a,
target: a.target + "-parent",
segments: a.segments.map((s) => ({
...s,
next:
typeof s.next === "string"
? s.next + "-parent"
: s.next,
})),
})),
}
}),
}
}

@ -42,6 +42,7 @@ import {
dropBallOnComponent,
getComponentCollided,
getRackPlayers,
mapToParentContent,
moveComponent,
placeBallAt,
placeObjectAt,
@ -202,6 +203,8 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
[stepsVersions, service, stepId, stepsTree],
)
const [parentContent, setParentContent] = useState<StepContent | null>(null)
const [stepContent, setStepContent, saveState] =
useContentState<StepContent>(
{ components: [] },
@ -271,21 +274,30 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
if (isNotInit) init()
}, [isNotInit, service, setStepContent, stepsVersions])
const editorService: EditorService = useMemo(
() => ({
const editorService: EditorService = useMemo(() => {
let internalStepsTree = stepsTree
return {
async addStep(
parent: StepInfoNode,
content: StepContent,
): Promise<StepInfoNode | ServiceError> {
const result = await service.addStep(parent, content)
if (typeof result !== "string")
setStepsTree(addStepNode(stepsTree!, parent, result))
if (typeof result !== "string") {
internalStepsTree = addStepNode(
internalStepsTree!,
parent,
result,
)
setStepsTree(internalStepsTree)
}
return result
},
async removeStep(step: number): Promise<void | ServiceError> {
const result = await service.removeStep(step)
if (typeof result !== "string")
setStepsTree(removeStepNode(stepsTree!, step))
if (typeof result !== "string") {
internalStepsTree = removeStepNode(internalStepsTree!, step)
setStepsTree(internalStepsTree)
}
stepsVersions.delete(step)
return result
},
@ -304,12 +316,19 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
async selectStep(step: number): Promise<void | ServiceError> {
const result = await service.getContent(step)
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)
setStepContent(result, false)
},
}),
[stepsVersions, service, setStepContent, stepsTree],
)
}
}, [stepsVersions, service, setStepContent, stepsTree])
if (panicMessage) {
return <p>{panicMessage}</p>
@ -326,6 +345,7 @@ function EditorPageWrapper({ service }: { service: TacticService }) {
stepId={stepId}
stepsTree={stepsTree}
contentSaveState={saveState}
parentContent={parentContent}
content={stepContent}
service={editorService}
courtRef={courtRef}
@ -337,10 +357,12 @@ export interface EditorViewProps {
stepsTree: StepInfoNode
name: string
courtType: CourtType
content: StepContent
contentSaveState: SaveState
stepId: number
parentContent: StepContent | null
content: StepContent
courtRef: RefObject<HTMLDivElement>
service: EditorService
@ -349,6 +371,7 @@ export interface EditorViewProps {
function EditorPage({
name,
courtType,
parentContent,
content,
stepId,
contentSaveState,
@ -516,9 +539,12 @@ function EditorPage({
)
const renderPlayer = useCallback(
(component: PlayerLike) => {
(component: PlayerLike, isFromParent: boolean) => {
let info: PlayerInfo
const isPhantom = component.type == "phantom"
let forceFreeze = isFromParent
if (isPhantom) {
const origin = getOrigin(component, content.components)
info = {
@ -536,24 +562,31 @@ function EditorPage({
} else {
info = component
if (component.frozen) {
return (
<CourtPlayer
key={component.id}
playerInfo={info}
className={"player"}
availableActions={() =>
renderAvailablePlayerActions(info, component)
}
/>
)
}
forceFreeze ||= component.frozen
}
const className =
(isPhantom ? "phantom" : "player") +
" " +
(isFromParent ? "from-parent" : "")
if (forceFreeze) {
return (
<CourtPlayer
key={component.id}
playerInfo={info}
className={className}
availableActions={() =>
renderAvailablePlayerActions(info, component)
}
/>
)
}
return (
<EditableCourtPlayer
key={component.id}
className={isPhantom ? "phantom" : "player"}
className={className}
playerInfo={info}
onPositionValidated={(newPos) =>
validatePlayerPosition(component, info, newPos)
@ -604,11 +637,11 @@ function EditorPage({
)
const renderComponent = useCallback(
(component: TacticComponent) => {
(component: TacticComponent, isFromParent: boolean) => {
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 (
<CourtBall
key="ball"
@ -630,7 +663,7 @@ function EditorPage({
)
const renderActions = useCallback(
(component: TacticComponent) =>
(component: TacticComponent, isFromParent: boolean) =>
component.actions.map((action, i) => {
return (
<CourtAction
@ -638,13 +671,16 @@ function EditorPage({
action={action}
origin={component.id}
courtRef={courtRef}
isInvalid={false}
color={isFromParent ? "gray" : "black"}
isEditable={!isFromParent}
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)
}
/>
)
}),
@ -698,6 +734,7 @@ function EditorPage({
<div id="court-div">
<div id="court-div-bounds">
<BasketCourt
parentComponents={parentContent?.components ?? null}
components={content.components}
courtImage={<Court courtType={courtType} />}
courtRef={courtRef}
@ -731,7 +768,9 @@ function EditorPage({
onRemoveNode={useCallback(
async (removed) => {
await service.removeStep(removed.id)
await service.selectStep(getParent(stepsTree, removed)!.id)
await service.selectStep(
getParent(stepsTree, removed.id)!.id,
)
},
[service, stepsTree],
)}

@ -6,6 +6,11 @@
opacity: 50%;
}
.from-parent .player-piece {
color: white;
background-color: var(--player-from-parent-color);
}
.player-content {
display: flex;
flex-direction: column;

@ -9,6 +9,7 @@
--selected-team-secondarycolor: #000000;
--player-allies-color: #64e4f5;
--player-opponents-color: #f59264;
--player-from-parent-color: #494949;
--buttons-shadow-color: #a8a8a8;

Loading…
Cancel
Save