1<!DOCTYPE html>
2<!--
3Copyright 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<head>
8  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
9
10  <script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
11
12  <link rel="import" href="/components/polymer/polymer.html">
13  <link rel="import" href="/tracing/ui/extras/full_config.html">
14  <link rel="import" href="/tracing/ui/timeline_view.html">
15  <link rel="import" href="/tracing/ui/extras/drive/drive_comment_provider.html">
16
17  <style>
18    body {
19      margin: 0;
20      padding: 0;
21      width: 100%;
22      height: 100%;
23      display: -webkit-flex;
24      -webkit-flex-direction: column;
25    }
26    body > x-timeline-view {
27      -webkit-flex: 1 1 auto;
28      overflow: hidden;
29      position: absolute;
30      top: 0px;
31      bottom: 0;
32      left: 0;
33      right: 0;
34    }
35    body > x-timeline-view:focus {
36      outline: none;
37    }
38    nav {
39      display: flex;
40      flex-direction: row;
41      justify-content: flex-end;
42    }
43    #navbar button {
44      height: 24px;
45      padding-bottom: 3px;
46      vertical-align: middle;
47      box-shadow: none;
48      background-color: #4d90fe;
49      background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed);
50      border: 1px solid #3079ed;
51      color: #fff;
52      border-radius: 2px;
53      cursor: default;
54      font-size: 11px;
55      font-weight: bold;
56      text-align: center;
57      white-space: nowrap;
58      line-height: 27px;
59      min-width: 54px;
60      outline: 0px;
61      padding: 0 8px;
62      font: normal 13px arial,sans-serif;
63      margin: 5px;
64    }
65    #collabs {
66      display: flex;
67      flex-direction: row;
68    }
69    .collaborator-div {
70      display: inline-block;
71      vertical-align: middle;
72      min-height: 0;
73      width: 100px;
74      font-size: 11px;
75      font-weight: bold;
76      font: normal 13px arial,sans-serif;
77      margin: 10px;
78    }
79    .collaborator-img {
80      margin: 2px;
81    }
82    .collaborator-tooltip {
83      z-index: 10000;
84      transition: visibility 0,opacity .13s ease-in;
85      background-color: #2a2a2a;
86      border: 1px solid #fff;
87      color: #fff;
88      cursor: default;
89      display: block;
90      font-family: arial, sans-serif;
91      font-size: 11px;
92      font-weight: bold;
93      margin-left: -1px;
94      opacity: 1;
95      padding: 7px 9px;
96      word-break: break-word;
97      position: absolute;
98    }
99    .collaborator-tooltip-content {
100      color: #fff;
101    }
102    .collaborator-tooltip-arrow {
103      position: absolute;
104      top: -6px;
105    }
106    .collaborator-tooltip-arrow-before {
107      border-color: #fff transparent !important;
108      left: -6px;
109      border: 6px solid;
110      border-top-width: 0;
111      content: '';
112      display: block;
113      height: 0;
114      position: absolute;
115      width: 0;
116    }
117    .collaborator-tooltip-arrow-after {
118      top: 1px;
119      border-color: #2a2a2a transparent !important;
120      left: -5px;
121      border: 5px solid;
122      border-top-width: 0;
123      content: '';
124      display: block;
125      height: 0;
126      position: absolute;
127      width: 0;
128    }
129
130  </style>
131  <title>Trace Viewer</title>
132</head>
133<body>
134  <nav id="navbar">
135    <div id="collabs"></div>
136    <button id="x-drive-save-to-disk">Save to disk</button>
137    <button id="x-drive-save-to-drive">Save to Drive</button>
138    <button id="x-drive-load-from-drive">Load from Drive</button>
139    <button id="x-drive-share">Share</button>
140  </nav>
141  <x-timeline-view>
142  </x-timeline-view>
143
144  <script>
145  'use strict';
146
147  // Needs to be global as it's passed through the Google API as a
148  // GET parameter.
149  var onAPIClientLoaded_ = null;
150
151  (function() {
152
153    tr.exportTo('tr.ui.e.drive', function() {
154      var appId = '239864068844';
155      var constants = {
156        APP_ID: appId,
157        ANCHOR_NAME: appId + '.trace_viewer',
158        DEVELOPER_KEY: 'AIzaSyDR-6_wL9vHg1_oz4JHk8IQAkv2_Y0Y8-M',
159        CLIENT_ID: '239864068844-c7gefbfdcp0j6grltulh2r88tsvl18c1.apps.' +
160            'googleusercontent.com',
161        SCOPE: [
162            'https://www.googleapis.com/auth/drive',
163            'https://www.googleapis.com/auth/drive.install',
164            'https://www.googleapis.com/auth/drive.file',
165            'profile'
166        ]
167      };
168
169      return {
170        getDriveFileId: function() { return driveFileId_; },
171        constants: constants
172      };
173    });
174
175
176    var pickerApiLoaded_ = false;
177    var oauthToken_ = null;
178
179    var timelineViewEl_ = null;
180    var driveDocument_ = null;
181    var shareClient_ = null;
182    var fileIdToLoad_ = null;
183    var driveFileId_ = null;
184
185    function parseGETParameter(val) {
186      var result = null;
187      var tmp = [];
188      location.search.substr(1).split('&').forEach(function(item) {
189        tmp = item.split('=');
190        if (tmp[0] === val)
191          result = decodeURIComponent(tmp[1]);
192      });
193      return result;
194    }
195
196    // Use the Google API Loader script to load the google.picker script.
197    onAPIClientLoaded_ = function() {
198      var driveState = parseGETParameter('state');
199      if (driveState != null) {
200        var driveStateJson = JSON.parse(driveState);
201        fileIdToLoad_ = String(driveStateJson.ids);
202      }
203
204      gapi.load('picker', {'callback': onPickerApiLoad});
205      gapi.load('auth', {'callback': function() {
206        onAuthApiLoad(true, onAuthResultSuccess);
207        setTimeout(function onRepeatAuthApiLoad() {
208          onAuthApiLoad(true, function() {});
209          setTimeout(onRepeatAuthApiLoad, 30000);
210        }, 30000);
211      }});
212    }
213
214    function onAuthApiLoad(tryImmediate, resultCallback) {
215      window.gapi.auth.authorize(
216          {'client_id': tr.ui.e.drive.constants.CLIENT_ID,
217           'scope': tr.ui.e.drive.constants.SCOPE, 'immediate': tryImmediate},
218          function(authResult) {
219            handleAuthResult(authResult, tryImmediate, resultCallback);
220          });
221    }
222
223    function onPickerApiLoad() {
224      pickerApiLoaded_ = true;
225      if (fileIdToLoad_ == null)
226        createPicker();
227    }
228
229    function onAuthResultSuccess() {
230      if (fileIdToLoad_ == null)
231        createPicker();
232      else
233        loadFileFromDrive(fileIdToLoad_);
234    }
235
236    function handleAuthResult(authResult, wasImmediate, resultCallback) {
237      if (authResult && !authResult.error) {
238        oauthToken_ = authResult.access_token;
239        resultCallback();
240      } else if (wasImmediate) {
241        onAuthApiLoad(false);
242      }
243    }
244
245    function createPicker() {
246      if (pickerApiLoaded_ && oauthToken_) {
247        var view = new google.picker.View(google.picker.ViewId.DOCS);
248        view.setMimeTypes('application/json,application/octet-stream');
249        var picker = new google.picker.PickerBuilder()
250                       .enableFeature(google.picker.Feature.NAV_HIDDEN)
251                       .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
252                       .setAppId(tr.ui.e.drive.constants.APP_ID)
253                       .setOAuthToken(oauthToken_)
254                       .addView(view)
255                       .addView(new google.picker.DocsUploadView())
256                       .setDeveloperKey(tr.ui.e.drive.constants.DEVELOPER_KEY)
257                       .setCallback(pickerCallback)
258                       .build();
259        picker.setVisible(true);
260      }
261    }
262
263    function pickerCallback(data) {
264      if (data.action == google.picker.Action.PICKED) {
265        loadFileFromDrive(data.docs[0].id);
266      }
267    }
268
269    function initShareButton() {
270      shareClient_ = new gapi.drive.share.ShareClient(
271          tr.ui.e.drive.constants.APP_ID);
272      shareClient_.setItemIds([driveFileId_]);
273    }
274
275    function loadFileFromDrive(fileId) {
276      gapi.client.load('drive', 'v2', function() {
277        var request = gapi.client.drive.files.get({'fileId': fileId});
278        request.execute(function(resp) { downloadFile(resp); });
279        driveFileId_ = fileId;
280        gapi.load('drive-share', initShareButton);
281      });
282    }
283
284    function downloadFile(file) {
285      if (file.downloadUrl) {
286        var downloadingOverlay = tr.ui.b.Overlay();
287        downloadingOverlay.title = 'Downloading...';
288        downloadingOverlay.userCanClose = false;
289        downloadingOverlay.msgEl = document.createElement('div');
290        downloadingOverlay.appendChild(downloadingOverlay.msgEl);
291        downloadingOverlay.msgEl.style.margin = '20px';
292        downloadingOverlay.update = function(msg) {
293          this.msgEl.textContent = msg;
294        }
295        downloadingOverlay.visible = true;
296
297        var accessToken = gapi.auth.getToken().access_token;
298        var xhr = new XMLHttpRequest();
299        xhr.open('GET', file.downloadUrl);
300        xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
301        xhr.onload = function() {
302          downloadingOverlay.visible = false;
303          onDownloaded(file.title, xhr.responseText);
304        };
305        xhr.onprogress = function(evt) {
306          downloadingOverlay.update(
307              Math.floor(evt.position * 100 / file.fileSize) + '% complete');
308        };
309        xhr.onerror = function() { alert('Failed downloading!'); };
310        xhr.send();
311      } else {
312        alert('No URL!');
313      }
314    }
315
316    function displayAllCollaborators() {
317      var allCollaborators = driveDocument_.getCollaborators();
318      var collaboratorCount = allCollaborators.length;
319      var collabspan = document.getElementById('collabs');
320      collabspan.innerHTML = '';
321      var imageList = [];
322      for (var i = 0; i < collaboratorCount; i++) {
323        var user = allCollaborators[i];
324
325        var img = document.createElement('img');
326        img.src = user.photoUrl;
327        img.alt = user.displayName;
328        img.height = 30;
329        img.width = 30;
330        img.className = 'collaborator-img';
331        collabspan.appendChild(img);
332        imageList.push({'image': img, 'name': user.displayName});
333      }
334      for (i = 0; i < imageList.length; i++) {
335        var collabTooltip = tr.ui.b.createDiv({
336            className: 'collaborator-tooltip'
337        });
338        var collabTooltipContent = tr.ui.b.createDiv({
339            className: 'collaborator-tooltip-content'
340        });
341        collabTooltipContent.textContent = imageList[i].name;
342        collabTooltip.appendChild(collabTooltipContent);
343        collabspan.appendChild(collabTooltip);
344        var collabTooltipArrow = tr.ui.b.createDiv({
345            className: 'collaborator-tooltip-arrow'});
346        collabTooltip.appendChild(collabTooltipArrow);
347        var collabTooltipArrowBefore = tr.ui.b.createDiv({
348            className: 'collaborator-tooltip-arrow-before'});
349        collabTooltipArrow.appendChild(collabTooltipArrowBefore);
350        var collabTooltipArrowAfter = tr.ui.b.createDiv({
351            className: 'collaborator-tooltip-arrow-after'});
352        collabTooltipArrow.appendChild(collabTooltipArrowAfter);
353
354        var rect = imageList[i].image.getBoundingClientRect();
355        collabTooltip.style.top = (rect.bottom - 6) + 'px';
356        collabTooltip.style.left =
357            (rect.left + 16 - (collabTooltip.offsetWidth / 2)) + 'px';
358        collabTooltipArrow.style.left = (collabTooltip.offsetWidth / 2) + 'px';
359        collabTooltip.style.visibility = 'hidden';
360        function visibilityDelegate(element, visibility) {
361          return function() {
362            element.style.visibility = visibility;
363          }
364        }
365        imageList[i].image.addEventListener(
366            'mouseover', visibilityDelegate(collabTooltip, 'visible'));
367        imageList[i].image.addEventListener(
368            'mouseout', visibilityDelegate(collabTooltip, 'hidden'));
369      }
370    }
371
372    function onRealtimeFileLoaded(doc) {
373      if (driveDocument_)
374        driveDocument_.close();
375      driveDocument_ = doc;
376      doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_JOINED,
377                           displayAllCollaborators);
378      doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_LEFT,
379                           displayAllCollaborators);
380
381      displayAllCollaborators(doc);
382    }
383
384    function onRealtimeError(e) {
385      alert('Error loading realtime: ' + e);
386    }
387
388    function onDownloaded(filename, content) {
389      gapi.load('auth:client,drive-realtime,drive-share', function() {
390        gapi.drive.realtime.load(driveFileId_,
391                                 onRealtimeFileLoaded,
392                                 null,
393                                 onRealtimeError);
394
395      });
396
397      var traces = [];
398      var filenames = [];
399      filenames.push(filename);
400      traces.push(content);
401      createViewFromTraces(filenames, traces);
402    }
403
404    function createViewFromTraces(filenames, traces) {
405      var m = new tr.Model();
406      var i = new tr.importer.Import(m);
407      var p = i.importTracesWithProgressDialog(traces);
408      p.then(
409          function() {
410            timelineViewEl_.model = m;
411            timelineViewEl_.updateDocumentFavicon();
412            timelineViewEl_.globalMode = true;
413            timelineViewEl_.viewTitle = '';
414          },
415          function(err) {
416            var downloadingOverlay = new tr.ui.b.Overlay();
417            downloadingOverlay.textContent =
418                tr.b.normalizeException(err).message;
419            downloadingOverlay.title = 'Import error';
420            downloadingOverlay.visible = true;
421          });
422    }
423
424    function onSaveToDiskClicked() {
425      throw new Error('Not implemented');
426    }
427
428    function onSaveToDriveClicked() {
429      throw new Error('Not implemented');
430    }
431
432    function onLoadFromDriveClicked() {
433      createPicker();
434    }
435
436    function onLoad() {
437      timelineViewEl_ = document.querySelector('x-timeline-view');
438      timelineViewEl_.globalMode = true;
439      var navbar = document.getElementById('navbar');
440      timelineViewEl_.style.top = navbar.offsetHeight + 'px';
441      tr.ui.b.decorate(timelineViewEl_, tr.ui.TimelineView);
442    }
443
444    window.addEventListener('load', onLoad);
445
446    document.getElementById('x-drive-save-to-disk').onclick =
447        onSaveToDiskClicked;
448    document.getElementById('x-drive-save-to-drive').onclick =
449        onSaveToDriveClicked;
450    document.getElementById('x-drive-load-from-drive').onclick =
451        onLoadFromDriveClicked;
452    document.getElementById('x-drive-share').onclick = function() {
453      shareClient_.showSettingsDialog();
454    };
455
456  }());
457
458  </script>
459  <script type="text/javascript"
460          src="https://apis.google.com/js/client.js?onload=onAPIClientLoaded_">
461  </script>
462</body>
463