From 6823676e14fe356b5bc0c6fa722f6b5a4e533320 Mon Sep 17 00:00:00 2001 From: slava Date: Mon, 16 Jun 2025 20:55:55 +0700 Subject: [PATCH] Add UI dump visualizer with HTML, CSS, and JS components Introduced a new tool for visualizing UI dumps, enabling users to upload XML and image files for inspection. The implementation includes an HTML structure, styled with CSS, and JavaScript for processing files and rendering elements dynamically onto a canvas. This provides a lightweight and functional interface for visual analysis. --- assets/visualizer/index.html | 28 ++++++++++++++ assets/visualizer/script.js | 70 +++++++++++++++++++++++++++++++++++ assets/visualizer/style.css | 72 ++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 assets/visualizer/index.html create mode 100644 assets/visualizer/script.js create mode 100644 assets/visualizer/style.css diff --git a/assets/visualizer/index.html b/assets/visualizer/index.html new file mode 100644 index 0000000..90dc36f --- /dev/null +++ b/assets/visualizer/index.html @@ -0,0 +1,28 @@ + + + + + UI Dump Viewer + + + +
+ + +
+
+
+ screenshot +
+
+
+ + + diff --git a/assets/visualizer/script.js b/assets/visualizer/script.js new file mode 100644 index 0000000..c27511f --- /dev/null +++ b/assets/visualizer/script.js @@ -0,0 +1,70 @@ +let dumpWidth = 1080; +let dumpHeight = 1920; + +document.getElementById('xmlInput').addEventListener('change', handleXml); +document.getElementById('imgInput').addEventListener('change', handleImage); + +function handleImage(event) { + const file = event.target.files[0]; + if (!file) return; + + const url = URL.createObjectURL(file); + document.getElementById('screenshot').src = url; +} + +async function handleXml(event) { + const file = event.target.files[0]; + if (!file) return; + + const text = await file.text(); + const parser = new DOMParser(); + const xml = parser.parseFromString(text, "text/xml"); + + const nodes = Array.from(xml.querySelectorAll("node")); + const canvas = document.getElementById("canvas"); + canvas.innerHTML = ""; + + // Автоопределение размеров дампа + let maxX = 0, maxY = 0; + for (const node of nodes) { + const bounds = node.getAttribute("bounds"); + const match = bounds && bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/); + if (!match) continue; + + const [, , , x2, y2] = match.map(Number); + if (x2 > maxX) maxX = x2; + if (y2 > maxY) maxY = y2; + } + dumpWidth = maxX; + dumpHeight = maxY; + + // Масштабирование по высоте экрана с отступами + const padding = 100; + const displayHeight = window.innerHeight - padding; + const scale = displayHeight / dumpHeight; + const displayWidth = dumpWidth * scale; + + // Установка размеров canvas + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + + for (const node of nodes) { + const bounds = node.getAttribute("bounds"); + const match = bounds && bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/); + if (!match) continue; + + const [, x1, y1, x2, y2] = match.map(Number); + + const div = document.createElement("div"); + div.className = "rect"; + div.style.left = `${x1 * scale}px`; + div.style.top = `${y1 * scale}px`; + div.style.width = `${(x2 - x1) * scale}px`; + div.style.height = `${(y2 - y1) * scale}px`; + + const text = (node.getAttribute("text") || '').trim(); + if (text) div.innerText = text; + + canvas.appendChild(div); + } +} diff --git a/assets/visualizer/style.css b/assets/visualizer/style.css new file mode 100644 index 0000000..492c800 --- /dev/null +++ b/assets/visualizer/style.css @@ -0,0 +1,72 @@ +body, html { + margin: 0; + height: 100%; + font-family: sans-serif; + background-color: #f4f4f4; /* вернули светлый фон */ + color: #333; +} + +#layout { + display: flex; + height: 100vh; +} + +#sidebar { + width: 250px; + padding: 20px; + background: #ffffff; /* светлая боковая панель */ + border-right: 1px solid #ccc; + box-sizing: border-box; +} + +#sidebar h3, label { + color: #333; +} + +#sidebar input[type="file"] { + background: #fff; + color: #000; + border: 1px solid #ccc; + padding: 6px; + margin-top: 4px; + width: 100%; +} + +#content { + display: flex; + flex: 1; + align-items: center; + justify-content: space-around; + overflow: auto; + padding: 50px 20px; + box-sizing: border-box; + gap: 20px; + background-color: #f4f4f4; +} + +#canvas { + position: relative; + background: #2a2a2a; /* серый фон области дампа */ + border: 1px solid #444; + flex-shrink: 0; +} + +#screenshot-container { + flex-shrink: 0; +} + +#screenshot { + max-height: calc(100vh - 100px); + border: 1px solid #ccc; +} + +.rect { + position: absolute; + border: 1px dashed #ff9800; + background: transparent; + color: #ff9800; + font-size: 11px; + font-weight: bold; +} + +