Minor winscope improvements.

Expand/collapse button for traces now centred with icon. Keyboard arrows
move cursor in input text fields, rather than the timeline. Error lines now clickable and hit
area increased - click to navigate to error timestamp. Timestamp label
on minimized timeline and seek time field on expanded timeline are now
editable in the same way as the search timestamp function in search bar.

Bug: b/199146199
Test: Upload any trace to see the implementations.
Change-Id: Iff2c5423933b7f77292f0786ddcf5ffe715c2854
This commit is contained in:
Priyanka
2021-08-31 15:16:08 +00:00
parent e228166ec3
commit 5292fee7c2
9 changed files with 169 additions and 52 deletions

View File

@@ -107,6 +107,7 @@ export default {
navigationStyle: NAVIGATION_STYLE.GLOBAL,
flickerTraceView: false,
showFileTypes: [],
isInputMode: false,
}),
overlayRef: 'overlay',
mainContentStyle: {
@@ -187,6 +188,7 @@ export default {
},
onKeyDown(event) {
event = event || window.event;
if (this.store.isInputMode) return false;
if (event.keyCode == 37 /* left */ ) {
this.$store.dispatch('advanceTimeline', DIRECTION.BACKWARD);
} else if (event.keyCode == 39 /* right */ ) {

View File

@@ -16,13 +16,13 @@
<div @click="onClick($event)">
<flat-card v-if="hasDataView(file)">
<md-card-header>
<button class="toggle-view-button" @click="toggleView">
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
</i>
</button>
<md-card-header-text>
<div class="md-title">
<button class="toggle-view-button" @click="toggleView">
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
</i>
</button>
<md-icon>{{ TRACE_ICONS[file.type] }}</md-icon>
{{ file.type }}
</div>

View File

@@ -65,6 +65,13 @@
md-elevation="0"
class="md-transparent">
<md-button
@click="toggleSearch()"
class="drop-search"
>
Toggle search bar
</md-button>
<div class="toolbar" :class="{ expanded: expanded }">
<div class="resize-bar" v-show="expanded">
<div v-if="video" @mousedown="resizeBottomNav">
@@ -75,11 +82,6 @@
</div>
</div>
<md-button
@click="toggleSearch()"
class="drop-search"
>Toggle search bar</md-button>
<div class="active-timeline" v-show="minimized">
<div
class="active-timeline-icon"
@@ -149,9 +151,15 @@
v-show="minimized"
v-if="hasTimeline"
>
<label>
{{ seekTime }}
</label>
<input
class="timestamp-search-input"
v-model="searchInput"
spellcheck="false"
:placeholder="seekTime"
@focus="updateInputMode(true)"
@blur="updateInputMode(false)"
@keyup.enter="updateSearchForTimestamp"
/>
<timeline
:store="store"
:flickerMode="flickerMode"
@@ -213,7 +221,17 @@
:style="`padding-top: ${resizeOffset}px;`"
>
<div class="seek-time" v-if="seekTime">
<b>Seek time</b>: {{ seekTime }}
<b>Seek time: </b>
<input
class="timestamp-search-input"
:class="{ expanded: expanded }"
v-model="searchInput"
spellcheck="false"
:placeholder="seekTime"
@focus="updateInputMode(true)"
@blur="updateInputMode(false)"
@keyup.enter="updateSearchForTimestamp"
/>
</div>
<timelines
@@ -283,10 +301,10 @@ import MdIconOption from './components/IconSelection/IconSelectOption.vue';
import Searchbar from './Searchbar.vue';
import FileType from './mixins/FileType.js';
import {NAVIGATION_STYLE} from './utils/consts';
import {TRACE_ICONS, FILE_TYPES} from '@/decode.js';
import {TRACE_ICONS} from '@/decode.js';
// eslint-disable-next-line camelcase
import {nanos_to_string} from './transform.js';
import {nanos_to_string, getClosestTimestamp} from './transform.js';
export default {
name: 'overlay',
@@ -312,6 +330,8 @@ export default {
cropIntent: null,
TRACE_ICONS,
search: false,
searchInput: "",
isSeekTimeInputMode: false,
};
},
created() {
@@ -324,6 +344,7 @@ export default {
},
destroyed() {
this.$store.commit('removeMergedTimeline', this.mergedTimeline);
this.updateInputMode(false);
},
watch: {
navigationStyle(style) {
@@ -483,6 +504,26 @@ export default {
toggleSearch() {
this.search = !(this.search);
},
/**
* determines whether left/right arrow keys should move cursor in input field
* and upon click of input field, fills with current timestamp
*/
updateInputMode(isInputMode) {
this.isSeekTimeInputMode = isInputMode;
this.store.isInputMode = isInputMode;
if (!isInputMode) {
this.searchInput = "";
} else {
this.searchInput = this.seekTime;
}
},
/** Navigates to closest timestamp in timeline to search input*/
updateSearchForTimestamp() {
const closestTimestamp = getClosestTimestamp(this.searchInput, this.mergedTimeline.timeline);
this.$store.dispatch("updateTimelineTime", closestTimestamp);
this.updateInputMode(false);
},
emitBottomHeightUpdate() {
if (this.$refs.bottomNav) {
const newHeight = this.$refs.bottomNav.$el.clientHeight;
@@ -855,6 +896,7 @@ export default {
color: rgba(0,0,0,0.54);
font-size: 12px;
font-family: inherit;
cursor: text;
}
.minimized-timeline-content .minimized-timeline {
@@ -880,6 +922,27 @@ export default {
cursor: help;
}
.timestamp-search-input {
outline: none;
border-width: 0 0 1px;
border-color: gray;
font-family: inherit;
color: #448aff;
font-size: 12px;
padding: 0;
letter-spacing: inherit;
width: 125px;
}
.timestamp-search-input:focus {
border-color: #448aff;
}
.timestamp-search-input.expanded {
font-size: 14px;
width: 150px;
}
.drop-search:hover {
background-color: #9af39f;
}

View File

@@ -17,9 +17,14 @@
<div class="tabs">
<div class="search-timestamp" v-if="isTimestampSearch()">
<md-field class="search-input">
<md-field md-inline class="search-input">
<label>Enter timestamp</label>
<md-input v-model="searchInput" @keyup.enter.native="updateSearchForTimestamp" />
<md-input
v-model="searchInput"
v-on:focus="updateInputMode(true)"
v-on:blur="updateInputMode(false)"
@keyup.enter.native="updateSearchForTimestamp"
/>
</md-field>
<md-button
class="md-dense md-primary search-timestamp-button"
@@ -37,7 +42,7 @@
<th style="width: 80%">Description</th>
</tr>
<tr v-for="item in filteredTransitionsAndErrors" :key="item">
<tr v-for="item in filteredTransitionsAndErrors" :key="item.id">
<td
v-if="isTransition(item)"
class="inline-time"
@@ -58,9 +63,7 @@
v-if="isTransition(item)"
class="inline-transition"
:style="{color: transitionTextColor(item.transition)}"
@click="
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
"
@click="setCurrentTimestamp(transitionStart(transitionTags(item.id)))"
>
{{ transitionDesc(item.transition) }}
</td>
@@ -82,12 +85,16 @@
</td>
</tr>
</table>
<md-field class="search-input">
<md-field md-inline class="search-input">
<label
>Filter by transition or error message. Click to navigate to closest
timestamp in active timeline.</label
>
<md-input v-model="searchInput"></md-input>
<md-input
v-model="searchInput"
v-on:focus="updateInputMode(true)"
v-on:blur="updateInputMode(false)"
/>
</md-field>
</div>
</div>
@@ -107,9 +114,7 @@
</template>
<script>
import { transitionMap, SEARCH_TYPE } from "./utils/consts";
import { nanos_to_string, string_to_nanos } from "./transform";
const regExpTimestampSearch = new RegExp(/^\d+$/);
import { nanos_to_string, getClosestTimestamp } from "./transform";
export default {
name: "searchbar",
@@ -174,8 +179,10 @@ export default {
var times = tags.map((tag) => tag.timestamp);
return times[times.length - 1];
},
/** Upon selecting a start/end tag in the dropdown;
* navigates to that timestamp in the timeline */
/**
* Upon selecting a start/end tag in the dropdown;
* navigates to that timestamp in the timeline
*/
setCurrentTimestamp(timestamp) {
this.$store.dispatch("updateTimelineTime", timestamp);
},
@@ -195,17 +202,7 @@ export default {
/** Navigates to closest timestamp in timeline to search input*/
updateSearchForTimestamp() {
if (regExpTimestampSearch.test(this.searchInput)) {
var roundedTimestamp = parseInt(this.searchInput);
} else {
var roundedTimestamp = string_to_nanos(this.searchInput);
}
var closestTimestamp = this.timeline.reduce(function (prev, curr) {
return Math.abs(curr - roundedTimestamp) <
Math.abs(prev - roundedTimestamp)
? curr
: prev;
});
const closestTimestamp = getClosestTimestamp(this.searchInput, this.timeline);
this.setCurrentTimestamp(closestTimestamp);
},
@@ -218,6 +215,11 @@ export default {
isTransition(item) {
return item.stacktrace === undefined;
},
/** determines whether left/right arrow keys should move cursor in input field */
updateInputMode(isInputMode) {
this.store.isInputMode = isInputMode;
},
},
computed: {
filteredTransitionsAndErrors() {
@@ -231,6 +233,9 @@ export default {
});
},
},
destroyed() {
this.updateInputMode(false);
},
};
</script>
<style scoped>

View File

@@ -57,12 +57,13 @@
/>
<line
v-for="error in errorPositions"
:key="error"
:x1="`${error}%`"
:x2="`${error}%`"
:key="error.pos"
:x1="`${error.pos}%`"
:x2="`${error.pos}%`"
y1="0"
y2="18px"
class="error"
@click="onErrorClick(error.ts)"
/>
</svg>
</div>
@@ -139,6 +140,7 @@ export default {
}
.error {
stroke: rgb(255, 0, 0);
stroke-width: 2px;
stroke-width: 8px;
cursor: pointer;
}
</style>

View File

@@ -45,7 +45,11 @@
<md-checkbox v-if="hasTagsOrErrors" v-model="store.flickerTraceView">Flicker</md-checkbox>
<md-field md-inline class="filter">
<label>Filter...</label>
<md-input v-model="hierarchyPropertyFilterString"></md-input>
<md-input
v-model="hierarchyPropertyFilterString"
v-on:focus="updateInputMode(true)"
v-on:blur="updateInputMode(false)"
/>
</md-field>
</md-content>
<div class="tree-view-wrapper">
@@ -98,7 +102,11 @@
</md-checkbox>
<md-field md-inline class="filter">
<label>Filter...</label>
<md-input v-model="propertyFilterString"></md-input>
<md-input
v-model="propertyFilterString"
v-on:focus="updateInputMode(true)"
v-on:blur="updateInputMode(false)"
/>
</md-field>
</md-content>
<div class="properties-content">
@@ -335,6 +343,11 @@ export default {
isEntryTagMatch(entryItem) {
return this.matchItems(this.presentTags, entryItem) || this.matchItems(this.presentErrors, entryItem);
},
/** determines whether left/right arrow keys should move cursor in input field */
updateInputMode(isInputMode) {
this.store.isInputMode = isInputMode;
},
},
created() {
this.setData(this.file.data[this.file.selectedIndex ?? 0]);

View File

@@ -169,7 +169,7 @@ export default {
}
//sort transitions in ascending start position in order to handle overlap
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1: -1);
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1 : -1);
//compare each transition to the ones that came before
for (let curr=0; curr<transitions.length; curr++) {
@@ -194,7 +194,9 @@ export default {
},
errorPositions() {
if (!this.flickerMode) return [];
const errorPositions = this.errors.map(error => this.position(error.timestamp));
const errorPositions = this.errors.map(
error => ({ pos: this.position(error.timestamp), ts: error.timestamp })
);
return Object.freeze(errorPositions);
},
},
@@ -342,6 +344,16 @@ export default {
this.$store.dispatch('updateTimelineTime', timestamp);
},
/**
* Handles the error click event.
* When an error in the timeline is clicked this function will update the timeline
* to match the error timestamp.
* @param {number} errorTimestamp
*/
onErrorClick(errorTimestamp) {
this.$store.dispatch('updateTimelineTime', errorTimestamp);
},
/**
* Generate a block object that can be used by the timeline SVG to render
* a transformed block that starts at `startTs` and ends at `endTs`.
@@ -373,8 +385,11 @@ export default {
const transitionColor = transitionMap.get(transitionType).color;
var tooltip = `${transitionDesc}. Start: ${nanos_to_string(startTs)}. End: ${nanos_to_string(endTs)}.`;
if (layerId !== 0 && taskId === 0 && windowToken === "") tooltip += " SF only.";
else if ((taskId !== 0 || windowToken !== "") && layerId === 0) tooltip += " WM only.";
if (layerId !== 0 && taskId === 0 && windowToken === "") {
tooltip += " SF only.";
} else if ((taskId !== 0 || windowToken !== "") && layerId === 0) {
tooltip += " WM only.";
}
return new Transition(this.position(startTs), startTs, endTs, transitionWidth, transitionColor, overlap, tooltip);
},

View File

@@ -15,6 +15,7 @@
*/
import {DiffType} from './utils/diff.js';
import {regExpTimestampSearch} from './utils/consts';
// kind - a type used for categorization of different levels
// name - name of the node
@@ -400,5 +401,18 @@ function get_visible_chip() {
return {short: 'V', long: 'visible', class: 'default'};
}
// Returns closest timestamp in timeline based on search input*/
function getClosestTimestamp(searchInput, timeline) {
if (regExpTimestampSearch.test(searchInput)) {
var roundedTimestamp = parseInt(searchInput);
} else {
var roundedTimestamp = string_to_nanos(searchInput);
}
const closestTimestamp = timeline.reduce((prev, curr) => {
return Math.abs(curr-roundedTimestamp) < Math.abs(prev-roundedTimestamp) ? curr : prev;
});
return closestTimestamp;
}
// eslint-disable-next-line camelcase
export {transform, ObjectTransformer, nanos_to_string, string_to_nanos, get_visible_chip};
export {transform, ObjectTransformer, nanos_to_string, string_to_nanos, get_visible_chip, getClosestTimestamp};

View File

@@ -60,4 +60,7 @@ const transitionMap = new Map([
[TransitionType.APP_PAIRS_EXIT, {desc: 'Exiting app pairs mode', color: 'rgb(45, 110, 32)'}],
])
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap };
//used to split timestamp search input by unit, to convert to nanoseconds
const regExpTimestampSearch = new RegExp(/^\d+$/);
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap, regExpTimestampSearch };