results.webkit.org: Move legend into sidebar
authorjbedard@apple.com <jbedard@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Sep 2019 21:52:16 +0000 (21:52 +0000)
committerjbedard@apple.com <jbedard@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 3 Sep 2019 21:52:16 +0000 (21:52 +0000)
https://bugs.webkit.org/show_bug.cgi?id=201258

Rubber-stamped by Aakash Jain.

* resultsdbpy/resultsdbpy/view/static/js/timeline.js:
(Legend): Make the legend vertical instead of horizontal, add ToolTip to dots in the legend.
* resultsdbpy/resultsdbpy/view/static/js/tooltip.css: Add left and right tooltip arrows.
* resultsdbpy/resultsdbpy/view/static/js/tooltip.js:
(isPointInElement): Make bound check include borders.
(_ToolTip.toString): Add left/right cases.
(_ToolTip.prototype.setByElement): Set the tooltip location given an element.
* resultsdbpy/resultsdbpy/view/templates/search.html: Put the legend into the sidebar.
* resultsdbpy/resultsdbpy/view/templates/suite_results.html: Ditto.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249443 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/resultsdbpy/resultsdbpy/view/static/css/timeline.css
Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css
Tools/resultsdbpy/resultsdbpy/view/static/js/drawer.js
Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js
Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js
Tools/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css
Tools/resultsdbpy/resultsdbpy/view/templates/search.html
Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html

index 47e506f..b7667fd 100644 (file)
@@ -1,5 +1,22 @@
 2019-09-03  Jonathan Bedard  <jbedard@apple.com>
 
+        results.webkit.org: Move legend into sidebar
+        https://bugs.webkit.org/show_bug.cgi?id=201258
+
+        Rubber-stamped by Aakash Jain.
+
+        * resultsdbpy/resultsdbpy/view/static/js/timeline.js: 
+        (Legend): Make the legend vertical instead of horizontal, add ToolTip to dots in the legend.
+        * resultsdbpy/resultsdbpy/view/static/js/tooltip.css: Add left and right tooltip arrows.
+        * resultsdbpy/resultsdbpy/view/static/js/tooltip.js:
+        (isPointInElement): Make bound check include borders.
+        (_ToolTip.toString): Add left/right cases.
+        (_ToolTip.prototype.setByElement): Set the tooltip location given an element.
+        * resultsdbpy/resultsdbpy/view/templates/search.html: Put the legend into the sidebar.
+        * resultsdbpy/resultsdbpy/view/templates/suite_results.html: Ditto.
+
+2019-09-03  Jonathan Bedard  <jbedard@apple.com>
+
         results.webkit.org: Increase default limit for LimitSlider
         https://bugs.webkit.org/show_bug.cgi?id=201424
 
index abd9bef..ed8f1cb 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-.dot.timeout {
+.dot.timedout {
     background-color: var(--orangeLight);
 }
 
-.dot.crash {
+.dot.crashed {
     background-color: var(--purpleLight);
 }
index 385a365..f307c33 100644 (file)
     border-color: #cccd transparent transparent transparent;
 }
 
+.tooltip.arrow-left {
+    border-color: transparent transparent transparent #cccd;
+}
+.tooltip.arrow-right {
+     border-color: transparent #cccd transparent  transparent;
+}
+
 .tooltip-content {
-    z-index: 50;
+    z-index: var(--middleZIndex);
     position: absolute;
     -webkit-backdrop-filter: blur(10px) brightness(88%);
     backdrop-filter: blur(10px) brightness(88%);
@@ -54,4 +61,6 @@
     font-size: var(--smallSize);
     list-style: none;
     max-width: 600px;
+    min-width: 30px;
+    min-height: 30px;
 }
index 5faccea..f77d794 100644 (file)
@@ -74,8 +74,13 @@ function Drawer(controls = [], onCollapseChange) {
             sidebarControl.onclick = () => {
                 if (element.style.display)
                     element.style.display = null;
-                else
+                else {
+                    for (let node of element.children) {
+                        if (node.classList.contains("list"))
+                            setEnableRecursive(node, true);
+                    }
                     element.style.display = 'block';
+                }
             }
         }
     });
