Update dev-tools.js
Browse files- dev-tools.js +413 -400
dev-tools.js
CHANGED
@@ -17,142 +17,104 @@
|
|
17 |
let refreshElementsPanel = null;
|
18 |
let vitalsMeasurementStarted = false;
|
19 |
|
20 |
-
//
|
21 |
-
const loadWebVitals = () => {
|
22 |
-
return new Promise((resolve) => {
|
23 |
-
if (window.webVitals) {
|
24 |
-
resolve();
|
25 |
-
return;
|
26 |
-
}
|
27 |
-
|
28 |
-
const webVitalsScript = document.createElement('script');
|
29 |
-
webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
|
30 |
-
webVitalsScript.onload = resolve;
|
31 |
-
document.head.appendChild(webVitalsScript);
|
32 |
-
});
|
33 |
-
};
|
34 |
-
|
35 |
-
// DOM変更を監視するMutationObserver
|
36 |
-
const setupMutationObserver = () => {
|
37 |
-
observer = new MutationObserver((mutations) => {
|
38 |
-
mutations.forEach((mutation) => {
|
39 |
-
if (mutation.type === 'childList') {
|
40 |
-
mutation.addedNodes.forEach((node) => {
|
41 |
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
42 |
-
highlightNode(node);
|
43 |
-
}
|
44 |
-
});
|
45 |
-
}
|
46 |
-
});
|
47 |
-
if (refreshElementsPanel) {
|
48 |
-
refreshElementsPanel();
|
49 |
-
}
|
50 |
-
});
|
51 |
-
|
52 |
-
observer.observe(document.documentElement, {
|
53 |
-
childList: true,
|
54 |
-
subtree: true,
|
55 |
-
attributes: true,
|
56 |
-
characterData: true
|
57 |
-
});
|
58 |
-
};
|
59 |
const style = document.createElement('style');
|
60 |
style.textContent = `
|
61 |
-
|
62 |
-
--bg-color: #1e1e1e;
|
63 |
-
--panel-bg: #252526;
|
64 |
-
--border-color: #3c3c3c;
|
65 |
-
--text-color: #e0e0e0;
|
66 |
-
--text-muted: #a0a0a0;
|
67 |
-
--primary-color: #007acc;
|
68 |
-
--primary-hover: #3e9fda;
|
69 |
-
--success-color: #4caf50;
|
70 |
-
--error-color: #f44336;
|
71 |
-
--warning-color: #ff9800;
|
72 |
-
--info-color: #2196f3;
|
73 |
-
--highlight-bg: rgba(0, 122, 204, 0.2);
|
74 |
-
--tab-bg: #2d2d2d;
|
75 |
-
--tab-active-bg: #1e1e1e;
|
76 |
-
--console-log-color: #e0e0e0;
|
77 |
-
--console-error-color: #f44336;
|
78 |
-
--console-warn-color: #ff9800;
|
79 |
-
--console-info-color: #4fc3f7;
|
80 |
-
--json-key: #9cdcfe;
|
81 |
-
--json-string: #ce9178;
|
82 |
-
--json-number: #b5cea8;
|
83 |
-
--json-boolean: #569cd6;
|
84 |
-
--json-null: #569cd6;
|
85 |
-
--dom-tag: #569cd6;
|
86 |
-
--dom-attr: #9cdcfe;
|
87 |
-
--dom-text: #d4d4d4;
|
88 |
-
}
|
89 |
-
|
90 |
-
.devtools-container {
|
91 |
position: fixed;
|
92 |
bottom: 0;
|
93 |
left: 0;
|
94 |
width: 100%;
|
95 |
height: 300px;
|
96 |
-
background-color: var(--panel-bg);
|
97 |
-
border-top: 1px solid var(--border-color);
|
98 |
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
|
99 |
z-index: 9999;
|
100 |
display: flex;
|
101 |
flex-direction: column;
|
102 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
103 |
-
color: var(--text-color);
|
104 |
}
|
105 |
|
106 |
-
.devtools-header {
|
107 |
display: flex;
|
108 |
justify-content: space-between;
|
109 |
align-items: center;
|
110 |
padding: 5px 10px;
|
111 |
-
background-color: var(--tab-bg);
|
112 |
-
border-bottom: 1px solid var(--border-color);
|
113 |
}
|
114 |
|
115 |
-
.devtools-tabs {
|
116 |
display: flex;
|
117 |
gap: 5px;
|
118 |
}
|
119 |
|
120 |
-
.devtools-tab {
|
121 |
padding: 5px 10px;
|
122 |
cursor: pointer;
|
123 |
border-radius: 3px 3px 0 0;
|
124 |
-
background-color: var(--tab-bg);
|
125 |
-
border: 1px solid var(--border-color);
|
126 |
border-bottom: none;
|
127 |
font-size: 12px;
|
128 |
-
color: var(--text-muted);
|
129 |
}
|
130 |
|
131 |
-
.devtools-tab.active {
|
132 |
-
background-color: var(--tab-active-bg);
|
133 |
-
color: var(--text-color);
|
134 |
-
border-bottom: 1px solid var(--tab-active-bg);
|
135 |
margin-bottom: -1px;
|
136 |
font-weight: bold;
|
137 |
}
|
138 |
|
139 |
-
.devtools-close {
|
140 |
background: none;
|
141 |
border: none;
|
142 |
font-size: 16px;
|
143 |
cursor: pointer;
|
144 |
padding: 0 5px;
|
145 |
-
color: var(--text-color);
|
146 |
}
|
147 |
|
148 |
-
.devtools-content {
|
149 |
flex: 1;
|
150 |
overflow: auto;
|
151 |
position: relative;
|
152 |
-
background-color: var(--panel-bg);
|
153 |
}
|
154 |
|
155 |
-
.devtools-panel {
|
156 |
position: absolute;
|
157 |
top: 0;
|
158 |
left: 0;
|
@@ -161,45 +123,45 @@
|
|
161 |
padding: 10px;
|
162 |
overflow: auto;
|
163 |
display: none;
|
164 |
-
background-color: var(--panel-bg);
|
165 |
}
|
166 |
|
167 |
-
.devtools-panel.active {
|
168 |
display: block;
|
169 |
}
|
170 |
|
171 |
/* Console スタイル */
|
172 |
-
#console-log {
|
173 |
white-space: pre-wrap;
|
174 |
margin: 0;
|
175 |
line-height: 1.4;
|
176 |
flex: 1;
|
177 |
-
color: var(--console-log-color);
|
178 |
font-family: 'Consolas', 'Monaco', monospace;
|
179 |
font-size: 13px;
|
180 |
}
|
181 |
|
182 |
-
.console-log {
|
183 |
-
color: var(--console-log-color);
|
184 |
}
|
185 |
|
186 |
-
.console-error {
|
187 |
-
color: var(--console-error-color);
|
188 |
}
|
189 |
|
190 |
-
.console-warn {
|
191 |
-
color: var(--console-warn-color);
|
192 |
}
|
193 |
|
194 |
-
.console-info {
|
195 |
-
color: var(--console-info-color);
|
196 |
}
|
197 |
|
198 |
-
.console-input {
|
199 |
width: calc(100% - 16px);
|
200 |
-
background: var(--tab-bg);
|
201 |
-
border: 1px solid var(--border-color);
|
202 |
-
color: var(--text-color);
|
203 |
padding: 8px;
|
204 |
margin-top: 10px;
|
205 |
font-family: monospace;
|
@@ -207,117 +169,117 @@
|
|
207 |
}
|
208 |
|
209 |
/* Elements スタイル */
|
210 |
-
.elements-container {
|
211 |
display: flex;
|
212 |
flex: 1;
|
213 |
overflow: hidden;
|
214 |
}
|
215 |
|
216 |
-
.dom-tree {
|
217 |
font-family: 'Consolas', 'Monaco', monospace;
|
218 |
flex: 1;
|
219 |
overflow: auto;
|
220 |
-
border-right: 1px solid var(--border-color);
|
221 |
padding-right: 10px;
|
222 |
-
color: var(--dom-text);
|
223 |
font-size: 13px;
|
224 |
}
|
225 |
|
226 |
-
.dom-node {
|
227 |
margin-left: 15px;
|
228 |
position: relative;
|
229 |
line-height: 1.4;
|
230 |
transition: background-color 0.3s;
|
231 |
}
|
232 |
|
233 |
-
.dom-node.selected {
|
234 |
-
background: var(--highlight-bg);
|
235 |
}
|
236 |
|
237 |
-
.dom-node.highlight {
|
238 |
-
animation: highlight-fade 1.5s;
|
239 |
}
|
240 |
|
241 |
-
@keyframes highlight-fade {
|
242 |
0% { background-color: rgba(79, 195, 247, 0.5); }
|
243 |
100% { background-color: transparent; }
|
244 |
}
|
245 |
|
246 |
-
.dom-tag {
|
247 |
-
color: var(--dom-tag);
|
248 |
font-weight: bold;
|
249 |
}
|
250 |
|
251 |
-
.dom-attr {
|
252 |
-
color: var(--dom-attr);
|
253 |
}
|
254 |
|
255 |
-
.dom-attr.editable:hover {
|
256 |
text-decoration: underline;
|
257 |
cursor: pointer;
|
258 |
}
|
259 |
|
260 |
-
.dom-text {
|
261 |
-
color: var(--dom-text);
|
262 |
}
|
263 |
|
264 |
-
.dom-edit-input {
|
265 |
-
background: var(--panel-bg);
|
266 |
-
border: 1px solid var(--primary-color);
|
267 |
padding: 0 2px;
|
268 |
margin: -1px 0;
|
269 |
font-family: monospace;
|
270 |
min-width: 50px;
|
271 |
-
color: var(--text-color);
|
272 |
}
|
273 |
|
274 |
-
.css-panel {
|
275 |
flex: 1;
|
276 |
overflow: auto;
|
277 |
padding-left: 10px;
|
278 |
font-size: 13px;
|
279 |
}
|
280 |
|
281 |
-
.css-rule {
|
282 |
margin-bottom: 15px;
|
283 |
-
border: 1px solid var(--border-color);
|
284 |
padding: 8px;
|
285 |
-
background-color: var(--tab-bg);
|
286 |
border-radius: 3px;
|
287 |
}
|
288 |
|
289 |
-
.css-selector {
|
290 |
-
color: var(--primary-color);
|
291 |
margin-bottom: 5px;
|
292 |
font-weight: bold;
|
293 |
}
|
294 |
|
295 |
-
.css-property {
|
296 |
display: flex;
|
297 |
margin-bottom: 3px;
|
298 |
}
|
299 |
|
300 |
-
.css-property-name {
|
301 |
-
color: var(--dom-attr);
|
302 |
min-width: 120px;
|
303 |
}
|
304 |
|
305 |
-
.css-property-value {
|
306 |
-
color: var(--dom-text);
|
307 |
flex: 1;
|
308 |
}
|
309 |
|
310 |
-
.css-toggle {
|
311 |
margin-left: 10px;
|
312 |
-
color: var(--error-color);
|
313 |
cursor: pointer;
|
314 |
}
|
315 |
|
316 |
/* Context Menu */
|
317 |
-
.context-menu {
|
318 |
position: absolute;
|
319 |
-
background: var(--panel-bg);
|
320 |
-
border: 1px solid var(--border-color);
|
321 |
z-index: 10000;
|
322 |
min-width: 200px;
|
323 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
@@ -326,43 +288,43 @@
|
|
326 |
overflow: hidden;
|
327 |
}
|
328 |
|
329 |
-
.context-menu-item {
|
330 |
padding: 8px 15px;
|
331 |
cursor: pointer;
|
332 |
-
color: var(--text-color);
|
333 |
font-size: 13px;
|
334 |
}
|
335 |
|
336 |
-
.context-menu-item:hover {
|
337 |
-
background: var(--primary-color);
|
338 |
color: #000;
|
339 |
}
|
340 |
|
341 |
/* Storage スタイル */
|
342 |
-
.storage-table {
|
343 |
width: 100%;
|
344 |
border-collapse: collapse;
|
345 |
margin-bottom: 10px;
|
346 |
font-size: 13px;
|
347 |
}
|
348 |
|
349 |
-
.storage-table th, .storage-table td {
|
350 |
-
border: 1px solid var(--border-color);
|
351 |
padding: 5px;
|
352 |
text-align: left;
|
353 |
}
|
354 |
|
355 |
-
.storage-table th {
|
356 |
-
background: var(--tab-bg);
|
357 |
}
|
358 |
|
359 |
-
.storage-actions {
|
360 |
display: flex;
|
361 |
gap: 5px;
|
362 |
}
|
363 |
|
364 |
-
.storage-btn {
|
365 |
-
background: var(--primary-color);
|
366 |
border: none;
|
367 |
padding: 2px 5px;
|
368 |
cursor: pointer;
|
@@ -371,18 +333,18 @@
|
|
371 |
font-size: 12px;
|
372 |
}
|
373 |
|
374 |
-
.editable {
|
375 |
cursor: pointer;
|
376 |
padding: 2px 5px;
|
377 |
border: 1px dashed transparent;
|
378 |
}
|
379 |
|
380 |
-
.editable:hover {
|
381 |
-
border-color: var(--primary-color);
|
382 |
}
|
383 |
|
384 |
-
.add-btn {
|
385 |
-
background: var(--primary-color);
|
386 |
color: #000;
|
387 |
border: none;
|
388 |
padding: 5px 10px;
|
@@ -392,47 +354,47 @@
|
|
392 |
font-size: 13px;
|
393 |
}
|
394 |
|
395 |
-
.add-btn:hover {
|
396 |
-
background: var(--primary-hover);
|
397 |
}
|
398 |
|
399 |
/* Network スタイル */
|
400 |
-
.network-container {
|
401 |
display: flex;
|
402 |
height: 100%;
|
403 |
overflow: hidden;
|
404 |
}
|
405 |
|
406 |
-
.network-requests {
|
407 |
width: 40%;
|
408 |
overflow-y: auto;
|
409 |
-
border-right: 1px solid var(--border-color);
|
410 |
font-size: 13px;
|
411 |
}
|
412 |
|
413 |
-
.network-details {
|
414 |
width: 60%;
|
415 |
overflow-y: auto;
|
416 |
padding-left: 10px;
|
417 |
}
|
418 |
|
419 |
-
.network-request {
|
420 |
padding: 8px;
|
421 |
-
border-bottom: 1px solid var(--border-color);
|
422 |
cursor: pointer;
|
423 |
display: flex;
|
424 |
align-items: center;
|
425 |
}
|
426 |
|
427 |
-
.network-request:hover {
|
428 |
background-color: rgba(0, 122, 204, 0.1);
|
429 |
}
|
430 |
|
431 |
-
.network-request.selected {
|
432 |
-
background-color: var(--highlight-bg);
|
433 |
}
|
434 |
|
435 |
-
.network-status {
|
436 |
width: 20px;
|
437 |
height: 20px;
|
438 |
border-radius: 50%;
|
@@ -443,51 +405,51 @@
|
|
443 |
flex-shrink: 0;
|
444 |
}
|
445 |
|
446 |
-
.network-status.success {
|
447 |
-
background-color: var(--success-color);
|
448 |
color: white;
|
449 |
}
|
450 |
|
451 |
-
.network-status.error {
|
452 |
-
background-color: var(--error-color);
|
453 |
color: white;
|
454 |
}
|
455 |
|
456 |
-
.network-method {
|
457 |
font-weight: bold;
|
458 |
margin-right: 8px;
|
459 |
-
color: var(--primary-color);
|
460 |
min-width: 40px;
|
461 |
}
|
462 |
|
463 |
-
.network-url {
|
464 |
flex: 1;
|
465 |
white-space: nowrap;
|
466 |
overflow: hidden;
|
467 |
text-overflow: ellipsis;
|
468 |
}
|
469 |
|
470 |
-
.network-time {
|
471 |
-
color: var(--text-muted);
|
472 |
font-size: 11px;
|
473 |
margin-left: 8px;
|
474 |
}
|
475 |
|
476 |
-
.network-detail-section {
|
477 |
margin-bottom: 15px;
|
478 |
}
|
479 |
|
480 |
-
.network-detail-title {
|
481 |
font-weight: bold;
|
482 |
margin-bottom: 5px;
|
483 |
-
color: var(--primary-color);
|
484 |
}
|
485 |
|
486 |
-
.network-detail-content {
|
487 |
-
background: var(--tab-bg);
|
488 |
padding: 8px;
|
489 |
border-radius: 3px;
|
490 |
-
border: 1px solid var(--border-color);
|
491 |
font-family: monospace;
|
492 |
white-space: pre-wrap;
|
493 |
font-size: 12px;
|
@@ -496,141 +458,175 @@
|
|
496 |
}
|
497 |
|
498 |
/* Web Vitals スタイル */
|
499 |
-
.vitals-container {
|
500 |
display: flex;
|
501 |
flex-direction: column;
|
502 |
gap: 15px;
|
503 |
}
|
504 |
|
505 |
-
.vital-card {
|
506 |
-
background: var(--tab-bg);
|
507 |
-
border: 1px solid var(--border-color);
|
508 |
border-radius: 5px;
|
509 |
padding: 15px;
|
510 |
}
|
511 |
|
512 |
-
.vital-title {
|
513 |
font-weight: bold;
|
514 |
margin-bottom: 10px;
|
515 |
-
color: var(--primary-color);
|
516 |
display: flex;
|
517 |
justify-content: space-between;
|
518 |
align-items: center;
|
519 |
}
|
520 |
|
521 |
-
.vital-value {
|
522 |
font-size: 24px;
|
523 |
font-weight: bold;
|
524 |
margin: 10px 0;
|
525 |
}
|
526 |
|
527 |
-
.vital-good {
|
528 |
-
color: var(--success-color);
|
529 |
}
|
530 |
|
531 |
-
.vital-needs-improvement {
|
532 |
-
color: var(--warning-color);
|
533 |
}
|
534 |
|
535 |
-
.vital-poor {
|
536 |
-
color: var(--error-color);
|
537 |
}
|
538 |
|
539 |
-
.vital-description {
|
540 |
font-size: 13px;
|
541 |
-
color: var(--text-muted);
|
542 |
}
|
543 |
|
544 |
-
.vital-thresholds {
|
545 |
display: flex;
|
546 |
margin-top: 10px;
|
547 |
font-size: 12px;
|
548 |
}
|
549 |
|
550 |
-
.vital-threshold {
|
551 |
flex: 1;
|
552 |
text-align: center;
|
553 |
padding: 5px;
|
554 |
border-radius: 3px;
|
555 |
}
|
556 |
|
557 |
-
.vital-threshold.active {
|
558 |
background: rgba(0, 122, 204, 0.2);
|
559 |
}
|
560 |
|
561 |
/* DOM Tree Toggle */
|
562 |
-
.dom-toggle {
|
563 |
position: absolute;
|
564 |
left: -12px;
|
565 |
top: 2px;
|
566 |
width: 10px;
|
567 |
height: 10px;
|
568 |
cursor: pointer;
|
569 |
-
background-color: var(--text-muted);
|
570 |
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
571 |
transition: transform 0.2s;
|
572 |
}
|
573 |
|
574 |
-
.dom-toggle.collapsed {
|
575 |
transform: rotate(-90deg);
|
576 |
}
|
577 |
|
578 |
-
.dom-children {
|
579 |
overflow: hidden;
|
580 |
transition: max-height 0.3s ease-out;
|
581 |
}
|
582 |
|
583 |
/* JSON スタイル */
|
584 |
-
.json-key {
|
585 |
-
color: var(--json-key);
|
586 |
}
|
587 |
|
588 |
-
.json-string {
|
589 |
-
color: var(--json-string);
|
590 |
}
|
591 |
|
592 |
-
.json-number {
|
593 |
-
color: var(--json-number);
|
594 |
}
|
595 |
|
596 |
-
.json-boolean {
|
597 |
-
color: var(--json-boolean);
|
598 |
}
|
599 |
|
600 |
-
.json-null {
|
601 |
-
color: var(--json-null);
|
602 |
}
|
603 |
`;
|
604 |
document.head.appendChild(style);
|
|
|
605 |
// ノードをハイライト表示
|
606 |
function highlightNode(node) {
|
607 |
const elementId = node.id || Math.random().toString(36).substr(2, 9);
|
608 |
-
node.setAttribute('data-element-id', elementId);
|
609 |
|
610 |
setTimeout(() => {
|
611 |
-
const domNode = document.querySelector(`[data-element-id="${elementId}"]`);
|
612 |
if (domNode) {
|
613 |
-
domNode.classList.add('highlight');
|
614 |
setTimeout(() => {
|
615 |
-
domNode.classList.remove('highlight');
|
616 |
}, 1500);
|
617 |
}
|
618 |
}, 100);
|
619 |
}
|
620 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
621 |
// 開発者ツールのメイン関数
|
622 |
const createDevTools = () => {
|
623 |
const container = document.createElement('div');
|
624 |
-
container.className = 'devtools-container';
|
625 |
-
container.id = 'devtools-container';
|
626 |
container.style.display = 'none';
|
627 |
|
628 |
// ヘッダー部分
|
629 |
const header = document.createElement('div');
|
630 |
-
header.className = 'devtools-header';
|
631 |
|
632 |
const tabs = document.createElement('div');
|
633 |
-
tabs.className = 'devtools-tabs';
|
634 |
|
635 |
const consoleTab = createTab('Console', 'console');
|
636 |
const elementsTab = createTab('Elements', 'elements');
|
@@ -645,7 +641,7 @@
|
|
645 |
tabs.appendChild(vitalsTab);
|
646 |
|
647 |
const closeBtn = document.createElement('button');
|
648 |
-
closeBtn.className = 'devtools-close';
|
649 |
closeBtn.textContent = '×';
|
650 |
closeBtn.onclick = toggleDevTools;
|
651 |
|
@@ -654,7 +650,7 @@
|
|
654 |
|
655 |
// コンテンツ部分
|
656 |
const content = document.createElement('div');
|
657 |
-
content.className = 'devtools-content';
|
658 |
|
659 |
const consolePanel = createConsolePanel();
|
660 |
const elementsPanel = createElementsPanel();
|
@@ -679,11 +675,11 @@
|
|
679 |
// タブ切り替え機能
|
680 |
function createTab(name, panelId) {
|
681 |
const tab = document.createElement('div');
|
682 |
-
tab.className = 'devtools-tab';
|
683 |
tab.textContent = name;
|
684 |
tab.onclick = () => {
|
685 |
-
document.querySelectorAll('.devtools-tab').forEach(t => t.classList.remove('active'));
|
686 |
-
document.querySelectorAll('.devtools-panel').forEach(p => p.classList.remove('active'));
|
687 |
tab.classList.add('active');
|
688 |
document.getElementById(panelId + '-panel').classList.add('active');
|
689 |
|
@@ -700,86 +696,86 @@
|
|
700 |
// Web Vitalsパネル作成
|
701 |
function createVitalsPanel() {
|
702 |
const panel = document.createElement('div');
|
703 |
-
panel.className = 'devtools-panel';
|
704 |
panel.id = 'vitals-panel';
|
705 |
|
706 |
const container = document.createElement('div');
|
707 |
-
container.className = 'vitals-container';
|
708 |
panel.appendChild(container);
|
709 |
|
710 |
// 測定開始ボタン
|
711 |
const startButton = document.createElement('button');
|
712 |
-
startButton.className = 'add-btn';
|
713 |
startButton.textContent = '測定を開始';
|
714 |
startButton.onclick = startVitalsMeasurement;
|
715 |
container.appendChild(startButton);
|
716 |
|
717 |
// CLS (Cumulative Layout Shift)
|
718 |
const clsCard = document.createElement('div');
|
719 |
-
clsCard.className = 'vital-card';
|
720 |
clsCard.innerHTML = `
|
721 |
-
<div class="vital-title">CLS (Cumulative Layout Shift) <span class="vital-description">視覚的な安定性</span></div>
|
722 |
-
<div class="vital-value" id="cls-value">-</div>
|
723 |
-
<div class="vital-thresholds">
|
724 |
-
<div class="vital-threshold">Good: < 0.1</div>
|
725 |
-
<div class="vital-threshold">Needs Improvement: < 0.25</div>
|
726 |
-
<div class="vital-threshold">Poor: ≥ 0.25</div>
|
727 |
</div>
|
728 |
`;
|
729 |
container.appendChild(clsCard);
|
730 |
|
731 |
// FCP (First Contentful Paint)
|
732 |
const fcpCard = document.createElement('div');
|
733 |
-
fcpCard.className = 'vital-card';
|
734 |
fcpCard.innerHTML = `
|
735 |
-
<div class="vital-title">FCP (First Contentful Paint) <span class="vital-description">最初のコンテンツ表示</span></div>
|
736 |
-
<div class="vital-value" id="fcp-value">-</div>
|
737 |
-
<div class="vital-thresholds">
|
738 |
-
<div class="vital-threshold">Good: < 1.8s</div>
|
739 |
-
<div class="vital-threshold">Needs Improvement: < 3s</div>
|
740 |
-
<div class="vital-threshold">Poor: ≥ 3s</div>
|
741 |
</div>
|
742 |
`;
|
743 |
container.appendChild(fcpCard);
|
744 |
|
745 |
// FID (First Input Delay)
|
746 |
const fidCard = document.createElement('div');
|
747 |
-
fidCard.className = 'vital-card';
|
748 |
fidCard.innerHTML = `
|
749 |
-
<div class="vital-title">FID (First Input Delay) <span class="vital-description">最初の入力遅延</span></div>
|
750 |
-
<div class="vital-value" id="fid-value">-</div>
|
751 |
-
<div class="vital-thresholds">
|
752 |
-
<div class="vital-threshold">Good: < 100ms</div>
|
753 |
-
<div class="vital-threshold">Needs Improvement: < 300ms</div>
|
754 |
-
<div class="vital-threshold">Poor: ≥ 300ms</div>
|
755 |
</div>
|
756 |
`;
|
757 |
container.appendChild(fidCard);
|
758 |
|
759 |
// LCP (Largest Contentful Paint)
|
760 |
const lcpCard = document.createElement('div');
|
761 |
-
lcpCard.className = 'vital-card';
|
762 |
lcpCard.innerHTML = `
|
763 |
-
<div class="vital-title">LCP (Largest Contentful Paint) <span class="vital-description">最大のコンテンツ表示</span></div>
|
764 |
-
<div class="vital-value" id="lcp-value">-</div>
|
765 |
-
<div class="vital-thresholds">
|
766 |
-
<div class="vital-threshold">Good: < 2.5s</div>
|
767 |
-
<div class="vital-threshold">Needs Improvement: < 4s</div>
|
768 |
-
<div class="vital-threshold">Poor: ≥ 4s</div>
|
769 |
</div>
|
770 |
`;
|
771 |
container.appendChild(lcpCard);
|
772 |
|
773 |
// TTFB (Time to First Byte)
|
774 |
const ttfbCard = document.createElement('div');
|
775 |
-
ttfbCard.className = 'vital-card';
|
776 |
ttfbCard.innerHTML = `
|
777 |
-
<div class="vital-title">TTFB (Time to First Byte) <span class="vital-description">最初のバイト到達時間</span></div>
|
778 |
-
<div class="vital-value" id="ttfb-value">-</div>
|
779 |
-
<div class="vital-thresholds">
|
780 |
-
<div class="vital-threshold">Good: < 800ms</div>
|
781 |
-
<div class="vital-threshold">Needs Improvement: < 1.8s</div>
|
782 |
-
<div class="vital-threshold">Poor: ≥ 1.8s</div>
|
783 |
</div>
|
784 |
`;
|
785 |
container.appendChild(ttfbCard);
|
@@ -805,9 +801,9 @@
|
|
805 |
vitals.onLCP(updateLCP);
|
806 |
vitals.onTTFB(updateTTFB);
|
807 |
}
|
808 |
-
|
809 |
} catch (e) {
|
810 |
-
|
811 |
}
|
812 |
}
|
813 |
});
|
@@ -839,7 +835,7 @@
|
|
839 |
}
|
840 |
|
841 |
function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
|
842 |
-
const element = document.getElementById(
|
843 |
if (!element) return;
|
844 |
|
845 |
const displayValue = id === 'ttfb' ?
|
@@ -848,16 +844,16 @@
|
|
848 |
|
849 |
element.textContent = displayValue;
|
850 |
|
851 |
-
element.className = 'vital-value';
|
852 |
if (value <= goodThreshold) {
|
853 |
-
element.classList.add('vital-good');
|
854 |
} else if (value <= needsImprovementThreshold) {
|
855 |
-
element.classList.add('vital-needs-improvement');
|
856 |
} else {
|
857 |
-
element.classList.add('vital-poor');
|
858 |
}
|
859 |
|
860 |
-
const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
|
861 |
thresholds.forEach((threshold, index) => {
|
862 |
threshold.classList.remove('active');
|
863 |
if (
|
@@ -876,21 +872,21 @@
|
|
876 |
// ネットワークパネル作成
|
877 |
function createNetworkPanel() {
|
878 |
const panel = document.createElement('div');
|
879 |
-
panel.className = 'devtools-panel';
|
880 |
panel.id = 'network-panel';
|
881 |
|
882 |
const container = document.createElement('div');
|
883 |
-
container.className = 'network-container';
|
884 |
panel.appendChild(container);
|
885 |
|
886 |
// リクエストリスト
|
887 |
const requestsList = document.createElement('div');
|
888 |
-
requestsList.className = 'network-requests';
|
889 |
container.appendChild(requestsList);
|
890 |
|
891 |
// 詳細パネル
|
892 |
const detailsPanel = document.createElement('div');
|
893 |
-
detailsPanel.className = 'network-details';
|
894 |
container.appendChild(detailsPanel);
|
895 |
|
896 |
// ネットワークリクエストを監視
|
@@ -1051,14 +1047,14 @@
|
|
1051 |
const panel = document.getElementById('network-panel');
|
1052 |
if (!panel || !panel.classList.contains('active')) return;
|
1053 |
|
1054 |
-
const requestsList = panel.querySelector('.network-requests');
|
1055 |
-
const detailsPanel = panel.querySelector('.network-details');
|
1056 |
|
1057 |
requestsList.innerHTML = '';
|
1058 |
|
1059 |
networkRequests.forEach(request => {
|
1060 |
const requestElement = document.createElement('div');
|
1061 |
-
requestElement.className = 'network-request';
|
1062 |
if (selectedRequest && selectedRequest.id === request.id) {
|
1063 |
requestElement.classList.add('selected');
|
1064 |
}
|
@@ -1070,19 +1066,19 @@
|
|
1070 |
};
|
1071 |
|
1072 |
const statusElement = document.createElement('div');
|
1073 |
-
statusElement.className = `network-status ${request.status}`;
|
1074 |
statusElement.textContent = request.status === 'success' ? '✓' : '✕';
|
1075 |
|
1076 |
const methodElement = document.createElement('div');
|
1077 |
-
methodElement.className = 'network-method';
|
1078 |
methodElement.textContent = request.method;
|
1079 |
|
1080 |
const urlElement = document.createElement('div');
|
1081 |
-
urlElement.className = 'network-url';
|
1082 |
urlElement.textContent = request.url;
|
1083 |
|
1084 |
const timeElement = document.createElement('div');
|
1085 |
-
timeElement.className = 'network-time';
|
1086 |
timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
|
1087 |
|
1088 |
requestElement.appendChild(statusElement);
|
@@ -1099,15 +1095,15 @@
|
|
1099 |
const panel = document.getElementById('network-panel');
|
1100 |
if (!panel || !selectedRequest) return;
|
1101 |
|
1102 |
-
const detailsPanel = panel.querySelector('.network-details');
|
1103 |
detailsPanel.innerHTML = '';
|
1104 |
|
1105 |
// 一般情報
|
1106 |
const generalSection = document.createElement('div');
|
1107 |
-
generalSection.className = 'network-detail-section';
|
1108 |
generalSection.innerHTML = `
|
1109 |
-
<div class="network-detail-title">一般</div>
|
1110 |
-
<div class="network-detail-content">
|
1111 |
<strong>URL:</strong> ${selectedRequest.url}<br>
|
1112 |
<strong>メソッド:</strong> ${selectedRequest.method}<br>
|
1113 |
<strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
|
@@ -1119,14 +1115,14 @@
|
|
1119 |
// リクエストヘッダー
|
1120 |
if (selectedRequest.requestHeaders) {
|
1121 |
const headersSection = document.createElement('div');
|
1122 |
-
headersSection.className = 'network-detail-section';
|
1123 |
|
1124 |
const headersTitle = document.createElement('div');
|
1125 |
-
headersTitle.className = 'network-detail-title';
|
1126 |
headersTitle.textContent = 'リクエストヘッダー';
|
1127 |
|
1128 |
const headersContent = document.createElement('div');
|
1129 |
-
headersContent.className = 'network-detail-content';
|
1130 |
|
1131 |
if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
|
1132 |
Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
|
@@ -1146,14 +1142,14 @@
|
|
1146 |
// リクエストボディ
|
1147 |
if (selectedRequest.requestBody) {
|
1148 |
const bodySection = document.createElement('div');
|
1149 |
-
bodySection.className = 'network-detail-section';
|
1150 |
|
1151 |
const bodyTitle = document.createElement('div');
|
1152 |
-
bodyTitle.className = 'network-detail-title';
|
1153 |
bodyTitle.textContent = 'リクエストボディ';
|
1154 |
|
1155 |
const bodyContent = document.createElement('div');
|
1156 |
-
bodyContent.className = 'network-detail-content';
|
1157 |
|
1158 |
try {
|
1159 |
if (typeof selectedRequest.requestBody === 'string') {
|
@@ -1173,14 +1169,14 @@
|
|
1173 |
// レスポンス
|
1174 |
if (selectedRequest.response) {
|
1175 |
const responseSection = document.createElement('div');
|
1176 |
-
responseSection.className = 'network-detail-section';
|
1177 |
|
1178 |
const responseTitle = document.createElement('div');
|
1179 |
-
responseTitle.className = 'network-detail-title';
|
1180 |
responseTitle.textContent = 'レスポンス';
|
1181 |
|
1182 |
const responseContent = document.createElement('div');
|
1183 |
-
responseContent.className = 'network-detail-content';
|
1184 |
|
1185 |
if (selectedRequest.response.body) {
|
1186 |
if (typeof selectedRequest.response.body === 'object') {
|
@@ -1200,14 +1196,14 @@
|
|
1200 |
// エラー
|
1201 |
if (selectedRequest.error) {
|
1202 |
const errorSection = document.createElement('div');
|
1203 |
-
errorSection.className = 'network-detail-section';
|
1204 |
|
1205 |
const errorTitle = document.createElement('div');
|
1206 |
-
errorTitle.className = 'network-detail-title';
|
1207 |
errorTitle.textContent = 'エラー';
|
1208 |
|
1209 |
const errorContent = document.createElement('div');
|
1210 |
-
errorContent.className = 'network-detail-content';
|
1211 |
errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
|
1212 |
|
1213 |
if (selectedRequest.error.stack) {
|
@@ -1223,20 +1219,20 @@
|
|
1223 |
// コンテキストメニュー作成
|
1224 |
function createContextMenu() {
|
1225 |
contextMenu = document.createElement('div');
|
1226 |
-
contextMenu.className = 'context-menu';
|
1227 |
contextMenu.innerHTML = `
|
1228 |
-
<div class="context-menu-item" data-action="edit-html">HTMLとして編集</div>
|
1229 |
-
<div class="context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
|
1230 |
-
<div class="context-menu-item" data-action="add-attribute">属性を追加</div>
|
1231 |
-
<div class="context-menu-item" data-action="edit-element">要素を編集</div>
|
1232 |
-
<div class="context-menu-item" data-action="duplicate">要素を複製</div>
|
1233 |
-
<div class="context-menu-item" data-action="remove">要素を削除</div>
|
1234 |
-
<div class="context-menu-item" data-action="toggle-visibility">要素を非表示</div>
|
1235 |
-
<div class="context-menu-item" data-action="force-state">状態を強制</div>
|
1236 |
`;
|
1237 |
document.body.appendChild(contextMenu);
|
1238 |
|
1239 |
-
contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
|
1240 |
item.addEventListener('click', (e) => {
|
1241 |
const action = e.target.getAttribute('data-action');
|
1242 |
handleContextMenuAction(action);
|
@@ -1254,7 +1250,7 @@
|
|
1254 |
// Storageパネル作成
|
1255 |
function createStoragePanel() {
|
1256 |
const panel = document.createElement('div');
|
1257 |
-
panel.className = 'devtools-panel';
|
1258 |
panel.id = 'storage-panel';
|
1259 |
|
1260 |
// LocalStorage表示
|
@@ -1263,11 +1259,11 @@
|
|
1263 |
panel.appendChild(localStorageTitle);
|
1264 |
|
1265 |
const localStorageTable = document.createElement('table');
|
1266 |
-
localStorageTable.className = 'storage-table';
|
1267 |
panel.appendChild(localStorageTable);
|
1268 |
|
1269 |
const addLocalStorageBtn = document.createElement('button');
|
1270 |
-
addLocalStorageBtn.className = 'add-btn';
|
1271 |
addLocalStorageBtn.textContent = '+ Local Storageに追加';
|
1272 |
addLocalStorageBtn.onclick = () => {
|
1273 |
const key = prompt('キー名を入力');
|
@@ -1286,11 +1282,11 @@
|
|
1286 |
panel.appendChild(sessionStorageTitle);
|
1287 |
|
1288 |
const sessionStorageTable = document.createElement('table');
|
1289 |
-
sessionStorageTable.className = 'storage-table';
|
1290 |
panel.appendChild(sessionStorageTable);
|
1291 |
|
1292 |
const addSessionStorageBtn = document.createElement('button');
|
1293 |
-
addSessionStorageBtn.className = 'add-btn';
|
1294 |
addSessionStorageBtn.textContent = '+ Session Storageに追加';
|
1295 |
addSessionStorageBtn.onclick = () => {
|
1296 |
const key = prompt('キー名を入力');
|
@@ -1309,11 +1305,11 @@
|
|
1309 |
panel.appendChild(cookiesTitle);
|
1310 |
|
1311 |
const cookiesTable = document.createElement('table');
|
1312 |
-
cookiesTable.className = 'storage-table';
|
1313 |
panel.appendChild(cookiesTable);
|
1314 |
|
1315 |
const addCookieBtn = document.createElement('button');
|
1316 |
-
addCookieBtn.className = 'add-btn';
|
1317 |
addCookieBtn.textContent = '+ Cookieに追加';
|
1318 |
addCookieBtn.onclick = () => {
|
1319 |
const name = prompt('Cookie名を入力');
|
@@ -1354,7 +1350,7 @@
|
|
1354 |
|
1355 |
const keyCell = document.createElement('td');
|
1356 |
const keySpan = document.createElement('span');
|
1357 |
-
keySpan.className = 'editable';
|
1358 |
keySpan.textContent = key;
|
1359 |
keySpan.onclick = () => {
|
1360 |
const newKey = prompt('新しいキー名を入力', key);
|
@@ -1368,7 +1364,7 @@
|
|
1368 |
|
1369 |
const valueCell = document.createElement('td');
|
1370 |
const valueSpan = document.createElement('span');
|
1371 |
-
valueSpan.className = 'editable';
|
1372 |
valueSpan.textContent = value;
|
1373 |
valueSpan.onclick = () => {
|
1374 |
const newValue = prompt('新しい値を入力', value);
|
@@ -1380,10 +1376,10 @@
|
|
1380 |
valueCell.appendChild(valueSpan);
|
1381 |
|
1382 |
const actionsCell = document.createElement('td');
|
1383 |
-
actionsCell.className = 'storage-actions';
|
1384 |
|
1385 |
const deleteBtn = document.createElement('button');
|
1386 |
-
deleteBtn.className = 'storage-btn';
|
1387 |
deleteBtn.textContent = 'Delete';
|
1388 |
deleteBtn.onclick = () => {
|
1389 |
storage.removeItem(key);
|
@@ -1425,7 +1421,7 @@
|
|
1425 |
|
1426 |
const nameCell = document.createElement('td');
|
1427 |
const nameSpan = document.createElement('span');
|
1428 |
-
nameSpan.className = 'editable';
|
1429 |
nameSpan.textContent = decodedName;
|
1430 |
nameSpan.onclick = () => {
|
1431 |
const newName = prompt('新しい名前を入力', decodedName);
|
@@ -1442,7 +1438,7 @@
|
|
1442 |
|
1443 |
const valueCell = document.createElement('td');
|
1444 |
const valueSpan = document.createElement('span');
|
1445 |
-
valueSpan.className = 'editable';
|
1446 |
valueSpan.textContent = decodeURIComponent(value);
|
1447 |
valueSpan.onclick = () => {
|
1448 |
const newValue = prompt('新しい値を入力', decodeURIComponent(value));
|
@@ -1454,10 +1450,10 @@
|
|
1454 |
valueCell.appendChild(valueSpan);
|
1455 |
|
1456 |
const actionsCell = document.createElement('td');
|
1457 |
-
actionsCell.className = 'storage-actions';
|
1458 |
|
1459 |
const deleteBtn = document.createElement('button');
|
1460 |
-
deleteBtn.className = 'storage-btn';
|
1461 |
deleteBtn.textContent = 'Delete';
|
1462 |
deleteBtn.onclick = () => {
|
1463 |
document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
@@ -1559,7 +1555,7 @@
|
|
1559 |
}
|
1560 |
break;
|
1561 |
case 'edit-element':
|
1562 |
-
startInlineEdit(selectedDOMNode.querySelector('.dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
|
1563 |
const newElement = document.createElement(newValue);
|
1564 |
Array.from(selectedElement.attributes).forEach(attr => {
|
1565 |
newElement.setAttribute(attr.name, attr.value);
|
@@ -1609,7 +1605,7 @@
|
|
1609 |
const rect = element.getBoundingClientRect();
|
1610 |
|
1611 |
const input = document.createElement('input');
|
1612 |
-
input.className = 'dom-edit-input';
|
1613 |
input.value = initialValue || originalValue;
|
1614 |
input.style.position = 'absolute';
|
1615 |
input.style.left = `${rect.left}px`;
|
@@ -1670,25 +1666,25 @@
|
|
1670 |
// Consoleパネル作成
|
1671 |
function createConsolePanel() {
|
1672 |
const panel = document.createElement('div');
|
1673 |
-
panel.className = 'devtools-panel';
|
1674 |
panel.id = 'console-panel';
|
1675 |
|
1676 |
const log = document.createElement('div');
|
1677 |
-
log.id = 'console-log';
|
1678 |
|
1679 |
const input = document.createElement('input');
|
1680 |
-
input.className = 'console-input';
|
1681 |
input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
|
1682 |
input.onkeypress = (e) => {
|
1683 |
if (e.key === 'Enter') {
|
1684 |
try {
|
1685 |
const result = eval(e.target.value);
|
1686 |
if (result !== undefined) {
|
1687 |
-
logMessage('> ' + e.target.value, 'console-log');
|
1688 |
-
logMessage('← ' + formatOutput(result), 'console-log');
|
1689 |
}
|
1690 |
} catch (err) {
|
1691 |
-
logMessage(err.message, 'console-error');
|
1692 |
}
|
1693 |
e.target.value = '';
|
1694 |
}
|
@@ -1707,22 +1703,22 @@
|
|
1707 |
|
1708 |
console.log = (...args) => {
|
1709 |
originalConsole.log.apply(console, args);
|
1710 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-log');
|
1711 |
};
|
1712 |
|
1713 |
console.error = (...args) => {
|
1714 |
originalConsole.error.apply(console, args);
|
1715 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-error');
|
1716 |
};
|
1717 |
|
1718 |
console.warn = (...args) => {
|
1719 |
originalConsole.warn.apply(console, args);
|
1720 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-warn');
|
1721 |
};
|
1722 |
|
1723 |
console.info = (...args) => {
|
1724 |
originalConsole.info.apply(console, args);
|
1725 |
-
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'console-info');
|
1726 |
};
|
1727 |
|
1728 |
function logMessage(message, className) {
|
@@ -1734,23 +1730,23 @@
|
|
1734 |
}
|
1735 |
|
1736 |
function formatOutput(output) {
|
1737 |
-
if (output === null) return '<span class="json-null">null</span>';
|
1738 |
-
if (output === undefined) return '<span class="json-null">undefined</span>';
|
1739 |
-
if (typeof output === 'boolean') return `<span class="json-boolean">${output}</span>`;
|
1740 |
-
if (typeof output === 'number') return `<span class="json-number">${output}</span>`;
|
1741 |
-
if (typeof output === 'string') return `<span class="json-string">"${output}"</span>`;
|
1742 |
-
if (typeof output === 'function') return `<span class="json-object">function ${output.name}() { ... }</span>`;
|
1743 |
-
if (Array.isArray(output)) return `<span class="json-object">[${output.map(formatOutput).join(', ')}]</span>`;
|
1744 |
if (typeof output === 'object') {
|
1745 |
try {
|
1746 |
-
return `<span class="json-object">${JSON.stringify(output, null, 2)
|
1747 |
-
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
|
1748 |
-
.replace(/"([^"]+)"/g, '<span class="json-string">"$1"</span>')
|
1749 |
-
.replace(/\b(true|false)\b/g, '<span class="json-boolean">$1</span>')
|
1750 |
-
.replace(/\b(null)\b/g, '<span class="json-null">$1</span>')
|
1751 |
-
.replace(/\b(\d+)\b/g, '<span class="json-number">$1</span>')}</span>`;
|
1752 |
} catch (e) {
|
1753 |
-
return `<span class="json-object">${output.toString()}</span>`;
|
1754 |
}
|
1755 |
}
|
1756 |
return output;
|
@@ -1762,19 +1758,19 @@
|
|
1762 |
// Elementsパネル作成
|
1763 |
function createElementsPanel() {
|
1764 |
const panel = document.createElement('div');
|
1765 |
-
panel.className = 'devtools-panel';
|
1766 |
panel.id = 'elements-panel';
|
1767 |
|
1768 |
const container = document.createElement('div');
|
1769 |
-
container.className = 'elements-container';
|
1770 |
|
1771 |
const tree = document.createElement('div');
|
1772 |
-
tree.className = 'dom-tree';
|
1773 |
-
tree.id = 'dom-tree';
|
1774 |
|
1775 |
const cssPanel = document.createElement('div');
|
1776 |
-
cssPanel.className = 'css-panel';
|
1777 |
-
cssPanel.id = 'css-panel';
|
1778 |
|
1779 |
container.appendChild(tree);
|
1780 |
container.appendChild(cssPanel);
|
@@ -1782,17 +1778,17 @@
|
|
1782 |
|
1783 |
// CSSパネル更新関数
|
1784 |
function updateCSSPanel(element) {
|
1785 |
-
const cssPanel = document.getElementById('css-panel');
|
1786 |
cssPanel.innerHTML = '';
|
1787 |
|
1788 |
if (!element) return;
|
1789 |
|
1790 |
if (element.style.length > 0) {
|
1791 |
const inlineRule = document.createElement('div');
|
1792 |
-
inlineRule.className = 'css-rule';
|
1793 |
|
1794 |
const selector = document.createElement('div');
|
1795 |
-
selector.className = 'css-selector';
|
1796 |
selector.textContent = 'インラインスタイル';
|
1797 |
inlineRule.appendChild(selector);
|
1798 |
|
@@ -1801,20 +1797,20 @@
|
|
1801 |
const propValue = element.style[propName];
|
1802 |
|
1803 |
const propDiv = document.createElement('div');
|
1804 |
-
propDiv.className = 'css-property';
|
1805 |
|
1806 |
const nameSpan = document.createElement('span');
|
1807 |
-
nameSpan.className = 'css-property-name editable';
|
1808 |
nameSpan.textContent = propName;
|
1809 |
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1810 |
|
1811 |
const valueSpan = document.createElement('span');
|
1812 |
-
valueSpan.className = 'css-property-value editable';
|
1813 |
valueSpan.textContent = propValue;
|
1814 |
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1815 |
|
1816 |
const toggleSpan = document.createElement('span');
|
1817 |
-
toggleSpan.className = 'css-toggle';
|
1818 |
toggleSpan.textContent = '×';
|
1819 |
toggleSpan.title = 'プロパティを無効化';
|
1820 |
toggleSpan.onclick = () => {
|
@@ -1833,10 +1829,10 @@
|
|
1833 |
|
1834 |
const computedStyles = window.getComputedStyle(element);
|
1835 |
const computedRule = document.createElement('div');
|
1836 |
-
computedRule.className = 'css-rule';
|
1837 |
|
1838 |
const computedSelector = document.createElement('div');
|
1839 |
-
computedSelector.className = 'css-selector';
|
1840 |
computedSelector.textContent = '計算されたスタイル';
|
1841 |
computedRule.appendChild(computedSelector);
|
1842 |
|
@@ -1849,14 +1845,14 @@
|
|
1849 |
const value = computedStyles[prop];
|
1850 |
|
1851 |
const propDiv = document.createElement('div');
|
1852 |
-
propDiv.className = 'css-property';
|
1853 |
|
1854 |
const nameSpan = document.createElement('span');
|
1855 |
-
nameSpan.className = 'css-property-name';
|
1856 |
nameSpan.textContent = prop;
|
1857 |
|
1858 |
const valueSpan = document.createElement('span');
|
1859 |
-
valueSpan.className = 'css-property-value';
|
1860 |
valueSpan.textContent = value;
|
1861 |
|
1862 |
propDiv.appendChild(nameSpan);
|
@@ -1871,9 +1867,9 @@
|
|
1871 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1872 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1873 |
const element = document.createElement('div');
|
1874 |
-
element.className = 'dom-node';
|
1875 |
element.style.marginLeft = `${depth * 15}px`;
|
1876 |
-
element.dataset.
|
1877 |
|
1878 |
// 右クリックイベント
|
1879 |
element.oncontextmenu = (e) => {
|
@@ -1881,7 +1877,7 @@
|
|
1881 |
selectedElement = node;
|
1882 |
selectedDOMNode = element;
|
1883 |
|
1884 |
-
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1885 |
element.classList.add('selected');
|
1886 |
|
1887 |
if (node !== document.documentElement) {
|
@@ -1895,13 +1891,13 @@
|
|
1895 |
|
1896 |
// 左クリックで選択
|
1897 |
element.onclick = (e) => {
|
1898 |
-
if (e.target.classList.contains('dom-toggle')) return;
|
1899 |
|
1900 |
e.stopPropagation();
|
1901 |
selectedElement = node;
|
1902 |
selectedDOMNode = element;
|
1903 |
|
1904 |
-
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1905 |
element.classList.add('selected');
|
1906 |
|
1907 |
updateCSSPanel(node);
|
@@ -1913,10 +1909,10 @@
|
|
1913 |
|
1914 |
if (hasChildren) {
|
1915 |
const toggle = document.createElement('div');
|
1916 |
-
toggle.className = 'dom-toggle';
|
1917 |
toggle.onclick = (e) => {
|
1918 |
e.stopPropagation();
|
1919 |
-
const children = element.querySelector('.dom-children');
|
1920 |
if (children) {
|
1921 |
if (children.style.maxHeight === '0px') {
|
1922 |
children.style.maxHeight = children.scrollHeight + 'px';
|
@@ -1932,11 +1928,11 @@
|
|
1932 |
|
1933 |
// タグ名(HTML要素は編集不可)
|
1934 |
const tag = document.createElement('span');
|
1935 |
-
tag.className = 'dom-tag';
|
1936 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
1937 |
|
1938 |
if (node !== document.documentElement) {
|
1939 |
-
tag.classList.add('editable');
|
1940 |
tag.onclick = (e) => {
|
1941 |
e.stopPropagation();
|
1942 |
startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
|
@@ -1957,11 +1953,11 @@
|
|
1957 |
// 属性(HTML要素は編集不可)
|
1958 |
Array.from(node.attributes).forEach(attr => {
|
1959 |
const attrSpan = document.createElement('span');
|
1960 |
-
attrSpan.className = 'dom-attr';
|
1961 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
1962 |
|
1963 |
if (node !== document.documentElement) {
|
1964 |
-
attrSpan.classList.add('editable');
|
1965 |
attrSpan.onclick = (e) => {
|
1966 |
e.stopPropagation();
|
1967 |
startInlineEdit(attrSpan, attr.value, (newValue) => {
|
@@ -1978,7 +1974,7 @@
|
|
1978 |
|
1979 |
if (hasChildren) {
|
1980 |
const childrenContainer = document.createElement('div');
|
1981 |
-
childrenContainer.className = 'dom-children';
|
1982 |
childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
|
1983 |
|
1984 |
node.childNodes.forEach(child => {
|
@@ -1988,7 +1984,7 @@
|
|
1988 |
if (node.tagName.toLowerCase() !== 'br') {
|
1989 |
const closeTag = document.createElement('div');
|
1990 |
closeTag.style.marginLeft = `${depth * 15}px`;
|
1991 |
-
closeTag.innerHTML = `<span class="dom-tag"></${node.tagName.toLowerCase()}></span>`;
|
1992 |
childrenContainer.appendChild(closeTag);
|
1993 |
}
|
1994 |
|
@@ -1999,7 +1995,7 @@
|
|
1999 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
2000 |
const text = document.createElement('div');
|
2001 |
text.style.marginLeft = `${depth * 15}px`;
|
2002 |
-
text.className = 'dom-text editable';
|
2003 |
text.textContent = `"${node.textContent.trim()}"`;
|
2004 |
text.onclick = (e) => {
|
2005 |
e.stopPropagation();
|
@@ -2034,17 +2030,17 @@
|
|
2034 |
|
2035 |
// refreshElementsPanel関数の定義
|
2036 |
refreshElementsPanel = function() {
|
2037 |
-
let tree = document.getElementById('dom-tree');
|
2038 |
|
2039 |
if (!tree) {
|
2040 |
const panel = document.getElementById('elements-panel');
|
2041 |
if (panel) {
|
2042 |
-
const container = panel.querySelector('.elements-container');
|
2043 |
if (container) {
|
2044 |
tree = document.createElement('div');
|
2045 |
-
tree.className = 'dom-tree';
|
2046 |
-
tree.id = 'dom-tree';
|
2047 |
-
container.insertBefore(tree, container.querySelector('.css-panel'));
|
2048 |
}
|
2049 |
}
|
2050 |
}
|
@@ -2056,10 +2052,10 @@
|
|
2056 |
|
2057 |
if (selectedElement) {
|
2058 |
const elementId = selectedElement.id || Array.from(selectedElement.attributes)
|
2059 |
-
.find(attr => attr.name.startsWith('data-element-id'))?.value;
|
2060 |
|
2061 |
if (elementId) {
|
2062 |
-
const node = document.querySelector(`[data-element-id="${elementId}"]`);
|
2063 |
if (node) {
|
2064 |
node.classList.add('selected');
|
2065 |
updateCSSPanel(selectedElement);
|
@@ -2081,7 +2077,7 @@
|
|
2081 |
|
2082 |
// 開発者ツール表示/非表示
|
2083 |
function toggleDevTools() {
|
2084 |
-
const container = document.getElementById('devtools-container');
|
2085 |
if (container.style.display === 'none') {
|
2086 |
container.style.display = 'flex';
|
2087 |
} else {
|
@@ -2092,13 +2088,13 @@
|
|
2092 |
// 開くボタン作成
|
2093 |
function createOpenButton() {
|
2094 |
const button = document.createElement('button');
|
2095 |
-
button.id = 'open-devtools-btn';
|
2096 |
button.textContent = '開発者ツールを開く';
|
2097 |
button.style.position = 'fixed';
|
2098 |
button.style.bottom = '10px';
|
2099 |
button.style.right = '10px';
|
2100 |
button.style.padding = '8px 16px';
|
2101 |
-
button.style.background = 'var(--primary-color)';
|
2102 |
button.style.color = '#000';
|
2103 |
button.style.border = 'none';
|
2104 |
button.style.borderRadius = '4px';
|
@@ -2111,9 +2107,26 @@
|
|
2111 |
|
2112 |
// 初期化
|
2113 |
document.addEventListener('DOMContentLoaded', function() {
|
2114 |
-
|
2115 |
createOpenButton();
|
2116 |
-
|
2117 |
-
|
|
|
|
|
|
|
|
|
|
|
2118 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2119 |
})();
|
|
|
17 |
let refreshElementsPanel = null;
|
18 |
let vitalsMeasurementStarted = false;
|
19 |
|
20 |
+
// スタイルの追加
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
const style = document.createElement('style');
|
22 |
style.textContent = `
|
23 |
+
.dt-root {
|
24 |
+
--dt-bg-color: #1e1e1e;
|
25 |
+
--dt-panel-bg: #252526;
|
26 |
+
--dt-border-color: #3c3c3c;
|
27 |
+
--dt-text-color: #e0e0e0;
|
28 |
+
--dt-text-muted: #a0a0a0;
|
29 |
+
--dt-primary-color: #007acc;
|
30 |
+
--dt-primary-hover: #3e9fda;
|
31 |
+
--dt-success-color: #4caf50;
|
32 |
+
--dt-error-color: #f44336;
|
33 |
+
--dt-warning-color: #ff9800;
|
34 |
+
--dt-info-color: #2196f3;
|
35 |
+
--dt-highlight-bg: rgba(0, 122, 204, 0.2);
|
36 |
+
--dt-tab-bg: #2d2d2d;
|
37 |
+
--dt-tab-active-bg: #1e1e1e;
|
38 |
+
--dt-console-log-color: #e0e0e0;
|
39 |
+
--dt-console-error-color: #f44336;
|
40 |
+
--dt-console-warn-color: #ff9800;
|
41 |
+
--dt-console-info-color: #4fc3f7;
|
42 |
+
--dt-json-key: #9cdcfe;
|
43 |
+
--dt-json-string: #ce9178;
|
44 |
+
--dt-json-number: #b5cea8;
|
45 |
+
--dt-json-boolean: #569cd6;
|
46 |
+
--dt-json-null: #569cd6;
|
47 |
+
--dt-dom-tag: #569cd6;
|
48 |
+
--dt-dom-attr: #9cdcfe;
|
49 |
+
--dt-dom-text: #d4d4d4;
|
50 |
+
}
|
51 |
+
|
52 |
+
.dt-devtools-container {
|
53 |
position: fixed;
|
54 |
bottom: 0;
|
55 |
left: 0;
|
56 |
width: 100%;
|
57 |
height: 300px;
|
58 |
+
background-color: var(--dt-panel-bg);
|
59 |
+
border-top: 1px solid var(--dt-border-color);
|
60 |
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
|
61 |
z-index: 9999;
|
62 |
display: flex;
|
63 |
flex-direction: column;
|
64 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
65 |
+
color: var(--dt-text-color);
|
66 |
}
|
67 |
|
68 |
+
.dt-devtools-header {
|
69 |
display: flex;
|
70 |
justify-content: space-between;
|
71 |
align-items: center;
|
72 |
padding: 5px 10px;
|
73 |
+
background-color: var(--dt-tab-bg);
|
74 |
+
border-bottom: 1px solid var(--dt-border-color);
|
75 |
}
|
76 |
|
77 |
+
.dt-devtools-tabs {
|
78 |
display: flex;
|
79 |
gap: 5px;
|
80 |
}
|
81 |
|
82 |
+
.dt-devtools-tab {
|
83 |
padding: 5px 10px;
|
84 |
cursor: pointer;
|
85 |
border-radius: 3px 3px 0 0;
|
86 |
+
background-color: var(--dt-tab-bg);
|
87 |
+
border: 1px solid var(--dt-border-color);
|
88 |
border-bottom: none;
|
89 |
font-size: 12px;
|
90 |
+
color: var(--dt-text-muted);
|
91 |
}
|
92 |
|
93 |
+
.dt-devtools-tab.active {
|
94 |
+
background-color: var(--dt-tab-active-bg);
|
95 |
+
color: var(--dt-text-color);
|
96 |
+
border-bottom: 1px solid var(--dt-tab-active-bg);
|
97 |
margin-bottom: -1px;
|
98 |
font-weight: bold;
|
99 |
}
|
100 |
|
101 |
+
.dt-devtools-close {
|
102 |
background: none;
|
103 |
border: none;
|
104 |
font-size: 16px;
|
105 |
cursor: pointer;
|
106 |
padding: 0 5px;
|
107 |
+
color: var(--dt-text-color);
|
108 |
}
|
109 |
|
110 |
+
.dt-devtools-content {
|
111 |
flex: 1;
|
112 |
overflow: auto;
|
113 |
position: relative;
|
114 |
+
background-color: var(--dt-panel-bg);
|
115 |
}
|
116 |
|
117 |
+
.dt-devtools-panel {
|
118 |
position: absolute;
|
119 |
top: 0;
|
120 |
left: 0;
|
|
|
123 |
padding: 10px;
|
124 |
overflow: auto;
|
125 |
display: none;
|
126 |
+
background-color: var(--dt-panel-bg);
|
127 |
}
|
128 |
|
129 |
+
.dt-devtools-panel.active {
|
130 |
display: block;
|
131 |
}
|
132 |
|
133 |
/* Console スタイル */
|
134 |
+
#dt-console-log {
|
135 |
white-space: pre-wrap;
|
136 |
margin: 0;
|
137 |
line-height: 1.4;
|
138 |
flex: 1;
|
139 |
+
color: var(--dt-console-log-color);
|
140 |
font-family: 'Consolas', 'Monaco', monospace;
|
141 |
font-size: 13px;
|
142 |
}
|
143 |
|
144 |
+
.dt-console-log {
|
145 |
+
color: var(--dt-console-log-color);
|
146 |
}
|
147 |
|
148 |
+
.dt-console-error {
|
149 |
+
color: var(--dt-console-error-color);
|
150 |
}
|
151 |
|
152 |
+
.dt-console-warn {
|
153 |
+
color: var(--dt-console-warn-color);
|
154 |
}
|
155 |
|
156 |
+
.dt-console-info {
|
157 |
+
color: var(--dt-console-info-color);
|
158 |
}
|
159 |
|
160 |
+
.dt-console-input {
|
161 |
width: calc(100% - 16px);
|
162 |
+
background: var(--dt-tab-bg);
|
163 |
+
border: 1px solid var(--dt-border-color);
|
164 |
+
color: var(--dt-text-color);
|
165 |
padding: 8px;
|
166 |
margin-top: 10px;
|
167 |
font-family: monospace;
|
|
|
169 |
}
|
170 |
|
171 |
/* Elements スタイル */
|
172 |
+
.dt-elements-container {
|
173 |
display: flex;
|
174 |
flex: 1;
|
175 |
overflow: hidden;
|
176 |
}
|
177 |
|
178 |
+
.dt-dom-tree {
|
179 |
font-family: 'Consolas', 'Monaco', monospace;
|
180 |
flex: 1;
|
181 |
overflow: auto;
|
182 |
+
border-right: 1px solid var(--dt-border-color);
|
183 |
padding-right: 10px;
|
184 |
+
color: var(--dt-dom-text);
|
185 |
font-size: 13px;
|
186 |
}
|
187 |
|
188 |
+
.dt-dom-node {
|
189 |
margin-left: 15px;
|
190 |
position: relative;
|
191 |
line-height: 1.4;
|
192 |
transition: background-color 0.3s;
|
193 |
}
|
194 |
|
195 |
+
.dt-dom-node.selected {
|
196 |
+
background: var(--dt-highlight-bg);
|
197 |
}
|
198 |
|
199 |
+
.dt-dom-node.highlight {
|
200 |
+
animation: dt-highlight-fade 1.5s;
|
201 |
}
|
202 |
|
203 |
+
@keyframes dt-highlight-fade {
|
204 |
0% { background-color: rgba(79, 195, 247, 0.5); }
|
205 |
100% { background-color: transparent; }
|
206 |
}
|
207 |
|
208 |
+
.dt-dom-tag {
|
209 |
+
color: var(--dt-dom-tag);
|
210 |
font-weight: bold;
|
211 |
}
|
212 |
|
213 |
+
.dt-dom-attr {
|
214 |
+
color: var(--dt-dom-attr);
|
215 |
}
|
216 |
|
217 |
+
.dt-dom-attr.editable:hover {
|
218 |
text-decoration: underline;
|
219 |
cursor: pointer;
|
220 |
}
|
221 |
|
222 |
+
.dt-dom-text {
|
223 |
+
color: var(--dt-dom-text);
|
224 |
}
|
225 |
|
226 |
+
.dt-dom-edit-input {
|
227 |
+
background: var(--dt-panel-bg);
|
228 |
+
border: 1px solid var(--dt-primary-color);
|
229 |
padding: 0 2px;
|
230 |
margin: -1px 0;
|
231 |
font-family: monospace;
|
232 |
min-width: 50px;
|
233 |
+
color: var(--dt-text-color);
|
234 |
}
|
235 |
|
236 |
+
.dt-css-panel {
|
237 |
flex: 1;
|
238 |
overflow: auto;
|
239 |
padding-left: 10px;
|
240 |
font-size: 13px;
|
241 |
}
|
242 |
|
243 |
+
.dt-css-rule {
|
244 |
margin-bottom: 15px;
|
245 |
+
border: 1px solid var(--dt-border-color);
|
246 |
padding: 8px;
|
247 |
+
background-color: var(--dt-tab-bg);
|
248 |
border-radius: 3px;
|
249 |
}
|
250 |
|
251 |
+
.dt-css-selector {
|
252 |
+
color: var(--dt-primary-color);
|
253 |
margin-bottom: 5px;
|
254 |
font-weight: bold;
|
255 |
}
|
256 |
|
257 |
+
.dt-css-property {
|
258 |
display: flex;
|
259 |
margin-bottom: 3px;
|
260 |
}
|
261 |
|
262 |
+
.dt-css-property-name {
|
263 |
+
color: var(--dt-dom-attr);
|
264 |
min-width: 120px;
|
265 |
}
|
266 |
|
267 |
+
.dt-css-property-value {
|
268 |
+
color: var(--dt-dom-text);
|
269 |
flex: 1;
|
270 |
}
|
271 |
|
272 |
+
.dt-css-toggle {
|
273 |
margin-left: 10px;
|
274 |
+
color: var(--dt-error-color);
|
275 |
cursor: pointer;
|
276 |
}
|
277 |
|
278 |
/* Context Menu */
|
279 |
+
.dt-context-menu {
|
280 |
position: absolute;
|
281 |
+
background: var(--dt-panel-bg);
|
282 |
+
border: 1px solid var(--dt-border-color);
|
283 |
z-index: 10000;
|
284 |
min-width: 200px;
|
285 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
|
288 |
overflow: hidden;
|
289 |
}
|
290 |
|
291 |
+
.dt-context-menu-item {
|
292 |
padding: 8px 15px;
|
293 |
cursor: pointer;
|
294 |
+
color: var(--dt-text-color);
|
295 |
font-size: 13px;
|
296 |
}
|
297 |
|
298 |
+
.dt-context-menu-item:hover {
|
299 |
+
background: var(--dt-primary-color);
|
300 |
color: #000;
|
301 |
}
|
302 |
|
303 |
/* Storage スタイル */
|
304 |
+
.dt-storage-table {
|
305 |
width: 100%;
|
306 |
border-collapse: collapse;
|
307 |
margin-bottom: 10px;
|
308 |
font-size: 13px;
|
309 |
}
|
310 |
|
311 |
+
.dt-storage-table th, .dt-storage-table td {
|
312 |
+
border: 1px solid var(--dt-border-color);
|
313 |
padding: 5px;
|
314 |
text-align: left;
|
315 |
}
|
316 |
|
317 |
+
.dt-storage-table th {
|
318 |
+
background: var(--dt-tab-bg);
|
319 |
}
|
320 |
|
321 |
+
.dt-storage-actions {
|
322 |
display: flex;
|
323 |
gap: 5px;
|
324 |
}
|
325 |
|
326 |
+
.dt-storage-btn {
|
327 |
+
background: var(--dt-primary-color);
|
328 |
border: none;
|
329 |
padding: 2px 5px;
|
330 |
cursor: pointer;
|
|
|
333 |
font-size: 12px;
|
334 |
}
|
335 |
|
336 |
+
.dt-editable {
|
337 |
cursor: pointer;
|
338 |
padding: 2px 5px;
|
339 |
border: 1px dashed transparent;
|
340 |
}
|
341 |
|
342 |
+
.dt-editable:hover {
|
343 |
+
border-color: var(--dt-primary-color);
|
344 |
}
|
345 |
|
346 |
+
.dt-add-btn {
|
347 |
+
background: var(--dt-primary-color);
|
348 |
color: #000;
|
349 |
border: none;
|
350 |
padding: 5px 10px;
|
|
|
354 |
font-size: 13px;
|
355 |
}
|
356 |
|
357 |
+
.dt-add-btn:hover {
|
358 |
+
background: var(--dt-primary-hover);
|
359 |
}
|
360 |
|
361 |
/* Network スタイル */
|
362 |
+
.dt-network-container {
|
363 |
display: flex;
|
364 |
height: 100%;
|
365 |
overflow: hidden;
|
366 |
}
|
367 |
|
368 |
+
.dt-network-requests {
|
369 |
width: 40%;
|
370 |
overflow-y: auto;
|
371 |
+
border-right: 1px solid var(--dt-border-color);
|
372 |
font-size: 13px;
|
373 |
}
|
374 |
|
375 |
+
.dt-network-details {
|
376 |
width: 60%;
|
377 |
overflow-y: auto;
|
378 |
padding-left: 10px;
|
379 |
}
|
380 |
|
381 |
+
.dt-network-request {
|
382 |
padding: 8px;
|
383 |
+
border-bottom: 1px solid var(--dt-border-color);
|
384 |
cursor: pointer;
|
385 |
display: flex;
|
386 |
align-items: center;
|
387 |
}
|
388 |
|
389 |
+
.dt-network-request:hover {
|
390 |
background-color: rgba(0, 122, 204, 0.1);
|
391 |
}
|
392 |
|
393 |
+
.dt-network-request.selected {
|
394 |
+
background-color: var(--dt-highlight-bg);
|
395 |
}
|
396 |
|
397 |
+
.dt-network-status {
|
398 |
width: 20px;
|
399 |
height: 20px;
|
400 |
border-radius: 50%;
|
|
|
405 |
flex-shrink: 0;
|
406 |
}
|
407 |
|
408 |
+
.dt-network-status.success {
|
409 |
+
background-color: var(--dt-success-color);
|
410 |
color: white;
|
411 |
}
|
412 |
|
413 |
+
.dt-network-status.error {
|
414 |
+
background-color: var(--dt-error-color);
|
415 |
color: white;
|
416 |
}
|
417 |
|
418 |
+
.dt-network-method {
|
419 |
font-weight: bold;
|
420 |
margin-right: 8px;
|
421 |
+
color: var(--dt-primary-color);
|
422 |
min-width: 40px;
|
423 |
}
|
424 |
|
425 |
+
.dt-network-url {
|
426 |
flex: 1;
|
427 |
white-space: nowrap;
|
428 |
overflow: hidden;
|
429 |
text-overflow: ellipsis;
|
430 |
}
|
431 |
|
432 |
+
.dt-network-time {
|
433 |
+
color: var(--dt-text-muted);
|
434 |
font-size: 11px;
|
435 |
margin-left: 8px;
|
436 |
}
|
437 |
|
438 |
+
.dt-network-detail-section {
|
439 |
margin-bottom: 15px;
|
440 |
}
|
441 |
|
442 |
+
.dt-network-detail-title {
|
443 |
font-weight: bold;
|
444 |
margin-bottom: 5px;
|
445 |
+
color: var(--dt-primary-color);
|
446 |
}
|
447 |
|
448 |
+
.dt-network-detail-content {
|
449 |
+
background: var(--dt-tab-bg);
|
450 |
padding: 8px;
|
451 |
border-radius: 3px;
|
452 |
+
border: 1px solid var(--dt-border-color);
|
453 |
font-family: monospace;
|
454 |
white-space: pre-wrap;
|
455 |
font-size: 12px;
|
|
|
458 |
}
|
459 |
|
460 |
/* Web Vitals スタイル */
|
461 |
+
.dt-vitals-container {
|
462 |
display: flex;
|
463 |
flex-direction: column;
|
464 |
gap: 15px;
|
465 |
}
|
466 |
|
467 |
+
.dt-vital-card {
|
468 |
+
background: var(--dt-tab-bg);
|
469 |
+
border: 1px solid var(--dt-border-color);
|
470 |
border-radius: 5px;
|
471 |
padding: 15px;
|
472 |
}
|
473 |
|
474 |
+
.dt-vital-title {
|
475 |
font-weight: bold;
|
476 |
margin-bottom: 10px;
|
477 |
+
color: var(--dt-primary-color);
|
478 |
display: flex;
|
479 |
justify-content: space-between;
|
480 |
align-items: center;
|
481 |
}
|
482 |
|
483 |
+
.dt-vital-value {
|
484 |
font-size: 24px;
|
485 |
font-weight: bold;
|
486 |
margin: 10px 0;
|
487 |
}
|
488 |
|
489 |
+
.dt-vital-good {
|
490 |
+
color: var(--dt-success-color);
|
491 |
}
|
492 |
|
493 |
+
.dt-vital-needs-improvement {
|
494 |
+
color: var(--dt-warning-color);
|
495 |
}
|
496 |
|
497 |
+
.dt-vital-poor {
|
498 |
+
color: var(--dt-error-color);
|
499 |
}
|
500 |
|
501 |
+
.dt-vital-description {
|
502 |
font-size: 13px;
|
503 |
+
color: var(--dt-text-muted);
|
504 |
}
|
505 |
|
506 |
+
.dt-vital-thresholds {
|
507 |
display: flex;
|
508 |
margin-top: 10px;
|
509 |
font-size: 12px;
|
510 |
}
|
511 |
|
512 |
+
.dt-vital-threshold {
|
513 |
flex: 1;
|
514 |
text-align: center;
|
515 |
padding: 5px;
|
516 |
border-radius: 3px;
|
517 |
}
|
518 |
|
519 |
+
.dt-vital-threshold.active {
|
520 |
background: rgba(0, 122, 204, 0.2);
|
521 |
}
|
522 |
|
523 |
/* DOM Tree Toggle */
|
524 |
+
.dt-dom-toggle {
|
525 |
position: absolute;
|
526 |
left: -12px;
|
527 |
top: 2px;
|
528 |
width: 10px;
|
529 |
height: 10px;
|
530 |
cursor: pointer;
|
531 |
+
background-color: var(--dt-text-muted);
|
532 |
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
533 |
transition: transform 0.2s;
|
534 |
}
|
535 |
|
536 |
+
.dt-dom-toggle.collapsed {
|
537 |
transform: rotate(-90deg);
|
538 |
}
|
539 |
|
540 |
+
.dt-dom-children {
|
541 |
overflow: hidden;
|
542 |
transition: max-height 0.3s ease-out;
|
543 |
}
|
544 |
|
545 |
/* JSON スタイル */
|
546 |
+
.dt-json-key {
|
547 |
+
color: var(--dt-json-key);
|
548 |
}
|
549 |
|
550 |
+
.dt-json-string {
|
551 |
+
color: var(--dt-json-string);
|
552 |
}
|
553 |
|
554 |
+
.dt-json-number {
|
555 |
+
color: var(--dt-json-number);
|
556 |
}
|
557 |
|
558 |
+
.dt-json-boolean {
|
559 |
+
color: var(--dt-json-boolean);
|
560 |
}
|
561 |
|
562 |
+
.dt-json-null {
|
563 |
+
color: var(--dt-json-null);
|
564 |
}
|
565 |
`;
|
566 |
document.head.appendChild(style);
|
567 |
+
|
568 |
// ノードをハイライト表示
|
569 |
function highlightNode(node) {
|
570 |
const elementId = node.id || Math.random().toString(36).substr(2, 9);
|
571 |
+
node.setAttribute('data-dt-element-id', elementId);
|
572 |
|
573 |
setTimeout(() => {
|
574 |
+
const domNode = document.querySelector(`[data-dt-element-id="${elementId}"]`);
|
575 |
if (domNode) {
|
576 |
+
domNode.classList.add('dt-highlight');
|
577 |
setTimeout(() => {
|
578 |
+
domNode.classList.remove('dt-highlight');
|
579 |
}, 1500);
|
580 |
}
|
581 |
}, 100);
|
582 |
}
|
583 |
|
584 |
+
// Web Vitalsスクリプトを動的に読み込み
|
585 |
+
const loadWebVitals = () => {
|
586 |
+
return new Promise((resolve) => {
|
587 |
+
if (window.webVitals) {
|
588 |
+
resolve();
|
589 |
+
return;
|
590 |
+
}
|
591 |
+
|
592 |
+
const webVitalsScript = document.createElement('script');
|
593 |
+
webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
|
594 |
+
webVitalsScript.onload = resolve;
|
595 |
+
document.head.appendChild(webVitalsScript);
|
596 |
+
});
|
597 |
+
};
|
598 |
+
|
599 |
+
// DOM変更を監視するMutationObserver
|
600 |
+
const setupMutationObserver = () => {
|
601 |
+
if (observer) observer.disconnect();
|
602 |
+
|
603 |
+
observer = new MutationObserver((mutations) => {
|
604 |
+
if (refreshElementsPanel) {
|
605 |
+
refreshElementsPanel();
|
606 |
+
}
|
607 |
+
});
|
608 |
+
|
609 |
+
observer.observe(document.documentElement, {
|
610 |
+
childList: true,
|
611 |
+
subtree: true,
|
612 |
+
attributes: false,
|
613 |
+
characterData: false
|
614 |
+
});
|
615 |
+
};
|
616 |
+
|
617 |
// 開発者ツールのメイン関数
|
618 |
const createDevTools = () => {
|
619 |
const container = document.createElement('div');
|
620 |
+
container.className = 'dt-devtools-container';
|
621 |
+
container.id = 'dt-devtools-container';
|
622 |
container.style.display = 'none';
|
623 |
|
624 |
// ヘッダー部分
|
625 |
const header = document.createElement('div');
|
626 |
+
header.className = 'dt-devtools-header';
|
627 |
|
628 |
const tabs = document.createElement('div');
|
629 |
+
tabs.className = 'dt-devtools-tabs';
|
630 |
|
631 |
const consoleTab = createTab('Console', 'console');
|
632 |
const elementsTab = createTab('Elements', 'elements');
|
|
|
641 |
tabs.appendChild(vitalsTab);
|
642 |
|
643 |
const closeBtn = document.createElement('button');
|
644 |
+
closeBtn.className = 'dt-devtools-close';
|
645 |
closeBtn.textContent = '×';
|
646 |
closeBtn.onclick = toggleDevTools;
|
647 |
|
|
|
650 |
|
651 |
// コンテンツ部分
|
652 |
const content = document.createElement('div');
|
653 |
+
content.className = 'dt-devtools-content';
|
654 |
|
655 |
const consolePanel = createConsolePanel();
|
656 |
const elementsPanel = createElementsPanel();
|
|
|
675 |
// タブ切り替え機能
|
676 |
function createTab(name, panelId) {
|
677 |
const tab = document.createElement('div');
|
678 |
+
tab.className = 'dt-devtools-tab';
|
679 |
tab.textContent = name;
|
680 |
tab.onclick = () => {
|
681 |
+
document.querySelectorAll('.dt-devtools-tab').forEach(t => t.classList.remove('active'));
|
682 |
+
document.querySelectorAll('.dt-devtools-panel').forEach(p => p.classList.remove('active'));
|
683 |
tab.classList.add('active');
|
684 |
document.getElementById(panelId + '-panel').classList.add('active');
|
685 |
|
|
|
696 |
// Web Vitalsパネル作成
|
697 |
function createVitalsPanel() {
|
698 |
const panel = document.createElement('div');
|
699 |
+
panel.className = 'dt-devtools-panel';
|
700 |
panel.id = 'vitals-panel';
|
701 |
|
702 |
const container = document.createElement('div');
|
703 |
+
container.className = 'dt-vitals-container';
|
704 |
panel.appendChild(container);
|
705 |
|
706 |
// 測定開始ボタン
|
707 |
const startButton = document.createElement('button');
|
708 |
+
startButton.className = 'dt-add-btn';
|
709 |
startButton.textContent = '測定を開始';
|
710 |
startButton.onclick = startVitalsMeasurement;
|
711 |
container.appendChild(startButton);
|
712 |
|
713 |
// CLS (Cumulative Layout Shift)
|
714 |
const clsCard = document.createElement('div');
|
715 |
+
clsCard.className = 'dt-vital-card';
|
716 |
clsCard.innerHTML = `
|
717 |
+
<div class="dt-vital-title">CLS (Cumulative Layout Shift) <span class="dt-vital-description">視覚的な安定性</span></div>
|
718 |
+
<div class="dt-vital-value" id="dt-cls-value">-</div>
|
719 |
+
<div class="dt-vital-thresholds">
|
720 |
+
<div class="dt-vital-threshold">Good: < 0.1</div>
|
721 |
+
<div class="dt-vital-threshold">Needs Improvement: < 0.25</div>
|
722 |
+
<div class="dt-vital-threshold">Poor: ≥ 0.25</div>
|
723 |
</div>
|
724 |
`;
|
725 |
container.appendChild(clsCard);
|
726 |
|
727 |
// FCP (First Contentful Paint)
|
728 |
const fcpCard = document.createElement('div');
|
729 |
+
fcpCard.className = 'dt-vital-card';
|
730 |
fcpCard.innerHTML = `
|
731 |
+
<div class="dt-vital-title">FCP (First Contentful Paint) <span class="dt-vital-description">最初のコンテンツ表示</span></div>
|
732 |
+
<div class="dt-vital-value" id="dt-fcp-value">-</div>
|
733 |
+
<div class="dt-vital-thresholds">
|
734 |
+
<div class="dt-vital-threshold">Good: < 1.8s</div>
|
735 |
+
<div class="dt-vital-threshold">Needs Improvement: < 3s</div>
|
736 |
+
<div class="dt-vital-threshold">Poor: ≥ 3s</div>
|
737 |
</div>
|
738 |
`;
|
739 |
container.appendChild(fcpCard);
|
740 |
|
741 |
// FID (First Input Delay)
|
742 |
const fidCard = document.createElement('div');
|
743 |
+
fidCard.className = 'dt-vital-card';
|
744 |
fidCard.innerHTML = `
|
745 |
+
<div class="dt-vital-title">FID (First Input Delay) <span class="dt-vital-description">最初の入力遅延</span></div>
|
746 |
+
<div class="dt-vital-value" id="dt-fid-value">-</div>
|
747 |
+
<div class="dt-vital-thresholds">
|
748 |
+
<div class="dt-vital-threshold">Good: < 100ms</div>
|
749 |
+
<div class="dt-vital-threshold">Needs Improvement: < 300ms</div>
|
750 |
+
<div class="dt-vital-threshold">Poor: ≥ 300ms</div>
|
751 |
</div>
|
752 |
`;
|
753 |
container.appendChild(fidCard);
|
754 |
|
755 |
// LCP (Largest Contentful Paint)
|
756 |
const lcpCard = document.createElement('div');
|
757 |
+
lcpCard.className = 'dt-vital-card';
|
758 |
lcpCard.innerHTML = `
|
759 |
+
<div class="dt-vital-title">LCP (Largest Contentful Paint) <span class="dt-vital-description">最大のコンテンツ表示</span></div>
|
760 |
+
<div class="dt-vital-value" id="dt-lcp-value">-</div>
|
761 |
+
<div class="dt-vital-thresholds">
|
762 |
+
<div class="dt-vital-threshold">Good: < 2.5s</div>
|
763 |
+
<div class="dt-vital-threshold">Needs Improvement: < 4s</div>
|
764 |
+
<div class="dt-vital-threshold">Poor: ≥ 4s</div>
|
765 |
</div>
|
766 |
`;
|
767 |
container.appendChild(lcpCard);
|
768 |
|
769 |
// TTFB (Time to First Byte)
|
770 |
const ttfbCard = document.createElement('div');
|
771 |
+
ttfbCard.className = 'dt-vital-card';
|
772 |
ttfbCard.innerHTML = `
|
773 |
+
<div class="dt-vital-title">TTFB (Time to First Byte) <span class="dt-vital-description">最初のバイト到達時間</span></div>
|
774 |
+
<div class="dt-vital-value" id="dt-ttfb-value">-</div>
|
775 |
+
<div class="dt-vital-thresholds">
|
776 |
+
<div class="dt-vital-threshold">Good: < 800ms</div>
|
777 |
+
<div class="dt-vital-threshold">Needs Improvement: < 1.8s</div>
|
778 |
+
<div class="dt-vital-threshold">Poor: ≥ 1.8s</div>
|
779 |
</div>
|
780 |
`;
|
781 |
container.appendChild(ttfbCard);
|
|
|
801 |
vitals.onLCP(updateLCP);
|
802 |
vitals.onTTFB(updateTTFB);
|
803 |
}
|
804 |
+
logMessage('Web Vitalsの測定を開始しました', 'dt-console-info');
|
805 |
} catch (e) {
|
806 |
+
logMessage('Web Vitals error: ' + e.message, 'dt-console-error');
|
807 |
}
|
808 |
}
|
809 |
});
|
|
|
835 |
}
|
836 |
|
837 |
function updateVitalDisplay(id, value, goodThreshold, needsImprovementThreshold) {
|
838 |
+
const element = document.getElementById(`dt-${id}-value`);
|
839 |
if (!element) return;
|
840 |
|
841 |
const displayValue = id === 'ttfb' ?
|
|
|
844 |
|
845 |
element.textContent = displayValue;
|
846 |
|
847 |
+
element.className = 'dt-vital-value';
|
848 |
if (value <= goodThreshold) {
|
849 |
+
element.classList.add('dt-vital-good');
|
850 |
} else if (value <= needsImprovementThreshold) {
|
851 |
+
element.classList.add('dt-vital-needs-improvement');
|
852 |
} else {
|
853 |
+
element.classList.add('dt-vital-poor');
|
854 |
}
|
855 |
|
856 |
+
const thresholds = element.parentElement.querySelectorAll('.dt-vital-threshold');
|
857 |
thresholds.forEach((threshold, index) => {
|
858 |
threshold.classList.remove('active');
|
859 |
if (
|
|
|
872 |
// ネットワークパネル作成
|
873 |
function createNetworkPanel() {
|
874 |
const panel = document.createElement('div');
|
875 |
+
panel.className = 'dt-devtools-panel';
|
876 |
panel.id = 'network-panel';
|
877 |
|
878 |
const container = document.createElement('div');
|
879 |
+
container.className = 'dt-network-container';
|
880 |
panel.appendChild(container);
|
881 |
|
882 |
// リクエストリスト
|
883 |
const requestsList = document.createElement('div');
|
884 |
+
requestsList.className = 'dt-network-requests';
|
885 |
container.appendChild(requestsList);
|
886 |
|
887 |
// 詳細パネル
|
888 |
const detailsPanel = document.createElement('div');
|
889 |
+
detailsPanel.className = 'dt-network-details';
|
890 |
container.appendChild(detailsPanel);
|
891 |
|
892 |
// ネットワークリクエストを監視
|
|
|
1047 |
const panel = document.getElementById('network-panel');
|
1048 |
if (!panel || !panel.classList.contains('active')) return;
|
1049 |
|
1050 |
+
const requestsList = panel.querySelector('.dt-network-requests');
|
1051 |
+
const detailsPanel = panel.querySelector('.dt-network-details');
|
1052 |
|
1053 |
requestsList.innerHTML = '';
|
1054 |
|
1055 |
networkRequests.forEach(request => {
|
1056 |
const requestElement = document.createElement('div');
|
1057 |
+
requestElement.className = 'dt-network-request';
|
1058 |
if (selectedRequest && selectedRequest.id === request.id) {
|
1059 |
requestElement.classList.add('selected');
|
1060 |
}
|
|
|
1066 |
};
|
1067 |
|
1068 |
const statusElement = document.createElement('div');
|
1069 |
+
statusElement.className = `dt-network-status ${request.status}`;
|
1070 |
statusElement.textContent = request.status === 'success' ? '✓' : '✕';
|
1071 |
|
1072 |
const methodElement = document.createElement('div');
|
1073 |
+
methodElement.className = 'dt-network-method';
|
1074 |
methodElement.textContent = request.method;
|
1075 |
|
1076 |
const urlElement = document.createElement('div');
|
1077 |
+
urlElement.className = 'dt-network-url';
|
1078 |
urlElement.textContent = request.url;
|
1079 |
|
1080 |
const timeElement = document.createElement('div');
|
1081 |
+
timeElement.className = 'dt-network-time';
|
1082 |
timeElement.textContent = request.duration ? `${Math.round(request.duration)}ms` : '';
|
1083 |
|
1084 |
requestElement.appendChild(statusElement);
|
|
|
1095 |
const panel = document.getElementById('network-panel');
|
1096 |
if (!panel || !selectedRequest) return;
|
1097 |
|
1098 |
+
const detailsPanel = panel.querySelector('.dt-network-details');
|
1099 |
detailsPanel.innerHTML = '';
|
1100 |
|
1101 |
// 一般情報
|
1102 |
const generalSection = document.createElement('div');
|
1103 |
+
generalSection.className = 'dt-network-detail-section';
|
1104 |
generalSection.innerHTML = `
|
1105 |
+
<div class="dt-network-detail-title">一般</div>
|
1106 |
+
<div class="dt-network-detail-content">
|
1107 |
<strong>URL:</strong> ${selectedRequest.url}<br>
|
1108 |
<strong>メソッド:</strong> ${selectedRequest.method}<br>
|
1109 |
<strong>ステータス:</strong> ${selectedRequest.response ? selectedRequest.response.status : '-'}<br>
|
|
|
1115 |
// リクエストヘッダー
|
1116 |
if (selectedRequest.requestHeaders) {
|
1117 |
const headersSection = document.createElement('div');
|
1118 |
+
headersSection.className = 'dt-network-detail-section';
|
1119 |
|
1120 |
const headersTitle = document.createElement('div');
|
1121 |
+
headersTitle.className = 'dt-network-detail-title';
|
1122 |
headersTitle.textContent = 'リクエストヘッダー';
|
1123 |
|
1124 |
const headersContent = document.createElement('div');
|
1125 |
+
headersContent.className = 'dt-network-detail-content';
|
1126 |
|
1127 |
if (typeof selectedRequest.requestHeaders === 'object' && !(selectedRequest.requestHeaders instanceof Headers)) {
|
1128 |
Object.entries(selectedRequest.requestHeaders).forEach(([key, value]) => {
|
|
|
1142 |
// リクエストボディ
|
1143 |
if (selectedRequest.requestBody) {
|
1144 |
const bodySection = document.createElement('div');
|
1145 |
+
bodySection.className = 'dt-network-detail-section';
|
1146 |
|
1147 |
const bodyTitle = document.createElement('div');
|
1148 |
+
bodyTitle.className = 'dt-network-detail-title';
|
1149 |
bodyTitle.textContent = 'リクエストボディ';
|
1150 |
|
1151 |
const bodyContent = document.createElement('div');
|
1152 |
+
bodyContent.className = 'dt-network-detail-content';
|
1153 |
|
1154 |
try {
|
1155 |
if (typeof selectedRequest.requestBody === 'string') {
|
|
|
1169 |
// レスポンス
|
1170 |
if (selectedRequest.response) {
|
1171 |
const responseSection = document.createElement('div');
|
1172 |
+
responseSection.className = 'dt-network-detail-section';
|
1173 |
|
1174 |
const responseTitle = document.createElement('div');
|
1175 |
+
responseTitle.className = 'dt-network-detail-title';
|
1176 |
responseTitle.textContent = 'レスポンス';
|
1177 |
|
1178 |
const responseContent = document.createElement('div');
|
1179 |
+
responseContent.className = 'dt-network-detail-content';
|
1180 |
|
1181 |
if (selectedRequest.response.body) {
|
1182 |
if (typeof selectedRequest.response.body === 'object') {
|
|
|
1196 |
// エラー
|
1197 |
if (selectedRequest.error) {
|
1198 |
const errorSection = document.createElement('div');
|
1199 |
+
errorSection.className = 'dt-network-detail-section';
|
1200 |
|
1201 |
const errorTitle = document.createElement('div');
|
1202 |
+
errorTitle.className = 'dt-network-detail-title';
|
1203 |
errorTitle.textContent = 'エラー';
|
1204 |
|
1205 |
const errorContent = document.createElement('div');
|
1206 |
+
errorContent.className = 'dt-network-detail-content';
|
1207 |
errorContent.textContent = `${selectedRequest.error.name}: ${selectedRequest.error.message}`;
|
1208 |
|
1209 |
if (selectedRequest.error.stack) {
|
|
|
1219 |
// コンテキストメニュー作成
|
1220 |
function createContextMenu() {
|
1221 |
contextMenu = document.createElement('div');
|
1222 |
+
contextMenu.className = 'dt-context-menu';
|
1223 |
contextMenu.innerHTML = `
|
1224 |
+
<div class="dt-context-menu-item" data-action="edit-html">HTMLとして編集</div>
|
1225 |
+
<div class="dt-context-menu-item" data-action="edit-whole-html">HTML全体を編集</div>
|
1226 |
+
<div class="dt-context-menu-item" data-action="add-attribute">属性を追加</div>
|
1227 |
+
<div class="dt-context-menu-item" data-action="edit-element">要素を編集</div>
|
1228 |
+
<div class="dt-context-menu-item" data-action="duplicate">要素を複製</div>
|
1229 |
+
<div class="dt-context-menu-item" data-action="remove">要素を削除</div>
|
1230 |
+
<div class="dt-context-menu-item" data-action="toggle-visibility">要素を非表示</div>
|
1231 |
+
<div class="dt-context-menu-item" data-action="force-state">状態を強制</div>
|
1232 |
`;
|
1233 |
document.body.appendChild(contextMenu);
|
1234 |
|
1235 |
+
contextMenu.querySelectorAll('.dt-context-menu-item').forEach(item => {
|
1236 |
item.addEventListener('click', (e) => {
|
1237 |
const action = e.target.getAttribute('data-action');
|
1238 |
handleContextMenuAction(action);
|
|
|
1250 |
// Storageパネル作成
|
1251 |
function createStoragePanel() {
|
1252 |
const panel = document.createElement('div');
|
1253 |
+
panel.className = 'dt-devtools-panel';
|
1254 |
panel.id = 'storage-panel';
|
1255 |
|
1256 |
// LocalStorage表示
|
|
|
1259 |
panel.appendChild(localStorageTitle);
|
1260 |
|
1261 |
const localStorageTable = document.createElement('table');
|
1262 |
+
localStorageTable.className = 'dt-storage-table';
|
1263 |
panel.appendChild(localStorageTable);
|
1264 |
|
1265 |
const addLocalStorageBtn = document.createElement('button');
|
1266 |
+
addLocalStorageBtn.className = 'dt-add-btn';
|
1267 |
addLocalStorageBtn.textContent = '+ Local Storageに追加';
|
1268 |
addLocalStorageBtn.onclick = () => {
|
1269 |
const key = prompt('キー名を入力');
|
|
|
1282 |
panel.appendChild(sessionStorageTitle);
|
1283 |
|
1284 |
const sessionStorageTable = document.createElement('table');
|
1285 |
+
sessionStorageTable.className = 'dt-storage-table';
|
1286 |
panel.appendChild(sessionStorageTable);
|
1287 |
|
1288 |
const addSessionStorageBtn = document.createElement('button');
|
1289 |
+
addSessionStorageBtn.className = 'dt-add-btn';
|
1290 |
addSessionStorageBtn.textContent = '+ Session Storageに追加';
|
1291 |
addSessionStorageBtn.onclick = () => {
|
1292 |
const key = prompt('キー名を入力');
|
|
|
1305 |
panel.appendChild(cookiesTitle);
|
1306 |
|
1307 |
const cookiesTable = document.createElement('table');
|
1308 |
+
cookiesTable.className = 'dt-storage-table';
|
1309 |
panel.appendChild(cookiesTable);
|
1310 |
|
1311 |
const addCookieBtn = document.createElement('button');
|
1312 |
+
addCookieBtn.className = 'dt-add-btn';
|
1313 |
addCookieBtn.textContent = '+ Cookieに追加';
|
1314 |
addCookieBtn.onclick = () => {
|
1315 |
const name = prompt('Cookie名を入力');
|
|
|
1350 |
|
1351 |
const keyCell = document.createElement('td');
|
1352 |
const keySpan = document.createElement('span');
|
1353 |
+
keySpan.className = 'dt-editable';
|
1354 |
keySpan.textContent = key;
|
1355 |
keySpan.onclick = () => {
|
1356 |
const newKey = prompt('新しいキー名を入力', key);
|
|
|
1364 |
|
1365 |
const valueCell = document.createElement('td');
|
1366 |
const valueSpan = document.createElement('span');
|
1367 |
+
valueSpan.className = 'dt-editable';
|
1368 |
valueSpan.textContent = value;
|
1369 |
valueSpan.onclick = () => {
|
1370 |
const newValue = prompt('新しい値を入力', value);
|
|
|
1376 |
valueCell.appendChild(valueSpan);
|
1377 |
|
1378 |
const actionsCell = document.createElement('td');
|
1379 |
+
actionsCell.className = 'dt-storage-actions';
|
1380 |
|
1381 |
const deleteBtn = document.createElement('button');
|
1382 |
+
deleteBtn.className = 'dt-storage-btn';
|
1383 |
deleteBtn.textContent = 'Delete';
|
1384 |
deleteBtn.onclick = () => {
|
1385 |
storage.removeItem(key);
|
|
|
1421 |
|
1422 |
const nameCell = document.createElement('td');
|
1423 |
const nameSpan = document.createElement('span');
|
1424 |
+
nameSpan.className = 'dt-editable';
|
1425 |
nameSpan.textContent = decodedName;
|
1426 |
nameSpan.onclick = () => {
|
1427 |
const newName = prompt('新しい名前を入力', decodedName);
|
|
|
1438 |
|
1439 |
const valueCell = document.createElement('td');
|
1440 |
const valueSpan = document.createElement('span');
|
1441 |
+
valueSpan.className = 'dt-editable';
|
1442 |
valueSpan.textContent = decodeURIComponent(value);
|
1443 |
valueSpan.onclick = () => {
|
1444 |
const newValue = prompt('新しい値を入力', decodeURIComponent(value));
|
|
|
1450 |
valueCell.appendChild(valueSpan);
|
1451 |
|
1452 |
const actionsCell = document.createElement('td');
|
1453 |
+
actionsCell.className = 'dt-storage-actions';
|
1454 |
|
1455 |
const deleteBtn = document.createElement('button');
|
1456 |
+
deleteBtn.className = 'dt-storage-btn';
|
1457 |
deleteBtn.textContent = 'Delete';
|
1458 |
deleteBtn.onclick = () => {
|
1459 |
document.cookie = `${name.trim()}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
|
1555 |
}
|
1556 |
break;
|
1557 |
case 'edit-element':
|
1558 |
+
startInlineEdit(selectedDOMNode.querySelector('.dt-dom-tag'), selectedElement.tagName.toLowerCase(), (newValue) => {
|
1559 |
const newElement = document.createElement(newValue);
|
1560 |
Array.from(selectedElement.attributes).forEach(attr => {
|
1561 |
newElement.setAttribute(attr.name, attr.value);
|
|
|
1605 |
const rect = element.getBoundingClientRect();
|
1606 |
|
1607 |
const input = document.createElement('input');
|
1608 |
+
input.className = 'dt-dom-edit-input';
|
1609 |
input.value = initialValue || originalValue;
|
1610 |
input.style.position = 'absolute';
|
1611 |
input.style.left = `${rect.left}px`;
|
|
|
1666 |
// Consoleパネル作成
|
1667 |
function createConsolePanel() {
|
1668 |
const panel = document.createElement('div');
|
1669 |
+
panel.className = 'dt-devtools-panel';
|
1670 |
panel.id = 'console-panel';
|
1671 |
|
1672 |
const log = document.createElement('div');
|
1673 |
+
log.id = 'dt-console-log';
|
1674 |
|
1675 |
const input = document.createElement('input');
|
1676 |
+
input.className = 'dt-console-input';
|
1677 |
input.placeholder = 'ここにJavaScriptを入力... (Enterで実行)';
|
1678 |
input.onkeypress = (e) => {
|
1679 |
if (e.key === 'Enter') {
|
1680 |
try {
|
1681 |
const result = eval(e.target.value);
|
1682 |
if (result !== undefined) {
|
1683 |
+
logMessage('> ' + e.target.value, 'dt-console-log');
|
1684 |
+
logMessage('← ' + formatOutput(result), 'dt-console-log');
|
1685 |
}
|
1686 |
} catch (err) {
|
1687 |
+
logMessage(err.message, 'dt-console-error');
|
1688 |
}
|
1689 |
e.target.value = '';
|
1690 |
}
|
|
|
1703 |
|
1704 |
console.log = (...args) => {
|
1705 |
originalConsole.log.apply(console, args);
|
1706 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-log');
|
1707 |
};
|
1708 |
|
1709 |
console.error = (...args) => {
|
1710 |
originalConsole.error.apply(console, args);
|
1711 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-error');
|
1712 |
};
|
1713 |
|
1714 |
console.warn = (...args) => {
|
1715 |
originalConsole.warn.apply(console, args);
|
1716 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-warn');
|
1717 |
};
|
1718 |
|
1719 |
console.info = (...args) => {
|
1720 |
originalConsole.info.apply(console, args);
|
1721 |
+
logMessage(args.map(arg => formatOutput(arg)).join(' '), 'dt-console-info');
|
1722 |
};
|
1723 |
|
1724 |
function logMessage(message, className) {
|
|
|
1730 |
}
|
1731 |
|
1732 |
function formatOutput(output) {
|
1733 |
+
if (output === null) return '<span class="dt-json-null">null</span>';
|
1734 |
+
if (output === undefined) return '<span class="dt-json-null">undefined</span>';
|
1735 |
+
if (typeof output === 'boolean') return `<span class="dt-json-boolean">${output}</span>`;
|
1736 |
+
if (typeof output === 'number') return `<span class="dt-json-number">${output}</span>`;
|
1737 |
+
if (typeof output === 'string') return `<span class="dt-json-string">"${output}"</span>`;
|
1738 |
+
if (typeof output === 'function') return `<span class="dt-json-object">function ${output.name}() { ... }</span>`;
|
1739 |
+
if (Array.isArray(output)) return `<span class="dt-json-object">[${output.map(formatOutput).join(', ')}]</span>`;
|
1740 |
if (typeof output === 'object') {
|
1741 |
try {
|
1742 |
+
return `<span class="dt-json-object">${JSON.stringify(output, null, 2)
|
1743 |
+
.replace(/"([^"]+)":/g, '<span class="dt-json-key">"$1"</span>:')
|
1744 |
+
.replace(/"([^"]+)"/g, '<span class="dt-json-string">"$1"</span>')
|
1745 |
+
.replace(/\b(true|false)\b/g, '<span class="dt-json-boolean">$1</span>')
|
1746 |
+
.replace(/\b(null)\b/g, '<span class="dt-json-null">$1</span>')
|
1747 |
+
.replace(/\b(\d+)\b/g, '<span class="dt-json-number">$1</span>')}</span>`;
|
1748 |
} catch (e) {
|
1749 |
+
return `<span class="dt-json-object">${output.toString()}</span>`;
|
1750 |
}
|
1751 |
}
|
1752 |
return output;
|
|
|
1758 |
// Elementsパネル作成
|
1759 |
function createElementsPanel() {
|
1760 |
const panel = document.createElement('div');
|
1761 |
+
panel.className = 'dt-devtools-panel';
|
1762 |
panel.id = 'elements-panel';
|
1763 |
|
1764 |
const container = document.createElement('div');
|
1765 |
+
container.className = 'dt-elements-container';
|
1766 |
|
1767 |
const tree = document.createElement('div');
|
1768 |
+
tree.className = 'dt-dom-tree';
|
1769 |
+
tree.id = 'dt-dom-tree';
|
1770 |
|
1771 |
const cssPanel = document.createElement('div');
|
1772 |
+
cssPanel.className = 'dt-css-panel';
|
1773 |
+
cssPanel.id = 'dt-css-panel';
|
1774 |
|
1775 |
container.appendChild(tree);
|
1776 |
container.appendChild(cssPanel);
|
|
|
1778 |
|
1779 |
// CSSパネル更新関数
|
1780 |
function updateCSSPanel(element) {
|
1781 |
+
const cssPanel = document.getElementById('dt-css-panel');
|
1782 |
cssPanel.innerHTML = '';
|
1783 |
|
1784 |
if (!element) return;
|
1785 |
|
1786 |
if (element.style.length > 0) {
|
1787 |
const inlineRule = document.createElement('div');
|
1788 |
+
inlineRule.className = 'dt-css-rule';
|
1789 |
|
1790 |
const selector = document.createElement('div');
|
1791 |
+
selector.className = 'dt-css-selector';
|
1792 |
selector.textContent = 'インラインスタイル';
|
1793 |
inlineRule.appendChild(selector);
|
1794 |
|
|
|
1797 |
const propValue = element.style[propName];
|
1798 |
|
1799 |
const propDiv = document.createElement('div');
|
1800 |
+
propDiv.className = 'dt-css-property';
|
1801 |
|
1802 |
const nameSpan = document.createElement('span');
|
1803 |
+
nameSpan.className = 'dt-css-property-name dt-editable';
|
1804 |
nameSpan.textContent = propName;
|
1805 |
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1806 |
|
1807 |
const valueSpan = document.createElement('span');
|
1808 |
+
valueSpan.className = 'dt-css-property-value dt-editable';
|
1809 |
valueSpan.textContent = propValue;
|
1810 |
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1811 |
|
1812 |
const toggleSpan = document.createElement('span');
|
1813 |
+
toggleSpan.className = 'dt-css-toggle';
|
1814 |
toggleSpan.textContent = '×';
|
1815 |
toggleSpan.title = 'プロパティを無効化';
|
1816 |
toggleSpan.onclick = () => {
|
|
|
1829 |
|
1830 |
const computedStyles = window.getComputedStyle(element);
|
1831 |
const computedRule = document.createElement('div');
|
1832 |
+
computedRule.className = 'dt-css-rule';
|
1833 |
|
1834 |
const computedSelector = document.createElement('div');
|
1835 |
+
computedSelector.className = 'dt-css-selector';
|
1836 |
computedSelector.textContent = '計算されたスタイル';
|
1837 |
computedRule.appendChild(computedSelector);
|
1838 |
|
|
|
1845 |
const value = computedStyles[prop];
|
1846 |
|
1847 |
const propDiv = document.createElement('div');
|
1848 |
+
propDiv.className = 'dt-css-property';
|
1849 |
|
1850 |
const nameSpan = document.createElement('span');
|
1851 |
+
nameSpan.className = 'dt-css-property-name';
|
1852 |
nameSpan.textContent = prop;
|
1853 |
|
1854 |
const valueSpan = document.createElement('span');
|
1855 |
+
valueSpan.className = 'dt-css-property-value';
|
1856 |
valueSpan.textContent = value;
|
1857 |
|
1858 |
propDiv.appendChild(nameSpan);
|
|
|
1867 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1868 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1869 |
const element = document.createElement('div');
|
1870 |
+
element.className = 'dt-dom-node';
|
1871 |
element.style.marginLeft = `${depth * 15}px`;
|
1872 |
+
element.dataset.dtElementId = node.id || Math.random().toString(36).substr(2, 9);
|
1873 |
|
1874 |
// 右クリックイベント
|
1875 |
element.oncontextmenu = (e) => {
|
|
|
1877 |
selectedElement = node;
|
1878 |
selectedDOMNode = element;
|
1879 |
|
1880 |
+
document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
|
1881 |
element.classList.add('selected');
|
1882 |
|
1883 |
if (node !== document.documentElement) {
|
|
|
1891 |
|
1892 |
// 左クリックで選択
|
1893 |
element.onclick = (e) => {
|
1894 |
+
if (e.target.classList.contains('dt-dom-toggle')) return;
|
1895 |
|
1896 |
e.stopPropagation();
|
1897 |
selectedElement = node;
|
1898 |
selectedDOMNode = element;
|
1899 |
|
1900 |
+
document.querySelectorAll('.dt-dom-node').forEach(el => el.classList.remove('selected'));
|
1901 |
element.classList.add('selected');
|
1902 |
|
1903 |
updateCSSPanel(node);
|
|
|
1909 |
|
1910 |
if (hasChildren) {
|
1911 |
const toggle = document.createElement('div');
|
1912 |
+
toggle.className = 'dt-dom-toggle';
|
1913 |
toggle.onclick = (e) => {
|
1914 |
e.stopPropagation();
|
1915 |
+
const children = element.querySelector('.dt-dom-children');
|
1916 |
if (children) {
|
1917 |
if (children.style.maxHeight === '0px') {
|
1918 |
children.style.maxHeight = children.scrollHeight + 'px';
|
|
|
1928 |
|
1929 |
// タグ名(HTML要素は編集不可)
|
1930 |
const tag = document.createElement('span');
|
1931 |
+
tag.className = 'dt-dom-tag';
|
1932 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
1933 |
|
1934 |
if (node !== document.documentElement) {
|
1935 |
+
tag.classList.add('dt-editable');
|
1936 |
tag.onclick = (e) => {
|
1937 |
e.stopPropagation();
|
1938 |
startInlineEdit(tag, node.tagName.toLowerCase(), (newValue) => {
|
|
|
1953 |
// 属性(HTML要素は編集不可)
|
1954 |
Array.from(node.attributes).forEach(attr => {
|
1955 |
const attrSpan = document.createElement('span');
|
1956 |
+
attrSpan.className = 'dt-dom-attr';
|
1957 |
attrSpan.textContent = ` ${attr.name}="${attr.value}"`;
|
1958 |
|
1959 |
if (node !== document.documentElement) {
|
1960 |
+
attrSpan.classList.add('dt-editable');
|
1961 |
attrSpan.onclick = (e) => {
|
1962 |
e.stopPropagation();
|
1963 |
startInlineEdit(attrSpan, attr.value, (newValue) => {
|
|
|
1974 |
|
1975 |
if (hasChildren) {
|
1976 |
const childrenContainer = document.createElement('div');
|
1977 |
+
childrenContainer.className = 'dt-dom-children';
|
1978 |
childrenContainer.style.maxHeight = isRoot ? 'none' : '0px';
|
1979 |
|
1980 |
node.childNodes.forEach(child => {
|
|
|
1984 |
if (node.tagName.toLowerCase() !== 'br') {
|
1985 |
const closeTag = document.createElement('div');
|
1986 |
closeTag.style.marginLeft = `${depth * 15}px`;
|
1987 |
+
closeTag.innerHTML = `<span class="dt-dom-tag"></${node.tagName.toLowerCase()}></span>`;
|
1988 |
childrenContainer.appendChild(closeTag);
|
1989 |
}
|
1990 |
|
|
|
1995 |
} else if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
1996 |
const text = document.createElement('div');
|
1997 |
text.style.marginLeft = `${depth * 15}px`;
|
1998 |
+
text.className = 'dt-dom-text dt-editable';
|
1999 |
text.textContent = `"${node.textContent.trim()}"`;
|
2000 |
text.onclick = (e) => {
|
2001 |
e.stopPropagation();
|
|
|
2030 |
|
2031 |
// refreshElementsPanel関数の定義
|
2032 |
refreshElementsPanel = function() {
|
2033 |
+
let tree = document.getElementById('dt-dom-tree');
|
2034 |
|
2035 |
if (!tree) {
|
2036 |
const panel = document.getElementById('elements-panel');
|
2037 |
if (panel) {
|
2038 |
+
const container = panel.querySelector('.dt-elements-container');
|
2039 |
if (container) {
|
2040 |
tree = document.createElement('div');
|
2041 |
+
tree.className = 'dt-dom-tree';
|
2042 |
+
tree.id = 'dt-dom-tree';
|
2043 |
+
container.insertBefore(tree, container.querySelector('.dt-css-panel'));
|
2044 |
}
|
2045 |
}
|
2046 |
}
|
|
|
2052 |
|
2053 |
if (selectedElement) {
|
2054 |
const elementId = selectedElement.id || Array.from(selectedElement.attributes)
|
2055 |
+
.find(attr => attr.name.startsWith('data-dt-element-id'))?.value;
|
2056 |
|
2057 |
if (elementId) {
|
2058 |
+
const node = document.querySelector(`[data-dt-element-id="${elementId}"]`);
|
2059 |
if (node) {
|
2060 |
node.classList.add('selected');
|
2061 |
updateCSSPanel(selectedElement);
|
|
|
2077 |
|
2078 |
// 開発者ツール表示/非表示
|
2079 |
function toggleDevTools() {
|
2080 |
+
const container = document.getElementById('dt-devtools-container');
|
2081 |
if (container.style.display === 'none') {
|
2082 |
container.style.display = 'flex';
|
2083 |
} else {
|
|
|
2088 |
// 開くボタン作成
|
2089 |
function createOpenButton() {
|
2090 |
const button = document.createElement('button');
|
2091 |
+
button.id = 'dt-open-devtools-btn';
|
2092 |
button.textContent = '開発者ツールを開く';
|
2093 |
button.style.position = 'fixed';
|
2094 |
button.style.bottom = '10px';
|
2095 |
button.style.right = '10px';
|
2096 |
button.style.padding = '8px 16px';
|
2097 |
+
button.style.background = 'var(--dt-primary-color)';
|
2098 |
button.style.color = '#000';
|
2099 |
button.style.border = 'none';
|
2100 |
button.style.borderRadius = '4px';
|
|
|
2107 |
|
2108 |
// 初期化
|
2109 |
document.addEventListener('DOMContentLoaded', function() {
|
2110 |
+
// 最小限のUIだけ先に表示
|
2111 |
createOpenButton();
|
2112 |
+
|
2113 |
+
// メインのツールは遅延読み込み
|
2114 |
+
setTimeout(() => {
|
2115 |
+
createDevTools();
|
2116 |
+
logMessage('開発者ツールが初期化されました', 'dt-console-info');
|
2117 |
+
logMessage('このコンソールでJavaScriptを実行できます', 'dt-console-info');
|
2118 |
+
}, 1000);
|
2119 |
});
|
2120 |
+
|
2121 |
+
// ログメッセージ用ヘルパー関数
|
2122 |
+
function logMessage(message, className) {
|
2123 |
+
const log = document.getElementById('dt-console-log');
|
2124 |
+
if (!log) return;
|
2125 |
+
|
2126 |
+
const line = document.createElement('div');
|
2127 |
+
line.className = className;
|
2128 |
+
line.innerHTML = message;
|
2129 |
+
log.appendChild(line);
|
2130 |
+
log.scrollTop = log.scrollHeight;
|
2131 |
+
}
|
2132 |
})();
|