(function (global) {
'use strict';
/**
* 控制台面板(壳侧):DevTools 风格渲染;原始 console 仍在游戏页输出。
* @constructor
*/
function ConsolePanel() {
this.el = null;
this.list = null;
this.filter = 'all';
this.query = '';
this.buffer = [];
this.capacity = 500;
this._bridge = null;
this._expanded = {};
this._onListClick = this._onListClick.bind(this);
}
ConsolePanel.prototype.setBridge = function (bridge) {
this._bridge = bridge;
};
ConsolePanel.prototype.mount = function () {
var self = this;
var root = document.createElement('div');
root.className = 'pw-panel-inner pw-console';
root.innerHTML =
'
' +
'
控制台' +
'
' +
'' +
'' +
'' +
'
' +
'';
this.el = root;
this.list = root.querySelector('#pw-console-list');
this.list.addEventListener('click', this._onListClick);
root.querySelector('#pw-console-level').addEventListener('change', function (ev) {
self.filter = ev.target.value;
self.render();
});
root.querySelector('#pw-console-q').addEventListener('input', function (ev) {
self.query = ev.target.value || '';
self.render();
});
root.querySelector('#pw-console-clear').addEventListener('click', function () {
self.buffer.length = 0;
self._expanded = {};
self.render();
});
return root;
};
/**
* @param {Array} items Agent 序列化后的富日志
*/
ConsolePanel.prototype.applyBatch = function (items) {
if (!items || !items.length) return;
for (var i = 0; i < items.length; i++) {
var it = items[i];
var key = it.level + '|' + (it.plain || '');
var last = this.buffer.length ? this.buffer[this.buffer.length - 1] : null;
if (last && last._key === key) {
last.count = (last.count || 1) + 1;
} else {
it.count = 1;
it._key = key;
if (!it.children) it.children = {};
this.buffer.push(it);
}
}
while (this.buffer.length > this.capacity) {
var removed = this.buffer.shift();
if (removed) delete this._expanded[removed.id];
}
this.render();
};
ConsolePanel.prototype._onListClick = function (ev) {
var toggle = ev.target.closest && ev.target.closest('[data-expand]');
if (!toggle) return;
var oid = toggle.getAttribute('data-oid');
if (!oid) return;
var self = this;
var open = !this._expanded[oid];
this._expanded[oid] = open;
if (open && this._bridge) {
this._bridge.call('console.inspect', { oid: oid, path: '' }).then(function (res) {
self._storeChildren(oid, res && res.props);
self.render();
}).catch(function () {
self.render();
});
} else {
this.render();
}
};
ConsolePanel.prototype._storeChildren = function (oid, props) {
if (!props) return;
for (var i = 0; i < this.buffer.length; i++) {
var item = this.buffer[i];
if (!item.children) item.children = {};
item.children[oid] = props;
}
};
ConsolePanel.prototype.render = function () {
if (!this.list) return;
var q = this.query.toLowerCase();
var html = '';
var any = false;
for (var i = 0; i < this.buffer.length; i++) {
var item = this.buffer[i];
if (this.filter !== 'all' && item.level !== this.filter) continue;
if (q && String(item.plain || '').toLowerCase().indexOf(q) < 0) continue;
any = true;
html += this._renderRow(item);
}
this.list.innerHTML = any ? html : '暂无日志(打开本面板后开始捕获;原始输出仍在浏览器控制台)
';
this.list.scrollTop = this.list.scrollHeight;
};
ConsolePanel.prototype._renderRow = function (item) {
var icon = '';
if (item.level === 'warn') icon = '';
else if (item.level === 'error') icon = '';
else if (item.level === 'info') icon = '';
var body = this._renderParts(item.parts || [], item);
var count = item.count > 1
? '' + item.count + ''
: '';
return (
'' +
icon + count +
'
' + body + '
' +
'
'
);
};
ConsolePanel.prototype._renderParts = function (parts, item) {
var html = '';
for (var i = 0; i < parts.length; i++) {
if (i) html += ' ';
html += this._renderPart(parts[i], item);
}
return html;
};
ConsolePanel.prototype._renderPart = function (part, item) {
if (!part) return '';
var kind = part.kind;
if (kind === 'styled') {
var css = sanitizeCss(part.css || '');
var styleAttr = css ? ' style="' + escapeAttr(css) + '"' : '';
return '' + escapeHtml(part.text || '') + '';
}
if (kind === 'text') return '' + escapeHtml(part.text || '') + '';
if (kind === 'string') return '"' + escapeHtml(part.text || '') + '"';
if (kind === 'number' || kind === 'bigint') {
return '' + escapeHtml(String(part.value)) + (kind === 'bigint' ? 'n' : '') + '';
}
if (kind === 'boolean') return '' + String(part.value) + '';
if (kind === 'null') return 'null';
if (kind === 'undefined') return 'undefined';
if (kind === 'symbol') return '' + escapeHtml(part.text || '') + '';
if (kind === 'function') {
return 'ƒ ' + escapeHtml(part.name || 'anonymous') + '()';
}
if (kind === 'error') {
var stack = part.stack ? '' + escapeHtml(part.stack) + '
' : '';
return '' + escapeHtml((part.name || 'Error') + ': ' + (part.message || '')) + '' + stack;
}
if (kind === 'object') return this._renderObject(part, item);
return '' + escapeHtml(part.plain || '') + '';
};
ConsolePanel.prototype._renderObject = function (part, item) {
return this._renderPartNested(part, item);
};
ConsolePanel.prototype._renderPartNested = function (part, item) {
if (!part) return '';
if (part.kind !== 'object') return this._renderPart(part, item);
var oid = part.oid;
var open = !!this._expanded[oid];
var head =
'' +
'' +
'' + escapeHtml(part.preview || part.ctor || 'Object') + '' +
'';
if (!open) return head;
var props = (item.children && item.children[oid]) || [];
var rows = '';
for (var i = 0; i < props.length; i++) {
var pr = props[i];
if (pr.key === '…') {
rows += '…
';
continue;
}
rows +=
'' +
'' + escapeHtml(pr.key) + '' +
': ' +
this._renderPartNested(pr.part, item) +
'
';
}
return head + '' + rows + '
';
};
function sanitizeCss(css) {
if (typeof css !== 'string' || !css) return '';
if (/url\s*\(|expression\s*\(|javascript:|@import|behavior\s*:/i.test(css)) return '';
var allow = {
color: 1, background: 1, 'background-color': 1, 'font-weight': 1,
'font-style': 1, 'font-size': 1, 'text-decoration': 1, 'text-shadow': 1,
'line-height': 1, opacity: 1,
};
var parts = css.split(';');
var out = [];
for (var i = 0; i < parts.length; i++) {
var p = parts[i].trim();
if (!p) continue;
var colon = p.indexOf(':');
if (colon <= 0) continue;
var prop = p.slice(0, colon).trim().toLowerCase();
var val = p.slice(colon + 1).trim();
if (!allow[prop] || !val) continue;
if (/url\s*\(|expression|javascript:/i.test(val)) continue;
out.push(prop + ': ' + val);
}
return out.join('; ');
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
function escapeAttr(s) {
return String(s)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(//g, '>');
}
global.PWConsolePanel = ConsolePanel;
})(window);