Skip to content

Commit e71a870

Browse files
committed
Separate composable components and change workflow UI pattern
All simple nodes (with no children) are shown at the top and nested nodes (with children) are shown at the bottom. This makes it the nesting more clear.
1 parent 6d2d31b commit e71a870

File tree

1 file changed

+160
-56
lines changed
  • workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui

1 file changed

+160
-56
lines changed

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt

Lines changed: 160 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ internal fun RenderTrace(
6363
val affectedNodes = remember(traceSource) { mutableStateListOf<Set<Node>>() }
6464

6565
// Updates current state with the new data from trace source.
66-
fun addToStates(frame: List<Node>, tree: List<Node>, affected: List<Set<Node>>) {
66+
fun addToStates(
67+
frame: List<Node>,
68+
tree: List<Node>,
69+
affected: List<Set<Node>>
70+
) {
6771
frames.addAll(frame)
6872
fullTree.addAll(tree)
6973
affectedNodes.addAll(affected)
@@ -82,6 +86,7 @@ internal fun RenderTrace(
8286
error = parseResult.error
8387
false
8488
}
89+
8590
is ParseResult.Success -> {
8691
addToStates(
8792
frame = parseResult.trace,
@@ -160,15 +165,12 @@ private fun DrawTree(
160165
.fillMaxSize(),
161166
horizontalAlignment = Alignment.CenterHorizontally,
162167
) {
163-
val groupKey = "${node.id}-unaffected"
164168
val isAffected = affectedNodes.contains(node)
165169
// By default, nodes that relevant to this specific frame are expanded. All others are closed.
166170
LaunchedEffect(expandedNodes) {
167171
expandedNodes[node.id] = isAffected
168-
expandedNodes[groupKey] = false
169172
}
170173
val isExpanded = expandedNodes[node.id] == true
171-
val unaffectedChildrenExpanded = expandedNodes[groupKey] == true
172174

173175
DrawNode(
174176
node = node,
@@ -179,49 +181,88 @@ private fun DrawTree(
179181
onExpandToggle = { expandedNodes[node.id] = !expandedNodes[node.id]!! }
180182
)
181183

182-
// Draws the node's children recursively.
183184
if (isExpanded) {
184-
// Draw the affected children, and only draw the unaffected children it is clicked annd expanded.
185185
val (affectedChildren, unaffectedChildren) = node.children.values
186186
.partition { affectedNodes.contains(it) }
187187

188+
UnaffectedChildrenGroup(
189+
node = node,
190+
children = unaffectedChildren,
191+
previousFrameNode = previousFrameNode,
192+
affectedNodes = affectedNodes,
193+
expandedNodes = expandedNodes,
194+
onNodeSelect = onNodeSelect
195+
)
188196

189-
if (unaffectedChildren.isNotEmpty()) {
190-
Box (
191-
modifier = Modifier
192-
.onPointerEvent(PointerEventType.Press) {
193-
if (it.buttons.isSecondaryPressed) {
194-
// The open/close state for this group is always set when this node is first composed.
195-
expandedNodes[groupKey] = !expandedNodes[groupKey]!!
196-
}
197-
}
198-
) {
199-
if (!unaffectedChildrenExpanded) {
200-
Column(
201-
modifier = Modifier
202-
.background(Color.LightGray.copy(alpha = 0.3f), shape = RoundedCornerShape(4.dp))
203-
.border(1.dp, Color.Gray)
204-
.padding(8.dp),
205-
horizontalAlignment = Alignment.CenterHorizontally
206-
) {
207-
Text(text = "${node.name}'s", color = Color.DarkGray)
208-
Text(text = "${unaffectedChildren.size} unaffected children", color = Color.DarkGray, fontSize = 12.sp)
209-
}
210-
} else {
211-
DrawChildren(
212-
children = unaffectedChildren,
213-
previousFrameNode = previousFrameNode,
214-
affectedNodes = affectedNodes,
215-
expandedNodes = expandedNodes,
216-
onNodeSelect = onNodeSelect
217-
)
218-
}
197+
AffectedChildrenGroup(
198+
children = affectedChildren,
199+
previousFrameNode = previousFrameNode,
200+
affectedNodes = affectedNodes,
201+
expandedNodes = expandedNodes,
202+
onNodeSelect = onNodeSelect
203+
)
204+
}
205+
}
206+
}
207+
208+
/**
209+
* Draws the group of unaffected children, which can be open and closed to expand/collapse them.
210+
*
211+
* If an unaffected children also has other children, it cannot be opened, since the this group
212+
* treats all nodes as one entity, so the onClick for the whole group overrides the onClick for the
213+
* individual nodes.
214+
*/
215+
@OptIn(ExperimentalComposeUiApi::class)
216+
@Composable
217+
private fun UnaffectedChildrenGroup(
218+
node: Node,
219+
children: List<Node>,
220+
previousFrameNode: Node?,
221+
affectedNodes: Set<Node>,
222+
expandedNodes: MutableMap<String, Boolean>,
223+
onNodeSelect: (NodeUpdate) -> Unit
224+
) {
225+
if (children.isEmpty()) return
226+
227+
val groupKey = "${node.id}_unaffected_group"
228+
LaunchedEffect(Unit) {
229+
expandedNodes[groupKey] = false
230+
}
231+
val isGroupExpanded = expandedNodes[groupKey] == true
232+
233+
Box(
234+
modifier = Modifier
235+
.onPointerEvent(PointerEventType.Press) {
236+
if (it.buttons.isSecondaryPressed) {
237+
expandedNodes[groupKey] = !isGroupExpanded
219238
}
220239
}
221-
222-
if (affectedChildren.isNotEmpty()) {
223-
DrawChildren(
224-
children = affectedChildren,
240+
) {
241+
if (!isGroupExpanded) {
242+
Column(
243+
modifier = Modifier
244+
.background(Color.LightGray.copy(alpha = 0.3f), shape = RoundedCornerShape(4.dp))
245+
.border(1.dp, Color.Gray)
246+
.padding(8.dp),
247+
horizontalAlignment = Alignment.CenterHorizontally
248+
) {
249+
Text(text = "${node.name}'s", color = Color.DarkGray)
250+
Text(
251+
text = "${children.size} unaffected children",
252+
color = Color.DarkGray,
253+
fontSize = 12.sp
254+
)
255+
}
256+
} else {
257+
Column(
258+
modifier = Modifier
259+
.fillMaxWidth()
260+
.padding(6.dp)
261+
.border(3.dp, Color.Black),
262+
horizontalAlignment = Alignment.CenterHorizontally,
263+
) {
264+
DrawChildrenInGroups(
265+
children = children,
225266
previousFrameNode = previousFrameNode,
226267
affectedNodes = affectedNodes,
227268
expandedNodes = expandedNodes,
@@ -232,29 +273,92 @@ private fun DrawTree(
232273
}
233274
}
234275

276+
/**
277+
* Draws the group of affected children
278+
*/
235279
@Composable
236-
private fun DrawChildren(
280+
private fun AffectedChildrenGroup(
237281
children: List<Node>,
238282
previousFrameNode: Node?,
239283
affectedNodes: Set<Node>,
240284
expandedNodes: MutableMap<String, Boolean>,
241-
onNodeSelect: (NodeUpdate) -> Unit,
285+
onNodeSelect: (NodeUpdate) -> Unit
242286
) {
243-
Row(
244-
modifier = Modifier
245-
.fillMaxWidth()
246-
.padding(8.dp),
247-
horizontalArrangement = Arrangement.SpaceEvenly,
248-
verticalAlignment = Alignment.Top
287+
if (children.isEmpty()) return
288+
289+
DrawChildrenInGroups(
290+
children = children,
291+
previousFrameNode = previousFrameNode,
292+
affectedNodes = affectedNodes,
293+
expandedNodes = expandedNodes,
294+
onNodeSelect = onNodeSelect
295+
)
296+
}
297+
298+
/**
299+
* Draws the children in a grid manner, to avoid horizontal clutter and make better use of space.
300+
*/
301+
@Composable
302+
private fun DrawChildrenInGroups(
303+
children: List<Node>,
304+
previousFrameNode: Node?,
305+
affectedNodes: Set<Node>,
306+
expandedNodes: MutableMap<String, Boolean>,
307+
onNodeSelect: (NodeUpdate) -> Unit
308+
) {
309+
// Split children into those with children (nested) and those without
310+
val (nestedChildren, simpleChildren) = children.partition { it.children.isNotEmpty() }
311+
312+
Column(
313+
verticalArrangement = Arrangement.spacedBy(16.dp), // Increased spacing between sections
249314
) {
250-
children.forEach { childNode ->
251-
DrawTree(
252-
node = childNode,
253-
previousFrameNode = previousFrameNode?.children?.get(childNode.id),
254-
affectedNodes = affectedNodes,
255-
expandedNodes = expandedNodes,
256-
onNodeSelect = onNodeSelect
257-
)
315+
// Draw simple children in a grid at the top
316+
if (simpleChildren.isNotEmpty()) {
317+
val groupedSimpleChildren = simpleChildren.chunked(5)
318+
319+
groupedSimpleChildren.forEach { group ->
320+
Row(
321+
modifier = Modifier
322+
.fillMaxWidth()
323+
.padding(8.dp)
324+
.align(Alignment.CenterHorizontally),
325+
horizontalArrangement = Arrangement.SpaceEvenly,
326+
verticalAlignment = Alignment.Top
327+
) {
328+
group.forEach { childNode ->
329+
DrawTree(
330+
node = childNode,
331+
previousFrameNode = previousFrameNode?.children?.get(childNode.id),
332+
affectedNodes = affectedNodes,
333+
expandedNodes = expandedNodes,
334+
onNodeSelect = onNodeSelect
335+
)
336+
}
337+
}
338+
339+
}
340+
}
341+
342+
// Draw nested children in a single row at the bottom
343+
if (nestedChildren.isNotEmpty()) {
344+
Row(
345+
modifier = Modifier
346+
.fillMaxWidth()
347+
.padding(horizontal = 8.dp)
348+
.align(Alignment.CenterHorizontally),
349+
horizontalArrangement = Arrangement.SpaceEvenly,
350+
verticalAlignment = Alignment.Top
351+
) {
352+
nestedChildren.forEach { childNode ->
353+
DrawTree(
354+
node = childNode,
355+
previousFrameNode = previousFrameNode?.children?.get(childNode.id),
356+
affectedNodes = affectedNodes,
357+
expandedNodes = expandedNodes,
358+
onNodeSelect = onNodeSelect
359+
)
360+
}
361+
}
258362
}
259363
}
260364
}

0 commit comments

Comments
 (0)