add parent's content view in children

maxime 1 year ago committed by maxime.batista
parent 102bc774af
commit 69b25224fa

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

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

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

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

@ -98,9 +98,9 @@ export function getAvailableId(root: StepInfoNode): number {
export function getParent( export function getParent(
root: StepInfoNode, root: StepInfoNode,
node: StepInfoNode, node: number,
): StepInfoNode | null { ): 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) { for (const child of root.children) {
const result = getParent(child, node) const result = getParent(child, node)

@ -472,3 +472,27 @@ export function drainTerminalStateOnChildContent(
return gotUpdated ? childContent : null 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, 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],
)} )}

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

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

Loading…
Cancel
Save