156 lines
4.5 KiB
JavaScript
156 lines
4.5 KiB
JavaScript
export class PlaygroundConsole {
|
|
constructor(elem, editor) {
|
|
this.elem = elem;
|
|
|
|
// Used within error stack traces
|
|
globalThis.gotoLine = (line, col) => {
|
|
editor.gotoLine(line, col, true);
|
|
editor.focus();
|
|
};
|
|
}
|
|
|
|
formatStacktrace(stack) {
|
|
return stack
|
|
.replaceAll(globalThis.origin + "/", "")
|
|
.replace(
|
|
/data:text\/javascript;charset=utf-8,[^:]+:(\d+):(\d+)/g,
|
|
"<a href='javascript:gotoLine($1,$2)'>karlkoder-playground:$1:$2</a>",
|
|
);
|
|
}
|
|
|
|
getTypeName(arg) {
|
|
if (arg instanceof Error) {
|
|
return "error";
|
|
}
|
|
|
|
if (arg === null) {
|
|
return "null";
|
|
}
|
|
|
|
return typeof arg;
|
|
}
|
|
|
|
addKeyValue(_entryType, parent, property, arg) {
|
|
if (property) {
|
|
const keyEl = document.createElement("span");
|
|
keyEl.className = property === "__proto__" ? "prototype" : "property";
|
|
keyEl.textContent = property + ": ";
|
|
parent.appendChild(keyEl);
|
|
}
|
|
|
|
const argString = typeof arg === "function" ? `function ${arg.name}()` : `${arg}`;
|
|
|
|
const value = document.createElement("span");
|
|
value.textContent = argString;
|
|
value.dataset.type = this.getTypeName(arg);
|
|
parent.appendChild(value);
|
|
}
|
|
|
|
addEntry(entryType, property, parent, ...args) {
|
|
for (const arg of args) {
|
|
// For objects, show a collapsed list of properties
|
|
if (typeof arg === "object" && arg !== null) {
|
|
const summary = document.createElement("summary");
|
|
summary.className = entryType;
|
|
summary.dataset.type = "object";
|
|
summary.style.marginLeft = "-1rem";
|
|
|
|
this.addKeyValue(entryType, summary, property, arg);
|
|
|
|
const details = document.createElement("details");
|
|
details.className = entryType;
|
|
details.style.marginLeft = "1rem";
|
|
details.open = entryType === "dir"; // Expand if console.dir() is used
|
|
details.appendChild(summary);
|
|
|
|
if (arg instanceof Error) {
|
|
// Add error stack trace
|
|
const el = document.createElement("p");
|
|
el.innerHTML = this.formatStacktrace(arg.stack);
|
|
details.appendChild(el);
|
|
} else {
|
|
// Add object properties
|
|
for (const prop of Object.getOwnPropertyNames(arg)) {
|
|
this.addEntry(
|
|
entryType,
|
|
prop,
|
|
details,
|
|
arg.__lookupGetter__(prop) ?? arg[prop],
|
|
);
|
|
}
|
|
|
|
// Add prototype if one exists
|
|
const prototype = Object.getPrototypeOf(arg);
|
|
if (prototype && Object.getOwnPropertyNames(prototype).length > 0) {
|
|
this.addEntry(entryType, "__proto__", details, prototype);
|
|
}
|
|
}
|
|
|
|
parent.appendChild(details);
|
|
|
|
return;
|
|
}
|
|
|
|
// For non-object values, show it directly
|
|
const wrapper = document.createElement("p");
|
|
wrapper.className = entryType;
|
|
this.addKeyValue(entryType, wrapper, property, arg);
|
|
parent.appendChild(wrapper);
|
|
}
|
|
}
|
|
|
|
addTopLevelEntry(entryType, ...args) {
|
|
this.addEntry(entryType, "", this.elem, ...args);
|
|
|
|
this.elem.scrollTop = this.elem.scrollHeight;
|
|
}
|
|
|
|
addInput() {
|
|
this.addTopLevelEntry("input", ...arguments);
|
|
}
|
|
|
|
getInterface() {
|
|
return new PlaygroundConsoleInterface(this);
|
|
}
|
|
|
|
clear() {
|
|
this.elem.textContent = "";
|
|
}
|
|
}
|
|
|
|
class PlaygroundConsoleInterface {
|
|
#console;
|
|
|
|
constructor(console) {
|
|
this.#console = console;
|
|
}
|
|
|
|
log() {
|
|
this.#console.addTopLevelEntry("log", ...arguments);
|
|
}
|
|
|
|
dir() {
|
|
this.#console.addTopLevelEntry("dir", ...arguments);
|
|
}
|
|
|
|
debug() {
|
|
this.#console.addTopLevelEntry("debug", ...arguments);
|
|
}
|
|
|
|
info() {
|
|
this.#console.addTopLevelEntry("info", ...arguments);
|
|
}
|
|
|
|
warn() {
|
|
this.#console.addTopLevelEntry("warn", ...arguments);
|
|
}
|
|
|
|
error() {
|
|
this.#console.addTopLevelEntry("error", ...arguments);
|
|
}
|
|
|
|
clear() {
|
|
this.#console.clear();
|
|
}
|
|
}
|