parent
9e8606184c
commit
15b47f354e
@ -0,0 +1,224 @@
|
|||||||
|
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
|
import { APITacticService } from "../service/APITacticService.ts"
|
||||||
|
import { CourtType, StepContent, StepInfoNode, TacticComponent } from "../model/tactic/Tactic.ts"
|
||||||
|
import { getParent } from "../domains/StepsDomain.ts"
|
||||||
|
import { computeRelativePositions, getPhantomInfo } from "../domains/PlayerDomains.ts"
|
||||||
|
import { PlayerInfo, PlayerLike } from "../model/tactic/Player.ts"
|
||||||
|
import { CourtPlayer } from "./editor/CourtPlayer.tsx"
|
||||||
|
import { BALL_TYPE } from "../model/tactic/CourtObjects.ts"
|
||||||
|
import { CourtBallPiece } from "./editor/CourtBall.tsx"
|
||||||
|
import { CourtAction } from "./editor/CourtAction.tsx"
|
||||||
|
import { BasketCourt, Court } from "./editor/BasketCourt.tsx"
|
||||||
|
import { TacticService } from "../service/MutableTacticService.ts"
|
||||||
|
import { useAppFetcher } from "../App.tsx"
|
||||||
|
|
||||||
|
|
||||||
|
export interface VisualizerProps {
|
||||||
|
tacticId: number
|
||||||
|
stepId?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Visualizer({ tacticId, stepId }: VisualizerProps) {
|
||||||
|
const [panicMessage, setPanicMessage] = useState<string | null>(null)
|
||||||
|
const [courtType, setCourtType] = useState<CourtType | null>()
|
||||||
|
const [stepsTree, setStepsTree] = useState<StepInfoNode | null>()
|
||||||
|
const fetcher = useAppFetcher()
|
||||||
|
const service = useMemo(() => new APITacticService(fetcher, tacticId), [tacticId])
|
||||||
|
|
||||||
|
const isNotInit = !stepsTree || !courtType
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function init() {
|
||||||
|
const contextResult = await service.getContext()
|
||||||
|
if (typeof contextResult === "string") {
|
||||||
|
setPanicMessage(contextResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const rootStep = contextResult.stepsTree
|
||||||
|
setStepsTree(rootStep)
|
||||||
|
setCourtType(contextResult.courtType)
|
||||||
|
}
|
||||||
|
if (isNotInit)
|
||||||
|
init()
|
||||||
|
}, [isNotInit, service])
|
||||||
|
|
||||||
|
if (panicMessage) {
|
||||||
|
return <p>{panicMessage}</p>
|
||||||
|
}
|
||||||
|
if (isNotInit) {
|
||||||
|
return <p>Loading...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StepVisualizer
|
||||||
|
courtType={courtType}
|
||||||
|
stepsTree={stepsTree}
|
||||||
|
stepId={stepId}
|
||||||
|
service={service}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StepVisualizerProps {
|
||||||
|
stepId?: number
|
||||||
|
stepsTree: StepInfoNode
|
||||||
|
courtType: CourtType
|
||||||
|
service: TacticService
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StepVisualizer({stepId, stepsTree, courtType, service}: StepVisualizerProps) {
|
||||||
|
const [panicMessage, setPanicMessage] = useState<string | null>(null)
|
||||||
|
const [content, setContent] = useState<StepContent | null>(null)
|
||||||
|
const [parentContent, setParentContent] = useState<StepContent | null>()
|
||||||
|
|
||||||
|
const isNotInit = !content || !parentContent
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function init() {
|
||||||
|
|
||||||
|
const contentStepId = stepId ?? stepsTree.id
|
||||||
|
|
||||||
|
const contentResult = await service.getContent(contentStepId)
|
||||||
|
if (typeof contentResult === "string") {
|
||||||
|
setPanicMessage(contentResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepParent = getParent(stepsTree, contentStepId)
|
||||||
|
let parentContent = null
|
||||||
|
if (stepParent) {
|
||||||
|
const parentResult = await service.getContent(contentStepId)
|
||||||
|
if (typeof parentResult === "string") {
|
||||||
|
setPanicMessage(parentResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parentContent = parentResult
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent(contentResult)
|
||||||
|
setParentContent(parentContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotInit) init()
|
||||||
|
}, [isNotInit, service, stepId, stepsTree])
|
||||||
|
|
||||||
|
if (panicMessage) {
|
||||||
|
return <p>{panicMessage}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotInit) {
|
||||||
|
return <p>Loading Content...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VisualizerFrame
|
||||||
|
content={content}
|
||||||
|
parentContent={parentContent}
|
||||||
|
courtType={courtType}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualizerFrameProps {
|
||||||
|
content: StepContent
|
||||||
|
parentContent: StepContent | null
|
||||||
|
courtType: CourtType
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VisualizerFrame({
|
||||||
|
content,
|
||||||
|
parentContent,
|
||||||
|
courtType,
|
||||||
|
}: VisualizerFrameProps) {
|
||||||
|
|
||||||
|
console.log(content, parentContent)
|
||||||
|
|
||||||
|
const courtRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const courtBounds = useCallback(
|
||||||
|
() => courtRef.current!.getBoundingClientRect(),
|
||||||
|
[courtRef],
|
||||||
|
)
|
||||||
|
|
||||||
|
const relativePositions = useMemo(() => {
|
||||||
|
const courtBounds = courtRef.current?.getBoundingClientRect()
|
||||||
|
return courtBounds
|
||||||
|
? computeRelativePositions(courtBounds, content)
|
||||||
|
: new Map()
|
||||||
|
}, [content, courtRef])
|
||||||
|
|
||||||
|
const renderPlayer = useCallback(
|
||||||
|
(component: PlayerLike, isFromParent: boolean) => {
|
||||||
|
let info: PlayerInfo
|
||||||
|
const isPhantom = component.type == "phantom"
|
||||||
|
const usedContent = isFromParent ? parentContent! : content
|
||||||
|
|
||||||
|
if (isPhantom) {
|
||||||
|
info = getPhantomInfo(
|
||||||
|
component,
|
||||||
|
usedContent,
|
||||||
|
relativePositions,
|
||||||
|
courtBounds(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
info = component
|
||||||
|
}
|
||||||
|
|
||||||
|
const className =
|
||||||
|
(isPhantom ? "phantom" : "player") +
|
||||||
|
" " +
|
||||||
|
(isFromParent ? "from-parent" : "")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CourtPlayer
|
||||||
|
key={component.id}
|
||||||
|
playerInfo={info}
|
||||||
|
className={className}
|
||||||
|
availableActions={() => []}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[content, courtBounds, parentContent, relativePositions],
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderComponent = useCallback(
|
||||||
|
(component: TacticComponent, isFromParent: boolean): ReactNode => {
|
||||||
|
if (component.type === "player" || component.type === "phantom") {
|
||||||
|
return renderPlayer(component, isFromParent)
|
||||||
|
}
|
||||||
|
if (component.type === BALL_TYPE) {
|
||||||
|
return <CourtBallPiece key="ball" pos={component.pos} />
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
},
|
||||||
|
[renderPlayer],
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderActions = useCallback(
|
||||||
|
(component: TacticComponent, isFromParent: boolean) =>
|
||||||
|
component.actions.map((action, i) => {
|
||||||
|
return (
|
||||||
|
<CourtAction
|
||||||
|
key={"action-" + component.id + "-" + i}
|
||||||
|
action={action}
|
||||||
|
origin={component.id}
|
||||||
|
courtRef={courtRef}
|
||||||
|
color={isFromParent ? "gray" : "black"}
|
||||||
|
isEditable={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
[courtRef],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasketCourt
|
||||||
|
parentComponents={parentContent?.components ?? null}
|
||||||
|
components={content.components}
|
||||||
|
courtImage={<Court courtType={courtType} />}
|
||||||
|
courtRef={courtRef}
|
||||||
|
renderComponent={renderComponent}
|
||||||
|
renderActions={renderActions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { StepInfoNode } from "../model/tactic/Tactic"
|
import { StepInfoNode } from "../model/tactic/Tactic.ts"
|
||||||
|
|
||||||
export function addStepNode(
|
export function addStepNode(
|
||||||
root: StepInfoNode,
|
root: StepInfoNode,
|
@ -1,23 +0,0 @@
|
|||||||
// import React, { CSSProperties, useState } from "react"
|
|
||||||
// import "../style/visualizer.css"
|
|
||||||
// import Court from "../assets/court/full_court.svg"
|
|
||||||
//
|
|
||||||
// export default function Visualizer({ id, name }: { id: number; name: string }) {
|
|
||||||
// const [style, setStyle] = useState<CSSProperties>({})
|
|
||||||
//
|
|
||||||
// return (
|
|
||||||
// <div id="main">
|
|
||||||
// <div id="topbar">
|
|
||||||
// <h1>{name}</h1>
|
|
||||||
// </div>
|
|
||||||
// <div id="court-container">
|
|
||||||
// <img
|
|
||||||
// id="court"
|
|
||||||
// src={Court}
|
|
||||||
// style={style}
|
|
||||||
// alt="Basketball Court"
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// )
|
|
||||||
// }
|
|
@ -0,0 +1,178 @@
|
|||||||
|
import { ServiceError, TacticService } from "../service/MutableTacticService.ts"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
import { useCallback, useEffect, useMemo, useReducer, useState } from "react"
|
||||||
|
import { VisualizerState, VisualizerStateActionKind, visualizerStateReducer } from "../visualizer/VisualizerState.ts"
|
||||||
|
import { getParent } from "../domains/StepsDomain.ts"
|
||||||
|
import { mapToParentContent } from "../domains/TacticContentDomains.ts"
|
||||||
|
import StepsTree from "../components/editor/StepsTree.tsx"
|
||||||
|
import { StepInfoNode } from "../model/tactic/Tactic.ts"
|
||||||
|
import SplitLayout from "../components/SplitLayout.tsx"
|
||||||
|
import { LocalStorageTacticService } from "../service/LocalStorageTacticService.ts"
|
||||||
|
import { APITacticService } from "../service/APITacticService.ts"
|
||||||
|
|
||||||
|
import "../style/visualizer.css"
|
||||||
|
import { VisualizerFrame } from "../components/Visualizer.tsx"
|
||||||
|
import { useAppFetcher } from "../App.tsx"
|
||||||
|
|
||||||
|
export interface VisualizerPageProps {
|
||||||
|
guestMode: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VisualizerPage({ guestMode }: VisualizerPageProps) {
|
||||||
|
const { tacticId: idStr } = useParams()
|
||||||
|
|
||||||
|
const fetcher = useAppFetcher()
|
||||||
|
if (guestMode || !idStr) {
|
||||||
|
return (
|
||||||
|
<ServedVisualizerPage service={LocalStorageTacticService.init()} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServedVisualizerPage service={new APITacticService(fetcher, parseInt(idStr))} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VisualizerService {
|
||||||
|
selectStep(step: number): Promise<void | ServiceError>
|
||||||
|
}
|
||||||
|
|
||||||
|
function ServedVisualizerPage({ service }: { service: TacticService }) {
|
||||||
|
const [panicMessage, setPanicMessage] = useState<string>()
|
||||||
|
const [state, dispatch] = useReducer(visualizerStateReducer, null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function init() {
|
||||||
|
const contextResult = await service.getContext()
|
||||||
|
if (typeof contextResult === "string") {
|
||||||
|
setPanicMessage(
|
||||||
|
"There has been an error retrieving the editor initial context : " +
|
||||||
|
contextResult,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepId = contextResult.stepsTree.id
|
||||||
|
const contentResult = await service.getContent(stepId)
|
||||||
|
|
||||||
|
if (typeof contentResult === "string") {
|
||||||
|
setPanicMessage(
|
||||||
|
"There has been an error retrieving the tactic's root step content : " +
|
||||||
|
contentResult,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: VisualizerStateActionKind.INIT,
|
||||||
|
state: {
|
||||||
|
stepId,
|
||||||
|
stepsTree: contextResult.stepsTree,
|
||||||
|
courtType: contextResult.courtType,
|
||||||
|
tacticName: contextResult.name,
|
||||||
|
content: contentResult,
|
||||||
|
parentContent: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === null) init()
|
||||||
|
}, [service, state])
|
||||||
|
|
||||||
|
const visualizerService: VisualizerService = useMemo(
|
||||||
|
() => ({
|
||||||
|
async selectStep(step: number): Promise<void | ServiceError> {
|
||||||
|
const result = await service.getContent(step)
|
||||||
|
if (typeof result === "string") return result
|
||||||
|
const stepParent = getParent(state!.stepsTree!, step)?.id
|
||||||
|
let parentContent = null
|
||||||
|
if (stepParent) {
|
||||||
|
const parentResult = await service.getContent(stepParent)
|
||||||
|
if (typeof parentResult === "string") return parentResult
|
||||||
|
parentContent = mapToParentContent(parentResult)
|
||||||
|
}
|
||||||
|
dispatch({
|
||||||
|
type: VisualizerStateActionKind.SET_CONTENTS,
|
||||||
|
content: result,
|
||||||
|
parentContent,
|
||||||
|
stepId: step,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[service, state],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (panicMessage) {
|
||||||
|
return <p>{panicMessage}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === null) {
|
||||||
|
return <p>Retrieving tactic context. Please wait...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <VisualizerPageContent state={state} service={visualizerService} />
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VisualizerPageContentProps {
|
||||||
|
state: VisualizerState
|
||||||
|
service: VisualizerService
|
||||||
|
}
|
||||||
|
|
||||||
|
function VisualizerPageContent({
|
||||||
|
state: { content, parentContent, stepId, stepsTree, courtType, tacticName },
|
||||||
|
service,
|
||||||
|
}: VisualizerPageContentProps) {
|
||||||
|
const [isStepsTreeVisible, setStepsTreeVisible] = useState(false)
|
||||||
|
|
||||||
|
const [editorContentCurtainWidth, setEditorContentCurtainWidth] =
|
||||||
|
useState(80)
|
||||||
|
|
||||||
|
const stepsTreeNode = (
|
||||||
|
<div id={"steps-div"}>
|
||||||
|
<StepsTree
|
||||||
|
root={stepsTree}
|
||||||
|
selectedStepId={stepId}
|
||||||
|
onStepSelected={useCallback(
|
||||||
|
(node: StepInfoNode) => service.selectStep(node.id),
|
||||||
|
[service],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const contentNode = (
|
||||||
|
<div id="content-div">
|
||||||
|
<VisualizerFrame
|
||||||
|
content={content}
|
||||||
|
parentContent={parentContent}
|
||||||
|
courtType={courtType}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="visualizer">
|
||||||
|
<div id="topbar-div">
|
||||||
|
<p id="title">{tacticName}</p>
|
||||||
|
<button
|
||||||
|
id={"show-steps-button"}
|
||||||
|
onClick={() => setStepsTreeVisible((b) => !b)}>
|
||||||
|
ETAPES
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="editor-div">
|
||||||
|
{isStepsTreeVisible ? (
|
||||||
|
<SplitLayout
|
||||||
|
rightWidth={editorContentCurtainWidth}
|
||||||
|
onRightWidthChange={setEditorContentCurtainWidth}>
|
||||||
|
{contentNode}
|
||||||
|
{stepsTreeNode}
|
||||||
|
</SplitLayout>
|
||||||
|
) : (
|
||||||
|
contentNode
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
.court-image-div {
|
||||||
|
position: relative;
|
||||||
|
background-color: white;
|
||||||
|
height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.court-container {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.court-image {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.court-image * {
|
||||||
|
stroke: var(--selected-team-secondarycolor);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
.player-piece.opponents {
|
||||||
|
background-color: var(--player-opponents-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-piece.allies {
|
||||||
|
background-color: var(--player-allies-color);
|
||||||
|
}
|
@ -1,30 +1,72 @@
|
|||||||
#main {
|
@import "court.css";
|
||||||
height: 100vh;
|
@import "theme/default.css";
|
||||||
width: 100%;
|
@import "player.css";
|
||||||
|
@import "tactic.css";
|
||||||
|
|
||||||
|
#visualizer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#topbar {
|
#editor-div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.curtain {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-div {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--main-color);
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
#topbar-div {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--main-color);
|
||||||
|
align-content: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#show-steps-button {
|
||||||
|
user-select: none;
|
||||||
|
align-self: flex-end;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
align-self: center;
|
||||||
|
margin: 0;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 0;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#court-container {
|
#topbar {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--main-color);
|
background-color: var(--main-color);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#court {
|
#steps-div {
|
||||||
max-width: 80%;
|
background-color: var(--editor-tree-background);
|
||||||
max-height: 80%;
|
overflow: scroll;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { CourtType, StepContent, StepInfoNode } from "../model/tactic/Tactic.ts"
|
||||||
|
|
||||||
|
export interface VisualizerState {
|
||||||
|
stepId: number
|
||||||
|
tacticName: string
|
||||||
|
courtType: CourtType
|
||||||
|
stepsTree: StepInfoNode
|
||||||
|
content: StepContent
|
||||||
|
parentContent: StepContent | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VisualizerStateActionKind {
|
||||||
|
INIT,
|
||||||
|
SET_CONTENTS,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VisualizerStateAction =
|
||||||
|
| {
|
||||||
|
type: VisualizerStateActionKind.INIT
|
||||||
|
state: VisualizerState
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: VisualizerStateActionKind.SET_CONTENTS
|
||||||
|
content: StepContent
|
||||||
|
parentContent: StepContent | null
|
||||||
|
stepId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function visualizerStateReducer(
|
||||||
|
state: VisualizerState | null,
|
||||||
|
action: VisualizerStateAction,
|
||||||
|
): VisualizerState | null {
|
||||||
|
switch (action.type) {
|
||||||
|
case VisualizerStateActionKind.INIT:
|
||||||
|
return action.state
|
||||||
|
case VisualizerStateActionKind.SET_CONTENTS:
|
||||||
|
if (state === null) throw Error("State is uninitialized !")
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stepId: action.stepId,
|
||||||
|
content: action.content,
|
||||||
|
parentContent: action.parentContent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue