@preact/signals-devtools-ui 0.4.0 → 0.4.2
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/CHANGELOG.md +12 -0
- package/dist/devtools-ui.d.ts +1 -1
- package/dist/devtools-ui.js +1 -1
- package/dist/devtools-ui.js.map +1 -1
- package/dist/devtools-ui.min.js +1 -1
- package/dist/devtools-ui.min.js.map +1 -1
- package/dist/devtools-ui.mjs +1 -1
- package/dist/devtools-ui.mjs.map +1 -1
- package/dist/devtools-ui.module.js +1 -1
- package/dist/devtools-ui.module.js.map +1 -1
- package/dist/styles.css +400 -101
- package/package.json +2 -2
- package/src/DevToolsPanel.tsx +1 -1
- package/src/components/Graph.tsx +6 -18
- package/src/components/Header.tsx +24 -3
- package/src/components/SettingsPanel.tsx +3 -4
- package/src/context.ts +28 -314
- package/src/index.ts +3 -3
- package/src/models/ConnectionModel.ts +38 -0
- package/src/models/SettingsModel.ts +40 -0
- package/src/models/ThemeModel.ts +70 -0
- package/src/models/UpdatesModel.ts +228 -0
- package/src/models/index.ts +11 -0
- package/src/styles.css +400 -101
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { signal, computed, createModel } from "@preact/signals";
|
|
2
|
+
import type {
|
|
3
|
+
DevToolsAdapter,
|
|
4
|
+
SignalDisposed,
|
|
5
|
+
DependencyInfo,
|
|
6
|
+
} from "@preact/signals-devtools-adapter";
|
|
7
|
+
import type { SettingsModel } from "./SettingsModel";
|
|
8
|
+
|
|
9
|
+
export interface SignalUpdate {
|
|
10
|
+
type: "update" | "effect" | "component";
|
|
11
|
+
signalType: "signal" | "computed" | "effect" | "component";
|
|
12
|
+
signalName: string;
|
|
13
|
+
signalId?: string;
|
|
14
|
+
prevValue?: any;
|
|
15
|
+
newValue?: any;
|
|
16
|
+
timestamp?: number;
|
|
17
|
+
receivedAt: number;
|
|
18
|
+
depth?: number;
|
|
19
|
+
subscribedTo?: string;
|
|
20
|
+
/** All dependencies this computed/effect currently depends on (with rich info) */
|
|
21
|
+
allDependencies?: DependencyInfo[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type Divider = { type: "divider" };
|
|
25
|
+
|
|
26
|
+
export interface UpdateTreeNodeBase {
|
|
27
|
+
id: string;
|
|
28
|
+
update: SignalUpdate;
|
|
29
|
+
children: UpdateTreeNode[];
|
|
30
|
+
depth: number;
|
|
31
|
+
hasChildren: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UpdateTreeNodeSingle extends UpdateTreeNodeBase {
|
|
35
|
+
type: "single";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UpdateTreeNodeGroup extends UpdateTreeNodeBase {
|
|
39
|
+
type: "group";
|
|
40
|
+
count: number;
|
|
41
|
+
firstUpdate: SignalUpdate;
|
|
42
|
+
firstChildren: UpdateTreeNode[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type UpdateTreeNode = UpdateTreeNodeGroup | UpdateTreeNodeSingle;
|
|
46
|
+
|
|
47
|
+
const nodesAreEqual = (a: UpdateTreeNode, b: UpdateTreeNode): boolean => {
|
|
48
|
+
return (
|
|
49
|
+
a.update.signalId === b.update.signalId &&
|
|
50
|
+
a.children.length === b.children.length &&
|
|
51
|
+
a.children.every((child, index) => nodesAreEqual(child, b.children[index]))
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const collapseTree = (nodes: UpdateTreeNodeSingle[]): UpdateTreeNode[] => {
|
|
56
|
+
const tree: UpdateTreeNode[] = [];
|
|
57
|
+
let lastNode: UpdateTreeNode | undefined;
|
|
58
|
+
|
|
59
|
+
for (const node of nodes) {
|
|
60
|
+
if (lastNode && nodesAreEqual(lastNode, node)) {
|
|
61
|
+
if (lastNode.type !== "group") {
|
|
62
|
+
tree.pop();
|
|
63
|
+
lastNode = {
|
|
64
|
+
...lastNode,
|
|
65
|
+
type: "group",
|
|
66
|
+
count: 2,
|
|
67
|
+
firstUpdate: node.update,
|
|
68
|
+
firstChildren: node.children,
|
|
69
|
+
};
|
|
70
|
+
tree.push(lastNode);
|
|
71
|
+
} else {
|
|
72
|
+
lastNode.count++;
|
|
73
|
+
lastNode.firstUpdate = node.update;
|
|
74
|
+
lastNode.firstChildren = node.children;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
tree.push(node);
|
|
79
|
+
lastNode = node;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return tree;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const UpdatesModel = createModel(
|
|
86
|
+
(
|
|
87
|
+
adapter: DevToolsAdapter,
|
|
88
|
+
settingsStore: InstanceType<typeof SettingsModel>
|
|
89
|
+
) => {
|
|
90
|
+
const updates = signal<(SignalUpdate | Divider)[]>([]);
|
|
91
|
+
const isPaused = signal<boolean>(false);
|
|
92
|
+
const disposedSignalIds = signal<Set<string>>(new Set());
|
|
93
|
+
|
|
94
|
+
const addUpdate = (
|
|
95
|
+
update: SignalUpdate | Divider | Array<SignalUpdate | Divider>
|
|
96
|
+
) => {
|
|
97
|
+
if (Array.isArray(update)) {
|
|
98
|
+
update.forEach(item => {
|
|
99
|
+
if (item.type !== "divider") item.receivedAt = Date.now();
|
|
100
|
+
});
|
|
101
|
+
} else if (update.type === "update") {
|
|
102
|
+
update.receivedAt = Date.now();
|
|
103
|
+
}
|
|
104
|
+
updates.value = [
|
|
105
|
+
...updates.value,
|
|
106
|
+
...(Array.isArray(update) ? update : [update]),
|
|
107
|
+
];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const addDisposal = (disposal: SignalDisposed | SignalDisposed[]) => {
|
|
111
|
+
const disposals = Array.isArray(disposal) ? disposal : [disposal];
|
|
112
|
+
const newDisposed = new Set(disposedSignalIds.value);
|
|
113
|
+
for (const d of disposals) {
|
|
114
|
+
if (d.signalId) {
|
|
115
|
+
newDisposed.add(d.signalId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
disposedSignalIds.value = newDisposed;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const hasUpdates = computed(() => updates.value.length > 0);
|
|
122
|
+
|
|
123
|
+
const signalCounts = computed(() => {
|
|
124
|
+
const counts = new Map<string, number>();
|
|
125
|
+
updates.value.forEach(update => {
|
|
126
|
+
if (update.type === "divider") return;
|
|
127
|
+
const signalName = update.signalName || "Unknown";
|
|
128
|
+
counts.set(signalName, (counts.get(signalName) || 0) + 1);
|
|
129
|
+
});
|
|
130
|
+
return counts;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const updateTree = computed(() => {
|
|
134
|
+
const buildTree = (
|
|
135
|
+
updates: (SignalUpdate | Divider)[]
|
|
136
|
+
): UpdateTreeNodeSingle[] => {
|
|
137
|
+
const tree: UpdateTreeNodeSingle[] = [];
|
|
138
|
+
const stack: UpdateTreeNodeSingle[] = [];
|
|
139
|
+
|
|
140
|
+
const recentUpdates = updates.slice(-100).reverse();
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < recentUpdates.length; i++) {
|
|
143
|
+
const item = recentUpdates[i];
|
|
144
|
+
|
|
145
|
+
if (item.type === "divider") {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const update = item as SignalUpdate;
|
|
150
|
+
const depth = update.depth || 0;
|
|
151
|
+
|
|
152
|
+
const nodeId = `${update.signalName}-${update.receivedAt}-${i}`;
|
|
153
|
+
|
|
154
|
+
const node: UpdateTreeNodeSingle = {
|
|
155
|
+
type: "single",
|
|
156
|
+
id: nodeId,
|
|
157
|
+
update,
|
|
158
|
+
children: [],
|
|
159
|
+
depth,
|
|
160
|
+
hasChildren: false,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
164
|
+
stack.pop();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (stack.length === 0) {
|
|
168
|
+
tree.push(node);
|
|
169
|
+
} else {
|
|
170
|
+
const parent = stack[stack.length - 1];
|
|
171
|
+
parent.children.push(node);
|
|
172
|
+
parent.hasChildren = true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
stack.push(node);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return tree;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return buildTree(updates.value);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const clearUpdates = () => {
|
|
185
|
+
updates.value = [];
|
|
186
|
+
disposedSignalIds.value = new Set();
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Listen to adapter events
|
|
190
|
+
adapter.on("signalUpdate", (signalUpdates: SignalUpdate[]) => {
|
|
191
|
+
if (isPaused.value) return;
|
|
192
|
+
|
|
193
|
+
const updatesArray: Array<SignalUpdate | Divider> = [
|
|
194
|
+
...signalUpdates,
|
|
195
|
+
].reverse();
|
|
196
|
+
updatesArray.push({ type: "divider" });
|
|
197
|
+
|
|
198
|
+
addUpdate(updatesArray);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Listen to disposal events
|
|
202
|
+
adapter.on("signalDisposed", (disposals: SignalDisposed[]) => {
|
|
203
|
+
if (isPaused.value) return;
|
|
204
|
+
addDisposal(disposals);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const collapsedUpdateTree = computed(() => {
|
|
208
|
+
const updateTreeValue = updateTree.value;
|
|
209
|
+
if (settingsStore.settings.value.grouped) {
|
|
210
|
+
return collapseTree(updateTreeValue);
|
|
211
|
+
}
|
|
212
|
+
return updateTreeValue;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
updates,
|
|
217
|
+
updateTree,
|
|
218
|
+
collapsedUpdateTree,
|
|
219
|
+
totalUpdates: computed(() => Object.keys(updateTree.value).length),
|
|
220
|
+
signalCounts,
|
|
221
|
+
disposedSignalIds,
|
|
222
|
+
addUpdate,
|
|
223
|
+
clearUpdates,
|
|
224
|
+
hasUpdates,
|
|
225
|
+
isPaused,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ConnectionModel } from "./ConnectionModel";
|
|
2
|
+
export { SettingsModel } from "./SettingsModel";
|
|
3
|
+
export { ThemeModel, type ThemeMode } from "./ThemeModel";
|
|
4
|
+
export {
|
|
5
|
+
UpdatesModel,
|
|
6
|
+
type SignalUpdate,
|
|
7
|
+
type Divider,
|
|
8
|
+
type UpdateTreeNode,
|
|
9
|
+
type UpdateTreeNodeSingle,
|
|
10
|
+
type UpdateTreeNodeGroup,
|
|
11
|
+
} from "./UpdatesModel";
|