sample-ui-component-library 0.0.38-dev → 0.0.40-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.jsx +32 -0
- package/src/components/BehavioralGraphBuilder/BehavioralGraphBuilder.scss +0 -0
- package/src/components/BehavioralGraphBuilder/DagreLayout.js +33 -0
- package/src/components/BehavioralGraphBuilder/Flow.jsx +164 -0
- package/src/components/BehavioralGraphBuilder/helper.js +35 -0
- package/src/components/BehavioralGraphBuilder/index.js +1 -0
- package/src/index.js +2 -1
- package/src/stories/BehavioralGraphBuilder.stories.js +66 -0
- package/src/stories/BehavioralGraphBuilderStories.scss +17 -0
- package/src/stories/components/ToolBar/ToolBar.js +57 -0
- package/src/stories/components/ToolBar/ToolBar.scss +29 -0
- package/src/stories/data/Designs/simple_design_temp.json +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Flow } from "./Flow";
|
|
2
|
+
import { ReactFlowProvider } from "@xyflow/react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { designToReactFlowElements } from "./helper";
|
|
5
|
+
|
|
6
|
+
export const BehavioralGraphBuilder = ({
|
|
7
|
+
activeTool,
|
|
8
|
+
onBehaviorSelect,
|
|
9
|
+
onAddBehavior,
|
|
10
|
+
onAddEdge,
|
|
11
|
+
design,
|
|
12
|
+
}) => {
|
|
13
|
+
const [initialElements, setInitialElements] = useState(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (design) {
|
|
17
|
+
setInitialElements(designToReactFlowElements(design));
|
|
18
|
+
}
|
|
19
|
+
}, [design]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ReactFlowProvider>
|
|
23
|
+
<Flow
|
|
24
|
+
initialElements={initialElements}
|
|
25
|
+
activeTool={activeTool}
|
|
26
|
+
onBehaviorSelect={onBehaviorSelect}
|
|
27
|
+
onAddBehavior={onAddBehavior}
|
|
28
|
+
onAddEdge={onAddEdge}
|
|
29
|
+
/>
|
|
30
|
+
</ReactFlowProvider>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
import Dagre from '@dagrejs/dagre';
|
|
3
|
+
|
|
4
|
+
export const getLayoutedElements = (nodes, edges, options) => {
|
|
5
|
+
// Reference: https://reactflow.dev/learn/layouting/layouting
|
|
6
|
+
|
|
7
|
+
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
|
8
|
+
g.setGraph({ rankdir: options.direction, ranksep: 100, nodesep: 170});
|
|
9
|
+
|
|
10
|
+
edges.forEach((edge) => g.setEdge(edge.source, edge.target));
|
|
11
|
+
nodes.forEach((node) =>
|
|
12
|
+
g.setNode(node.id, {
|
|
13
|
+
...node,
|
|
14
|
+
width: node.measured?.width ?? 0,
|
|
15
|
+
height: node.measured?.height ?? 0,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Dagre.layout(g);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
nodes: nodes.map((node) => {
|
|
23
|
+
const position = g.node(node.id);
|
|
24
|
+
// We are shifting the dagre node position (anchor=center center) to the top left
|
|
25
|
+
// so it matches the React Flow node anchor point (top left).
|
|
26
|
+
const x = position.x - (node.measured?.width ?? 0) / 2;
|
|
27
|
+
const y = position.y - (node.measured?.height ?? 0) / 2;
|
|
28
|
+
|
|
29
|
+
return { ...node, position: { x, y } };
|
|
30
|
+
}),
|
|
31
|
+
edges,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ReactFlow,
|
|
4
|
+
ReactFlowProvider,
|
|
5
|
+
useNodesState,
|
|
6
|
+
useEdgesState,
|
|
7
|
+
Background,
|
|
8
|
+
Controls,
|
|
9
|
+
useReactFlow,
|
|
10
|
+
addEdge,
|
|
11
|
+
BezierEdge ,
|
|
12
|
+
applyNodeChanges,
|
|
13
|
+
applyEdgeChanges
|
|
14
|
+
} from "@xyflow/react";
|
|
15
|
+
import "@xyflow/react/dist/style.css";
|
|
16
|
+
|
|
17
|
+
import { getLayoutedElements } from "./DagreLayout.js";
|
|
18
|
+
|
|
19
|
+
const edgeTypes = {
|
|
20
|
+
bezier: BezierEdge,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const NODE_WIDTH = 150;
|
|
24
|
+
const NODE_HEIGHT = 40;
|
|
25
|
+
export const Flow = ({ activeTool, onBehaviorSelect, onAddBehavior, onAddEdge, initialElements }) => {
|
|
26
|
+
const { screenToFlowPosition } = useReactFlow();
|
|
27
|
+
const [nodes, setNodes] = useState([]);
|
|
28
|
+
const [edges, setEdges] = useState([]);
|
|
29
|
+
const [ghostNode, setGhostNode] = useState(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!initialElements) return;
|
|
33
|
+
const layouted = getLayoutedElements(
|
|
34
|
+
initialElements.nodes,
|
|
35
|
+
initialElements.edges,
|
|
36
|
+
{direction:"TB"}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
setNodes([...layouted.nodes]);
|
|
40
|
+
setEdges([...layouted.edges]);
|
|
41
|
+
}, [initialElements]);
|
|
42
|
+
|
|
43
|
+
// Callbacks to apply node changes
|
|
44
|
+
const onNodesChange = useCallback((changes) => {
|
|
45
|
+
setNodes((nds) => applyNodeChanges(changes, nds));
|
|
46
|
+
}, []);
|
|
47
|
+
const onEdgesChange = useCallback((changes) => {
|
|
48
|
+
setEdges((eds) => applyEdgeChanges(changes, eds));
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
// Console messages for debugging
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
console.log("Active Tool:", activeTool);
|
|
54
|
+
}, [activeTool]);
|
|
55
|
+
|
|
56
|
+
// Callback for mouse pane move
|
|
57
|
+
const onPaneMouseMove = useCallback( (event) => {
|
|
58
|
+
const pos = screenToFlowPosition({
|
|
59
|
+
x: event.clientX,
|
|
60
|
+
y: event.clientY,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (activeTool === "drop") {
|
|
64
|
+
setGhostNode({
|
|
65
|
+
id: "ghost",
|
|
66
|
+
position: {
|
|
67
|
+
x: pos.x - NODE_WIDTH / 2,
|
|
68
|
+
y: pos.y - NODE_HEIGHT / 2,
|
|
69
|
+
},
|
|
70
|
+
data: { label: "Behavior" },
|
|
71
|
+
draggable: false,
|
|
72
|
+
selectable: false,
|
|
73
|
+
style: {
|
|
74
|
+
opacity: 0.5,
|
|
75
|
+
pointerEvents: "none",
|
|
76
|
+
width: NODE_WIDTH,
|
|
77
|
+
height: NODE_HEIGHT,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}, [activeTool]);
|
|
82
|
+
|
|
83
|
+
// Callback for when mouse leaves pane
|
|
84
|
+
const onPaneMouseLeave = useCallback(() => {
|
|
85
|
+
setGhostNode(null);
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
// Callback for pane click
|
|
89
|
+
const onPaneClick = useCallback((event, node) => {
|
|
90
|
+
if (activeTool === "drop") {
|
|
91
|
+
if (!ghostNode) return;
|
|
92
|
+
setNodes((nds) => [
|
|
93
|
+
...nds,
|
|
94
|
+
{
|
|
95
|
+
...ghostNode,
|
|
96
|
+
draggable: true,
|
|
97
|
+
selectable: true,
|
|
98
|
+
id: crypto.randomUUID(),
|
|
99
|
+
style: {},
|
|
100
|
+
},
|
|
101
|
+
]);
|
|
102
|
+
onAddBehavior(ghostNode);
|
|
103
|
+
setGhostNode(null);
|
|
104
|
+
}
|
|
105
|
+
}, [ghostNode, activeTool, onAddBehavior]);
|
|
106
|
+
|
|
107
|
+
// Callback for node click to delete it (if tool is active)
|
|
108
|
+
const onNodeClick = useCallback((event, node) => {
|
|
109
|
+
if (activeTool === "delete") {
|
|
110
|
+
setNodes((nds) => nds.filter((n) => n.id !== node.id));
|
|
111
|
+
setEdges((eds) =>
|
|
112
|
+
eds.filter((e) => e.source !== node.id && e.target !== node.id)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}, [activeTool, onBehaviorSelect]);
|
|
116
|
+
|
|
117
|
+
// Callback for edge click to delete it (if tool is active)
|
|
118
|
+
const onEdgeClick = useCallback((event, edge) => {
|
|
119
|
+
if (activeTool === "delete") {
|
|
120
|
+
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
|
121
|
+
}
|
|
122
|
+
}, [activeTool]);
|
|
123
|
+
|
|
124
|
+
const onSelectionChange = useCallback((selection) => {
|
|
125
|
+
if (selection.nodes.length > 0) {
|
|
126
|
+
onBehaviorSelect(selection.nodes[0]);
|
|
127
|
+
}
|
|
128
|
+
}, [ onBehaviorSelect]);
|
|
129
|
+
|
|
130
|
+
// Callback for when edge is connected, nodesConnectable={activeTool === "connect"}
|
|
131
|
+
// is used to determine connectability
|
|
132
|
+
const onConnect = useCallback((connection) => {
|
|
133
|
+
const edge = {
|
|
134
|
+
...connection,
|
|
135
|
+
id: crypto.randomUUID(),
|
|
136
|
+
type: "smoothstep",
|
|
137
|
+
animated: true,
|
|
138
|
+
};
|
|
139
|
+
setEdges((eds) => [...eds, edge]);
|
|
140
|
+
onAddEdge(edge);
|
|
141
|
+
}, [activeTool, onAddEdge]);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<ReactFlow
|
|
145
|
+
nodes={ghostNode ? [...nodes, ghostNode] : nodes}
|
|
146
|
+
edges={edges}
|
|
147
|
+
onPaneMouseMove={onPaneMouseMove}
|
|
148
|
+
onPaneMouseLeave={onPaneMouseLeave}
|
|
149
|
+
onPaneClick={onPaneClick}
|
|
150
|
+
onConnect={onConnect}
|
|
151
|
+
onNodesChange={onNodesChange}
|
|
152
|
+
onEdgesChange={onEdgesChange}
|
|
153
|
+
onNodeClick={onNodeClick}
|
|
154
|
+
onEdgeClick={onEdgeClick}
|
|
155
|
+
colorMode={"dark"}
|
|
156
|
+
edgeTypes={edgeTypes}
|
|
157
|
+
onSelectionChange={onSelectionChange}
|
|
158
|
+
fitView
|
|
159
|
+
>
|
|
160
|
+
<Background />
|
|
161
|
+
<Controls />
|
|
162
|
+
</ReactFlow>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Converts a DAL design specification object into React Flow elements (nodes and edges).
|
|
4
|
+
* @param {Object} design
|
|
5
|
+
* @returns {Object} An object containing nodes and edges for React Flow
|
|
6
|
+
*/
|
|
7
|
+
export const designToReactFlowElements = (design) => {
|
|
8
|
+
|
|
9
|
+
let edges = [];
|
|
10
|
+
let nodes = [];
|
|
11
|
+
design.nodes.forEach((node) => {
|
|
12
|
+
nodes.push({
|
|
13
|
+
id: node.behavior.uid,
|
|
14
|
+
type: 'default',
|
|
15
|
+
data: { label: node.behavior.name },
|
|
16
|
+
position: { x: 10, y: 10 },
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
node.goToBehaviors.forEach((goTo) => {
|
|
20
|
+
edges.push({
|
|
21
|
+
id: `${node.behavior.uid}->${goTo.uid}`,
|
|
22
|
+
type: "bezier",
|
|
23
|
+
source: node.behavior.uid,
|
|
24
|
+
target: goTo.uid,
|
|
25
|
+
animated: true
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
nodes: nodes,
|
|
32
|
+
edges: edges
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./BehavioralGraphBuilder.jsx"
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect } from "react";
|
|
2
|
+
import { BehavioralGraphBuilder } from "../components/BehavioralGraphBuilder";
|
|
3
|
+
import { ToolBar } from "./components/ToolBar/ToolBar";
|
|
4
|
+
import { useArgs } from "@storybook/preview-api";
|
|
5
|
+
import { action } from "@storybook/addon-actions";
|
|
6
|
+
|
|
7
|
+
import design from "./data/Designs/simple_design_temp.json";
|
|
8
|
+
import "./BehavioralGraphBuilderStories.scss"
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
title: 'BehavioralGraphBuilder',
|
|
12
|
+
component: BehavioralGraphBuilder,
|
|
13
|
+
argTypes: {
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const Template = (args) => {
|
|
18
|
+
const [, updateArgs] = useArgs();
|
|
19
|
+
|
|
20
|
+
const [activeTool, setActiveTool] = useState("select");
|
|
21
|
+
|
|
22
|
+
const selectTool = useCallback((tool) => {
|
|
23
|
+
updateArgs({activeTool : tool});
|
|
24
|
+
}, [activeTool, setActiveTool]);
|
|
25
|
+
|
|
26
|
+
const onBehaviorSelect = useCallback((behavior) => {
|
|
27
|
+
action('Selected Behavior:')(behavior);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const onAddBehavior = useCallback((behavior) => {
|
|
31
|
+
action('Added Behavior:')(behavior);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const onAddEdge = useCallback((edge) => {
|
|
35
|
+
action('Added Edge:')(edge);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
updateArgs(
|
|
40
|
+
{
|
|
41
|
+
activeTool: activeTool,
|
|
42
|
+
design: design,
|
|
43
|
+
onBehaviorSelect: onBehaviorSelect,
|
|
44
|
+
onAddBehavior: onAddBehavior,
|
|
45
|
+
onAddEdge: onAddEdge
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="graphBuilderRootContainer">
|
|
52
|
+
<div className="toolbar">
|
|
53
|
+
<ToolBar onSelectTool={selectTool}/>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="flow">
|
|
56
|
+
<BehavioralGraphBuilder {...args}/>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const Default = Template.bind({})
|
|
63
|
+
|
|
64
|
+
Default.args = {
|
|
65
|
+
design: design
|
|
66
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Cursor,
|
|
5
|
+
Square,
|
|
6
|
+
NodePlus,
|
|
7
|
+
Trash
|
|
8
|
+
} from "react-bootstrap-icons";
|
|
9
|
+
|
|
10
|
+
import "./ToolBar.scss";
|
|
11
|
+
|
|
12
|
+
ToolBar.propTypes = {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Toolbar Component
|
|
16
|
+
* @return {JSX.Element}
|
|
17
|
+
*/
|
|
18
|
+
export function ToolBar({ onSelectTool }) {
|
|
19
|
+
const [selectedTool, setSelectedTool] = useState("select");
|
|
20
|
+
|
|
21
|
+
const selectTool = (tool) => {
|
|
22
|
+
setSelectedTool(tool);
|
|
23
|
+
onSelectTool(tool);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="toolbarWrapper">
|
|
28
|
+
<div className="toolbarContainer">
|
|
29
|
+
<Cursor
|
|
30
|
+
onClick={(e) => selectTool("select")}
|
|
31
|
+
style={{color: selectedTool === "select" ? "white": "grey"}}
|
|
32
|
+
title="Select"
|
|
33
|
+
className="icon"
|
|
34
|
+
/>
|
|
35
|
+
<Square
|
|
36
|
+
onClick={(e) => selectTool("drop")}
|
|
37
|
+
style={{color: selectedTool === "drop" ? "white": "grey"}}
|
|
38
|
+
title="Add Node"
|
|
39
|
+
className="icon"
|
|
40
|
+
/>
|
|
41
|
+
<NodePlus
|
|
42
|
+
onClick={(e) => selectTool("connect")}
|
|
43
|
+
style={{color: selectedTool === "connect" ? "white": "grey"}}
|
|
44
|
+
title="Connect Node"
|
|
45
|
+
className="icon"
|
|
46
|
+
/>
|
|
47
|
+
<Trash
|
|
48
|
+
onClick={(e) => selectTool("delete")}
|
|
49
|
+
style={{color: selectedTool === "delete" ? "white": "grey"}}
|
|
50
|
+
title="Delete Node"
|
|
51
|
+
className="icon"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="toolbarContainer bottom"></div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.toolbarWrapper {
|
|
2
|
+
display:flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
justify-content:space-around;
|
|
5
|
+
height: 100%;
|
|
6
|
+
background-color: #333333;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.toolbarContainer {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 15px;
|
|
13
|
+
width:100%;
|
|
14
|
+
align-items: center;
|
|
15
|
+
padding: 10px 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.toolbarWrapper > .bottom {
|
|
19
|
+
margin-top: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.toolbarContainer > .icon {
|
|
23
|
+
color:grey;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.toolbarContainer > .icon:hover {
|
|
27
|
+
color:white;
|
|
28
|
+
cursor:pointer;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"uid":"60e79b4f-fc4b-4237-989f-e1b1559c2ee9","type":5,"nodes":[{"uid":"08a4e18c-f293-4ba1-be0a-c8451b50e819","type":6,"goToBehaviors":[{"uid":"35a2759b-e0df-47db-93b7-1f18c216922d","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptBookFromUser"}],"behavior":{"uid":"a873c4ba-3a63-4097-80df-46ed361f2e26","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAddBookToBasket"}},{"uid":"db74773a-2f47-4873-b66f-08da9aaf196f","type":6,"goToBehaviors":[{"uid":"ac522716-13d2-48e5-b241-f2366b37efdb","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToBasket"}],"behavior":{"uid":"35a2759b-e0df-47db-93b7-1f18c216922d","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptBookFromUser"}},{"uid":"31bba9d3-9af2-41bc-bd70-d757f62b40c6","type":6,"goToBehaviors":[{"uid":"d4ec5fe5-acd2-4a8b-8e09-9ea3d5212a1f","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GenerateAuditReport"}],"behavior":{"uid":"53cebd65-5c19-4dde-8e50-4acc581dc166","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToAuditLibrary"}},{"uid":"836fdec5-db3e-4770-95fe-70c2d2c1261c","type":6,"goToBehaviors":[{"uid":"3e0b400c-f92a-48b5-b67b-e65d69f18a21","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"HandAuditToUser"}],"behavior":{"uid":"d4ec5fe5-acd2-4a8b-8e09-9ea3d5212a1f","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GenerateAuditReport"}},{"uid":"f79c407e-66fc-4d64-bcce-4a1c4841d098","type":6,"goToBehaviors":[{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}],"behavior":{"uid":"b39dfb15-d121-4ded-aa29-d317149308e9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AcceptChoiceToPlaceBooksOnShelf"}},{"uid":"0e266610-d498-4857-9249-7e6c98a1077f","type":6,"goToBehaviors":[{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}],"behavior":{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}},{"uid":"b009ee09-e037-4864-99a2-15f80187623f","type":6,"goToBehaviors":[{"uid":"457332a3-3c79-4c04-83c7-2b5b0539c0da","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"CreateSlotOnBookShelf"}],"behavior":{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}},{"uid":"b2acd6d3-d6b8-41de-b8a1-f22b4db4fcbe","type":6,"goToBehaviors":[{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}],"behavior":{"uid":"7e80caf3-7fa9-4684-af89-fe2556addb87","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetFirstLetterOfBookName"}},{"uid":"3baca40a-9584-4ba7-be1b-235a660ca472","type":6,"goToBehaviors":[{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}],"behavior":{"uid":"457332a3-3c79-4c04-83c7-2b5b0539c0da","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"CreateSlotOnBookShelf"}},{"uid":"14acf45d-ca77-48f4-9ff0-5692133a6258","type":6,"goToBehaviors":[{"uid":"1c75d75c-8970-4dd1-b91e-c79590354bd9","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"GetBookFromBasket"}],"behavior":{"uid":"a666f210-c00c-48bc-a13f-aea72f068e37","type":1,"participants":[],"abstractionIds":[],"invalidWorldState":false,"name":"AddBookToShelf"}}]}
|