piral-debug-utils
Version:
Utilities for debugging Piral instances.
236 lines (218 loc) • 7.18 kB
JavaScript
import { convertError } from './stack';
// Create an element with provided attributes and optional children
function h(e, attrs = {}, ...children) {
const elem = document.createElement(e);
for (const [k, v] of Object.entries(attrs)) {
elem.setAttribute(k, v);
}
elem.append(...children);
return elem;
}
// set :host styles to make playwright detect the element as visible
const templateStyle = /*css*/ `
:host {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999;
--monospace: 'SFMono-Regular', Consolas,
'Liberation Mono', Menlo, Courier, monospace;
--red: #ff5555;
--yellow: #e2aa53;
--purple: #cfa4ff;
--cyan: #2dd9da;
--dim: #c9c9c9;
--window-background: #181818;
--window-color: #d8d8d8;
}
.backdrop {
position: fixed;
z-index: 99999;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-y: scroll;
margin: 0;
background: rgba(0, 0, 0, 0.66);
}
.window {
font-family: var(--monospace);
line-height: 1.5;
max-width: 80vw;
color: var(--window-color);
box-sizing: border-box;
margin: 30px auto;
padding: 2.5vh 4vw;
position: relative;
background: var(--window-background);
border-radius: 6px 6px 8px 8px;
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
overflow: hidden;
border-top: 8px solid var(--red);
direction: ltr;
text-align: left;
}
pre {
font-family: var(--monospace);
font-size: 16px;
margin-top: 0;
margin-bottom: 1em;
overflow-x: scroll;
scrollbar-width: none;
}
pre::-webkit-scrollbar {
display: none;
}
pre.frame::-webkit-scrollbar {
display: block;
height: 5px;
}
pre.frame::-webkit-scrollbar-thumb {
background: #999;
border-radius: 5px;
}
pre.frame {
scrollbar-width: thin;
}
.message {
line-height: 1.3;
font-weight: 600;
white-space: pre-wrap;
}
.message-body {
color: var(--red);
}
.plugin {
color: var(--purple);
}
.file {
color: var(--cyan);
margin-bottom: 0;
white-space: pre-wrap;
word-break: break-all;
}
.frame {
color: var(--yellow);
}
.stack {
font-size: 13px;
color: var(--dim);
}
.tip {
font-size: 13px;
color: #999;
border-top: 1px dotted #999;
padding-top: 13px;
line-height: 1.8;
}
code {
font-size: 13px;
font-family: var(--monospace);
color: var(--yellow);
}
.file-link {
text-decoration: underline;
cursor: pointer;
}
kbd {
line-height: 1.5;
font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.75rem;
font-weight: 700;
background-color: rgb(38, 40, 44);
color: rgb(166, 167, 171);
padding: 0.15rem 0.3rem;
border-radius: 0.25rem;
border-width: 0.0625rem 0.0625rem 0.1875rem;
border-style: solid;
border-color: rgb(54, 57, 64);
border-image: initial;
}
`;
// Error Template
const createTemplate = () => h('div', { class: 'backdrop', part: 'backdrop' }, h('div', { class: 'window', part: 'window' }, h('pre', { class: 'message', part: 'message' }, h('span', { class: 'plugin', part: 'plugin' }), h('span', { class: 'message-body', part: 'message-body' })), h('pre', { class: 'file', part: 'file' }), h('pre', { class: 'frame', part: 'frame' }), h('pre', { class: 'stack', part: 'stack' }), h('div', { class: 'tip', part: 'tip' }, 'Click outside, press ', h('kbd', {}, 'Esc'), ' key, or fix the code to dismiss.', h('br'), 'You can also disable this overlay by setting ', h('code', { part: 'config-option-name' }, 'dbg:error-overlay'), ' to ', h('code', { part: 'config-option-value' }, '"off"'), ' in ', h('code', { part: 'config-file-name' }, 'sessionStorage'), '.')), h('style', {}, templateStyle));
const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d*/g;
const codeframeRE = /^(?:>?\s*\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm;
export const overlayId = 'piral-error-overlay';
if (typeof window !== 'undefined' && 'customElements' in window) {
class ErrorOverlay extends HTMLElement {
constructor(props, links = true) {
super();
const { error, pilet, errorType } = props;
this.root = this.attachShadow({ mode: 'open' });
this.root.appendChild(createTemplate());
codeframeRE.lastIndex = 0;
const hasFrame = error.frame && codeframeRE.test(error.frame);
const message = hasFrame ? error.message.replace(codeframeRE, '') : error.message;
if (pilet) {
this.text('.plugin', `[${pilet}] `);
}
this.text('.message-body', message.trim());
const [file] = (error.loc?.file || error.id || 'unknown file').split(`?`);
if (error.loc) {
this.text('.file', `${file}:${error.loc.line}:${error.loc.column}`, links);
}
else if (error.id) {
this.text('.file', file);
}
if (hasFrame) {
this.text('.frame', error.frame.trim());
}
this.text('.stack', error.stack.split('\n').slice(0, 15).join('\n'), links);
convertError(error, 0, 15).then((newStack) => {
this.text('.stack', newStack, links);
});
this.root.querySelector('.window').addEventListener('click', (e) => {
e.stopPropagation();
});
this.addEventListener('click', () => {
this.close();
});
this.closeOnEsc = (e) => {
if (e.key === 'Escape' || e.code === 'Escape') {
this.close();
}
};
this.closeOnReload = () => this.close();
window.addEventListener('pilets-reloaded', this.closeOnReload);
document.addEventListener('keydown', this.closeOnEsc);
}
text(selector, text, linkFiles = false) {
const el = this.root.querySelector(selector);
el.textContent = '';
if (linkFiles) {
let curIndex = 0;
let match;
fileRE.lastIndex = 0;
while ((match = fileRE.exec(text))) {
const { 0: file, index } = match;
if (index != null) {
const frag = text.slice(curIndex, index);
el.appendChild(document.createTextNode(frag));
const link = document.createElement('a');
link.textContent = file;
link.className = 'file-link';
link.onclick = () => {
console.log('Clicked');
};
el.appendChild(link);
curIndex += frag.length + file.length;
}
}
}
else {
el.textContent = text;
}
}
close() {
this.parentNode?.removeChild(this);
window.removeEventListener('pilets-reloaded', this.closeOnReload);
document.removeEventListener('keydown', this.closeOnEsc);
}
}
customElements.define(overlayId, ErrorOverlay);
}
//# sourceMappingURL=overlay.js.map