Some applications truncates the last closing parenthesis in perf dashboard URL
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / pages / chart-pane.js
1
2 class ChartPane extends ChartPaneBase {
3     constructor(chartsPage, platformId, metricId)
4     {
5         super('chart-pane');
6
7         this._mainChartIndicatorWasLocked = false;
8         this._chartsPage = chartsPage;
9         this._paneOpenedByClick = null;
10
11         this.content().querySelector('close-button').component().setCallback(chartsPage.closePane.bind(chartsPage, this));
12
13         this.configure(platformId, metricId);
14     }
15
16     serializeState()
17     {
18         var state = [this._platformId, this._metricId];
19         if (this._mainChart) {
20             var selection = this._mainChart.currentSelection();
21             if (selection)
22                 state[2] = selection;
23             else if (this._mainChartIndicatorWasLocked)
24                 state[2] = this._mainChart.currentPoint().id;
25         }
26         return state;
27     }
28
29     updateFromSerializedState(state, isOpen)
30     {
31         if (!this._mainChart)
32             return;
33
34         var selectionOrIndicatedPoint = state[2];
35         if (selectionOrIndicatedPoint instanceof Array)
36             this._mainChart.setSelection([parseFloat(selectionOrIndicatedPoint[0]), parseFloat(selectionOrIndicatedPoint[1])]);
37         else if (typeof(selectionOrIndicatedPoint) == 'number') {
38             this._mainChart.setIndicator(selectionOrIndicatedPoint, true);
39             this._mainChartIndicatorWasLocked = true;
40         } else
41             this._mainChart.setIndicator(null, false);
42     }
43
44     setOverviewSelection(selection)
45     {
46         if (this._overviewChart)
47             this._overviewChart.setSelection(selection);
48     }
49
50     _overviewSelectionDidChange(domain, didEndDrag)
51     {
52         super._overviewSelectionDidChange(domain, didEndDrag);
53         this._chartsPage.setMainDomainFromOverviewSelection(domain, this, didEndDrag);
54     }
55
56     _mainSelectionDidChange(selection, didEndDrag)
57     {
58         super._mainSelectionDidChange(selection, didEndDrag);
59         this._chartsPage.mainChartSelectionDidChange(this, didEndDrag);
60     }
61
62     _mainSelectionDidZoom(selection)
63     {
64         super._mainSelectionDidZoom(selection);
65         this._chartsPage.setMainDomainFromZoom(selection, this);
66     }
67
68     router() { return this._chartsPage.router(); }
69
70     _requestOpeningCommitViewer(repository, from, to)
71     {
72         super._requestOpeningCommitViewer(repository, from, to);
73         this._chartsPage.setOpenRepository(repository);
74     }
75
76     setOpenRepository(repository)
77     {
78         if (repository != this._commitLogViewer.currentRepository()) {
79             var range = this._mainChartStatus.setCurrentRepository(repository);
80             this._commitLogViewer.view(repository, range.from, range.to).then(this.render.bind(this));
81             this.render();
82         }
83     }
84
85     _indicatorDidChange(indicatorID, isLocked)
86     {
87         this._chartsPage.mainChartIndicatorDidChange(this, isLocked != this._mainChartIndicatorWasLocked);
88         this._mainChartIndicatorWasLocked = isLocked;
89         super._indicatorDidChange(indicatorID, isLocked);
90     }
91
92     render()
93     {
94         if (this._platform && this._metric) {
95             var metric = this._metric;
96             var platform = this._platform;
97
98             this.renderReplace(this.content().querySelector('.chart-pane-title'),
99                 metric.fullName() + ' on ' + platform.name());
100         }
101
102         if (this._mainChartStatus)
103             this._renderActionToolbar();
104
105         super.render();
106     }
107
108     _renderActionToolbar()
109     {
110         var actions = [];
111         var platform = this._platform;
112         var metric = this._metric;
113
114         var element = ComponentBase.createElement;
115         var link = ComponentBase.createLink;
116         var self = this;
117
118         if (this._chartsPage.canBreakdown(platform, metric)) {
119             actions.push(element('li', link('Breakdown', function () {
120                 self._chartsPage.insertBreakdownPanesAfter(platform, metric, self);
121             })));
122         }
123
124         var platformPane = this.content().querySelector('.chart-pane-alternative-platforms');
125         var alternativePlatforms = this._chartsPage.alternatePlatforms(platform, metric);
126         if (alternativePlatforms.length) {
127             this.renderReplace(platformPane, Platform.sortByName(alternativePlatforms).map(function (platform) {
128                 return element('li', link(platform.label(), function () {
129                     self._chartsPage.insertPaneAfter(platform, metric, self);
130                 }));
131             }));
132
133             actions.push(element('li', {class: this._paneOpenedByClick == platformPane ? 'selected' : ''},
134                 this._makeAnchorToOpenPane(platformPane, 'Other Platforms', true)));
135         } else {
136             platformPane.style.display = 'none';
137         }
138
139         var analyzePane = this.content().querySelector('.chart-pane-analyze-pane');
140         var pointsRangeForAnalysis = this._mainChartStatus.pointsRangeForAnalysis();
141         if (pointsRangeForAnalysis) {
142             actions.push(element('li', {class: this._paneOpenedByClick == analyzePane ? 'selected' : ''},
143                 this._makeAnchorToOpenPane(analyzePane, 'Analyze', false)));
144
145             var router = this._chartsPage.router();
146             analyzePane.onsubmit = function (event) {
147                 event.preventDefault();
148                 var newWindow = window.open(router.url('analysis/task/create'), '_blank');
149
150                 var name = analyzePane.querySelector('input').value;
151                 AnalysisTask.create(name, pointsRangeForAnalysis.startPointId, pointsRangeForAnalysis.endPointId).then(function (data) {
152                     newWindow.location.href = router.url('analysis/task/' + data['taskId']);
153                     // FIXME: Refetch the list of analysis tasks.
154                 }, function (error) {
155                     newWindow.location.href = router.url('analysis/task/create', {error: error});
156                 });
157             }
158         } else {
159             analyzePane.style.display = 'none';
160             analyzePane.onsubmit = function (event) { event.preventDefault(); }
161         }
162
163         this._paneOpenedByClick = null;
164         this.renderReplace(this.content().querySelector('.chart-pane-action-buttons'), actions);
165     }
166
167     _makeAnchorToOpenPane(pane, label, shouldRespondToHover)
168     {
169         var anchor = null;
170         var ignoreMouseLeave = false;
171         var self = this;
172         var setPaneVisibility = function (pane, shouldShow) {
173             var anchor = pane.anchor;
174             if (shouldShow) {
175                 var width = anchor.offsetParent.offsetWidth;
176                 pane.style.top = anchor.offsetTop + anchor.offsetHeight + 'px';
177                 pane.style.right = (width - anchor.offsetLeft - anchor.offsetWidth) + 'px';
178             }
179             pane.style.display = shouldShow ? null : 'none';
180             anchor.parentNode.className = shouldShow ? 'selected' : '';
181             if (self._paneOpenedByClick == pane && !shouldShow)
182                 self._paneOpenedByClick = null;
183         }
184
185         var attributes = {
186             href: '#',
187             onclick: function (event) {
188                 event.preventDefault();
189                 var shouldShowPane = pane.style.display == 'none';
190                 if (shouldShowPane) {
191                     if (self._paneOpenedByClick)
192                         setPaneVisibility(self._paneOpenedByClick, false);
193                     self._paneOpenedByClick = pane;
194                 }
195                 setPaneVisibility(pane, shouldShowPane);
196             },
197         };
198         if (shouldRespondToHover) {
199             var mouseIsInAnchor = false;
200             var mouseIsInPane = false;
201
202             attributes.onmouseenter = function () {
203                 if (self._paneOpenedByClick)
204                     return;
205                 mouseIsInAnchor = true;
206                 setPaneVisibility(pane, true);
207             }
208             attributes.onmouseleave = function () {
209                 setTimeout(function () {
210                     if (!mouseIsInPane)
211                         setPaneVisibility(pane, false);
212                 }, 0);
213                 mouseIsInAnchor = false;                
214             }
215
216             pane.onmouseleave = function () {
217                 setTimeout(function () {
218                     if (!mouseIsInAnchor)
219                         setPaneVisibility(pane, false);
220                 }, 0);
221                 mouseIsInPane = false;
222             }
223             pane.onmouseenter = function () {
224                 mouseIsInPane = true;
225             }
226         }
227
228         var anchor = ComponentBase.createElement('a', attributes, label);
229         pane.anchor = anchor;
230         return anchor;
231     }
232
233     static paneHeaderTemplate()
234     {
235         return `
236             <header class="chart-pane-header">
237                 <h2 class="chart-pane-title">-</h2>
238                 <nav class="chart-pane-actions">
239                     <ul>
240                         <li class="close"><close-button></close-button></li>
241                     </ul>
242                     <ul class="chart-pane-action-buttons buttoned-toolbar"></ul>
243                     <ul class="chart-pane-alternative-platforms" style="display:none"></ul>
244                     <form class="chart-pane-analyze-pane" style="display:none">
245                         <input type="text" required>
246                         <button>Create</button>
247                     </form>
248                 </nav>
249             </header>
250         `;
251     }
252
253     static cssTemplate()
254     {
255         return ChartPaneBase.cssTemplate() + `
256             .chart-pane {
257                 border: solid 1px #ccc;
258                 border-radius: 0.5rem;
259                 margin: 1rem;
260                 margin-bottom: 2rem;
261             }
262
263             .chart-pane-body {
264                 height: calc(100% - 2rem);
265             }
266
267             .chart-pane-header {
268                 position: relative;
269                 left: 0;
270                 top: 0;
271                 width: 100%;
272                 height: 2rem;
273                 line-height: 2rem;
274                 border-bottom: solid 1px #ccc;
275             }
276
277             .chart-pane-title {
278                 margin: 0 0.5rem;
279                 padding: 0;
280                 padding-left: 1.5rem;
281                 font-size: 1rem;
282                 font-weight: inherit;
283             }
284
285             .chart-pane-actions {
286                 position: absolute;
287                 display: flex;
288                 flex-direction: row;
289                 justify-content: space-between;
290                 align-items: center;
291                 width: 100%;
292                 height: 2rem;
293                 top: 0;
294                 padding: 0 0;
295             }
296
297             .chart-pane-actions ul {
298                 display: block;
299                 padding: 0;
300                 margin: 0 0.5rem;
301                 font-size: 1rem;
302                 line-height: 1rem;
303                 list-style: none;
304             }
305
306             .chart-pane-actions .chart-pane-action-buttons {
307                 font-size: 0.9rem;
308                 line-height: 0.9rem;
309             }
310
311             .chart-pane-actions .chart-pane-alternative-platforms,
312             .chart-pane-analyze-pane {
313                 position: absolute;
314                 top: 0;
315                 right: 0;
316                 border: solid 1px #ccc;
317                 border-radius: 0.2rem;
318                 z-index: 10;
319                 background: rgba(255, 255, 255, 0.8);
320                 -webkit-backdrop-filter: blur(0.5rem);
321                 padding: 0.2rem 0;
322                 margin: 0;
323                 margin-top: -0.2rem;
324                 margin-right: -0.2rem;
325             }
326
327             .chart-pane-alternative-platforms li {
328             }
329
330             .chart-pane-alternative-platforms li a {
331                 display: block;
332                 text-decoration: none;
333                 color: inherit;
334                 font-size: 0.9rem;
335                 padding: 0.2rem 0.5rem;
336             }
337
338             .chart-pane-alternative-platforms a:hover,
339             .chart-pane-analyze-pane input:focus {
340                 background: rgba(204, 153, 51, 0.1);
341             }
342
343             .chart-pane-analyze-pane {
344                 padding: 0.5rem;
345             }
346
347             .chart-pane-analyze-pane input {
348                 font-size: 1rem;
349                 width: 15rem;
350                 outline: none;
351                 border: solid 1px #ccc;
352             }
353 `;
354     }
355 }