@uistate/core 3.1.2 → 4.0.1
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/package.json +6 -15
- package/src/index.js +14 -349
- package/src/stateInspector.js +0 -140
- package/src/telemetryPlugin.js +0 -638
package/package.json
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance)",
|
|
5
6
|
"main": "src/index.js",
|
|
6
7
|
"module": "src/index.js",
|
|
7
8
|
"files": [
|
|
8
9
|
"src"
|
|
9
10
|
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "jest",
|
|
12
|
-
"lint": "eslint src"
|
|
13
|
-
},
|
|
11
|
+
"scripts": {},
|
|
14
12
|
"keywords": [
|
|
15
13
|
"state-management",
|
|
16
14
|
"ui",
|
|
17
|
-
"react",
|
|
18
15
|
"css",
|
|
19
|
-
"performance"
|
|
16
|
+
"performance",
|
|
17
|
+
"framework-agnostic",
|
|
18
|
+
"vanilla-js"
|
|
20
19
|
],
|
|
21
20
|
"author": "Ajdin Imsirovic <ajdika@live.com> (GitHub)",
|
|
22
21
|
"contributors": [
|
|
@@ -31,13 +30,5 @@
|
|
|
31
30
|
"url": "https://github.com/ImsirovicAjdin/uistate/issues"
|
|
32
31
|
},
|
|
33
32
|
"homepage": "https://github.com/ImsirovicAjdin/uistate#readme",
|
|
34
|
-
"peerDependencies": {
|
|
35
|
-
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"eslint": "^8.0.0",
|
|
39
|
-
"jest": "^29.0.0",
|
|
40
|
-
"jest-environment-jsdom": "^29.0.0"
|
|
41
|
-
},
|
|
42
33
|
"sideEffects": false
|
|
43
34
|
}
|
package/src/index.js
CHANGED
|
@@ -1,353 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* UIstate -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* UIstate - Simple barrel file for core modules
|
|
3
|
+
*
|
|
4
|
+
* Exports the four core UIstate modules:
|
|
5
|
+
* - cssState: CSS custom properties state management
|
|
6
|
+
* - eventState: Event-based state management
|
|
7
|
+
* - stateSerializer: State serialization utilities
|
|
8
|
+
* - templateManager: Declarative template management
|
|
9
9
|
*/
|
|
10
|
-
import createCssState from './cssState.js';
|
|
11
|
-
import { createEventState } from './eventState.js';
|
|
12
|
-
import { createTemplateManager } from './templateManager.js';
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
function convertPathToCssPath(path) {
|
|
21
|
-
return path.replace(/\./g, "-");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const createUnifiedState = (initialState = {}) => {
|
|
25
|
-
// Create the CSS state manager with integrated serialization
|
|
26
|
-
const cssState = createCssState(initialState);
|
|
27
|
-
|
|
28
|
-
// Create the event state manager with the same initial state
|
|
29
|
-
const eventState = createEventState(initialState);
|
|
30
|
-
|
|
31
|
-
// Plugin system
|
|
32
|
-
const plugins = new Map();
|
|
33
|
-
const middlewares = [];
|
|
34
|
-
const lifecycleHooks = {
|
|
35
|
-
beforeStateChange: [],
|
|
36
|
-
afterStateChange: [],
|
|
37
|
-
onInit: [],
|
|
38
|
-
onDestroy: []
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Create a unified API
|
|
42
|
-
const unifiedState = {
|
|
43
|
-
_isNotifying: false,
|
|
44
|
-
_cssState: cssState, // Reference to cssState for direct access to advanced features
|
|
45
|
-
|
|
46
|
-
// Get state with hierarchical path support
|
|
47
|
-
getState(path) {
|
|
48
|
-
// Always use eventState as the source of truth
|
|
49
|
-
// This ensures consistency with the DOM state
|
|
50
|
-
const value = eventState.get(path);
|
|
51
|
-
|
|
52
|
-
// If path is undefined or value not found in eventState
|
|
53
|
-
if (path && value === undefined) {
|
|
54
|
-
// Fall back to CSS variable (for values set outside this API)
|
|
55
|
-
const cssPath = convertPathToCssPath(path);
|
|
56
|
-
return cssState.getState(cssPath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return value;
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
// Set state with hierarchical path support
|
|
63
|
-
setState(path, value) {
|
|
64
|
-
if (!path) return this;
|
|
65
|
-
|
|
66
|
-
// Run before state change middlewares
|
|
67
|
-
let finalValue = value;
|
|
68
|
-
let shouldContinue = true;
|
|
69
|
-
|
|
70
|
-
// Apply middlewares
|
|
71
|
-
for (const middleware of middlewares) {
|
|
72
|
-
const result = middleware(path, finalValue, this.getState.bind(this));
|
|
73
|
-
if (result === false) {
|
|
74
|
-
shouldContinue = false;
|
|
75
|
-
break;
|
|
76
|
-
} else if (result !== undefined) {
|
|
77
|
-
finalValue = result;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Run lifecycle hooks
|
|
82
|
-
for (const hook of lifecycleHooks.beforeStateChange) {
|
|
83
|
-
hook(path, finalValue, this.getState.bind(this));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!shouldContinue) return this;
|
|
87
|
-
|
|
88
|
-
// Update event state (for JS access and pub/sub)
|
|
89
|
-
eventState.set(path, finalValue);
|
|
90
|
-
|
|
91
|
-
// Update CSS state (for styling and DOM representation)
|
|
92
|
-
const cssPath = convertPathToCssPath(path);
|
|
93
|
-
cssState.setState(cssPath, finalValue);
|
|
94
|
-
|
|
95
|
-
// Run after state change hooks
|
|
96
|
-
for (const hook of lifecycleHooks.afterStateChange) {
|
|
97
|
-
hook(path, finalValue, this.getState.bind(this));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return this;
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
// Plugin system methods
|
|
104
|
-
use(pluginName, plugin) {
|
|
105
|
-
if (plugins.has(pluginName)) {
|
|
106
|
-
console.warn(`Plugin '${pluginName}' is already registered. It will be replaced.`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Register the plugin
|
|
110
|
-
plugins.set(pluginName, plugin);
|
|
111
|
-
|
|
112
|
-
// Initialize the plugin
|
|
113
|
-
if (typeof plugin.init === 'function') {
|
|
114
|
-
// Pass the API to the plugin
|
|
115
|
-
plugin.init(this);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Register middlewares
|
|
119
|
-
if (Array.isArray(plugin.middlewares)) {
|
|
120
|
-
middlewares.push(...plugin.middlewares);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Register lifecycle hooks
|
|
124
|
-
if (plugin.hooks) {
|
|
125
|
-
Object.entries(plugin.hooks).forEach(([hookName, hookFn]) => {
|
|
126
|
-
if (Array.isArray(lifecycleHooks[hookName])) {
|
|
127
|
-
lifecycleHooks[hookName].push(hookFn);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Add plugin methods to the API
|
|
133
|
-
if (plugin.methods) {
|
|
134
|
-
Object.entries(plugin.methods).forEach(([methodName, method]) => {
|
|
135
|
-
if (typeof method === 'function') {
|
|
136
|
-
this[methodName] = method.bind(plugin);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return this;
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
getPlugin(pluginName) {
|
|
145
|
-
return plugins.get(pluginName);
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
hasPlugin(pluginName) {
|
|
149
|
-
return plugins.has(pluginName);
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
removePlugin(pluginName) {
|
|
153
|
-
const plugin = plugins.get(pluginName);
|
|
154
|
-
if (!plugin) return false;
|
|
155
|
-
|
|
156
|
-
// Run cleanup if available
|
|
157
|
-
if (typeof plugin.destroy === 'function') {
|
|
158
|
-
plugin.destroy();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Remove plugin methods
|
|
162
|
-
if (plugin.methods) {
|
|
163
|
-
Object.keys(plugin.methods).forEach(methodName => {
|
|
164
|
-
delete this[methodName];
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Remove middlewares
|
|
169
|
-
if (Array.isArray(plugin.middlewares)) {
|
|
170
|
-
plugin.middlewares.forEach(middleware => {
|
|
171
|
-
const index = middlewares.indexOf(middleware);
|
|
172
|
-
if (index !== -1) {
|
|
173
|
-
middlewares.splice(index, 1);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Remove lifecycle hooks
|
|
179
|
-
if (plugin.hooks) {
|
|
180
|
-
Object.entries(plugin.hooks).forEach(([hookName, hookFn]) => {
|
|
181
|
-
if (Array.isArray(lifecycleHooks[hookName])) {
|
|
182
|
-
const index = lifecycleHooks[hookName].indexOf(hookFn);
|
|
183
|
-
if (index !== -1) {
|
|
184
|
-
lifecycleHooks[hookName].splice(index, 1);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Remove the plugin
|
|
191
|
-
plugins.delete(pluginName);
|
|
192
|
-
return true;
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
// Add middleware
|
|
196
|
-
addMiddleware(middleware) {
|
|
197
|
-
if (typeof middleware === 'function') {
|
|
198
|
-
middlewares.push(middleware);
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
return false;
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
// Add lifecycle hook
|
|
205
|
-
addHook(hookName, hookFn) {
|
|
206
|
-
if (typeof hookFn === 'function' && Array.isArray(lifecycleHooks[hookName])) {
|
|
207
|
-
lifecycleHooks[hookName].push(hookFn);
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
return false;
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
// Subscribe to state changes with support for wildcards
|
|
214
|
-
subscribe(path, callback) {
|
|
215
|
-
return eventState.subscribe(path, callback);
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
// Observe CSS state changes (simpler API, no wildcards)
|
|
219
|
-
observe(key, callback) {
|
|
220
|
-
return cssState.observe(key, callback);
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
// Configure serialization options
|
|
224
|
-
configureSerializer(config) {
|
|
225
|
-
cssState.configureSerializer(config);
|
|
226
|
-
return this;
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
// Get current serializer configuration
|
|
230
|
-
getSerializerConfig() {
|
|
231
|
-
return cssState._serializer?.config || null;
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Set serialization mode ('hybrid', 'json', or 'escape')
|
|
235
|
-
setSerializationMode(mode) {
|
|
236
|
-
return this.configureSerializer({ mode });
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
// Clean up resources
|
|
240
|
-
destroy() {
|
|
241
|
-
// Run onDestroy hooks
|
|
242
|
-
for (const hook of lifecycleHooks.onDestroy) {
|
|
243
|
-
hook();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Destroy all plugins
|
|
247
|
-
for (const [pluginName, plugin] of plugins.entries()) {
|
|
248
|
-
if (typeof plugin.destroy === 'function') {
|
|
249
|
-
plugin.destroy();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Clear plugins and middlewares
|
|
254
|
-
plugins.clear();
|
|
255
|
-
middlewares.length = 0;
|
|
256
|
-
Object.keys(lifecycleHooks).forEach(key => {
|
|
257
|
-
lifecycleHooks[key].length = 0;
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Destroy core state management
|
|
261
|
-
cssState.destroy();
|
|
262
|
-
eventState.destroy();
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return unifiedState;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// Create a singleton instance of the unified state
|
|
270
|
-
const UnifiedState = createUnifiedState();
|
|
271
|
-
|
|
272
|
-
// Create a template manager plugin
|
|
273
|
-
const templateManagerPlugin = {
|
|
274
|
-
name: 'templateManager',
|
|
275
|
-
instance: null,
|
|
276
|
-
|
|
277
|
-
init(api) {
|
|
278
|
-
this.instance = createTemplateManager(api);
|
|
279
|
-
return this;
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
destroy() {
|
|
283
|
-
// Any cleanup needed for the template manager
|
|
284
|
-
},
|
|
285
|
-
|
|
286
|
-
// Plugin methods that will be added to the UIstate API
|
|
287
|
-
methods: {
|
|
288
|
-
onAction(action, handler) {
|
|
289
|
-
return this.instance.onAction(action, handler);
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
registerActions(actionsMap) {
|
|
293
|
-
return this.instance.registerActions(actionsMap);
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
attachDelegation(root = document.body) {
|
|
297
|
-
return this.instance.attachDelegation(root);
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
mount(componentName, container) {
|
|
301
|
-
return this.instance.mount(componentName, container);
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
renderTemplateFromCss(templateName, data) {
|
|
305
|
-
return this.instance.renderTemplateFromCss(templateName, data);
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
createComponent(name, renderFn, stateKeys) {
|
|
309
|
-
return this.instance.createComponent(name, renderFn, stateKeys);
|
|
310
|
-
},
|
|
311
|
-
|
|
312
|
-
applyClassesFromState(element, stateKey, options) {
|
|
313
|
-
return this.instance.applyClassesFromState(element, stateKey, options);
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
// Expose handlers property
|
|
318
|
-
get handlers() {
|
|
319
|
-
return this.instance.handlers;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
// Create a combined API with plugin support
|
|
324
|
-
const UIstate = {
|
|
325
|
-
...UnifiedState,
|
|
326
|
-
|
|
327
|
-
// Initialize the system with plugins
|
|
328
|
-
init() {
|
|
329
|
-
// Register the template manager plugin
|
|
330
|
-
this.use('templateManager', templateManagerPlugin);
|
|
331
|
-
|
|
332
|
-
// Run onInit hooks
|
|
333
|
-
for (const hook of this.getPlugin('templateManager').instance.lifecycleHooks?.onInit || []) {
|
|
334
|
-
// for (const hook of lifecycleHooks.onInit) {
|
|
335
|
-
hook();
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Attach event delegation to document body
|
|
339
|
-
this.attachDelegation(document.body);
|
|
340
|
-
|
|
341
|
-
return this;
|
|
342
|
-
}
|
|
343
|
-
};
|
|
11
|
+
// Export the four core modules
|
|
12
|
+
export { createCssState } from './cssState.js';
|
|
13
|
+
export { createEventState } from './eventState.js';
|
|
14
|
+
export { default as stateSerializer } from './stateSerializer.js';
|
|
15
|
+
export { createTemplateManager } from './templateManager.js';
|
|
344
16
|
|
|
345
|
-
export default
|
|
346
|
-
export {
|
|
347
|
-
createUnifiedState,
|
|
348
|
-
createCssState,
|
|
349
|
-
createEventState,
|
|
350
|
-
createTemplateManager,
|
|
351
|
-
convertPathToCssPath,
|
|
352
|
-
templateManagerPlugin
|
|
353
|
-
};
|
|
17
|
+
// For convenience, also export cssState as default
|
|
18
|
+
export { createCssState as default } from './cssState.js';
|
package/src/stateInspector.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StateInspector - Real-time state inspection panel for UIstate
|
|
3
|
-
*
|
|
4
|
-
* This module provides a configurable UI panel that displays and allows manipulation
|
|
5
|
-
* of CSS variables and state values in UIstate applications.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Toggle visibility with a dedicated button
|
|
9
|
-
* - Real-time updates of CSS variable values
|
|
10
|
-
* - Organized display of state by categories
|
|
11
|
-
* - Ability to filter state variables
|
|
12
|
-
* - Direct manipulation of state values
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a configured state inspector instance
|
|
17
|
-
* @param {Object} config - Configuration options
|
|
18
|
-
* @returns {Object} - StateInspector instance
|
|
19
|
-
*/
|
|
20
|
-
function createStateInspector(config = {}) {
|
|
21
|
-
// Default configuration
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
target: document.body,
|
|
24
|
-
initiallyVisible: false,
|
|
25
|
-
categories: ['app', 'ui', 'data'],
|
|
26
|
-
position: 'bottom-right',
|
|
27
|
-
maxHeight: '300px',
|
|
28
|
-
stateManager: null, // Optional reference to UIstate or CssState instance
|
|
29
|
-
theme: 'light'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Merge provided config with defaults
|
|
33
|
-
const options = { ...defaultConfig, ...config };
|
|
34
|
-
|
|
35
|
-
// Private state
|
|
36
|
-
let isVisible = options.initiallyVisible;
|
|
37
|
-
let panel = null;
|
|
38
|
-
let toggleButton = null;
|
|
39
|
-
let filterInput = null;
|
|
40
|
-
let stateContainer = null;
|
|
41
|
-
let currentFilter = '';
|
|
42
|
-
|
|
43
|
-
// StateInspector instance
|
|
44
|
-
const inspector = {
|
|
45
|
-
// Current configuration
|
|
46
|
-
config: options,
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Update configuration
|
|
50
|
-
* @param {Object} newConfig - New configuration options
|
|
51
|
-
*/
|
|
52
|
-
configure(newConfig) {
|
|
53
|
-
Object.assign(this.config, newConfig);
|
|
54
|
-
this.refresh();
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Attach the inspector panel to the target element
|
|
59
|
-
* @param {Element} element - Target element to attach to (defaults to config.target)
|
|
60
|
-
* @returns {Object} - The inspector instance for chaining
|
|
61
|
-
*/
|
|
62
|
-
attach(element = this.config.target) {
|
|
63
|
-
// Implementation will create and attach the panel
|
|
64
|
-
// For now, this is a placeholder
|
|
65
|
-
console.log('StateInspector: Panel would be attached to', element);
|
|
66
|
-
return this;
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Show the inspector panel
|
|
71
|
-
* @returns {Object} - The inspector instance for chaining
|
|
72
|
-
*/
|
|
73
|
-
show() {
|
|
74
|
-
isVisible = true;
|
|
75
|
-
if (panel) panel.style.display = 'block';
|
|
76
|
-
return this;
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Hide the inspector panel
|
|
81
|
-
* @returns {Object} - The inspector instance for chaining
|
|
82
|
-
*/
|
|
83
|
-
hide() {
|
|
84
|
-
isVisible = false;
|
|
85
|
-
if (panel) panel.style.display = 'none';
|
|
86
|
-
return this;
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Toggle the visibility of the inspector panel
|
|
91
|
-
* @returns {Object} - The inspector instance for chaining
|
|
92
|
-
*/
|
|
93
|
-
toggle() {
|
|
94
|
-
return isVisible ? this.hide() : this.show();
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Refresh the state display
|
|
99
|
-
* @returns {Object} - The inspector instance for chaining
|
|
100
|
-
*/
|
|
101
|
-
refresh() {
|
|
102
|
-
// Implementation will update the displayed state values
|
|
103
|
-
// For now, this is a placeholder
|
|
104
|
-
console.log('StateInspector: State display would be refreshed');
|
|
105
|
-
return this;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Filter the displayed state variables
|
|
110
|
-
* @param {string} filterText - Text to filter by
|
|
111
|
-
* @returns {Object} - The inspector instance for chaining
|
|
112
|
-
*/
|
|
113
|
-
filter(filterText) {
|
|
114
|
-
currentFilter = filterText;
|
|
115
|
-
// Implementation will filter the displayed variables
|
|
116
|
-
return this.refresh();
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Clean up resources used by the inspector
|
|
121
|
-
*/
|
|
122
|
-
destroy() {
|
|
123
|
-
if (panel && panel.parentNode) {
|
|
124
|
-
panel.parentNode.removeChild(panel);
|
|
125
|
-
}
|
|
126
|
-
panel = null;
|
|
127
|
-
toggleButton = null;
|
|
128
|
-
filterInput = null;
|
|
129
|
-
stateContainer = null;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return inspector;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create a default instance
|
|
137
|
-
const StateInspector = createStateInspector();
|
|
138
|
-
|
|
139
|
-
export default StateInspector;
|
|
140
|
-
export { createStateInspector };
|
package/src/telemetryPlugin.js
DELETED
|
@@ -1,638 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIstate Telemetry Plugin
|
|
3
|
-
*
|
|
4
|
-
* A plugin for tracking and analyzing usage patterns in UIstate applications.
|
|
5
|
-
* Provides insights into method calls, state changes, and performance metrics.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Automatic tracking of API method calls
|
|
9
|
-
* - State change monitoring
|
|
10
|
-
* - Performance timing
|
|
11
|
-
* - Usage statistics and reporting
|
|
12
|
-
* - Visual dashboard integration
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a telemetry plugin instance
|
|
17
|
-
* @param {Object} config - Configuration options
|
|
18
|
-
* @returns {Object} - Configured telemetry plugin
|
|
19
|
-
*/
|
|
20
|
-
const createTelemetryPlugin = (config = {}) => {
|
|
21
|
-
// Default configuration
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
enabled: true,
|
|
24
|
-
trackStateChanges: true,
|
|
25
|
-
trackMethodCalls: true,
|
|
26
|
-
trackPerformance: true,
|
|
27
|
-
maxEntries: 1000,
|
|
28
|
-
logToConsole: false,
|
|
29
|
-
samplingRate: 1.0, // 1.0 = track everything, 0.5 = track 50% of events
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Merge provided config with defaults
|
|
33
|
-
const options = { ...defaultConfig, ...config };
|
|
34
|
-
|
|
35
|
-
// Telemetry data storage
|
|
36
|
-
const data = {
|
|
37
|
-
startTime: Date.now(),
|
|
38
|
-
methodCalls: new Map(),
|
|
39
|
-
stateChanges: new Map(),
|
|
40
|
-
performanceMetrics: new Map(),
|
|
41
|
-
unusedMethods: new Set(),
|
|
42
|
-
activeTimers: new Map()
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Reference to the UIstate instance
|
|
46
|
-
let uistateRef = null;
|
|
47
|
-
|
|
48
|
-
// Original method references
|
|
49
|
-
const originalMethods = new Map();
|
|
50
|
-
|
|
51
|
-
// Methods to track (will be populated during initialization)
|
|
52
|
-
const methodsToTrack = [
|
|
53
|
-
'getState', 'setState', 'subscribe', 'observe',
|
|
54
|
-
'configureSerializer', 'getSerializerConfig', 'setSerializationMode',
|
|
55
|
-
'onAction', 'registerActions', 'attachDelegation', 'mount',
|
|
56
|
-
'renderTemplateFromCss', 'createComponent', 'applyClassesFromState'
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
// Helper function to check if we should sample this event
|
|
60
|
-
const shouldSample = () => {
|
|
61
|
-
return options.samplingRate >= 1.0 || Math.random() <= options.samplingRate;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Helper function to track a method call
|
|
65
|
-
const trackMethodCall = (methodName, args = [], result = undefined, error = null, duration = 0) => {
|
|
66
|
-
if (!options.enabled || !options.trackMethodCalls || !shouldSample()) return;
|
|
67
|
-
|
|
68
|
-
if (!data.methodCalls.has(methodName)) {
|
|
69
|
-
data.methodCalls.set(methodName, []);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const calls = data.methodCalls.get(methodName);
|
|
73
|
-
|
|
74
|
-
// Limit the number of entries to prevent memory issues
|
|
75
|
-
if (calls.length >= options.maxEntries) {
|
|
76
|
-
calls.shift(); // Remove oldest entry
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Create a safe copy of arguments (avoiding circular references)
|
|
80
|
-
const safeArgs = Array.from(args).map(arg => {
|
|
81
|
-
try {
|
|
82
|
-
// For simple types, return as is
|
|
83
|
-
if (arg === null || arg === undefined ||
|
|
84
|
-
typeof arg === 'string' ||
|
|
85
|
-
typeof arg === 'number' ||
|
|
86
|
-
typeof arg === 'boolean') {
|
|
87
|
-
return arg;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// For functions, just return the function name or "[Function]"
|
|
91
|
-
if (typeof arg === 'function') {
|
|
92
|
-
return arg.name || "[Function]";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// For objects, create a simplified representation
|
|
96
|
-
if (typeof arg === 'object') {
|
|
97
|
-
if (Array.isArray(arg)) {
|
|
98
|
-
return `[Array(${arg.length})]`;
|
|
99
|
-
}
|
|
100
|
-
return Object.keys(arg).length > 0
|
|
101
|
-
? `[Object: ${Object.keys(arg).join(', ')}]`
|
|
102
|
-
: '[Object]';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return String(arg);
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return "[Complex Value]";
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Create a safe copy of the result
|
|
112
|
-
let safeResult;
|
|
113
|
-
try {
|
|
114
|
-
if (result === null || result === undefined ||
|
|
115
|
-
typeof result === 'string' ||
|
|
116
|
-
typeof result === 'number' ||
|
|
117
|
-
typeof result === 'boolean') {
|
|
118
|
-
safeResult = result;
|
|
119
|
-
} else if (typeof result === 'function') {
|
|
120
|
-
safeResult = result.name || "[Function]";
|
|
121
|
-
} else if (typeof result === 'object') {
|
|
122
|
-
if (Array.isArray(result)) {
|
|
123
|
-
safeResult = `[Array(${result.length})]`;
|
|
124
|
-
} else {
|
|
125
|
-
safeResult = Object.keys(result).length > 0
|
|
126
|
-
? `[Object: ${Object.keys(result).join(', ')}]`
|
|
127
|
-
: '[Object]';
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
safeResult = String(result);
|
|
131
|
-
}
|
|
132
|
-
} catch (e) {
|
|
133
|
-
safeResult = "[Complex Value]";
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Record the call
|
|
137
|
-
calls.push({
|
|
138
|
-
timestamp: Date.now(),
|
|
139
|
-
relativeTime: Date.now() - data.startTime,
|
|
140
|
-
args: safeArgs,
|
|
141
|
-
result: safeResult,
|
|
142
|
-
error: error ? error.message : null,
|
|
143
|
-
duration
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Log to console if enabled
|
|
147
|
-
if (options.logToConsole) {
|
|
148
|
-
console.log(`[Telemetry] ${methodName}`, {
|
|
149
|
-
args: safeArgs,
|
|
150
|
-
result: safeResult,
|
|
151
|
-
error: error ? error.message : null,
|
|
152
|
-
duration
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Helper function to track state changes
|
|
158
|
-
const trackStateChange = (path, value, previousValue) => {
|
|
159
|
-
if (!options.enabled || !options.trackStateChanges || !shouldSample()) return;
|
|
160
|
-
|
|
161
|
-
if (!data.stateChanges.has(path)) {
|
|
162
|
-
data.stateChanges.set(path, []);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const changes = data.stateChanges.get(path);
|
|
166
|
-
|
|
167
|
-
// Limit the number of entries to prevent memory issues
|
|
168
|
-
if (changes.length >= options.maxEntries) {
|
|
169
|
-
changes.shift(); // Remove oldest entry
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Create safe copies of values
|
|
173
|
-
let safeValue, safePreviousValue;
|
|
174
|
-
try {
|
|
175
|
-
safeValue = JSON.stringify(value);
|
|
176
|
-
} catch (e) {
|
|
177
|
-
safeValue = String(value);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
safePreviousValue = JSON.stringify(previousValue);
|
|
182
|
-
} catch (e) {
|
|
183
|
-
safePreviousValue = String(previousValue);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Record the change
|
|
187
|
-
changes.push({
|
|
188
|
-
timestamp: Date.now(),
|
|
189
|
-
relativeTime: Date.now() - data.startTime,
|
|
190
|
-
value: safeValue,
|
|
191
|
-
previousValue: safePreviousValue
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Helper function to start a performance timer
|
|
196
|
-
const startTimer = (label) => {
|
|
197
|
-
if (!options.enabled || !options.trackPerformance) return;
|
|
198
|
-
|
|
199
|
-
data.activeTimers.set(label, performance.now());
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// Helper function to end a performance timer
|
|
203
|
-
const endTimer = (label) => {
|
|
204
|
-
if (!options.enabled || !options.trackPerformance) return 0;
|
|
205
|
-
|
|
206
|
-
if (!data.activeTimers.has(label)) return 0;
|
|
207
|
-
|
|
208
|
-
const startTime = data.activeTimers.get(label);
|
|
209
|
-
const duration = performance.now() - startTime;
|
|
210
|
-
|
|
211
|
-
data.activeTimers.delete(label);
|
|
212
|
-
|
|
213
|
-
if (!data.performanceMetrics.has(label)) {
|
|
214
|
-
data.performanceMetrics.set(label, {
|
|
215
|
-
count: 0,
|
|
216
|
-
totalDuration: 0,
|
|
217
|
-
minDuration: Infinity,
|
|
218
|
-
maxDuration: 0,
|
|
219
|
-
samples: []
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const metrics = data.performanceMetrics.get(label);
|
|
224
|
-
metrics.count++;
|
|
225
|
-
metrics.totalDuration += duration;
|
|
226
|
-
metrics.minDuration = Math.min(metrics.minDuration, duration);
|
|
227
|
-
metrics.maxDuration = Math.max(metrics.maxDuration, duration);
|
|
228
|
-
|
|
229
|
-
// Store sample data (limited to prevent memory issues)
|
|
230
|
-
if (metrics.samples.length >= options.maxEntries) {
|
|
231
|
-
metrics.samples.shift();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
metrics.samples.push({
|
|
235
|
-
timestamp: Date.now(),
|
|
236
|
-
relativeTime: Date.now() - data.startTime,
|
|
237
|
-
duration
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return duration;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Method proxying function
|
|
244
|
-
const createMethodProxy = (obj, methodName) => {
|
|
245
|
-
// Store original method
|
|
246
|
-
const originalMethod = obj[methodName];
|
|
247
|
-
originalMethods.set(methodName, originalMethod);
|
|
248
|
-
|
|
249
|
-
// Create proxy
|
|
250
|
-
return function(...args) {
|
|
251
|
-
if (!options.enabled) {
|
|
252
|
-
return originalMethod.apply(this, args);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let result, error;
|
|
256
|
-
|
|
257
|
-
startTimer(`method.${methodName}`);
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
// Call original method
|
|
261
|
-
result = originalMethod.apply(this, args);
|
|
262
|
-
|
|
263
|
-
// Handle promises
|
|
264
|
-
if (result instanceof Promise) {
|
|
265
|
-
return result
|
|
266
|
-
.then(asyncResult => {
|
|
267
|
-
const duration = endTimer(`method.${methodName}`);
|
|
268
|
-
trackMethodCall(methodName, args, asyncResult, null, duration);
|
|
269
|
-
return asyncResult;
|
|
270
|
-
})
|
|
271
|
-
.catch(asyncError => {
|
|
272
|
-
const duration = endTimer(`method.${methodName}`);
|
|
273
|
-
trackMethodCall(methodName, args, undefined, asyncError, duration);
|
|
274
|
-
throw asyncError;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const duration = endTimer(`method.${methodName}`);
|
|
279
|
-
trackMethodCall(methodName, args, result, null, duration);
|
|
280
|
-
return result;
|
|
281
|
-
|
|
282
|
-
} catch (e) {
|
|
283
|
-
error = e;
|
|
284
|
-
const duration = endTimer(`method.${methodName}`);
|
|
285
|
-
trackMethodCall(methodName, args, undefined, error, duration);
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// Create the telemetry plugin
|
|
292
|
-
const telemetryPlugin = {
|
|
293
|
-
name: 'telemetry',
|
|
294
|
-
|
|
295
|
-
// Initialize the plugin
|
|
296
|
-
init(api) {
|
|
297
|
-
if (!options.enabled) return this;
|
|
298
|
-
|
|
299
|
-
uistateRef = api;
|
|
300
|
-
|
|
301
|
-
// Proxy methods for tracking
|
|
302
|
-
if (options.trackMethodCalls) {
|
|
303
|
-
methodsToTrack.forEach(methodName => {
|
|
304
|
-
if (typeof api[methodName] === 'function') {
|
|
305
|
-
// Add to list of methods to check for usage
|
|
306
|
-
data.unusedMethods.add(methodName);
|
|
307
|
-
|
|
308
|
-
// Create proxy
|
|
309
|
-
api[methodName] = createMethodProxy(api, methodName);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return this;
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
// Middleware for state changes
|
|
318
|
-
middlewares: [
|
|
319
|
-
(path, value, getState) => {
|
|
320
|
-
if (options.enabled && options.trackStateChanges) {
|
|
321
|
-
const previousValue = getState(path);
|
|
322
|
-
startTimer(`stateChange.${path}`);
|
|
323
|
-
|
|
324
|
-
// Return a proxy that will track after the state change
|
|
325
|
-
return {
|
|
326
|
-
__telemetryValue: true,
|
|
327
|
-
value,
|
|
328
|
-
path,
|
|
329
|
-
previousValue,
|
|
330
|
-
finalize() {
|
|
331
|
-
const duration = endTimer(`stateChange.${path}`);
|
|
332
|
-
trackStateChange(path, value, previousValue);
|
|
333
|
-
return value;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
return value;
|
|
338
|
-
}
|
|
339
|
-
],
|
|
340
|
-
|
|
341
|
-
// Lifecycle hooks
|
|
342
|
-
hooks: {
|
|
343
|
-
beforeStateChange: (path, value) => {
|
|
344
|
-
// If this is our proxy value, do nothing
|
|
345
|
-
if (value && value.__telemetryValue) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
afterStateChange: (path, value) => {
|
|
351
|
-
// If this is our proxy value, finalize it
|
|
352
|
-
if (value && value.__telemetryValue) {
|
|
353
|
-
return value.finalize();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
// Plugin methods
|
|
359
|
-
methods: {
|
|
360
|
-
// Enable or disable telemetry
|
|
361
|
-
setEnabled(enabled) {
|
|
362
|
-
options.enabled = enabled;
|
|
363
|
-
return this;
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// Get current configuration
|
|
367
|
-
getConfig() {
|
|
368
|
-
return { ...options };
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
// Update configuration
|
|
372
|
-
configure(newConfig) {
|
|
373
|
-
Object.assign(options, newConfig);
|
|
374
|
-
return this;
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
// Reset telemetry data
|
|
378
|
-
reset() {
|
|
379
|
-
data.startTime = Date.now();
|
|
380
|
-
data.methodCalls.clear();
|
|
381
|
-
data.stateChanges.clear();
|
|
382
|
-
data.performanceMetrics.clear();
|
|
383
|
-
data.activeTimers.clear();
|
|
384
|
-
return this;
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
// Get method call statistics
|
|
388
|
-
getMethodStats() {
|
|
389
|
-
const stats = {};
|
|
390
|
-
|
|
391
|
-
data.methodCalls.forEach((calls, methodName) => {
|
|
392
|
-
// Mark method as used
|
|
393
|
-
data.unusedMethods.delete(methodName);
|
|
394
|
-
|
|
395
|
-
stats[methodName] = {
|
|
396
|
-
count: calls.length,
|
|
397
|
-
averageDuration: calls.reduce((sum, call) => sum + (call.duration || 0), 0) / calls.length,
|
|
398
|
-
lastCall: calls[calls.length - 1],
|
|
399
|
-
firstCall: calls[0]
|
|
400
|
-
};
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return stats;
|
|
404
|
-
},
|
|
405
|
-
|
|
406
|
-
// Get detailed method call data
|
|
407
|
-
getMethodCalls(methodName) {
|
|
408
|
-
if (!methodName) {
|
|
409
|
-
const result = {};
|
|
410
|
-
data.methodCalls.forEach((calls, method) => {
|
|
411
|
-
result[method] = [...calls];
|
|
412
|
-
});
|
|
413
|
-
return result;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return data.methodCalls.has(methodName)
|
|
417
|
-
? [...data.methodCalls.get(methodName)]
|
|
418
|
-
: [];
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
// Get state change statistics
|
|
422
|
-
getStateStats() {
|
|
423
|
-
const stats = {};
|
|
424
|
-
|
|
425
|
-
data.stateChanges.forEach((changes, path) => {
|
|
426
|
-
stats[path] = {
|
|
427
|
-
count: changes.length,
|
|
428
|
-
lastChange: changes[changes.length - 1],
|
|
429
|
-
firstChange: changes[0]
|
|
430
|
-
};
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
return stats;
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
// Get detailed state change data
|
|
437
|
-
getStateChanges(path) {
|
|
438
|
-
if (!path) {
|
|
439
|
-
const result = {};
|
|
440
|
-
data.stateChanges.forEach((changes, statePath) => {
|
|
441
|
-
result[statePath] = [...changes];
|
|
442
|
-
});
|
|
443
|
-
return result;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return data.stateChanges.has(path)
|
|
447
|
-
? [...data.stateChanges.get(path)]
|
|
448
|
-
: [];
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
// Get performance metrics
|
|
452
|
-
getPerformanceMetrics() {
|
|
453
|
-
const metrics = {};
|
|
454
|
-
|
|
455
|
-
data.performanceMetrics.forEach((data, label) => {
|
|
456
|
-
metrics[label] = {
|
|
457
|
-
count: data.count,
|
|
458
|
-
totalDuration: data.totalDuration,
|
|
459
|
-
averageDuration: data.totalDuration / data.count,
|
|
460
|
-
minDuration: data.minDuration,
|
|
461
|
-
maxDuration: data.maxDuration
|
|
462
|
-
};
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
return metrics;
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
// Get unused methods
|
|
469
|
-
getUnusedMethods() {
|
|
470
|
-
return [...data.unusedMethods];
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
// Get comprehensive telemetry report
|
|
474
|
-
getReport() {
|
|
475
|
-
return {
|
|
476
|
-
startTime: data.startTime,
|
|
477
|
-
duration: Date.now() - data.startTime,
|
|
478
|
-
methodStats: this.getMethodStats(),
|
|
479
|
-
stateStats: this.getStateStats(),
|
|
480
|
-
performanceMetrics: this.getPerformanceMetrics(),
|
|
481
|
-
unusedMethods: this.getUnusedMethods(),
|
|
482
|
-
config: this.getConfig(),
|
|
483
|
-
// Add detailed logs for download
|
|
484
|
-
detailedLogs: {
|
|
485
|
-
methodCalls: this.getMethodCalls(),
|
|
486
|
-
stateChanges: this.getStateChanges()
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
},
|
|
490
|
-
|
|
491
|
-
// Download telemetry logs as a JSON file
|
|
492
|
-
downloadLogs() {
|
|
493
|
-
const report = this.getReport();
|
|
494
|
-
const data = JSON.stringify(report, null, 2);
|
|
495
|
-
const blob = new Blob([data], { type: 'application/json' });
|
|
496
|
-
const url = URL.createObjectURL(blob);
|
|
497
|
-
|
|
498
|
-
const a = document.createElement('a');
|
|
499
|
-
a.href = url;
|
|
500
|
-
a.download = `uistate-telemetry-${new Date().toISOString().replace(/:/g, '-')}.json`;
|
|
501
|
-
document.body.appendChild(a);
|
|
502
|
-
a.click();
|
|
503
|
-
document.body.removeChild(a);
|
|
504
|
-
URL.revokeObjectURL(url);
|
|
505
|
-
|
|
506
|
-
return true;
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
// Create a visual dashboard
|
|
510
|
-
createDashboard(container = document.body) {
|
|
511
|
-
// Create dashboard element
|
|
512
|
-
const dashboard = document.createElement('div');
|
|
513
|
-
dashboard.className = 'uistate-telemetry-dashboard';
|
|
514
|
-
dashboard.style.cssText = `
|
|
515
|
-
position: fixed;
|
|
516
|
-
bottom: 10px;
|
|
517
|
-
right: 10px;
|
|
518
|
-
width: 300px;
|
|
519
|
-
max-height: 400px;
|
|
520
|
-
overflow: auto;
|
|
521
|
-
background: #fff;
|
|
522
|
-
border: 1px solid #ccc;
|
|
523
|
-
border-radius: 4px;
|
|
524
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
525
|
-
padding: 10px;
|
|
526
|
-
font-family: monospace;
|
|
527
|
-
font-size: 12px;
|
|
528
|
-
z-index: 9999;
|
|
529
|
-
`;
|
|
530
|
-
|
|
531
|
-
// Update function
|
|
532
|
-
const updateDashboard = () => {
|
|
533
|
-
const report = this.getReport();
|
|
534
|
-
|
|
535
|
-
dashboard.innerHTML = `
|
|
536
|
-
<h3 style="margin: 0 0 10px; font-size: 14px;">UIstate Telemetry</h3>
|
|
537
|
-
<div style="margin-bottom: 5px;">
|
|
538
|
-
<button id="telemetry-refresh">Refresh</button>
|
|
539
|
-
<button id="telemetry-reset">Reset</button>
|
|
540
|
-
<button id="telemetry-close">Close</button>
|
|
541
|
-
</div>
|
|
542
|
-
<div style="margin: 10px 0;">
|
|
543
|
-
<strong>Duration:</strong> ${Math.round((Date.now() - report.startTime) / 1000)}s
|
|
544
|
-
</div>
|
|
545
|
-
|
|
546
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">Method Calls</h4>
|
|
547
|
-
<table style="width: 100%; border-collapse: collapse;">
|
|
548
|
-
<tr>
|
|
549
|
-
<th style="text-align: left; border-bottom: 1px solid #eee;">Method</th>
|
|
550
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
|
|
551
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Avg Time</th>
|
|
552
|
-
</tr>
|
|
553
|
-
${Object.entries(report.methodStats)
|
|
554
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
555
|
-
.map(([method, stats]) => `
|
|
556
|
-
<tr>
|
|
557
|
-
<td style="padding: 2px 0; border-bottom: 1px solid #eee;">${method}</td>
|
|
558
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
|
|
559
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.averageDuration ? stats.averageDuration.toFixed(2) + 'ms' : 'n/a'}</td>
|
|
560
|
-
</tr>
|
|
561
|
-
`).join('')}
|
|
562
|
-
</table>
|
|
563
|
-
|
|
564
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">State Changes</h4>
|
|
565
|
-
<table style="width: 100%; border-collapse: collapse;">
|
|
566
|
-
<tr>
|
|
567
|
-
<th style="text-align: left; border-bottom: 1px solid #eee;">Path</th>
|
|
568
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
|
|
569
|
-
</tr>
|
|
570
|
-
${Object.entries(report.stateStats)
|
|
571
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
572
|
-
.map(([path, stats]) => `
|
|
573
|
-
<tr>
|
|
574
|
-
<td style="padding: 2px 0; border-bottom: 1px solid #eee;">${path}</td>
|
|
575
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
|
|
576
|
-
</tr>
|
|
577
|
-
`).join('')}
|
|
578
|
-
</table>
|
|
579
|
-
|
|
580
|
-
${report.unusedMethods.length > 0 ? `
|
|
581
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">Unused Methods</h4>
|
|
582
|
-
<div style="color: #666;">
|
|
583
|
-
${report.unusedMethods.join(', ')}
|
|
584
|
-
</div>
|
|
585
|
-
` : ''}
|
|
586
|
-
`;
|
|
587
|
-
|
|
588
|
-
// Add event listeners
|
|
589
|
-
dashboard.querySelector('#telemetry-refresh').addEventListener('click', updateDashboard);
|
|
590
|
-
dashboard.querySelector('#telemetry-reset').addEventListener('click', () => {
|
|
591
|
-
this.reset();
|
|
592
|
-
updateDashboard();
|
|
593
|
-
});
|
|
594
|
-
dashboard.querySelector('#telemetry-close').addEventListener('click', () => {
|
|
595
|
-
dashboard.remove();
|
|
596
|
-
});
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
// Initial update
|
|
600
|
-
updateDashboard();
|
|
601
|
-
|
|
602
|
-
// Add to container
|
|
603
|
-
container.appendChild(dashboard);
|
|
604
|
-
|
|
605
|
-
return dashboard;
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
|
|
609
|
-
// Clean up
|
|
610
|
-
destroy() {
|
|
611
|
-
// Restore original methods
|
|
612
|
-
if (uistateRef) {
|
|
613
|
-
originalMethods.forEach((originalMethod, methodName) => {
|
|
614
|
-
if (uistateRef[methodName]) {
|
|
615
|
-
uistateRef[methodName] = originalMethod;
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Clear data
|
|
621
|
-
data.methodCalls.clear();
|
|
622
|
-
data.stateChanges.clear();
|
|
623
|
-
data.performanceMetrics.clear();
|
|
624
|
-
data.activeTimers.clear();
|
|
625
|
-
data.unusedMethods.clear();
|
|
626
|
-
|
|
627
|
-
uistateRef = null;
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
return telemetryPlugin;
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// Create a default instance with standard configuration
|
|
635
|
-
const telemetryPlugin = createTelemetryPlugin();
|
|
636
|
-
|
|
637
|
-
export default telemetryPlugin;
|
|
638
|
-
export { createTelemetryPlugin };
|