blob: 229566a36b062025313a7ef82c27c942e7a2d080 [file] [log] [blame]
Deepanjan Royfd79a7d2018-07-24 13:26:55 -04001// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as m from 'mithril';
16
Isabelle Taylord99a5d72019-03-25 16:45:32 +000017import {Actions} from '../common/actions';
Hector Dearman79549c52019-04-01 13:30:46 +010018import {LogExists, LogExistsKey} from '../common/logs';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010019import {QueryResponse} from '../common/queries';
Primiano Tuccif30cd9c2018-08-13 01:53:26 +020020import {TimeSpan} from '../common/time';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010021
Nicolò Mazzucato87943de2019-07-25 16:26:33 +010022import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
Hector Dearmana89177f2018-10-04 15:42:59 +010023import {copyToClipboard} from './clipboard';
Neda Topoljanac643dbaa2019-09-11 12:06:56 +010024import {CounterDetailsPanel} from './counter_panel';
Hector Dearmanafa51b22018-12-11 16:38:30 +000025import {DragGestureHandler} from './drag_gesture_handler';
Primiano Tucci8afc06d2018-08-06 19:11:42 +010026import {globals} from './globals';
Neda Topoljanaca20caba2019-09-17 12:41:35 +010027import {HeapDumpDetailsPanel} from './heap_dump_panel';
Hector Dearman79549c52019-04-01 13:30:46 +010028import {LogPanel} from './logs_panel';
Hector Dearmanea002ea2019-01-21 11:43:45 +000029import {NotesEditorPanel, NotesPanel} from './notes_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040030import {OverviewTimelinePanel} from './overview_timeline_panel';
Deepanjan Royfd79a7d2018-07-24 13:26:55 -040031import {createPage} from './pages';
Michail Schwab405002c2018-07-26 13:19:10 -040032import {PanAndZoomHandler} from './pan_and_zoom_handler';
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040033import {Panel} from './panel';
34import {AnyAttrsVnode, PanelContainer} from './panel_container';
Isabelle Taylord99a5d72019-03-25 16:45:32 +000035import {SliceDetailsPanel} from './slice_panel';
36import {ThreadStatePanel} from './thread_state_panel';
Hector Dearman372ec602019-09-03 12:12:02 +010037import {TickmarkPanel} from './tickmark_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040038import {TimeAxisPanel} from './time_axis_panel';
Hector Dearman3e82d682019-01-09 15:55:10 +000039import {computeZoom} from './time_scale';
Isabelle Taylor8325c5a2019-02-13 13:38:17 +000040import {TimeSelectionPanel} from './time_selection_panel';
Hector Dearmanccb0b792019-01-22 13:19:41 +000041import {TRACK_SHELL_WIDTH} from './track_constants';
Deepanjan Roy32ad0d72018-10-18 14:50:16 -040042import {TrackGroupPanel} from './track_group_panel';
Deepanjan Roy1f658fe2018-09-11 08:38:17 -040043import {TrackPanel} from './track_panel';
Kodi Obika9adb0582019-06-12 09:36:03 -070044import {VideoPanel} from './video_panel';
Deepanjan Royfd79a7d2018-07-24 13:26:55 -040045
Isabelle Tayloree67d1b2019-02-27 14:12:53 +000046const DRAG_HANDLE_HEIGHT_PX = 28;
Isabelle Taylorb6257fc2019-02-19 13:13:32 +000047const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
Isabelle Tayloree67d1b2019-02-27 14:12:53 +000048const UP_ICON = 'keyboard_arrow_up';
49const DOWN_ICON = 'keyboard_arrow_down';
Isabelle Taylorf57b24f2019-07-31 11:06:41 +010050const SIDEBAR_WIDTH = 256;
Primiano Tucci8afc06d2018-08-06 19:11:42 +010051
Hector Dearman79549c52019-04-01 13:30:46 +010052function hasLogs(): boolean {
53 const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
54 return data && data.exists;
55}
56
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -040057class QueryTable extends Panel {
Primiano Tucci8afc06d2018-08-06 19:11:42 +010058 view() {
59 const resp = globals.queryResults.get('command') as QueryResponse;
60 if (resp === undefined) {
61 return m('');
62 }
63 const cols = [];
64 for (const col of resp.columns) {
65 cols.push(m('td', col));
66 }
67 const header = m('tr', cols);
68
69 const rows = [];
70 for (let i = 0; i < resp.rows.length; i++) {
71 const cells = [];
72 for (const col of resp.columns) {
73 cells.push(m('td', resp.rows[i][col]));
74 }
75 rows.push(m('tr', cells));
76 }
77 return m(
78 'div',
79 m('header.overview',
Hector Dearmanb1179f92019-01-08 16:18:56 +000080 `Query result - ${Math.round(resp.durationMs)} ms`,
81 m('span.code', resp.query),
Hector Dearmana89177f2018-10-04 15:42:59 +010082 resp.error ? null :
Hector Dearmanb1179f92019-01-08 16:18:56 +000083 m('button.query-ctrl',
Hector Dearmana89177f2018-10-04 15:42:59 +010084 {
85 onclick: () => {
86 const lines: string[][] = [];
87 lines.push(resp.columns);
88 for (const row of resp.rows) {
89 const line = [];
90 for (const col of resp.columns) {
91 line.push(row[col].toString());
92 }
93 lines.push(line);
94 }
95 copyToClipboard(
96 lines.map(line => line.join('\t')).join('\n'));
97 },
98 },
Hector Dearmanb1179f92019-01-08 16:18:56 +000099 'Copy as .tsv'),
100 m('button.query-ctrl',
101 {
102 onclick: () => {
103 globals.queryResults.delete('command');
104 globals.rafScheduler.scheduleFullRedraw();
105 }
106 },
107 'Close'), ),
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100108 resp.error ?
109 m('.query-error', `SQL error: ${resp.error}`) :
110 m('table.query-table', m('thead', header), m('tbody', rows)));
Deepanjan Roy97f63242018-09-20 15:32:01 -0400111 }
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400112
113 renderCanvas() {}
Deepanjan Roy97f63242018-09-20 15:32:01 -0400114}
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100115
Hector Dearmanafa51b22018-12-11 16:38:30 +0000116interface DragHandleAttrs {
117 height: number;
118 resize: (height: number) => void;
119}
120
121class DragHandle implements m.ClassComponent<DragHandleAttrs> {
122 private dragStartHeight = 0;
123 private height = 0;
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000124 private resize: (height: number) => void = () => {};
Hector Dearman79549c52019-04-01 13:30:46 +0100125 private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000126
127 oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
128 this.resize = attrs.resize;
129 this.height = attrs.height;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100130 this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000131 const elem = dom as HTMLElement;
132 new DragGestureHandler(
133 elem,
134 this.onDrag.bind(this),
135 this.onDragStart.bind(this),
136 this.onDragEnd.bind(this));
137 }
138
139 onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
140 this.resize = attrs.resize;
141 this.height = attrs.height;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100142 this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
Hector Dearmanafa51b22018-12-11 16:38:30 +0000143 }
144
145 onDrag(_x: number, y: number) {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000146 const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
147 this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
148 this.resize(Math.floor(newHeight));
Hector Dearmanafa51b22018-12-11 16:38:30 +0000149 globals.rafScheduler.scheduleFullRedraw();
150 }
151
152 onDragStart(_x: number, _y: number) {
153 this.dragStartHeight = this.height;
154 }
155
156 onDragEnd() {}
157
158 view() {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000159 const icon = this.isClosed ? UP_ICON : DOWN_ICON;
160 const title = this.isClosed ? 'Show panel' : 'Hide panel';
161 return m(
162 '.handle',
163 m('.handle-title', 'Current Selection'),
164 m('i.material-icons',
165 {
166 onclick: () => {
167 if (this.height === DRAG_HANDLE_HEIGHT_PX) {
168 this.isClosed = false;
169 this.resize(DEFAULT_DETAILS_HEIGHT_PX);
170 } else {
171 this.isClosed = true;
172 this.resize(DRAG_HANDLE_HEIGHT_PX);
173 }
174 globals.rafScheduler.scheduleFullRedraw();
175 },
176 title
177 },
178 icon));
Hector Dearmanafa51b22018-12-11 16:38:30 +0000179 }
180}
181
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400182/**
183 * Top-most level component for the viewer page. Holds tracks, brush timeline,
184 * panels, and everything else that's part of the main trace viewer page.
185 */
Deepanjan Roy97f63242018-09-20 15:32:01 -0400186class TraceViewer implements m.ClassComponent {
187 private onResize: () => void = () => {};
188 private zoomContent?: PanAndZoomHandler;
Hector Dearman79549c52019-04-01 13:30:46 +0100189 private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
Isabelle Taylor0733c8c2019-02-07 13:55:23 +0000190 // Used to set details panel to default height on selection.
Hector Dearman79549c52019-04-01 13:30:46 +0100191 private showDetailsPanel = true;
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000192 // Used to prevent global deselection if a pan/drag select occurred.
193 private keepCurrentSelection = false;
Hector Dearman59e98122018-08-13 14:21:38 +0100194
Deepanjan Roy97f63242018-09-20 15:32:01 -0400195 oncreate(vnode: m.CVnodeDOM) {
Deepanjan Roy9d95a252018-08-09 10:10:19 -0400196 const frontendLocalState = globals.frontendLocalState;
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400197 const updateDimensions = () => {
Michail Schwab405002c2018-07-26 13:19:10 -0400198 const rect = vnode.dom.getBoundingClientRect();
Deepanjan Roy9d95a252018-08-09 10:10:19 -0400199 frontendLocalState.timeScale.setLimitsPx(
Deepanjan Roy97f63242018-09-20 15:32:01 -0400200 0, rect.width - TRACK_SHELL_WIDTH);
Michail Schwab405002c2018-07-26 13:19:10 -0400201 };
202
Deepanjan Roy1f658fe2018-09-11 08:38:17 -0400203 updateDimensions();
204
205 // TODO: Do resize handling better.
206 this.onResize = () => {
207 updateDimensions();
208 globals.rafScheduler.scheduleFullRedraw();
209 };
Michail Schwab405002c2018-07-26 13:19:10 -0400210
211 // Once ResizeObservers are out, we can stop accessing the window here.
212 window.addEventListener('resize', this.onResize);
213
Michail Schwaba52306a2018-07-31 13:40:52 -0400214 const panZoomEl =
Deepanjan Royabd79aa2018-08-28 07:29:15 -0400215 vnode.dom.querySelector('.pan-and-zoom-content') as HTMLElement;
Michail Schwaba52306a2018-07-31 13:40:52 -0400216
Michail Schwab405002c2018-07-26 13:19:10 -0400217 this.zoomContent = new PanAndZoomHandler({
Michail Schwaba52306a2018-07-31 13:40:52 -0400218 element: panZoomEl,
Isabelle Taylorf57b24f2019-07-31 11:06:41 +0100219 contentOffsetX: SIDEBAR_WIDTH,
Michail Schwab405002c2018-07-26 13:19:10 -0400220 onPanned: (pannedPx: number) => {
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000221 this.keepCurrentSelection = true;
Primiano Tuccie36ca632018-08-21 14:32:23 +0200222 const traceTime = globals.state.traceTime;
223 const vizTime = globals.frontendLocalState.visibleWindowTime;
224 const origDelta = vizTime.duration;
225 const tDelta = frontendLocalState.timeScale.deltaPxToDuration(pannedPx);
226 let tStart = vizTime.start + tDelta;
227 let tEnd = vizTime.end + tDelta;
228 if (tStart < traceTime.startSec) {
229 tStart = traceTime.startSec;
230 tEnd = tStart + origDelta;
231 } else if (tEnd > traceTime.endSec) {
232 tEnd = traceTime.endSec;
233 tStart = tEnd - origDelta;
Primiano Tuccif30cd9c2018-08-13 01:53:26 +0200234 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200235 frontendLocalState.updateVisibleTime(new TimeSpan(tStart, tEnd));
Hector Dearman2ba11332018-10-18 14:32:43 +0100236 globals.rafScheduler.scheduleRedraw();
Michail Schwab405002c2018-07-26 13:19:10 -0400237 },
Hector Dearman3e82d682019-01-09 15:55:10 +0000238 onZoomed: (zoomedPositionPx: number, zoomRatio: number) => {
239 // TODO(hjd): Avoid hardcoding TRACK_SHELL_WIDTH.
240 // TODO(hjd): Improve support for zooming in overview timeline.
241 const span = frontendLocalState.visibleWindowTime;
242 const scale = frontendLocalState.timeScale;
243 const zoomPx = zoomedPositionPx - TRACK_SHELL_WIDTH;
244 const newSpan = computeZoom(scale, span, 1 - zoomRatio, zoomPx);
245 frontendLocalState.updateVisibleTime(newSpan);
246 globals.rafScheduler.scheduleRedraw();
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000247 },
248 onDragSelect: (selectStartPx: number|null, selectEndPx: number) => {
249 if (!selectStartPx) return;
250 this.keepCurrentSelection = true;
Isabelle Taylor1ae33bc2019-02-22 11:44:33 +0000251 globals.frontendLocalState.setShowTimeSelectPreview(false);
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000252 const traceTime = globals.state.traceTime;
253 const scale = frontendLocalState.timeScale;
254 const startPx = Math.min(selectStartPx, selectEndPx);
255 const endPx = Math.max(selectStartPx, selectEndPx);
256 const startTs = Math.max(traceTime.startSec,
257 scale.pxToTime(startPx - TRACK_SHELL_WIDTH));
258 const endTs = Math.min(traceTime.endSec,
259 scale.pxToTime(endPx - TRACK_SHELL_WIDTH));
Isabelle Taylor593ac3f2019-10-03 10:46:52 +0100260 globals.frontendLocalState.selectTimeRange(startTs, endTs);
Isabelle Taylor8325c5a2019-02-13 13:38:17 +0000261 globals.rafScheduler.scheduleRedraw();
Michail Schwab405002c2018-07-26 13:19:10 -0400262 }
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400263 });
Deepanjan Roy97f63242018-09-20 15:32:01 -0400264 }
Hector Dearman59e98122018-08-13 14:21:38 +0100265
Michail Schwab405002c2018-07-26 13:19:10 -0400266 onremove() {
267 window.removeEventListener('resize', this.onResize);
Deepanjan Roy97f63242018-09-20 15:32:01 -0400268 if (this.zoomContent) this.zoomContent.shutdown();
269 }
Hector Dearman59e98122018-08-13 14:21:38 +0100270
Michail Schwab405002c2018-07-26 13:19:10 -0400271 view() {
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400272 const scrollingPanels: AnyAttrsVnode[] =
Hector Dearman9189b6b2019-02-27 14:58:51 +0000273 globals.state.scrollingTracks.map(id => m(TrackPanel, {key: id, id}));
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400274
275 for (const group of Object.values(globals.state.trackGroups)) {
276 scrollingPanels.push(m(TrackGroupPanel, {
277 trackGroupId: group.id,
278 key: `trackgroup-${group.id}`,
279 }));
280 if (group.collapsed) continue;
281 for (const trackId of group.tracks) {
282 scrollingPanels.push(m(TrackPanel, {
283 key: `track-${group.id}-${trackId}`,
284 id: trackId,
285 }));
286 }
287 }
Isabelle Taylor1acfa2b2019-05-17 17:26:40 +0100288 scrollingPanels.unshift(m(QueryTable, {key: 'query'}));
Deepanjan Roy4fa0ecc2018-10-03 16:10:46 -0400289
Hector Dearmanea002ea2019-01-21 11:43:45 +0000290 const detailsPanels: AnyAttrsVnode[] = [];
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000291 const curSelection = globals.state.currentSelection;
292 if (curSelection) {
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000293 switch (curSelection.kind) {
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000294 case 'NOTE':
295 detailsPanels.push(m(NotesEditorPanel, {
296 key: 'notes',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000297 id: curSelection.id,
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000298 }));
299 break;
300 case 'SLICE':
301 detailsPanels.push(m(SliceDetailsPanel, {
302 key: 'slice',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000303 utid: curSelection.utid,
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000304 }));
305 break;
Neda Topoljanac643dbaa2019-09-11 12:06:56 +0100306 case 'COUNTER':
307 detailsPanels.push(m(CounterDetailsPanel, {
308 key: 'counter',
309 }));
310 break;
Neda Topoljanaca20caba2019-09-17 12:41:35 +0100311 case 'HEAP_DUMP':
Neda Topoljanac39288012019-09-25 15:25:01 +0100312 detailsPanels.push(m(HeapDumpDetailsPanel, {key: 'heap_dump'}));
Neda Topoljanaca20caba2019-09-17 12:41:35 +0100313 break;
Nicolò Mazzucato87943de2019-07-25 16:26:33 +0100314 case 'CHROME_SLICE':
315 detailsPanels.push(m(ChromeSliceDetailsPanel));
316 break;
Isabelle Taylord99a5d72019-03-25 16:45:32 +0000317 case 'THREAD_STATE':
318 detailsPanels.push(m(ThreadStatePanel, {
319 key: 'thread_state',
320 ts: curSelection.ts,
321 dur: curSelection.dur,
322 utid: curSelection.utid,
Isabelle Taylor9d3b9942019-09-03 13:18:19 +0100323 state: curSelection.state,
324 cpu: curSelection.cpu
Isabelle Taylord99a5d72019-03-25 16:45:32 +0000325 }));
326 break;
Isabelle Taylor9a4afc62019-02-06 11:54:36 +0000327 default:
328 break;
329 }
Hector Dearman79549c52019-04-01 13:30:46 +0100330 } else if (hasLogs()) {
331 detailsPanels.push(m(LogPanel, {}));
Isabelle Taylorb9222c32019-01-31 10:58:37 +0000332 }
333
Isabelle Taylorcc850612019-08-19 14:15:20 +0100334 const wasShowing = this.showDetailsPanel;
Hector Dearman79549c52019-04-01 13:30:46 +0100335 this.showDetailsPanel = detailsPanels.length > 0;
Isabelle Taylorcc850612019-08-19 14:15:20 +0100336 // Pop up details panel on first selection.
337 if (!wasShowing && this.showDetailsPanel &&
338 this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
339 this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
340 }
Hector Dearman79549c52019-04-01 13:30:46 +0100341
Michail Schwabc834e482018-07-26 13:30:13 -0400342 return m(
Primiano Tucci8afc06d2018-08-06 19:11:42 +0100343 '.page',
Kodi Obika9adb0582019-06-12 09:36:03 -0700344 m('.split-panel',
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100345 m('.pan-and-zoom-content',
346 {
347 onclick: () => {
348 // We don't want to deselect when panning/drag selecting.
349 if (this.keepCurrentSelection) {
350 this.keepCurrentSelection = false;
351 return;
352 }
Isabelle Taylor061c8712019-09-16 13:38:58 +0100353 globals.makeSelection(Actions.deselect({}));
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000354 }
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100355 },
356 m('.pinned-panel-container', m(PanelContainer, {
357 doesScroll: false,
358 panels: [
359 m(OverviewTimelinePanel, {key: 'overview'}),
360 m(TimeAxisPanel, {key: 'timeaxis'}),
361 m(TimeSelectionPanel, {key: 'timeselection'}),
362 m(NotesPanel, {key: 'notes'}),
Hector Dearman372ec602019-09-03 12:12:02 +0100363 m(TickmarkPanel, {key: 'searchTickmarks'}),
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100364 ...globals.state.pinnedTracks.map(
365 id => m(TrackPanel, {key: id, id})),
366 ],
367 kind: 'OVERVIEW',
368 })),
369 m('.scrolling-panel-container', m(PanelContainer, {
370 doesScroll: true,
371 panels: scrollingPanels,
372 kind: 'TRACKS',
373 }))),
374 m('.video-panel',
375 (globals.state.videoEnabled && globals.state.video != null) ?
376 m(VideoPanel) :
377 null)),
Hector Dearmanafa51b22018-12-11 16:38:30 +0000378 m('.details-content',
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000379 {
380 style: {
381 height: `${this.detailsHeight}px`,
Hector Dearman79549c52019-04-01 13:30:46 +0100382 display: this.showDetailsPanel ? null : 'none'
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000383 }
384 },
Hector Dearmanafa51b22018-12-11 16:38:30 +0000385 m(DragHandle, {
386 resize: (height: number) => {
387 this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
388 },
389 height: this.detailsHeight,
Hector Dearmanea002ea2019-01-21 11:43:45 +0000390 }),
Isabelle Tayloree67d1b2019-02-27 14:12:53 +0000391 m('.details-panel-container',
Isabelle Taylorf41b7c72019-07-24 13:55:43 +0100392 m(PanelContainer,
393 {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'}))));
Deepanjan Roy97f63242018-09-20 15:32:01 -0400394 }
395}
Deepanjan Royfd79a7d2018-07-24 13:26:55 -0400396
397export const ViewerPage = createPage({
398 view() {
399 return m(TraceViewer);
400 }
Hector Dearman64484b92018-07-30 16:14:43 +0100401});