From 371bc5cd761f3b8efa3656e53ab7dbba3776bbe9 Mon Sep 17 00:00:00 2001 From: "jbedard@apple.com" Date: Tue, 3 Sep 2019 21:52:16 +0000 Subject: [PATCH] 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. git-svn-id: https://svn.webkit.org/repository/webkit/trunk@249443 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- Tools/ChangeLog | 17 +++ .../resultsdbpy/view/static/css/timeline.css | 4 +- .../resultsdbpy/view/static/css/tooltip.css | 11 +- .../resultsdbpy/view/static/js/drawer.js | 7 +- .../resultsdbpy/view/static/js/timeline.js | 91 +++++++++------- .../resultsdbpy/view/static/js/tooltip.js | 115 ++++++++++++++++----- .../resultsdbpy/view/static/library/css/webkit.css | 3 +- .../resultsdbpy/view/templates/search.html | 11 +- .../resultsdbpy/view/templates/suite_results.html | 13 +-- 9 files changed, 190 insertions(+), 82 deletions(-) diff --git a/Tools/ChangeLog b/Tools/ChangeLog index 47e506f..b7667fd 100644 --- a/Tools/ChangeLog +++ b/Tools/ChangeLog @@ -1,5 +1,22 @@ 2019-09-03 Jonathan Bedard + 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 + results.webkit.org: Increase default limit for LimitSlider https://bugs.webkit.org/show_bug.cgi?id=201424 diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/css/timeline.css b/Tools/resultsdbpy/resultsdbpy/view/static/css/timeline.css index abd9bef..ed8f1cb 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/css/timeline.css +++ b/Tools/resultsdbpy/resultsdbpy/view/static/css/timeline.css @@ -23,10 +23,10 @@ * 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); } diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css b/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css index 385a365..f307c33 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css +++ b/Tools/resultsdbpy/resultsdbpy/view/static/css/tooltip.css @@ -43,8 +43,15 @@ 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; } diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/js/drawer.js b/Tools/resultsdbpy/resultsdbpy/view/static/js/drawer.js index 5faccea..f77d794 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/js/drawer.js +++ b/Tools/resultsdbpy/resultsdbpy/view/static/js/drawer.js @@ -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'; + } } } }); diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js b/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js index 5a5325c..37647f0 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js +++ b/Tools/resultsdbpy/resultsdbpy/view/static/js/timeline.js @@ -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 = `
-
-
-
${TestResultsSymbolMap.success}
- ${LegendLabel( - updateLabelEvents, - plural ? 'No unexpected results' : 'Result expected', - plural ? 'All tests passed' : 'Test passed', - )} -
-
-
${TestResultsSymbolMap.failed}
- ${LegendLabel( - updateLabelEvents, - plural ? 'Some tests unexpectedly failed' : 'Unexpectedly failed', - plural ? 'Some tests failed' : 'Test failed', - )} -
-
-
${TestResultsSymbolMap.timedout}
- ${LegendLabel( - updateLabelEvents, - plural ? 'Some tests unexpectedly timed out' : 'Unexpectedly timed out', - plural ? 'Some tests timed out' : 'Test timed out', - )} -
-
-
${TestResultsSymbolMap.crashed}
- ${LegendLabel( - updateLabelEvents, - plural ? 'Some tests unexpectedly crashed' : 'Unexpectedly crashed', - plural ? 'Some tests crashed' : 'Test crashed', - )} -
-
+ 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 = `
+ ${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( + `
+ ${willFilterExpected ? legendDetails[key].expected : legendDetails[key].unexpected} +
`, + element, + {orientation: ToolTip.HORIZONTAL}, + ); + }; + } + }); + return `
+
${TestResultsSymbolMap[key]}
+ ${LegendLabel(updateLabelEvents, legendDetails[key].expected, legendDetails[key].unexpected)} +
` + }).join('')}
`; if (callback) { @@ -852,8 +863,8 @@ function Legend(callback=null, plural=false) { }, }); - result += `
- + result += `
+
`; } - return `
${result}

`; + return `${result}`; } export {Legend, TimelineFromEndpoint, Expectations}; diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js b/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js index 7390e8e..0f452a3 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js +++ b/Tools/resultsdbpy/resultsdbpy/view/static/js/tooltip.js @@ -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}); diff --git a/Tools/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css b/Tools/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css index 3db25d5..802a6b4 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css +++ b/Tools/resultsdbpy/resultsdbpy/view/static/library/css/webkit.css @@ -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 { diff --git a/Tools/resultsdbpy/resultsdbpy/view/templates/search.html b/Tools/resultsdbpy/resultsdbpy/view/templates/search.html index 6cabda4..fa3aa43 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/templates/search.html +++ b/Tools/resultsdbpy/resultsdbpy/view/templates/search.html @@ -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(); diff --git a/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html b/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html index ea98b99..6ff5736 100644 --- a/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html +++ b/Tools/resultsdbpy/resultsdbpy/view/templates/suite_results.html @@ -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 `
${suite}
@@ -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(); -- 1.8.3.1