Commit 71e03311 authored by Zeynep Cankara's avatar Zeynep Cankara Committed by Commit Bot

[tools][system-analyzer] Convert App to MVC Pattern

This CL aims to clean the code in App Class to
handle State, View according to the Model-View-Controller
design pattern.

Bug: v8:10644, v8:10735

Link: https://docs.google.com/presentation/d/1ssCIWKS5TIp_PHZRUx2BfElEz6JFrYzz_Ce1h1g8ZBg/edit?usp=sharing

Change-Id: Ie36d437df0df574f505a4396b26526a82215f237
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2324247
Commit-Queue: Zeynep Cankara <zcankara@google.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69218}
parent 6fff5758
......@@ -2,179 +2,67 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { V8Map } from './map-processor.mjs';
class State {
#mapPanel;
#timelinePanel;
#mapTrack;
#icTrack;
#timeSelection = {start: 0, end: Infinity};
#map;
#ic;
#navigation;
constructor(mapPanel, timelinePanel, mapTrack, icTrack) {
this.#mapPanel = mapPanel;
this.#timelinePanel = timelinePanel;
this.#mapTrack = mapTrack;
this.#icTrack = icTrack;
this.#navigation = new Navigation(this);
this.#timelinePanel.addEventListener(
'mapchange', e => this.handleMapChange(e));
this.#timelinePanel.addEventListener(
'showmaps', e => this.handleShowMaps(e));
this.#mapPanel.addEventListener(
'mapchange', e => this.handleMapChange(e));
this.#mapPanel.addEventListener(
'selectmapdblclick', e => this.handleDblClickSelectMap(e));
this.#mapPanel.addEventListener(
'sourcepositionsclick', e => this.handleClickSourcePositions(e));
}
get chunks(){
//TODO(zcankara) for timeline dependency
return this.#mapTrack.chunks;
#nofChunks;
#chunks;
#icTimeline;
#mapTimeline;
get mapTimeline(){
return this.#mapTimeline;
}
handleMapChange(e){
if (!(e.detail instanceof V8Map)) return;
this.map = e.detail;
set mapTimeline(value){
this.#mapTimeline = value;
}
handleStateMapChange(e){
this.map = e.detail;
set icTimeline(value){
this.#icTimeline = value;
}
handleShowMaps(e){
if (!(e.detail instanceof V8Map)) return;
this.#mapPanel.mapEntries = e.detail.filter();
get icTimeline(){
return this.#icTimeline;
}
set transitions(value) {
this.#mapPanel.transitions = value;
set chunks(value){
//TODO(zcankara) split up between maps and ics, and every timeline track
this.#chunks = value;
}
set mapTimeline(value) {
this.#mapPanel.timeline = value;
get chunks(){
//TODO(zcankara) split up between maps and ics, and every timeline track
return this.#chunks;
}
get nofChunks() {
return this.timelinePanel.nofChunks;
return this.#nofChunks;
}
set nofChunks(count) {
this.timelinePanel.nofChunks = count;
}
get mapPanel() {
return this.#mapPanel;
this.#nofChunks = count;
}
get timelinePanel() {
return this.#timelinePanel;
}
get navigation() {
return this.#navigation
}
get map() {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
return this.#map;
}
set map(value) {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
if(!value) return;
this.#map = value;
this.#mapTrack.selectedEntry = value;
this.#navigation.updateUrl();
this.mapPanel.map = value;
}
get ic() {
//TODO(zcankara) rename selectedICEvents, array of selected events
return this.#ic;
}
set ic(value) {
//TODO(zcankara) rename selectedIcEvents, array of selected events
if(!value) return;
this.#ic = value;
}
get timeSelection() {
return this.#timeSelection;
}
get entries() {
if (!this.map) return {};
return {
map: this.map.id, time: this.map.time
}
}
handleClickSourcePositions(e){
//TODO(zcankara) Handle source position
console.log("source position map detail: ", e.detail);
}
handleDblClickSelectMap(e){
//TODO(zcankara) Handle double clicked map
console.log("double clicked map: ", e.detail);
}
}
class Navigation {
constructor(state) {
this.state = state;
}
get map() {
return this.state.map
}
set map(value) {
this.state.map = value
}
get chunks() {
return this.state.chunks
}
increaseTimelineResolution() {
this.state.nofChunks *= 1.5;
}
decreaseTimelineResolution() {
this.state.nofChunks /= 1.5;
}
selectNextEdge() {
if (!this.map) return;
if (this.map.children.length != 1) return;
this.map = 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 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);
}
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;
}
updateUrl() {
let entries = this.state.entries;
let params = new URLSearchParams(entries);
window.history.pushState(entries, '', '?' + params.toString());
}
}
export { State };
......@@ -109,7 +109,7 @@ globalThis.app = new App("#log-file-reader", "#map-panel", "#timeline-panel",
<timeline-track id="map-track"></timeline-track>
<timeline-track id="ic-track"></timeline-track>
</timeline-panel>
<map-panel id="map-panel" onclick="app.handleMapAddressSearch(event)" onchange="app.handleShowMaps(event)"></map-panel>
<map-panel id="map-panel"></map-panel>
<ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel>
</div>
</div>
......
......@@ -4,45 +4,53 @@
import CustomIcProcessor from "./ic-processor.mjs";
import {State} from './app-model.mjs';
import {MapProcessor} from './map-processor.mjs';
import {MapProcessor, V8Map} from './map-processor.mjs';
import {Chunk} from './timeline.mjs';
import {$} from './helper.mjs';
import './ic-panel.mjs';
import './timeline-panel.mjs';
import './map-panel.mjs';
import './log-file-reader.mjs';
class App {
#timeSelection = {start: 0, end: Infinity};
#mapPanel;
#timelinePanel;
#icPanel;
#mapTrack;
#icTrack;
#logFileReader;
#state
#view;
constructor(fileReaderId, mapPanelId, timelinePanelId,
icPanelId, mapTrackId, icTrackId) {
this.#logFileReader = this.$(fileReaderId);
this.#mapPanel = this.$(mapPanelId);
this.#timelinePanel = this.$(timelinePanelId);
this.#icPanel = this.$(icPanelId);
this.#mapTrack = this.$(mapTrackId);
this.#icTrack = this.$(icTrackId);
this.#logFileReader.addEventListener('fileuploadstart',
this.#view = {
logFileReader: $(fileReaderId),
icPanel: $(icPanelId),
mapPanel: $(mapPanelId),
timelinePanel: $(timelinePanelId),
mapTrack: $(mapTrackId),
icTrack: $(icTrackId),
}
this.#state = new State();
this.#view.logFileReader.addEventListener('fileuploadstart',
e => this.handleFileUpload(e));
this.#logFileReader.addEventListener('fileuploadend',
this.#view.logFileReader.addEventListener('fileuploadend',
e => this.handleDataUpload(e));
document.addEventListener('keydown', e => this.handleKeyDown(e));
this.#icPanel.addEventListener(
this.toggleSwitch = $('.theme-switch input[type="checkbox"]');
this.toggleSwitch.addEventListener('change', e => this.switchTheme(e));
this.#view.timelinePanel.addEventListener(
'mapchange', e => this.handleMapChange(e));
this.#view.timelinePanel.addEventListener(
'showmaps', e => this.handleShowMaps(e));
this.#view.mapPanel.addEventListener(
'mapchange', e => this.handleMapChange(e));
this.#view.mapPanel.addEventListener(
'selectmapdblclick', e => this.handleDblClickSelectMap(e));
this.#view.mapPanel.addEventListener(
'sourcepositionsclick', e => this.handleClickSourcePositions(e));
this.#view.icPanel.addEventListener(
'ictimefilter', e => this.handleICTimeFilter(e));
this.#icPanel.addEventListener(
this.#view.icPanel.addEventListener(
'mapclick', e => this.handleMapClick(e));
this.#icPanel.addEventListener(
this.#view.mapPanel.addEventListener(
'click', e => this.handleMapAddressSearch(e));
this.#view.mapPanel.addEventListener(
'change', e => this.handleShowMapsChange(e));
this.#view.icPanel.addEventListener(
'filepositionclick', e => this.handleFilePositionClick(e));
this.toggleSwitch = this.$('.theme-switch input[type="checkbox"]');
this.toggleSwitch.addEventListener('change', e => this.switchTheme(e));
}
handleFileUpload(e){
this.$('#container').style.display = 'none';
}
handleMapClick(e) {
//TODO(zcankara) Direct the event based on the key and value
......@@ -52,50 +60,52 @@ class App {
//TODO(zcankara) Direct the event based on the key and value
console.log("filePosition: ", e.detail.key);
}
handleClickSourcePositions(e){
//TODO(zcankara) Handle source position
console.log("source position map detail: ", e.detail);
}
handleDblClickSelectMap(e){
//TODO(zcankara) Handle double clicked map
console.log("double clicked map: ", e.detail);
}
handleMapChange(e){
if (!(e.detail instanceof V8Map)){
console.error("selected entry not a V8Map instance");
return;
}
this.#state.map = e.detail;
this.#view.mapTrack.selectedEntry = e.detail;
this.#view.mapPanel.map = e.detail;
}
handleShowMaps(e){
if (!(e.detail instanceof Chunk)){
console.error("Chunk not selected");
return;
}
this.#view.mapPanel.mapEntries = e.detail.filter();
}
handleICTimeFilter(event) {
this.#timeSelection.start = event.detail.startTime;
this.#timeSelection.end = event.detail.endTime;
this.#icTrack.data.selectTimeRange(this.#timeSelection.start,
this.#timeSelection.end);
this.#icPanel.filteredEntries = this.#icTrack.data.selection;
this.#state.timeSelection.start = event.detail.startTime;
this.#state.timeSelection.end = event.detail.endTime;
this.#view.icTrack.data.selectTimeRange(this.#state.timeSelection.start,
this.#state.timeSelection.end);
this.#view.icPanel.filteredEntries = this.#view.icTrack.data.selection;
}
$(id) { return document.querySelector(id); }
handleKeyDown(event) {
let nav = document.state.navigation;
switch(event.key) {
case "ArrowUp":
event.preventDefault();
if (event.shiftKey) {
nav.selectPrevEdge();
} else {
nav.moveInChunk(-1);
}
return false;
case "ArrowDown":
event.preventDefault();
if (event.shiftKey) {
nav.selectNextEdge();
} else {
nav.moveInChunk(1);
}
return false;
case "ArrowLeft":
nav.moveInChunks(false);
break;
case "ArrowRight":
nav.moveInChunks(true);
break;
case "+":
nav.increaseTimelineResolution();
break;
case "-":
nav.decreaseTimelineResolution();
break;
handleMapAddressSearch(e) {
//TODO(zcankara) Combine with handleMapChange into selectMap event
//TODO(zcankara) Possibility of select no map
if(!e.detail.map) return;
this.#state.map = e.detail.map;
this.#view.mapTrack.selectedEntry = e.detail.map;
this.#view.mapPanel.map = e.detail.map;
}
handleShowMapsChange(e) {
//TODO(zcankara) Map entries repeats "map". Probabluy not needed
this.#view.mapPanel.mapEntries = e.detail;
}
handleFileUpload(e){
//TODO(zcankara) Set a state on the document.body. Exe: .loading, .loaded
$('#container').style.display = 'none';
}
// Map event log processing
......@@ -111,28 +121,30 @@ class App {
let icProcessor = new CustomIcProcessor();
//TODO(zcankara) Assign timeline directly to the ic panel
//TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline
this.#icTrack.data = icProcessor.processString(fileData.chunk);
this.#icPanel.filteredEntries = this.#icTrack.data.all;
this.#icPanel.count.innerHTML = this.#icTrack.data.all.length;
//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;
}
reader.readAsText(fileData.file);
this.#icPanel.initGroupKeySelect();
this.#view.icPanel.initGroupKeySelect();
}
// call when a new file uploaded
handleDataUpload(e) {
if(!e.detail) return;
this.$('#container').style.display = 'block';
$('#container').style.display = 'block';
// instantiate the app logic
let fileData = e.detail;
document.state = new State(this.#mapPanel, this.#timelinePanel,
this.#mapTrack, this.#icTrack);
try {
const timeline = this.handleLoadTextMapProcessor(fileData.chunk);
// Transitions must be set before timeline for stats panel.
document.state.transitions= timeline.transitions;
this.#mapTrack.data = timeline;
document.state.mapTimeline = timeline;
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;
} catch (error) {
console.log(error);
}
......@@ -140,21 +152,6 @@ class App {
this.fileLoaded = true;
}
handleMapAddressSearch(e) {
if(!e.detail.isValidMap) return;
document.state.map = e.detail.map;
}
handleShowMaps(e) {
document.state.mapPanel.mapEntries = e.detail;
}
handleSelectIc(e){
if(!e.detail) return;
//TODO(zcankara) Send filtered entries to State
console.log("filtered IC entried: ", e.detail)
}
switchTheme(event) {
if(this.fileLoaded) return;
document.documentElement.dataset.theme =
......
......@@ -9,7 +9,7 @@ found in the LICENSE file. -->
color: black;
}
</style>
<stats-panel id="stats-panel" onchange="app.handleShowMaps(event)"></stats-panel>
<stats-panel id="stats-panel"></stats-panel>
<div class="panel">
<h2>Map Panel</h2>
<map-transitions id="map-transitions"></map-transitions>
......
......@@ -26,22 +26,20 @@ defineCustomElement('timeline-panel', (templateText) =>
}
get timelineCanvas() {
//TODO Don't access the timeline canvas from outside timeline track
if(!this.timelineTracks || !this.timelineTracks.length) return;
return this.timelineTracks[0].timelineCanvas;
}
get timeline() {
//TODO(zcankara) Don't access the timeline from outside timeline track
if(!this.timelineTracks || !this.timelineTracks.length) return;
return this.timelineTracks[0].timeline;
}
set nofChunks(count){
for (const track of this.timelineTracks) {
track.nofChunks = count;
}
}
get nofChunks(){
return this.timelineTracks[0].nofChunks;
}
get timelineTracks(){
return this.$("slot").assignedNodes().filter(
track => track.nodeType === Node.ELEMENT_NODE);
......
......@@ -235,4 +235,4 @@ class Chunk {
}
export {Timeline};
export {Timeline, Chunk};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment