1<!-- Copyright (C) 2017 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--> 15<template> 16 <div id="app"> 17 <md-whiteframe md-tag="md-toolbar"> 18 <h1 class="md-title" style="flex: 1">{{title}}</h1> 19 <a class="md-button md-accent md-raised md-theme-default" @click="clear()" v-if="dataLoaded">Clear</a> 20 </md-whiteframe> 21 <div class="main-content"> 22 <md-layout v-if="!dataLoaded" class="m-2"> 23 <dataadb ref="adb" :store="store" @dataReady="onDataReady" @statusChange="setStatus"/> 24 <datainput ref="input" :store="store" @dataReady="onDataReady" @statusChange="setStatus"/> 25 </md-layout> 26 <md-card v-if="dataLoaded"> 27 <md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"> 28 <h2 class="md-title">Timeline</h2> 29 <datafilter v-for="file in files" :key="file.filename" :store="store" :file="file" /> 30 </md-whiteframe> 31 <md-list> 32 <md-list-item v-for="(file, idx) in files" :key="file.filename"> 33 <md-icon>{{file.type.icon}}</md-icon> 34 <timeline :items="file.timeline" :selected-index="file.selectedIndex" :scale="scale" @item-selected="onTimelineItemSelected($event, idx)" class="timeline" /> 35 </md-list-item> 36 </md-list> 37 </md-card> 38 <dataview v-for="file in files" :key="file.filename" :ref="file.filename" :store="store" :file="file" @focus="onDataViewFocus(file.filename)" /> 39 </div> 40 </div> 41</template> 42<script> 43import TreeView from './TreeView.vue' 44import Timeline from './Timeline.vue' 45import Rects from './Rects.vue' 46import DataView from './DataView.vue' 47import DataInput from './DataInput.vue' 48import LocalStore from './localstore.js' 49import DataAdb from './DataAdb.vue' 50import DataFilter from './DataFilter.vue' 51 52const APP_NAME = "Winscope" 53 54// Find the index of the last element matching the predicate in a sorted array 55function findLastMatchingSorted(array, predicate) { 56 var a = 0; 57 var b = array.length - 1; 58 while (b - a > 1) { 59 var m = Math.floor((a + b) / 2); 60 if (predicate(array, m)) { 61 a = m; 62 } else { 63 b = m - 1; 64 } 65 } 66 return predicate(array, b) ? b : a; 67} 68 69export default { 70 name: 'app', 71 data() { 72 return { 73 files: [], 74 title: APP_NAME, 75 currentTimestamp: 0, 76 activeDataView: null, 77 store: LocalStore('app', { 78 flattened: false, 79 onlyVisible: false, 80 displayDefaults: true, 81 }), 82 } 83 }, 84 created() { 85 window.addEventListener('keydown', this.onKeyDown); 86 document.title = this.title; 87 }, 88 methods: { 89 clear() { 90 this.files.forEach(function(item) { item.destroy(); }) 91 this.files = []; 92 this.activeDataView = null; 93 }, 94 onTimelineItemSelected(index, timelineIndex) { 95 this.files[timelineIndex].selectedIndex = index; 96 var t = parseInt(this.files[timelineIndex].timeline[index]); 97 for (var i = 0; i < this.files.length; i++) { 98 if (i != timelineIndex) { 99 this.files[i].selectedIndex = findLastMatchingSorted(this.files[i].timeline, function(array, idx) { 100 return parseInt(array[idx]) <= t; 101 }); 102 } 103 } 104 this.currentTimestamp = t; 105 }, 106 advanceTimeline(direction) { 107 var closestTimeline = -1; 108 var timeDiff = Infinity; 109 for (var idx = 0; idx < this.files.length; idx++) { 110 var file = this.files[idx]; 111 var cur = file.selectedIndex; 112 if (cur + direction < 0 || cur + direction >= this.files[idx].timeline.length) { 113 continue; 114 } 115 var d = Math.abs(parseInt(file.timeline[cur + direction]) - this.currentTimestamp); 116 if (timeDiff > d) { 117 timeDiff = d; 118 closestTimeline = idx; 119 } 120 } 121 if (closestTimeline >= 0) { 122 this.files[closestTimeline].selectedIndex += direction; 123 this.currentTimestamp = parseInt(this.files[closestTimeline].timeline[this.files[closestTimeline].selectedIndex]); 124 } 125 }, 126 onDataViewFocus(view) { 127 this.activeDataView = view; 128 }, 129 onKeyDown(event) { 130 event = event || window.event; 131 if (event.keyCode == 37 /* left */ ) { 132 this.advanceTimeline(-1); 133 } else if (event.keyCode == 39 /* right */ ) { 134 this.advanceTimeline(1); 135 } else if (event.keyCode == 38 /* up */ ) { 136 this.$refs[this.activeView][0].arrowUp(); 137 } else if (event.keyCode == 40 /* down */ ) { 138 this.$refs[this.activeView][0].arrowDown(); 139 } else { 140 return false; 141 } 142 event.preventDefault(); 143 return true; 144 }, 145 onDataReady(files) { 146 this.files = files; 147 }, 148 setStatus(status) { 149 if (status) { 150 this.title = status; 151 } else { 152 this.title = APP_NAME; 153 } 154 } 155 }, 156 computed: { 157 prettyDump: function() { return JSON.stringify(this.dump, null, 2); }, 158 dataLoaded: function() { return this.files.length > 0 }, 159 scale() { 160 var mx = Math.max(...(this.files.map(f => Math.max(...f.timeline)))); 161 var mi = Math.min(...(this.files.map(f => Math.min(...f.timeline)))); 162 return [mi, mx]; 163 }, 164 activeView: function() { 165 if (!this.activeDataView && this.files.length > 0) { 166 this.activeDataView = this.files[0].filename; 167 } 168 return this.activeDataView; 169 } 170 }, 171 watch: { 172 title() { 173 document.title = this.title; 174 } 175 }, 176 components: { 177 'timeline': Timeline, 178 'dataview': DataView, 179 'datainput': DataInput, 180 'dataadb': DataAdb, 181 'datafilter': DataFilter, 182 }, 183} 184 185</script> 186<style> 187.main-content>* { 188 margin: 1em; 189} 190 191.card-toolbar { 192 border-bottom: 1px solid rgba(0, 0, 0, .12); 193} 194 195.timeline { 196 margin: 16px; 197} 198 199.container { 200 display: flex; 201 flex-wrap: wrap; 202} 203 204.md-layout > .md-card { 205 margin: 0.5em; 206} 207 208.md-button { 209 margin-top: 1em 210} 211 212h1, 213h2 { 214 font-weight: normal; 215} 216 217ul { 218 list-style-type: none; 219 padding: 0; 220} 221 222li { 223 display: inline-block; 224 margin: 0 10px; 225} 226 227a { 228 color: #42b983; 229} 230 231</style> 232