canvasframework 0.5.18 → 0.5.20

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.
Files changed (113) hide show
  1. package/README.md +30 -0
  2. package/components/Accordion.js +265 -0
  3. package/components/AndroidDatePickerDialog.js +406 -0
  4. package/components/AppBar.js +398 -0
  5. package/components/AudioPlayer.js +611 -0
  6. package/components/Avatar.js +202 -0
  7. package/components/Banner.js +342 -0
  8. package/components/BottomNavigationBar.js +433 -0
  9. package/components/BottomSheet.js +234 -0
  10. package/components/Button.js +358 -0
  11. package/components/Camera.js +644 -0
  12. package/components/Card.js +193 -0
  13. package/components/Chart.js +700 -0
  14. package/components/Checkbox.js +166 -0
  15. package/components/Chip.js +212 -0
  16. package/components/CircularProgress.js +327 -0
  17. package/components/ContextMenu.js +116 -0
  18. package/components/DatePicker.js +298 -0
  19. package/components/Dialog.js +337 -0
  20. package/components/Divider.js +125 -0
  21. package/components/Drawer.js +276 -0
  22. package/components/FAB.js +270 -0
  23. package/components/FileUpload.js +315 -0
  24. package/components/FloatedCamera.js +644 -0
  25. package/components/IOSDatePickerWheel.js +430 -0
  26. package/components/ImageCarousel.js +219 -0
  27. package/components/ImageComponent.js +223 -0
  28. package/components/Input.js +831 -0
  29. package/components/InputDatalist.js +723 -0
  30. package/components/InputTags.js +624 -0
  31. package/components/List.js +95 -0
  32. package/components/ListItem.js +269 -0
  33. package/components/Modal.js +364 -0
  34. package/components/MorphingFAB.js +428 -0
  35. package/components/MultiSelectDialog.js +206 -0
  36. package/components/NumberInput.js +271 -0
  37. package/components/PasswordInput.js +462 -0
  38. package/components/ProgressBar.js +88 -0
  39. package/components/QRCodeReader.js +539 -0
  40. package/components/RadioButton.js +151 -0
  41. package/components/SearchInput.js +315 -0
  42. package/components/SegmentedControl.js +357 -0
  43. package/components/Select.js +199 -0
  44. package/components/SelectDialog.js +255 -0
  45. package/components/Slider.js +113 -0
  46. package/components/SliverAppBar.js +139 -0
  47. package/components/Snackbar.js +243 -0
  48. package/components/SpeedDialFAB.js +397 -0
  49. package/components/Stepper.js +281 -0
  50. package/components/SwipeableListItem.js +327 -0
  51. package/components/Switch.js +147 -0
  52. package/components/Table.js +492 -0
  53. package/components/Tabs.js +423 -0
  54. package/components/Text.js +141 -0
  55. package/components/TextField.js +151 -0
  56. package/components/TimePicker.js +934 -0
  57. package/components/Toast.js +236 -0
  58. package/components/TreeView.js +420 -0
  59. package/components/Video.js +397 -0
  60. package/components/View.js +140 -0
  61. package/components/VirtualList.js +120 -0
  62. package/core/CanvasFramework.js +3045 -0
  63. package/core/Component.js +243 -0
  64. package/core/ThemeManager.js +358 -0
  65. package/core/UIBuilder.js +267 -0
  66. package/core/WebGLCanvasAdapter.js +782 -0
  67. package/features/Column.js +43 -0
  68. package/features/Grid.js +47 -0
  69. package/features/LayoutComponent.js +43 -0
  70. package/features/OpenStreetMap.js +310 -0
  71. package/features/Positioned.js +33 -0
  72. package/features/PullToRefresh.js +328 -0
  73. package/features/Row.js +40 -0
  74. package/features/SignaturePad.js +257 -0
  75. package/features/Skeleton.js +193 -0
  76. package/features/Stack.js +21 -0
  77. package/index.js +119 -0
  78. package/manager/AccessibilityManager.js +107 -0
  79. package/manager/ErrorHandler.js +59 -0
  80. package/manager/FeatureFlags.js +60 -0
  81. package/manager/MemoryManager.js +107 -0
  82. package/manager/PerformanceMonitor.js +84 -0
  83. package/manager/SecurityManager.js +54 -0
  84. package/package.json +22 -16
  85. package/utils/AnimationEngine.js +734 -0
  86. package/utils/CryptoManager.js +303 -0
  87. package/utils/DataStore.js +403 -0
  88. package/utils/DevTools.js +1618 -0
  89. package/utils/DevToolsConsole.js +201 -0
  90. package/utils/EventBus.js +407 -0
  91. package/utils/FetchClient.js +74 -0
  92. package/utils/FirebaseAuth.js +653 -0
  93. package/utils/FirebaseCore.js +246 -0
  94. package/utils/FirebaseFirestore.js +581 -0
  95. package/utils/FirebaseFunctions.js +97 -0
  96. package/utils/FirebaseRealtimeDB.js +498 -0
  97. package/utils/FirebaseStorage.js +612 -0
  98. package/utils/FormValidator.js +355 -0
  99. package/utils/GeoLocationService.js +62 -0
  100. package/utils/I18n.js +207 -0
  101. package/utils/IndexedDBManager.js +273 -0
  102. package/utils/InspectionOverlay.js +308 -0
  103. package/utils/NotificationManager.js +60 -0
  104. package/utils/OfflineSyncManager.js +342 -0
  105. package/utils/PayPalPayment.js +678 -0
  106. package/utils/QueryBuilder.js +478 -0
  107. package/utils/SafeArea.js +64 -0
  108. package/utils/SecureStorage.js +289 -0
  109. package/utils/StateManager.js +207 -0
  110. package/utils/StripePayment.js +552 -0
  111. package/utils/WebSocketClient.js +66 -0
  112. package/dist/canvasframework.js +0 -2
  113. package/dist/canvasframework.js.LICENSE.txt +0 -1
