2020-07-14 15:03:14 +00:00
|
|
|
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
import CustomIcProcessor from "./ic-processor.mjs";
|
2020-08-12 15:10:23 +00:00
|
|
|
import { Entry } from "./ic-processor.mjs";
|
|
|
|
import { State } from "./app-model.mjs";
|
|
|
|
import { MapProcessor, V8Map } from "./map-processor.mjs";
|
2020-08-13 09:14:29 +00:00
|
|
|
import { SelectTimeEvent } from "./events.mjs";
|
2020-08-12 15:10:23 +00:00
|
|
|
import { $ } from "./helper.mjs";
|
|
|
|
import "./ic-panel.mjs";
|
|
|
|
import "./timeline-panel.mjs";
|
|
|
|
import "./map-panel.mjs";
|
|
|
|
import "./log-file-reader.mjs";
|
2020-07-16 10:02:52 +00:00
|
|
|
class App {
|
2020-08-12 15:10:23 +00:00
|
|
|
#state;
|
2020-08-04 08:51:59 +00:00
|
|
|
#view;
|
2020-08-13 07:35:23 +00:00
|
|
|
#navigation;
|
|
|
|
constructor(fileReaderId, mapPanelId, timelinePanelId,
|
|
|
|
icPanelId, mapTrackId, icTrackId) {
|
2020-08-04 08:51:59 +00:00
|
|
|
this.#view = {
|
|
|
|
logFileReader: $(fileReaderId),
|
|
|
|
icPanel: $(icPanelId),
|
|
|
|
mapPanel: $(mapPanelId),
|
|
|
|
timelinePanel: $(timelinePanelId),
|
|
|
|
mapTrack: $(mapTrackId),
|
|
|
|
icTrack: $(icTrackId),
|
2020-08-12 15:10:23 +00:00
|
|
|
};
|
2020-08-04 08:51:59 +00:00
|
|
|
this.#state = new State();
|
2020-08-13 07:35:23 +00:00
|
|
|
this.#navigation = new Navigation(this.#state, this.#view);
|
|
|
|
document.addEventListener('keydown',
|
|
|
|
e => this.#navigation.handleKeyDown(e));
|
2020-08-06 14:41:38 +00:00
|
|
|
this.toggleSwitch = $('.theme-switch input[type="checkbox"]');
|
2020-08-12 15:10:23 +00:00
|
|
|
this.toggleSwitch.addEventListener("change", (e) => this.switchTheme(e));
|
|
|
|
this.#view.logFileReader.addEventListener("fileuploadstart", (e) =>
|
|
|
|
this.handleFileUpload(e)
|
|
|
|
);
|
|
|
|
this.#view.logFileReader.addEventListener("fileuploadend", (e) =>
|
|
|
|
this.handleDataUpload(e)
|
|
|
|
);
|
2020-08-06 14:41:38 +00:00
|
|
|
Object.entries(this.#view).forEach(([_, value]) => {
|
2020-08-13 09:14:29 +00:00
|
|
|
value.addEventListener('showentries',
|
|
|
|
e => this.handleShowEntries(e));
|
|
|
|
value.addEventListener('showentrydetail',
|
|
|
|
e => this.handleShowEntryDetail(e));
|
|
|
|
value.addEventListener(SelectTimeEvent.name,
|
|
|
|
e => this.handleTimeRangeSelect(e));
|
2020-08-06 14:41:38 +00:00
|
|
|
});
|
2020-07-23 08:41:38 +00:00
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
handleShowEntries(e) {
|
|
|
|
if (e.entries[0] instanceof V8Map) {
|
2020-08-13 09:14:29 +00:00
|
|
|
this.showMapEntries(e.entries);
|
2020-08-06 14:41:38 +00:00
|
|
|
}
|
2020-07-22 14:41:42 +00:00
|
|
|
}
|
2020-08-13 09:14:29 +00:00
|
|
|
handleTimeRangeSelect(e) {
|
|
|
|
this.selectTimeRange(e.start, e.end);
|
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
handleShowEntryDetail(e) {
|
|
|
|
if (e.entry instanceof V8Map) {
|
2020-08-06 14:41:38 +00:00
|
|
|
this.selectMapLogEvent(e.entry);
|
2020-08-12 15:10:23 +00:00
|
|
|
} else if (e.entry instanceof Entry) {
|
2020-08-06 14:41:38 +00:00
|
|
|
this.selectICLogEvent(e.entry);
|
2020-08-13 09:14:29 +00:00
|
|
|
} else if (typeof e.entry === 'string') {
|
2020-08-06 14:41:38 +00:00
|
|
|
this.selectSourcePositionEvent(e.entry);
|
2020-08-12 15:10:23 +00:00
|
|
|
} else {
|
2020-08-06 14:41:38 +00:00
|
|
|
console.log("undefined");
|
|
|
|
}
|
2020-07-22 14:41:42 +00:00
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
handleClickSourcePositions(e) {
|
2020-08-04 08:51:59 +00:00
|
|
|
//TODO(zcankara) Handle source position
|
2020-08-06 14:41:38 +00:00
|
|
|
console.log("Entry containing source position: ", e.entries);
|
2020-07-22 14:41:42 +00:00
|
|
|
}
|
2020-08-13 09:14:29 +00:00
|
|
|
selectTimeRange(start, end) {
|
|
|
|
this.#state.timeSelection.start = start;
|
|
|
|
this.#state.timeSelection.end = end;
|
|
|
|
this.#state.icTimeline.selectTimeRange(start, end);
|
|
|
|
this.#state.mapTimeline.selectTimeRange(start, end);
|
|
|
|
this.#view.mapPanel.selectedMapLogEvents = this.#state.mapTimeline.selection;
|
|
|
|
this.#view.icPanel.filteredEntries = this.#state.icTimeline.selection;
|
|
|
|
}
|
|
|
|
showMapEntries(entries) {
|
|
|
|
this.#state.selectedMapLogEvents = entries;
|
|
|
|
this.#view.mapPanel.selectedMapLogEvents = this.#state.selectedMapLogEvents;
|
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
selectMapLogEvent(entry) {
|
2020-08-06 14:41:38 +00:00
|
|
|
this.#state.map = entry;
|
|
|
|
this.#view.mapTrack.selectedEntry = entry;
|
|
|
|
this.#view.mapPanel.map = entry;
|
2020-08-04 08:51:59 +00:00
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
selectICLogEvent(entry) {
|
2020-08-06 14:41:38 +00:00
|
|
|
console.log("IC Entry selection");
|
2020-08-04 08:51:59 +00:00
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
selectSourcePositionEvent(sourcePositions) {
|
2020-08-06 14:41:38 +00:00
|
|
|
console.log("source positions: ", sourcePositions);
|
2020-08-04 08:51:59 +00:00
|
|
|
}
|
2020-08-12 15:10:23 +00:00
|
|
|
handleFileUpload(e) {
|
|
|
|
$("#container").className = "initial";
|
2020-07-16 10:02:52 +00:00
|
|
|
}
|
|
|
|
// Map event log processing
|
|
|
|
handleLoadTextMapProcessor(text) {
|
2020-07-14 15:03:14 +00:00
|
|
|
let mapProcessor = new MapProcessor();
|
|
|
|
return mapProcessor.processString(text);
|
|
|
|
}
|
2020-07-16 10:02:52 +00:00
|
|
|
// IC event file reading and log processing
|
|
|
|
loadICLogFile(fileData) {
|
|
|
|
let reader = new FileReader();
|
|
|
|
reader.onload = (evt) => {
|
|
|
|
let icProcessor = new CustomIcProcessor();
|
2020-07-24 13:51:36 +00:00
|
|
|
//TODO(zcankara) Assign timeline directly to the ic panel
|
2020-07-28 12:46:10 +00:00
|
|
|
//TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline
|
2020-08-04 08:51:59 +00:00
|
|
|
//TODO(zcankara) Set the data of the State object first
|
|
|
|
this.#state.icTimeline = icProcessor.processString(fileData.chunk);
|
|
|
|
this.#view.icTrack.data = this.#state.icTimeline;
|
|
|
|
this.#view.icPanel.filteredEntries = this.#view.icTrack.data.all;
|
|
|
|
this.#view.icPanel.count.innerHTML = this.#view.icTrack.data.all.length;
|
2020-08-12 15:10:23 +00:00
|
|
|
};
|
2020-07-16 10:02:52 +00:00
|
|
|
reader.readAsText(fileData.file);
|
2020-08-04 08:51:59 +00:00
|
|
|
this.#view.icPanel.initGroupKeySelect();
|
2020-07-16 10:02:52 +00:00
|
|
|
}
|
2020-07-14 15:03:14 +00:00
|
|
|
|
2020-07-16 10:02:52 +00:00
|
|
|
// call when a new file uploaded
|
|
|
|
handleDataUpload(e) {
|
2020-08-12 15:10:23 +00:00
|
|
|
if (!e.detail) return;
|
|
|
|
$("#container").className = "loaded";
|
2020-07-16 10:02:52 +00:00
|
|
|
// instantiate the app logic
|
|
|
|
let fileData = e.detail;
|
2020-07-16 19:38:43 +00:00
|
|
|
try {
|
2020-07-24 13:51:36 +00:00
|
|
|
const timeline = this.handleLoadTextMapProcessor(fileData.chunk);
|
|
|
|
// Transitions must be set before timeline for stats panel.
|
2020-08-04 08:51:59 +00:00
|
|
|
this.#state.mapTimeline = timeline;
|
|
|
|
this.#view.mapPanel.transitions = this.#state.mapTimeline.transitions;
|
|
|
|
this.#view.mapTrack.data = this.#state.mapTimeline;
|
|
|
|
this.#state.chunks = this.#view.mapTrack.chunks;
|
|
|
|
this.#view.mapPanel.timeline = this.#state.mapTimeline;
|
2020-07-16 19:38:43 +00:00
|
|
|
} catch (error) {
|
|
|
|
console.log(error);
|
|
|
|
}
|
2020-07-16 10:02:52 +00:00
|
|
|
this.loadICLogFile(fileData);
|
2020-07-28 09:02:26 +00:00
|
|
|
this.fileLoaded = true;
|
2020-07-16 10:02:52 +00:00
|
|
|
}
|
2020-07-14 15:03:14 +00:00
|
|
|
|
2020-08-12 15:10:23 +00:00
|
|
|
refreshTimelineTrackView() {
|
2020-08-05 04:13:01 +00:00
|
|
|
this.#view.mapTrack.data = this.#state.mapTimeline;
|
|
|
|
this.#view.icTrack.data = this.#state.icTimeline;
|
|
|
|
}
|
|
|
|
|
2020-07-28 09:02:26 +00:00
|
|
|
switchTheme(event) {
|
2020-08-12 15:10:23 +00:00
|
|
|
document.documentElement.dataset.theme = event.target.checked
|
|
|
|
? "light"
|
|
|
|
: "dark";
|
|
|
|
if (this.fileLoaded) {
|
2020-08-05 04:13:01 +00:00
|
|
|
this.refreshTimelineTrackView();
|
|
|
|
}
|
2020-07-28 09:02:26 +00:00
|
|
|
}
|
2020-07-15 07:28:00 +00:00
|
|
|
}
|
|
|
|
|
2020-08-13 07:35:23 +00:00
|
|
|
class Navigation {
|
|
|
|
#view;
|
|
|
|
constructor(state, view) {
|
|
|
|
this.state = state;
|
|
|
|
this.#view = view;
|
|
|
|
}
|
|
|
|
get map() {
|
|
|
|
return this.state.map
|
|
|
|
}
|
|
|
|
set map(value) {
|
|
|
|
this.state.map = value
|
|
|
|
}
|
|
|
|
get chunks() {
|
|
|
|
return this.state.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());
|
|
|
|
}
|
|
|
|
handleKeyDown(event) {
|
|
|
|
switch (event.key) {
|
|
|
|
case "ArrowUp":
|
|
|
|
event.preventDefault();
|
|
|
|
if (event.shiftKey) {
|
|
|
|
this.selectPrevEdge();
|
|
|
|
} else {
|
|
|
|
this.moveInChunk(-1);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case "ArrowDown":
|
|
|
|
event.preventDefault();
|
|
|
|
if (event.shiftKey) {
|
|
|
|
this.selectNextEdge();
|
|
|
|
} else {
|
|
|
|
this.moveInChunk(1);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
case "ArrowLeft":
|
|
|
|
this.moveInChunks(false);
|
|
|
|
break;
|
|
|
|
case "ArrowRight":
|
|
|
|
this.moveInChunks(true);
|
|
|
|
break;
|
|
|
|
case "+":
|
|
|
|
this.increaseTimelineResolution();
|
|
|
|
break;
|
|
|
|
case "-":
|
|
|
|
this.decreaseTimelineResolution();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 15:10:23 +00:00
|
|
|
export { App };
|