improve object and array previews in console
This commit is contained in:
parent
494b92ac8a
commit
245d4b2ad0
@ -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 <summary>
|
||||
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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user