[tools][system-analyzer] Improve selection support
- Double click on the current timeline selection to focus and zoom in - Make timeline-tracks focusable by setting a tabindex - Add back arrow-key navigation for the map panel (only when focused) - Prepare code for adding keyboard-based horizontal scrolling - Use --code-font CSS variable Bug: v8:10644 Change-Id: Ic473695c9fcdc795d173cd064b4660e100ae8b24 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3568475 Reviewed-by: Patrick Thier <pthier@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/main@{#79786}
This commit is contained in:
parent
91bfde4287
commit
c39e47aaa0
@ -41,6 +41,10 @@ export class V8CustomElement extends HTMLElement {
|
||||
_update() {
|
||||
throw Error('Subclass responsibility');
|
||||
}
|
||||
|
||||
get isFocused() {
|
||||
return document.activeElement === this;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileReader extends V8CustomElement {
|
||||
|
@ -1,4 +1,5 @@
|
||||
:root {
|
||||
--code-font: Consolas, Monaco, Menlo, monospace;
|
||||
--background-color: #000000;
|
||||
--surface-color-rgb: 18, 18, 18;
|
||||
--surface-color: rgb(var(--surface-color-rgb));
|
||||
@ -69,6 +70,10 @@ kbd {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
kbd, code, pre {
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
@ -245,7 +250,7 @@ button:hover,
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
.legend dt {
|
||||
font-family: monospace;
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
.legend h3 {
|
||||
margin-top: 10px;
|
||||
|
@ -176,6 +176,12 @@ found in the LICENSE file. -->
|
||||
|
||||
<h3>Keyboard Shortcuts for Navigation</h3>
|
||||
<dl>
|
||||
<dt><kbd>A</kbd></dt>
|
||||
<dd>Scroll left</dd>
|
||||
|
||||
<dt><kbd>D</kbd></dt>
|
||||
<dd>Sroll right</dd>
|
||||
|
||||
<dt><kbd>SHIFT</kbd> + <kbd>Arrow Up</kbd></dt>
|
||||
<dd>Follow Map transition forward (first child)</dd>
|
||||
|
||||
|
@ -231,10 +231,10 @@ class App {
|
||||
|
||||
handleTimeRangeSelect(e) {
|
||||
e.stopImmediatePropagation();
|
||||
this.selectTimeRange(e.start, e.end);
|
||||
this.selectTimeRange(e.start, e.end, e.focus, e.zoom);
|
||||
}
|
||||
|
||||
selectTimeRange(start, end) {
|
||||
selectTimeRange(start, end, focus = false, zoom = false) {
|
||||
this._state.selectTimeRange(start, end);
|
||||
this.showMapEntries(this._state.mapTimeline.selectionOrSelf, false);
|
||||
this.showIcEntries(this._state.icTimeline.selectionOrSelf, false);
|
||||
@ -243,7 +243,7 @@ class App {
|
||||
this.showApiEntries(this._state.apiTimeline.selectionOrSelf, false);
|
||||
this.showTickEntries(this._state.tickTimeline.selectionOrSelf, false);
|
||||
this.showTimerEntries(this._state.timerTimeline.selectionOrSelf, false);
|
||||
this._view.timelinePanel.timeSelection = {start, end};
|
||||
this._view.timelinePanel.timeSelection = {start, end, focus, zoom};
|
||||
}
|
||||
|
||||
handleFocusLogEntry(e) {
|
||||
@ -421,115 +421,53 @@ class Navigation {
|
||||
this.state = state;
|
||||
this._view = view;
|
||||
}
|
||||
|
||||
get map() {
|
||||
return this.state.map
|
||||
}
|
||||
|
||||
set map(value) {
|
||||
this.state.map = value
|
||||
}
|
||||
|
||||
get chunks() {
|
||||
return this.state.mapTimeline.chunks;
|
||||
}
|
||||
|
||||
increaseTimelineResolution() {
|
||||
this._view.timelinePanel.nofChunks *= 1.5;
|
||||
this.state.nofChunks *= 1.5;
|
||||
}
|
||||
|
||||
decreaseTimelineResolution() {
|
||||
this._view.timelinePanel.nofChunks /= 1.5;
|
||||
this.state.nofChunks /= 1.5;
|
||||
}
|
||||
selectNextEdge() {
|
||||
if (!this.map) return;
|
||||
if (this.map.children.length != 1) return;
|
||||
this.map = this.map.children[0].to;
|
||||
this._view.mapTrack.selectedEntry = this.map;
|
||||
this.updateUrl();
|
||||
this._view.mapPanel.map = this.map;
|
||||
}
|
||||
selectPrevEdge() {
|
||||
if (!this.map) return;
|
||||
if (!this.map.parent) return;
|
||||
this.map = this.map.parent;
|
||||
this._view.mapTrack.selectedEntry = this.map;
|
||||
this.updateUrl();
|
||||
this._view.mapPanel.map = this.map;
|
||||
}
|
||||
selectDefaultMap() {
|
||||
this.map = this.chunks[0].at(0);
|
||||
this._view.mapTrack.selectedEntry = this.map;
|
||||
this.updateUrl();
|
||||
this._view.mapPanel.map = this.map;
|
||||
}
|
||||
moveInChunks(next) {
|
||||
if (!this.map) return this.selectDefaultMap();
|
||||
let chunkIndex = this.map.chunkIndex(this.chunks);
|
||||
let chunk = this.chunks[chunkIndex];
|
||||
let index = chunk.indexOf(this.map);
|
||||
if (next) {
|
||||
chunk = chunk.next(this.chunks);
|
||||
} else {
|
||||
chunk = chunk.prev(this.chunks);
|
||||
}
|
||||
if (!chunk) return;
|
||||
index = Math.min(index, chunk.size() - 1);
|
||||
this.map = chunk.at(index);
|
||||
this._view.mapTrack.selectedEntry = this.map;
|
||||
this.updateUrl();
|
||||
this._view.mapPanel.map = this.map;
|
||||
}
|
||||
moveInChunk(delta) {
|
||||
if (!this.map) return this.selectDefaultMap();
|
||||
let chunkIndex = this.map.chunkIndex(this.chunks)
|
||||
let chunk = this.chunks[chunkIndex];
|
||||
let index = chunk.indexOf(this.map) + delta;
|
||||
let map;
|
||||
if (index < 0) {
|
||||
map = chunk.prev(this.chunks).last();
|
||||
} else if (index >= chunk.size()) {
|
||||
map = chunk.next(this.chunks).first()
|
||||
} else {
|
||||
map = chunk.at(index);
|
||||
}
|
||||
this.map = map;
|
||||
this._view.mapTrack.selectedEntry = this.map;
|
||||
this.updateUrl();
|
||||
this._view.mapPanel.map = this.map;
|
||||
}
|
||||
|
||||
updateUrl() {
|
||||
let entries = this.state.entries;
|
||||
let params = new URLSearchParams(entries);
|
||||
window.history.pushState(entries, '', '?' + params.toString());
|
||||
}
|
||||
|
||||
scrollLeft() {}
|
||||
|
||||
scrollRight() {}
|
||||
|
||||
handleKeyDown(event) {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
if (event.shiftKey) {
|
||||
this.selectPrevEdge();
|
||||
} else {
|
||||
this.moveInChunk(-1);
|
||||
}
|
||||
case 'd':
|
||||
this.scrollLeft();
|
||||
return false;
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
if (event.shiftKey) {
|
||||
this.selectNextEdge();
|
||||
} else {
|
||||
this.moveInChunk(1);
|
||||
}
|
||||
case 'a':
|
||||
this.scrollRight();
|
||||
return false;
|
||||
case 'ArrowLeft':
|
||||
this.moveInChunks(false);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.moveInChunks(true);
|
||||
break;
|
||||
case '+':
|
||||
this.increaseTimelineResolution();
|
||||
break;
|
||||
return false;
|
||||
case '-':
|
||||
this.decreaseTimelineResolution();
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,10 +50,12 @@ export class SelectTimeEvent extends AppEvent {
|
||||
return 'timerangeselect';
|
||||
}
|
||||
|
||||
constructor(start = 0, end = Infinity) {
|
||||
constructor(start = 0, end = Infinity, focus = false, zoom = false) {
|
||||
super(SelectTimeEvent.name);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.focus = focus;
|
||||
this.zoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ h3 {
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: monospace;
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
@ -7,7 +7,7 @@ found in the LICENSE file. -->
|
||||
</head>
|
||||
<style>
|
||||
.scriptNode {
|
||||
font-family: Consolas, monospace;
|
||||
font-family: var(--code-font);
|
||||
}
|
||||
|
||||
.scriptNode span {
|
||||
|
@ -24,10 +24,8 @@ DOM.defineCustomElement(
|
||||
}
|
||||
|
||||
set nofChunks(count) {
|
||||
const time = this.currentTime
|
||||
for (const track of this.timelineTracks) {
|
||||
track.nofChunks = count;
|
||||
track.currentTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +33,12 @@ DOM.defineCustomElement(
|
||||
return this.timelineTracks[0].nofChunks;
|
||||
}
|
||||
|
||||
set currentTime(time) {
|
||||
for (const track of this.timelineTracks) {
|
||||
track.currentTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
return this.timelineTracks[0].currentTime;
|
||||
}
|
||||
@ -54,12 +58,23 @@ DOM.defineCustomElement(
|
||||
this.timeSelection = {start: event.start, end: event.end};
|
||||
}
|
||||
|
||||
set timeSelection(timeSelection) {
|
||||
if (timeSelection.start > timeSelection.end) {
|
||||
set timeSelection(selection) {
|
||||
if (selection.start > selection.end) {
|
||||
throw new Error('Invalid time range');
|
||||
}
|
||||
const tracks = Array.from(this.timelineTracks);
|
||||
if (selection.zoom) {
|
||||
// To avoid inconsistencies copy the zoom/nofChunks from the first
|
||||
// track
|
||||
const firstTrack = tracks.pop();
|
||||
firstTrack.timeSelection = selection;
|
||||
selection.zoom = false;
|
||||
for (const track of tracks) track.timeSelection = selection;
|
||||
this.nofChunks = firstTrack.nofChunks;
|
||||
} else {
|
||||
for (const track of this.timelineTracks) {
|
||||
track.timeSelection = timeSelection;
|
||||
track.timeSelection = selection;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -37,6 +37,7 @@ export class TimelineTrackBase extends V8CustomElement {
|
||||
this.timelineMarkersNode = this.$('#timelineMarkers');
|
||||
this._scalableContentNode = this.$('#scalableContent');
|
||||
this.isLocked = false;
|
||||
this.setAttribute('tabindex', 0);
|
||||
}
|
||||
|
||||
_initEventListeners() {
|
||||
@ -46,6 +47,8 @@ export class TimelineTrackBase extends V8CustomElement {
|
||||
this.hitPanelNode.onclick = this._handleClick.bind(this);
|
||||
this.hitPanelNode.ondblclick = this._handleDoubleClick.bind(this);
|
||||
this.hitPanelNode.onmousemove = this._handleMouseMove.bind(this);
|
||||
this.$('#selectionForeground')
|
||||
.addEventListener('mousemove', this._handleMouseMove.bind(this));
|
||||
window.addEventListener('resize', () => this._resetCachedDimensions());
|
||||
}
|
||||
|
||||
@ -72,9 +75,24 @@ export class TimelineTrackBase extends V8CustomElement {
|
||||
this._updateChunks();
|
||||
}
|
||||
|
||||
set timeSelection(selection) {
|
||||
this._selectionHandler.timeSelection = selection;
|
||||
set timeSelection({start, end, focus = false, zoom = false}) {
|
||||
this._selectionHandler.timeSelection = {start, end};
|
||||
this.updateSelection();
|
||||
if (focus || zoom) {
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
||||
throw new Error('Invalid number ranges');
|
||||
}
|
||||
if (focus) {
|
||||
this.currentTime = (start + end) / 2;
|
||||
}
|
||||
if (zoom) {
|
||||
const margin = 0.2;
|
||||
const newVisibleTime = (end - start) * (1 + 2 * margin);
|
||||
const currentVisibleTime =
|
||||
this._cachedTimelineBoundingClientRect.width / this._timeToPixel;
|
||||
this.nofChunks = this.nofChunks * (currentVisibleTime / newVisibleTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSelection() {
|
||||
@ -125,8 +143,14 @@ export class TimelineTrackBase extends V8CustomElement {
|
||||
}
|
||||
|
||||
set nofChunks(count) {
|
||||
const centerTime = this.currentTime;
|
||||
const kMinNofChunks = 100;
|
||||
if (count < kMinNofChunks) count = kMinNofChunks;
|
||||
const kMaxNofChunks = 10 * 1000;
|
||||
if (count > kMaxNofChunks) count = kMaxNofChunks;
|
||||
this._nofChunks = count | 0;
|
||||
this._updateChunks();
|
||||
this.currentTime = centerTime;
|
||||
}
|
||||
|
||||
get nofChunks() {
|
||||
@ -150,18 +174,42 @@ export class TimelineTrackBase extends V8CustomElement {
|
||||
|
||||
set selectedEntry(value) {
|
||||
this._selectedEntry = value;
|
||||
this.drawAnnotations(value);
|
||||
}
|
||||
|
||||
get selectedEntry() {
|
||||
return this._selectedEntry;
|
||||
}
|
||||
|
||||
get focusedEntry() {
|
||||
return this._focusedEntry;
|
||||
}
|
||||
|
||||
set focusedEntry(entry) {
|
||||
this._focusedEntry = entry;
|
||||
if (entry) this._drawAnnotations(entry);
|
||||
}
|
||||
|
||||
set scrollLeft(offset) {
|
||||
this.timelineNode.scrollLeft = offset;
|
||||
this._cachedTimelineScrollLeft = offset;
|
||||
}
|
||||
|
||||
get scrollLeft() {
|
||||
return this._cachedTimelineScrollLeft;
|
||||
}
|
||||
|
||||
set currentTime(time) {
|
||||
const position = this.timeToPosition(time);
|
||||
const centerOffset = this._timelineBoundingClientRect.width / 2;
|
||||
this.scrollLeft = Math.max(0, position - centerOffset);
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
const centerOffset =
|
||||
this._timelineBoundingClientRect.width / 2 + this.scrollLeft;
|
||||
return this.relativePositionToTime(centerOffset);
|
||||
}
|
||||
|
||||
handleEntryTypeDoubleClick(e) {
|
||||
this.dispatchEvent(new SelectionEvent(e.target.parentNode.entries));
|
||||
}
|
||||
@ -387,12 +435,20 @@ class SelectionHandler {
|
||||
|
||||
constructor(timeline) {
|
||||
this._timeline = timeline;
|
||||
this._timelineNode = this._timeline.$('#timeline');
|
||||
this._timelineNode.addEventListener(
|
||||
'mousedown', e => this._handleTimeSelectionMouseDown(e));
|
||||
'mousedown', this._handleMouseDown.bind(this));
|
||||
this._timelineNode.addEventListener(
|
||||
'mouseup', e => this._handleTimeSelectionMouseUp(e));
|
||||
'mouseup', this._handleMouseUp.bind(this));
|
||||
this._timelineNode.addEventListener(
|
||||
'mousemove', e => this._handleTimeSelectionMouseMove(e));
|
||||
'mousemove', this._handleMouseMove.bind(this));
|
||||
this._selectionNode = this._timeline.$('#selection');
|
||||
this._selectionForegroundNode = this._timeline.$('#selectionForeground');
|
||||
this._selectionForegroundNode.addEventListener(
|
||||
'dblclick', this._handleDoubleClick.bind(this));
|
||||
this._selectionBackgroundNode = this._timeline.$('#selectionBackground');
|
||||
this._leftHandleNode = this._timeline.$('#leftHandle');
|
||||
this._rightHandleNode = this._timeline.$('#rightHandle');
|
||||
}
|
||||
|
||||
update() {
|
||||
@ -406,9 +462,10 @@ class SelectionHandler {
|
||||
this._leftHandleNode.style.left = startPosition + 'px';
|
||||
this._rightHandleNode.style.left = endPosition + 'px';
|
||||
const delta = endPosition - startPosition;
|
||||
const selectionNode = this._selectionBackgroundNode;
|
||||
selectionNode.style.left = startPosition + 'px';
|
||||
selectionNode.style.width = delta + 'px';
|
||||
this._selectionForegroundNode.style.left = startPosition + 'px';
|
||||
this._selectionForegroundNode.style.width = delta + 'px';
|
||||
this._selectionBackgroundNode.style.left = startPosition + 'px';
|
||||
this._selectionBackgroundNode.style.width = delta + 'px';
|
||||
}
|
||||
|
||||
set timeSelection(selection) {
|
||||
@ -437,26 +494,6 @@ class SelectionHandler {
|
||||
this._timeSelection.end != Infinity;
|
||||
}
|
||||
|
||||
get _timelineNode() {
|
||||
return this._timeline.$('#timeline');
|
||||
}
|
||||
|
||||
get _selectionNode() {
|
||||
return this._timeline.$('#selection');
|
||||
}
|
||||
|
||||
get _selectionBackgroundNode() {
|
||||
return this._timeline.$('#selectionBackground');
|
||||
}
|
||||
|
||||
get _leftHandleNode() {
|
||||
return this._timeline.$('#leftHandle');
|
||||
}
|
||||
|
||||
get _rightHandleNode() {
|
||||
return this._timeline.$('#rightHandle');
|
||||
}
|
||||
|
||||
get _leftHandlePosX() {
|
||||
return this._leftHandleNode.getBoundingClientRect().x;
|
||||
}
|
||||
@ -475,7 +512,7 @@ class SelectionHandler {
|
||||
SelectionHandler.SELECTION_OFFSET;
|
||||
}
|
||||
|
||||
_handleTimeSelectionMouseDown(event) {
|
||||
_handleMouseDown(event) {
|
||||
if (event.button !== 0) return;
|
||||
let xPosition = event.clientX
|
||||
// Update origin time in case we click on a handle.
|
||||
@ -488,7 +525,7 @@ class SelectionHandler {
|
||||
this._selectionOriginTime = this.positionToTime(xPosition);
|
||||
}
|
||||
|
||||
_handleTimeSelectionMouseMove(event) {
|
||||
_handleMouseMove(event) {
|
||||
if (event.button !== 0) return;
|
||||
if (!this.isSelecting) return;
|
||||
const currentTime = this.positionToTime(event.clientX);
|
||||
@ -497,7 +534,7 @@ class SelectionHandler {
|
||||
Math.max(this._selectionOriginTime, currentTime)));
|
||||
}
|
||||
|
||||
_handleTimeSelectionMouseUp(event) {
|
||||
_handleMouseUp(event) {
|
||||
if (event.button !== 0) return;
|
||||
this._selectionOriginTime = -1;
|
||||
if (this._timeSelection.start === -1) return;
|
||||
@ -506,6 +543,13 @@ class SelectionHandler {
|
||||
this._timeline.dispatchEvent(new SelectTimeEvent(
|
||||
this._timeSelection.start, this._timeSelection.end));
|
||||
}
|
||||
|
||||
_handleDoubleClick(event) {
|
||||
if (!this.hasSelection) return;
|
||||
// Focus and zoom to the current selection.
|
||||
this._timeline.dispatchEvent(new SelectTimeEvent(
|
||||
this._timeSelection.start, this._timeSelection.end, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
class Legend {
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {kChunkVisualWidth, MapLogEntry} from '../../log/map.mjs';
|
||||
import {FocusEvent} from '../events.mjs';
|
||||
import {CSSColor, DOM} from '../helper.mjs';
|
||||
|
||||
import {TimelineTrackBase} from './timeline-track-base.mjs'
|
||||
@ -12,8 +13,11 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-map',
|
||||
class TimelineTrackMap extends TimelineTrackBase {
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.navigation = new Navigation(this)
|
||||
}
|
||||
|
||||
_handleKeyDown(event) {}
|
||||
|
||||
getMapStyle(map) {
|
||||
return map.edge && map.edge.from ? CSSColor.onBackgroundColor :
|
||||
CSSColor.onPrimaryColor;
|
||||
@ -136,3 +140,116 @@ DOM.defineCustomElement('view/timeline/timeline-track', 'timeline-track-map',
|
||||
return buffer;
|
||||
}
|
||||
})
|
||||
|
||||
class Navigation {
|
||||
constructor(track) {
|
||||
this._track = track;
|
||||
this._track.addEventListener('keydown', this._handleKeyDown.bind(this));
|
||||
this._map = undefined;
|
||||
}
|
||||
|
||||
_handleKeyDown(event) {
|
||||
if (!this._track.isFocused) return;
|
||||
let handled = false;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
handled = true;
|
||||
if (event.shiftKey) {
|
||||
this.selectPrevEdge();
|
||||
} else {
|
||||
this.moveInChunk(-1);
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
handled = true;
|
||||
if (event.shiftKey) {
|
||||
this.selectNextEdge();
|
||||
} else {
|
||||
this.moveInChunk(1);
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
handled = true;
|
||||
this.moveInChunks(false);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
handled = true;
|
||||
this.moveInChunks(true);
|
||||
break;
|
||||
case 'Enter':
|
||||
handled = true;
|
||||
this.selectMap();
|
||||
break
|
||||
}
|
||||
if (handled) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
get map() {
|
||||
return this._track.focusedEntry;
|
||||
}
|
||||
|
||||
set map(map) {
|
||||
this._track.focusedEntry = map;
|
||||
}
|
||||
|
||||
get chunks() {
|
||||
return this._track.chunks;
|
||||
}
|
||||
|
||||
selectMap() {
|
||||
if (!this.map) return;
|
||||
this._track.dispatchEvent(new FocusEvent(this.map))
|
||||
}
|
||||
|
||||
selectNextEdge() {
|
||||
if (!this.map) return;
|
||||
if (this.map.children.length != 1) return;
|
||||
this.show(this.map.children[0].to);
|
||||
}
|
||||
|
||||
selectPrevEdge() {
|
||||
if (!this.map) return;
|
||||
if (!this.map.parent) return;
|
||||
this.map = this.map.parent;
|
||||
}
|
||||
|
||||
selectDefaultMap() {
|
||||
this.map = this.chunks[0].at(0);
|
||||
}
|
||||
|
||||
moveInChunks(next) {
|
||||
if (!this.map) return this.selectDefaultMap();
|
||||
let chunkIndex = this.map.chunkIndex(this.chunks);
|
||||
let currentChunk = this.chunks[chunkIndex];
|
||||
let currentIndex = currentChunk.indexOf(this.map);
|
||||
let newChunk;
|
||||
if (next) {
|
||||
newChunk = chunk.next(this.chunks);
|
||||
} else {
|
||||
newChunk = chunk.prev(this.chunks);
|
||||
}
|
||||
if (!newChunk) return;
|
||||
let newIndex = Math.min(currentIndex, newChunk.size() - 1);
|
||||
this.map = newChunk.at(newIndex);
|
||||
}
|
||||
|
||||
moveInChunk(delta) {
|
||||
if (!this.map) return this.selectDefaultMap();
|
||||
let chunkIndex = this.map.chunkIndex(this.chunks)
|
||||
let chunk = this.chunks[chunkIndex];
|
||||
let index = chunk.indexOf(this.map) + delta;
|
||||
let map;
|
||||
if (index < 0) {
|
||||
map = chunk.prev(this.chunks).last();
|
||||
} else if (index >= chunk.size()) {
|
||||
map = chunk.next(this.chunks).first()
|
||||
} else {
|
||||
map = chunk.at(index);
|
||||
}
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,15 @@ found in the LICENSE file. -->
|
||||
border-right: 1px solid var(--on-surface-color);
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
#selectionForeground{
|
||||
z-index: 2;
|
||||
cursor: grab;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
#selectionForeground:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
#selectionBackground {
|
||||
background-color: rgba(133, 68, 163, 0.5);
|
||||
height: 100%;
|
||||
@ -159,7 +167,7 @@ found in the LICENSE file. -->
|
||||
<style>
|
||||
/* SVG styles */
|
||||
.txt {
|
||||
font: 8px monospace;
|
||||
font: 8px var(--code-font);
|
||||
transform: var(--txt-scale);
|
||||
}
|
||||
.annotationLabel {
|
||||
@ -208,6 +216,7 @@ found in the LICENSE file. -->
|
||||
<div id="timeline">
|
||||
<div id="selection" class="dataSized">
|
||||
<div id="leftHandle"></div>
|
||||
<div id="selectionForeground"></div>
|
||||
<div id="selectionBackground"></div>
|
||||
<div id="rightHandle"></div>
|
||||
</div>
|
||||
|
@ -37,7 +37,7 @@ found in the LICENSE file. -->
|
||||
}
|
||||
|
||||
.textContent {
|
||||
font-family: monospace;
|
||||
font-family: var(--code-font);
|
||||
white-space: pre;
|
||||
overflow-wrap: anywhere;
|
||||
overflow-x: hidden;
|
||||
|
Loading…
Reference in New Issue
Block a user