Title: [248910] trunk/Tools
Revision
248910
Author
jbed...@apple.com
Date
2019-08-20 11:55:34 -0700 (Tue, 20 Aug 2019)

Log Message

results.webkit.org: Add ToolTips
https://bugs.webkit.org/show_bug.cgi?id=200801

Rubber-stamped by Aakash Jain.

When dots or scale labels are hovered over, we should display a tool tip with additional information about
The specific element.

* resultsdbpy/resultsdbpy/view/static/css/tooltip.css: Added.
(.tooltip): Add class for ToolTip text box.
(.tooltip-arrow-up): Add class for ToolTip arrow pointing up.
(.tooltip-arrow-down): Add class for ToolTip arrow pointing down.
* resultsdbpy/resultsdbpy/view/static/js/commit.js:
(_CommitBank.prototype.commitsDuringUuid): Return a list of commits which were the trunk of their respective
repositories at a given time.
* resultsdbpy/resultsdbpy/view/static/js/timeline.js:
(xAxisFromScale): Add callbacks triggered when the mouse enters or leaves elements in the scale canvas.
(TimelineFromEndpoint.render): Add callbacks triggered when the mouse enters or leaves dot elements.
* resultsdbpy/resultsdbpy/view/static/js/tooltip.js: Added.
(isPointInElement): Given an element and a point, return true if that point is within the bounds of the element.
(_ToolTip):
(_ToolTip.prototype.set): Set the content and location of the ToolTip.
(_ToolTip.prototype.toString): Return the html needed to render the ToolTip.
(_ToolTip.prototype.unset): Clear and hide the ToolTip.
(_ToolTip.prototype.isIn): Check if a given point is contained within the ToolTip.
* resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js:
(Timeline.CanvasSeriesComponent): Convert onHover events to onEnter/onLeave events. Add toolTips points to both
dot and scale elements.
* resultsdbpy/resultsdbpy/view/templates/search.html: Add ToolTip.
* resultsdbpy/resultsdbpy/view/templates/suite_results.html: Ditto.

Modified Paths

Added Paths

Diff

Modified: trunk/Tools/ChangeLog (248909 => 248910)


--- trunk/Tools/ChangeLog	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/ChangeLog	2019-08-20 18:55:34 UTC (rev 248910)
@@ -1,3 +1,36 @@
+2019-08-20  Jonathan Bedard  <jbed...@apple.com>
+
+        results.webkit.org: Add ToolTips
+        https://bugs.webkit.org/show_bug.cgi?id=200801
+
+        Rubber-stamped by Aakash Jain.
+
+        When dots or scale labels are hovered over, we should display a tool tip with additional information about
+        The specific element.
+
+        * resultsdbpy/resultsdbpy/view/static/css/tooltip.css: Added.
+        (.tooltip): Add class for ToolTip text box.
+        (.tooltip-arrow-up): Add class for ToolTip arrow pointing up.
+        (.tooltip-arrow-down): Add class for ToolTip arrow pointing down.
+        * resultsdbpy/resultsdbpy/view/static/js/commit.js:
+        (_CommitBank.prototype.commitsDuringUuid): Return a list of commits which were the trunk of their respective
+        repositories at a given time.
+        * resultsdbpy/resultsdbpy/view/static/js/timeline.js:
+        (xAxisFromScale): Add callbacks triggered when the mouse enters or leaves elements in the scale canvas.
+        (TimelineFromEndpoint.render): Add callbacks triggered when the mouse enters or leaves dot elements.
+        * resultsdbpy/resultsdbpy/view/static/js/tooltip.js: Added.
+        (isPointInElement): Given an element and a point, return true if that point is within the bounds of the element.
+        (_ToolTip):
+        (_ToolTip.prototype.set): Set the content and location of the ToolTip.
+        (_ToolTip.prototype.toString): Return the html needed to render the ToolTip.
+        (_ToolTip.prototype.unset): Clear and hide the ToolTip.
+        (_ToolTip.prototype.isIn): Check if a given point is contained within the ToolTip.
+        * resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js:
+        (Timeline.CanvasSeriesComponent): Convert onHover events to onEnter/onLeave events. Add toolTips points to both
+        dot and scale elements.
+        * resultsdbpy/resultsdbpy/view/templates/search.html: Add ToolTip.
+        * resultsdbpy/resultsdbpy/view/templates/suite_results.html: Ditto.
+
 2019-08-20  Justin Michaud  <justin_mich...@apple.com>
 
         Fix InBounds speculation of typed array PutByVal and add extra step to integer range optimization to search for equality relationships on the RHS value

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css (0 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css	2019-08-20 18:55:34 UTC (rev 248910)
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ 
+.tooltip {
+    z-index: 50;
+    position: absolute;
+    opacity: 80%;
+    width: 0;
+    height: 0;
+    border-style: solid;
+    border-width: 15px;
+    -webkit-background-clip: padding-box;
+    background-clip: padding-box;
+}
+
+.tooltip.arrow-up {
+     border-color: transparent transparent #cccd transparent;
+}
+
+.tooltip.arrow-down {
+    border-color: #cccd transparent transparent transparent;
+}
+
+.tooltip-content {
+    z-index: 50;
+    position: absolute;
+    -webkit-backdrop-filter: blur(10px) brightness(88%);
+    backdrop-filter: blur(10px) brightness(88%);
+    color: var(--boldInverseColor);
+    border: 1px solid #ccc;
+    border-radius: 5px;
+    padding: 5px;
+    font-size: var(--smallSize);
+    list-style: none;
+    max-width: 600px;
+}

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/commit.js (248909 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/commit.js	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/commit.js	2019-08-20 18:55:34 UTC (rev 248910)
@@ -162,6 +162,48 @@
         }
         return null;
     }
