log.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Constants
  2. const regex = {
  3. error: new RegExp('^[a-zA-Z]*Error:'),
  4. };
  5. // DOM elements
  6. const elements = {
  7. wrapper: document.querySelector('#wrapper'),
  8. prompt: document.querySelector('#prompt'),
  9. container: document.querySelector('#container'),
  10. browser: document.querySelector('#browser'),
  11. config: document.querySelector('#config'),
  12. log: document.querySelector('#log'),
  13. };
  14. // Show prompt and hide log container
  15. elements.prompt.hidden = false;
  16. elements.container.hidden = true;
  17. /**
  18. * Escape HTML.
  19. */
  20. function escapeHTML(text) {
  21. const template = document.createElement('span');
  22. template.innerText = text;
  23. return template.innerHTML;
  24. }
  25. /**
  26. * Create an element from HTML.
  27. */
  28. function createElementFromHTML(html) {
  29. const template = document.createElement('template');
  30. template.innerHTML = html.trim();
  31. return template.content.firstChild;
  32. }
  33. /**
  34. * Format a record (message) value.
  35. */
  36. function formatRecordValue(value) {
  37. // Handle null
  38. if (value === null) {
  39. return `<span class="null">${escapeHTML(`${value}`)}</span>`;
  40. }
  41. // Handle boolean
  42. if (value.constructor === Boolean) {
  43. return `<span class="boolean">${escapeHTML(value)}</span>`;
  44. }
  45. // Handle number
  46. if (value.constructor === Number) {
  47. return `<span class="number">${escapeHTML(value)}</span>`;
  48. }
  49. // Handle string, converted types (e.g. ArrayBuffer, Blob, ...)
  50. // and errors (exceptions).
  51. if (value.constructor === String) {
  52. if (value.startsWith('[') && value.endsWith(']')) {
  53. return `<span class="converted">${escapeHTML(value)}</span>`;
  54. }
  55. if (regex.error.test(value)) {
  56. return `<span class="error">${escapeHTML(value)}</span>`;
  57. }
  58. return `<span class="string">${escapeHTML(value)}</span>`;
  59. }
  60. // Handle object
  61. if (value.constructor === Object) {
  62. const entries = Object.entries(value);
  63. return `
  64. <details>
  65. <summary class="type">Object(${entries.length})</summary>
  66. <ul>
  67. ${entries.map(([key, value]) => {
  68. return `<li><span class="type">${escapeHTML(key)}:</span> ${formatRecordValue(value)}</li>`;
  69. }).join('\n')}
  70. </ul>
  71. </details>`;
  72. }
  73. // Handle array
  74. if (value instanceof Array) {
  75. return `
  76. <details>
  77. <summary class="type">Array(${value.length})</summary>
  78. <ol>
  79. ${value.map((item, index) => {
  80. return `<li><span class="type">${index}:</span> ${formatRecordValue(item)}</li>`;
  81. }).join('\n')}
  82. </ol>
  83. </details>`;
  84. }
  85. // Unknown
  86. return `[${value.constructor}]`;
  87. }
  88. /**
  89. * Show the log in the UI.
  90. * @param data A log report in JSON notation.
  91. */
  92. function showLog(data) {
  93. // Decode as JSON
  94. let container;
  95. try {
  96. container = JSON.parse(data);
  97. } catch (error) {
  98. return console.error('Could not parse pasted text to object:', error);
  99. }
  100. // Required keys to be available
  101. if (!(container.config instanceof Object) ||
  102. container.browser.constructor !== String ||
  103. !(container.log instanceof Array)) {
  104. return console.error('Not a valid container object');
  105. }
  106. // Hide prompt and show log container
  107. elements.prompt.hidden = true;
  108. elements.container.hidden = false;
  109. // Display meta data
  110. elements.browser.textContent = container.browser;
  111. elements.config.textContent = JSON.stringify(container.config, null, 2);
  112. // Display log records
  113. elements.log.innerHTML = '';
  114. let startTimestampMs;
  115. for (let [timestampMs, type, tag, ...values] of container.log) {
  116. // Determine start timestamp so we can display the offset in seconds
  117. if (startTimestampMs === undefined) {
  118. startTimestampMs = timestampMs;
  119. }
  120. // Get CSS style from tag (if any)
  121. if (tag.startsWith('%c')) {
  122. const style = escapeHTML(values.shift());
  123. tag = `<span style="${style}">${escapeHTML(tag.substring(2))}</span>`;
  124. } else {
  125. tag = escapeHTML(tag);
  126. }
  127. // Add element to log container
  128. elements.log.appendChild(createElementFromHTML(`
  129. <tbody>
  130. <tr class="record ${escapeHTML(type)}">
  131. <td class="date" title="${new Date(timestampMs)}">${((timestampMs - startTimestampMs) / 1000).toFixed(3)}</td>
  132. <td class="tag">${tag}</td>
  133. <td class="message">${values.map((value) => formatRecordValue(value)).join('\n')}</td>
  134. </tr>
  135. </tbody>`));
  136. }
  137. }
  138. /**
  139. * Listen for *paste* events.
  140. */
  141. document.addEventListener('paste', (event) => {
  142. // If no clipboard data is available, do nothing.
  143. let text;
  144. try {
  145. text = event.clipboardData.getData('text');
  146. } catch (error) {
  147. return console.error('Could not retrieve pasted data as text:', error);
  148. }
  149. // Show log
  150. showLog(text);
  151. });
  152. /**
  153. * Listen for *drag* events.
  154. */
  155. document.addEventListener('dragover', (event) => {
  156. event.preventDefault();
  157. });
  158. document.addEventListener('dragenter', () => {
  159. elements.wrapper.className = 'drag-over';
  160. });
  161. document.addEventListener('drop', (event) => {
  162. event.preventDefault();
  163. elements.wrapper.className = '';
  164. // Read first file (if any)
  165. const files = event.dataTransfer.files;
  166. if (files.length === 0) {
  167. console.error('No files in drop event');
  168. return;
  169. }
  170. const reader = new FileReader();
  171. reader.addEventListener('load', (event) => {
  172. showLog(event.target.result);
  173. });
  174. reader.readAsText(files[0]);
  175. });