index 5a5325c..37647f0 100644 (file)
@@ -244,7 +244,8 @@ function xAxisFromScale(scale, repository, updatesArray, isTop=false)
             );
         },
         onScaleLeave: (event, canvas) => {
-            if (!ToolTip.isIn({x: event.x, y: event.y}))
+            const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+            if (!ToolTip.isIn({x: event.x, y: event.y - scrollDelta}))
                 ToolTip.unset();
         },
         // Per the birthday paradox, 10% change of collision with 7.7 million commits with 12 character commits
@@ -642,7 +643,8 @@ class TimelineFromEndpoint {
         }
 
         function onDotLeave(event, canvas) {
-            if (!ToolTip.isIn({x: event.pageX, y: event.pageY}))
+            const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+            if (!ToolTip.isIn({x: event.pageX, y: event.pageY - scrollDelta}))
                 ToolTip.unset();
         }
 
@@ -801,41 +803,50 @@ function LegendLabel(eventStream, filterExpectedText, filterUnexpectedText) {
 
 function Legend(callback=null, plural=false) {
     let updateLabelEvents = new EventStream();
-    let result = `<br>
-         <div class="lengend timeline">
-            <div class="item">
-                <div class="dot success"><div class="text">${TestResultsSymbolMap.success}</div></div>
-                ${LegendLabel(
-                    updateLabelEvents,
-                    plural ? 'No unexpected results' : 'Result expected',
-                    plural ? 'All tests passed' : 'Test passed',
-                )}
-            </div>
-            <div class="item">
-                <div class="dot failed"><div class="text">${TestResultsSymbolMap.failed}</div></div>
-                ${LegendLabel(
-                    updateLabelEvents,
-                    plural ? 'Some tests unexpectedly failed' : 'Unexpectedly failed',
-                    plural ? 'Some tests failed' : 'Test failed',
-                )}
-            </div>
-            <div class="item">
-                <div class="dot timeout"><div class="text">${TestResultsSymbolMap.timedout}</div></div>
-                ${LegendLabel(
-                    updateLabelEvents,
-                    plural ? 'Some tests unexpectedly timed out' : 'Unexpectedly timed out',
-                    plural ? 'Some tests timed out' : 'Test timed out',
-                )}
-            </div>
-            <div class="item">
-                <div class="dot crash"><div class="text">${TestResultsSymbolMap.crashed}</div></div>
-                ${LegendLabel(
-                    updateLabelEvents,
-                    plural ? 'Some tests unexpectedly crashed' : 'Unexpectedly crashed',
-                    plural ? 'Some tests crashed' : 'Test crashed',
-                )}
-            </div>
-            <br>
+    const legendDetails = {
+        success: {
+            expected: plural ? 'No unexpected results' : 'Result expected',
+            unexpected: plural ? 'All tests passed' : 'Test passed',
+        },
+        failed: {
+            expected: plural ? 'Some tests unexpectedly failed' : 'Unexpectedly failed',
+            unexpected: plural ? 'Some tests failed' : 'Test failed',
+        },
+        timedout: {
+            expected: plural ? 'Some tests unexpectedly timed out' : 'Unexpectedly timed out',
+            unexpected: plural ? 'Some tests timed out' : 'Test timed out',
+        },
+        crashed: {
+            expected: plural ? 'Some tests unexpectedly crashed' : 'Unexpectedly crashed',
+            unexpected: plural ? 'Some tests crashed' : 'Test crashed',
+        },
+    };
+    let result = `<div class="lengend horizontal">
+            ${Object.keys(legendDetails).map((key) => {
+                const dot = REF.createRef({
+                    onElementMount: (element) => {
+                        element.addEventListener('mouseleave', (event) => {
+                            if (!ToolTip.isIn({x: event.x, y: event.y}))
+                                ToolTip.unset();
+                        });
+                        element.onmouseover = (event) => {
+                            if (!element.classList.contains('disabled'))
+                                return;
+                            ToolTip.setByElement(
+                                `<div class="content">
+                                    ${willFilterExpected ? legendDetails[key].expected : legendDetails[key].unexpected}
+                                </div>`,
+                                element,
+                                {orientation: ToolTip.HORIZONTAL},
+                            );
+                        };
+                    }
+                });
+                return `<div class="item">
+                        <div class="dot ${key}" ref="${dot}"><div class="text">${TestResultsSymbolMap[key]}</div></div>
+                        ${LegendLabel(updateLabelEvents, legendDetails[key].expected, legendDetails[key].unexpected)}
+                    </div>`
+            }).join('')}
         </div>`;
 
     if (callback) {
@@ -852,8 +863,8 @@ function Legend(callback=null, plural=false) {
             },
         });
 
-        result += `<div class="input" style="width:400px">
-            <label>Filter expected results</label>
+        result += `<div class="input">
+            <label style="font-size: var(--tinySize); color: var(--boldInverseColor)">Filter expected results</label>
             <label class="switch">
                 <input type="checkbox"${willFilterExpected ? ' checked': ''} ref="${swtch}">
                 <span class="slider"></span>
@@ -861,7 +872,7 @@ function Legend(callback=null, plural=false) {
         </div>`;
     }
 
-    return `<div class="content">${result}</div><br>`;
+    return `${result}`;
 }
 
 export {Legend, TimelineFromEndpoint, Expectations};
index 7390e8e..0f452a3 100644 (file)
@@ -28,7 +28,7 @@ function isPointInElement(element, point)
     if (!element || element.style.display == 'none')
         return false;
     const bounds = element.getBoundingClientRect();
-    return point.x >= bounds.left && point.x <= bounds.right && point.y >= bounds.top && point.y <= bounds.bottom;
+    return point.x >= bounds.left - 1 && point.x <= bounds.right + 1 && point.y >= bounds.top - 1 && point.y <= bounds.bottom + 1;
 }
 
 class _ToolTip {
@@ -36,6 +36,9 @@ class _ToolTip {
         this.ref = null;
         this.arrow = null;
         this.onArrowClick = null;
+
+        this.VERTICAL = 0;
+        this.HORIZONTAL = 1;
     }
     toString() {
         const self = this;
@@ -43,6 +46,8 @@ class _ToolTip {
             state: {content: null, points: null},
             onElementMount: (element) => {
                 element.addEventListener('mouseleave', (event) => {
+                    if (element.style.display === 'none')
+                        return;
                     if (!isPointInElement(self.arrow.element, event))
                         this.unset()
                 });
@@ -51,11 +56,11 @@ class _ToolTip {
                 if (stateDiff.content) {
                     DOM.inject(element, stateDiff.content);
                     element.style.display = null;
-                }
-                if (!state.content && !element.style.display) {
+                } else {
                     element.style.display = 'none';
                     DOM.inject(element, '');
                 }
+
                 if (stateDiff.points) {
                     element.style.left = '0px';
                     element.style.top = '0px';
@@ -67,23 +72,48 @@ class _ToolTip {
                     const viewportWitdh = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
                     const viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
 
-                    // Make an effort to place the tooltip in the center of the viewport.
                     let direction = 'down';
-                    let tipY = upperPoint.y - 8 - bounds.height;
                     let point = upperPoint;
-                    if (tipY < scrollDelta || tipY + bounds.height + (lowerPoint.y - upperPoint.y) / 2 < scrollDelta + viewportHeight / 2) {
-                        direction = 'up';
-                        tipY = lowerPoint.y + 16;
-                        point = lowerPoint;
-                    }
-                    element.style.top = `${tipY}px`;
 
-                    let tipX = point.x - bounds.width / 2;
-                    if (tipX + bounds.width > viewportWitdh)
-                        tipX = viewportWitdh - bounds.width;
-                    if (tipX < 0)
-                        tipX = 0;
-                    element.style.left = `${tipX}px`;
+                    if (upperPoint.y == lowerPoint.y) {
+                        // Horizontal tooltip
+                        const leftPoint = stateDiff.points.length > 1 && stateDiff.points[0].x > stateDiff.points[1].x ? stateDiff.points[1] : stateDiff.points[0];
+                        const rightPoint = stateDiff.points.length > 1 && stateDiff.points[1].x > stateDiff.points[0].x ? stateDiff.points[1] : stateDiff.points[0];
+
+                        direction = 'left';
+                        let tipX = leftPoint.x - 12 - bounds.width;
+                        point = rightPoint;
+                        if (tipX < 0 || tipX + bounds.width + (rightPoint.x - leftPoint.x) / 2 < viewportWitdh / 2) {
+                            direction = 'right';
+                            tipX = rightPoint.x + 16;
+                            point = rightPoint;
+                        }
+                        element.style.left = `${tipX}px`;
+
+                        let tipY = point.y - bounds.height / 2;
+                        if (tipY + bounds.height > scrollDelta + viewportHeight)
+                            tipY = scrollDelta + viewportHeight - bounds.height;
+                        if (tipY < 0)
+                            tipY = 0;
+                        element.style.top = `${tipY}px`;
+                    } else {
+                        // Make an effort to place the tooltip in the center of the viewport.
+                        let tipY = upperPoint.y - 8 - bounds.height;
+                        point = upperPoint;
+                        if (tipY < scrollDelta || tipY + bounds.height + (lowerPoint.y - upperPoint.y) / 2 < scrollDelta + viewportHeight / 2) {
+                            direction = 'up';
+                            tipY = lowerPoint.y + 16;
+                            point = lowerPoint;
+                        }
+                        element.style.top = `${tipY}px`;
+
+                        let tipX = point.x - bounds.width / 2;
+                        if (tipX + bounds.width > viewportWitdh)
+                            tipX = viewportWitdh - bounds.width;
+                        if (tipX < 0)
+                            tipX = 0;
+                        element.style.left = `${tipX}px`;
+                    }
 
                     self.arrow.setState({direction: direction, location: point});
                 }
@@ -93,12 +123,14 @@ class _ToolTip {
             state: {direction: null, location: null},
             onElementMount: (element) => {
                 element.addEventListener('mouseleave', (event) => {
-                    if (!isPointInElement(self.ref.element, event))
+                    if (element.style.display === 'none')
+                        return;
+                    if (!isPointInElement(self.ref.element, event) && !isPointInElement(element, event))
                         this.unset()
                 });
             },
-            onStateUpdate: (element, stateDiff, state) => {
-                if (!state.direction || !state.location) {
+            onStateUpdate: (element, stateDiff) => {
+                if (!stateDiff.direction || !stateDiff.location) {
                     element.style.display = 'none';
                     element.onclick = null;
                     element.style.cursor = null;
@@ -113,12 +145,21 @@ class _ToolTip {
                     element.style.cursor = null;
                 }
 
-                element.classList = [`tooltip arrow-${state.direction}`];
-                element.style.left = `${state.location.x - 15}px`;
-                if (state.direction == 'down')
-                    element.style.top = `${state.location.y - 8}px`;
-                else
-                    element.style.top = `${state.location.y - 13}px`;
+                element.classList = [`tooltip arrow-${stateDiff.direction}`];
+                
+                if (stateDiff.direction == 'down') {
+                    element.style.left = `${stateDiff.location.x - 15}px`;
+                    element.style.top = `${stateDiff.location.y - 8}px`;
+                } else if (stateDiff.direction == 'left') {
+                    element.style.left = `${stateDiff.location.x - 30}px`;
+                    element.style.top = `${stateDiff.location.y - 15}px`;
+                } else if (stateDiff.direction == 'right') {
+                    element.style.left = `${stateDiff.location.x - 13}px`;
+                    element.style.top = `${stateDiff.location.y - 15}px`;
+                } else {
+                    element.style.left = `${stateDiff.location.x - 15}px`;
+                    element.style.top = `${stateDiff.location.y - 13}px`;
+                }
                 element.style.display = null;
             },
 
@@ -139,6 +180,28 @@ class _ToolTip {
         this.onArrowClick = onArrowClick;
         this.ref.setState({content: content, points: points});
     }
+    setByElement(content, element, options) {
+        const bound = element.getBoundingClientRect();
+        const orientation = options.orientation ? options.orientation : this.VERTICAL;
+        const onArrowClick = options.onArrowClick ? options.onArrowClick : null;
+
+        // Manage the scroll delta
+        let scrollDelta = 0;
+        if (window.getComputedStyle(element.offsetParent).getPropertyValue('position') == 'fixed')
+            scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+
+        if (options.orientation) {
+            this.set(content, [
+                {x: bound.right, y: (bound.top + bound.bottom) / 2 + scrollDelta},
+                {x: bound.left, y: (bound.top + bound.bottom) / 2 + scrollDelta},
+            ], onArrowClick);
+        } else {
+            this.set(content, [
+                {x: (bound.right + bound.left) / 2, y: bound.top + scrollDelta},
+                {x: (bound.right + bound.left) / 2, y: bound.bottom + scrollDelta},
+            ], onArrowClick);
+        }
+    }
     unset() {
         if (this.ref)
             this.ref.setState({content: null, points: null});
index 3db25d5..802a6b4 100644 (file)
@@ -90,6 +90,7 @@ Copyright (C) 2019 Apple Inc. All rights reserved.
   --colPaddingSize: 4px;
   --sectionPaddingSize: 12px 24px;
   --formLabelPadding: 14px;
+  --middleZIndex: 50;
   --topZIndex: 100;
   --boldInverseColor: var(--black);
 }
@@ -922,7 +923,7 @@ pre {
   background-color: var(--blurBackgroundColor);
   -webkit-backdrop-filter: blur(5px) brightness(88%);
   backdrop-filter: blur(5px) brightness(88%);
-  z-index: var(--topZIndex);
+  z-index: 50;
   right: 0;
 }
 .sidebar.hidden {
index 6cabda4..fa3aa43 100644 (file)
@@ -128,11 +128,7 @@ class SearchView {
                 }
 
                 if (diff.children)
-                    DOM.inject(element, Legend(() => {
-                        self.ref.state.children.forEach((child) => {
-                            child.timeline.update();
-                        });
-                    }, false) + diff.children.map(renderChild).join(''));
+                    DOM.inject(element, diff.children.map(renderChild).join(''));
                 if (diff.prepending) {
                     diff.prepending.forEach(child => {
                         DOM.after(element.firstElementChild, renderChild(child));
@@ -227,6 +223,11 @@ DOM.inject(
     document.getElementById('app'),
     `${ToolTip}
     ${Drawer([
+        Legend(() => {
+            view.children.forEach((child) => {
+                child.timeline.update();
+            });
+        }, false),
         LimitSlider(() => {view.reload()}),
         BranchSelector(() => {
             CommitBank.reload();
index ea98b99..6ff5736 100644 (file)
@@ -39,7 +39,7 @@ import {ToolTip} from '/assets/js/tooltip.js';
 import {DOM, REF, EventStream} from '/library/js/Ref.js';
 
 const DEFAULT_LIMIT = 100;
-const SUITES = JSON.parse('{{ suites|safe }}');    
+const SUITES = JSON.parse('{{ suites|safe }}');
 
 class MainView {
     constructor() {    
@@ -143,11 +143,7 @@ class MainView {
     }
     render(suites) {
         let children = this.children;
-        return Legend(() => {
-            for (let suite in children) {
-                children[suite].update();
-            }
-        }, true) + suites.map(suite => {
+        return suites.map(suite => {
             return `<div class="section">
                     <div class="header">
                         <div class="title">${suite}</div>
@@ -171,6 +167,11 @@ onLayoutChange.action(() => {
 
 DOM.inject(document.getElementById('app'), `${ToolTip}
 ${Drawer([
+    Legend(() => {
+        for (let suite in view.children) {
+            view.children[suite].update();
+        }
+    }, true),
     LimitSlider(() => {view.reload()}),
     BranchSelector(() => {
         CommitBank.reload();