Skip to content

Commit 924469c

Browse files
committed
feature symfony#21109 [Profiler][VarDumper] Add a search feature to the HtmlDumper (ogizanagi)
This PR was squashed before being merged into the 3.3-dev branch (closes symfony#21109). Discussion ---------- [Profiler][VarDumper] Add a search feature to the HtmlDumper | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#20873 | License | MIT | Doc PR | N/A I finally took some time to continue this. Here is the result so far: | Raw | Profiler | Profiler Bar | |:-------------:|:-------------:|:-----:| | ![sfdump](https://cloud.githubusercontent.com/assets/2211145/21567183/4edfcd98-ceaa-11e6-9c51-873663551f32.gif) | ![sfdump profiler](https://cloud.githubusercontent.com/assets/2211145/21567181/4eb8013c-ceaa-11e6-8ed9-f9cb40090d55.gif) | ![sfdump profilerbar](https://cloud.githubusercontent.com/assets/2211145/21567180/4eb71740-ceaa-11e6-92dd-cc61d68a8def.gif) | New outputs: | Raw | Profiler Bar | |:-------------:|:-----:| | ![capture d ecran 2017-01-03 a 16 20 54](https://cloud.githubusercontent.com/assets/2211145/21612323/f6b275c4-d1d0-11e6-9f78-059940fe1c2f.png) | <img width="245" alt="capture d ecran 2017-01-03 a 16 21 34" src="https://cloud.githubusercontent.com/assets/2211145/21612337/0641ad34-d1d1-11e6-92ac-a0fff2721241.png"> | | Profiler Dump Panel | Profiler - Request Panel | |:-------------:|:-----:| | ![capture d ecran 2017-01-03 a 16 21 57](https://cloud.githubusercontent.com/assets/2211145/21612329/00a0ead4-d1d1-11e6-958e-e11bc87c0a7d.png) | ![capture d ecran 2017-01-03 a 16 22 22](https://cloud.githubusercontent.com/assets/2211145/21612330/00a2135a-d1d1-11e6-867d-18c55b86897e.png) ![capture d ecran 2017-01-03 a 16 22 33](https://cloud.githubusercontent.com/assets/2211145/21612331/00a2e000-d1d1-11e6-8f2a-2965a837fa60.png) | Usage: 1. Click on the dump 1. <kbd>CTRL</kbd>/<kbd>CMD</kbd> + <kbd>F</kbd>. 1. <kbd>ESC</kbd> to quit. Code, styles and rendering might not be perfect, but I think it's enough polished to be considered. Anyway, I'll accept any PR on my own branch if anyone wishes to contribute to it 😃 ~~I moved javascript and css into dedicated files, as I find it easier to maintain (but will complicate diff with other branches). I know PHPStorm is already able to do partial language injection in PHP files, so it might not be needed... But still looks more elegant to me ^^'~~ Commits ------- 1fe82fa [Profiler][VarDumper] Add a search feature to the HtmlDumper
2 parents be85fcc + 1fe82fa commit 924469c

File tree

2 files changed

+302
-0
lines changed

2 files changed

+302
-0
lines changed

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@
383383
.sf-toolbar-block-dump pre.sf-dump:last-child {
384384
margin-bottom: 0;
385385
}
386+
.sf-toolbar-block-dump pre.sf-dump span.sf-dump-search-count {
387+
color: #333;
388+
font-size: 12px;
389+
}
386390
.sf-toolbar-block-dump .sf-toolbar-info-piece {
387391
display: block;
388392
}

src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,81 @@ function toggle(a, recursive) {
184184
return true;
185185
};
186186
187+
function collapse(a, recursive) {
188+
var s = a.nextSibling || {}, oldClass = s.className;
189+
190+
if ('sf-dump-expanded' == oldClass) {
191+
toggle(a, recursive);
192+
193+
return true;
194+
}
195+
196+
return false;
197+
};
198+
199+
function expand(a, recursive) {
200+
var s = a.nextSibling || {}, oldClass = s.className;
201+
202+
if ('sf-dump-compact' == oldClass) {
203+
toggle(a, recursive);
204+
205+
return true;
206+
}
207+
208+
return false;
209+
};
210+
211+
function collapseAll(root) {
212+
var a = root.querySelector('a.sf-dump-toggle');
213+
if (a) {
214+
collapse(a, true);
215+
expand(a);
216+
217+
return true;
218+
}
219+
220+
return false;
221+
}
222+
223+
function reveal(node) {
224+
var previous, parents = [];
225+
226+
while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) {
227+
parents.push(previous);
228+
}
229+
230+
if (0 !== parents.length) {
231+
parents.forEach(function (parent) {
232+
expand(parent);
233+
});
234+
235+
return true;
236+
}
237+
238+
return false;
239+
}
240+
241+
function highlight(root, activeNode, nodes) {
242+
resetHighlightedNodes(root);
243+
244+
Array.from(nodes||[]).forEach(function (node) {
245+
if (!/\bsf-dump-highlight\b/.test(node.className)) {
246+
node.className = node.className + ' sf-dump-highlight';
247+
}
248+
});
249+
250+
if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) {
251+
activeNode.className = activeNode.className + ' sf-dump-highlight-active';
252+
}
253+
}
254+
255+
function resetHighlightedNodes(root) {
256+
Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) {
257+
strNode.className = strNode.className.replace(/\b sf-dump-highlight\b/, '');
258+
strNode.className = strNode.className.replace(/\b sf-dump-highlight-active\b/, '');
259+
});
260+
}
261+
187262
return function (root, x) {
188263
root = doc.getElementById(root);
189264
@@ -214,6 +289,20 @@ function a(e, f) {
214289
function isCtrlKey(e) {
215290
return e.ctrlKey || e.metaKey;
216291
}
292+
function xpathString(str) {
293+
var parts = str.match(/[^'"]+|['"]/g).map(function (part) {
294+
if ("'" == part) {
295+
return '"\'"';
296+
}
297+
if ('"' == part) {
298+
return "'\"'";
299+
}
300+
301+
return "'" + part + "'";
302+
});
303+
304+
return "concat(" + parts.join(",") + ", '')";
305+
}
217306
addEventListener(root, 'mouseover', function (e) {
218307
if ('' != refStyle.innerHTML) {
219308
refStyle.innerHTML = '';
@@ -324,6 +413,134 @@ function isCtrlKey(e) {
324413
}
325414
}
326415
416+
if (doc.evaluate && Array.from && root.children.length > 1) {
417+
root.setAttribute('tabindex', 0);
418+
419+
SearchState = function () {
420+
this.nodes = [];
421+
this.idx = 0;
422+
};
423+
SearchState.prototype = {
424+
next: function () {
425+
if (this.isEmpty()) {
426+
return this.current();
427+
}
428+
this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : this.idx;
429+
430+
return this.current();
431+
},
432+
previous: function () {
433+
if (this.isEmpty()) {
434+
return this.current();
435+
}
436+
this.idx = this.idx > 0 ? this.idx - 1 : this.idx;
437+
438+
return this.current();
439+
},
440+
isEmpty: function () {
441+
return 0 === this.count();
442+
},
443+
current: function () {
444+
if (this.isEmpty()) {
445+
return null;
446+
}
447+
return this.nodes[this.idx];
448+
},
449+
reset: function () {
450+
this.nodes = [];
451+
this.idx = 0;
452+
},
453+
count: function () {
454+
return this.nodes.length;
455+
},
456+
};
457+
458+
function showCurrent(state)
459+
{
460+
var currentNode = state.current();
461+
if (currentNode) {
462+
reveal(currentNode);
463+
highlight(root, currentNode, state.nodes);
464+
}
465+
counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' on ' + state.count();
466+
}
467+
468+
var search = doc.createElement('div');
469+
search.className = 'sf-dump-search-wrapper sf-dump-search-hidden';
470+
search.innerHTML = '
471+
<input type="text" class="sf-dump-search-input">
472+
<span class="sf-dump-search-count">0 on 0<\/span>
473+
<button type="button" class="sf-dump-search-input-previous" tabindex="-1">
474+
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
475+
<path d="M1683 1331l-166 165q-19 19-45 19t-45-19l-531-531-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/>
476+
<\/svg>
477+
<\/button>
478+
<button type="button" class="sf-dump-search-input-next" tabindex="-1">
479+
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
480+
<path d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/>
481+
<\/svg>
482+
<\/button>
483+
';
484+
root.insertBefore(search, root.firstChild);
485+
486+
var state = new SearchState();
487+
var searchInput = search.querySelector('.sf-dump-search-input');
488+
var counter = search.querySelector('.sf-dump-search-count');
489+
var searchInputTimer = 0;
490+
491+
addEventListener(searchInput, 'keydown', function (e) {
492+
/* Don't intercept escape key in order to not start a search */
493+
if (27 === e.keyCode) {
494+
return;
495+
}
496+
497+
clearTimeout(searchInputTimer);
498+
searchInputTimer = setTimeout(function () {
499+
state.reset();
500+
collapseAll(root);
501+
resetHighlightedNodes(root);
502+
var searchQuery = e.target.value;
503+
if ('' === searchQuery) {
504+
counter.textContent = '0 on 0';
505+
506+
return;
507+
}
508+
509+
var xpathResult = doc.evaluate('//pre[@id="' + root.id + '"]//span[@class="sf-dump-str" or @class="sf-dump-key" or @class="sf-dump-public" or @class="sf-dump-protected" or @class="sf-dump-private"][contains(child::text(), ' + xpathString(searchQuery) + ')]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
510+
511+
while (node = xpathResult.iterateNext()) state.nodes.push(node);
512+
513+
showCurrent(state);
514+
}, 400);
515+
});
516+
517+
Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) {
518+
addEventListener(btn, 'click', function (e) {
519+
e.preventDefault();
520+
var direction = -1 !== e.target.className.indexOf('next') ? 'next' : 'previous';
521+
'next' === direction ? state.next() : state.previous();
522+
searchInput.focus();
523+
collapseAll(root);
524+
showCurrent(state);
525+
})
526+
});
527+
528+
addEventListener(root, 'keydown', function (e) {
529+
if (114 === e.keyCode || (isCtrlKey(e) && 70 === e.keyCode)) {
530+
/* CTRL + F or CMD + F */
531+
e.preventDefault();
532+
search.className = search.className.replace(/\bsf-dump-search-hidden\b/, '');
533+
searchInput.focus();
534+
} else if (27 === e.keyCode && !/\bsf-dump-search-hidden\b/.test(search.className)) {
535+
/* ESC key */
536+
search.className += ' sf-dump-search-hidden';
537+
e.preventDefault();
538+
resetHighlightedNodes(root);
539+
searchInput.value = '';
540+
}
541+
});
542+
}
543+
327544
if (0 >= options.maxStringLength) {
328545
return;
329546
}
@@ -359,6 +576,13 @@ function isCtrlKey(e) {
359576
white-space: pre;
360577
padding: 5px;
361578
}
579+
pre.sf-dump:after {
580+
content: "";
581+
visibility: hidden;
582+
display: block;
583+
height: 0;
584+
clear: both;
585+
}
362586
pre.sf-dump span {
363587
display: inline;
364588
}
@@ -397,6 +621,80 @@ function isCtrlKey(e) {
397621
.sf-dump-str-expand .sf-dump-str-expand {
398622
display: none;
399623
}
624+
.sf-dump-public.sf-dump-highlight,
625+
.sf-dump-protected.sf-dump-highlight,
626+
.sf-dump-private.sf-dump-highlight,
627+
.sf-dump-str.sf-dump-highlight,
628+
.sf-dump-key.sf-dump-highlight {
629+
background: rgba(111, 172, 204, 0.3);
630+
border: 1px solid #7DA0B1;
631+
border-radius: 3px;
632+
}
633+
.sf-dump-public.sf-dump-highlight-active,
634+
.sf-dump-protected.sf-dump-highlight-active,
635+
.sf-dump-private.sf-dump-highlight-active,
636+
.sf-dump-str.sf-dump-highlight-active,
637+
.sf-dump-key.sf-dump-highlight-active {
638+
background: rgba(253, 175, 0, 0.4);
639+
border: 1px solid #ffa500;
640+
border-radius: 3px;
641+
}
642+
.sf-dump-search-hidden {
643+
display: none;
644+
}
645+
.sf-dump-search-wrapper {
646+
float: right;
647+
font-size: 0;
648+
white-space: nowrap;
649+
max-width: 100%;
650+
text-align: right;
651+
}
652+
.sf-dump-search-wrapper > * {
653+
vertical-align: top;
654+
box-sizing: border-box;
655+
height: 21px;
656+
font-weight: normal;
657+
border-radius: 0;
658+
background: #FFF;
659+
color: #757575;
660+
border: 1px solid #BBB;
661+
}
662+
.sf-dump-search-wrapper > input.sf-dump-search-input {
663+
padding: 3px;
664+
height: 21px;
665+
font-size: 12px;
666+
border-right: none;
667+
width: 140px;
668+
border-top-left-radius: 3px;
669+
border-bottom-left-radius: 3px;
670+
color: #000;
671+
}
672+
.sf-dump-search-wrapper > .sf-dump-search-input-next,
673+
.sf-dump-search-wrapper > .sf-dump-search-input-previous {
674+
background: #F2F2F2;
675+
outline: none;
676+
border-left: none;
677+
font-size: 0;
678+
line-height: 0;
679+
}
680+
.sf-dump-search-wrapper > .sf-dump-search-input-next {
681+
border-top-right-radius: 3px;
682+
border-bottom-right-radius: 3px;
683+
}
684+
.sf-dump-search-wrapper > .sf-dump-search-input-next > svg,
685+
.sf-dump-search-wrapper > .sf-dump-search-input-previous > svg {
686+
pointer-events: none;
687+
width: 12px;
688+
height: 12px;
689+
}
690+
.sf-dump-search-wrapper > .sf-dump-search-count {
691+
display: inline-block;
692+
padding: 0 5px;
693+
margin: 0;
694+
border-left: none;
695+
line-height: 21px;
696+
font-size: 12px;
697+
}
400698
EOHTML
401699
);
402700

0 commit comments

Comments
 (0)