+    commitsDuringUuid(uuid) {
+        let commits = [];
+        let begin = 0;
+        let end = this.commits.length - 1;
+        let index = this.commits.length - 1;
+        while (begin <= end) {
+            const mid = Math.ceil((begin + end) / 2);
+            const candidate = this.commits[mid];
+            if (candidate.uuid === uuid) {
+                commits.push(candidate);
+                index = mid - 1;
+                break;
+            }
+            if (candidate.uuid < uuid)
+                begin = mid + 1;
+            else
+                end = mid - 1;
+        }
+
+        let repositories = new Set();
+        if (commits.length)
+            repositories.add(commits[0].repository_id);
+
+        while (index >= 0) {
+            if (repositories.has(this.commits[index].repository_id)) {
+                --index;
+                continue;
+            }
+            if (this._repositories.length && !this._repositories.has(this.commits[index].repository_id)) {
+                --index;
+                continue;
+            }
+
+            commits.push(this.commits[index]);
+            repositories.add(this.commits[index].repository_id);
+            if (repositories.length == this._repositories.length)
+                break;
+
+            --index;
+        }
+        return commits.sort(function(a, b) {return a.repository_id.localeCompare(b.repository_id)});
+    }
     _loadSiblings(commit) {
         const query = paramsToQuery({
             branch: [commit.branch],

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js (248909 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js	2019-08-20 18:55:34 UTC (rev 248910)
@@ -23,9 +23,10 @@
 
 import {CommitBank} from '/assets/js/commit.js';
 import {Configuration} from '/assets/js/configuration.js';
-import {deepCompare, ErrorDisplay, paramsToQuery, queryToParams} from '/assets/js/common.js';
+import {deepCompare, ErrorDisplay, escapeHTML, paramsToQuery, queryToParams} from '/assets/js/common.js';
+import {ToolTip} from '/assets/js/tooltip.js';
+import {Timeline} from '/library/js/components/TimelineComponents.js';
 import {DOM, EventStream, REF, FP} from '/library/js/Ref.js';
-import {Timeline} from '/library/js/components/TimelineComponents.js';
 
 
 const DEFAULT_LIMIT = 100;
@@ -219,6 +220,25 @@
             const query = paramsToQuery(params);
             window.open(`/commit?${query}`, '_blank');
         },
+        onScaleEnter: (node, event, canvas) => {
+            const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+            ToolTip.set(
+                `<div class="content">
+                    Time: ${new Date(node.label.timestamp * 1000).toLocaleString()}<br>
+                    Repository: ${node.label.repository_id}<br>
+                    Branch: ${node.label.branch}<br>
+                    Committer: ${node.label.committer}
+                    ${node.label.message ? `<br><div>${escapeHTML(node.label.message.split('\n')[0])}</div>` : ''}
+                </div>`,
+                node.tipPoints.map((point) => {
+                    return {x: canvas.x + point.x, y: canvas.y + scrollDelta + point.y};
+                })
+            );
+        },
+        onScaleLeave: (event, canvas) => {
+            if (!ToolTip.isIn({x: event.x, y: event.y}))
+                ToolTip.unset();
+        },
         // Per the birthday paradox, 10% change of collision with 7.7 million commits with 12 character commits
         getLabelFunc: (commit) => {return commit ? commit.id.substring(0,12) : '?';},
         getScaleFunc: (commit) => commit.uuid,
@@ -570,6 +590,48 @@
             }
         }
 
+        function onDotEnterFactory(configuration) {
+            return (data, event, canvas) => {
+                const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+                ToolTip.set(
+                    `<div class="content">
+                        ${data.start_time ? `<a href="" () {
+                            let buildParams = configuration.toParams();
+                            buildParams['suite'] = [self.suite];
+                            buildParams['uuid'] = [data.uuid];
+                            buildParams['after_time'] = [data.start_time];
+                            buildParams['before_time'] = [data.start_time];
+                            if (branch)
+                                buildParams['branch'] = branch;
+                            return buildParams;
+                        } ())}" target="_blank">Test run</a> @ ${new Date(data.start_time * 1000).toLocaleString()}<br>` : ''}
+                        Commits: ${CommitBank.commitsDuringUuid(data.uuid).map((commit) => {
+                            let params = {
+                                branch: commit.branch ? [commit.branch] : branch,
+                                uuid: [commit.uuid],
+                            }
+                            if (!params.branch)
+                                delete params.branch;
+                            const query = paramsToQuery(params);
+                            return `<a href="" target="_blank">${commit.id.substring(0,12)}</a>`;
+                        }).join(', ')}
+                        <br>
+
+                        ${data.expected ? `Expected: ${data.expected}<br>` : ''}
+                        ${data.actual ? `Actual: ${data.actual}<br>` : ''}
+                    </div>`,
+                    data.tipPoints.map((point) => {
+                        return {x: canvas.x + point.x, y: canvas.y + scrollDelta + point.y};
+                    })
+                );
+            }
+        }
+
+        function onDotLeave(event, canvas) {
+            if (!ToolTip.isIn({x: event.pageX, y: event.pageY}))
+                ToolTip.unset();
+        }
+
         function exporterFactory(data) {
             return (updateFunction) => {
                 self.updates.push((scale) => {updateFunction(data, scale);});
@@ -624,6 +686,8 @@
                         renderFactory: options.renderFactory,
                         exporter: options.exporter,
                         onDotClick: onDotClickFactory(config),
+                        onDotEnter: onDotEnterFactory(config),
+                        onDotLeave: onDotLeave,
                         exporter: exporterFactory(resultsForConfig),
                     }));
 
@@ -638,6 +702,8 @@
                                     renderFactory: options.renderFactory,
                                     exporter: options.exporter,
                                     onDotClick: onDotClickFactory(sdkConfig),
+                                    onDotEnter: onDotEnterFactory(sdkConfig),
+                                    onDotLeave: onDotLeave,
                                     exporter: exporterFactory(resultsByKey[sdkConfig.toKey()]),
                                 })));
                     });
@@ -663,6 +729,8 @@
                         compareFunc: options.compareFunc,
                         renderFactory: options.renderFactory,
                         onDotClick: onDotClickFactory(configuration),
+                        onDotEnter: onDotEnterFactory(configuration),
+                        onDotLeave: onDotLeave,
                         exporter: exporterFactory(allResults),
                     })),
                 ...collapsedTimelines

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js (0 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js	2019-08-20 18:55:34 UTC (rev 248910)
@@ -0,0 +1,144 @@
+// Copyright (C) 2019 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+import {DOM, REF} from '/library/js/Ref.js';
+
+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;
+}
+
+class _ToolTip {
+    constructor() {
+        this.ref = null;
+        this.arrow = null;
+    }
+    toString() {
+        const self = this;
+        this.ref = REF.createRef({
+            state: {content: null, points: null},
+            onElementMount: (element) => {
+                element.addEventListener('mouseleave', (event) => {
+                    if (!isPointInElement(self.arrow.element, event))
+                        this.unset()
+                });
+            },
+            onStateUpdate: (element, stateDiff, state) => {
+                if (stateDiff.content) {
+                    DOM.inject(element, stateDiff.content);
+                    element.style.display = null;
+                }
+                if (!state.content && !element.style.display) {
+                    element.style.display = 'none';
+                    DOM.inject(element, '');
+                }
+                if (stateDiff.points) {
+                    element.style.left = '0px';
+                    element.style.top = '0px';
+
+                    const upperPoint = stateDiff.points.length > 1 && stateDiff.points[0].y > stateDiff.points[1].y ? stateDiff.points[1] : stateDiff.points[0];
+                    const lowerPoint = stateDiff.points.length > 1 && stateDiff.points[1].y > stateDiff.points[0].y ? stateDiff.points[1] : stateDiff.points[0];
+                    const scrollDelta = document.documentElement.scrollTop || document.body.scrollTop;
+                    const bounds = element.getBoundingClientRect();
+                    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`;
+
+                    self.arrow.setState({direction: direction, location: point});
+                }
+            },
+        });
+        this.arrow = REF.createRef({
+            state: {direction: null, location: null},
+            onElementMount: (element) => {
+                element.addEventListener('mouseleave', (event) => {
+                    if (!isPointInElement(self.ref.element, event))
+                        this.unset()
+                });
+            },
+            onStateUpdate: (element, stateDiff, state) => {
+                if (!state.direction || !state.location) {
+                    element.style.display = 'none';
+                    return;
+                }
+
+                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.style.display = null;
+            },
+
+        });
+        return `<div class="tooltip arrow-up" ref="${this.arrow}"></div>
+            <div class="tooltip-content" ref="${this.ref}">
+            </div>`;
+    }
+    set(content, points) {
+        if (!this.ref) {
+            console.error('Cannot set ToolTip content, no tooltip on the page');
+            return;
+        }
+        if (!points || points.length == 0) {
+            console.error('Tool tips require a location');
+            return;
+        }
+        
+        this.ref.setState({content: content, points: points});
+    }
+    unset() {
+        if (this.ref)
+            this.ref.setState({content: null, points: null});
+        if (this.arrow)
+            this.arrow.setState({direction: null, points: null});
+    }
+    isIn(point) {
+        return isPointInElement(this.ref.element, point) || isPointInElement(this.arrow.element, point);
+    }
+}
+
+const ToolTip = new _ToolTip();
+
+export {ToolTip};

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js (248909 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TimelineComponents.js	2019-08-20 18:55:34 UTC (rev 248910)
@@ -223,7 +223,8 @@
     const getScale = typeof option.getScaleFunc === "function" ? option.getScaleFunc : (a) => a;
     const comp = typeof option.compareFunc === "function" ? option.compareFunc : (a, b) => a - b;
     const _onDotClick_ = typeof option._onDotClick_ === "function" ? option.onDotClick : null;
-    const _onDotHover_ = typeof option._onDotHover_ === "function" ? option.onDotHover : null;
+    const _onDotEnter_ = typeof option._onDotEnter_ === "function" ? option.onDotEnter : null;
+    const _onDotLeave_ = typeof option._onDotLeave_ === "function" ? option.onDotLeave : null;
     const tagHeight = defaultFontSize;
     const height = option.height ? option.height : 2 * radius + tagHeight;
     const colorBatchRender = new ColorBatchRender();
@@ -360,8 +361,26 @@
     };
 
     return ListProviderReceiver((updateContainerWidth, onContainerScroll, onResize) => {
+        const mouseMove = (e) => {
+            let dots = getMouseEventTirggerDots(e, canvasRef.state.scrollLeft, canvasRef.element);
+            if (dots.length) {
+                if (onDotEnter) {
+                    dots[0].tipPoints = [
+                        {x: dots[0]._dotCenter.x, y: dots[0]._dotCenter.y - 3 * radius / 2},
+                        {x: dots[0]._dotCenter.x, y: dots[0]._dotCenter.y + radius / 2},
+                    ];
+                    onDotEnter(dots[0], e, canvasRef.element.getBoundingClientRect());
+                }
+                canvasRef.element.style.cursor = "pointer";
+            } else {
+                if (onDotLeave)
+                    onDotLeave(e, canvasRef.element.getBoundingClientRect());
+                canvasRef.element.style.cursor = "default";
+            }
+        }
         const _onScrollAction_ = (e) => {
             canvasRef.setState({scrollLeft: e.target.scrollLeft / getDevicePixelRatio()});
+            mouseMove(e);
         };
         const _onResizeAction_ = (width) => {
             canvasRef.setState({width: width});
@@ -386,17 +405,10 @@
                     });
                 }
 
