canvasframework 0.3.21 → 0.3.23
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/components/SliverAppBar.js +139 -0
- package/core/CanvasFramework.js +98 -0
- package/core/UIBuilder.js +2 -1
- package/index.js +3 -1
- package/package.json +1 -1
- package/utils/DevTools.js +1247 -0
- package/utils/DevToolsConsole.js +201 -0
- package/utils/InspectionOverlay.js +308 -0
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outils de développement pour CanvasFramework
|
|
3
|
+
* @class
|
|
4
|
+
*/
|
|
5
|
+
class DevTools {
|
|
6
|
+
/**
|
|
7
|
+
* Crée une instance de DevTools
|
|
8
|
+
* @param {CanvasFramework} framework - Instance du framework
|
|
9
|
+
*/
|
|
10
|
+
constructor(framework) {
|
|
11
|
+
this.framework = framework;
|
|
12
|
+
this.isOpen = false;
|
|
13
|
+
this.currentTab = 'components';
|
|
14
|
+
this.panels = {};
|
|
15
|
+
this.hoveredComponent = null;
|
|
16
|
+
this.selectedComponent = null;
|
|
17
|
+
this.performanceStats = {
|
|
18
|
+
fps: [],
|
|
19
|
+
memory: [],
|
|
20
|
+
drawCalls: []
|
|
21
|
+
};
|
|
22
|
+
this.setupUI();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configure l'interface du DevTools
|
|
27
|
+
*/
|
|
28
|
+
setupUI() {
|
|
29
|
+
// Créer le conteneur principal
|
|
30
|
+
this.container = document.createElement('div');
|
|
31
|
+
this.container.style.cssText = `
|
|
32
|
+
position: fixed;
|
|
33
|
+
top: 0;
|
|
34
|
+
right: 0;
|
|
35
|
+
width: 400px;
|
|
36
|
+
height: 100vh;
|
|
37
|
+
background: #1e1e1e;
|
|
38
|
+
color: #fff;
|
|
39
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
z-index: 9999;
|
|
42
|
+
transform: translateX(100%);
|
|
43
|
+
transition: transform 0.3s ease;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
box-shadow: -2px 0 10px rgba(0,0,0,0.5);
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
// Header
|
|
50
|
+
this.header = document.createElement('div');
|
|
51
|
+
this.header.style.cssText = `
|
|
52
|
+
background: #252526;
|
|
53
|
+
padding: 10px;
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
align-items: center;
|
|
57
|
+
border-bottom: 1px solid #333;
|
|
58
|
+
user-select: none;
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
this.title = document.createElement('div');
|
|
62
|
+
this.title.textContent = 'Canvas Framework DevTools';
|
|
63
|
+
this.title.style.fontWeight = 'bold';
|
|
64
|
+
|
|
65
|
+
this.closeBtn = document.createElement('button');
|
|
66
|
+
this.closeBtn.textContent = '×';
|
|
67
|
+
this.closeBtn.style.cssText = `
|
|
68
|
+
background: transparent;
|
|
69
|
+
border: none;
|
|
70
|
+
color: #fff;
|
|
71
|
+
font-size: 20px;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
padding: 0 8px;
|
|
74
|
+
`;
|
|
75
|
+
this.closeBtn.onclick = () => this.toggle();
|
|
76
|
+
|
|
77
|
+
this.header.appendChild(this.title);
|
|
78
|
+
this.header.appendChild(this.closeBtn);
|
|
79
|
+
|
|
80
|
+
// Tabs
|
|
81
|
+
this.tabs = document.createElement('div');
|
|
82
|
+
this.tabs.style.cssText = `
|
|
83
|
+
display: flex;
|
|
84
|
+
background: #2d2d2d;
|
|
85
|
+
border-bottom: 1px solid #333;
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const tabConfigs = [
|
|
89
|
+
{ id: 'components', label: 'Composants' },
|
|
90
|
+
{ id: 'performance', label: 'Performance' },
|
|
91
|
+
{ id: 'hierarchy', label: 'Hiérarchie' },
|
|
92
|
+
{ id: 'properties', label: 'Propriétés' },
|
|
93
|
+
{ id: 'events', label: 'Événements' },
|
|
94
|
+
{ id: 'routing', label: 'Routing' }
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
tabConfigs.forEach(config => {
|
|
98
|
+
const tab = document.createElement('button');
|
|
99
|
+
tab.textContent = config.label;
|
|
100
|
+
tab.dataset.tab = config.id;
|
|
101
|
+
tab.style.cssText = `
|
|
102
|
+
flex: 1;
|
|
103
|
+
padding: 8px;
|
|
104
|
+
background: transparent;
|
|
105
|
+
border: none;
|
|
106
|
+
color: #ccc;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
border-bottom: 2px solid transparent;
|
|
109
|
+
`;
|
|
110
|
+
tab.onclick = () => this.switchTab(config.id);
|
|
111
|
+
this.tabs.appendChild(tab);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Content area
|
|
115
|
+
this.content = document.createElement('div');
|
|
116
|
+
this.content.style.cssText = `
|
|
117
|
+
flex: 1;
|
|
118
|
+
overflow: auto;
|
|
119
|
+
padding: 10px;
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// Create panels
|
|
123
|
+
this.createComponentsPanel();
|
|
124
|
+
this.createPerformancePanel();
|
|
125
|
+
this.createHierarchyPanel();
|
|
126
|
+
this.createPropertiesPanel();
|
|
127
|
+
this.createEventsPanel();
|
|
128
|
+
this.createRoutingPanel();
|
|
129
|
+
|
|
130
|
+
// Assembler le conteneur
|
|
131
|
+
this.container.appendChild(this.header);
|
|
132
|
+
this.container.appendChild(this.tabs);
|
|
133
|
+
this.container.appendChild(this.content);
|
|
134
|
+
|
|
135
|
+
document.body.appendChild(this.container);
|
|
136
|
+
|
|
137
|
+
// Bouton toggle flottant
|
|
138
|
+
this.toggleBtn = document.createElement('button');
|
|
139
|
+
this.toggleBtn.textContent = 'DevTools';
|
|
140
|
+
this.toggleBtn.style.cssText = `
|
|
141
|
+
position: fixed;
|
|
142
|
+
bottom: 20px;
|
|
143
|
+
right: 20px;
|
|
144
|
+
background: #007acc;
|
|
145
|
+
color: white;
|
|
146
|
+
border: none;
|
|
147
|
+
border-radius: 4px;
|
|
148
|
+
padding: 8px 12px;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
z-index: 10000;
|
|
152
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
153
|
+
`;
|
|
154
|
+
this.toggleBtn.onclick = () => this.toggle();
|
|
155
|
+
document.body.appendChild(this.toggleBtn);
|
|
156
|
+
|
|
157
|
+
// Setup hotkeys
|
|
158
|
+
this.setupHotkeys();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Crée le panneau des composants
|
|
163
|
+
*/
|
|
164
|
+
createComponentsPanel() {
|
|
165
|
+
const panel = document.createElement('div');
|
|
166
|
+
panel.id = 'components-panel';
|
|
167
|
+
panel.style.display = 'none';
|
|
168
|
+
|
|
169
|
+
// Stats
|
|
170
|
+
const stats = document.createElement('div');
|
|
171
|
+
stats.style.cssText = `
|
|
172
|
+
background: #252526;
|
|
173
|
+
padding: 8px;
|
|
174
|
+
border-radius: 4px;
|
|
175
|
+
margin-bottom: 10px;
|
|
176
|
+
`;
|
|
177
|
+
stats.innerHTML = `
|
|
178
|
+
<div style="display: flex; justify-content: space-between;">
|
|
179
|
+
<span>Total: <span id="component-count">0</span></span>
|
|
180
|
+
<span>Visibles: <span id="visible-count">0</span></span>
|
|
181
|
+
<span>Dirty: <span id="dirty-count">0</span></span>
|
|
182
|
+
</div>
|
|
183
|
+
`;
|
|
184
|
+
|
|
185
|
+
// Filter
|
|
186
|
+
const filter = document.createElement('input');
|
|
187
|
+
filter.type = 'text';
|
|
188
|
+
filter.placeholder = 'Filtrer les composants...';
|
|
189
|
+
filter.style.cssText = `
|
|
190
|
+
width: 100%;
|
|
191
|
+
padding: 6px;
|
|
192
|
+
margin-bottom: 10px;
|
|
193
|
+
background: #333;
|
|
194
|
+
border: 1px solid #555;
|
|
195
|
+
color: #fff;
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
// Component list
|
|
200
|
+
const list = document.createElement('div');
|
|
201
|
+
list.id = 'component-list';
|
|
202
|
+
list.style.cssText = `
|
|
203
|
+
max-height: 400px;
|
|
204
|
+
overflow-y: auto;
|
|
205
|
+
border: 1px solid #333;
|
|
206
|
+
border-radius: 4px;
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
// Controls
|
|
210
|
+
const controls = document.createElement('div');
|
|
211
|
+
controls.style.cssText = `
|
|
212
|
+
display: flex;
|
|
213
|
+
gap: 8px;
|
|
214
|
+
margin-top: 10px;
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
const toggleAllBtn = document.createElement('button');
|
|
218
|
+
toggleAllBtn.textContent = 'Toggle All';
|
|
219
|
+
toggleAllBtn.style.cssText = `
|
|
220
|
+
flex: 1;
|
|
221
|
+
padding: 6px;
|
|
222
|
+
background: #555;
|
|
223
|
+
color: #fff;
|
|
224
|
+
border: none;
|
|
225
|
+
border-radius: 4px;
|
|
226
|
+
cursor: pointer;
|
|
227
|
+
`;
|
|
228
|
+
toggleAllBtn.onclick = () => this.toggleAllComponents();
|
|
229
|
+
|
|
230
|
+
const refreshBtn = document.createElement('button');
|
|
231
|
+
refreshBtn.textContent = 'Refresh';
|
|
232
|
+
refreshBtn.style.cssText = toggleAllBtn.style.cssText;
|
|
233
|
+
refreshBtn.onclick = () => this.refreshComponents();
|
|
234
|
+
|
|
235
|
+
controls.appendChild(toggleAllBtn);
|
|
236
|
+
controls.appendChild(refreshBtn);
|
|
237
|
+
|
|
238
|
+
panel.appendChild(stats);
|
|
239
|
+
panel.appendChild(filter);
|
|
240
|
+
panel.appendChild(list);
|
|
241
|
+
panel.appendChild(controls);
|
|
242
|
+
|
|
243
|
+
this.content.appendChild(panel);
|
|
244
|
+
this.panels.components = panel;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Crée le panneau de performance
|
|
249
|
+
*/
|
|
250
|
+
createPerformancePanel() {
|
|
251
|
+
const panel = document.createElement('div');
|
|
252
|
+
panel.id = 'performance-panel';
|
|
253
|
+
panel.style.display = 'none';
|
|
254
|
+
|
|
255
|
+
// FPS Chart
|
|
256
|
+
const fpsSection = document.createElement('div');
|
|
257
|
+
fpsSection.style.marginBottom = '20px';
|
|
258
|
+
|
|
259
|
+
const fpsTitle = document.createElement('h3');
|
|
260
|
+
fpsTitle.textContent = 'FPS';
|
|
261
|
+
fpsTitle.style.marginBottom = '8px';
|
|
262
|
+
|
|
263
|
+
const fpsCanvas = document.createElement('canvas');
|
|
264
|
+
fpsCanvas.width = 380;
|
|
265
|
+
fpsCanvas.height = 100;
|
|
266
|
+
fpsCanvas.style.cssText = `
|
|
267
|
+
width: 100%;
|
|
268
|
+
height: 100px;
|
|
269
|
+
background: #252526;
|
|
270
|
+
border-radius: 4px;
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
fpsSection.appendChild(fpsTitle);
|
|
274
|
+
fpsSection.appendChild(fpsCanvas);
|
|
275
|
+
|
|
276
|
+
// Memory Stats
|
|
277
|
+
const memorySection = document.createElement('div');
|
|
278
|
+
memorySection.style.marginBottom = '20px';
|
|
279
|
+
|
|
280
|
+
const memoryTitle = document.createElement('h3');
|
|
281
|
+
memoryTitle.textContent = 'Mémoire';
|
|
282
|
+
memoryTitle.style.marginBottom = '8px';
|
|
283
|
+
|
|
284
|
+
const memoryInfo = document.createElement('div');
|
|
285
|
+
memoryInfo.id = 'memory-info';
|
|
286
|
+
memoryInfo.style.cssText = `
|
|
287
|
+
background: #252526;
|
|
288
|
+
padding: 8px;
|
|
289
|
+
border-radius: 4px;
|
|
290
|
+
font-family: monospace;
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
memorySection.appendChild(memoryTitle);
|
|
294
|
+
memorySection.appendChild(memoryInfo);
|
|
295
|
+
|
|
296
|
+
// Performance Tips
|
|
297
|
+
const tipsSection = document.createElement('div');
|
|
298
|
+
|
|
299
|
+
const tipsTitle = document.createElement('h3');
|
|
300
|
+
tipsTitle.textContent = 'Conseils d\'optimisation';
|
|
301
|
+
tipsTitle.style.marginBottom = '8px';
|
|
302
|
+
|
|
303
|
+
const tipsList = document.createElement('ul');
|
|
304
|
+
tipsList.style.cssText = `
|
|
305
|
+
padding-left: 20px;
|
|
306
|
+
color: #aaa;
|
|
307
|
+
`;
|
|
308
|
+
tipsList.innerHTML = `
|
|
309
|
+
<li>Activer l'optimizationEnabled</li>
|
|
310
|
+
<li>Réduire le nombre de composants visibles</li>
|
|
311
|
+
<li>Utiliser VirtualList pour les longues listes</li>
|
|
312
|
+
<li>Éviter les animations sur trop de composants</li>
|
|
313
|
+
`;
|
|
314
|
+
|
|
315
|
+
tipsSection.appendChild(tipsTitle);
|
|
316
|
+
tipsSection.appendChild(tipsList);
|
|
317
|
+
|
|
318
|
+
panel.appendChild(fpsSection);
|
|
319
|
+
panel.appendChild(memorySection);
|
|
320
|
+
panel.appendChild(tipsSection);
|
|
321
|
+
|
|
322
|
+
this.content.appendChild(panel);
|
|
323
|
+
this.panels.performance = panel;
|
|
324
|
+
this.fpsCanvas = fpsCanvas;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Crée le panneau hiérarchique
|
|
329
|
+
*/
|
|
330
|
+
createHierarchyPanel() {
|
|
331
|
+
const panel = document.createElement('div');
|
|
332
|
+
panel.id = 'hierarchy-panel';
|
|
333
|
+
panel.style.display = 'none';
|
|
334
|
+
|
|
335
|
+
const tree = document.createElement('div');
|
|
336
|
+
tree.id = 'component-tree';
|
|
337
|
+
tree.style.cssText = `
|
|
338
|
+
font-family: monospace;
|
|
339
|
+
line-height: 1.4;
|
|
340
|
+
`;
|
|
341
|
+
|
|
342
|
+
panel.appendChild(tree);
|
|
343
|
+
this.content.appendChild(panel);
|
|
344
|
+
this.panels.hierarchy = panel;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Crée le panneau des propriétés
|
|
349
|
+
*/
|
|
350
|
+
createPropertiesPanel() {
|
|
351
|
+
const panel = document.createElement('div');
|
|
352
|
+
panel.id = 'properties-panel';
|
|
353
|
+
panel.style.display = 'none';
|
|
354
|
+
|
|
355
|
+
const noSelection = document.createElement('div');
|
|
356
|
+
noSelection.id = 'no-selection';
|
|
357
|
+
noSelection.textContent = 'Sélectionnez un composant pour voir ses propriétés';
|
|
358
|
+
noSelection.style.color = '#888';
|
|
359
|
+
|
|
360
|
+
const propertiesTable = document.createElement('div');
|
|
361
|
+
propertiesTable.id = 'properties-table';
|
|
362
|
+
propertiesTable.style.display = 'none';
|
|
363
|
+
|
|
364
|
+
panel.appendChild(noSelection);
|
|
365
|
+
panel.appendChild(propertiesTable);
|
|
366
|
+
this.content.appendChild(panel);
|
|
367
|
+
this.panels.properties = panel;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Crée le panneau des événements
|
|
372
|
+
*/
|
|
373
|
+
createEventsPanel() {
|
|
374
|
+
const panel = document.createElement('div');
|
|
375
|
+
panel.id = 'events-panel';
|
|
376
|
+
panel.style.display = 'none';
|
|
377
|
+
|
|
378
|
+
const eventsList = document.createElement('div');
|
|
379
|
+
eventsList.id = 'events-list';
|
|
380
|
+
eventsList.style.cssText = `
|
|
381
|
+
max-height: 400px;
|
|
382
|
+
overflow-y: auto;
|
|
383
|
+
border: 1px solid #333;
|
|
384
|
+
border-radius: 4px;
|
|
385
|
+
padding: 8px;
|
|
386
|
+
`;
|
|
387
|
+
|
|
388
|
+
const clearBtn = document.createElement('button');
|
|
389
|
+
clearBtn.textContent = 'Clear Events';
|
|
390
|
+
clearBtn.style.cssText = `
|
|
391
|
+
margin-top: 10px;
|
|
392
|
+
padding: 6px 12px;
|
|
393
|
+
background: #555;
|
|
394
|
+
color: #fff;
|
|
395
|
+
border: none;
|
|
396
|
+
border-radius: 4px;
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
`;
|
|
399
|
+
clearBtn.onclick = () => this.clearEvents();
|
|
400
|
+
|
|
401
|
+
panel.appendChild(eventsList);
|
|
402
|
+
panel.appendChild(clearBtn);
|
|
403
|
+
this.content.appendChild(panel);
|
|
404
|
+
this.panels.events = panel;
|
|
405
|
+
this.eventsList = eventsList;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Crée le panneau de routing
|
|
410
|
+
*/
|
|
411
|
+
createRoutingPanel() {
|
|
412
|
+
const panel = document.createElement('div');
|
|
413
|
+
panel.id = 'routing-panel';
|
|
414
|
+
panel.style.display = 'none';
|
|
415
|
+
|
|
416
|
+
const routeInfo = document.createElement('div');
|
|
417
|
+
routeInfo.id = 'route-info';
|
|
418
|
+
routeInfo.style.cssText = `
|
|
419
|
+
background: #252526;
|
|
420
|
+
padding: 10px;
|
|
421
|
+
border-radius: 4px;
|
|
422
|
+
margin-bottom: 10px;
|
|
423
|
+
`;
|
|
424
|
+
|
|
425
|
+
const historySection = document.createElement('div');
|
|
426
|
+
|
|
427
|
+
const historyTitle = document.createElement('h3');
|
|
428
|
+
historyTitle.textContent = 'Historique de navigation';
|
|
429
|
+
historyTitle.style.marginBottom = '8px';
|
|
430
|
+
|
|
431
|
+
const historyList = document.createElement('div');
|
|
432
|
+
historyList.id = 'history-list';
|
|
433
|
+
historyList.style.cssText = `
|
|
434
|
+
max-height: 200px;
|
|
435
|
+
overflow-y: auto;
|
|
436
|
+
border: 1px solid #333;
|
|
437
|
+
border-radius: 4px;
|
|
438
|
+
padding: 8px;
|
|
439
|
+
`;
|
|
440
|
+
|
|
441
|
+
historySection.appendChild(historyTitle);
|
|
442
|
+
historySection.appendChild(historyList);
|
|
443
|
+
|
|
444
|
+
panel.appendChild(routeInfo);
|
|
445
|
+
panel.appendChild(historySection);
|
|
446
|
+
|
|
447
|
+
this.content.appendChild(panel);
|
|
448
|
+
this.panels.routing = panel;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Configure les raccourcis clavier
|
|
453
|
+
*/
|
|
454
|
+
setupHotkeys() {
|
|
455
|
+
document.addEventListener('keydown', (e) => {
|
|
456
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
|
457
|
+
e.preventDefault();
|
|
458
|
+
this.toggle();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Permet d'inspecter les composants avec Ctrl+Click
|
|
462
|
+
this.framework.canvas.addEventListener('click', (e) => {
|
|
463
|
+
if (e.ctrlKey && this.isOpen) {
|
|
464
|
+
e.preventDefault();
|
|
465
|
+
const rect = this.framework.canvas.getBoundingClientRect();
|
|
466
|
+
const x = e.clientX - rect.left;
|
|
467
|
+
const y = e.clientY - rect.top;
|
|
468
|
+
this.inspectComponentAt(x, y);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Bascule l'affichage du DevTools
|
|
476
|
+
*/
|
|
477
|
+
toggle() {
|
|
478
|
+
this.isOpen = !this.isOpen;
|
|
479
|
+
if (this.isOpen) {
|
|
480
|
+
this.container.style.transform = 'translateX(0)';
|
|
481
|
+
this.refreshAll();
|
|
482
|
+
this.startMonitoring();
|
|
483
|
+
} else {
|
|
484
|
+
this.container.style.transform = 'translateX(100%)';
|
|
485
|
+
this.stopMonitoring();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Change l'onglet actif
|
|
491
|
+
*/
|
|
492
|
+
switchTab(tabId) {
|
|
493
|
+
this.currentTab = tabId;
|
|
494
|
+
|
|
495
|
+
// Mettre à jour les styles des tabs
|
|
496
|
+
this.tabs.querySelectorAll('button').forEach(btn => {
|
|
497
|
+
btn.style.borderBottomColor = btn.dataset.tab === tabId ? '#007acc' : 'transparent';
|
|
498
|
+
btn.style.color = btn.dataset.tab === tabId ? '#fff' : '#ccc';
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Afficher/masquer les panels
|
|
502
|
+
Object.keys(this.panels).forEach(panelId => {
|
|
503
|
+
this.panels[panelId].style.display = panelId === tabId ? 'block' : 'none';
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (tabId === 'performance') {
|
|
507
|
+
this.updatePerformancePanel();
|
|
508
|
+
} else if (tabId === 'hierarchy') {
|
|
509
|
+
this.updateHierarchyPanel();
|
|
510
|
+
} else if (tabId === 'components') {
|
|
511
|
+
this.updateComponentsPanel();
|
|
512
|
+
} else if (tabId === 'routing') {
|
|
513
|
+
this.updateRoutingPanel();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Rafraîchit toutes les données
|
|
519
|
+
*/
|
|
520
|
+
refreshAll() {
|
|
521
|
+
this.updateComponentsPanel();
|
|
522
|
+
this.updateHierarchyPanel();
|
|
523
|
+
this.updateRoutingPanel();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Met à jour le panneau des composants
|
|
528
|
+
*/
|
|
529
|
+
updateComponentsPanel() {
|
|
530
|
+
if (!this.isOpen || this.currentTab !== 'components') return;
|
|
531
|
+
|
|
532
|
+
const components = this.framework.components;
|
|
533
|
+
const visibleCount = components.filter(c => c.visible).length;
|
|
534
|
+
const dirtyCount = components.filter(c => c._dirty).length;
|
|
535
|
+
|
|
536
|
+
document.getElementById('component-count').textContent = components.length;
|
|
537
|
+
document.getElementById('visible-count').textContent = visibleCount;
|
|
538
|
+
document.getElementById('dirty-count').textContent = dirtyCount;
|
|
539
|
+
|
|
540
|
+
const list = document.getElementById('component-list');
|
|
541
|
+
list.innerHTML = '';
|
|
542
|
+
|
|
543
|
+
components.forEach((comp, index) => {
|
|
544
|
+
const item = document.createElement('div');
|
|
545
|
+
item.style.cssText = `
|
|
546
|
+
padding: 6px 8px;
|
|
547
|
+
border-bottom: 1px solid #333;
|
|
548
|
+
cursor: pointer;
|
|
549
|
+
display: flex;
|
|
550
|
+
align-items: center;
|
|
551
|
+
background: ${comp === this.selectedComponent ? '#007acc20' : 'transparent'};
|
|
552
|
+
`;
|
|
553
|
+
|
|
554
|
+
const type = comp.constructor.name;
|
|
555
|
+
const bounds = `[${Math.round(comp.x)},${Math.round(comp.y)} ${Math.round(comp.width)}x${Math.round(comp.height)}]`;
|
|
556
|
+
|
|
557
|
+
item.innerHTML = `
|
|
558
|
+
<div style="display: flex; align-items: center; flex: 1;">
|
|
559
|
+
<input type="checkbox" ${comp.visible ? 'checked' : ''}
|
|
560
|
+
style="margin-right: 8px;"
|
|
561
|
+
onchange="event.stopPropagation(); devTools.toggleComponentVisibility(${index}, this.checked)">
|
|
562
|
+
<span style="color: #4ec9b0; min-width: 120px;">${type}</span>
|
|
563
|
+
<span style="color: #ccc; margin-left: 8px;">${bounds}</span>
|
|
564
|
+
${comp._dirty ? '<span style="color: orange; margin-left: 8px;">●</span>' : ''}
|
|
565
|
+
</div>
|
|
566
|
+
<button onclick="event.stopPropagation(); devTools.highlightComponent(${index})"
|
|
567
|
+
style="background: transparent; border: 1px solid #555; color: #ccc; padding: 2px 6px; border-radius: 3px; font-size: 10px;">
|
|
568
|
+
✨
|
|
569
|
+
</button>
|
|
570
|
+
`;
|
|
571
|
+
|
|
572
|
+
item.onclick = () => this.selectComponent(comp);
|
|
573
|
+
list.appendChild(item);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Met à jour le panneau de performance
|
|
579
|
+
*/
|
|
580
|
+
updatePerformancePanel() {
|
|
581
|
+
if (!this.isOpen || this.currentTab !== 'performance') return;
|
|
582
|
+
|
|
583
|
+
// Mettre à jour les stats FPS
|
|
584
|
+
this.performanceStats.fps.push(this.framework.fps);
|
|
585
|
+
if (this.performanceStats.fps.length > 100) {
|
|
586
|
+
this.performanceStats.fps.shift();
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Mettre à jour le graphique FPS
|
|
590
|
+
this.drawFPSChart();
|
|
591
|
+
|
|
592
|
+
// Mettre à jour les infos mémoire
|
|
593
|
+
if (performance.memory) {
|
|
594
|
+
const memory = performance.memory;
|
|
595
|
+
document.getElementById('memory-info').innerHTML = `
|
|
596
|
+
<div>Used: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)} MB</div>
|
|
597
|
+
<div>Total: ${Math.round(memory.totalJSHeapSize / 1024 / 1024)} MB</div>
|
|
598
|
+
<div>Limit: ${Math.round(memory.jsHeapSizeLimit / 1024 / 1024)} MB</div>
|
|
599
|
+
`;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Dessine le graphique FPS
|
|
605
|
+
*/
|
|
606
|
+
drawFPSChart() {
|
|
607
|
+
if (!this.fpsCanvas) return;
|
|
608
|
+
|
|
609
|
+
const ctx = this.fpsCanvas.getContext('2d');
|
|
610
|
+
const width = this.fpsCanvas.width;
|
|
611
|
+
const height = this.fpsCanvas.height;
|
|
612
|
+
|
|
613
|
+
// Clear
|
|
614
|
+
ctx.fillStyle = '#252526';
|
|
615
|
+
ctx.fillRect(0, 0, width, height);
|
|
616
|
+
|
|
617
|
+
// Draw grid
|
|
618
|
+
ctx.strokeStyle = '#333';
|
|
619
|
+
ctx.lineWidth = 1;
|
|
620
|
+
|
|
621
|
+
// Horizontal lines (FPS markers)
|
|
622
|
+
for (let i = 0; i <= 60; i += 15) {
|
|
623
|
+
const y = height - (i / 60 * height);
|
|
624
|
+
ctx.beginPath();
|
|
625
|
+
ctx.moveTo(0, y);
|
|
626
|
+
ctx.lineTo(width, y);
|
|
627
|
+
ctx.stroke();
|
|
628
|
+
|
|
629
|
+
// Labels
|
|
630
|
+
ctx.fillStyle = '#888';
|
|
631
|
+
ctx.font = '10px monospace';
|
|
632
|
+
ctx.fillText(`${i}`, 5, y - 2);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Draw FPS line
|
|
636
|
+
if (this.performanceStats.fps.length > 1) {
|
|
637
|
+
ctx.strokeStyle = '#4ec9b0';
|
|
638
|
+
ctx.lineWidth = 2;
|
|
639
|
+
ctx.beginPath();
|
|
640
|
+
|
|
641
|
+
const step = width / (this.performanceStats.fps.length - 1);
|
|
642
|
+
this.performanceStats.fps.forEach((fps, i) => {
|
|
643
|
+
const x = i * step;
|
|
644
|
+
const y = height - (Math.min(fps, 60) / 60 * height);
|
|
645
|
+
|
|
646
|
+
if (i === 0) {
|
|
647
|
+
ctx.moveTo(x, y);
|
|
648
|
+
} else {
|
|
649
|
+
ctx.lineTo(x, y);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
ctx.stroke();
|
|
654
|
+
|
|
655
|
+
// Current FPS
|
|
656
|
+
const currentFps = this.performanceStats.fps[this.performanceStats.fps.length - 1] || 0;
|
|
657
|
+
ctx.fillStyle = currentFps < 30 ? '#ff5555' : '#4ec9b0';
|
|
658
|
+
ctx.font = 'bold 12px monospace';
|
|
659
|
+
ctx.textAlign = 'right';
|
|
660
|
+
ctx.fillText(`${currentFps} FPS`, width - 5, 15);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Met à jour le panneau hiérarchique
|
|
666
|
+
*/
|
|
667
|
+
updateHierarchyPanel() {
|
|
668
|
+
if (!this.isOpen || this.currentTab !== 'hierarchy') return;
|
|
669
|
+
|
|
670
|
+
const tree = document.getElementById('component-tree');
|
|
671
|
+
tree.innerHTML = '';
|
|
672
|
+
|
|
673
|
+
const renderComponent = (comp, depth = 0) => {
|
|
674
|
+
const item = document.createElement('div');
|
|
675
|
+
item.style.cssText = `
|
|
676
|
+
padding-left: ${depth * 20}px;
|
|
677
|
+
margin: 2px 0;
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
background: ${comp === this.selectedComponent ? '#007acc20' : 'transparent'};
|
|
680
|
+
`;
|
|
681
|
+
|
|
682
|
+
const type = comp.constructor.name;
|
|
683
|
+
const hasChildren = comp.children && comp.children.length > 0;
|
|
684
|
+
|
|
685
|
+
item.innerHTML = `
|
|
686
|
+
<span style="color: ${comp.visible ? '#4ec9b0' : '#555'}">
|
|
687
|
+
${hasChildren ? '▼' : '○'} ${type}
|
|
688
|
+
<span style="color: #888; font-size: 10px;">
|
|
689
|
+
(${comp.width}x${comp.height})
|
|
690
|
+
</span>
|
|
691
|
+
</span>
|
|
692
|
+
`;
|
|
693
|
+
|
|
694
|
+
item.onclick = () => this.selectComponent(comp);
|
|
695
|
+
tree.appendChild(item);
|
|
696
|
+
|
|
697
|
+
// Rendre les enfants si c'est une Card ou autre conteneur
|
|
698
|
+
if (hasChildren && comp.children) {
|
|
699
|
+
// Vérifier si c'est un itérable
|
|
700
|
+
const childArray = Array.isArray(comp.children) ? comp.children : [];
|
|
701
|
+
|
|
702
|
+
childArray.forEach(child => {
|
|
703
|
+
// Vérifier si c'est un objet avec les propriétés d'un composant
|
|
704
|
+
if (child && typeof child === 'object' &&
|
|
705
|
+
'x' in child && 'y' in child && 'width' in child && 'height' in child) {
|
|
706
|
+
renderComponent(child, depth + 1);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
this.framework.components.forEach(comp => {
|
|
713
|
+
renderComponent(comp);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Met à jour le panneau des propriétés
|
|
719
|
+
*/
|
|
720
|
+
updatePropertiesPanel() {
|
|
721
|
+
if (!this.selectedComponent) return;
|
|
722
|
+
|
|
723
|
+
const table = document.getElementById('properties-table');
|
|
724
|
+
const noSelection = document.getElementById('no-selection');
|
|
725
|
+
|
|
726
|
+
noSelection.style.display = 'none';
|
|
727
|
+
table.style.display = 'block';
|
|
728
|
+
table.innerHTML = '';
|
|
729
|
+
|
|
730
|
+
const comp = this.selectedComponent;
|
|
731
|
+
const props = {};
|
|
732
|
+
|
|
733
|
+
// Collecter toutes les propriétés
|
|
734
|
+
for (let key in comp) {
|
|
735
|
+
if (key.startsWith('_') || key === 'framework') continue; // <-- EXCLURE 'framework'
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
const value = comp[key];
|
|
739
|
+
if (typeof value === 'function') continue;
|
|
740
|
+
|
|
741
|
+
// Éviter les références circulaires
|
|
742
|
+
if (key === 'parent' || key === 'children' || key === 'framework') {
|
|
743
|
+
props[key] = `[${value?.constructor?.name || 'Object'}]`;
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Tenter de stringifier, mais avec un repli sûr
|
|
748
|
+
props[key] = value;
|
|
749
|
+
} catch (e) {
|
|
750
|
+
props[key] = '<error>';
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Créer le tableau
|
|
755
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
756
|
+
const row = document.createElement('div');
|
|
757
|
+
row.style.cssText = `
|
|
758
|
+
display: flex;
|
|
759
|
+
padding: 4px 0;
|
|
760
|
+
border-bottom: 1px solid #333;
|
|
761
|
+
`;
|
|
762
|
+
|
|
763
|
+
const keyCell = document.createElement('div');
|
|
764
|
+
keyCell.textContent = key;
|
|
765
|
+
keyCell.style.cssText = `
|
|
766
|
+
color: #9cdcfe;
|
|
767
|
+
min-width: 150px;
|
|
768
|
+
font-family: monospace;
|
|
769
|
+
`;
|
|
770
|
+
|
|
771
|
+
const valueCell = document.createElement('div');
|
|
772
|
+
|
|
773
|
+
try {
|
|
774
|
+
// Fonction pour stringifier en évitant les cycles
|
|
775
|
+
const safeStringify = (obj, replacer, space) => {
|
|
776
|
+
const seen = new WeakSet();
|
|
777
|
+
return JSON.stringify(obj, (key, value) => {
|
|
778
|
+
if (typeof value === 'object' && value !== null) {
|
|
779
|
+
if (seen.has(value)) {
|
|
780
|
+
return '[Circular Reference]';
|
|
781
|
+
}
|
|
782
|
+
seen.add(value);
|
|
783
|
+
}
|
|
784
|
+
return replacer ? replacer(key, value) : value;
|
|
785
|
+
}, space);
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
if (typeof value === 'object' && value !== null) {
|
|
789
|
+
// Exclure certaines propriétés problématiques
|
|
790
|
+
if (value === comp || value === this.framework) {
|
|
791
|
+
valueCell.textContent = '[Circular Reference]';
|
|
792
|
+
} else if (value instanceof HTMLElement) {
|
|
793
|
+
valueCell.textContent = `[HTMLElement: ${value.tagName}]`;
|
|
794
|
+
} else if (value instanceof Event) {
|
|
795
|
+
valueCell.textContent = `[Event: ${value.type}]`;
|
|
796
|
+
} else if (value instanceof NodeList || value instanceof HTMLCollection) {
|
|
797
|
+
valueCell.textContent = `[Collection: ${value.length} items]`;
|
|
798
|
+
} else {
|
|
799
|
+
try {
|
|
800
|
+
valueCell.textContent = safeStringify(value, null, 2);
|
|
801
|
+
} catch (e) {
|
|
802
|
+
valueCell.textContent = `[Object: ${value.constructor?.name || 'Unknown'}]`;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
valueCell.style.color = '#ce9178';
|
|
806
|
+
} else if (typeof value === 'boolean') {
|
|
807
|
+
valueCell.textContent = value.toString();
|
|
808
|
+
valueCell.style.color = '#569cd6';
|
|
809
|
+
} else if (typeof value === 'number') {
|
|
810
|
+
valueCell.textContent = value;
|
|
811
|
+
valueCell.style.color = '#b5cea8';
|
|
812
|
+
} else {
|
|
813
|
+
valueCell.textContent = String(value);
|
|
814
|
+
valueCell.style.color = '#d4d4d4';
|
|
815
|
+
}
|
|
816
|
+
} catch (stringifyError) {
|
|
817
|
+
valueCell.textContent = '[Unserializable]';
|
|
818
|
+
valueCell.style.color = '#ff5555';
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
valueCell.style.cssText += `
|
|
822
|
+
font-family: monospace;
|
|
823
|
+
white-space: pre-wrap;
|
|
824
|
+
word-break: break-all;
|
|
825
|
+
flex: 1;
|
|
826
|
+
`;
|
|
827
|
+
|
|
828
|
+
row.appendChild(keyCell);
|
|
829
|
+
row.appendChild(valueCell);
|
|
830
|
+
table.appendChild(row);
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Ajouter des contrôles d'édition pour les propriétés importantes
|
|
834
|
+
const controls = document.createElement('div');
|
|
835
|
+
controls.style.marginTop = '20px';
|
|
836
|
+
|
|
837
|
+
const editTitle = document.createElement('h4');
|
|
838
|
+
editTitle.textContent = 'Modifier';
|
|
839
|
+
editTitle.style.marginBottom = '8px';
|
|
840
|
+
|
|
841
|
+
// Éditeur de position
|
|
842
|
+
const posEditor = document.createElement('div');
|
|
843
|
+
posEditor.style.cssText = `
|
|
844
|
+
display: grid;
|
|
845
|
+
grid-template-columns: 1fr 1fr;
|
|
846
|
+
gap: 8px;
|
|
847
|
+
margin-bottom: 10px;
|
|
848
|
+
`;
|
|
849
|
+
|
|
850
|
+
const createNumberInput = (label, value, onChange) => {
|
|
851
|
+
const container = document.createElement('div');
|
|
852
|
+
container.style.cssText = `
|
|
853
|
+
display: flex;
|
|
854
|
+
align-items: center;
|
|
855
|
+
gap: 4px;
|
|
856
|
+
`;
|
|
857
|
+
|
|
858
|
+
const labelEl = document.createElement('span');
|
|
859
|
+
labelEl.textContent = label;
|
|
860
|
+
labelEl.style.minWidth = '40px';
|
|
861
|
+
|
|
862
|
+
const input = document.createElement('input');
|
|
863
|
+
input.type = 'number';
|
|
864
|
+
input.value = value;
|
|
865
|
+
input.style.cssText = `
|
|
866
|
+
width: 60px;
|
|
867
|
+
padding: 2px 4px;
|
|
868
|
+
background: #333;
|
|
869
|
+
border: 1px solid #555;
|
|
870
|
+
color: #fff;
|
|
871
|
+
border-radius: 3px;
|
|
872
|
+
`;
|
|
873
|
+
input.onchange = (e) => onChange(parseFloat(e.target.value));
|
|
874
|
+
|
|
875
|
+
container.appendChild(labelEl);
|
|
876
|
+
container.appendChild(input);
|
|
877
|
+
return container;
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
posEditor.appendChild(
|
|
881
|
+
createNumberInput('X:', comp.x, (val) => {
|
|
882
|
+
comp.x = val;
|
|
883
|
+
comp.markDirty();
|
|
884
|
+
})
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
posEditor.appendChild(
|
|
888
|
+
createNumberInput('Y:', comp.y, (val) => {
|
|
889
|
+
comp.y = val;
|
|
890
|
+
comp.markDirty();
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
posEditor.appendChild(
|
|
895
|
+
createNumberInput('W:', comp.width, (val) => {
|
|
896
|
+
comp.width = val;
|
|
897
|
+
comp.markDirty();
|
|
898
|
+
})
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
posEditor.appendChild(
|
|
902
|
+
createNumberInput('H:', comp.height, (val) => {
|
|
903
|
+
comp.height = val;
|
|
904
|
+
comp.markDirty();
|
|
905
|
+
})
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
// Toggle pour visible
|
|
909
|
+
const visibleControl = document.createElement('div');
|
|
910
|
+
visibleControl.style.cssText = `
|
|
911
|
+
display: flex;
|
|
912
|
+
align-items: center;
|
|
913
|
+
gap: 8px;
|
|
914
|
+
margin-top: 10px;
|
|
915
|
+
`;
|
|
916
|
+
|
|
917
|
+
const visibleLabel = document.createElement('span');
|
|
918
|
+
visibleLabel.textContent = 'Visible:';
|
|
919
|
+
|
|
920
|
+
const visibleCheckbox = document.createElement('input');
|
|
921
|
+
visibleCheckbox.type = 'checkbox';
|
|
922
|
+
visibleCheckbox.checked = comp.visible;
|
|
923
|
+
visibleCheckbox.onchange = () => {
|
|
924
|
+
comp.visible = visibleCheckbox.checked;
|
|
925
|
+
comp.markDirty();
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
visibleControl.appendChild(visibleLabel);
|
|
929
|
+
visibleControl.appendChild(visibleCheckbox);
|
|
930
|
+
|
|
931
|
+
controls.appendChild(editTitle);
|
|
932
|
+
controls.appendChild(posEditor);
|
|
933
|
+
controls.appendChild(visibleControl);
|
|
934
|
+
table.appendChild(controls);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Met à jour le panneau de routing
|
|
939
|
+
*/
|
|
940
|
+
updateRoutingPanel() {
|
|
941
|
+
if (!this.isOpen || this.currentTab !== 'routing') return;
|
|
942
|
+
|
|
943
|
+
const routeInfo = document.getElementById('route-info');
|
|
944
|
+
const historyList = document.getElementById('history-list');
|
|
945
|
+
|
|
946
|
+
// Current route
|
|
947
|
+
routeInfo.innerHTML = `
|
|
948
|
+
<div><strong>Route actuelle:</strong> ${this.framework.currentRoute}</div>
|
|
949
|
+
<div><strong>Paramètres:</strong> ${JSON.stringify(this.framework.currentParams)}</div>
|
|
950
|
+
<div><strong>Query:</strong> ${JSON.stringify(this.framework.currentQuery)}</div>
|
|
951
|
+
`;
|
|
952
|
+
|
|
953
|
+
// History
|
|
954
|
+
historyList.innerHTML = '';
|
|
955
|
+
this.framework.history.forEach((entry, index) => {
|
|
956
|
+
const item = document.createElement('div');
|
|
957
|
+
item.style.cssText = `
|
|
958
|
+
padding: 4px 8px;
|
|
959
|
+
margin: 2px 0;
|
|
960
|
+
background: ${index === this.framework.historyIndex ? '#007acc20' : 'transparent'};
|
|
961
|
+
border-radius: 3px;
|
|
962
|
+
cursor: pointer;
|
|
963
|
+
font-family: monospace;
|
|
964
|
+
font-size: 11px;
|
|
965
|
+
`;
|
|
966
|
+
item.textContent = `${index}: ${entry.path}`;
|
|
967
|
+
item.onclick = () => {
|
|
968
|
+
this.framework.navigate(entry.path, { replace: true });
|
|
969
|
+
};
|
|
970
|
+
historyList.appendChild(item);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Ajoute un événement au panneau
|
|
976
|
+
*/
|
|
977
|
+
logEvent(type, data) {
|
|
978
|
+
if (!this.isOpen || this.currentTab !== 'events') return;
|
|
979
|
+
|
|
980
|
+
const eventItem = document.createElement('div');
|
|
981
|
+
eventItem.style.cssText = `
|
|
982
|
+
padding: 4px 8px;
|
|
983
|
+
margin: 2px 0;
|
|
984
|
+
background: #252526;
|
|
985
|
+
border-left: 3px solid #007acc;
|
|
986
|
+
font-family: monospace;
|
|
987
|
+
font-size: 11px;
|
|
988
|
+
`;
|
|
989
|
+
|
|
990
|
+
const time = new Date().toLocaleTimeString();
|
|
991
|
+
eventItem.innerHTML = `
|
|
992
|
+
<div style="color: #569cd6;">${time} - ${type}</div>
|
|
993
|
+
<div style="color: #888; font-size: 10px;">${JSON.stringify(data)}</div>
|
|
994
|
+
`;
|
|
995
|
+
|
|
996
|
+
this.eventsList.appendChild(eventItem);
|
|
997
|
+
this.eventsList.scrollTop = this.eventsList.scrollHeight;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Nettoie la liste des événements
|
|
1002
|
+
*/
|
|
1003
|
+
clearEvents() {
|
|
1004
|
+
this.eventsList.innerHTML = '';
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Sélectionne un composant
|
|
1009
|
+
*/
|
|
1010
|
+
selectComponent(component) {
|
|
1011
|
+
this.selectedComponent = component;
|
|
1012
|
+
this.updatePropertiesPanel();
|
|
1013
|
+
this.updateComponentsPanel();
|
|
1014
|
+
this.updateHierarchyPanel();
|
|
1015
|
+
this.highlightComponentDirectly(component);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Inspecte un composant à une position donnée
|
|
1020
|
+
*/
|
|
1021
|
+
inspectComponentAt(x, y) {
|
|
1022
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
1023
|
+
|
|
1024
|
+
for (let i = this.framework.components.length - 1; i >= 0; i--) {
|
|
1025
|
+
const comp = this.framework.components[i];
|
|
1026
|
+
|
|
1027
|
+
if (comp.visible && comp.isPointInside(x, adjustedY)) {
|
|
1028
|
+
this.selectComponent(comp);
|
|
1029
|
+
this.switchTab('properties');
|
|
1030
|
+
return comp;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Met en surbrillance un composant
|
|
1038
|
+
*/
|
|
1039
|
+
highlightComponent(index) {
|
|
1040
|
+
if (index >= 0 && index < this.framework.components.length) {
|
|
1041
|
+
const comp = this.framework.components[index];
|
|
1042
|
+
this.highlightComponentDirectly(comp);
|
|
1043
|
+
|
|
1044
|
+
// Animation de flash
|
|
1045
|
+
const originalOpacity = comp.opacity;
|
|
1046
|
+
let flashCount = 0;
|
|
1047
|
+
const flashInterval = setInterval(() => {
|
|
1048
|
+
comp.opacity = flashCount % 2 === 0 ? 0.5 : (originalOpacity || 1);
|
|
1049
|
+
comp.markDirty();
|
|
1050
|
+
flashCount++;
|
|
1051
|
+
|
|
1052
|
+
if (flashCount > 5) {
|
|
1053
|
+
clearInterval(flashInterval);
|
|
1054
|
+
comp.opacity = originalOpacity || 1;
|
|
1055
|
+
comp.markDirty();
|
|
1056
|
+
}
|
|
1057
|
+
}, 200);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Met en surbrillance directe d'un composant
|
|
1063
|
+
*/
|
|
1064
|
+
highlightComponentDirectly(comp) {
|
|
1065
|
+
// Dessiner une surbrillance autour du composant
|
|
1066
|
+
const originalDraw = comp.draw;
|
|
1067
|
+
comp.draw = function(ctx) {
|
|
1068
|
+
originalDraw.call(this, ctx);
|
|
1069
|
+
|
|
1070
|
+
// Dessiner un cadre de surbrillance
|
|
1071
|
+
ctx.save();
|
|
1072
|
+
ctx.strokeStyle = '#ff5555';
|
|
1073
|
+
ctx.lineWidth = 2;
|
|
1074
|
+
ctx.setLineDash([5, 3]);
|
|
1075
|
+
ctx.strokeRect(this.x - 2, this.y - 2, this.width + 4, this.height + 4);
|
|
1076
|
+
|
|
1077
|
+
// Ajouter une étiquette
|
|
1078
|
+
ctx.fillStyle = '#ff5555';
|
|
1079
|
+
ctx.font = '10px Arial';
|
|
1080
|
+
ctx.fillText(`${this.constructor.name}`, this.x, this.y - 5);
|
|
1081
|
+
ctx.restore();
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
comp.markDirty();
|
|
1085
|
+
|
|
1086
|
+
// Restaurer après 2 secondes
|
|
1087
|
+
setTimeout(() => {
|
|
1088
|
+
comp.draw = originalDraw;
|
|
1089
|
+
comp.markDirty();
|
|
1090
|
+
}, 2000);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Bascule la visibilité de tous les composants
|
|
1095
|
+
*/
|
|
1096
|
+
toggleAllComponents() {
|
|
1097
|
+
const allVisible = this.framework.components.every(c => c.visible);
|
|
1098
|
+
this.framework.components.forEach(comp => {
|
|
1099
|
+
comp.visible = !allVisible;
|
|
1100
|
+
comp.markDirty();
|
|
1101
|
+
});
|
|
1102
|
+
this.updateComponentsPanel();
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Bascule la visibilité d'un composant
|
|
1107
|
+
*/
|
|
1108
|
+
toggleComponentVisibility(index, visible) {
|
|
1109
|
+
if (index >= 0 && index < this.framework.components.length) {
|
|
1110
|
+
const comp = this.framework.components[index];
|
|
1111
|
+
comp.visible = visible;
|
|
1112
|
+
comp.markDirty();
|
|
1113
|
+
this.updateComponentsPanel();
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Rafraîchit la liste des composants
|
|
1119
|
+
*/
|
|
1120
|
+
refreshComponents() {
|
|
1121
|
+
this.updateComponentsPanel();
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Démarre la surveillance des performances
|
|
1126
|
+
*/
|
|
1127
|
+
startMonitoring() {
|
|
1128
|
+
this.monitorInterval = setInterval(() => {
|
|
1129
|
+
this.updatePerformancePanel();
|
|
1130
|
+
}, 1000);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Arrête la surveillance des performances
|
|
1135
|
+
*/
|
|
1136
|
+
stopMonitoring() {
|
|
1137
|
+
if (this.monitorInterval) {
|
|
1138
|
+
clearInterval(this.monitorInterval);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Nettoie les références et restaure le framework
|
|
1144
|
+
*/
|
|
1145
|
+
cleanup() {
|
|
1146
|
+
// Restaurer les méthodes originales si elles existent
|
|
1147
|
+
if (this.framework) {
|
|
1148
|
+
if (this.originalNavigate) {
|
|
1149
|
+
this.framework.navigate = this.originalNavigate;
|
|
1150
|
+
this.originalNavigate = null;
|
|
1151
|
+
}
|
|
1152
|
+
if (this.originalAdd) {
|
|
1153
|
+
this.framework.add = this.originalAdd;
|
|
1154
|
+
this.originalAdd = null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Supprimer la référence globale
|
|
1159
|
+
if (window.devTools === this) {
|
|
1160
|
+
delete window.devTools;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Masquer le DevTools
|
|
1164
|
+
if (this.isOpen) {
|
|
1165
|
+
this.toggle();
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Masquer le bouton flottant
|
|
1169
|
+
if (this.toggleBtn) {
|
|
1170
|
+
this.toggleBtn.style.display = 'none';
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Nettoyer les écouteurs d'événements
|
|
1174
|
+
if (this.closeBtn) {
|
|
1175
|
+
this.closeBtn.onclick = null;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (this.toggleBtn) {
|
|
1179
|
+
this.toggleBtn.onclick = null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Nettoyer les tabs
|
|
1183
|
+
this.tabs.querySelectorAll('button').forEach(tab => {
|
|
1184
|
+
tab.onclick = null;
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Enregistre le DevTools dans le framework
|
|
1190
|
+
*/
|
|
1191
|
+
attachToFramework() {
|
|
1192
|
+
window.devTools = this;
|
|
1193
|
+
|
|
1194
|
+
// Sauvegarder les références originales
|
|
1195
|
+
this.originalNavigate = this.framework.navigate;
|
|
1196
|
+
this.originalAdd = this.framework.add;
|
|
1197
|
+
|
|
1198
|
+
// Intercepter les événements du framework
|
|
1199
|
+
this.framework.navigate = (...args) => {
|
|
1200
|
+
if (window.devTools) {
|
|
1201
|
+
window.devTools.logEvent('NAVIGATE', { path: args[0] });
|
|
1202
|
+
}
|
|
1203
|
+
return this.originalNavigate.apply(this.framework, args);
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// Intercepter les ajouts de composants
|
|
1207
|
+
this.framework.add = (...args) => {
|
|
1208
|
+
if (window.devTools) {
|
|
1209
|
+
window.devTools.logEvent('ADD_COMPONENT', {
|
|
1210
|
+
type: args[0]?.constructor?.name
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
return this.originalAdd.apply(this.framework, args);
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Détache le DevTools du framework
|
|
1219
|
+
*/
|
|
1220
|
+
detachFromFramework() {
|
|
1221
|
+
// Restaurer les méthodes originales
|
|
1222
|
+
if (this.originalNavigate && this.framework) {
|
|
1223
|
+
this.framework.navigate = this.originalNavigate;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (this.originalAdd && this.framework) {
|
|
1227
|
+
this.framework.add = this.originalAdd;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Supprimer la référence globale
|
|
1231
|
+
if (window.devTools === this) {
|
|
1232
|
+
delete window.devTools;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Masquer le DevTools s'il est ouvert
|
|
1236
|
+
if (this.isOpen) {
|
|
1237
|
+
this.toggle();
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Masquer le bouton flottant
|
|
1241
|
+
if (this.toggleBtn) {
|
|
1242
|
+
this.toggleBtn.style.display = 'none';
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
export default DevTools;
|