|
Поговорим о...
|
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для правильного форматирования SVG контента function formatSvgContent(svgContent) { // В рабочем примере SVG имеет специфичную структуру с дополнительными атрибутами // Заменяем корневой тег SVG на тот, который используется в рабочем примере let formatted = svgContent.replace( /<svg[^>]*>/, `<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">` ); // Добавляем метаданные, которые есть в рабочем примере formatted = formatted.replace( /<defs id="defs1"\/>/, `<metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id="defs1"/>` ); // В рабочем примере используются одинарные кавычки для XML декларации // и двойные для атрибутов SVG formatted = formatted.replace(/<?xml version="1.0" encoding="UTF-8"?>/, `<?xml version='1.0' encoding='UTF-8'?>`); // В рабочем примере теги line имеют другой формат атрибутов formatted = formatted.replace( /<line ([^>]*)\/>/g, '<line $1/>' ); // Экранируем только амперсанды formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&'); return formatted; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное форматирование SVG для XML svgContent = formatSvgContent(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в рабочем примере return { "color": "#ffffffff", "data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>
|
нет код должен быть таким "data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"1763111511311\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1000 Лед.svg\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?>
<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">
<metadata>
<rdf:RDF>
<cc:Work>
<dc:title>ИСИХОГИ_Литология</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs1"/>
<g id="layer1">
<line id="19935" x1="74" y1="54" y2="54" stroke-width="2" style="stroke:#000000;" x2="127"/>
<line id="19939" x1="144" y1="112" y2="91" stroke-width="2" style="stroke:#000000;" x2="144"/>
<line id="35611" x1="127" y1="53" y2="75" stroke-width="2" style="stroke:#000000;" x2="127"/>
<line id="35612" x1="143" y1="90" y2="91" stroke-width="2" style="stroke:#000000;" x2="198"/>
<line id="35613" x1="199" y1="90" y2="112" stroke-width="2" style="stroke:#000000;" x2="198"/>
<line id="35614" x1="143" y1="17" y2="17" stroke-width="2" style="stroke:#000000;" x2="199"/>
<line id="35615" x1="144" y1="17" y2="38" stroke-width="2" style="stroke:#000000;" x2="143"/>
<line id="35618" x1="0" y1="17" y2="17" stroke-width="2" style="stroke:#000000;" x2="58"/>
<line id="35620" x1="22" y1="18" y2="18" stroke-width="2" style="stroke:#000000;" x2="22"/>
<line id="35621" x1="56" y1="16" y2="37" stroke-width="2" style="stroke:#000000;" x2="57"/>
<line id="35622" x1="1" y1="18" y2="37" stroke-width="2" style="stroke:#000000;" x2="0"/>
<line id="35625" x1="2" y1="89" y2="89" stroke-width="2" style="stroke:#000000;" x2="56"/>
<line id="35626" x1="1" y1="89" y2="108" stroke-width="2" style="stroke:#000000;" x2="0"/>
<line id="35627" x1="56" y1="89" y2="108" stroke-width="2" style="stroke:#000000;" x2="55"/>
<line id="28652" x1="22" y1="110" y2="110" stroke-width="2" style="stroke:#000000;" x2="22"/>
<line id="28672" x1="321" y1="58" y2="79" stroke-width="2" style="stroke:#000000;" x2="320"/>
<line id="40169" x1="72" y1="124" y2="125" stroke-width="2" style="stroke:#000000;" x2="126"/>
<line id="40166" x1="199" y1="17" y2="38" stroke-width="2" style="stroke:#000000;" x2="198"/>
<line id="40167" x1="74" y1="53" y2="74" stroke-width="2" style="stroke:#000000;" x2="73"/>
<line id="40170" x1="71" y1="124" y2="145" stroke-width="2" style="stroke:#000000;" x2="72"/>
<line id="40171" x1="126" y1="125" y2="145" stroke-width="2" style="stroke:#000000;" x2="127"/>
<line id="36675" x1="215" y1="55" y2="54" stroke-width="2" style="stroke:#000000;" x2="271"/>
<line id="36676" x1="217" y1="126" y2="125" stroke-width="2" style="stroke:#000000;" x2="268"/>
<line id="36677" x1="216" y1="125" y2="144" stroke-width="2" style="stroke:#000000;" x2="217"/>
<line id="36678" x1="269" y1="126" y2="143" stroke-width="2" style="stroke:#000000;" x2="269"/>
<line id="36679" x1="216" y1="54" y2="74" stroke-width="2" style="stroke:#000000;" x2="217"/>
<line id="36680" x1="271" y1="53" y2="74" stroke-width="2" style="stroke:#000000;" x2="270"/>
</g>
</svg>
\"/>\n</StyledFill>\n",
а у тебя получается такой "data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"1763113124411\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1000 Лед.svg\" svgContent=\"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"svg1\" height=\"142px\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" viewBox=\"0 0 282 142\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:cc=\"http://creativecommons.org/ns#\" width=\"282px\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"1.1\">\n <metadata> <rdf:RDF> <cc:Work> <dc:title>ИСИХОГИ_Литология</dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs1\"/>\n <g id=\"layer1\">\n <line id=\"19935\" x1=\"74\" y1=\"54\" x2=\"127\" y2=\"54\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"19939\" x1=\"144\" y1=\"112\" x2=\"144\" y2=\"91\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35611\" x1=\"127\" y1=\"53\" x2=\"127\" y2=\"75\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35612\" x1=\"143\" y1=\"90\" x2=\"198\" y2=\"91\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35613\" x1=\"199\" y1=\"90\" x2=\"198\" y2=\"112\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35614\" x1=\"143\" y1=\"17\" x2=\"199\" y2=\"17\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35615\" x1=\"144\" y1=\"17\" x2=\"143\" y2=\"38\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35618\" x1=\"0\" y1=\"17\" x2=\"58\" y2=\"17\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35620\" x1=\"22\" y1=\"18\" x2=\"22\" y2=\"18\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35621\" x1=\"56\" y1=\"16\" x2=\"57\" y2=\"37\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35622\" x1=\"1\" y1=\"18\" x2=\"0\" y2=\"37\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35625\" x1=\"2\" y1=\"89\" x2=\"56\" y2=\"89\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35626\" x1=\"1\" y1=\"89\" x2=\"0\" y2=\"108\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"35627\" x1=\"56\" y1=\"89\" x2=\"55\" y2=\"108\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"28652\" x1=\"22\" y1=\"110\" x2=\"22\" y2=\"110\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"28672\" x1=\"321\" y1=\"58\" x2=\"320\" y2=\"79\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40169\" x1=\"72\" y1=\"124\" x2=\"126\" y2=\"125\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40166\" x1=\"199\" y1=\"17\" x2=\"198\" y2=\"38\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40167\" x1=\"74\" y1=\"53\" x2=\"73\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40170\" x1=\"71\" y1=\"124\" x2=\"72\" y2=\"145\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"40171\" x1=\"126\" y1=\"125\" x2=\"127\" y2=\"145\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36675\" x1=\"215\" y1=\"55\" x2=\"271\" y2=\"54\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36676\" x1=\"217\" y1=\"126\" x2=\"268\" y2=\"125\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36677\" x1=\"216\" y1=\"125\" x2=\"217\" y2=\"144\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36678\" x1=\"269\" y1=\"126\" x2=\"269\" y2=\"143\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36679\" x1=\"216\" y1=\"54\" x2=\"217\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/><line id=\"36680\" x1=\"271\" y1=\"53\" x2=\"270\" y2=\"74\" stroke-width=\"2\" style=\"stroke:#000000;\"/></g>\n</svg>\n\"/>\n</StyledFill>\n",
и с таким неработает он непрогружается а вот сам файл свг из него должен получится <?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="282px" height="142px" viewBox="0 0 282 142" version="1.1" id="svg1"> <defs id="defs1"/> <g id="layer1"> <line id="19935" x1="74" y1="54" x2="127" y2="54" stroke-width="2" style="stroke:#000000;"/><line id="19939" x1="144" y1="112" x2="144" y2="91" stroke-width="2" style="stroke:#000000;"/><line id="35611" x1="127" y1="53" x2="127" y2="75" stroke-width="2" style="stroke:#000000;"/><line id="35612" x1="143" y1="90" x2="198" y2="91" stroke-width="2" style="stroke:#000000;"/><line id="35613" x1="199" y1="90" x2="198" y2="112" stroke-width="2" style="stroke:#000000;"/><line id="35614" x1="143" y1="17" x2="199" y2="17" stroke-width="2" style="stroke:#000000;"/><line id="35615" x1="144" y1="17" x2="143" y2="38" stroke-width="2" style="stroke:#000000;"/><line id="35618" x1="0" y1="17" x2="58" y2="17" stroke-width="2" style="stroke:#000000;"/><line id="35620" x1="22" y1="18" x2="22" y2="18" stroke-width="2" style="stroke:#000000;"/><line id="35621" x1="56" y1="16" x2="57" y2="37" stroke-width="2" style="stroke:#000000;"/><line id="35622" x1="1" y1="18" x2="0" y2="37" stroke-width="2" style="stroke:#000000;"/><line id="35625" x1="2" y1="89" x2="56" y2="89" stroke-width="2" style="stroke:#000000;"/><line id="35626" x1="1" y1="89" x2="0" y2="108" stroke-width="2" style="stroke:#000000;"/><line id="35627" x1="56" y1="89" x2="55" y2="108" stroke-width="2" style="stroke:#000000;"/><line id="28652" x1="22" y1="110" x2="22" y2="110" stroke-width="2" style="stroke:#000000;"/><line id="28672" x1="321" y1="58" x2="320" y2="79" stroke-width="2" style="stroke:#000000;"/><line id="40169" x1="72" y1="124" x2="126" y2="125" stroke-width="2" style="stroke:#000000;"/><line id="40166" x1="199" y1="17" x2="198" y2="38" stroke-width="2" style="stroke:#000000;"/><line id="40167" x1="74" y1="53" x2="73" y2="74" stroke-width="2" style="stroke:#000000;"/><line id="40170" x1="71" y1="124" x2="72" y2="145" stroke-width="2" style="stroke:#000000;"/><line id="40171" x1="126" y1="125" x2="127" y2="145" stroke-width="2" style="stroke:#000000;"/><line id="36675" x1="215" y1="55" x2="271" y2="54" stroke-width="2" style="stroke:#000000;"/><line id="36676" x1="217" y1="126" x2="268" y2="125" stroke-width="2" style="stroke:#000000;"/><line id="36677" x1="216" y1="125" x2="217" y2="144" stroke-width="2" style="stroke:#000000;"/><line id="36678" x1="269" y1="126" x2="269" y2="143" stroke-width="2" style="stroke:#000000;"/><line id="36679" x1="216" y1="54" x2="217" y2="74" stroke-width="2" style="stroke:#000000;"/><line id="36680" x1="271" y1="53" x2="270" y2="74" stroke-width="2" style="stroke:#000000;"/></g> </svg>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для правильного форматирования SVG контента function formatSvgContent(svgContent) { // Убираем лишние пробелы и переносы строк, но сохраняем структуру let formatted = svgContent .replace(/\s+/g, ' ') .trim(); // В рабочем примере XML декларация использует одинарные кавычки formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>"); // Экранируем только амперсанды formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&'); return formatted; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное форматирование SVG для XML svgContent = formatSvgContent(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в рабочем примере return { "color": "#ffffffff", "data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>Добавлено (2025-11-14, 16:51) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для правильного форматирования SVG контента function formatSvgContent(svgContent) { // Убираем лишние пробелы и переносы строк, но сохраняем структуру let formatted = svgContent .replace(/\s+/g, ' ') .trim(); // В рабочем примере XML декларация использует одинарные кавычки formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>"); // Экранируем только амперсанды formatted = formatted.replace(/&(?!amp;|lt;|gt;|quot;|#)/g, '&'); return formatted; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное форматирование SVG для XML svgContent = formatSvgContent(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в рабочем примере return { "color": "#ffffffff", "data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html> Добавлено (2025-11-14, 16:59) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файе</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для правильного форматирования SVG контента function formatSvgContent(svgContent) { // Убираем лишние пробелы и переносы строк let formatted = svgContent.replace(/\s+/g, ' ').trim(); // В рабочем примере XML декларация использует одинарные кавычки formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>"); // Экранируем все специальные символы для XML как в рабочем примере formatted = formatted .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); return formatted; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное форматирование SVG для XML svgContent = formatSvgContent(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в рабочем примере return { "color": "#ffffffff", "data": `<StyledFill name="" group="" brushColor="#000000" objectName="" id="${id}" penColor="#000000">\n <SvgFill penWidth="0.4" objectName="" patternWidth="20" lineWidth="0.4" svgFilePath="${fullSvgFilePath}" svgContent="${svgContent}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html> Добавлено (2025-11-14, 17:07) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для правильного экранирования SVG контента function formatSvgContent(svgContent) { // Убираем лишние пробелы и переносы строк let formatted = svgContent.replace(/\s+/g, ' ').trim(); // Заменяем XML декларацию на версию с одинарными кавычками formatted = formatted.replace(/<\?xml version="1.0" encoding="UTF-8"\?>/g, "<?xml version='1.0' encoding='UTF-8'?>"); // Правильно экранируем специальные символы для XML formatted = formatted .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); return formatted; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное форматирование SVG для XML svgContent = formatSvgContent(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в рабочем примере return { "color": "#ffffffff", "data": "<StyledFill name=\"\" group=\"\" brushColor=\"#000000\" objectName=\"\" id=\"" + id + "\" penColor=\"#000000\">\n <SvgFill penWidth=\"0.4\" objectName=\"\" patternWidth=\"20\" lineWidth=\"0.4\" svgFilePath=\"" + fullSvgFilePath + "\" svgContent=\"" + svgContent + "\"/>\n</StyledFill>\n", "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = '<div class="message ' + type + '">' + message + '</div>'; // Автоматическое скрытие сообщения через 5 секунд setTimeout(function() { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = "1300 Суглинок\n1700 Илы\n1800 Торф\n1400 Супесь\n2300 Аргиллит\n2400 Алевролит"; }; </script> </body> </html>
|
|
|
отлично работает, теперь все работает как надо, большая часть svg теперь корректно отображается,
но есть некоторые которые не отображаются к примеру вот это изображение <?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="282px" height="142px" viewBox="0 0 282 142" version="1.1" id="svg1"> <defs id="defs1"/> <g id="layer1"> <path id="35707" d="M 221 30.5 A 9.75 10.5 0 0 0 240.5 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="41" d="M 240.5 30.5 A 9.75 10.5 0 0 1 260 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="35708" d="M 21 31 A 9.75 11 0 0 0 40.5 31" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="42" d="M 40.5 31 A 9.75 11 0 0 1 60 31" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28684" d="M 291 19 A 14 11 0 0 0 319 19" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="23" d="M 319 19 A 14 11 0 0 1 347 19" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28686" d="M 294 100.5 A 14 10.5 0 0 0 322 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="24" d="M 322 100.5 A 14 10.5 0 0 1 350 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28792" d="M 120 30 A 10 10 0 0 0 140 30" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="26" d="M 140 30 A 10 10 0 0 1 160 30" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28794" d="M 74 69.5 A 9.75 9.5 0 0 0 93.5 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28" d="M 93.5 69.5 A 9.75 9.5 0 0 1 113 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28795" d="M 169 70 A 10.25 10 0 0 0 189.5 70" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="29" d="M 189.5 70 A 10.25 10 0 0 1 210 70" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28797" d="M 21 109.5 A 9.75 8.5 0 0 0 40.5 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="31" d="M 40.5 109.5 A 9.75 8.5 0 0 1 60 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28798" d="M 120 108 A 10 9 0 0 0 140 108" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="32" d="M 140 108 A 10 9 0 0 1 160 108" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="28799" d="M 220 109 A 9.25 9 0 0 0 238.5 109" stroke-width="NA" style="fill:none;stroke:#000000;"/><path id="33" d="M 238.5 109 A 9.25 9 0 0 1 257 109" stroke-width="NA" style="fill:none;stroke:#000000;"/><ellipse id="3324" cx="319" cy="61" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34913" cx="96" cy="36" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34914" cx="194" cy="36" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34916" cx="46" cy="79" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34917" cx="146" cy="78" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34918" cx="246" cy="77" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34919" cx="97" cy="118" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/><ellipse id="34920" cx="188" cy="111" rx="4" ry="4" stroke-width="2" fill="#000000" stroke="#000000"/></g> </svg>
получается для этого именно файла вот такое (хотя другие работают корректно):
"data": "<StyledFill id=\"1763342045616\" name=\"\" objectName=\"\" brushColor=\"#000000\" penColor=\"#000000\" group=\"\">\n <SvgFill patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?>
<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">
<metadata>
<rdf:RDF>
<cc:Work>
<dc:title>ИСИХОГИ_Литология</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs1"/>
<g id="layer1">
</g>
</svg>
\" lineWidth=\"0.4\" objectName=\"\" penWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1300 Суглинок.svg\"/>\n</StyledFill>\n", "desc": "Суглинок", "value": 1300
а должен получится таким: "data": "<StyledFill name=\"\" brushColor=\"#000000\" penColor=\"#000000\" id=\"1763340634935\" objectName=\"\" group=\"\">\n <SvgFill penWidth=\"0.4\" lineWidth=\"0.4\" svgFilePath=\"C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/1300 Суглинок.svg\" patternWidth=\"20\" svgContent=\"<?xml version='1.0' encoding='UTF-8'?>
<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">
<metadata>
<rdf:RDF>
<cc:Work>
<dc:title>ИСИХОГИ_Литология</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs1"/>
<g id="layer1">
<path id="35707" d="M 221 30.5 A 9.75 10.5 0 0 0 240.5 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="41" d="M 240.5 30.5 A 9.75 10.5 0 0 1 260 30.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="35708" d="M 21 31 A 9.75 11 0 0 0 40.5 31" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="42" d="M 40.5 31 A 9.75 11 0 0 1 60 31" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28684" d="M 291 19 A 14 11 0 0 0 319 19" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="23" d="M 319 19 A 14 11 0 0 1 347 19" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28686" d="M 294 100.5 A 14 10.5 0 0 0 322 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="24" d="M 322 100.5 A 14 10.5 0 0 1 350 100.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28792" d="M 120 30 A 10 10 0 0 0 140 30" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="26" d="M 140 30 A 10 10 0 0 1 160 30" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28794" d="M 74 69.5 A 9.75 9.5 0 0 0 93.5 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28" d="M 93.5 69.5 A 9.75 9.5 0 0 1 113 69.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28795" d="M 169 70 A 10.25 10 0 0 0 189.5 70" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="29" d="M 189.5 70 A 10.25 10 0 0 1 210 70" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28797" d="M 21 109.5 A 9.75 8.5 0 0 0 40.5 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="31" d="M 40.5 109.5 A 9.75 8.5 0 0 1 60 109.5" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28798" d="M 120 108 A 10 9 0 0 0 140 108" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="32" d="M 140 108 A 10 9 0 0 1 160 108" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="28799" d="M 220 109 A 9.25 9 0 0 0 238.5 109" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<path id="33" d="M 238.5 109 A 9.25 9 0 0 1 257 109" stroke-width="NA" style="fill:none;stroke:#000000;"/>
<ellipse id="3324" cx="319" fill="#000000" rx="4" cy="61" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34913" cx="96" fill="#000000" rx="4" cy="36" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34914" cx="194" fill="#000000" rx="4" cy="36" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34916" cx="46" fill="#000000" rx="4" cy="79" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34917" cx="146" fill="#000000" rx="4" cy="78" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34918" cx="246" fill="#000000" rx="4" cy="77" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34919" cx="97" fill="#000000" rx="4" cy="118" stroke-width="2" stroke="#000000" ry="4"/>
<ellipse id="34920" cx="188" fill="#000000" rx="4" cy="111" stroke-width="2" stroke="#000000" ry="4"/>
</g>
</svg>
\" objectName=\"\"/>\n</StyledFill>\n", "desc": "Сглинок", "value": 1300
также смотри в каждом svg прописывается patternWidth=\"20\" - это размер маркера - добавь возможность перед генерацией выставлять размер для всех svg
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; }
/* Новые стили для JSON отображения */ #jsonContent { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; padding: 15px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.4; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div>
<div class="form-group"> <label>Размер patternWidth для SVG:</label> <input type="number" id="patternWidth" value="20" min="1" max="100"> <div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const patternWidthInput = document.getElementById('patternWidth'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для преобразования SVG в формат как в оригинале function convertSvgToOriginalFormat(svgContent) { // Создаем временный div для парсинга SVG const tempDiv = document.createElement('div'); tempDiv.innerHTML = svgContent; const svgElement = tempDiv.querySelector('svg'); if (!svgElement) return svgContent; // Создаем SVG с атрибутами как в оригинале let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`; // Добавляем все path элементы const paths = svgElement.querySelectorAll('path'); paths.forEach(path => { const id = path.getAttribute('id') || ''; const d = path.getAttribute('d') || ''; const strokeWidth = path.getAttribute('stroke-width') || ''; const style = path.getAttribute('style') || ''; originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`; }); // Добавляем все line элементы в формате оригинала const lines = svgElement.querySelectorAll('line'); lines.forEach(line => { const id = line.getAttribute('id') || ''; const x1 = line.getAttribute('x1') || ''; const y1 = line.getAttribute('y1') || ''; const x2 = line.getAttribute('x2') || ''; const y2 = line.getAttribute('y2') || ''; const strokeWidth = line.getAttribute('stroke-width') || ''; const style = line.getAttribute('style') || ''; originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`; }); // Добавляем все ellipse элементы в формате оригинала const ellipses = svgElement.querySelectorAll('ellipse'); ellipses.forEach(ellipse => { const id = ellipse.getAttribute('id') || ''; const cx = ellipse.getAttribute('cx') || ''; const cy = ellipse.getAttribute('cy') || ''; const rx = ellipse.getAttribute('rx') || ''; const ry = ellipse.getAttribute('ry') || ''; const strokeWidth = ellipse.getAttribute('stroke-width') || ''; const fill = ellipse.getAttribute('fill') || ''; const stroke = ellipse.getAttribute('stroke') || ''; originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`; }); originalSvg += ` </g>\n</svg>\n`; return originalSvg; } // Функция для правильного экранирования SVG контента для XML function escapeSvgForXml(svgContent) { // Сначала преобразуем SVG в формат оригинала let convertedSvg = convertSvgToOriginalFormat(svgContent); // Затем экранируем все специальные символы let escaped = convertedSvg .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // Заменяем переносы строк escaped = escaped.replace(/\n/g, '
'); return escaped; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; const patternWidth = patternWidthInput.value || '20'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное экранирование SVG для XML svgContent = escapeSvgForXml(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в оригинале return { "color": "#ffffffff", "data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON с правильным форматированием jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>Добавлено (2025-11-17, 11:29) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Генератор шаблонов литологии</title> <style> /* Все стили остаются без изменений */ * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { margin: 0; padding: 20px; background-color: #f5f7fa; color: #333; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); padding: 25px; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; border-bottom: 2px solid #3498db; padding-bottom: 15px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; background-color: #f8f9fa; } .section-title { font-size: 1.3rem; color: #2c3e50; margin-bottom: 15px; display: flex; align-items: center; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 600; color: #2c3e50; } input, textarea, select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } textarea { min-height: 100px; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button.secondary { background-color: #95a5a6; } button.secondary:hover { background-color: #7f8c8d; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { border: 1px solid #ddd; padding: 12px; text-align: left; } th { background-color: #3498db; color: white; } tr:nth-child(even) { background-color: #f2f2f2; } .preview-container { display: flex; flex-wrap: wrap; gap: 20px; margin-top: 20px; } .preview-item { border: 1px solid #ddd; border-radius: 8px; padding: 15px; width: 200px; text-align: center; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .preview-svg { height: 100px; display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .preview-svg svg { max-width: 100%; max-height: 100%; } .preview-code { font-weight: bold; color: #2c3e50; } .preview-name { color: #7f8c8d; font-size: 0.9rem; } .hidden { display: none; } .message { padding: 15px; border-radius: 4px; margin: 15px 0; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .actions { display: flex; gap: 10px; margin-top: 20px; } .file-input-container { position: relative; overflow: hidden; display: inline-block; width: 100%; } .file-input-container input[type=file] { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer; } .file-input-button { display: block; padding: 10px; background: #f8f9fa; border: 2px dashed #3498db; border-radius: 4px; text-align: center; color: #3498db; font-weight: 600; } .file-list { margin-top: 10px; max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; } .file-item { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #eee; } .file-item:last-child { border-bottom: none; } .file-name { flex-grow: 1; } .file-size { color: #7f8c8d; font-size: 0.8rem; } .data-table-container { overflow-x: auto; } .help-text { font-size: 0.9rem; color: #7f8c8d; margin-top: 5px; }
/* Новые стили для JSON отображения */ #jsonContent { background-color: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; padding: 15px; max-height: 500px; overflow-y: auto; white-space: pre-wrap; word-wrap: break-word; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.4; } </style> </head> <body> <div class="container"> <h1>Генератор шаблонов литологии</h1> <div class="section"> <h2 class="section-title">1. Загрузка SVG-файлов с текстурами</h2> <div class="form-group"> <label>Выберите папку с SVG-файлами:</label> <div class="file-input-container"> <div class="file-input-button">Выбрать папку с SVG-файлами</div> <input type="file" id="svgFolderInput" webkitdirectory multiple> </div> <div class="help-text">SVG-файлы должны быть названы в формате "Код_Породы Название.svg", например: "1000 Лед.svg"</div> </div> <div class="form-group"> <label>Путь к SVG файлам в программе:</label> <input type="text" id="svgFilePath" placeholder="C:/Users/User/AppData/Local/OISTerra/OISTerraManager/svg/" value="C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/"> <div class="help-text">Укажите путь, который будет использоваться в сгенерированном JSON файле</div> </div> <div id="fileList" class="file-list hidden"> <!-- Список загруженных файлов будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">2. Ввод данных о породах</h2> <div class="form-group"> <label>Добавить данные о породах:</label> <div class="help-text">Введите данные в формате: Код_Породы Название (каждая порода с новой строки)</div> <textarea id="rockDataInput" placeholder="1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит"></textarea> </div> <div class="actions"> <button id="parseDataBtn">Обработать данные</button> <button id="addRockBtn" class="secondary">Добавить породу вручную</button> </div> <div id="rockTableContainer" class="data-table-container hidden"> <h3>Список пород:</h3> <table id="rockTable"> <thead> <tr> <th>Код породы</th> <th>Наименование породы</th> <th>SVG файл</th> <th>Действия</th> </tr> </thead> <tbody id="rockTableBody"> <!-- Данные о породах будут здесь --> </tbody> </table> </div> </div> <div class="section"> <h2 class="section-title">3. Предварительный просмотр</h2> <div id="previewContainer" class="preview-container"> <!-- Предварительный просмотр будет здесь --> </div> </div> <div class="section"> <h2 class="section-title">4. Генерация шаблона</h2> <div class="form-group"> <label>Название палитры:</label> <input type="text" id="paletteName" value="тест_лито"> </div> <div class="form-group"> <label>Название шаблона корреляции:</label> <input type="text" id="templateName" value="Корреляционный шаблон"> </div>
<div class="form-group"> <label>Размер patternWidth для SVG:</label> <input type="number" id="patternWidth" value="20" min="1" max="100"> <div class="help-text">Размер маркера для всех SVG текстур (рекомендуется 20)</div> </div> <div class="actions"> <button id="generateBtn">Сгенерировать JSON шаблон</button> <button id="downloadBtn" class="secondary hidden">Скачать шаблон</button> </div> <div id="messageArea"></div> <div id="jsonOutput" class="hidden"> <h3>Сгенерированный JSON:</h3> <pre id="jsonContent"></pre> </div> </div> </div>
<script> // Переменные для хранения данных let svgFiles = {}; let rockData = []; // Элементы DOM const svgFolderInput = document.getElementById('svgFolderInput'); const svgFilePathInput = document.getElementById('svgFilePath'); const patternWidthInput = document.getElementById('patternWidth'); const fileList = document.getElementById('fileList'); const rockDataInput = document.getElementById('rockDataInput'); const parseDataBtn = document.getElementById('parseDataBtn'); const addRockBtn = document.getElementById('addRockBtn'); const rockTableContainer = document.getElementById('rockTableContainer'); const rockTableBody = document.getElementById('rockTableBody'); const previewContainer = document.getElementById('previewContainer'); const paletteNameInput = document.getElementById('paletteName'); const templateNameInput = document.getElementById('templateName'); const generateBtn = document.getElementById('generateBtn'); const downloadBtn = document.getElementById('downloadBtn'); const messageArea = document.getElementById('messageArea'); const jsonOutput = document.getElementById('jsonOutput'); const jsonContent = document.getElementById('jsonContent'); // Функция для преобразования SVG в формат как в оригинале function convertSvgToOriginalFormat(svgContent) { // Создаем временный div для парсинга SVG const tempDiv = document.createElement('div'); tempDiv.innerHTML = svgContent; const svgElement = tempDiv.querySelector('svg'); if (!svgElement) return svgContent; // Создаем SVG с атрибутами как в оригинале let originalSvg = `<?xml version='1.0' encoding='UTF-8'?>\n<svg id="svg1" height="142px" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" viewBox="0 0 282 142" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" width="282px" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1">\n <metadata>\n <rdf:RDF>\n <cc:Work>\n <dc:title>ИСИХОГИ_Литология</dc:title>\n </cc:Work>\n </rdf:RDF>\n </metadata>\n <defs id="defs1"/>\n <g id="layer1">\n`; // Добавляем все path элементы const paths = svgElement.querySelectorAll('path'); paths.forEach(path => { const id = path.getAttribute('id') || ''; const d = path.getAttribute('d') || ''; const strokeWidth = path.getAttribute('stroke-width') || ''; const style = path.getAttribute('style') || ''; originalSvg += ` <path id="${id}" d="${d}" stroke-width="${strokeWidth}" style="${style}"/>\n`; }); // Добавляем все line элементы в формате оригинала const lines = svgElement.querySelectorAll('line'); lines.forEach(line => { const id = line.getAttribute('id') || ''; const x1 = line.getAttribute('x1') || ''; const y1 = line.getAttribute('y1') || ''; const x2 = line.getAttribute('x2') || ''; const y2 = line.getAttribute('y2') || ''; const strokeWidth = line.getAttribute('stroke-width') || ''; const style = line.getAttribute('style') || ''; originalSvg += ` <line id="${id}" x1="${x1}" y1="${y1}" y2="${y2}" stroke-width="${strokeWidth}" style="${style}" x2="${x2}"/>\n`; }); // Добавляем все ellipse элементы в формате оригинала const ellipses = svgElement.querySelectorAll('ellipse'); ellipses.forEach(ellipse => { const id = ellipse.getAttribute('id') || ''; const cx = ellipse.getAttribute('cx') || ''; const cy = ellipse.getAttribute('cy') || ''; const rx = ellipse.getAttribute('rx') || ''; const ry = ellipse.getAttribute('ry') || ''; const strokeWidth = ellipse.getAttribute('stroke-width') || ''; const fill = ellipse.getAttribute('fill') || ''; const stroke = ellipse.getAttribute('stroke') || ''; originalSvg += ` <ellipse id="${id}" cx="${cx}" fill="${fill}" rx="${rx}" cy="${cy}" stroke-width="${strokeWidth}" stroke="${stroke}" ry="${ry}"/>\n`; }); originalSvg += ` </g>\n</svg>\n`; return originalSvg; } // Функция для правильного экранирования SVG контента для XML function escapeSvgForXml(svgContent) { // Сначала преобразуем SVG в формат оригинала let convertedSvg = convertSvgToOriginalFormat(svgContent); // Затем экранируем все специальные символы - ИСПРАВЛЕНА ОШИБКА С КАВЫЧКАМИ let escaped = convertedSvg .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // Заменяем переносы строк escaped = escaped.replace(/\n/g, '
'); return escaped; } // Обработка загрузки SVG файлов svgFolderInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); svgFiles = {}; // Очистка списка файлов fileList.innerHTML = ''; // Фильтрация только SVG файлов const svgFilesList = files.filter(file => file.name.toLowerCase().endsWith('.svg')); if (svgFilesList.length === 0) { fileList.innerHTML = '<div>SVG файлы не найдены</div>'; fileList.classList.remove('hidden'); return; } // Обработка каждого SVG файла svgFilesList.forEach(file => { const fileName = file.name; const codeMatch = fileName.match(/^(\d+)/); if (codeMatch) { const code = codeMatch[1]; const reader = new FileReader(); reader.onload = function(e) { const svgContent = e.target.result; svgFiles[code] = { name: fileName, content: svgContent }; // Добавление в список файлов const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = ` <div class="file-name">${fileName}</div> <div class="file-size">${(file.size / 1024).toFixed(2)} KB</div> `; fileList.appendChild(fileItem); // Обновление таблицы пород, если код уже есть updateRockTableWithSvg(code); }; reader.readAsText(file); } }); fileList.classList.remove('hidden'); showMessage(`Загружено ${svgFilesList.length} SVG файлов`, 'success'); }); // Обработка данных о породах parseDataBtn.addEventListener('click', function() { const inputText = rockDataInput.value.trim(); if (!inputText) { showMessage('Введите данные о породах', 'error'); return; } const lines = inputText.split('\n'); rockData = []; lines.forEach(line => { const trimmedLine = line.trim(); if (trimmedLine) { const parts = trimmedLine.split(/\s+/); if (parts.length >= 2) { const code = parts[0]; const name = parts.slice(1).join(' '); rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); } } }); updateRockTable(); updatePreview(); showMessage(`Обработано ${rockData.length} пород`, 'success'); }); // Добавление породы вручную addRockBtn.addEventListener('click', function() { const code = prompt('Введите код породы:'); if (!code) return; const name = prompt('Введите название породы:'); if (!name) return; rockData.push({ code: code, name: name, svgFile: svgFiles[code] ? svgFiles[code].name : null }); updateRockTable(); updatePreview(); showMessage(`Добавлена порода: ${code} ${name}`, 'success'); }); // Обновление таблицы пород function updateRockTable() { rockTableBody.innerHTML = ''; if (rockData.length === 0) { rockTableContainer.classList.add('hidden'); return; } rockData.forEach((rock, index) => { const row = document.createElement('tr'); row.innerHTML = ` <td>${rock.code}</td> <td>${rock.name}</td> <td>${rock.svgFile || 'Не найден'}</td> <td> <button onclick="removeRock(${index})">Удалить</button> </td> `; rockTableBody.appendChild(row); }); rockTableContainer.classList.remove('hidden'); } // Обновление таблицы при наличии SVG function updateRockTableWithSvg(code) { rockData.forEach(rock => { if (rock.code === code) { rock.svgFile = svgFiles[code].name; } }); updateRockTable(); updatePreview(); } // Удаление породы function removeRock(index) { rockData.splice(index, 1); updateRockTable(); updatePreview(); showMessage('Порода удалена', 'success'); } // Обновление предварительного просмотра function updatePreview() { previewContainer.innerHTML = ''; if (rockData.length === 0) { previewContainer.innerHTML = '<p>Нет данных для предварительного просмотра</p>'; return; } rockData.forEach(rock => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; let svgPreview = '<div>SVG не загружен</div>'; if (rock.svgFile && svgFiles[rock.code]) { svgPreview = svgFiles[rock.code].content; } previewItem.innerHTML = ` <div class="preview-svg">${svgPreview}</div> <div class="preview-code">${rock.code}</div> <div class="preview-name">${rock.name}</div> `; previewContainer.appendChild(previewItem); }); } // Генерация JSON шаблона generateBtn.addEventListener('click', function() { if (rockData.length === 0) { showMessage('Нет данных о породах для генерации шаблона', 'error'); return; } const paletteName = paletteNameInput.value || 'тест_лито'; const templateName = templateNameInput.value || 'Корреляционный шаблон'; const svgFilePath = svgFilePathInput.value || 'C:/Users/StamberskiyAA/AppData/Local/OISTerra/OISTerraManager/svg/'; const patternWidth = patternWidthInput.value || '20'; // Создание элементов палитры const paletteItems = rockData.map(rock => { let svgContent = ''; if (rock.svgFile && svgFiles[rock.code]) { // Правильное экранирование SVG для XML svgContent = escapeSvgForXml(svgFiles[rock.code].content); } // Генерация случайного ID const id = Date.now() + Math.floor(Math.random() * 1000); // Полный путь к файлу SVG const fullSvgFilePath = svgFilePath + rock.svgFile; // Точный порядок атрибутов как в оригинале return { "color": "#ffffffff", "data": `<StyledFill id="${id}" name="" objectName="" brushColor="#000000" penColor="#000000" group="">\n <SvgFill patternWidth="${patternWidth}" svgContent="${svgContent}" lineWidth="0.4" objectName="" penWidth="0.4" svgFilePath="${fullSvgFilePath}"/>\n</StyledFill>\n`, "desc": rock.name, "value": parseInt(rock.code) }; }); // Создание основного JSON объекта с фиксированными ID как в рабочем примере const templateJson = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": paletteItems, "id": Date.now(), "name": paletteName, "transp": true, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ { "Tracks": [ { "allHeight": false, "hTxtSet": { "vis": true }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947027, "type": 0 }, { "Logs": [ { "logDataVers": 1, "logId": 1763096947080, "logParams": [ { "contId": 1763096947014, "contName": "РИГИС", "id": 1763096947013, "nm": "литология" } ], "logType": 3, "trackId": 1763096947055 } ], "allHeight": false, "hTxtSet": { "vis": false }, "lineStyle": { "fCol": "#ffc8ffff" }, "trackId": 1763096947055, "width": 50.69166666666666 } ], "Version": { "build": 3, "fillData": 0, "horizonView": 0, "logData": 1, "logRange": 0, "plastBuild": 0, "plastData": 0, "stratBuild": 0, "stratData": 0, "stratWidth": 0, "timeDepth": 0, "trackWidth": 1, "view": 7 }, "header": { "textBlockList": { "textBlocks": [ { "blockId": 1763096947023, "textItems": [ { "blockId": 1763096947023, "content": "[WellName]", "font": { "b": true }, "itemId": 1763096947024, "paramsHash": [ { "id": -1, "key": "[WellName]" } ] } ] } ] } }, "id": Date.now() + 1000, "name": templateName, "plastList": { "columnId": 1763096947025, "isPlast": true, "type": 0 } } ], "corrTemplatesVersion": 0 }; // Отображение JSON с правильным форматированием jsonContent.textContent = JSON.stringify(templateJson, null, 2); jsonOutput.classList.remove('hidden'); downloadBtn.classList.remove('hidden'); // Сохранение JSON для скачивания window.generatedJson = templateJson; showMessage('JSON шаблон успешно сгенерирован', 'success'); }); // Скачивание JSON файла downloadBtn.addEventListener('click', function() { if (!window.generatedJson) { showMessage('Нет сгенерированного JSON для скачивания', 'error'); return; } const dataStr = JSON.stringify(window.generatedJson, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = 'литология_шаблон.json'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showMessage('Файл успешно скачан', 'success'); }); // Функция для отображения сообщений function showMessage(message, type) { messageArea.innerHTML = `<div class="message ${type}">${message}</div>`; // Автоматическое скрытие сообщения через 5 секунд setTimeout(() => { messageArea.innerHTML = ''; }, 5000); } // Инициализация при загрузке страницы window.onload = function() { // Добавляем пример данных для демонстрации rockDataInput.value = `1300 Суглинок 1700 Илы 1800 Торф 1400 Супесь 2300 Аргиллит 2400 Алевролит`; }; </script> </body> </html>
|
|
|
|
|
{ "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": [ { "color": "#ffff0000", "data": "", "desc": "красный", "value": 1000 }, { "color": "#ff00ff00", "data": "", "desc": "зеленый", "value": 1001 } ], "id": 1763358376409, "name": "тест_возраст", "transp": false, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [ ], "corrTemplatesVersion": 0 }
вводимые данные 0 гтвуаштув #ffffff 1100 QIII-IV #ffffeb 1000 QIV #fffff5 1200 QIII[4]=QIV #ffffeb 1300 QIII[3]=QIV #ffffe6
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Конвертер данных в JSON</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #333; text-align: center; } .input-section, .output-section { margin-bottom: 20px; } textarea { width: 100%; height: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; font-family: monospace; } .example { background-color: #f9f9f9; padding: 10px; border-radius: 4px; margin-top: 10px; font-size: 0.9em; } button { background-color: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; display: block; margin: 0 auto; } button:hover { background-color: #45a049; } .output-section { display: none; } .color-preview { display: inline-block; width: 20px; height: 20px; border: 1px solid #ccc; margin-right: 5px; vertical-align: middle; } .success-message { color: #4CAF50; text-align: center; margin-top: 10px; } </style> </head> <body> <div class="container"> <h1>Конвертер данных в JSON</h1> <div class="input-section"> <h2>Введите данные</h2> <p>Формат: значение возраст_стратиграфии цвет</p> <textarea id="inputData" placeholder="Пример: 1100 QIII-IV #ffffeb 1000 QIV #fffff5 1200 QIII[4]=QIV #ffffeb 1300 QIII[3]=QIV #ffffe6"></textarea> <div class="example"> <strong>Пример заполнения:</strong><br> 1100 - значение вводится в value<br> QIII-IV - возраст стратиграфии вводится в desc<br> #ffffeb - цвет вводится в color<br> id генерируется автоматически </div> <div> <label for="paletteName">Название палитры:</label> <input type="text" id="paletteName" value="тест_возраст"> </div> <button id="convertBtn">Сгенерировать JSON</button> </div> <div class="output-section" id="outputSection"> <h2>Результат</h2> <textarea id="outputJson" readonly></textarea> <button id="downloadBtn">Скачать JSON файл</button> <div class="success-message" id="successMessage"></div> </div> </div>
<script> document.getElementById('convertBtn').addEventListener('click', function() { const inputData = document.getElementById('inputData').value; const paletteName = document.getElementById('paletteName').value || 'тест_возраст'; if (!inputData.trim()) { alert('Пожалуйста, введите данные для конвертации'); return; } const items = []; const lines = inputData.split('\n'); for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine) continue; const parts = trimmedLine.split(/\s+/); if (parts.length < 3) continue; const value = parseInt(parts[0]); if (isNaN(value)) continue; const color = parts[parts.length - 1]; const desc = parts.slice(1, parts.length - 1).join(' '); items.push({ color: color, data: "", desc: desc, value: value }); } const jsonData = { "Info": "OISTerra CorrTemplates", "Palettes": [ { "Items": items, "id": Date.now(), "name": paletteName, "transp": false, "type": 1 } ], "ScoPaletteVersion": 1, "corrTemplates": [], "corrTemplatesVersion": 0 }; const outputJson = JSON.stringify(jsonData, null, 2); document.getElementById('outputJson').value = outputJson; document.getElementById('outputSection').style.display = 'block'; }); document.getElementById('downloadBtn').addEventListener('click', function() { const jsonData = document.getElementById('outputJson').value; const paletteName = document.getElementById('paletteName').value || 'тест_возраст'; const blob = new Blob([jsonData], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${paletteName}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); document.getElementById('successMessage').textContent = 'Файл успешно скачан!'; setTimeout(() => { document.getElementById('successMessage').textContent = ''; }, 3000); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 15px; }
.group-section { margin-bottom: 15px; grid-column: 1 / -1; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.group-icons-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 15px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; grid-column: 1 / -1; }
.no-results { text-align: center; padding: 40px; color: #666; grid-column: 1 / -1; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Функция для обработки BMP с розовым фоном function processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Определяем розовый цвет для замены на прозрачный // Основные варианты розового цвета в BMP масках for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Ярко-розовый (255, 0, 255) - наиболее распространенный if (r > 250 && g < 10 && b > 250) { data[i + 3] = 0; // Прозрачность } // Светло-розовый (255, 192, 203) else if (r > 250 && g > 180 && b > 190) { data[i + 3] = 0; } // Другие оттенки розового else if (r > 240 && g < 100 && b > 240) { data[i + 3] = 0; } } ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => resolve({ element: processedImg, processed: true }); processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { const result = await processBMPWithPinkBackground(img); resolve(result); } catch (error) { resolve({ element: img, processed: false }); } } else { resolve({ element: img, processed: false }); } }; img.onerror = function() { // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Создаем контейнер для колонок группы const iconsContainer = document.createElement('div'); iconsContainer.className = 'group-icons-container'; // Создаем колонки для иконок группы const itemsPerColumn = Math.ceil(icons.length / Math.ceil(icons.length / 20)); for (let i = 0; i < icons.length; i += itemsPerColumn) { const columnIcons = icons.slice(i, i + itemsPerColumn); const column = createIconColumn(columnIcons); iconsContainer.appendChild(column); } section.appendChild(iconsContainer); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Создаем колонки для отсортированных иконок const itemsPerColumn = Math.ceil(sortedIcons.length / Math.ceil(sortedIcons.length / 20)); for (let i = 0; i < sortedIcons.length; i += itemsPerColumn) { const columnIcons = sortedIcons.slice(i, i + itemsPerColumn); const column = createIconColumn(columnIcons); container.appendChild(column); } }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); const iconsContainer = document.createElement('div'); iconsContainer.className = 'group-icons-container'; const itemsPerColumn = Math.ceil(noGroup.length / Math.ceil(noGroup.length / 20)); for (let i = 0; i < noGroup.length; i += itemsPerColumn) { const columnIcons = noGroup.slice(i, i + itemsPerColumn); const column = createIconColumn(columnIcons); iconsContainer.appendChild(column); } noGroupSection.appendChild(iconsContainer); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки без заголовка const itemsPerColumn = Math.ceil(noGroup.length / Math.ceil(noGroup.length / 20)); for (let i = 0; i < noGroup.length; i += itemsPerColumn) { const columnIcons = noGroup.slice(i, i + itemsPerColumn); const column = createIconColumn(columnIcons); container.appendChild(column); } } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>Добавлено (2025-11-25, 12:18) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 0 1 auto; min-width: 350px; max-width: 100%; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; border-left: 4px solid #007bff; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; min-width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем (розовым фоном) function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Более точное определение хромакея (розового фона) for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Основной хромакей - ярко-розовый (255, 0, 255) const isBrightPink = r > 250 && g < 50 && b > 250; // Светло-розовые оттенки const isLightPink = r > 240 && g > 170 && b > 220 && Math.abs(r - b) < 30 && r - g > 50; // Средние розовые оттенки const isMediumPink = r > 220 && g > 120 && b > 200 && r - g > 40 && r - b < 50; if (isBrightPink || isLightPink || isMediumPink) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => resolve({ element: processedImg, processed: true }); processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); console.log('BMP обработан успешно'); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Создаем колонку для иконок группы const column = createIconColumn(icons); section.appendChild(column); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Создаем колонки для отсортированных иконок const itemsPerColumn = Math.ceil(sortedIcons.length / Math.ceil(sortedIcons.length / 25)); for (let i = 0; i < sortedIcons.length; i += itemsPerColumn) { const columnIcons = sortedIcons.slice(i, i + itemsPerColumn); const column = createIconColumn(columnIcons); container.appendChild(column); } }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); const column = createIconColumn(noGroup); noGroupSection.appendChild(column); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html> Добавлено (2025-11-25, 12:36) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 1; min-width: 350px; max-width: calc(33.333% - 10px); }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; border-left: 4px solid #007bff; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; height: fit-content; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; }
.group-column { flex: 1; min-width: 350px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение чистого розового (255, 0, 255) const isPurePink = r === 255 && g === 0 && b === 255; // Близкие к чистому розовому (допуск ±5) const isNearPurePink = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; // Другие распространенные розовые оттенки для хромакея const isCommonChromaKey = (r > 240 && g < 20 && b > 240) || // Близкие к чистому (r === 255 && g === 0 && b === 255) || // Точный (r === 254 && g === 0 && b === 254) || // Почти точный (r === 255 && g === 1 && b === 255); // Почти точный if (isPurePink || isNearPurePink || isCommonChromaKey) { data[i + 3] = 0; // Полная прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, розовый фон удален'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для разбиения массива на части function splitArray(array, maxItemsPerPart) { const parts = []; for (let i = 0; i < array.length; i += maxItemsPerPart) { parts.push(array.slice(i, i + maxItemsPerPart)); } return parts; }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Разбиваем иконки на колонки (максимум 15 иконок в колонке) const iconParts = splitArray(icons, 15); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; iconParts.forEach(part => { const column = createIconColumn(part); columnsContainer.appendChild(column); }); section.appendChild(columnsContainer); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки по 15 иконок const iconParts = splitArray(sortedIcons, 15); iconParts.forEach(part => { const column = createIconColumn(part); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); // Разбиваем прочие иконки на колонки const noGroupParts = splitArray(noGroup, 15); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; noGroupParts.forEach(part => { const column = createIconColumn(part); columnsContainer.appendChild(column); }); noGroupSection.appendChild(columnsContainer); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html> Добавлено (2025-11-25, 13:41) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 0 0 auto; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; flex-shrink: 0; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 25) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Разбиваем иконки на несколько колонок const columns = splitIconsIntoColumns(icons); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); section.appendChild(columnsContainer); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); // Разбиваем прочие иконки на несколько колонок const columns = splitIconsIntoColumns(noGroup); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); noGroupSection.appendChild(columnsContainer); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html> Добавлено (2025-11-25, 14:14) --------------------------------------------- Ррр
|
|
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; }
.group-section { width: 100%; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEw IDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; // Один заголовок на всю группу const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Разбиваем иконки на несколько колонок const columns = splitIconsIntoColumns(icons); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); section.appendChild(columnsContainer); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); // Разбиваем прочие иконки на несколько колонок const columns = splitIconsIntoColumns(noGroup); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); noGroupSection.appendChild(columnsContainer); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { width: 100%; margin-bottom: 20px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; // Один заголовок на всю группу const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Разбиваем иконки на несколько колонок const columns = splitIconsIntoColumns(icons); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); section.appendChild(columnsContainer); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); // Разбиваем прочие иконки на несколько колонок const columns = splitIconsIntoColumns(noGroup); const columnsContainer = document.createElement('div'); columnsContainer.className = 'group-columns-container'; columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); columnsContainer.appendChild(column); }); noGroupSection.appendChild(columnsContainer); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Сравнение иконок ИСИХОГИ</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #fff; padding: 10px; font-size: 11px; }
.container { max-width: 100%; margin: 0 auto; }
h1 { text-align: center; color: #333; font-size: 16px; margin-bottom: 15px; font-weight: bold; }
.search-container { margin: 15px 0; text-align: center; }
.search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.search-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); }
.controls-row { display: flex; justify-content: center; align-items: center; gap: 20px; margin: 12px 0; padding: 8px; background: #f0f0f0; border-radius: 4px; border: 1px solid #ddd; }
.stats { font-size: 12px; }
.group-select { padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; background: white; }
.group-toggle { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.group-toggle input { margin: 0; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { width: 100%; margin-bottom: 20px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; display: inline-block; max-width: fit-content; }
.group-columns-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 350px; }
.column-header { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 5px 3px; margin-bottom: 5px; border-bottom: 1px solid #ddd; font-weight: bold; text-align: center; }
.header-label { font-size: 11px; color: #333; }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; }
.icon-row:last-child { border-bottom: none; }
.icon-image { width: 32px; height: 32px; object-fit: contain; image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; image-rendering: pixelated; }
.bmp-processed { /* Стиль для обработанных BMP с прозрачностью */ filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="Поиск по названию иконки..."> </div> <div class="controls-row"> <div class="stats"> Всего иконок для сравнения: <span id="totalCount">0</span> | Отображено: <span id="shownCount">0</span> </div> <select id="groupSelect" class="group-select" style="display: none;"> <option value="">Все группы</option> </select> <div class="group-toggle" id="groupToggle" style="display: none;"> <input type="checkbox" id="showGroups" checked> <label for="showGroups">Отображать группы</label> </div> </div>
<div class="comparison-container" id="iconsContainer"> <div class="loading">Загрузка иконок...</div> </div> </div>
<script> let allIcons = []; let filteredIcons = []; let availableGroups = []; let showGroups = true;
// Улучшенная функция для обработки BMP с хромакеем function processBMPWithChromaKey(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; // Рисуем изображение на canvas ctx.drawImage(img, 0, 0); // Получаем данные пикселей const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Обрабатываем каждый пиксель for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Точное определение ярко-розового хромакея (255, 0, 255) const isChromaKey = r === 255 && g === 0 && b === 255; // Также обрабатываем близкие оттенки (допуск ±5) const isNearChromaKey = r >= 250 && r <= 255 && g >= 0 && g <= 5 && b >= 250 && b <= 255; if (isChromaKey || isNearChromaKey) { data[i + 3] = 0; // Устанавливаем полную прозрачность } } // Записываем обработанные данные обратно ctx.putImageData(imageData, 0, 0); const processedImg = new Image(); processedImg.onload = () => { console.log('BMP успешно обработан, прозрачность применена'); resolve({ element: processedImg, processed: true }); }; processedImg.src = canvas.toDataURL('image/png'); } catch (error) { console.warn('Ошибка обработки BMP:', error); resolve({ element: img, processed: false }); } }); }
// Функция для загрузки изображения с обработкой BMP function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { // Обрабатываем только BMP файлы if (src.toLowerCase().endsWith('.bmp')) { try { console.log('Обработка BMP:', src); const result = await processBMPWithChromaKey(img); resolve(result); } catch (error) { console.warn('Ошибка при обработке BMP:', error); resolve({ element: img, processed: false }); } } else { // Для других форматов просто возвращаем изображение resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); // Заглушка для отсутствующего изображения const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIwIDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; resolve({ element: placeholder, processed: false }); }; img.src = src; img.alt = alt; }); }
// Функция для группировки иконок function groupIcons(icons) { const groups = {}; const noGroup = []; icons.forEach(icon => { if (icon.group && icon.group.trim() !== '') { if (!groups[icon.group]) { groups[icon.group] = []; } groups[icon.group].push(icon); } else { noGroup.push(icon); } }); return { groups, noGroup }; }
// Функция для разбиения иконок на несколько колонок function splitIconsIntoColumns(icons, maxIconsPerColumn = 18) { const columns = []; const totalIcons = icons.length; if (totalIcons <= maxIconsPerColumn) { columns.push(icons); } else { const numberOfColumns = Math.ceil(totalIcons / maxIconsPerColumn); const iconsPerColumn = Math.ceil(totalIcons / numberOfColumns); for (let i = 0; i < totalIcons; i += iconsPerColumn) { columns.push(icons.slice(i, i + iconsPerColumn)); } } return columns; }
// Функция для создания секции группы function createGroupSection(groupName, icons) { const section = document.createElement('div'); section.className = 'group-section'; // Один заголовок на всю группу const header = document.createElement('div'); header.className = 'group-header'; header.textContent = groupName; section.appendChild(header); // Разбиваем иконки на несколько колонок const columns = splitIconsIntoColumns(icons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); section.appendChild(column); }); return section; }
// Функция для создания колонки с иконками function createIconColumn(icons) { const column = document.createElement('div'); column.className = 'icon-column'; // Заголовок колонки const header = document.createElement('div'); header.className = 'column-header'; const beforeLabel = document.createElement('div'); beforeLabel.className = 'header-label'; beforeLabel.textContent = 'ДО'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; header.appendChild(beforeLabel); header.appendChild(arrowSpace); header.appendChild(afterLabel); header.appendChild(nameLabel); column.appendChild(header); // Добавляем иконки в колонку icons.forEach(icon => { column.appendChild(createIconRow(icon)); }); return column; }
// Функция для создания строки с иконками function createIconRow(icon) { const row = document.createElement('div'); row.className = 'icon-row'; // Контейнеры для изображений const beforeContainer = document.createElement('div'); const afterContainer = document.createElement('div'); // Загружаем и обрабатываем изображения Promise.all([ loadImageWithBMPProcessing(icon.before, `${icon.name} (до)`), loadImageWithBMPProcessing(icon.after, `${icon.name} (после)`) ]).then(([beforeResult, afterResult]) => { const beforeImg = beforeResult.element; const afterImg = afterResult.element; beforeImg.className = 'icon-image' + (beforeResult.processed ? ' bmp-processed' : ''); afterImg.className = 'icon-image' + (afterResult.processed ? ' bmp-processed' : ''); beforeContainer.appendChild(beforeImg); afterContainer.appendChild(afterImg); }); // Стрелка const arrow = document.createElement('div'); arrow.className = 'arrow'; arrow.textContent = '→'; // Название иконки const infoDiv = document.createElement('div'); infoDiv.className = 'icon-info'; const name = document.createElement('div'); name.className = 'icon-name'; name.textContent = icon.name; name.title = icon.name; infoDiv.appendChild(name); row.appendChild(beforeContainer); row.appendChild(arrow); row.appendChild(afterContainer); row.appendChild(infoDiv); return row; }
// Функция для отображения иконок без групп (по алфавиту) function renderIconsWithoutGroups(icons) { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (icons.length === 0) return; // Сортируем иконки по алфавиту const sortedIcons = [...icons].sort((a, b) => a.name.localeCompare(b.name)); // Разбиваем на колонки const columns = splitIconsIntoColumns(sortedIcons); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); container.appendChild(column); }); }
// Функция для отображения иконок с группами function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) return; // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); const hasGroups = Object.keys(groups).length > 0; // Отображаем группы в алфавитном порядке Object.keys(groups).sort().forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { if (hasGroups) { const noGroupSection = document.createElement('div'); noGroupSection.className = 'group-section'; const header = document.createElement('div'); header.className = 'group-header'; header.textContent = 'Прочие иконки'; noGroupSection.appendChild(header); // Разбиваем прочие иконки на несколько колонок const columns = splitIconsIntoColumns(noGroup); columns.forEach(columnIcons => { const column = createIconColumn(columnIcons); noGroupSection.appendChild(column); }); container.appendChild(noGroupSection); } else { // Если групп нет, просто отображаем иконки renderIconsWithoutGroups(noGroup); } } }
// Функция для фильтрации иконок по поисковому запросу и группе function filterIcons(searchTerm, selectedGroup = '') { let filtered = [...allIcons]; // Фильтрация по поисковому запросу if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter(icon => icon.name.toLowerCase().includes(term) || (icon.group && icon.group.toLowerCase().includes(term)) ); } // Фильтрация по группе if (selectedGroup) { filtered = filtered.filter(icon => icon.group === selectedGroup); } filteredIcons = filtered; renderFilteredIcons(); }
// Функция для отображения отфильтрованных иконок function renderFilteredIcons() { if (filteredIcons.length === 0) { const container = document.getElementById('iconsContainer'); container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } if (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(filteredIcons); } updateStats(); }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп и переключателя function initGroupControls(groups) { const groupSelect = document.getElementById('groupSelect'); const groupToggle = document.getElementById('groupToggle'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); const hasGroups = groups.length > 0; groupSelect.style.display = hasGroups ? 'block' : 'none'; groupToggle.style.display = hasGroups ? 'flex' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); document.getElementById('showGroups').addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupControls(groups); renderFilteredIcons(); }
// Загружаем данные из внешнего файла async function loadIcons() { try { const script = document.createElement('script'); script.src = 'image_list.js'; document.head.appendChild(script); script.onload = function() { try { const data = loadBeforeAfterImages(); if (data && data.images) { const processedImages = data.images.map(icon => { const displayName = icon.name.replace(/^32x32_/, ''); return { ...icon, name: displayName }; }); renderIcons(processedImages, data.availableGroups || []); } else { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки данных</div>'; } } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка обработки данных</div>'; } }; script.onerror = function() { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки файла image_list.js</div>'; }; } catch (error) { document.getElementById('iconsContainer').innerHTML = '<div class="loading">Ошибка загрузки иконок</div>'; } }
// Обработчик поиска function setupSearch() { const searchInput = document.getElementById('searchInput'); let searchTimeout; searchInput.addEventListener('input', function(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { const selectedGroup = document.getElementById('groupSelect').value; filterIcons(e.target.value, selectedGroup); }, 300); }); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { this.value = ''; const selectedGroup = document.getElementById('groupSelect').value; filterIcons('', selectedGroup); } }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); }); </script> </body> </html>
|