@@ -0,0 +1,1618 @@
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
+ // Dans le constructeur, ajoutez :
23
+ this.consoleLogs = [];
24
+ this.maxConsoleLogs = 100;
25
+ this.consoleFilters = {
26
+ log: true,
27
+ warn: true,
28
+ error: true,
29
+ debug: true,
30
+ info: true
31
+ };
32
+ this.setupUI();
33
+ }
34
+
35
+ /**
36
+ * Configure l'interface du DevTools
37
+ */
38
+ setupUI() {
39
+ // Créer le conteneur principal
40
+ this.container = document.createElement('div');
41
+ this.container.style.cssText = `
42
+ position: fixed;
43
+ top: 0;
44
+ right: 0;
45
+ width: 400px;
46
+ height: 100vh;
47
+ background: #1e1e1e;
48
+ color: #fff;
49
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
50
+ font-size: 12px;
51
+ z-index: 9999;
52
+ transform: translateX(100%);
53
+ transition: transform 0.3s ease;
54
+ display: flex;
55
+ flex-direction: column;
56
+ box-shadow: -2px 0 10px rgba(0,0,0,0.5);
57
+ `;
58
+
59
+ // Header
60
+ this.header = document.createElement('div');
61
+ this.header.style.cssText = `
62
+ background: #252526;
63
+ padding: 10px;
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ border-bottom: 1px solid #333;
68
+ user-select: none;
69
+ `;
70
+
71
+ this.title = document.createElement('div');
72
+ this.title.textContent = 'Canvas Framework DevTools';
73
+ this.title.style.fontWeight = 'bold';
74
+
75
+ this.closeBtn = document.createElement('button');
76
+ this.closeBtn.textContent = '×';
77
+ this.closeBtn.style.cssText = `
78
+ background: transparent;
79
+ border: none;
80
+ color: #fff;
81
+ font-size: 20px;
82
+ cursor: pointer;
83
+ padding: 0 8px;
84
+ `;
85
+ this.closeBtn.onclick = () => this.toggle();
86
+
87
+ this.header.appendChild(this.title);
88
+ this.header.appendChild(this.closeBtn);
89
+
90
+ // Tabs
91
+ this.tabs = document.createElement('div');
92
+ this.tabs.style.cssText = `
93
+ display: flex;
94
+ background: #2d2d2d;
95
+ border-bottom: 1px solid #333;
96
+ overflow-x: auto;
97
+ overflow-y: hidden;
98
+ scrollbar-width: thin;
99
+ scrollbar-color: #555 #2d2d2d;
100
+ `;
101
+
102
+ // Ajouter un style pour la scrollbar webkit (Chrome, Safari, Edge)
103
+ const style = document.createElement('style');
104
+ style.textContent = `
105
+ #devtools-tabs::-webkit-scrollbar {
106
+ height: 6px;
107
+ }
108
+
109
+ #devtools-tabs::-webkit-scrollbar-track {
110
+ background: #2d2d2d;
111
+ }
112
+
113
+ #devtools-tabs::-webkit-scrollbar-thumb {
114
+ background: #555;
115
+ border-radius: 3px;
116
+ }
117
+
118
+ #devtools-tabs::-webkit-scrollbar-thumb:hover {
119
+ background: #666;
120
+ }
121
+ `;
122
+ document.head.appendChild(style);
123
+
124
+ // Ajouter un ID pour cibler avec le CSS
125
+ this.tabs.id = 'devtools-tabs';
126
+
127
+ const tabConfigs = [
128
+ { id: 'components', label: 'Composants' },
129
+ { id: 'performance', label: 'Performance' },
130
+ { id: 'hierarchy', label: 'Hiérarchie' },
131
+ { id: 'properties', label: 'Propriétés' },
132
+ { id: 'events', label: 'Événements' },
133
+ { id: 'routing', label: 'Routing' },
134
+ { id: 'console', label: 'Console' } // <-- NOUVEAU
135
+ ];
136
+
137
+ tabConfigs.forEach(config => {
138
+ const tab = document.createElement('button');
139
+ tab.textContent = config.label;
140
+ tab.dataset.tab = config.id;
141
+ tab.style.cssText = `
142
+ flex: 1;
143
+ padding: 8px;
144
+ background: transparent;
145
+ border: none;
146
+ color: #ccc;
147
+ cursor: pointer;
148
+ border-bottom: 2px solid transparent;
149
+ `;
150
+ tab.onclick = () => this.switchTab(config.id);
151
+ this.tabs.appendChild(tab);
152
+ });
153
+
154
+ // Content area
155
+ this.content = document.createElement('div');
156
+ this.content.style.cssText = `
157
+ flex: 1;
158
+ overflow: auto;
159
+ padding: 10px;
160
+ `;
161
+
162
+ // Create panels
163
+ this.createComponentsPanel();
164
+ this.createPerformancePanel();
165
+ this.createHierarchyPanel();
166
+ this.createPropertiesPanel();
167
+ this.createEventsPanel();
168
+ this.createRoutingPanel();
169
+ // Ajoutez après createRoutingPanel() :
170
+ this.createConsolePanel();
171
+
172
+ // Assembler le conteneur
173
+ this.container.appendChild(this.header);
174
+ this.container.appendChild(this.tabs);
175
+ this.container.appendChild(this.content);
176
+
177
+ document.body.appendChild(this.container);
178
+
179
+ // Bouton toggle flottant
180
+ this.toggleBtn = document.createElement('button');
181
+ this.toggleBtn.textContent = 'DevTools';
182
+ this.toggleBtn.style.cssText = `
183
+ position: fixed;
184
+ bottom: 20px;
185
+ right: 20px;
186
+ background: #007acc;
187
+ color: white;
188
+ border: none;
189
+ border-radius: 4px;
190
+ padding: 8px 12px;
191
+ font-size: 12px;
192
+ cursor: pointer;
193
+ z-index: 10000;
194
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
195
+ `;
196
+ this.toggleBtn.onclick = () => this.toggle();
197
+ document.body.appendChild(this.toggleBtn);
198
+
199
+ // Setup hotkeys
200
+ this.setupHotkeys();
201
+ }
202
+
203
+ /**
204
+ * Crée le panneau des composants
205
+ */
206
+ createComponentsPanel() {
207
+ const panel = document.createElement('div');
208
+ panel.id = 'components-panel';
209
+ panel.style.display = 'none';
210
+
211
+ // Stats
212
+ const stats = document.createElement('div');
213
+ stats.style.cssText = `
214
+ background: #252526;
215
+ padding: 8px;
216
+ border-radius: 4px;
217
+ margin-bottom: 10px;
218
+ `;
219
+ stats.innerHTML = `
220
+ <div style="display: flex; justify-content: space-between;">
221
+ <span>Total: <span id="component-count">0</span></span>
222
+ <span>Visibles: <span id="visible-count">0</span></span>
223
+ <span>Dirty: <span id="dirty-count">0</span></span>
224
+ </div>
225
+ `;
226
+
227
+ // Filter
228
+ const filter = document.createElement('input');
229
+ filter.type = 'text';
230
+ filter.placeholder = 'Filtrer les composants...';
231
+ filter.style.cssText = `
232
+ width: 100%;
233
+ padding: 6px;
234
+ margin-bottom: 10px;
235
+ background: #333;
236
+ border: 1px solid #555;
237
+ color: #fff;
238
+ border-radius: 4px;
239
+ `;
240
+
241
+ // Component list
242
+ const list = document.createElement('div');
243
+ list.id = 'component-list';
244
+ list.style.cssText = `
245
+ max-height: 400px;
246
+ overflow-y: auto;
247
+ border: 1px solid #333;
248
+ border-radius: 4px;
249
+ `;
250
+
251
+ // Controls
252
+ const controls = document.createElement('div');
253
+ controls.style.cssText = `
254
+ display: flex;
255
+ gap: 8px;
256
+ margin-top: 10px;
257
+ `;
258
+
259
+ const toggleAllBtn = document.createElement('button');
260
+ toggleAllBtn.textContent = 'Toggle All';
261
+ toggleAllBtn.style.cssText = `
262
+ flex: 1;
263
+ padding: 6px;
264
+ background: #555;
265
+ color: #fff;
266
+ border: none;
267
+ border-radius: 4px;
268
+ cursor: pointer;
269
+ `;
270
+ toggleAllBtn.onclick = () => this.toggleAllComponents();
271
+
272
+ const refreshBtn = document.createElement('button');
273
+ refreshBtn.textContent = 'Refresh';
274
+ refreshBtn.style.cssText = toggleAllBtn.style.cssText;
275
+ refreshBtn.onclick = () => this.refreshComponents();
276
+
277
+ controls.appendChild(toggleAllBtn);
278
+ controls.appendChild(refreshBtn);
279
+
280
+ panel.appendChild(stats);
281
+ panel.appendChild(filter);
282
+ panel.appendChild(list);
283
+ panel.appendChild(controls);
284
+
285
+ this.content.appendChild(panel);
286
+ this.panels.components = panel;
287
+ }
288
+
289
+ /**
290
+ * Crée le panneau de performance
291
+ */
292
+ createPerformancePanel() {
293
+ const panel = document.createElement('div');
294
+ panel.id = 'performance-panel';
295
+ panel.style.display = 'none';
296
+
297
+ // FPS Chart
298
+ const fpsSection = document.createElement('div');
299
+ fpsSection.style.marginBottom = '20px';
300
+
301
+ const fpsTitle = document.createElement('h3');
302
+ fpsTitle.textContent = 'FPS';
303
+ fpsTitle.style.marginBottom = '8px';
304
+
305
+ const fpsCanvas = document.createElement('canvas');
306
+ fpsCanvas.width = 380;
307
+ fpsCanvas.height = 100;
308
+ fpsCanvas.style.cssText = `
309
+ width: 100%;
310
+ height: 100px;
311
+ background: #252526;
312
+ border-radius: 4px;
313
+ `;
314
+
315
+ fpsSection.appendChild(fpsTitle);
316
+ fpsSection.appendChild(fpsCanvas);
317
+
318
+ // Memory Stats
319
+ const memorySection = document.createElement('div');
320
+ memorySection.style.marginBottom = '20px';
321
+
322
+ const memoryTitle = document.createElement('h3');
323
+ memoryTitle.textContent = 'Mémoire';
324
+ memoryTitle.style.marginBottom = '8px';
325
+
326
+ const memoryInfo = document.createElement('div');
327
+ memoryInfo.id = 'memory-info';
328
+ memoryInfo.style.cssText = `
329
+ background: #252526;
330
+ padding: 8px;
331
+ border-radius: 4px;
332
+ font-family: monospace;
333
+ `;
334
+
335
+ memorySection.appendChild(memoryTitle);
336
+ memorySection.appendChild(memoryInfo);
337
+
338
+ // Performance Tips
339
+ const tipsSection = document.createElement('div');
340
+
341
+ const tipsTitle = document.createElement('h3');
342
+ tipsTitle.textContent = 'Conseils d\'optimisation';
343
+ tipsTitle.style.marginBottom = '8px';
344
+
345
+ const tipsList = document.createElement('ul');
346
+ tipsList.style.cssText = `
347
+ padding-left: 20px;
348
+ color: #aaa;
349
+ `;
350
+ tipsList.innerHTML = `
351
+ <li>Activer l'optimizationEnabled</li>
352
+ <li>Réduire le nombre de composants visibles</li>
353
+ <li>Utiliser VirtualList pour les longues listes</li>
354
+ <li>Éviter les animations sur trop de composants</li>
355
+ `;
356
+
357
+ tipsSection.appendChild(tipsTitle);
358
+ tipsSection.appendChild(tipsList);
359
+
360
+ panel.appendChild(fpsSection);
361
+ panel.appendChild(memorySection);
362
+ panel.appendChild(tipsSection);
363
+
364
+ this.content.appendChild(panel);
365
+ this.panels.performance = panel;
366
+ this.fpsCanvas = fpsCanvas;
367
+ }
368
+
369
+ /**
370
+ * Crée le panneau hiérarchique
371
+ */
372
+ createHierarchyPanel() {
373
+ const panel = document.createElement('div');
374
+ panel.id = 'hierarchy-panel';
375
+ panel.style.display = 'none';
376
+
377
+ const tree = document.createElement('div');
378
+ tree.id = 'component-tree';
379
+ tree.style.cssText = `
380
+ font-family: monospace;
381
+ line-height: 1.4;
382
+ `;
383
+
384
+ panel.appendChild(tree);
385
+ this.content.appendChild(panel);
386
+ this.panels.hierarchy = panel;
387
+ }
388
+
389
+ /**
390
+ * Crée le panneau des propriétés
391
+ */
392
+ createPropertiesPanel() {
393
+ const panel = document.createElement('div');
394
+ panel.id = 'properties-panel';
395
+ panel.style.display = 'none';
396
+
397
+ const noSelection = document.createElement('div');
398
+ noSelection.id = 'no-selection';
399
+ noSelection.textContent = 'Sélectionnez un composant pour voir ses propriétés';
400
+ noSelection.style.color = '#888';
401
+
402
+ const propertiesTable = document.createElement('div');
403
+ propertiesTable.id = 'properties-table';
404
+ propertiesTable.style.display = 'none';
405
+
406
+ panel.appendChild(noSelection);
407
+ panel.appendChild(propertiesTable);
408
+ this.content.appendChild(panel);
409
+ this.panels.properties = panel;
410
+ }
411
+
412
+ /**
413
+ * Crée le panneau des événements
414
+ */
415
+ createEventsPanel() {
416
+ const panel = document.createElement('div');
417
+ panel.id = 'events-panel';
418
+ panel.style.display = 'none';
419
+
420
+ const eventsList = document.createElement('div');
421
+ eventsList.id = 'events-list';
422
+ eventsList.style.cssText = `
423
+ max-height: 400px;
424
+ overflow-y: auto;
425
+ border: 1px solid #333;
426
+ border-radius: 4px;
427
+ padding: 8px;
428
+ `;
429
+
430
+ const clearBtn = document.createElement('button');
431
+ clearBtn.textContent = 'Clear Events';
432
+ clearBtn.style.cssText = `
433
+ margin-top: 10px;
434
+ padding: 6px 12px;
435
+ background: #555;
436
+ color: #fff;
437
+ border: none;
438
+ border-radius: 4px;
439
+ cursor: pointer;
440
+ `;
441
+ clearBtn.onclick = () => this.clearEvents();
442
+
443
+ panel.appendChild(eventsList);
444
+ panel.appendChild(clearBtn);
445
+ this.content.appendChild(panel);
446
+ this.panels.events = panel;
447
+ this.eventsList = eventsList;
448
+ }
449
+
450
+ /**
451
+ * Crée le panneau de routing
452
+ */
453
+ createRoutingPanel() {
454
+ const panel = document.createElement('div');
455
+ panel.id = 'routing-panel';
456
+ panel.style.display = 'none';
457
+
458
+ const routeInfo = document.createElement('div');
459
+ routeInfo.id = 'route-info';
460
+ routeInfo.style.cssText = `
461
+ background: #252526;
462
+ padding: 10px;
463
+ border-radius: 4px;
464
+ margin-bottom: 10px;
465
+ `;
466
+
467
+ const historySection = document.createElement('div');
468
+
469
+ const historyTitle = document.createElement('h3');
470
+ historyTitle.textContent = 'Historique de navigation';
471
+ historyTitle.style.marginBottom = '8px';
472
+
473
+ const historyList = document.createElement('div');
474
+ historyList.id = 'history-list';
475
+ historyList.style.cssText = `
476
+ max-height: 200px;
477
+ overflow-y: auto;
478
+ border: 1px solid #333;
479
+ border-radius: 4px;
480
+ padding: 8px;
481
+ `;
482
+
483
+ historySection.appendChild(historyTitle);
484
+ historySection.appendChild(historyList);
485
+
486
+ panel.appendChild(routeInfo);
487
+ panel.appendChild(historySection);
488
+
489
+ this.content.appendChild(panel);
490
+ this.panels.routing = panel;
491
+ }
492
+
493
+ /**
494
+ * Crée le panneau de console
495
+ */
496
+ createConsolePanel() {
497
+ const panel = document.createElement('div');
498
+ panel.id = 'console-panel';
499
+ panel.style.display = 'none';
500
+
501
+ // Filtres
502
+ const filters = document.createElement('div');
503
+ filters.style.cssText = `
504
+ display: flex;
505
+ gap: 10px;
506
+ padding: 8px;
507
+ background: #252526;
508
+ border-radius: 4px;
509
+ margin-bottom: 10px;
510
+ flex-wrap: wrap;
511
+ `;
512
+
513
+ const filterTypes = [
514
+ { type: 'log', label: 'Log', color: '#d4d4d4' },
515
+ { type: 'info', label: 'Info', color: '#4ec9b0' },
516
+ { type: 'warn', label: 'Warn', color: '#dcdcaa' },
517
+ { type: 'error', label: 'Error', color: '#f48771' },
518
+ { type: 'debug', label: 'Debug', color: '#569cd6' }
519
+ ];
520
+
521
+ filterTypes.forEach(({ type, label, color }) => {
522
+ const filterBtn = document.createElement('button');
523
+ filterBtn.textContent = label;
524
+ filterBtn.dataset.type = type;
525
+ filterBtn.style.cssText = `
526
+ padding: 4px 12px;
527
+ background: ${this.consoleFilters[type] ? color : '#555'};
528
+ color: ${this.consoleFilters[type] ? '#000' : '#aaa'};
529
+ border: none;
530
+ border-radius: 3px;
531
+ cursor: pointer;
532
+ font-size: 11px;
533
+ font-weight: bold;
534
+ transition: all 0.2s;
535
+ `;
536
+
537
+ filterBtn.onclick = () => {
538
+ this.consoleFilters[type] = !this.consoleFilters[type];
539
+ filterBtn.style.background = this.consoleFilters[type] ? color : '#555';
540
+ filterBtn.style.color = this.consoleFilters[type] ? '#000' : '#aaa';
541
+ this.updateConsolePanel();
542
+ };
543
+
544
+ filters.appendChild(filterBtn);
545
+ });
546
+
547
+ // Liste des logs
548
+ const consoleList = document.createElement('div');
549
+ consoleList.id = 'console-list';
550
+ consoleList.style.cssText = `
551
+ max-height: 400px;
552
+ overflow-y: auto;
553
+ border: 1px solid #333;
554
+ border-radius: 4px;
555
+ background: #1e1e1e;
556
+ font-family: 'Consolas', 'Monaco', monospace;
557
+ font-size: 11px;
558
+ `;
559
+
560
+ // Contrôles
561
+ const controls = document.createElement('div');
562
+ controls.style.cssText = `
563
+ display: flex;
564
+ gap: 8px;
565
+ margin-top: 10px;
566
+ `;
567
+
568
+ const clearBtn = document.createElement('button');
569
+ clearBtn.textContent = 'Clear Console';
570
+ clearBtn.style.cssText = `
571
+ flex: 1;
572
+ padding: 6px;
573
+ background: #555;
574
+ color: #fff;
575
+ border: none;
576
+ border-radius: 4px;
577
+ cursor: pointer;
578
+ `;
579
+ clearBtn.onclick = () => this.clearConsole();
580
+
581
+ const preserveLogCheckbox = document.createElement('label');
582
+ preserveLogCheckbox.style.cssText = `
583
+ display: flex;
584
+ align-items: center;
585
+ gap: 5px;
586
+ color: #ccc;
587
+ cursor: pointer;
588
+ `;
589
+ preserveLogCheckbox.innerHTML = `
590
+ <input type="checkbox" id="preserve-log">
591
+ <span>Preserve log</span>
592
+ `;
593
+
594
+ controls.appendChild(clearBtn);
595
+ controls.appendChild(preserveLogCheckbox);
596
+
597
+ panel.appendChild(filters);
598
+ panel.appendChild(consoleList);
599
+ panel.appendChild(controls);
600
+
601
+ this.content.appendChild(panel);
602
+ this.panels.console = panel;
603
+ this.consoleList = consoleList;
604
+ }
605
+
606
+ /**
607
+ * Met à jour le panneau console
608
+ */
609
+ updateConsolePanel() {
610
+ if (!this.isOpen || this.currentTab !== 'console') return;
611
+
612
+ this.consoleList.innerHTML = '';
613
+
614
+ this.consoleLogs
615
+ .filter(log => this.consoleFilters[log.type])
616
+ .forEach(log => {
617
+ const logItem = document.createElement('div');
618
+ logItem.style.cssText = `
619
+ padding: 6px 10px;
620
+ border-bottom: 1px solid #2d2d2d;
621
+ display: flex;
622
+ align-items: flex-start;
623
+ gap: 8px;
624
+ `;
625
+
626
+ const colors = {
627
+ log: '#d4d4d4',
628
+ info: '#4ec9b0',
629
+ warn: '#dcdcaa',
630
+ error: '#f48771',
631
+ debug: '#569cd6'
632
+ };
633
+
634
+ const icons = {
635
+ log: '○',
636
+ info: 'ℹ',
637
+ warn: '⚠',
638
+ error: '✖',
639
+ debug: '⚙'
640
+ };
641
+
642
+ const timestamp = document.createElement('span');
643
+ timestamp.textContent = log.timestamp;
644
+ timestamp.style.cssText = `
645
+ color: #888;
646
+ font-size: 10px;
647
+ min-width: 80px;
648
+ `;
649
+
650
+ const icon = document.createElement('span');
651
+ icon.textContent = icons[log.type];
652
+ icon.style.cssText = `
653
+ color: ${colors[log.type]};
654
+ min-width: 20px;
655
+ font-weight: bold;
656
+ `;
657
+
658
+ const message = document.createElement('div');
659
+ message.style.cssText = `
660
+ flex: 1;
661
+ color: ${colors[log.type]};
662
+ word-break: break-word;
663
+ `;
664
+
665
+ // Formater le message
666
+ if (typeof log.args[0] === 'object') {
667
+ // Fonction pour gérer les références circulaires
668
+ const safeStringify = (obj, indent = 2) => {
669
+ const seen = new WeakSet();
670
+ return JSON.stringify(obj, (key, value) => {
671
+ // Ignorer les propriétés framework pour éviter les cycles
672
+ if (key === 'framework') return '[CanvasFramework]';
673
+
674
+ if (typeof value === 'object' && value !== null) {
675
+ // Détection de cycle
676
+ if (seen.has(value)) {
677
+ return '[Circular]';
678
+ }
679
+ seen.add(value);
680
+
681
+ // Limiter la profondeur pour les gros objets
682
+ if (key === 'components' && Array.isArray(value)) {
683
+ return `[Array(${value.length})]`;
684
+ }
685
+ }
686
+ return value;
687
+ }, indent);
688
+ };
689
+
690
+ try {
691
+ message.innerHTML = `<pre style="margin: 0;">${safeStringify(log.args[0])}</pre>`;
692
+ } catch (e) {
693
+ message.textContent = '[Objet complexe non affichable]';
694
+ }
695
+ } else {
696
+ message.textContent = log.args.map(arg => {
697
+ if (typeof arg === 'object' && arg !== null) {
698
+ // Pour les objets dans les arguments
699
+ const seen = new WeakSet();
700
+ try {
701
+ return JSON.stringify(arg, (key, value) => {
702
+ if (key === 'framework') return '[Framework]';
703
+ if (typeof value === 'object' && value !== null) {
704
+ if (seen.has(value)) return '[Circular]';
705
+ seen.add(value);
706
+ }
707
+ return value;
708
+ });
709
+ } catch (e) {
710
+ return '[Object]';
711
+ }
712
+ }
713
+ return String(arg);
714
+ }).join(' ');
715
+ }
716
+
717
+ // Stack trace pour les erreurs
718
+ if (log.type === 'error' && log.stack) {
719
+ const stackTrace = document.createElement('div');
720
+ stackTrace.style.cssText = `
721
+ margin-top: 5px;
722
+ padding: 5px;
723
+ background: #2d2d2d;
724
+ border-radius: 3px;
725
+ font-size: 10px;
726
+ color: #888;
727
+ max-height: 100px;
728
+ overflow-y: auto;
729
+ `;
730
+ stackTrace.textContent = log.stack;
731
+ message.appendChild(stackTrace);
732
+ }
733
+
734
+ logItem.appendChild(timestamp);
735
+ logItem.appendChild(icon);
736
+ logItem.appendChild(message);
737
+
738
+ this.consoleList.appendChild(logItem);
739
+ });
740
+
741
+ // Auto-scroll vers le bas
742
+ this.consoleList.scrollTop = this.consoleList.scrollHeight;
743
+ }
744
+
745
+ /**
746
+ * Ajoute un log à la console
747
+ */
748
+ addConsoleLog(type, args, stack = null) {
749
+ const timestamp = new Date().toLocaleTimeString();
750
+
751
+ this.consoleLogs.push({
752
+ type,
753
+ args,
754
+ timestamp,
755
+ stack
756
+ });
757
+
758
+ // Limiter le nombre de logs
759
+ if (this.consoleLogs.length > this.maxConsoleLogs) {
760
+ this.consoleLogs.shift();
761
+ }
762
+
763
+ if (this.isOpen && this.currentTab === 'console') {
764
+ this.updateConsolePanel();
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Vide la console
770
+ */
771
+ clearConsole() {
772
+ const preserveLog = document.getElementById('preserve-log')?.checked;
773
+
774
+ if (!preserveLog) {
775
+ this.consoleLogs = [];
776
+ this.updateConsolePanel();
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Intercepte les méthodes console natives
782
+ */
783
+ interceptConsole() {
784
+ const originalConsole = {
785
+ log: console.log,
786
+ info: console.info,
787
+ warn: console.warn,
788
+ error: console.error,
789
+ debug: console.debug
790
+ };
791
+
792
+ ['log', 'info', 'warn', 'error', 'debug'].forEach(method => {
793
+ console[method] = (...args) => {
794
+ // Appeler la méthode originale
795
+ originalConsole[method].apply(console, args);
796
+
797
+ // Ajouter au DevTools
798
+ const stack = method === 'error' ? new Error().stack : null;
799
+ this.addConsoleLog(method, args, stack);
800
+ };
801
+ });
802
+
803
+ // Sauvegarder pour restauration
804
+ this.originalConsole = originalConsole;
805
+ }
806
+
807
+ /**
808
+ * Restaure les méthodes console natives
809
+ */
810
+ restoreConsole() {
811
+ if (this.originalConsole) {
812
+ Object.assign(console, this.originalConsole);
813
+ }
814
+ }
815
+
816
+ /**
817
+ * Configure les raccourcis clavier
818
+ */
819
+ setupHotkeys() {
820
+ document.addEventListener('keydown', (e) => {
821
+ if (e.ctrlKey && e.shiftKey && e.key === 'D') {
822
+ e.preventDefault();
823
+ this.toggle();
824
+ }
825
+
826
+ // Permet d'inspecter les composants avec Ctrl+Click
827
+ this.framework.canvas.addEventListener('click', (e) => {
828
+ if (e.ctrlKey && this.isOpen) {
829
+ e.preventDefault();
830
+ const rect = this.framework.canvas.getBoundingClientRect();
831
+ const x = e.clientX - rect.left;
832
+ const y = e.clientY - rect.top;
833
+ this.inspectComponentAt(x, y);
834
+ }
835
+ });
836
+ });
837
+ }
838
+
839
+ /**
840
+ * Bascule l'affichage du DevTools
841
+ */
842
+ toggle() {
843
+ this.isOpen = !this.isOpen;
844
+ if (this.isOpen) {
845
+ this.container.style.transform = 'translateX(0)';
846
+ this.refreshAll();
847
+ this.startMonitoring();
848
+ } else {
849
+ this.container.style.transform = 'translateX(100%)';
850
+ this.stopMonitoring();
851
+ }
852
+ }
853
+
854
+ /**
855
+ * Change l'onglet actif
856
+ */
857
+ switchTab(tabId) {
858
+ this.currentTab = tabId;
859
+
860
+ // Mettre à jour les styles des tabs
861
+ this.tabs.querySelectorAll('button').forEach(btn => {
862
+ btn.style.borderBottomColor = btn.dataset.tab === tabId ? '#007acc' : 'transparent';
863
+ btn.style.color = btn.dataset.tab === tabId ? '#fff' : '#ccc';
864
+ });
865
+
866
+ // Afficher/masquer les panels
867
+ Object.keys(this.panels).forEach(panelId => {
868
+ this.panels[panelId].style.display = panelId === tabId ? 'block' : 'none';
869
+ });
870
+
871
+ if (tabId === 'performance') {
872
+ this.updatePerformancePanel();
873
+ } else if (tabId === 'hierarchy') {
874
+ this.updateHierarchyPanel();
875
+ } else if (tabId === 'components') {
876
+ this.updateComponentsPanel();
877
+ } else if (tabId === 'routing') {
878
+ this.updateRoutingPanel();
879
+ } else if (tabId === 'console') { // <-- NOUVEAU
880
+ this.updateConsolePanel();
881
+ }
882
+ }
883
+
884
+ /**
885
+ * Rafraîchit toutes les données
886
+ */
887
+ refreshAll() {
888
+ this.updateComponentsPanel();
889
+ this.updateHierarchyPanel();
890
+ this.updateRoutingPanel();
891
+ }
892
+
893
+ /**
894
+ * Met à jour le panneau des composants
895
+ */
896
+ updateComponentsPanel() {
897
+ if (!this.isOpen || this.currentTab !== 'components') return;
898
+
899
+ const components = this.framework.components;
900
+ const visibleCount = components.filter(c => c.visible).length;
901
+ const dirtyCount = components.filter(c => c._dirty).length;
902
+
903
+ document.getElementById('component-count').textContent = components.length;
904
+ document.getElementById('visible-count').textContent = visibleCount;
905
+ document.getElementById('dirty-count').textContent = dirtyCount;
906
+
907
+ const list = document.getElementById('component-list');
908
+ list.innerHTML = '';
909
+
910
+ components.forEach((comp, index) => {
911
+ const item = document.createElement('div');
912
+ item.style.cssText = `
913
+ padding: 6px 8px;
914
+ border-bottom: 1px solid #333;
915
+ cursor: pointer;
916
+ display: flex;
917
+ align-items: center;
918
+ background: ${comp === this.selectedComponent ? '#007acc20' : 'transparent'};
919
+ `;
920
+
921
+ const type = comp.constructor.name;
922
+ const bounds = `[${Math.round(comp.x)},${Math.round(comp.y)} ${Math.round(comp.width)}x${Math.round(comp.height)}]`;
923
+
924
+ item.innerHTML = `
925
+ <div style="display: flex; align-items: center; flex: 1;">
926
+ <input type="checkbox" ${comp.visible ? 'checked' : ''}
927
+ style="margin-right: 8px;"
928
+ onchange="event.stopPropagation(); devTools.toggleComponentVisibility(${index}, this.checked)">
929
+ <span style="color: #4ec9b0; min-width: 120px;">${type}</span>
930
+ <span style="color: #ccc; margin-left: 8px;">${bounds}</span>
931
+ ${comp._dirty ? '<span style="color: orange; margin-left: 8px;">●</span>' : ''}
932
+ </div>
933
+ <button onclick="event.stopPropagation(); devTools.highlightComponent(${index})"
934
+ style="background: transparent; border: 1px solid #555; color: #ccc; padding: 2px 6px; border-radius: 3px; font-size: 10px;">
935
+
936
+ </button>
937
+ `;
938
+
939
+ item.onclick = () => this.selectComponent(comp);
940
+ list.appendChild(item);
941
+ });
942
+ }
943
+
944
+ /**
945
+ * Met à jour le panneau de performance
946
+ */
947
+ updatePerformancePanel() {
948
+ if (!this.isOpen || this.currentTab !== 'performance') return;
949
+
950
+ // Mettre à jour les stats FPS
951
+ this.performanceStats.fps.push(this.framework.fps);
952
+ if (this.performanceStats.fps.length > 100) {
953
+ this.performanceStats.fps.shift();
954
+ }
955
+
956
+ // Mettre à jour le graphique FPS
957
+ this.drawFPSChart();
958
+
959
+ // Mettre à jour les infos mémoire
960
+ if (performance.memory) {
961
+ const memory = performance.memory;
962
+ document.getElementById('memory-info').innerHTML = `
963
+ <div>Used: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)} MB</div>
964
+ <div>Total: ${Math.round(memory.totalJSHeapSize / 1024 / 1024)} MB</div>
965
+ <div>Limit: ${Math.round(memory.jsHeapSizeLimit / 1024 / 1024)} MB</div>
966
+ `;
967
+ }
968
+ }
969
+
970
+ /**
971
+ * Dessine le graphique FPS
972
+ */
973
+ drawFPSChart() {
974
+ if (!this.fpsCanvas) return;
975
+
976
+ const ctx = this.fpsCanvas.getContext('2d');
977
+ const width = this.fpsCanvas.width;
978
+ const height = this.fpsCanvas.height;
979
+
980
+ // Clear
981
+ ctx.fillStyle = '#252526';
982
+ ctx.fillRect(0, 0, width, height);
983
+
984
+ // Draw grid
985
+ ctx.strokeStyle = '#333';
986
+ ctx.lineWidth = 1;
987
+
988
+ // Horizontal lines (FPS markers)
989
+ for (let i = 0; i <= 60; i += 15) {
990
+ const y = height - (i / 60 * height);
991
+ ctx.beginPath();
992
+ ctx.moveTo(0, y);
993
+ ctx.lineTo(width, y);
994
+ ctx.stroke();
995
+
996
+ // Labels
997
+ ctx.fillStyle = '#888';
998
+ ctx.font = '10px monospace';
999
+ ctx.fillText(`${i}`, 5, y - 2);
1000
+ }
1001
+
1002
+ // Draw FPS line
1003
+ if (this.performanceStats.fps.length > 1) {
1004
+ ctx.strokeStyle = '#4ec9b0';
1005
+ ctx.lineWidth = 2;
1006
+ ctx.beginPath();
1007
+
1008
+ const step = width / (this.performanceStats.fps.length - 1);
1009
+ this.performanceStats.fps.forEach((fps, i) => {
1010
+ const x = i * step;
1011
+ const y = height - (Math.min(fps, 60) / 60 * height);
1012
+
1013
+ if (i === 0) {
1014
+ ctx.moveTo(x, y);
1015
+ } else {
1016
+ ctx.lineTo(x, y);
1017
+ }
1018
+ });
1019
+
1020
+ ctx.stroke();
1021
+
1022
+ // Current FPS
1023
+ const currentFps = this.performanceStats.fps[this.performanceStats.fps.length - 1] || 0;
1024
+ ctx.fillStyle = currentFps < 30 ? '#ff5555' : '#4ec9b0';
1025
+ ctx.font = 'bold 12px monospace';
1026
+ ctx.textAlign = 'right';
1027
+ ctx.fillText(`${currentFps} FPS`, width - 5, 15);
1028
+ }
1029
+ }
1030
+
1031
+ /**
1032
+ * Met à jour le panneau hiérarchique
1033
+ */
1034
+ updateHierarchyPanel() {
1035
+ if (!this.isOpen || this.currentTab !== 'hierarchy') return;
1036
+
1037
+ const tree = document.getElementById('component-tree');
1038
+ tree.innerHTML = '';
1039
+
1040
+ const renderComponent = (comp, depth = 0) => {
1041
+ const item = document.createElement('div');
1042
+ item.style.cssText = `
1043
+ padding-left: ${depth * 20}px;
1044
+ margin: 2px 0;
1045
+ cursor: pointer;
1046
+ background: ${comp === this.selectedComponent ? '#007acc20' : 'transparent'};
1047
+ `;
1048
+
1049
+ const type = comp.constructor.name;
1050
+ const hasChildren = comp.children && comp.children.length > 0;
1051
+
1052
+ item.innerHTML = `
1053
+ <span style="color: ${comp.visible ? '#4ec9b0' : '#555'}">
1054
+ ${hasChildren ? '▼' : '○'} ${type}
1055
+ <span style="color: #888; font-size: 10px;">
1056
+ (${comp.width}x${comp.height})
1057
+ </span>
1058
+ </span>
1059
+ `;
1060
+
1061
+ item.onclick = () => this.selectComponent(comp);
1062
+ tree.appendChild(item);
1063
+
1064
+ // Rendre les enfants si c'est une Card ou autre conteneur
1065
+ if (hasChildren && comp.children) {
1066
+ // Vérifier si c'est un itérable
1067
+ const childArray = Array.isArray(comp.children) ? comp.children : [];
1068
+
1069
+ childArray.forEach(child => {
1070
+ // Vérifier si c'est un objet avec les propriétés d'un composant
1071
+ if (child && typeof child === 'object' &&
1072
+ 'x' in child && 'y' in child && 'width' in child && 'height' in child) {
1073
+ renderComponent(child, depth + 1);
1074
+ }
1075
+ });
1076
+ }
1077
+ };
1078
+
1079
+ this.framework.components.forEach(comp => {
1080
+ renderComponent(comp);
1081
+ });
1082
+ }
1083
+
1084
+ /**
1085
+ * Met à jour le panneau des propriétés
1086
+ */
1087
+ updatePropertiesPanel() {
1088
+ if (!this.selectedComponent) return;
1089
+
1090
+ const table = document.getElementById('properties-table');
1091
+ const noSelection = document.getElementById('no-selection');
1092
+
1093
+ noSelection.style.display = 'none';
1094
+ table.style.display = 'block';
1095
+ table.innerHTML = '';
1096
+
1097
+ const comp = this.selectedComponent;
1098
+ const props = {};
1099
+
1100
+ // Collecter toutes les propriétés
1101
+ for (let key in comp) {
1102
+ if (key.startsWith('_') || key === 'framework') continue; // <-- EXCLURE 'framework'
1103
+
1104
+ try {
1105
+ const value = comp[key];
1106
+ if (typeof value === 'function') continue;
1107
+
1108
+ // Éviter les références circulaires
1109
+ if (key === 'parent' || key === 'children' || key === 'framework') {
1110
+ props[key] = `[${value?.constructor?.name || 'Object'}]`;
1111
+ continue;
1112
+ }
1113
+
1114
+ // Tenter de stringifier, mais avec un repli sûr
1115
+ props[key] = value;
1116
+ } catch (e) {
1117
+ props[key] = '<error>';
1118
+ }
1119
+ }
1120
+
1121
+ // Créer le tableau
1122
+ Object.entries(props).forEach(([key, value]) => {
1123
+ const row = document.createElement('div');
1124
+ row.style.cssText = `
1125
+ display: flex;
1126
+ padding: 4px 0;
1127
+ border-bottom: 1px solid #333;
1128
+ `;
1129
+
1130
+ const keyCell = document.createElement('div');
1131
+ keyCell.textContent = key;
1132
+ keyCell.style.cssText = `
1133
+ color: #9cdcfe;
1134
+ min-width: 150px;
1135
+ font-family: monospace;
1136
+ `;
1137
+
1138
+ const valueCell = document.createElement('div');
1139
+
1140
+ try {
1141
+ // Fonction pour stringifier en évitant les cycles
1142
+ const safeStringify = (obj, replacer, space) => {
1143
+ const seen = new WeakSet();
1144
+ return JSON.stringify(obj, (key, value) => {
1145
+ if (typeof value === 'object' && value !== null) {
1146
+ if (seen.has(value)) {
1147
+ return '[Circular Reference]';
1148
+ }
1149
+ seen.add(value);
1150
+ }
1151
+ return replacer ? replacer(key, value) : value;
1152
+ }, space);
1153
+ };
1154
+
1155
+ if (typeof value === 'object' && value !== null) {
1156
+ // Exclure certaines propriétés problématiques
1157
+ if (value === comp || value === this.framework) {
1158
+ valueCell.textContent = '[Circular Reference]';
1159
+ } else if (value instanceof HTMLElement) {
1160
+ valueCell.textContent = `[HTMLElement: ${value.tagName}]`;
1161
+ } else if (value instanceof Event) {
1162
+ valueCell.textContent = `[Event: ${value.type}]`;
1163
+ } else if (value instanceof NodeList || value instanceof HTMLCollection) {
1164
+ valueCell.textContent = `[Collection: ${value.length} items]`;
1165
+ } else {
1166
+ try {
1167
+ valueCell.textContent = safeStringify(value, null, 2);
1168
+ } catch (e) {
1169
+ valueCell.textContent = `[Object: ${value.constructor?.name || 'Unknown'}]`;
1170
+ }
1171
+ }
1172
+ valueCell.style.color = '#ce9178';
1173
+ } else if (typeof value === 'boolean') {
1174
+ valueCell.textContent = value.toString();
1175
+ valueCell.style.color = '#569cd6';
1176
+ } else if (typeof value === 'number') {
1177
+ valueCell.textContent = value;
1178
+ valueCell.style.color = '#b5cea8';
1179
+ } else {
1180
+ valueCell.textContent = String(value);
1181
+ valueCell.style.color = '#d4d4d4';
1182
+ }
1183
+ } catch (stringifyError) {
1184
+ valueCell.textContent = '[Unserializable]';
1185
+ valueCell.style.color = '#ff5555';
1186
+ }
1187
+
1188
+ valueCell.style.cssText += `
1189
+ font-family: monospace;
1190
+ white-space: pre-wrap;
1191
+ word-break: break-all;
1192
+ flex: 1;
1193
+ `;
1194
+
1195
+ row.appendChild(keyCell);
1196
+ row.appendChild(valueCell);
1197
+ table.appendChild(row);
1198
+ });
1199
+
1200
+ // Ajouter des contrôles d'édition pour les propriétés importantes
1201
+ const controls = document.createElement('div');
1202
+ controls.style.marginTop = '20px';
1203
+
1204
+ const editTitle = document.createElement('h4');
1205
+ editTitle.textContent = 'Modifier';
1206
+ editTitle.style.marginBottom = '8px';
1207
+
1208
+ // Éditeur de position
1209
+ const posEditor = document.createElement('div');
1210
+ posEditor.style.cssText = `
1211
+ display: grid;
1212
+ grid-template-columns: 1fr 1fr;
1213
+ gap: 8px;
1214
+ margin-bottom: 10px;
1215
+ `;
1216
+
1217
+ const createNumberInput = (label, value, onChange) => {
1218
+ const container = document.createElement('div');
1219
+ container.style.cssText = `
1220
+ display: flex;
1221
+ align-items: center;
1222
+ gap: 4px;
1223
+ `;
1224
+
1225
+ const labelEl = document.createElement('span');
1226
+ labelEl.textContent = label;
1227
+ labelEl.style.minWidth = '40px';
1228
+
1229
+ const input = document.createElement('input');
1230
+ input.type = 'number';
1231
+ input.value = value;
1232
+ input.style.cssText = `
1233
+ width: 60px;
1234
+ padding: 2px 4px;
1235
+ background: #333;
1236
+ border: 1px solid #555;
1237
+ color: #fff;
1238
+ border-radius: 3px;
1239
+ `;
1240
+ input.onchange = (e) => onChange(parseFloat(e.target.value));
1241
+
1242
+ container.appendChild(labelEl);
1243
+ container.appendChild(input);
1244
+ return container;
1245
+ };
1246
+
1247
+ posEditor.appendChild(
1248
+ createNumberInput('X:', comp.x, (val) => {
1249
+ comp.x = val;
1250
+ comp.markDirty();
1251
+ })
1252
+ );
1253
+
1254
+ posEditor.appendChild(
1255
+ createNumberInput('Y:', comp.y, (val) => {
1256
+ comp.y = val;
1257
+ comp.markDirty();
1258
+ })
1259
+ );
1260
+
1261
+ posEditor.appendChild(
1262
+ createNumberInput('W:', comp.width, (val) => {
1263
+ comp.width = val;
1264
+ comp.markDirty();
1265
+ })
1266
+ );
1267
+
1268
+ posEditor.appendChild(
1269
+ createNumberInput('H:', comp.height, (val) => {
1270
+ comp.height = val;
1271
+ comp.markDirty();
1272
+ })
1273
+ );
1274
+
1275
+ // Toggle pour visible
1276
+ const visibleControl = document.createElement('div');
1277
+ visibleControl.style.cssText = `
1278
+ display: flex;
1279
+ align-items: center;
1280
+ gap: 8px;
1281
+ margin-top: 10px;
1282
+ `;
1283
+
1284
+ const visibleLabel = document.createElement('span');
1285
+ visibleLabel.textContent = 'Visible:';
1286
+
1287
+ const visibleCheckbox = document.createElement('input');
1288
+ visibleCheckbox.type = 'checkbox';
1289
+ visibleCheckbox.checked = comp.visible;
1290
+ visibleCheckbox.onchange = () => {
1291
+ comp.visible = visibleCheckbox.checked;
1292
+ comp.markDirty();
1293
+ };
1294
+
1295
+ visibleControl.appendChild(visibleLabel);
1296
+ visibleControl.appendChild(visibleCheckbox);
1297
+
1298
+ controls.appendChild(editTitle);
1299
+ controls.appendChild(posEditor);
1300
+ controls.appendChild(visibleControl);
1301
+ table.appendChild(controls);
1302
+ }
1303
+
1304
+ /**
1305
+ * Met à jour le panneau de routing
1306
+ */
1307
+ updateRoutingPanel() {
1308
+ if (!this.isOpen || this.currentTab !== 'routing') return;
1309
+
1310
+ const routeInfo = document.getElementById('route-info');
1311
+ const historyList = document.getElementById('history-list');
1312
+
1313
+ // Current route
1314
+ routeInfo.innerHTML = `
1315
+ <div><strong>Route actuelle:</strong> ${this.framework.currentRoute}</div>
1316
+ <div><strong>Paramètres:</strong> ${JSON.stringify(this.framework.currentParams)}</div>
1317
+ <div><strong>Query:</strong> ${JSON.stringify(this.framework.currentQuery)}</div>
1318
+ `;
1319
+
1320
+ // History
1321
+ historyList.innerHTML = '';
1322
+ this.framework.history.forEach((entry, index) => {
1323
+ const item = document.createElement('div');
1324
+ item.style.cssText = `
1325
+ padding: 4px 8px;
1326
+ margin: 2px 0;
1327
+ background: ${index === this.framework.historyIndex ? '#007acc20' : 'transparent'};
1328
+ border-radius: 3px;
1329
+ cursor: pointer;
1330
+ font-family: monospace;
1331
+ font-size: 11px;
1332
+ `;
1333
+ item.textContent = `${index}: ${entry.path}`;
1334
+ item.onclick = () => {
1335
+ this.framework.navigate(entry.path, { replace: true });
1336
+ };
1337
+ historyList.appendChild(item);
1338
+ });
1339
+ }
1340
+
1341
+ /**
1342
+ * Ajoute un événement au panneau
1343
+ */
1344
+ logEvent(type, data) {
1345
+ if (!this.isOpen || this.currentTab !== 'events') return;
1346
+
1347
+ const eventItem = document.createElement('div');
1348
+ eventItem.style.cssText = `
1349
+ padding: 4px 8px;
1350
+ margin: 2px 0;
1351
+ background: #252526;
1352
+ border-left: 3px solid #007acc;
1353
+ font-family: monospace;
1354
+ font-size: 11px;
1355
+ `;
1356
+
1357
+ const time = new Date().toLocaleTimeString();
1358
+ eventItem.innerHTML = `
1359
+ <div style="color: #569cd6;">${time} - ${type}</div>
1360
+ <div style="color: #888; font-size: 10px;">${JSON.stringify(data)}</div>
1361
+ `;
1362
+
1363
+ this.eventsList.appendChild(eventItem);
1364
+ this.eventsList.scrollTop = this.eventsList.scrollHeight;
1365
+ }
1366
+
1367
+ /**
1368
+ * Nettoie la liste des événements
1369
+ */
1370
+ clearEvents() {
1371
+ this.eventsList.innerHTML = '';
1372
+ }
1373
+
1374
+ /**
1375
+ * Sélectionne un composant
1376
+ */
1377
+ selectComponent(component) {
1378
+ this.selectedComponent = component;
1379
+ this.updatePropertiesPanel();
1380
+ this.updateComponentsPanel();
1381
+ this.updateHierarchyPanel();
1382
+ this.highlightComponentDirectly(component);
1383
+ }
1384
+
1385
+ /**
1386
+ * Inspecte un composant à une position donnée
1387
+ */
1388
+ inspectComponentAt(x, y) {
1389
+ const adjustedY = y - this.framework.scrollOffset;
1390
+
1391
+ for (let i = this.framework.components.length - 1; i >= 0; i--) {
1392
+ const comp = this.framework.components[i];
1393
+
1394
+ if (comp.visible && comp.isPointInside(x, adjustedY)) {
1395
+ this.selectComponent(comp);
1396
+ this.switchTab('properties');
1397
+ return comp;
1398
+ }
1399
+ }
1400
+ return null;
1401
+ }
1402
+
1403
+ /**
1404
+ * Met en surbrillance un composant
1405
+ */
1406
+ highlightComponent(index) {
1407
+ if (index >= 0 && index < this.framework.components.length) {
1408
+ const comp = this.framework.components[index];
1409
+ this.highlightComponentDirectly(comp);
1410
+
1411
+ // Animation de flash
1412
+ const originalOpacity = comp.opacity;
1413
+ let flashCount = 0;
1414
+ const flashInterval = setInterval(() => {
1415
+ comp.opacity = flashCount % 2 === 0 ? 0.5 : (originalOpacity || 1);
1416
+ comp.markDirty();
1417
+ flashCount++;
1418
+
1419
+ if (flashCount > 5) {
1420
+ clearInterval(flashInterval);
1421
+ comp.opacity = originalOpacity || 1;
1422
+ comp.markDirty();
1423
+ }
1424
+ }, 200);
1425
+ }
1426
+ }
1427
+
1428
+ /**
1429
+ * Met en surbrillance directe d'un composant
1430
+ */
1431
+ highlightComponentDirectly(comp) {
1432
+ // Dessiner une surbrillance autour du composant
1433
+ const originalDraw = comp.draw;
1434
+ comp.draw = function(ctx) {
1435
+ originalDraw.call(this, ctx);
1436
+
1437
+ // Dessiner un cadre de surbrillance
1438
+ ctx.save();
1439
+ ctx.strokeStyle = '#ff5555';
1440
+ ctx.lineWidth = 2;
1441
+ ctx.setLineDash([5, 3]);
1442
+ ctx.strokeRect(this.x - 2, this.y - 2, this.width + 4, this.height + 4);
1443
+
1444
+ // Ajouter une étiquette
1445
+ ctx.fillStyle = '#ff5555';
1446
+ ctx.font = '10px Arial';
1447
+ ctx.fillText(`${this.constructor.name}`, this.x, this.y - 5);
1448
+ ctx.restore();
1449
+ };
1450
+
1451
+ comp.markDirty();
1452
+
1453
+ // Restaurer après 2 secondes
1454
+ setTimeout(() => {
1455
+ comp.draw = originalDraw;
1456
+ comp.markDirty();
1457
+ }, 2000);
1458
+ }
1459
+
1460
+ /**
1461
+ * Bascule la visibilité de tous les composants
1462
+ */
1463
+ toggleAllComponents() {
1464
+ const allVisible = this.framework.components.every(c => c.visible);
1465
+ this.framework.components.forEach(comp => {
1466
+ comp.visible = !allVisible;
1467
+ comp.markDirty();
1468
+ });
1469
+ this.updateComponentsPanel();
1470
+ }
1471
+
1472
+ /**
1473
+ * Bascule la visibilité d'un composant
1474
+ */
1475
+ toggleComponentVisibility(index, visible) {
1476
+ if (index >= 0 && index < this.framework.components.length) {
1477
+ const comp = this.framework.components[index];
1478
+ comp.visible = visible;
1479
+ comp.markDirty();
1480
+ this.updateComponentsPanel();
1481
+ }
1482
+ }
1483
+
1484
+ /**
1485
+ * Rafraîchit la liste des composants
1486
+ */
1487
+ refreshComponents() {
1488
+ this.updateComponentsPanel();
1489
+ }
1490
+
1491
+ /**
1492
+ * Démarre la surveillance des performances
1493
+ */
1494
+ startMonitoring() {
1495
+ this.monitorInterval = setInterval(() => {
1496
+ this.updatePerformancePanel();
1497
+ }, 1000);
1498
+ }
1499
+
1500
+ /**
1501
+ * Arrête la surveillance des performances
1502
+ */
1503
+ stopMonitoring() {
1504
+ if (this.monitorInterval) {
1505
+ clearInterval(this.monitorInterval);
1506
+ }
1507
+ }
1508
+
1509
+ /**
1510
+ * Nettoie les références et restaure le framework
1511
+ */
1512
+ cleanup() {
1513
+ this.restoreConsole();
1514
+
1515
+ // Restaurer les méthodes originales si elles existent
1516
+ if (this.framework) {
1517
+ if (this.originalNavigate) {
1518
+ this.framework.navigate = this.originalNavigate;
1519
+ this.originalNavigate = null;
1520
+ }
1521
+ if (this.originalAdd) {
1522
+ this.framework.add = this.originalAdd;
1523
+ this.originalAdd = null;
1524
+ }
1525
+ }
1526
+
1527
+ // Supprimer la référence globale
1528
+ if (window.devTools === this) {
1529
+ delete window.devTools;
1530
+ }
1531
+
1532
+ // Masquer le DevTools
1533
+ if (this.isOpen) {
1534
+ this.toggle();
1535
+ }
1536
+
1537
+ // Masquer le bouton flottant
1538
+ if (this.toggleBtn) {
1539
+ this.toggleBtn.style.display = 'none';
1540
+ }
1541
+
1542
+ // Nettoyer les écouteurs d'événements
1543
+ if (this.closeBtn) {
1544
+ this.closeBtn.onclick = null;
1545
+ }
1546
+
1547
+ if (this.toggleBtn) {
1548
+ this.toggleBtn.onclick = null;
1549
+ }
1550
+
1551
+ // Nettoyer les tabs
1552
+ this.tabs.querySelectorAll('button').forEach(tab => {
1553
+ tab.onclick = null;
1554
+ });
1555
+ }
1556
+
1557
+ /**
1558
+ * Enregistre le DevTools dans le framework
1559
+ */
1560
+ attachToFramework() {
1561
+ window.devTools = this;
1562
+ // Intercepter la console
1563
+ this.interceptConsole();
1564
+
1565
+ // Sauvegarder les références originales
1566
+ this.originalNavigate = this.framework.navigate;
1567
+ this.originalAdd = this.framework.add;
1568
+
1569
+ // Intercepter les événements du framework
1570
+ this.framework.navigate = (...args) => {
1571
+ if (window.devTools) {
1572
+ window.devTools.logEvent('NAVIGATE', { path: args[0] });
1573
+ }
1574
+ return this.originalNavigate.apply(this.framework, args);
1575
+ };
1576
+
1577
+ // Intercepter les ajouts de composants
1578
+ this.framework.add = (...args) => {
1579
+ if (window.devTools) {
1580
+ window.devTools.logEvent('ADD_COMPONENT', {
1581
+ type: args[0]?.constructor?.name
1582
+ });
1583
+ }
1584
+ return this.originalAdd.apply(this.framework, args);
1585
+ };
1586
+ }
1587
+
1588
+ /**
1589
+ * Détache le DevTools du framework
1590
+ */
1591
+ detachFromFramework() {
1592
+ // Restaurer les méthodes originales
1593
+ if (this.originalNavigate && this.framework) {
1594
+ this.framework.navigate = this.originalNavigate;
1595
+ }
1596
+
1597
+ if (this.originalAdd && this.framework) {
1598
+ this.framework.add = this.originalAdd;
1599
+ }
1600
+
1601
+ // Supprimer la référence globale
1602
+ if (window.devTools === this) {
1603
+ delete window.devTools;
1604
+ }
1605
+
1606
+ // Masquer le DevTools s'il est ouvert
1607
+ if (this.isOpen) {
1608
+ this.toggle();
1609
+ }
1610
+
1611
+ // Masquer le bouton flottant
1612
+ if (this.toggleBtn) {
1613
+ this.toggleBtn.style.display = 'none';
1614
+ }
1615
+ }
1616
+ }
1617
+
1618
+ export default DevTools;