Merge "perfetto-ui: Move details panel into it's own component"
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
new file mode 100644
index 0000000..1bed6ea
--- /dev/null
+++ b/ui/src/frontend/details_panel.ts
@@ -0,0 +1,181 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import * as m from 'mithril';
+
+import {LogExists, LogExistsKey} from '../common/logs';
+
+import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
+import {CounterDetailsPanel} from './counter_panel';
+import {DragGestureHandler} from './drag_gesture_handler';
+import {globals} from './globals';
+import {HeapProfileDetailsPanel} from './heap_profile_panel';
+import {LogPanel} from './logs_panel';
+import {NotesEditorPanel} from './notes_panel';
+import {AnyAttrsVnode, PanelContainer} from './panel_container';
+import {SliceDetailsPanel} from './slice_panel';
+import {ThreadStatePanel} from './thread_state_panel';
+
+const UP_ICON = 'keyboard_arrow_up';
+const DOWN_ICON = 'keyboard_arrow_down';
+const DRAG_HANDLE_HEIGHT_PX = 28;
+const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
+
+function hasLogs(): boolean {
+  const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
+  return data && data.exists;
+}
+
+interface DragHandleAttrs {
+  height: number;
+  resize: (height: number) => void;
+}
+
+class DragHandle implements m.ClassComponent<DragHandleAttrs> {
+  private dragStartHeight = 0;
+  private height = 0;
+  private resize: (height: number) => void = () => {};
+  private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+
+  oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
+    this.resize = attrs.resize;
+    this.height = attrs.height;
+    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+    const elem = dom as HTMLElement;
+    new DragGestureHandler(
+        elem,
+        this.onDrag.bind(this),
+        this.onDragStart.bind(this),
+        this.onDragEnd.bind(this));
+  }
+
+  onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
+    this.resize = attrs.resize;
+    this.height = attrs.height;
+    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
+  }
+
+  onDrag(_x: number, y: number) {
+    const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
+    this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
+    this.resize(Math.floor(newHeight));
+    globals.rafScheduler.scheduleFullRedraw();
+  }
+
+  onDragStart(_x: number, _y: number) {
+    this.dragStartHeight = this.height;
+  }
+
+  onDragEnd() {}
+
+  view() {
+    const icon = this.isClosed ? UP_ICON : DOWN_ICON;
+    const title = this.isClosed ? 'Show panel' : 'Hide panel';
+    return m(
+        '.handle',
+        m('.handle-title', 'Current Selection'),
+        m('i.material-icons',
+          {
+            onclick: () => {
+              if (this.height === DRAG_HANDLE_HEIGHT_PX) {
+                this.isClosed = false;
+                this.resize(DEFAULT_DETAILS_HEIGHT_PX);
+              } else {
+                this.isClosed = true;
+                this.resize(DRAG_HANDLE_HEIGHT_PX);
+              }
+              globals.rafScheduler.scheduleFullRedraw();
+            },
+            title
+          },
+          icon));
+  }
+}
+
+export class DetailsPanel implements m.ClassComponent {
+  private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
+  // Used to set details panel to default height on selection.
+  private showDetailsPanel = true;
+
+  view() {
+    const detailsPanels: AnyAttrsVnode[] = [];
+    const curSelection = globals.state.currentSelection;
+    if (curSelection) {
+      switch (curSelection.kind) {
+        case 'NOTE':
+          detailsPanels.push(m(NotesEditorPanel, {
+            key: 'notes',
+            id: curSelection.id,
+          }));
+          break;
+        case 'SLICE':
+          detailsPanels.push(m(SliceDetailsPanel, {
+            key: 'slice',
+          }));
+          break;
+        case 'COUNTER':
+          detailsPanels.push(m(CounterDetailsPanel, {
+            key: 'counter',
+          }));
+          break;
+        case 'HEAP_PROFILE':
+          detailsPanels.push(m(HeapProfileDetailsPanel, {key: 'heap_profile'}));
+          break;
+        case 'CHROME_SLICE':
+          detailsPanels.push(m(ChromeSliceDetailsPanel));
+          break;
+        case 'THREAD_STATE':
+          detailsPanels.push(m(ThreadStatePanel, {
+            key: 'thread_state',
+            ts: curSelection.ts,
+            dur: curSelection.dur,
+            utid: curSelection.utid,
+            state: curSelection.state,
+            cpu: curSelection.cpu
+          }));
+          break;
+        default:
+          break;
+      }
+    } else if (hasLogs()) {
+      detailsPanels.push(m(LogPanel, {}));
+    }
+
+    const wasShowing = this.showDetailsPanel;
+    this.showDetailsPanel = detailsPanels.length > 0;
+    // Pop up details panel on first selection.
+    if (!wasShowing && this.showDetailsPanel &&
+        this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
+      this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
+    }
+
+    return m(
+        '.details-content',
+        {
+          style: {
+            height: `${this.detailsHeight}px`,
+            display: this.showDetailsPanel ? null : 'none'
+          }
+        },
+        m(DragHandle, {
+          resize: (height: number) => {
+            this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
+          },
+          height: this.detailsHeight,
+        }),
+        m('.details-panel-container',
+          m(PanelContainer,
+            {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'})));
+  }
+}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 53430c4..37c0279 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -15,25 +15,18 @@
 import * as m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {LogExists, LogExistsKey} from '../common/logs';
 import {QueryResponse} from '../common/queries';
 import {TimeSpan} from '../common/time';
 
-import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
 import {copyToClipboard} from './clipboard';
-import {CounterDetailsPanel} from './counter_panel';
-import {DragGestureHandler} from './drag_gesture_handler';
+import {DetailsPanel} from './details_panel';
 import {globals} from './globals';
-import {HeapProfileDetailsPanel} from './heap_profile_panel';
-import {LogPanel} from './logs_panel';
-import {NotesEditorPanel, NotesPanel} from './notes_panel';
+import {NotesPanel} from './notes_panel';
 import {OverviewTimelinePanel} from './overview_timeline_panel';
 import {createPage} from './pages';
 import {PanAndZoomHandler} from './pan_and_zoom_handler';
 import {Panel} from './panel';
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
-import {SliceDetailsPanel} from './slice_panel';
-import {ThreadStatePanel} from './thread_state_panel';
 import {TickmarkPanel} from './tickmark_panel';
 import {TimeAxisPanel} from './time_axis_panel';
 import {computeZoom} from './time_scale';
@@ -43,17 +36,8 @@
 import {TrackPanel} from './track_panel';
 import {VideoPanel} from './video_panel';
 
-const DRAG_HANDLE_HEIGHT_PX = 28;
-const DEFAULT_DETAILS_HEIGHT_PX = 230 + DRAG_HANDLE_HEIGHT_PX;
-const UP_ICON = 'keyboard_arrow_up';
-const DOWN_ICON = 'keyboard_arrow_down';
 const SIDEBAR_WIDTH = 256;
 
-function hasLogs(): boolean {
-  const data = globals.trackDataStore.get(LogExistsKey) as LogExists;
-  return data && data.exists;
-}
-
 class QueryTable extends Panel {
   view() {
     const resp = globals.queryResults.get('command') as QueryResponse;
@@ -113,71 +97,6 @@
   renderCanvas() {}
 }
 
-interface DragHandleAttrs {
-  height: number;
-  resize: (height: number) => void;
-}
-
-class DragHandle implements m.ClassComponent<DragHandleAttrs> {
-  private dragStartHeight = 0;
-  private height = 0;
-  private resize: (height: number) => void = () => {};
-  private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-
-  oncreate({dom, attrs}: m.CVnodeDOM<DragHandleAttrs>) {
-    this.resize = attrs.resize;
-    this.height = attrs.height;
-    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-    const elem = dom as HTMLElement;
-    new DragGestureHandler(
-        elem,
-        this.onDrag.bind(this),
-        this.onDragStart.bind(this),
-        this.onDragEnd.bind(this));
-  }
-
-  onupdate({attrs}: m.CVnodeDOM<DragHandleAttrs>) {
-    this.resize = attrs.resize;
-    this.height = attrs.height;
-    this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX;
-  }
-
-  onDrag(_x: number, y: number) {
-    const newHeight = this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y;
-    this.isClosed = Math.floor(newHeight) <= DRAG_HANDLE_HEIGHT_PX;
-    this.resize(Math.floor(newHeight));
-    globals.rafScheduler.scheduleFullRedraw();
-  }
-
-  onDragStart(_x: number, _y: number) {
-    this.dragStartHeight = this.height;
-  }
-
-  onDragEnd() {}
-
-  view() {
-    const icon = this.isClosed ? UP_ICON : DOWN_ICON;
-    const title = this.isClosed ? 'Show panel' : 'Hide panel';
-    return m(
-        '.handle',
-        m('.handle-title', 'Current Selection'),
-        m('i.material-icons',
-          {
-            onclick: () => {
-              if (this.height === DRAG_HANDLE_HEIGHT_PX) {
-                this.isClosed = false;
-                this.resize(DEFAULT_DETAILS_HEIGHT_PX);
-              } else {
-                this.isClosed = true;
-                this.resize(DRAG_HANDLE_HEIGHT_PX);
-              }
-              globals.rafScheduler.scheduleFullRedraw();
-            },
-            title
-          },
-          icon));
-  }
-}
 
 // Checks if the mousePos is within 3px of the start or end of the
 // current selected time range.
@@ -206,9 +125,6 @@
 class TraceViewer implements m.ClassComponent {
   private onResize: () => void = () => {};
   private zoomContent?: PanAndZoomHandler;
-  private detailsHeight = DRAG_HANDLE_HEIGHT_PX;
-  // Used to set details panel to default height on selection.
-  private showDetailsPanel = true;
   // Used to prevent global deselection if a pan/drag select occurred.
   private keepCurrentSelection = false;
 
@@ -327,57 +243,6 @@
     }
     scrollingPanels.unshift(m(QueryTable, {key: 'query'}));
 
-    const detailsPanels: AnyAttrsVnode[] = [];
-    const curSelection = globals.state.currentSelection;
-    if (curSelection) {
-      switch (curSelection.kind) {
-        case 'NOTE':
-          detailsPanels.push(m(NotesEditorPanel, {
-            key: 'notes',
-            id: curSelection.id,
-          }));
-          break;
-        case 'SLICE':
-          detailsPanels.push(m(SliceDetailsPanel, {
-            key: 'slice',
-          }));
-          break;
-        case 'COUNTER':
-          detailsPanels.push(m(CounterDetailsPanel, {
-            key: 'counter',
-          }));
-          break;
-        case 'HEAP_PROFILE':
-          detailsPanels.push(m(HeapProfileDetailsPanel, {key: 'heap_profile'}));
-          break;
-        case 'CHROME_SLICE':
-          detailsPanels.push(m(ChromeSliceDetailsPanel));
-          break;
-        case 'THREAD_STATE':
-          detailsPanels.push(m(ThreadStatePanel, {
-            key: 'thread_state',
-            ts: curSelection.ts,
-            dur: curSelection.dur,
-            utid: curSelection.utid,
-            state: curSelection.state,
-            cpu: curSelection.cpu
-          }));
-          break;
-        default:
-          break;
-      }
-    } else if (hasLogs()) {
-      detailsPanels.push(m(LogPanel, {}));
-    }
-
-    const wasShowing = this.showDetailsPanel;
-    this.showDetailsPanel = detailsPanels.length > 0;
-    // Pop up details panel on first selection.
-    if (!wasShowing && this.showDetailsPanel &&
-        this.detailsHeight === DRAG_HANDLE_HEIGHT_PX) {
-      this.detailsHeight = DEFAULT_DETAILS_HEIGHT_PX;
-    }
-
     return m(
         '.page',
         m('.split-panel',
@@ -414,22 +279,7 @@
             (globals.state.videoEnabled && globals.state.video != null) ?
                 m(VideoPanel) :
                 null)),
-        m('.details-content',
-          {
-            style: {
-              height: `${this.detailsHeight}px`,
-              display: this.showDetailsPanel ? null : 'none'
-            }
-          },
-          m(DragHandle, {
-            resize: (height: number) => {
-              this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
-            },
-            height: this.detailsHeight,
-          }),
-          m('.details-panel-container',
-            m(PanelContainer,
-              {doesScroll: true, panels: detailsPanels, kind: 'DETAILS'}))));
+        m(DetailsPanel));
   }
 }