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, "karlkoder-playground:$1:$2", ); } 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(); } }