diff --git a/src/playground_console.js b/src/playground_console.js index 2e3e71c..7e48f09 100644 --- a/src/playground_console.js +++ b/src/playground_console.js @@ -30,107 +30,138 @@ export class PlaygroundConsole { return typeof arg; } - stringifyObject(obj) { - return `Object { ${Object.keys(obj).join(", ")} }`.replace(/\s\s/, " "); - } - getValueString(value) { if (typeof value === "function") { return `function ${value.name}()`; } + if (Array.isArray(value) || ArrayBuffer.isView(value) && value !== Array.prototype) { + let previewElems = []; + for (let i = 0; i < value.length; i++) { + previewElems.push(this.getValueString(value[i]) + this.getValueSuffix(value[i])); + } + + return `${value.constructor.name}(${value.length}) [ ${previewElems.join(", ")}`; + } + if (typeof value === "object" && value !== null) { - return this.stringifyObject(value).length < 50 - ? this.stringifyObject(value) - : "Object { ... }"; + return `Object { ${Object.keys(value).join(", ")}`.trim(); } return `${value}`; } + getValueSuffix(value) { + if (Array.isArray(value) || ArrayBuffer.isView(value) && value !== Array.prototype) { + return " ]"; + } + + if (typeof value === "object" && value !== null) { + return " }"; + } + + return ""; + } + addKeyValue(_entryType, parent, property, value) { if (property) { const keyEl = document.createElement("span"); + keyEl.style.paddingRight = "0.5rem"; keyEl.className = property === "__proto__" ? "prototype" : "property"; keyEl.textContent = property + ": "; parent.appendChild(keyEl); } const valueEl = document.createElement("span"); + valueEl.style.display = "list-item"; // Re-add arrow that was removed by applying flex on + valueEl.style.overflowX = "hidden"; + valueEl.style.textOverflow = "ellipsis"; valueEl.textContent = this.getValueString(value); valueEl.dataset.type = this.getTypeName(value); parent.appendChild(valueEl); - } - 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) { - // On Chrome, the first line of the stack trace is the error message repeated - if (globalThis.chrome) { - const trace = arg.stack.split("\n"); - trace.shift(); - arg.stack = trace.join("\n"); - } - - 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 details; - } - - // For non-object values, show it directly - const wrapper = document.createElement("p"); - wrapper.className = entryType; - this.addKeyValue(entryType, wrapper, property, arg); - parent.appendChild(wrapper); - - return wrapper; + // Suffix is added as a separate element so the css ellipsis will be applied inside the brackets + if (this.getValueSuffix(value)) { + const suffixEl = document.createElement("span"); + suffixEl.textContent = this.getValueSuffix(value); + suffixEl.style.paddingLeft = "0.5rem"; + parent.appendChild(suffixEl); } } + addEntry(entryType, property, parent, arg) { + // For objects, show a collapsed list of properties + if (typeof arg === "object" && arg !== null) { + const summary = document.createElement("summary"); + summary.style.display = "flex"; + summary.style.whiteSpace = "nowrap"; + 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) { + // On Chrome, the first line of the stack trace is the error message repeated + if (globalThis.chrome) { + const trace = arg.stack.split("\n"); + trace.shift(); + arg.stack = trace.join("\n"); + } + + 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 details; + } + + // For non-object values, show it directly + const wrapper = document.createElement("p"); + wrapper.style.display = "flex"; + wrapper.style.whiteSpace = "nowrap"; + wrapper.className = entryType; + this.addKeyValue(entryType, wrapper, property, arg); + parent.appendChild(wrapper); + + return wrapper; + } + addTopLevelEntry(entryType, ...args) { - const elem = this.addEntry(entryType, "", this.elem, ...args); + for (const arg of args) { + const elem = this.addEntry(entryType, "", this.elem, ...args); - elem.addEventListener("contextmenu", (ev) => { - // TODO add context menu + elem.addEventListener("contextmenu", (ev) => { + // TODO add context menu - ev.preventDefault(); - }); + ev.preventDefault(); + }); + } this.elem.scrollTop = this.elem.scrollHeight; }