Skip to content

Commit 81b0da7

Browse files
committed
Show before and after of node states when selecting them
This commit introduces the idea of a NodeDiff, which stores the node's current and previous state - from the previous frame - in order to better represent what is actually changing through the UI. If there is no previous frame (only on the first frame of the trace), there is no need to show a diff, so we plainly depict the node's states.
1 parent 32c5b9f commit 81b0da7

File tree

6 files changed

+104
-31
lines changed

6 files changed

+104
-31
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
1313
import androidx.compose.ui.Modifier
1414
import androidx.compose.ui.geometry.Offset
1515
import com.squareup.workflow1.traceviewer.model.Node
16+
import com.squareup.workflow1.traceviewer.model.NodeDiff
1617
import com.squareup.workflow1.traceviewer.ui.FrameSelectTab
1718
import com.squareup.workflow1.traceviewer.ui.RenderDiagram
1819
import com.squareup.workflow1.traceviewer.ui.RightInfoPanel
@@ -28,7 +29,7 @@ public fun App(
2829
modifier: Modifier = Modifier
2930
) {
3031
var selectedTraceFile by remember { mutableStateOf<PlatformFile?>(null) }
31-
var selectedNode by remember { mutableStateOf<Node?>(null) }
32+
var selectedNode by remember { mutableStateOf<NodeDiff?>(null) }
3233
var workflowFrames by remember { mutableStateOf<List<Node>>(emptyList()) }
3334
var frameIndex by remember { mutableIntStateOf(0) }
3435
val sandboxState = remember { SandboxState() }
@@ -49,7 +50,9 @@ public fun App(
4950
traceFile = selectedTraceFile!!,
5051
frameInd = frameIndex,
5152
onFileParse = { workflowFrames = it },
52-
onNodeSelect = { selectedNode = it }
53+
onNodeSelect = { node, prevNode ->
54+
selectedNode = NodeDiff(node, prevNode)
55+
}
5356
)
5457
}
5558
}

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/model/Node.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ internal data class Node(
3131
override fun hashCode(): Int {
3232
return id.hashCode()
3333
}
34+
35+
companion object {
36+
fun getNodeField(node: Node, field: String): String {
37+
return when(field.lowercase()) {
38+
"name" -> node.name
39+
"id" -> node.id
40+
"parent" -> node.parent
41+
"parentid" -> node.parentId
42+
"props" -> node.props
43+
"state" -> node.state
44+
"rendering" -> node.rendering
45+
"children" -> node.children.toString()
46+
else -> throw IllegalArgumentException("Unknown field: $field")
47+
}
48+
}
49+
}
3450
}
3551

