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 @@ ...@@ -2,179 +2,67 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import { V8Map } from './map-processor.mjs';
class State { class State {
#mapPanel; #timeSelection = {start: 0, end: Infinity};
#timelinePanel;
#mapTrack;
#icTrack;
#map; #map;
#ic; #ic;
#navigation; #nofChunks;
constructor(mapPanel, timelinePanel, mapTrack, icTrack) { #chunks;
this.#mapPanel = mapPanel; #icTimeline;
this.#timelinePanel = timelinePanel; #mapTimeline;
this.#mapTrack = mapTrack; get mapTimeline(){
this.#icTrack = icTrack; return this.#mapTimeline;
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;
} }
handleMapChange(e){ set mapTimeline(value){
if (!(e.detail instanceof V8Map)) return; this.#mapTimeline = value;
this.map = e.detail;
} }
handleStateMapChange(e){ set icTimeline(value){
this.map = e.detail; this.#icTimeline = value;
} }
handleShowMaps(e){ get icTimeline(){
if (!(e.detail instanceof V8Map)) return; return this.#icTimeline;
this.#mapPanel.mapEntries = e.detail.filter();
} }
set transitions(value) { set chunks(value){
this.#mapPanel.transitions = value; //TODO(zcankara) split up between maps and ics, and every timeline track
this.#chunks = value;
} }
set mapTimeline(value) { get chunks(){
this.#mapPanel.timeline = value; //TODO(zcankara) split up between maps and ics, and every timeline track
return this.#chunks;
} }
get nofChunks() { get nofChunks() {
return this.timelinePanel.nofChunks; return this.#nofChunks;
} }
set nofChunks(count) { set nofChunks(count) {
this.timelinePanel.nofChunks = count; this.#nofChunks = count;
}
get mapPanel() {
return this.#mapPanel;
} }
get timelinePanel() {
return this.#timelinePanel;
}
get navigation() {
return this.#navigation
}
get map() { get map() {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
return this.#map; return this.#map;
} }
set map(value) { set map(value) {
//TODO(zcankara) rename as selectedMapEvents, array of selected events
if(!value) return; if(!value) return;
this.#map = value; this.#map = value;
this.#mapTrack.selectedEntry = value;
this.#navigation.updateUrl();
this.mapPanel.map = value;
} }
get ic() { get ic() {
//TODO(zcankara) rename selectedICEvents, array of selected events
return this.#ic; return this.#ic;
} }
set ic(value) { set ic(value) {
//TODO(zcankara) rename selectedIcEvents, array of selected events
if(!value) return; if(!value) return;
this.#ic = value; this.#ic = value;
} }
get timeSelection() {
return this.#timeSelection;
}
get entries() { get entries() {
if (!this.map) return {}; if (!this.map) return {};
return { return {
map: this.map.id, time: this.map.time 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 }; export { State };
...@@ -109,7 +109,7 @@ globalThis.app = new App("#log-file-reader", "#map-panel", "#timeline-panel", ...@@ -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="map-track"></timeline-track>
<timeline-track id="ic-track"></timeline-track> <timeline-track id="ic-track"></timeline-track>
</timeline-panel> </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> <ic-panel id="ic-panel" onchange="app.handleSelectIc(event)"></ic-panel>
</div> </div>
</div> </div>
......
...@@ -4,98 +4,108 @@ ...@@ -4,98 +4,108 @@
import CustomIcProcessor from "./ic-processor.mjs"; import CustomIcProcessor from "./ic-processor.mjs";
import {State} from './app-model.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 './ic-panel.mjs';
import './timeline-panel.mjs'; import './timeline-panel.mjs';
import './map-panel.mjs'; import './map-panel.mjs';
import './log-file-reader.mjs'; import './log-file-reader.mjs';
class App { class App {
#timeSelection = {start: 0, end: Infinity}; #state
#mapPanel; #view;
#timelinePanel;
#icPanel;
#mapTrack;
#icTrack;
#logFileReader;
constructor(fileReaderId, mapPanelId, timelinePanelId, constructor(fileReaderId, mapPanelId, timelinePanelId,
icPanelId, mapTrackId, icTrackId) { icPanelId, mapTrackId, icTrackId) {
this.#logFileReader = this.$(fileReaderId); this.#view = {
this.#mapPanel = this.$(mapPanelId); logFileReader: $(fileReaderId),
this.#timelinePanel = this.$(timelinePanelId); icPanel: $(icPanelId),
this.#icPanel = this.$(icPanelId); mapPanel: $(mapPanelId),
this.#mapTrack = this.$(mapTrackId); timelinePanel: $(timelinePanelId),
this.#icTrack = this.$(icTrackId); mapTrack: $(mapTrackId),
icTrack: $(icTrackId),
this.#logFileReader.addEventListener('fileuploadstart', }
this.#state = new State();
this.#view.logFileReader.addEventListener('fileuploadstart',
e => this.handleFileUpload(e)); e => this.handleFileUpload(e));
this.#logFileReader.addEventListener('fileuploadend', this.#view.logFileReader.addEventListener('fileuploadend',
e => this.handleDataUpload(e)); e => this.handleDataUpload(e));
document.addEventListener('keydown', e => this.handleKeyDown(e)); this.toggleSwitch = $('.theme-switch input[type="checkbox"]');
this.#icPanel.addEventListener( 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)); 'ictimefilter', e => this.handleICTimeFilter(e));
this.#icPanel.addEventListener( this.#view.icPanel.addEventListener(
'mapclick', e => this.handleMapClick(e)); '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)); '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) { handleMapClick(e) {
//TODO(zcankara) Direct the event based on the key and value //TODO(zcankara) Direct the event based on the key and value
console.log("map: ", e.detail.key); console.log("map: ", e.detail.key);
} }
handleFilePositionClick(e) { handleFilePositionClick(e) {
//TODO(zcankara) Direct the event based on the key and value //TODO(zcankara) Direct the event based on the key and value
console.log("filePosition: ", e.detail.key); console.log("filePosition: ", e.detail.key);
} }
handleClickSourcePositions(e){
handleICTimeFilter(event) { //TODO(zcankara) Handle source position
this.#timeSelection.start = event.detail.startTime; console.log("source position map detail: ", e.detail);
this.#timeSelection.end = event.detail.endTime;
this.#icTrack.data.selectTimeRange(this.#timeSelection.start,
this.#timeSelection.end);
this.#icPanel.filteredEntries = this.#icTrack.data.selection;
} }
handleDblClickSelectMap(e){
//TODO(zcankara) Handle double clicked map
$(id) { return document.querySelector(id); } console.log("double clicked map: ", e.detail);
}
handleKeyDown(event) { handleMapChange(e){
let nav = document.state.navigation; if (!(e.detail instanceof V8Map)){
switch(event.key) { console.error("selected entry not a V8Map instance");
case "ArrowUp": return;
event.preventDefault(); }
if (event.shiftKey) { this.#state.map = e.detail;
nav.selectPrevEdge(); this.#view.mapTrack.selectedEntry = e.detail;
} else { this.#view.mapPanel.map = e.detail;
nav.moveInChunk(-1); }
} handleShowMaps(e){
return false; if (!(e.detail instanceof Chunk)){
case "ArrowDown": console.error("Chunk not selected");
event.preventDefault(); return;
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;
} }
this.#view.mapPanel.mapEntries = e.detail.filter();
}
handleICTimeFilter(event) {
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;
}
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 // Map event log processing
...@@ -111,28 +121,30 @@ class App { ...@@ -111,28 +121,30 @@ class App {
let icProcessor = new CustomIcProcessor(); let icProcessor = new CustomIcProcessor();
//TODO(zcankara) Assign timeline directly to the ic panel //TODO(zcankara) Assign timeline directly to the ic panel
//TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline //TODO(zcankara) Exe: this.#icPanel.timeline = document.state.icTimeline
this.#icTrack.data = icProcessor.processString(fileData.chunk); //TODO(zcankara) Set the data of the State object first
this.#icPanel.filteredEntries = this.#icTrack.data.all; this.#state.icTimeline = icProcessor.processString(fileData.chunk);
this.#icPanel.count.innerHTML = this.#icTrack.data.all.length; 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); reader.readAsText(fileData.file);
this.#icPanel.initGroupKeySelect(); this.#view.icPanel.initGroupKeySelect();
} }
// call when a new file uploaded // call when a new file uploaded
handleDataUpload(e) { handleDataUpload(e) {
if(!e.detail) return; if(!e.detail) return;
this.$('#container').style.display = 'block'; $('#container').style.display = 'block';
// instantiate the app logic // instantiate the app logic
let fileData = e.detail; let fileData = e.detail;
document.state = new State(this.#mapPanel, this.#timelinePanel,
this.#mapTrack, this.#icTrack);
try { try {
const timeline = this.handleLoadTextMapProcessor(fileData.chunk); const timeline = this.handleLoadTextMapProcessor(fileData.chunk);
// Transitions must be set before timeline for stats panel. // Transitions must be set before timeline for stats panel.
document.state.transitions= timeline.transitions; this.#state.mapTimeline = timeline;
this.#mapTrack.data = timeline; this.#view.mapPanel.transitions = this.#state.mapTimeline.transitions;
document.state.mapTimeline = timeline; this.#view.mapTrack.data = this.#state.mapTimeline;
this.#state.chunks = this.#view.mapTrack.chunks;
this.#view.mapPanel.timeline = this.#state.mapTimeline;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
...@@ -140,21 +152,6 @@ class App { ...@@ -140,21 +152,6 @@ class App {
this.fileLoaded = true; 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) { switchTheme(event) {
if(this.fileLoaded) return; if(this.fileLoaded) return;
document.documentElement.dataset.theme = document.documentElement.dataset.theme =
......
...@@ -9,7 +9,7 @@ found in the LICENSE file. --> ...@@ -9,7 +9,7 @@ found in the LICENSE file. -->
color: black; color: black;
} }
</style> </style>
<stats-panel id="stats-panel" onchange="app.handleShowMaps(event)"></stats-panel> <stats-panel id="stats-panel"></stats-panel>
<div class="panel"> <div class="panel">
<h2>Map Panel</h2> <h2>Map Panel</h2>
<map-transitions id="map-transitions"></map-transitions> <map-transitions id="map-transitions"></map-transitions>
......
...@@ -26,22 +26,20 @@ defineCustomElement('timeline-panel', (templateText) => ...@@ -26,22 +26,20 @@ defineCustomElement('timeline-panel', (templateText) =>
} }
get timelineCanvas() { 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; return this.timelineTracks[0].timelineCanvas;
} }
get timeline() { 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; return this.timelineTracks[0].timeline;
} }
set nofChunks(count){ set nofChunks(count){
for (const track of this.timelineTracks) { for (const track of this.timelineTracks) {
track.nofChunks = count; track.nofChunks = count;
} }
} }
get nofChunks(){
return this.timelineTracks[0].nofChunks;
}
get timelineTracks(){ get timelineTracks(){
return this.$("slot").assignedNodes().filter( return this.$("slot").assignedNodes().filter(
track => track.nodeType === Node.ELEMENT_NODE); track => track.nodeType === Node.ELEMENT_NODE);
......
...@@ -235,4 +235,4 @@ class Chunk { ...@@ -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