-                if (onDotClick || onDotHover) {
-                    element.addEventListener('mousemove', (e) => {
-                        let dots = getMouseEventTirggerDots(e, canvasRef.state.scrollLeft, element);
-                        if (dots.length) {
-                            if (onDotHover)
-                                onDotHover(dots[0], e);
-                            element.style.cursor = "pointer";
-                        } else
-                            element.style.cursor = "default";
-                    });
-                }
+                if (onDotClick || onDotEnter || onDotLeave)
+                    element.addEventListener('mousemove', mouseMove);
+                if (onDotLeave)
+                    element.addEventListener('mouseleave', (e) => onDotLeave(e, element.getBoundingClientRect()));
 
                 createInsertionObservers(element, (entries) => {
                     canvasRef.setState({onScreen: entries[0].isIntersecting});
@@ -533,7 +545,8 @@
     const getScaleKey = typeof option.getScaleFunc === "function" ? option.getScaleFunc : (a) => a;
     const comp = typeof option.compareFunc === "function" ? option.compareFunc : (a, b) => a - b;
     const _onScaleClick_ = typeof option._onScaleClick_ === "function" ? option.onScaleClick : null;
-    const _onScaleHover_ = typeof option._onScaleHover_ === "function" ? option.onScaleHover : null;
+    const _onScaleEnter_ = typeof option._onScaleEnter_ === "function" ? option.onScaleEnter : null;
+    const _onScaleLeave_ = typeof option._onScaleLeave_ === "function" ? option.onScaleLeave : null;
     const sortData = option.sortData === true ? option.sortData : false;
     const getLabel = typeof option.getLabelFunc === "function" ? option.getLabelFunc : (a) => a;
     const isTop = typeof option.isTop === "boolean" ? option.isTop : false;
@@ -626,8 +639,9 @@
     const getMouseEventTirggerScales = (e, scrollLeft, element) => {
         const {x, y} = getMousePosInCanvas(e, element);
         return onScreenScales.filter(scale => {
-            const width = scale.label.toString().length * fontSizeNumber / 2;
-            const height = scale.label.toString().length * fontSizeNumber / 2 * sqrt3;
+            const labelLength = getLabel(scale.label).length;
+            const width = labelLength * fontSizeNumber / 2;
+            const height = labelLength * fontSizeNumber / 2 * sqrt3;
             const point1 = {
                 x: scale._tagTop.x - scrollLeft - (isTop ? fontSizeNumber / 2 * sqrt3 : 0),
                 y: scale._tagTop.y + (fontSizeNumber / 2 + scaleTagLineHeight) * (isTop ? -1 : 1),
@@ -725,8 +739,30 @@
 
     return {
         series: ListProviderReceiver((updateContainerWidth, onContainerScroll, onResize) => {
+            const mouseMove = (e) => {
+                let scales = getMouseEventTirggerScales(e, canvasRef.state.scrollLeft, canvasRef.element);
+                if (scales.length) {
+                    if (onScaleEnter) {
+                        const labelLength = getLabel(scales[0].label).length;
+                        scales[0].tipPoints = [{
+                            x: scales[0]._tagTop.x - canvasRef.state.scrollLeft,
+                            y: scales[0]._tagTop.y + scaleTagLineHeight * (isTop ? -1 : 0),
+                        }, {
+                            x: scales[0]._tagTop.x - canvasRef.state.scrollLeft + labelLength * fontSizeNumber / 3 - scaleTagLineHeight * (isTop ? 1 : .25),
+                            y: scales[0]._tagTop.y + (labelLength * fontSizeNumber / 2 * sqrt3) * (isTop ? -1 : 1) + scaleTagLineHeight * (isTop ? 1 : 0),
+                        }];
+                        onScaleEnter(scales[0], e, canvasRef.element.getBoundingClientRect());
+                    }
+                    canvasRef.element.style.cursor = "pointer";
+                } else {
+                    if (onScaleEnter)
+                        onScaleLeave(e, canvasRef.element.getBoundingClientRect());
+                    canvasRef.element.style.cursor = "default";
+                }
+            }
             const _onScrollAction_ = (e) => {
                 canvasRef.setState({scrollLeft: e.target.scrollLeft / getDevicePixelRatio()});
+                mouseMove(e);
             };
             const _onResizeAction_ = (width) => {
                 canvasRef.setState({width: width});
@@ -750,18 +786,10 @@
                         });
                     }
 
-                    if (onScaleClick || onScaleHover) {
-                        element.addEventListener('mousemove', (e) => {
-                            let scales = getMouseEventTirggerScales(e, canvasRef.state.scrollLeft, element);
-                            if (scales.length) {
-                                if (onScaleHover)
-                                    onScaleHover(scales[0], e);
-                                element.style.cursor = "pointer";
-                            } else {
-                                element.style.cursor = "default";
-                            }
-                        });
-                    }
+                    if (onScaleClick || onScaleEnter || onScaleLeave)
+                        element.addEventListener('mousemove', mouseMove);
+                    if (onScaleLeave)
+                        element.addEventListener('mouseleave', (e) => onScaleLeave(e, element.getBoundingClientRect()));
                 },
                 onElementUnmount: (element) => {
                     onContainerScroll.stopAction(onScrollAction);

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/templates/search.html (248909 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/templates/search.html	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/templates/search.html	2019-08-20 18:55:34 UTC (rev 248910)
@@ -28,6 +28,7 @@
 <link rel="stylesheet" type="text/css" href=""
 <link rel="stylesheet" type="text/css" href=""
 <link rel="stylesheet" type="text/css" href=""
+<link rel="stylesheet" type="text/css" href=""
 
 <script type="module">
 import {CommitBank} from '/assets/js/commit.js';
@@ -34,9 +35,10 @@
 import {deepCompare, ErrorDisplay, queryToParams, paramsToQuery} from '/assets/js/common.js';
 import {Configuration} from '/assets/js/configuration.js';
 import {Drawer, BranchSelector, ConfigurationSelectors, LimitSlider} from '/assets/js/drawer.js';
-import {DOM, REF} from '/library/js/Ref.js';
 import {SearchBar} from '/assets/js/search.js';
 import {Legend, TimelineFromEndpoint} from '/assets/js/timeline.js';
+import {ToolTip} from '/assets/js/tooltip.js';
+import {DOM, REF} from '/library/js/Ref.js';
 
 const DEFAULT_LIMIT = 100;
 const SUITES = JSON.parse('{{ suites|safe }}');    
@@ -215,7 +217,8 @@
 
 DOM.inject(
     document.getElementById('app'),
-    `${Drawer([
+    `${ToolTip}
+    ${Drawer([
         LimitSlider(() => {view.reload()}),
         BranchSelector(() => {
             CommitBank.reload();

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html (248909 => 248910)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html	2019-08-20 18:46:36 UTC (rev 248909)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html	2019-08-20 18:55:34 UTC (rev 248910)
@@ -28,6 +28,7 @@
 <link rel="stylesheet" type="text/css" href=""
 <link rel="stylesheet" type="text/css" href=""
 <link rel="stylesheet" type="text/css" href=""
+<link rel="stylesheet" type="text/css" href=""
 
 <script type="module">
 import {CommitBank} from '/assets/js/commit.js';
@@ -34,8 +35,9 @@
 import {deepCompare, ErrorDisplay, queryToParams, paramsToQuery} from '/assets/js/common.js';
 import {Configuration} from '/assets/js/configuration.js';
 import {Drawer, BranchSelector, ConfigurationSelectors, LimitSlider} from '/assets/js/drawer.js';
+import {Legend, TimelineFromEndpoint} from '/assets/js/timeline.js';
+import {ToolTip} from '/assets/js/tooltip.js';
 import {DOM, REF} from '/library/js/Ref.js';
-import {Legend, TimelineFromEndpoint} from '/assets/js/timeline.js';
 
 const DEFAULT_LIMIT = 100;
 const SUITES = JSON.parse('{{ suites|safe }}');    
@@ -159,7 +161,8 @@
 
 let view = new MainView();
 
-DOM.inject(document.getElementById('app'), `${Drawer([
+DOM.inject(document.getElementById('app'), `${ToolTip}
+${Drawer([
     LimitSlider(() => {view.reload()}),
     BranchSelector(() => {
         CommitBank.reload();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to