3652
internal fun Node.addChild(child: Node): Node {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.squareup.workflow1.traceviewer.model
2+
3+
/**
4+
* Represents the difference between the current and previous state of a node in the workflow trace.
5+
* This will be what is passed as a state between UI to display the diff.
6+
*
7+
* If it's the first node in the frame, [previous] will be null and there is no difference to show.
8+
*/
9+
internal class NodeDiff(
10+
val current: Node,
11+
val previous: Node?,
12+
)

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

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ import androidx.compose.runtime.setValue
2929
import androidx.compose.ui.Alignment
3030
import androidx.compose.ui.Modifier
3131
import androidx.compose.ui.graphics.Color
32+
import androidx.compose.ui.text.TextStyle
33+
import androidx.compose.ui.text.font.FontStyle
3234
import androidx.compose.ui.text.font.FontWeight
3335
import androidx.compose.ui.unit.dp
3436
import com.squareup.workflow1.traceviewer.model.Node
37+
import com.squareup.workflow1.traceviewer.model.NodeDiff
3538

3639
/**
3740
* A panel that displays information about the selected workflow node.
@@ -41,7 +44,7 @@ import com.squareup.workflow1.traceviewer.model.Node
4144
*/
4245
@Composable
4346
internal fun RightInfoPanel(
44-
selectedNode: Node?,
47+
selectedNode: NodeDiff?,
4548
modifier: Modifier = Modifier
4649
) {
4750
Row(
@@ -77,7 +80,7 @@ internal fun RightInfoPanel(
7780
*/
7881
@Composable
7982
private fun NodePanelDetails(
80-
node: Node?,
83+
node: NodeDiff?,
8184
modifier: Modifier = Modifier
8285
) {
8386
LazyColumn(
@@ -93,26 +96,20 @@ private fun NodePanelDetails(
9396
}
9497
return@LazyColumn
9598
}
96-
97-
val fields = listOf(
98-
"Name" to node.name,
99-
"ID" to node.id,
100-
"Props" to node.props,
101-
"State" to node.state,
102-
"Rendering" to node.rendering
103-
)
10499
item {
105100
Text(
106101
text = "Workflow Details",
107102
style = MaterialTheme.typography.h6,
108103
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp)
109104
)
110105
}
106+
val fields = listOf("Name", "Id", "Props", "State", "Rendering")
111107

112-
items(fields) { (label, value) ->
108+
items(fields) { field ->
113109
DetailCard(
114-
label = label,
115-
value = value
110+
label = field,
111+
currValue = Node.getNodeField(node.current, field),
112+
pastValue = if (node.previous != null) Node.getNodeField(node.previous, field) else null
116113
)
117114
}
118115
}
@@ -126,7 +123,8 @@ private fun NodePanelDetails(
126123
@Composable
127124
private fun DetailCard(
128125
label: String,
129-
value: String,
126+
currValue: String,
127+
pastValue: String?
130128
) {
131129
var open by remember { mutableStateOf(true) }
132130
Card(
@@ -145,17 +143,46 @@ private fun DetailCard(
145143
) {
146144
Text(
147145
text = label,
148-
style = MaterialTheme.typography.subtitle1,
146+
style = MaterialTheme.typography.h6,
149147
color = Color.Black,
150148
fontWeight = FontWeight.Medium
151149
)
152150
if (!open) { return@Card }
153151

154152
Spacer(modifier = Modifier.height(4.dp))
155-
Text(
156-
text = value,
157-
style = MaterialTheme.typography.body1
158-
)
153+
if (pastValue != null) {
154+
Column {
155+
Text(
156+
text = "Before:",
157+
style = TextStyle(fontStyle = FontStyle.Italic),
158+
color = Color.Black,
159+
fontWeight = FontWeight.Medium
160+
)
161+
Text(
162+
text = pastValue,
163+
style = MaterialTheme.typography.body2,
164+
color = Color.Black
165+
)
166+
Spacer(modifier = Modifier.height(8.dp))
167+
Text(
168+
text = "After:",
169+
style = TextStyle(fontStyle = FontStyle.Italic),
170+
color = Color.Black,
171+
fontWeight = FontWeight.Medium
172+
)
173+
Text(
174+
text = currValue,
175+
style = MaterialTheme.typography.body2,
176+
color = Color.Black
177+
)
178+
}
179+
} else {
180+
Text(
181+
text = currValue,
182+
style = MaterialTheme.typography.body1,
183+
color = Color.Black
184+
)
185+
}
159186
}
160187
}
161188
}

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
2121
import androidx.compose.ui.graphics.Color
2222
import androidx.compose.ui.unit.dp
2323
import com.squareup.workflow1.traceviewer.model.Node
24+
import com.squareup.workflow1.traceviewer.model.NodeDiff
2425
import com.squareup.workflow1.traceviewer.util.ParseResult
2526
import com.squareup.workflow1.traceviewer.util.parseTrace
2627
import io.github.vinceglb.filekit.PlatformFile
@@ -34,7 +35,7 @@ internal fun RenderDiagram(
3435
traceFile: PlatformFile,
3536
frameInd: Int,
3637
onFileParse: (List<Node>) -> Unit,
37-
onNodeSelect: (Node) -> Unit,
38+
onNodeSelect: (Node, Node?) -> Unit,
3839
modifier: Modifier = Modifier
3940
) {
4041
var isLoading by remember(traceFile) { mutableStateOf(true) }
@@ -67,7 +68,8 @@ internal fun RenderDiagram(
6768
}
6869

6970
if (!isLoading) {
70-
DrawTree(fullTree[frameInd], affectedNodes[frameInd], onNodeSelect)
71+
val previousFrame = if (frameInd > 0) fullTree[frameInd - 1] else null
72+
DrawTree(fullTree[frameInd], previousFrame, affectedNodes[frameInd], onNodeSelect)
7173
}
7274
}
7375

@@ -78,10 +80,12 @@ internal fun RenderDiagram(
7880
@Composable
7981
private fun DrawTree(
8082
node: Node,
83+
previousNode: Node?,
8184
affectedNodes: Set<Node>,
82-
onNodeSelect: (Node) -> Unit,
85+
onNodeSelect: (Node, Node?) -> Unit,
8386
modifier: Modifier = Modifier,
8487
) {
88+
println("Drawing node: ${node.name} with state: ${node.state}")
8589
Column(
8690
modifier
8791
.padding(5.dp)
@@ -90,15 +94,21 @@ private fun DrawTree(
9094
horizontalAlignment = Alignment.CenterHorizontally,
9195
) {
9296
val isAffected = affectedNodes.contains(node)
93-
DrawNode(node, isAffected, onNodeSelect)
97+
DrawNode(node, previousNode, isAffected, onNodeSelect)
9498

9599
// Draws the node's children recursively.
96100
Row(
97101
horizontalArrangement = Arrangement.Center,
98102
verticalAlignment = Alignment.Top
99103
) {
100-
node.children.forEach { childNode ->
101-
DrawTree(childNode, affectedNodes, onNodeSelect)
104+
/*
105+
We pair up the current node's children with previous frame's children.
106+
In the edge case that the current frame has additional children compared to the previous
107+
frame, we replace with null and will check before next recursive call.
108+
*/
109+
node.children.forEachIndexed { index, childNode ->
110+
val prevChildNode = previousNode?.children?.getOrNull(index)
111+
DrawTree(childNode, prevChildNode, affectedNodes, onNodeSelect)
102112
}
103113
}
104114
}
@@ -110,19 +120,23 @@ private fun DrawTree(
110120
@Composable
111121
private fun DrawNode(
112122
node: Node,
123+
previousNode: Node?,
113124
isAffected: Boolean,
114-
onNodeSelect: (Node) -> Unit,
125+
onNodeSelect: (Node, Node?) -> Unit,
115126
) {
116127
Box(
117128
modifier = Modifier
118129
.background(if (isAffected) Color.Green else Color.Transparent)
119130
.clickable {
120131
// Selecting a node will bubble back up to the main view to handle the selection
121-
onNodeSelect(node)
132+
onNodeSelect(node, previousNode)
122133
}
123134
.padding(10.dp)
124135
) {
125136
Column(horizontalAlignment = Alignment.CenterHorizontally) {
137+
if (node.name == "LocationServicesGateKeeper"){
138+
println(node.state)
139+
}
126140
Text(text = node.name)
127141
Text(text = "ID: ${node.id}")
128142
}

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/JsonParser.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ private fun createMoshiAdapter(): JsonAdapter<List<List<Node>>> {
6868
*/
6969
private fun getFrameFromRenderPass(renderPass: List<Node>): Node {
7070
val childrenByParent: Map<String, List<Node>> = renderPass.groupBy { it.parentId }
71-
println(childrenByParent)
7271
val root = childrenByParent[ROOT_ID]?.single()
7372
return buildTree(root!!, childrenByParent)
7473
}
@@ -104,7 +103,9 @@ internal fun mergeFrameIntoMainTree(
104103
throw IllegalArgumentException("Frame root ID does not match main tree root ID.")
105104
}
106105

107-
return frame.children.fold(main) { mergedTree, frameChild ->
106+
val updatedNode = frame.copy(children = main.children)
107+
108+
return frame.children.fold(updatedNode) { mergedTree, frameChild ->
108109
val mainTreeChild = mergedTree.children.singleOrNull { it.id == frameChild.id }
109110
if (mainTreeChild != null) {
110111
mergedTree.replaceChild(mergeFrameIntoMainTree(frameChild, mainTreeChild))

0 commit comments

Comments
 (0)