(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);