Skip to content

Commit 3baf5e9

Browse files
authored
feat(extension): support grouped updates (#752)
* feat(extension): support grouped updates This makes the existing "group updates" setting work. When enabled, repeated updates of the same tree will be grouped into one node with a count. The updates are not lost, in that the setting can be disable to re-expand the nodes. * feat(extension): add collapsed tree to store * feat(extension): add count to tree nodes * feat(extension): use a union for node types and display first value
1 parent 9338bbf commit 3baf5e9

File tree

6 files changed

+100
-8
lines changed

6 files changed

+100
-8
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ charset = utf-8
77
trim_trailing_whitespace = true
88
insert_final_newline = true
99

10-
[{*.json,.*rc,*.yml}]
10+
[{*.json,.*rc,*.yml,*.css}]
1111
indent_style = space
1212
indent_size = 2
1313
insert_final_newline = false

extension/src/components/UpdateItem.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { SignalUpdate } from "../types";
22

33
interface UpdateItemProps {
44
update: SignalUpdate;
5+
firstUpdate?: SignalUpdate;
6+
count?: number;
57
}
68

7-
export function UpdateItem({ update }: UpdateItemProps) {
9+
export function UpdateItem({ update, count, firstUpdate }: UpdateItemProps) {
810
const time = new Date(
911
update.timestamp || update.receivedAt
1012
).toLocaleTimeString();
@@ -23,13 +25,19 @@ export function UpdateItem({ update }: UpdateItemProps) {
2325
}
2426
return String(value);
2527
};
28+
const countLabel = count && (
29+
<span class="update-count" title="Number of grouped identical updates">
30+
x{count}
31+
</span>
32+
);
2633

2734
if (update.type === "effect") {
2835
return (
2936
<div className={`update-item ${update.type}`}>
3037
<div className="update-header">
3138
<span className="signal-name">
3239
↪️ {update.signalName}
40+
{countLabel}
3341
{update.componentNames && update.componentNames.length > 0 && (
3442
<ul class="component-list">
3543
<span class="component-name-header">Rerendered</span>
@@ -50,16 +58,25 @@ export function UpdateItem({ update }: UpdateItemProps) {
5058

5159
const prevValue = formatValue(update.prevValue);
5260
const newValue = formatValue(update.newValue);
61+
const firstValue =
62+
firstUpdate !== undefined ? formatValue(firstUpdate.prevValue) : undefined;
5363

5464
return (
5565
<div class={`update-item ${update.type}`}>
5666
<div class="update-header">
5767
<span class="signal-name">
5868
{update.depth === 0 ? "🎯" : "↪️"} {update.signalName}
69+
{countLabel}
5970
</span>
6071
<span class="update-time">{time}</span>
6172
</div>
6273
<div class="value-change">
74+
{firstValue && firstValue !== prevValue && (
75+
<>
76+
<span class="value-prev">{firstValue}</span>
77+
<span class="value-arrow">...</span>
78+
</>
79+
)}
6380
<span class="value-prev">{prevValue}</span>
6481
<span class="value-arrow"></span>
6582
<span class="value-new">{newValue}</span>

extension/src/components/UpdateTreeNode.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export function UpdateTreeNodeComponent({ node }: UpdateTreeNodeProps) {
1414
};
1515

1616
const hasChildren = node.children.length > 0;
17+
const nodeCount = node.type === "group" ? node.count : undefined;
18+
const firstUpdate = node.type === "group" ? node.firstUpdate : undefined;
1719

1820
return (
1921
<div className="tree-node">
@@ -29,7 +31,11 @@ export function UpdateTreeNodeComponent({ node }: UpdateTreeNodeProps) {
2931
)}
3032
{!hasChildren && <div className="collapse-spacer" />}
3133
<div className="update-content">
32-
<UpdateItem update={node.update} />
34+
<UpdateItem
35+
update={node.update}
36+
count={nodeCount}
37+
firstUpdate={firstUpdate}
38+
/>
3339
</div>
3440
</div>
3541

extension/src/components/UpdatesContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useSignalEffect } from "@preact/signals";
55

66
export function UpdatesContainer() {
77
const updatesListRef = useRef<HTMLDivElement>(null);
8-
const updateTree = updatesStore.updateTree.value;
8+
const updateTree = updatesStore.collapsedUpdateTree.value;
99

1010
useSignalEffect(() => {
1111
// Register scroll restoration

extension/src/models/UpdatesModel.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,65 @@
11
import { signal, computed, effect } from "@preact/signals";
22
import { Divider, SignalUpdate } from "../types";
3+
import { settingsStore } from "./SettingsModel";
34

4-
export interface UpdateTreeNode {
5+
export interface UpdateTreeNodeBase {
56
id: string;
67
update: SignalUpdate;
78
children: UpdateTreeNode[];
89
depth: number;
910
hasChildren: boolean;
1011
}
1112

13+
export interface UpdateTreeNodeSingle extends UpdateTreeNodeBase {
14+
type: "single";
15+
}
16+
17+
export interface UpdateTreeNodeGroup extends UpdateTreeNodeBase {
18+
type: "group";
19+
count: number;
20+
firstUpdate: SignalUpdate;
21+
}
22+
23+
export type UpdateTreeNode = UpdateTreeNodeGroup | UpdateTreeNodeSingle;
24+
25+
const nodesAreEqual = (a: UpdateTreeNode, b: UpdateTreeNode): boolean => {
26+
return (
27+
a.update.signalId === b.update.signalId &&
28+
a.children.length === b.children.length &&
29+
a.children.every((child, index) => nodesAreEqual(child, b.children[index]))
30+
);
31+
};
32+
33+
const collapseTree = (nodes: UpdateTreeNodeSingle[]): UpdateTreeNode[] => {
34+
const tree: UpdateTreeNode[] = [];
35+
let lastNode: UpdateTreeNode | undefined;
36+
37+
for (const node of nodes) {
38+
if (lastNode && nodesAreEqual(lastNode, node)) {
39+
if (lastNode.type !== "group") {
40+
// TODO (jg): maybe its safe to mutate lastNode instead of cloning?
41+
tree.pop();
42+
lastNode = {
43+
...lastNode,
44+
type: "group",
45+
count: 2,
46+
firstUpdate: node.update,
47+
};
48+
tree.push(lastNode);
49+
} else {
50+
lastNode.count++;
51+
lastNode.firstUpdate = node.update;
52+
}
53+
// If the current node is equal to the last one, skip it
54+
continue;
55+
}
56+
tree.push(node);
57+
lastNode = node;
58+
}
59+
60+
return tree;
61+
};
62+
1263
const createUpdatesModel = () => {
1364
const updates = signal<(SignalUpdate | Divider)[]>([]);
1465
const lastUpdateId = signal<number>(0);
@@ -45,9 +96,9 @@ const createUpdatesModel = () => {
4596
const updateTree = computed(() => {
4697
const buildTree = (
4798
updates: (SignalUpdate | Divider)[]
48-
): UpdateTreeNode[] => {
49-
const tree: UpdateTreeNode[] = [];
50-
const stack: UpdateTreeNode[] = [];
99+
): UpdateTreeNodeSingle[] => {
100+
const tree: UpdateTreeNodeSingle[] = [];
101+
const stack: UpdateTreeNodeSingle[] = [];
51102

52103
// Process updates in reverse order to show newest first
53104
const recentUpdates = updates.slice(-100).reverse();
@@ -67,6 +118,7 @@ const createUpdatesModel = () => {
67118
const nodeId = `${update.signalName}-${update.receivedAt}-${i}`;
68119

69120
const node: UpdateTreeNode = {
121+
type: "single",
70122
id: nodeId,
71123
update,
72124
children: [],
@@ -134,9 +186,18 @@ const createUpdatesModel = () => {
134186
return () => window.removeEventListener("message", handleMessage);
135187
});
136188

189+
const collapsedUpdateTree = computed(() => {
190+
const updateTreeValue = updateTree.value;
191+
if (settingsStore.settings.grouped) {
192+
return collapseTree(updateTreeValue);
193+
}
194+
return updateTreeValue;
195+
});
196+
137197
return {
138198
updates,
139199
updateTree,
200+
collapsedUpdateTree,
140201
totalUpdates: computed(() => Object.keys(updateTree.value).length),
141202
signalCounts,
142203
addUpdate,

extension/styles/panel.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,14 @@ body {
324324
margin-bottom: 4px;
325325
}
326326

327+
.update-count {
328+
display: inline-block;
329+
padding: 0 6px;
330+
background: #e0e0e0;
331+
border-radius: 12px;
332+
margin: 0 2px;
333+
}
334+
327335
.signal-name {
328336
font-weight: 600;
329337
color: #673ab7;

0 commit comments

Comments
 (0)