blob: 0e0f22643d21dbe44c74ca613c940c34f0c1ca38 [file] [log] [blame]
Peter McNeeleyad3b2a52022-04-02 00:23:191<!DOCTYPE html>
2<html lang="en">
3<!--
Avi Drissmandfd880852022-09-15 20:11:094 Copyright 2022 The Chromium Authors
Peter McNeeleyad3b2a52022-04-02 00:23:195 Use of this source code is governed by a BSD-style license that can be
6 found in the LICENSE file.
7 -->
8
9<head>
10 <style>
11 #scrubberframe::-webkit-slider-thumb {
12 appearance: none;
13 width: 20px;
14 height: 20px;
15 background-color: grey;
16 }
17
Peter McNeeleyad3b2a52022-04-02 00:23:1918 #scrubberframe {
19 margin-top: 10px;
20 flex-grow: 1;
21 appearance: none;
22 background-color: #f0f0f0;
23 width: 100%;
24 }
25
Kevin Haslett78d72c92023-12-14 15:46:1126 .scrubber::-webkit-slider-thumb {
Peter McNeeleyad3b2a52022-04-02 00:23:1927 appearance: none;
28 width: 20px;
29 height: 20px;
30 background-color: grey;
31 }
32
Kevin Haslett78d72c92023-12-14 15:46:1133 .minMaxScrubber {
34 width: 100%;
35 display: flex;
36 }
37
38 .minMaxScrubber > .scrubber {
Peter McNeeleyad3b2a52022-04-02 00:23:1939 appearance: none;
40 background-color: #f0f0f0;
Kevin Haslett78d72c92023-12-14 15:46:1141 margin: 0px;
42 flex-grow: 1;
43 flex-basis: 20px; /* width of slider-thumb */
44 min-width: 0px;
Peter McNeeleyad3b2a52022-04-02 00:23:1945 }
46
47 #url {
48 font-family: monospace;
49 font-size: smaller;
50 }
51
Peter McNeeleyad3b2a52022-04-02 00:23:1952 #controls>#buttons {
53 display: flex;
54 }
55
56 #log {
57 min-height: 100px;
58 max-height: 100%;
59 font-family: monospace;
60 overflow: auto;
61 }
62
63 #connectionPanel,
64 #saveload {
65 display: inline-block;
66 }
67
68 #connectionPanel,
69 #saveload,
70 #topPanel {
71 padding-bottom: 10px;
72 }
73
74 #topPanel {
75 display: flex;
76 }
77
78 #settings {
79 display: flex;
80 flex-direction: column;
81 font-size: small;
82 }
83
84 .panelSection {
85 margin-right: 20px;
86 }
87
88 .panelSection:last-child {
89 margin-right: 0px;
90 flex-grow: 1;
91 }
92
93 #connection-status {
94 color: limegreen;
95 font-size: large;
96 padding-right: 5px;
97 }
98
99 #connection-status.disconnected {
100 color: orange;
101 }
102 </style>
103 <link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
104 <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
105 <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
Jay Yang6a756262022-08-10 19:32:13106 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" />
Peter McNeeleyad3b2a52022-04-02 00:23:19107
108 <link rel='stylesheet' href='style.css'>
109 <script src='filter.js'></script>
110 <script src='filter-ui.js'></script>
111 <script src='frame.js'></script>
112 <script src='connection.js'></script>
Jay Yang6a756262022-08-10 19:32:13113 <script src='thread.js'></script>
114 <script src='thread-ui.js'></script>
Peter McNeeleyad3b2a52022-04-02 00:23:19115</head>
116
117<div id='connectionPanel'>
118 <div class='sectionTitle'>
119 <font class='disconnected' id='connection-status'>&#9679;</font>Connection
120 </div>
121 <div class='section'>
122 <div title="Expert usage only. Autoconnect should provide the dev tools websocket through /json/version discovery.">
123 <input id='url' name='url' size=60 type="text"
124 placeholder='WebSocket URL or leave empty for autoconnect...'></input>
125 </div>
126 <button class="mdc-button mdc-button--outline" id='connect'>
127 <div class="mdc-button__ripple"></div>
128 <span class="mdc-button__label">Connect</span>
129 </button>
130 <button class="mdc-button mdc-button--outline" id='disconnect' disabled=true>
131 <div class="mdc-button__ripple"></div>
132 <span class="mdc-button__label">Disconnect</span>
133 </button>
Justin Chang9e976fb2022-06-22 22:50:03134 <input type="checkbox" id="autoconnect" name="autoconnect" checked="true">
135 <label for="autoconnect" style="font-size:small; font:Roboto" > Autoconnect</label><br>
Peter McNeeleyad3b2a52022-04-02 00:23:19136 </div>
137</div>
138
139<div id='saveload'>
140 <div class='sectionTitle'>Save/Load ...</div>
141 <div class='section'>
142 <button id='demo' class="mdc-button mdc-button--outline">
143 <div class="mdc-button__ripple"></div>
144 <span class="mdc-button__label">Load demo data</span>
145 </button>
146 <button id='savedata' class="mdc-button mdc-button--outline"
147 title="Serializes current session stream to json file.">
148 <div class="mdc-button__ripple"></div>
149 <span class="mdc-button__label">Save to disk</span>
150 </button>
151 <button id='loaddata' class="mdc-button mdc-button--outline"
152 title="Deserializes json file of previous session and imports these frames into the App.">
153 <div class="mdc-button__ripple"></div>
154 <span class="mdc-button__label">Load from disk</span>
155 </button>
156 </div>
157</div>
158
159<div id='logsPanel'>
Kevin Haslett6f0f72b2023-12-18 18:04:07160 <div class='panelSection collapsible'>
Peter McNeeleyad3b2a52022-04-02 00:23:19161 <div class='sectionTitle'>
162 Logs
Kevin Haslett6f0f72b2023-12-18 18:04:07163 [<span class="plus">+</span><span class="minus">-</span>]
Peter McNeeleyad3b2a52022-04-02 00:23:19164 <!--input size=40 placeholder='Comma separated filter ...'>(TODO)</input-->
165 </div>
166 <div class='section'>
167 <div id='log'></div>
168 </div>
169 </div>
170</div>
171
Kevin Haslett6f0f72b2023-12-18 18:04:07172<div class='panelSection collapsible'>
Peter McNeeleyad3b2a52022-04-02 00:23:19173 <div class='sectionTitle'
174 title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
175 <i class="material-icons-outlined">filter_list</i> Filters
Kevin Haslett6f0f72b2023-12-18 18:04:07176 [<span class="plus">+</span><span class="minus">-</span>]
Peter McNeeleyad3b2a52022-04-02 00:23:19177 </div>
178 <div class='section'>
179 <div id='filters' class="mdc-chip-set mdc-chip-set--filter" role="grid"
180 title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
181 </div>
182 <button class="mdc-button mdc-button--outline" id='createfilter'>
183 <div class="mdc-button__ripple"></div>
184 <span class="mdc-button__label"><i class="material-icons-outlined">add_box</i> Add new filter</span>
185 </button>
186 </div>
187</div>
188
Kevin Haslett6f0f72b2023-12-18 18:04:07189<div class='panelSection collapsible'>
Jay Yang9c4c5772022-08-06 16:00:34190 <div class='sectionTitle'>
Jay Yang6a756262022-08-10 19:32:13191 <i class="material-symbols-outlined">
192 airwave
Jay Yang9c4c5772022-08-06 16:00:34193 </i>
194 Threads
Kevin Haslett6f0f72b2023-12-18 18:04:07195 [<span class="plus">+</span><span class="minus">-</span>]
Jay Yang9c4c5772022-08-06 16:00:34196 </div>
197
198 <div id='threads' class='section'></div>
199</div>
200
Kevin Haslett6f0f72b2023-12-18 18:04:07201<div class='panelSection collapsible'>
202 <div class='sectionTitle'>
203 Viewer Controls
204 [<span class="plus">+</span><span class="minus">-</span>]
205 </div>
Peter McNeeleyad3b2a52022-04-02 00:23:19206 <div class='section' id='controls'>
207 <div id='buttons'>
208 <button class="mdc-button mdc-button--outline" id='prev'>
Peter McNeeleyad3b2a52022-04-02 00:23:19209 <span class="mdc-button__label"><i class="material-icons-outlined">skip_previous</i> Previous frame</span>
210 </button>
211 <button class="mdc-button mdc-button--outline" id='play'>
212 <div class="mdc-button__ripple"></div>
213 <span class="mdc-button__label"><i class="material-icons-outlined">play_circle_outline</i> Play</span>
214 </button>
215 <button class="mdc-button mdc-button--outline" id='pause'>
216 <div class="mdc-button__ripple"></div>
217 <span class="mdc-button__label"><i class="material-icons-outlined">pause_circle_outline</i> Pause</span>
218 </button>
219 <button class="mdc-button mdc-button--outline" id='next'>
Peter McNeeleyad3b2a52022-04-02 00:23:19220 <span class="mdc-button__label">Next frame <i class="material-icons-outlined">skip_next</i></span>
221 </button>
Peter McNeeley3cad8ee92023-04-28 01:20:50222 <button class="mdc-button mdc-button--raised" id='live'>
Justin Chang5cd0f4b2022-06-25 20:23:38223 <div class="mdc-button__ripple"></div>
224 <span class="mdc-button__label">Live <i class="material-icons-outlined">fast_forward</i></span>
225 </button>
Peter McNeeleyad3b2a52022-04-02 00:23:19226
227 </div>
Kevin Haslett6f0f72b2023-12-18 18:04:07228 <div>
Peter McNeeleyad3b2a52022-04-02 00:23:19229 <div class='sectionTitle'>Frame Selection</div>
230 <input type='range' min='0' max='0' value='0' id='scrubberframe'></input>
231 </div>
Kevin Haslett6f0f72b2023-12-18 18:04:07232 <div>
Kevin Haslett78d72c92023-12-14 15:46:11233 <div class='sectionTitle'>Draw Selection<span id="drawRange"></span></div>
234 <div class="minMaxScrubber">
235 <input type="range" id='minDrawScrubber' class="scrubber"
236 min="0" max="0" value="0" step="1"></input>
237 <input type="range" id='maxDrawScrubber' class="scrubber"
238 min="0" max="1" value="1" step="1"></input>
239 </div>
Peter McNeeleyad3b2a52022-04-02 00:23:19240 </div>
241 </div>
242</div>
243
Kevin Haslett6f0f72b2023-12-18 18:04:07244<div class="panelSection collapsible">
Peter McNeeleyad3b2a52022-04-02 00:23:19245 <div class='sectionTitle'>
246 Viewer
Kevin Haslett6f0f72b2023-12-18 18:04:07247 [<span class="plus">+</span><span class="minus">-</span>]
Peter McNeeleyad3b2a52022-04-02 00:23:19248 </div>
249 <div style='float: right' class='sectionTitle'>
250 Scale
251 <select id="viewerscale">
Kevin Haslett45222c92023-12-12 15:57:02252 <option id="100pct">100%</option>
253 <option id="50pct">50%</option>
254 <option id="200pct">200%</option>
“Jayf42d703a2024-12-10 19:49:15255 <option id="freeCam">Free Camera</option>
Peter McNeeleyad3b2a52022-04-02 00:23:19256 </select>
257 </div>
Jay Yangd3391f02022-06-17 19:04:00258
259 <div style='float: right' class='sectionTitle'>
260 Orientation
261 <select id="viewerorientation">
Kevin Haslett45222c92023-12-12 15:57:02262 <option id="0deg">0 deg clockwise</option>
263 <option id="90deg">90 deg clockwise</option>
264 <option id="180deg">180 deg clockwise</option>
265 <option id="270deg">270 deg clockwise</option>
266 <option id="hFlip">Horizontal Flip</option>
267 <option id="vFlip">Vertical Flip</option>
Jay Yangd3391f02022-06-17 19:04:00268 </select>
269 </div>
270
Peter McNeeleyad3b2a52022-04-02 00:23:19271 <div class='section'>
River Gilhuly02627b152022-11-23 22:36:02272 <!--We need to fix viewer size to avoid scroll position change when
Peter McNeeleybb96b262022-08-31 18:51:22273 multiple displays are present. crbug.com/1358526-->
Michael Tang8b96bb002023-05-08 20:19:28274 <div style="height:4000px; border: 1px dotted gray; background-color: #f0f0f0">
Peter McNeeleybb96b262022-08-31 18:51:22275 <canvas id='canvas' style="top :0px"></canvas>
276 </div>
Peter McNeeleyad3b2a52022-04-02 00:23:19277 </div>
278
279 <div class='modalContainer'></div>
280</div>
281
282<div id='importtracing'>
283 <div class='sectionTitle'>Tracing (Prototype)...</div>
284 <div class='section'>
285 <button id='importtracebutton' class="mdc-button mdc-button--outline"
286 title="Import tracing data (json) into visual debugger app.">
287 <div class="mdc-button__ripple"></div>
288 <span class="mdc-button__label">Import Trace</span>
289 </button>
290 </div>
291</div>
292
293<script>
294 function processIncomingFrame(json) {
295 if (!json) return;
296
297 new DrawFrame(json);
298 Player.instance.onNewFrame();
299 }
300
301 async function testAnimate() {
302 const f = await fetch('demo.json');
303 const text = await f.text();
304 const json = JSON.parse(text);
305 for (const frame of json) {
306 processIncomingFrame(frame);
307 }
308 }
309
310 async function saveDemoDataToDisk() {
Jay Yangeae9ccf2022-06-30 02:43:20311 const text = JSON.stringify(DrawFrame.frameBuffer.instances.map(d => d.toJSON()));
Peter McNeeleyad3b2a52022-04-02 00:23:19312 const blob = new Blob([text], { type: 'text/plain' });
313 const link = document.createElement('a');
314 link.download = 'cvd-stream.json';
315 link.href = window.URL.createObjectURL(blob);
316 link.click();
317 }
318
319 window.onload = function () {
320
321 Connection.initialize();
322
323 const addFilterButton = document.querySelector('#createfilter');
324 addFilterButton.addEventListener('click', () => {
325 showCreateFilterPopup(addFilterButton);
326 });
327
328 const container = document.querySelector('.modalContainer');
329 container.addEventListener('click', (event) => {
330 if (event.target == container)
331 hideModal();
332 });
333
334 const demo = document.querySelector('#demo');
335 demo.addEventListener('click', testAnimate);
336
337 const savedata = document.querySelector('#savedata');
338 savedata.addEventListener('click', saveDemoDataToDisk);
339
340 const loaddata = document.querySelector('#loaddata');
341 loaddata.addEventListener('click', () => {
342 const f = document.createElement('input');
343 f.type = 'file';
344 f.addEventListener('change', () => {
345 const file = new FileReader(f.files[0]);
346 file.addEventListener('load', () => {
347 const json = JSON.parse(file.result);
348 for (const frame of json) {
349 processIncomingFrame(frame);
350 }
351 });
352 file.readAsText(f.files[0]);
353 });
354 f.click();
355 });
356
357 const importtracedata = document.querySelector('#importtracebutton');
358 importtracedata.addEventListener('click', () => {
359 const f = document.createElement('input');
360 f.type = 'file';
361 f.addEventListener('change', () => {
Michael Tang606678b2024-02-05 20:31:17362 async function handleBlob(blob) {
363 if (blob.type === 'application/x-gzip') {
364 // If the blob is gzipped, decompress it and recurse.
365 const ds = new DecompressionStream("gzip");
366 const decompressedBlob = blob.stream().pipeThrough(ds);
367 handleBlob(await new Response(decompressedBlob).blob());
368 } else {
369 try {
370 const json = JSON.parse(await blob.text());
371 handleImportTraceJson(json);
372 } catch (e) {
373 if (e instanceof SyntaxError) {
374 // Not all JSON blobs have the right mimetype, so we just try
375 // and fail to check.
376 alert("Not valid JSON: " + blob.message);
377 } else {
378 throw e;
Michael Tanga18ce182024-01-03 22:43:41379 }
Peter McNeeleyad3b2a52022-04-02 00:23:19380 }
381 }
Michael Tang606678b2024-02-05 20:31:17382 }
383
384 handleBlob(f.files[0]);
Peter McNeeleyad3b2a52022-04-02 00:23:19385 });
386 f.click();
Michael Tang606678b2024-02-05 20:31:17387
388 function handleImportTraceJson(json) {
389 const traceEvents = json.traceEvents;
390
391 curr_frame = "0";
392 curr_draws = [];
393 sources_this_frame = []
394 global_sources_mapping = []
395 // Get the source index for an annotation, updating `global_sources_mapping` as needed.
396 let getSourceIndex = (anno) => {
397 let found_source_index = global_sources_mapping.indexOf(anno);
398 if (found_source_index === -1) {
399 global_sources_mapping.push(anno);
400 found_source_index = global_sources_mapping.length - 1;
401 sources_this_frame.push({
402 "anno": anno,
403 "file": "none",
404 "func": "none",
405 "index": found_source_index,
406 "line": -1,
407 });
408 }
409 return found_source_index;
410 };
411
412 threads_this_frame = new Set();
413 global_threads = {};
414 global_processes = {};
415 // Return a faux thread ID that also includes the process ID.
416 // VizDebugger only tracks a thread ID, but we know the process ID as well in this case.
417 let trackThreadAndProcessId = (event) => {
418 // We're assuming the thread ID is not going to exceed u16.
419 let thread_id = (event.pid * 65536) + event.tid;
420 threads_this_frame.add({
421 "thread_id": thread_id,
422 "thread_name": `${global_processes[event.pid]}/${global_threads[event.tid]}`,
423 })
424 return thread_id;
425 }
426 let resolveThreadNamesAndResetThreadIdsThisFrame = () => {
427 let threads = [];
428 for (const thread of threads_this_frame) {
429 threads.push(thread);
430 }
431 threads_this_frame.clear();
432 return threads;
433 };
434
435
436 for (const event of traceEvents) {
437 if (event.name === "thread_name") {
438 global_threads[event.tid] = event.args.name;
439 } else if (event.name === "process_name") {
440 global_processes[event.pid] = event.args.name;
441 } else if (event.cat.includes("viz.visual_debugger")) {
442 if (event.name == "visual_debugger_sync") {
443 single_frame = { "drawcalls": [], "frame": "none", "logs": [], "new_sources": [], "time": "0", "version": 1, "windowx": 2400, "windowy": 1600, "threads": [{ "thread_id": "1", "thread_name": "allthreads" }] };
444 single_frame.drawcalls = curr_draws;
445 curr_frame = event.args.last_presented_trace_id;
446 single_frame.frame = curr_frame;
447 single_frame.new_sources = sources_this_frame;
448 single_frame.windowx = parseInt(event.args.display_size.split("x")[0]);
449 single_frame.windowy = parseInt(event.args.display_size.split("x")[1]);
450 processIncomingFrame(single_frame);
451 curr_draws = [];
452 sources_this_frame = [];
453 threads_this_frame.clear();
454 }
455 else {
456 const single_call = { "drawindex": curr_draws.length, "option": { "alpha": 5, "color": "#ff0000" }, "pos": [-1, -1], "size": [-1, -1], "source_index": -1, "thread_id": 1, "buff_id": -1 };
457
458 single_call.pos[0] = parseFloat(event.args.args.pos_x);
459 single_call.pos[1] = parseFloat(event.args.args.pos_y);
460 single_call.size[0] = parseFloat(event.args.args.size_x);
461 single_call.size[1] = parseFloat(event.args.args.size_y);
462 single_call.text = event.args.args.text;
463 single_call.source_index = getSourceIndex(event.name);
464 curr_draws.push(single_call);
465 }
466 } else if (event.cat.includes("viz.quads")) {
467 if (event.name === "cc::LayerTreeHostImpl") {
468 let draw_calls = [];
469 let logs = [];
470
471 let render_pass_count = event.args.snapshot.frame.render_passes.length;
472 for (let i = 0; i < render_pass_count; i++) {
473 let render_pass = event.args.snapshot.frame.render_passes[i];
474
475 logs.push({
476 "source_index": getSourceIndex("frame.render_pass.meta"),
477 "drawindex": draw_calls.length + logs.length,
478 "option": { "alpha": 0, "color": "#0000ff" },
479 "thread_id": trackThreadAndProcessId(event),
480 "value": `Render pass id=${render_pass.id}, output_rect=${render_pass.output_rect}, damage_rect=${render_pass.damage_rect}, quad_list.size=${render_pass.quad_list.length}, copy_requests=${render_pass.copy_requests}`,
481 });
482
483 if (i < render_pass_count - 1) {
484 // Skip non-root render pass quads to reduce visual noise.
485 continue;
486 }
487
488 draw_calls.push({
489 "source_index": getSourceIndex("frame.render_pass.output_rect"),
490 "drawindex": draw_calls.length + logs.length,
491 "option": { "alpha": 5, "color": "#000000" },
492 "pos": [render_pass.output_rect[0], render_pass.output_rect[1]],
493 "size": [render_pass.output_rect[2], render_pass.output_rect[3]],
494 "thread_id": trackThreadAndProcessId(event),
495 "buff_id": -1,
496 });
497
498 draw_calls.push({
499 "source_index": getSourceIndex("frame.render_pass.damage"),
500 "drawindex": draw_calls.length + logs.length,
501 "option": { "alpha": 5, "color": "#000000" },
502 "pos": [render_pass.damage_rect[0], render_pass.damage_rect[1]],
503 "size": [render_pass.damage_rect[2], render_pass.damage_rect[3]],
504 "thread_id": trackThreadAndProcessId(event),
505 "buff_id": -1,
506 });
507
508 for (let quad of render_pass.quad_list) {
509 draw_calls.push({
510 "source_index": getSourceIndex("frame.render_pass.quad"),
511 "drawindex": draw_calls.length + logs.length,
512 "option": { "alpha": 5, "color": "#000000" },
513 "pos": [
514 quad.rect_as_target_space_quad[0],
515 quad.rect_as_target_space_quad[1],
516 ],
517 "size": [
518 quad.rect_as_target_space_quad[2] - quad.rect_as_target_space_quad[0],
519 quad.rect_as_target_space_quad[5] - quad.rect_as_target_space_quad[1],
520 ],
521 "thread_id": trackThreadAndProcessId(event),
522 "buff_id": -1,
523 });
524
525 draw_calls.push({
526 "source_index": getSourceIndex("frame.render_pass.material"),
527 "drawindex": draw_calls.length + logs.length,
528 "option": { "alpha": 0, "color": "#00ff00" },
529 "pos": [
530 quad.rect_as_target_space_quad[0],
531 quad.rect_as_target_space_quad[1],
532 ],
533 "size": [0, 0],
534 "text": `${quad.material}`,
535 "thread_id": trackThreadAndProcessId(event),
536 "buff_id": -1,
537 });
538 }
539 }
540
541 // This event contains a snapshot of the layer tree, so we can process a full frame immediately.
542 processIncomingFrame({
543 "drawcalls": draw_calls,
544 "frame": event.tts,
545 "logs": logs,
546 "new_sources": sources_this_frame,
547 "time": event.ts,
548 "version": 1,
549 "windowx": event.args.snapshot.device_viewport_size.width,
550 "windowy": event.args.snapshot.device_viewport_size.height,
551 "threads": resolveThreadNamesAndResetThreadIdsThisFrame(),
552 });
553 sources_this_frame = [];
554 }
555 }
556 }
557 }
Peter McNeeleyad3b2a52022-04-02 00:23:19558 });
559
Kevin Haslett6f0f72b2023-12-18 18:04:07560 document.querySelectorAll('.panelSection.collapsible').forEach(
561 (section) => {
562 let title = section.querySelector('.sectionTitle');
563 title.addEventListener('click', () => {
564 section.classList.toggle('collapsed');
565 });
566 });
567
Peter McNeeleyad3b2a52022-04-02 00:23:19568 setUpPlayer();
Jay Yang32d49852022-06-21 19:18:28569 restoreFilters();
Peter McNeeleyad3b2a52022-04-02 00:23:19570 }
571
Kevin Haslett45222c92023-12-12 15:57:02572 /**
573 * Gets value from localStorage if it exists, and sets the <option> with that
574 * id to be selected in options_el.
575 * @param {string} key: The localStorage key to get if present.
576 * @param {Element} options_el: The <select> Element to be updated.
577 * @returns {boolean} Whether a value from localStorage was used to change the
578 * the selected option.
579 */
580 function getStoredOptionsValue(key, options_el) {
581 let storedId = localStorage.getItem(key);
582 if (storedId != null) {
583 let option = options_el.namedItem(storedId);
584 if (option == null) {
585 console.error(`No option with id ${storedId} found on ${options_el.id}`);
586 localStorage.removeItem(key);
587 } else {
588 options_el.selectedIndex = option.index;
589 return true;
590 }
591 }
592
593 return false;
594 }
595
Kevin Haslett78d72c92023-12-14 15:46:11596 function updateDrawScrubberSizes(minIndex, maxIndex, nDraws) {
597 const scrubberMin = document.querySelector('#minDrawScrubber');
598 const scrubberMax = document.querySelector('#maxDrawScrubber');
599 const drawRange = document.querySelector('#drawRange');
600
601 // The point where the sliders meet will be halway between the two values.
602 // This means the slider you select when clicking will always be the one
603 // closest to your mouse.
604 let mid = Math.floor((minIndex + maxIndex) / 2);
605 scrubberMin.max = mid;
606 scrubberMax.min = mid;
607 // Update the size of each slider to its fraction of the total range.
608 scrubberMin.style = `flex-grow: ${mid}`;
609 scrubberMax.style = `flex-grow: ${nDraws - mid}`;
610 }
611
612 function setDrawScrubbers(minIndex, maxIndex, nDraws) {
613 const scrubberMin = document.querySelector('#minDrawScrubber');
614 const scrubberMax = document.querySelector('#maxDrawScrubber');
615 const drawRange = document.querySelector('#drawRange');
616
Kevin Haslett20172e9c2023-12-15 22:01:09617 scrubberMax.max = nDraws;
Kevin Haslett78d72c92023-12-14 15:46:11618 scrubberMin.value = minIndex;
619 scrubberMax.value = maxIndex;
Kevin Haslett78d72c92023-12-14 15:46:11620 drawRange.textContent = ` [${minIndex}, ${maxIndex})`;
621
622 updateDrawScrubberSizes(minIndex, maxIndex, nDraws);
623 }
624
Kevin Haslett20172e9c2023-12-15 22:01:09625 function updateFrameScrubber(oldest, newest, current) {
626 const scrubberFrame = document.querySelector('#scrubberframe');
627 scrubberFrame.min = oldest;
628 scrubberFrame.max = newest;
629 scrubberFrame.value = current;
630 }
631
Peter McNeeleyad3b2a52022-04-02 00:23:19632 function setUpPlayer() {
633 // First, set up the viewer.
634 const canvas = document.querySelector('#canvas');
635 const logContainer = document.querySelector('#log');
636 const viewer = new Viewer(canvas, logContainer);
637 // Now create the player for the viewer.
638 const player = new Player(viewer, (frame) => {
Kevin Haslett20172e9c2023-12-15 22:01:09639 updateFrameScrubber(
640 DrawFrame.frameBuffer.oldestIndex(),
641 DrawFrame.frameBuffer.newestIndex(),
642 player.currentFrameIndex);
Kevin Haslett78d72c92023-12-14 15:46:11643 setDrawScrubbers(
644 frame.minIndex(), frame.maxIndex(), frame.submissionCount());
Peter McNeeleyad3b2a52022-04-02 00:23:19645 });
646
647 document.querySelector('#pause').addEventListener('click', () => {
648 player.pause();
Justin Chang5cd0f4b2022-06-25 20:23:38649 pause.setAttribute('style', 'background:#000000;color:white');
Peter McNeeleyad3b2a52022-04-02 00:23:19650 });
651
652 document.querySelector('#play').addEventListener('click', () => {
653 player.play();
Justin Chang5cd0f4b2022-06-25 20:23:38654 pause.removeAttribute('style');
Peter McNeeleyad3b2a52022-04-02 00:23:19655 });
656
657 document.querySelector('#prev').addEventListener('click', () => {
658 player.rewind();
659 });
660
661 document.querySelector('#next').addEventListener('click', () => {
662 player.forward();
663 });
664
Justin Chang5cd0f4b2022-06-25 20:23:38665 document.querySelector('#live').addEventListener('click', () => {
Peter McNeeley3cad8ee92023-04-28 01:20:50666 player.live();
Justin Chang5cd0f4b2022-06-25 20:23:38667 has_disconnected = false;
Justin Chang5cd0f4b2022-06-25 20:23:38668 pause.removeAttribute('style');
669 });
670
Peter McNeeleyad3b2a52022-04-02 00:23:19671 const scrubberFrame = document.querySelector('#scrubberframe');
672 scrubberFrame.addEventListener('input', () => {
Kevin Haslett78d72c92023-12-14 15:46:11673 player.freezeFrame(parseInt(scrubberFrame.value));
Justin Chang5cd0f4b2022-06-25 20:23:38674 pause.setAttribute('style', 'background:#000000;color:white');
Peter McNeeleyad3b2a52022-04-02 00:23:19675 });
676
Kevin Haslett78d72c92023-12-14 15:46:11677 const scrubberDrawMin = document.querySelector('#minDrawScrubber');
678 const scrubberDrawMax = document.querySelector('#maxDrawScrubber');
679 function drawScrubberChanged() {
680 let min = parseInt(scrubberDrawMin.value);
681 let max = parseInt(scrubberDrawMax.value);
682 player.freezeFrame(parseInt(scrubberFrame.value), min, max);
683 updateDrawScrubberSizes(min, max, parseInt(scrubberDrawMax.max));
684 }
685
686 scrubberDrawMin.addEventListener('input', () => {
687 drawScrubberChanged();
688 });
689 scrubberDrawMax.addEventListener('input', () => {
690 drawScrubberChanged()
Peter McNeeleyad3b2a52022-04-02 00:23:19691 });
Justin Changc0f45a62022-06-24 15:07:10692
693 let currentMouseX = 0;
694 let currentMouseY = 0;
695 let zoom = 100;
Peter McNeeleyad3b2a52022-04-02 00:23:19696 const viewerScale = document.querySelector("#viewerscale");
Kevin Haslett45222c92023-12-12 15:57:02697
698 function scaleChanged() {
“Jayf42d703a2024-12-10 19:49:15699 const selected = viewerScale.selectedOptions[0];
Kevin Haslett45222c92023-12-12 15:57:02700 if (selected.id != "freeCam") {
“Jayf42d703a2024-12-10 19:49:15701 viewer.resetTranslation();
Justin Changc0f45a62022-06-24 15:07:10702 player.setViewerScale(viewerScale.value);
703 zoom = parseInt(viewerScale.value);
704 }
705 else {
706 player.setViewerScale(zoom);
707 canvas.addEventListener('mouseenter', () => {
708 canvas.addEventListener('mousemove', function (event) {
709 currentMouseX = Math.round(event.offsetX);
710 currentMouseY = Math.round(event.offsetY);
711 });
712 });
713 canvas.addEventListener('wheel', function(e) {
714 e.preventDefault();
715 var delta = e.deltaY;
“Jayf42d703a2024-12-10 19:49:15716 const selected = viewerScale.selectedOptions[0];
River Gilhuly02627b152022-11-23 22:36:02717
“Jayf42d703a2024-12-10 19:49:15718 if (selected.id == "freeCam") {
719 viewer.zoomToMouse(currentMouseX, currentMouseY, delta);
720 }
Justin Changc0f45a62022-06-24 15:07:10721
722 }, {passive:false});
723 }
Kevin Haslett45222c92023-12-12 15:57:02724 }
725 const scaleKey = "scale";
726 if (getStoredOptionsValue(scaleKey, viewerScale)) {
727 scaleChanged();
728 }
729 viewerScale.addEventListener('input', () => {
730 scaleChanged();
731 localStorage.setItem(scaleKey, viewerScale.selectedOptions[0].id);
Peter McNeeleyad3b2a52022-04-02 00:23:19732 });
Justin Changc0f45a62022-06-24 15:07:10733
Jay Yangd3391f02022-06-17 19:04:00734 const viewerOrientation = document.querySelector("#viewerorientation");
Kevin Haslett45222c92023-12-12 15:57:02735
736 function orientationChanged() {
737 let selected = viewerOrientation.selectedOptions[0];
738 // Change dropdown style when setting non-zero orientation.
739 if (selected.id != '0deg') {
740 viewerOrientation.classList.add("nonzero-orientation");
741 } else {
742 viewerOrientation.classList.remove("nonzero-orientation");
743 }
Jay Yangd3391f02022-06-17 19:04:00744 player.setViewerOrientation(viewerOrientation.value);
Kevin Haslett45222c92023-12-12 15:57:02745 }
746 const orientationKey = 'orientation';
747 if (getStoredOptionsValue(orientationKey, viewerOrientation)) {
748 orientationChanged();
749 }
Jay Yangd3391f02022-06-17 19:04:00750 viewerOrientation.addEventListener('change', () => {
Kevin Haslett45222c92023-12-12 15:57:02751 orientationChanged();
752 localStorage.setItem(orientationKey, viewerOrientation.selectedOptions[0].id);
Jay Yangd3391f02022-06-17 19:04:00753 });
Kevin Haslett45222c92023-12-12 15:57:02754
Peter McNeeleyad3b2a52022-04-02 00:23:19755 }
756
757
River Gilhuly02627b152022-11-23 22:36:02758 function showModal(element, focusSelector) {
Peter McNeeleyad3b2a52022-04-02 00:23:19759 const container = document.querySelector('.modalContainer');
760 container.appendChild(element);
761 container.style.display = 'block';
River Gilhuly02627b152022-11-23 22:36:02762 element.querySelector(focusSelector).focus();
Peter McNeeleyad3b2a52022-04-02 00:23:19763 }
764
765 function hideModal() {
766 const container = document.querySelector('.modalContainer');
767 container.style.display = 'none';
768 container.textContent = '';
769 }
770
771</script>
772
River Gilhuly02627b152022-11-23 22:36:02773</html>