// create a xterm for replay function createReplayTerminal() { // vscode-snazzy https://github.com/Tyriar/vscode-snazzy // copied from xterm.js website var baseTheme = { foreground: '#eff0eb', background: '#282a36', selection: '#97979b33', black: '#282a36', brightBlack: '#686868', red: '#ff5c57', brightRed: '#ff5c57', green: '#5af78e', brightGreen: '#5af78e', yellow: '#f3f99d', brightYellow: '#f3f99d', blue: '#57c7ff', brightBlue: '#57c7ff', magenta: '#ff6ac1', brightMagenta: '#ff6ac1', cyan: '#9aedfe', brightCyan: '#9aedfe', white: '#f1f1f0', brightWhite: '#eff0eb' }; const term = new Terminal({ fontFamily: `'Fira Code', ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`, fontSize: 12, theme: baseTheme, convertEol: true, cursorBlink: true, }); term.open(document.getElementById('terminal_view')); term.resize(120, 36); const weblinksAddon = new WebLinksAddon.WebLinksAddon(); term.loadAddon(weblinksAddon); // fit the xterm viewpoint to parent element const fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); fitAddon.fit(); return term; } // sleep for ms seconds function _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // we could sleep for a long time // periodically check if we need to end replay. // This is pretty ugly but the callback mess otherwise async function sleep(ms, paused) { var loop_cnt = parseInt(ms / 20) + 1 for (i = 0; i < loop_cnt; i++) { if (paused()) { return paused() } await _sleep(20) } return paused() } // convert data to uint8array, we cannot convert it to string as // it will mess up special characters function base64ToUint8array(base64) { var raw = window.atob(base64); var rawLength = raw.length; var array = new Uint8Array(new ArrayBuffer(rawLength)); for (i = 0; i < rawLength; i++) { array[i] = raw.charCodeAt(i); } return array; } // replay session // term: xterm, path: session file to replay, // start: start position to replay in percentile, range 0-100 // paused: callback whether to stop play // prog: callback to update the progress bar // shift: callback whether we should change position // end: callback when playback is finished async function replay_session(term, records, total_dur, start, paused, prog, end) { var cur = 0 start = parseInt(total_dur * start / 100) term.reset() for (const item of records) { // we will blast through the beginning of the session if (cur >= start) { // we are cheating a little bit here, we do not want to wait for too long exit = await sleep(Math.min(item.Duration, 1000), paused) if (exit) { return } } term.write(base64ToUint8array(item.Data)) cur += item.Duration if (cur > start) { prog(parseInt(cur * 100 / total_dur)) } } end() } function forwardScreen(term, records, end) { var cur = 0 end = parseInt(total_dur * end / 100) term.reset() for (const item of records) { // we will blast through the beginning of the session if (cur >= end) { return } term.write(base64ToUint8array(item.Data)) cur += item.Duration } } async function fetchAndParse(path, update) { var records var total_dur = 0 // read file from server, we need to await twice. var res = await fetch(path) if (!res.ok) { return } records = await res.json() //calculate the total duration for (const item of records) { item.Duration = parseInt(item.Duration / 1000000) total_dur += item.Duration } console.log("total_dur", total_dur) update(records, total_dur) } function Init() { let term = createReplayTerminal(); var str = [ ' ┌────────────────────────────────────────────────────────────────────────────┐\n', ' │ \u001b[32;1mhttps://github.com/syssecfsu/witty\x1b[0m <- click it! │\n', ' └────────────────────────────────────────────────────────────────────────────┘\n', '' ].join(''); term.writeln(str); // adjust the progress bar size to that of terminal var view = document.querySelector("#terminal") var pbar = document.querySelector("#replay-control") pbar.setAttribute("style", "width:" + (view.offsetWidth - 32) + "px"); return term }