blob: 84e6f1dbd48fecd17a99658b671d3966021c04ff [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<!--
Copyright 2022 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<head>
<style>
#scrubberframe::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberframe::slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberframe {
margin-top: 10px;
flex-grow: 1;
appearance: none;
background-color: #f0f0f0;
width: 100%;
}
#scrubberdraw::-webkit-slider-thumb {
appearance: none;
width: 15px;
height: 20px;
background-color: grey;
}
#scrubberdraw::slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberdraw {
margin-top: 10px;
flex-grow: 1;
appearance: none;
background-color: #f0f0f0;
width: 100%;
}
#url {
font-family: monospace;
font-size: smaller;
}
#controls {}
#controls>#buttons {
display: flex;
}
#log {
min-height: 100px;
max-height: 100%;
font-family: monospace;
overflow: auto;
}
#connectionPanel,
#saveload {
display: inline-block;
}
#connectionPanel,
#saveload,
#topPanel {
padding-bottom: 10px;
}
#topPanel {
display: flex;
}
#settings {
display: flex;
flex-direction: column;
font-size: small;
}
.panelSection {
margin-right: 20px;
}
.panelSection:last-child {
margin-right: 0px;
flex-grow: 1;
}
#connection-status {
color: limegreen;
font-size: large;
padding-right: 5px;
}
#connection-status.disconnected {
color: orange;
}
</style>
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
<link rel='stylesheet' href='style.css'>
<script src='filter.js'></script>
<script src='filter-ui.js'></script>
<script src='frame.js'></script>
<script src='connection.js'></script>
</head>
<div id='connectionPanel'>
<div class='sectionTitle'>
<font class='disconnected' id='connection-status'>&#9679;</font>Connection
</div>
<div class='section'>
<div title="Expert usage only. Autoconnect should provide the dev tools websocket through /json/version discovery.">
<input id='url' name='url' size=60 type="text"
placeholder='WebSocket URL or leave empty for autoconnect...'></input>
</div>
<button class="mdc-button mdc-button--outline" id='connect'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Connect</span>
</button>
<button class="mdc-button mdc-button--outline" id='disconnect' disabled=true>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Disconnect</span>
</button>
</div>
</div>
<div id='saveload'>
<div class='sectionTitle'>Save/Load ...</div>
<div class='section'>
<button id='demo' class="mdc-button mdc-button--outline">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Load demo data</span>
</button>
<button id='savedata' class="mdc-button mdc-button--outline"
title="Serializes current session stream to json file.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Save to disk</span>
</button>
<button id='loaddata' class="mdc-button mdc-button--outline"
title="Deserializes json file of previous session and imports these frames into the App.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Load from disk</span>
</button>
</div>
</div>
<div id='logsPanel'>
<div class='panelSection'>
<div class='sectionTitle'>
Logs
<!--input size=40 placeholder='Comma separated filter ...'>(TODO)</input-->
</div>
<div class='section'>
<div id='log'></div>
</div>
</div>
</div>
<div class='panelSection'>
<div class='sectionTitle'
title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
<i class="material-icons-outlined">filter_list</i> Filters
</div>
<div class='section'>
<div id='filters' class="mdc-chip-set mdc-chip-set--filter" role="grid"
title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
</div>
<button class="mdc-button mdc-button--outline" id='createfilter'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">add_box</i> Add new filter</span>
</button>
</div>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Viewer Controls</div>
<div class='section' id='controls'>
<div id='buttons'>
<button class="mdc-button mdc-button--outline" id='prev'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">skip_previous</i> Previous frame</span>
</button>
<button class="mdc-button mdc-button--outline" id='play'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">play_circle_outline</i> Play</span>
</button>
<button class="mdc-button mdc-button--outline" id='pause'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">pause_circle_outline</i> Pause</span>
</button>
<button class="mdc-button mdc-button--outline" id='next'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Next frame <i class="material-icons-outlined">skip_next</i></span>
</button>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Frame Selection</div>
<input type='range' min='0' max='0' value='0' id='scrubberframe'></input>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Draw Selection</div>
<input type='range' min='0' max='0' value='0' id='scrubberdraw'></input>
</div>
</div>
</div>
<div>
<div class='sectionTitle'>
Viewer
</div>
<div style='float: right' class='sectionTitle'>
Scale
<select id="viewerscale">
<option>100%</option>
<option>50%</option>
<option>200%</option>
</select>
</div>
<div class='section'>
<canvas id='canvas'></canvas>
</div>
<div class='modalContainer'></div>
</div>
<div id='importtracing'>
<div class='sectionTitle'>Tracing (Prototype)...</div>
<div class='section'>
<button id='importtracebutton' class="mdc-button mdc-button--outline"
title="Import tracing data (json) into visual debugger app.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Import Trace</span>
</button>
</div>
</div>
<script>
function processIncomingFrame(json) {
if (!json) return;
new DrawFrame(json);
Player.instance.onNewFrame();
}
async function testAnimate() {
const f = await fetch('demo.json');
const text = await f.text();
const json = JSON.parse(text);
for (const frame of json) {
processIncomingFrame(frame);
}
}
async function saveDemoDataToDisk() {
const text = JSON.stringify(DrawFrame.instances.map(d => d.toJSON()));
const blob = new Blob([text], { type: 'text/plain' });
const link = document.createElement('a');
link.download = 'cvd-stream.json';
link.href = window.URL.createObjectURL(blob);
link.click();
}
window.onload = function () {
Connection.initialize();
const addFilterButton = document.querySelector('#createfilter');
addFilterButton.addEventListener('click', () => {
showCreateFilterPopup(addFilterButton);
});
const container = document.querySelector('.modalContainer');
container.addEventListener('click', (event) => {
if (event.target == container)
hideModal();
});
const demo = document.querySelector('#demo');
demo.addEventListener('click', testAnimate);
const savedata = document.querySelector('#savedata');
savedata.addEventListener('click', saveDemoDataToDisk);
const loaddata = document.querySelector('#loaddata');
loaddata.addEventListener('click', () => {
const f = document.createElement('input');
f.type = 'file';
f.addEventListener('change', () => {
const file = new FileReader(f.files[0]);
file.addEventListener('load', () => {
const json = JSON.parse(file.result);
for (const frame of json) {
processIncomingFrame(frame);
}
});
file.readAsText(f.files[0]);
});
f.click();
});
const importtracedata = document.querySelector('#importtracebutton');
importtracedata.addEventListener('click', () => {
const f = document.createElement('input');
f.type = 'file';
f.addEventListener('change', () => {
const file = new FileReader(f.files[0]);
file.addEventListener('load', () => {
const json = JSON.parse(file.result);
const traceEvents = json.traceEvents;
let src_frame = {
"drawcalls": [], "frame": "80631", "logs": [], "new_sources": [{ "anno": "frame.root.display_rect", "file": "x", "func": "x", "index": 0, "line": 0 }], "text": [], "time": "0",
"version": 1, "windowx": 2400, "windowy": 1600
};
processIncomingFrame(src_frame);
curr_frame = "0";
curr_draws = [];
for (const event of traceEvents) {
if (event.name == "VisualDebuggerSync") {
single_frame = { "drawcalls": [], "frame": "80631", "logs": [], "new_sources": [], "text": [], "time": "0", "version": 1, "windowx": 2400, "windowy": 1600 };
single_frame.drawcalls = curr_draws;
curr_frame = event.args.last_presented_trace_id;
single_frame.frame = curr_frame;
processIncomingFrame(single_frame);
curr_draws = []
}
if (event.name == "VizTestRootRect") {
let single_call = { "drawindex": curr_draws.length, "option": { "alpha": 0, "color": "#ff0000" }, "pos": [832, 670], "size": [112, 112], "source_index": 0 };
single_call.pos[0] = parseInt(event.args.args.pos_x);
single_call.pos[1] = parseInt(event.args.args.pos_y);
single_call.size[0] = parseInt(event.args.args.size_x);
single_call.size[1] = parseInt(event.args.args.size_y);
curr_draws.push(single_call);
}
}
});
file.readAsText(f.files[0]);
});
f.click();
});
setUpPlayer();
FilterUIDefault.initialize();
}
function setUpPlayer() {
// First, set up the viewer.
const canvas = document.querySelector('#canvas');
const logContainer = document.querySelector('#log');
const viewer = new Viewer(canvas, logContainer);
// Now create the player for the viewer.
const player = new Player(viewer, (frame) => {
// TODO: This feels like a hack. Find a cleaner way to update the scrubbers?
const scrubberFrame = document.querySelector('#scrubberframe');
scrubberFrame.max = DrawFrame.count();
scrubberFrame.value = player.currentFrameIndex;
const scrubberDraw = document.querySelector('#scrubberdraw');
scrubberDraw.max = frame.submissionCount() - 1;
scrubberDraw.value = frame.submissionFreezeIndex();
});
document.querySelector('#pause').addEventListener('click', () => {
player.pause();
});
document.querySelector('#play').addEventListener('click', () => {
player.play();
});
document.querySelector('#prev').addEventListener('click', () => {
player.rewind();
});
document.querySelector('#next').addEventListener('click', () => {
player.forward();
});
const scrubberFrame = document.querySelector('#scrubberframe');
scrubberFrame.addEventListener('input', () => {
player.freezeFrame(scrubberFrame.value);
});
const scrubberDraw = document.querySelector('#scrubberdraw');
scrubberDraw.addEventListener('input', () => {
player.freezeFrame(scrubberFrame.value, scrubberDraw.value);
});
const viewerScale = document.querySelector("#viewerscale");
viewerScale.addEventListener('input', () => {
player.setViewerScale(viewerScale.value);
});
}
function showModal(element) {
const container = document.querySelector('.modalContainer');
container.appendChild(element);
container.style.display = 'block';
element.focus();
}
function hideModal() {
const container = document.querySelector('.modalContainer');
container.style.display = 'none';
container.textContent = '';
}
</script>
</html>