|
Поговорим о...
|
|
<!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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; /* Перемещаем вправо */ }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 1; min-width: 350px; margin-bottom: 15px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; min-height: 100px; }
.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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; // Удаляем только чистый розовый (255,0,255) - без допусков for (let i = 0; i < data.length; i += 4) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
// Функция для загрузки изображения с обработкой function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { // Для BMP файлов используем специальную обработку if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { // Для других форматов используем как есть img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; // Пробуем загрузить изображение img.src = src; }); }
// Функция для группировки иконок 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 renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Отображаем группы (не сортируем по алфавиту, сохраняем порядок из данных) Object.keys(groups).forEach(groupName => { const section = createGroupSection(groupName, groups[groupName]); container.appendChild(section); }); // Затем иконки без группы if (noGroup.length > 0) { 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); } updateStats(); }
// Функция для отображения иконок без группировки (улучшенная сортировка) function renderIconsWithoutGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Сортируем: сначала по группам, потом по алфавиту внутри групп const sortedIcons = [...filteredIcons].sort((a, b) => { // Сначала сравниваем группы const groupA = a.group || 'zzz'; // Без группы в конец const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } // Если группы одинаковые, сравниваем по имени return a.name.localeCompare(b.name); }); // Разбиваем на колонки по 20 иконок в каждой const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
// Функция для фильтрации иконок по поисковому запросу и группе 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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
// Обновление статистики function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
// Инициализация комбобокса групп function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
// Функция для отображения всех иконок function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
// Обработчик переключения отображения групп function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
// Загружаем иконки при загрузке страницы window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); }); </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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 1; min-width: 350px; max-width: 500px; margin-bottom: 15px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.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 { 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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Функция для расчета высоты группы (в строках) function calculateGroupHeight(iconsCount) { // Высота заголовка + высота строк иконок const headerHeight = 40; // заголовок группы const rowHeight = 40; // высота одной строки с иконкой const padding = 16; // отступы return headerHeight + (iconsCount * rowHeight) + padding; }
// Алгоритм упорядочивания по высоте (как в Pinterest) function arrangeGroups(groups) { const groupEntries = Object.entries(groups); if (groupEntries.length === 0) return [];
// Сортируем группы по количеству иконок (от больших к маленьким) // Это помогает уменьшить пустоты groupEntries.sort((a, b) => b[1].length - a[1].length);
const columns = []; const result = [];
for (const [groupName, icons] of groupEntries) { const groupHeight = calculateGroupHeight(icons.length); if (columns.length === 0) { // Первая группа columns.push({ height: groupHeight, groups: [{ name: groupName, icons, height: groupHeight }] }); result.push({ name: groupName, icons, column: 0 }); } else { // Ищем колонку с минимальной высотой let minHeight = Infinity; let minColumnIndex = 0; for (let i = 0; i < columns.length; i++) { if (columns[i].height < minHeight) { minHeight = columns[i].height; minColumnIndex = i; } }
// Добавляем группу в колонку с минимальной высотой columns[minColumnIndex].height += groupHeight + 15; // + gap columns[minColumnIndex].groups.push({ name: groupName, icons, height: groupHeight }); result.push({ name: groupName, icons, column: minColumnIndex }); } }
return result; }
// Функция для отображения иконок с группировкой (улучшенная компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Оптимально располагаем группы const arrangedGroups = arrangeGroups(groups); // Создаем колонки для отображения const columnCount = Math.max(1, Math.floor(container.clientWidth / 400)); // Примерно по 400px на колонку const columns = Array.from({ length: columnCount }, () => { const col = document.createElement('div'); col.className = 'group-column'; col.style.flex = '1'; col.style.minWidth = '350px'; col.style.display = 'flex'; col.style.flexDirection = 'column'; col.style.gap = '15px'; return col; });
// Распределяем группы по колонкам arrangedGroups.forEach(group => { const columnIndex = group.column % columnCount; const section = createGroupSection(group.name, group.icons); columns[columnIndex].appendChild(section); });
// Добавляем иконки без группы в первую колонку if (noGroup.length > 0) { 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);
// Добавляем в колонку с наименьшим количеством контента let minHeightColumn = 0; let minHeight = Infinity; columns.forEach((col, index) => { const height = col.scrollHeight; if (height < minHeight) { minHeight = height; minHeightColumn = index; } }); columns[minHeightColumn].appendChild(noGroupSection); }
// Очищаем контейнер и добавляем колонки container.innerHTML = ''; columns.forEach(col => { if (col.children.length > 0) { container.appendChild(col); } });
updateStats(); }
// Функция для создания секции группы 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 processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); }); </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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; }
.group-section { flex: 0 1 calc(33.333% - 15px); min-width: 350px; margin-bottom: 15px; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.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 { 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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Упрощенный алгоритм компоновки - просто располагаем группы плиткой function arrangeGroups(groups) { const groupEntries = Object.entries(groups); // Сортируем группы по количеству иконок (от больших к маленьким) // для лучшего визуального баланса groupEntries.sort((a, b) => b[1].length - a[1].length); return groupEntries.map(([name, icons]) => ({ name, icons })); }
// Функция для отображения иконок с группировкой (простая плиточная компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Оптимально располагаем группы const arrangedGroups = arrangeGroups(groups); // Добавляем все группы как отдельные секции arrangedGroups.forEach(group => { const section = createGroupSection(group.name, group.icons); container.appendChild(section); }); // Добавляем иконки без группы if (noGroup.length > 0) { 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); }
updateStats(); }
// Функция для создания секции группы 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 processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; resolve({ element: img, processed: false }); } }; img.onerror = function() { console.warn('Ошибка загрузки изображения:', src); const placeholder = new Image(); placeholder.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xNiAxOEMxNi41NTIzIDE4IDE3IDE3LjU1MjMgMTcgMTdWMTFDMTcgMTAuNDQ3NyAxNi41NTIzIDEwIDE2IDEwQzE1LjQ0NzcgMTAgMTUgMTAuNDQ3NyAxNSAxMVYxN0MxNSAxNy41NTIzIDE1LjQ0NzcgMTggMTYgMThaIiBmaWxsPSIjOTk5OTk5Ii8+CjxwYXRoIGQ9Ik0xNiAyMkMxNi41NTIzIDIyIDE3IDIxLjU1MjMgMTcgMjFDMTcgMjAuNDQ3NyAxNi41NTIzIDIw IDE2IDIwQzE1LjQ0NzcgMjAgMTUgMjAuNDQ3NyAxNSAyMUMxNSAyMS41NTIzIDE1LjQ0NzcgMjIgMTYgMjJaIiBmaWxsPSIjOTk5OTk5Ii8+Cjwvc3ZnPgo='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); }); </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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 15px; align-items: start; }
.group-section { break-inside: avoid; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; position: sticky; top: 0; z-index: 1; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 100%; min-width: 380px; max-width: 380px; }
.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; min-height: 40px; }
.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 { 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; width: 100%; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; font-size: 11px; max-width: 200px; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; grid-column: 1 / -1; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; grid-column: 1 / -1; }
.columns-container { display: flex; gap: 15px; width: 100%; }
.masonry-column { flex: 1; min-width: 380px; display: flex; flex-direction: column; gap: 15px; } </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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Функция для разбивки больших групп на части function splitLargeGroup(icons, maxItems = 15) { if (icons.length <= maxItems) { return [icons]; } const chunks = []; for (let i = 0; i < icons.length; i += maxItems) { chunks.push(icons.slice(i, i + maxItems)); } return chunks; }
// Алгоритм masonry компоновки function arrangeGroupsMasonry(groups) { const groupEntries = Object.entries(groups); const result = []; // Сначала разбиваем большие группы const allGroupChunks = []; groupEntries.forEach(([groupName, icons]) => { const chunks = splitLargeGroup(icons, 15); chunks.forEach((chunk, index) => { const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: groupName, height: calculateGroupHeight(chunk.length) }); }); }); // Сортируем по высоте (от высоких к низким для лучшего заполнения) allGroupChunks.sort((a, b) => b.height - a.height); return allGroupChunks; }
// Функция для расчета примерной высоты группы function calculateGroupHeight(iconsCount) { const headerHeight = 40; const rowHeight = 32; const padding = 16; return headerHeight + (iconsCount * rowHeight) + padding; }
// Функция для создания masonry колонок function createMasonryLayout(groupsChunks) { const container = document.getElementById('iconsContainer'); const containerWidth = container.clientWidth; const columnWidth = 380 + 15; // ширина колонки + gap const columnCount = Math.max(1, Math.floor(containerWidth / columnWidth)); // Создаем колонки const columns = Array.from({ length: columnCount }, () => { const col = document.createElement('div'); col.className = 'masonry-column'; return col; }); // Массив для отслеживания высоты каждой колонки const columnHeights = Array(columnCount).fill(0); // Распределяем группы по колонкам (алгоритм "наименьшая высота") groupsChunks.forEach(group => { // Находим колонку с наименьшей высотой let minHeight = Math.min(...columnHeights); let columnIndex = columnHeights.indexOf(minHeight); // Добавляем группу в эту колонку const section = createGroupSection(group.name, group.icons); columns[columnIndex].appendChild(section); // Обновляем высоту колонки columnHeights[columnIndex] += group.height; }); return columns; }
// Функция для отображения иконок с группировкой (masonry компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем masonry компоновку для групп const groupsChunks = arrangeGroupsMasonry(groups); // Добавляем иконки без группы if (noGroup.length > 0) { const noGroupChunks = splitLargeGroup(noGroup, 15); noGroupChunks.forEach((chunk, index) => { const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки'; groupsChunks.push({ name: chunkName, icons: chunk, originalGroup: 'no_group', height: calculateGroupHeight(chunk.length) }); }); } // Создаем masonry колонки const columns = createMasonryLayout(groupsChunks); // Очищаем контейнер и добавляем колонки container.innerHTML = ''; const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; columns.forEach(col => { if (col.children.length > 0) { columnsContainer.appendChild(col); } }); container.appendChild(columnsContainer); updateStats(); }
// Функция для создания секции группы 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 processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); // Для режима без групп используем простые колонки const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); }); </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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 15px; align-items: start; }
.group-section { break-inside: avoid; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; position: sticky; top: 0; z-index: 1; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 100%; min-width: 380px; max-width: 380px; }
.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; min-height: 40px; }
.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 { 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; width: 100%; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; font-size: 11px; max-width: 200px; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; grid-column: 1 / -1; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; grid-column: 1 / -1; }
.columns-container { display: flex; gap: 15px; width: 100%; }
.masonry-column { flex: 1; min-width: 380px; display: flex; flex-direction: column; gap: 15px; } </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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Функция для разбивки больших групп на части function splitLargeGroup(icons, maxItems = 15) { if (icons.length <= maxItems) { return [icons]; } const chunks = []; for (let i = 0; i < icons.length; i += maxItems) { chunks.push(icons.slice(i, i + maxItems)); } return chunks; }
// Алгоритм masonry компоновки function arrangeGroupsMasonry(groups) { const groupEntries = Object.entries(groups); const result = []; // Сначала разбиваем большие группы const allGroupChunks = []; groupEntries.forEach(([groupName, icons]) => { const chunks = splitLargeGroup(icons, 15); chunks.forEach((chunk, index) => { const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: groupName, height: calculateGroupHeight(chunk.length) }); }); }); // Сортируем по высоте (от высоких к низким для лучшего заполнения) allGroupChunks.sort((a, b) => b.height - a.height); return allGroupChunks; }
// Функция для расчета примерной высоты группы function calculateGroupHeight(iconsCount) { const headerHeight = 40; const rowHeight = 32; const padding = 16; return headerHeight + (iconsCount * rowHeight) + padding; }
// Функция для создания masonry колонок function createMasonryLayout(groupsChunks) { const container = document.getElementById('iconsContainer'); const containerWidth = container.clientWidth; const columnWidth = 380 + 15; // ширина колонки + gap const columnCount = Math.max(1, Math.floor(containerWidth / columnWidth)); // Создаем колонки const columns = Array.from({ length: columnCount }, () => { const col = document.createElement('div'); col.className = 'masonry-column'; return col; }); // Массив для отслеживания высоты каждой колонки const columnHeights = Array(columnCount).fill(0); // Распределяем группы по колонкам (алгоритм "наименьшая высота") groupsChunks.forEach(group => { // Находим колонку с наименьшей высотой let minHeight = Math.min(...columnHeights); let columnIndex = columnHeights.indexOf(minHeight); // Добавляем группу в эту колонку const section = createGroupSection(group.name, group.icons); columns[columnIndex].appendChild(section); // Обновляем высоту колонки columnHeights[columnIndex] += group.height; }); return columns; }
// Функция для отображения иконок с группировкой (masonry компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Создаем masonry компоновку для групп const groupsChunks = arrangeGroupsMasonry(groups); // Добавляем иконки без группы if (noGroup.length > 0) { const noGroupChunks = splitLargeGroup(noGroup, 15); noGroupChunks.forEach((chunk, index) => { const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки'; groupsChunks.push({ name: chunkName, icons: chunk, originalGroup: 'no_group', height: calculateGroupHeight(chunk.length) }); }); } // Создаем masonry колонки const columns = createMasonryLayout(groupsChunks); // Очищаем контейнер и добавляем колонки container.innerHTML = ''; const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; columns.forEach(col => { if (col.children.length > 0) { columnsContainer.appendChild(col); } }); container.appendChild(columnsContainer); updateStats(); }
// Функция для создания секции группы 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 processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); // Для режима без групп используем простые колонки const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); }); </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: 1200px; /* Ограничение максимальной ширины */ 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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; justify-content: center; /* Центрируем колонки */ }
.group-section { break-inside: avoid; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 380px; /* Фиксированная ширина */ 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; min-height: 40px; }
.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 { 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; width: 100%; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; font-size: 11px; max-width: 200px; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; }
.columns-container { display: flex; flex-wrap: wrap; gap: 15px; width: 100%; justify-content: center; /* Центрируем колонки */ }
.masonry-column { display: flex; flex-direction: column; gap: 15px; }
/* Адаптивность для мобильных устройств */ @media (max-width: 768px) { .container { max-width: 100%; padding: 0 10px; } .icon-column { width: 100%; /* На мобильных занимает всю ширину */ max-width: 380px; /* Но не больше фиксированной ширины */ } .search-input { width: 100%; max-width: 300px; } .controls-row { flex-wrap: wrap; gap: 10px; } } </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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Функция для разбивки больших групп на части function splitLargeGroup(icons, maxItems = 15) { if (icons.length <= maxItems) { return [icons]; } const chunks = []; for (let i = 0; i < icons.length; i += maxItems) { chunks.push(icons.slice(i, i + maxItems)); } return chunks; }
// Функция для расчета примерной высоты группы function calculateGroupHeight(iconsCount) { const headerHeight = 40; const rowHeight = 32; const padding = 16; return headerHeight + (iconsCount * rowHeight) + padding; }
// Функция для создания адаптивной masonry компоновки function createAdaptiveMasonryLayout(groupsChunks) { const container = document.getElementById('iconsContainer'); const containerWidth = container.clientWidth; const columnWidth = 380 + 15; // ширина колонки + gap // Ограничиваем максимальное количество колонок исходя из максимальной ширины 1200px const maxColumns = Math.floor(1200 / columnWidth); const availableColumns = Math.min(maxColumns, Math.max(1, Math.floor(containerWidth / columnWidth))); console.log(`Container width: ${containerWidth}, Columns: ${availableColumns}`); // Создаем колонки const columns = Array.from({ length: availableColumns }, () => { const col = document.createElement('div'); col.className = 'masonry-column'; return col; }); // Массив для отслеживания высоты каждой колонки const columnHeights = Array(availableColumns).fill(0); // Распределяем группы по колонкам (алгоритм "наименьшая высота") groupsChunks.forEach(group => { // Находим колонку с наименьшей высотой let minHeight = Math.min(...columnHeights); let columnIndex = columnHeights.indexOf(minHeight); // Добавляем группу в эту колонку const section = createGroupSection(group.name, group.icons); columns[columnIndex].appendChild(section); // Обновляем высоту колонки columnHeights[columnIndex] += group.height; }); return columns; }
// Функция для отображения иконок с группировкой (masonry компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Собираем все группы и разбиваем большие const allGroupChunks = []; // Обрабатываем группы с иконками Object.entries(groups).forEach(([groupName, icons]) => { const chunks = splitLargeGroup(icons, 15); chunks.forEach((chunk, index) => { const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: groupName, height: calculateGroupHeight(chunk.length) }); }); }); // Добавляем иконки без группы if (noGroup.length > 0) { const noGroupChunks = splitLargeGroup(noGroup, 15); noGroupChunks.forEach((chunk, index) => { const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки'; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: 'no_group', height: calculateGroupHeight(chunk.length) }); }); } // Сортируем по высоте (от высоких к низким для лучшего заполнения) allGroupChunks.sort((a, b) => b.height - a.height); // Создаем адаптивную masonry компоновку const columns = createAdaptiveMasonryLayout(allGroupChunks); // Очищаем контейнер и добавляем колонки container.innerHTML = ''; const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; columns.forEach(col => { if (col.children.length > 0) { columnsContainer.appendChild(col); } }); container.appendChild(columnsContainer); updateStats(); }
// Функция для создания секции группы 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 setupResizeHandler() { let resizeTimeout; window.addEventListener('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (showGroups && filteredIcons.length > 0) { renderIconsWithGroups(); } }, 250); }); }
// Остальные функции без изменений function processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); // Для режима без групп используем простые колонки const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('searchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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); } }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); setupResizeHandler(); // Добавляем обработчик изменения размера }); </script> </body> </html>
|
у меня есть las файл геофизических ислледований он имеет кодировку ANSI и неправильную информацию в заглоловке (~Well information и ~Version information) , которую необходимо переработать, а по результату сделать экспорт файла в кодировке уже UTF-8 и обязательно со спецификацией. Данную программу необходимо написать в HTML. А в заголовке написать "Конвертер ЛАС файлов для OIS TERRA"
вот исходная часть которую необходимо менять
~Version information VERS. 2.20: CWLS LAS - VERSION 2.20 WRAP. NO: One line per depth step
~Well information # MNEM.UNIT DATA TYPE INFORMATION # ====.================================: =================== STRT.M 2.20:Начальная глубина STOP.M 51.00:Конечная глубина STEP.M 0.09999999999999964:Шаг квантования по глубине NULL. -999999.00:Null values COMP. :БГРЭ WELL. :/М-563 FLD :Хампинский-3/Бюгюехский-1 LOC . LOCATION: CNTY. COUNTY: STAT. STATE: CTRY. COUNTRY: SRVC : DATE. LOG DATE: METD. METHOD: ~Curve information # MNEM.UNIT API CODE CURVE DESCRIPTION # ====.================================:==================== DEPT.M :Глубина K.имп/мин : TH.имп/мин : U.имп/мин : ИК.мСим/м : КМВ.*10^(-5)ед.СИ : СГК.мкР/час : СМ.нТл : ~Parameter information block # MNEM.UNIT VALUE DESCRIPTION # ====.================================:====================
~Other information # ------------------- REMARKS AREA ------------------------ # ==========================================================
~Ascii Log Data 2.20 -9999.00 -9999.00 -9999.00 3.277 -9999.00 -9999.00 -9999.00
и там дальше остальные даные
Необходимо получить шапку в таком формате:
~Version information VERS. 2.0: CWLS LAS - VERSION 2.20 WRAP. NO: One line per depth step
~Well information # MNEM.UNIT DATA TYPE INFORMATION # ====.================================: =================== STRT.M 2.20: Начальная глубина STOP.M 51.00: Конечная глубина DRILL.M 51.00: Глубина по бурению, м STEP.M 0.09999999999999964: Шаг квантования по глубине NULL. -999999.00: Поле забоя COMP. БГРЭ: Партия WELL. /М-563: Скважина OBT. Хампинский-3/Бюгюехский-1: Объект SRVC. БГРЭ: Предприятие-исполнитель ~Curve information # MNEM.UNIT API CODE CURVE DESCRIPTION # ====.================================:==================== MD.м :Depth K.имп/мин : TH.имп/мин : U.имп/мин : ИК.мСим/м : КМВ.*10^(-5)ед.СИ : СГК.мкР/час : СМ.нТл : ~Ascii Log Data 2.20 -9999.00 -9999.00 -9999.00 3.277 -9999.00 -9999.00 -9999.00
там учти часть убирается, что-то добавляется а какие-то данные прописываются из других, и учти что после ~Ascii Log Data там у разных файлов свои данные и их много, также и методы там могут быть разные, объекты, номера и т.д.
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Конвертер ЛАС файлов для OIS TERRA</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; line-height: 1.6; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; } .container { background-color: #f9f9f9; border-radius: 8px; padding: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .input-section, .output-section { margin-bottom: 20px; } textarea { width: 100%; height: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; margin-right: 10px; } button:hover { background-color: #2980b9; } .file-input { margin-bottom: 10px; } .specification { margin-top: 20px; padding: 15px; background-color: #e8f4fc; border-radius: 4px; font-size: 14px; } .specification h3 { margin-top: 0; } .status { margin-top: 10px; padding: 10px; border-radius: 4px; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } </style> </head> <body> <h1>Конвертер ЛАС файлов для OIS TERRA</h1> <div class="container"> <div class="input-section"> <h2>Входной LAS файл</h2> <input type="file" id="fileInput" class="file-input" accept=".las"> <textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea> <button id="convertBtn">Конвертировать</button> <button id="downloadBtn" disabled>Скачать результат</button> <div id="status" class="status"></div> </div> <div class="output-section"> <h2>Результат конвертации</h2> <textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea> </div> <div class="specification"> <h3>Спецификация преобразования:</h3> <ul> <li>Изменение кодировки с ANSI на UTF-8</li> <li>Обновление версии LAS с 2.20 на 2.0</li> <li>Переработка секции ~Well information: <ul> <li>Добавление поля DRILL.M со значением STOP.M</li> <li>Изменение описания NULL. на "Поле забоя"</li> <li>Заполнение COMP. значением "БГРЭ"</li> <li>Заполнение WELL. значением "/М-563"</li> <li>Добавление поля OBT. со значением FLD</li> <li>Заполнение SRVC. значением "БГРЭ"</li> </ul> </li> <li>Переименование DEPT.M в MD.м в секции ~Curve information</li> <li>Удаление секций ~Parameter information block и ~Other information</li> <li>Сохранение всех данных из секции ~Ascii Log Data</li> </ul> </div> </div>
<script> document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { document.getElementById('inputText').value = e.target.result; }; reader.readAsText(file, 'windows-1251'); // Чтение в кодировке ANSI } });
document.getElementById('convertBtn').addEventListener('click', function() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error'); return; } try { const convertedText = convertLasFile(inputText); document.getElementById('outputText').value = convertedText; document.getElementById('downloadBtn').disabled = false; showStatus('Файл успешно сконвертирован!', 'success'); } catch (error) { showStatus('Ошибка при конвертации: ' + error.message, 'error'); } });
document.getElementById('downloadBtn').addEventListener('click', function() { const outputText = document.getElementById('outputText').value; if (!outputText.trim()) { showStatus('Нет данных для скачивания', 'error'); return; } const blob = new Blob([outputText], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'converted_file.las'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); });
function convertLasFile(inputText) { // Разделяем файл на секции const sections = splitIntoSections(inputText); // Обрабатываем каждую секцию let result = ''; // Обработка секции ~Version information if (sections.version) { result += '~Version information\n'; const lines = sections.version.split('\n'); for (let line of lines) { if (line.startsWith('VERS.')) { // Заменяем версию на 2.0 result += line.replace('VERS. 2.20', 'VERS. 2.0') + '\n'; } else if (line.trim() !== '') { result += line + '\n'; } } result += '\n'; } // Обработка секции ~Well information if (sections.well) { result += '~Well information\n'; const lines = sections.well.split('\n'); // Извлекаем значения из исходного файла const values = extractWellValues(lines); // Формируем новую секцию result += '# MNEM.UNIT DATA TYPE INFORMATION\n'; result += '# ====.================================: ===================\n'; result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\n`; result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\n`; result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\n`; result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\n`; result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\n`; result += `COMP. БГРЭ: Партия\n`; result += `WELL. /М-563: Скважина\n`; result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\n`; result += `SRVC. БГРЭ: Предприятие-исполнитель\n`; result += '\n'; } // Обработка секции ~Curve information if (sections.curve) { result += '~Curve information\n'; const lines = sections.curve.split('\n'); for (let line of lines) { if (line.startsWith('DEPT.M')) { // Заменяем DEPT.M на MD.м result += line.replace('DEPT.M', 'MD.м') + '\n'; } else if (line.trim() !== '') { result += line + '\n'; } } result += '\n'; } // Добавляем секцию ~Ascii Log Data if (sections.ascii) { result += '~Ascii Log Data\n'; result += sections.ascii + '\n'; } return result; }
function splitIntoSections(text) { const sections = {}; const lines = text.split('\n'); let currentSection = ''; let sectionContent = ''; for (let line of lines) { if (line.startsWith('~')) { // Сохраняем предыдущую секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } // Начинаем новую секцию currentSection = line.substring(1).toLowerCase().split(' ')[0]; sectionContent = ''; } else if (currentSection) { sectionContent += line + '\n'; } } // Сохраняем последнюю секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } return sections; }
function extractWellValues(lines) { const values = {}; for (let line of lines) { // Пропускаем комментарии и пустые строки if (line.startsWith('#') || line.trim() === '') continue; // Разбираем строку на части const parts = line.split(/[: ]+/); if (parts.length >= 2) { const mnem = parts[0].split('.')[0]; // Получаем мнемонику const value = parts[1]; // Получаем значение if (mnem && value) { values[mnem] = value; } } } return values; }
function showStatus(message, type) { const statusDiv = document.getElementById('status'); statusDiv.textContent = message; statusDiv.className = 'status ' + type; } </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>Конвертер ЛАС файлов для OIS TERRA</title> <style> body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; background-color: #f5f5f5; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; padding-bottom: 10px; border-bottom: 2px solid #3498db; } .container { background-color: white; border-radius: 8px; padding: 25px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .input-section, .output-section { margin-bottom: 25px; } h2 { color: #3498db; margin-top: 0; } textarea { width: 100%; height: 200px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical; font-size: 14px; } .button-group { margin: 15px 0; display: flex; flex-wrap: wrap; gap: 10px; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button:disabled { background-color: #95a5a6; cursor: not-allowed; } .file-input { margin-bottom: 15px; } .specification { margin-top: 25px; padding: 20px; background-color: #e8f4fc; border-radius: 4px; font-size: 14px; border-left: 4px solid #3498db; } .specification h3 { margin-top: 0; color: #2c3e50; } .status { margin-top: 15px; padding: 12px; border-radius: 4px; font-weight: bold; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .section-title { display: flex; align-items: center; margin-bottom: 10px; } .section-title h2 { margin: 0; } .icon { margin-right: 10px; font-size: 20px; } .encoding-info { font-size: 12px; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <h1>Конвертер ЛАС файлов для OIS TERRA</h1> <div class="container"> <div class="input-section"> <div class="section-title"> <span class="icon">📥</span> <h2>Входной LAS файл (ANSI)</h2> </div> <input type="file" id="fileInput" class="file-input" accept=".las"> <textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea> <div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div> <div class="button-group"> <button id="convertBtn">Конвертировать</button> <button id="downloadBtn" disabled>Скачать результат</button> <button id="clearBtn">Очистить</button> </div> <div id="status" class="status"></div> </div> <div class="output-section"> <div class="section-title"> <span class="icon">📤</span> <h2>Результат конвертации (UTF-8 с BOM)</h2> </div> <textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea> <div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div> </div> <div class="specification"> <h3>Спецификация преобразования:</h3> <ul> <li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li> <li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li> <li><strong>Версия LAS:</strong> с 2.20 на 2.0</li> <li><strong>Переработка секции ~Well information:</strong> <ul> <li>Добавление поля DRILL.M со значением STOP.M</li> <li>Изменение описания NULL. на "Поле забоя"</li> <li>Заполнение COMP. значением "БГРЭ"</li> <li>Заполнение WELL. значением "/М-563"</li> <li>Добавление поля OBT. со значением FLD</li> <li>Заполнение SRVC. значением "БГРЭ"</li> <li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li> </ul> </li> <li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li> <li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li> <li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li> </ul> </div> </div>
<script> document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { document.getElementById('inputText').value = e.target.result; showStatus('Файл загружен успешно', 'success'); }; reader.onerror = function() { showStatus('Ошибка при чтении файла', 'error'); }; // Чтение в кодировке ANSI (Windows-1251) reader.readAsText(file, 'windows-1251'); } });
document.getElementById('convertBtn').addEventListener('click', function() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error'); return; } try { const convertedText = convertLasFile(inputText); document.getElementById('outputText').value = convertedText; document.getElementById('downloadBtn').disabled = false; showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success'); } catch (error) { showStatus('Ошибка при конвертации: ' + error.message, 'error'); console.error(error); } });
document.getElementById('downloadBtn').addEventListener('click', function() { const outputText = document.getElementById('outputText').value; if (!outputText.trim()) { showStatus('Нет данных для скачивания', 'error'); return; } // Добавляем BOM для UTF-8 const bom = '\uFEFF'; const textWithBom = bom + outputText; // Создаем Blob с правильной кодировкой const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'converted_file_utf8.las'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success'); });
document.getElementById('clearBtn').addEventListener('click', function() { document.getElementById('inputText').value = ''; document.getElementById('outputText').value = ''; document.getElementById('fileInput').value = ''; document.getElementById('downloadBtn').disabled = true; showStatus('Поля очищены', 'info'); });
function convertLasFile(inputText) { // Разделяем файл на секции const sections = splitIntoSections(inputText); // Обрабатываем каждую секцию let result = ''; // Обработка секции ~Version information if (sections.version) { result += '~Version information\r\n'; const lines = sections.version.split('\n'); for (let line of lines) { if (line.startsWith('VERS.')) { // Заменяем версию на 2.0 result += line.replace('VERS. 2.20', 'VERS. 2.0') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Обработка секции ~Well information if (sections.well) { result += '~Well information\r\n'; const lines = sections.well.split('\n'); // Извлекаем значения из исходного файла const values = extractWellValues(lines); // Формируем новую секцию result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n'; result += '# ====.================================: ===================\r\n'; result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`; result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`; result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`; result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`; result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`; result += `COMP. БГРЭ: Партия\r\n`; result += `WELL. /М-563: Скважина\r\n`; result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`; result += `SRVC. БГРЭ: Предприятие-исполнитель\r\n`; result += '\r\n'; } // Обработка секции ~Curve information if (sections.curve) { result += '~Curve information\r\n'; const lines = sections.curve.split('\n'); for (let line of lines) { if (line.startsWith('DEPT.M')) { // Заменяем DEPT.M на MD.м result += line.replace('DEPT.M', 'MD.м') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Добавляем секцию ~Ascii Log Data if (sections.ascii) { result += '~Ascii Log Data\r\n'; // Сохраняем данные как есть, но с правильными окончаниями строк const asciiLines = sections.ascii.split('\n'); for (let line of asciiLines) { if (line.trim() !== '') { result += line + '\r\n'; } } } return result; }
function splitIntoSections(text) { const sections = {}; const lines = text.split('\n'); let currentSection = ''; let sectionContent = ''; for (let line of lines) { if (line.startsWith('~')) { // Сохраняем предыдущую секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } // Начинаем новую секцию currentSection = line.substring(1).toLowerCase().split(' ')[0]; sectionContent = ''; } else if (currentSection) { sectionContent += line + '\n'; } } // Сохраняем последнюю секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } return sections; }
function extractWellValues(lines) { const values = {}; for (let line of lines) { // Пропускаем комментарии и пустые строки if (line.startsWith('#') || line.trim() === '') continue; // Разбираем строку на части const parts = line.split(/[: ]+/); if (parts.length >= 2) { const mnem = parts[0].split('.')[0]; // Получаем мнемонику const value = parts[1]; // Получаем значение if (mnem && value) { values[mnem] = value; } } } return values; }
function showStatus(message, type) { const statusDiv = document.getElementById('status'); statusDiv.textContent = message; statusDiv.className = 'status ' + type; // Автоматически скрыть сообщение через 5 секунд setTimeout(() => { statusDiv.textContent = ''; statusDiv.className = 'status'; }, 5000); } </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>Конвертер ЛАС файлов для OIS TERRA</title> <style> body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; background-color: #f5f5f5; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; padding-bottom: 10px; border-bottom: 2px solid #3498db; } .container { background-color: white; border-radius: 8px; padding: 25px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .input-section, .output-section { margin-bottom: 25px; } h2 { color: #3498db; margin-top: 0; } textarea { width: 100%; height: 200px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical; font-size: 14px; } .button-group { margin: 15px 0; display: flex; flex-wrap: wrap; gap: 10px; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button:disabled { background-color: #95a5a6; cursor: not-allowed; } .file-input { margin-bottom: 15px; } .specification { margin-top: 25px; padding: 20px; background-color: #e8f4fc; border-radius: 4px; font-size: 14px; border-left: 4px solid #3498db; } .specification h3 { margin-top: 0; color: #2c3e50; } .status { margin-top: 15px; padding: 12px; border-radius: 4px; font-weight: bold; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .section-title { display: flex; align-items: center; margin-bottom: 10px; } .section-title h2 { margin: 0; } .icon { margin-right: 10px; font-size: 20px; } .encoding-info { font-size: 12px; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <h1>Конвертер ЛАС файлов для OIS TERRA</h1> <div class="container"> <div class="input-section"> <div class="section-title"> <span class="icon">📥</span> <h2>Входной LAS файл (ANSI)</h2> </div> <input type="file" id="fileInput" class="file-input" accept=".las"> <textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea> <div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div> <div class="button-group"> <button id="convertBtn">Конвертировать</button> <button id="downloadBtn" disabled>Скачать результат</button> <button id="clearBtn">Очистить</button> </div> <div id="status" class="status"></div> </div> <div class="output-section"> <div class="section-title"> <span class="icon">📤</span> <h2>Результат конвертации (UTF-8 с BOM)</h2> </div> <textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea> <div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div> </div> <div class="specification"> <h3>Спецификация преобразования:</h3> <ul> <li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li> <li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li> <li><strong>Версия LAS:</strong> с 2.20 на 2.0</li> <li><strong>Переработка секции ~Well information:</strong> <ul> <li>Добавление поля DRILL.M со значением STOP.M</li> <li>Изменение описания NULL. на "Поле забоя"</li> <li>COMP. - данные берутся из исходного файла</li> <li>WELL. - данные берутся из исходного файла</li> <li>OBT. - данные берутся из поля FLD исходного файла</li> <li>SRVC. - данные берутся из поля COMP исходного файла</li> <li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li> </ul> </li> <li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li> <li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li> <li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li> </ul> </div> </div>
<script> document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { document.getElementById('inputText').value = e.target.result; showStatus('Файл загружен успешно', 'success'); }; reader.onerror = function() { showStatus('Ошибка при чтении файла', 'error'); }; // Чтение в кодировке ANSI (Windows-1251) reader.readAsText(file, 'windows-1251'); } });
document.getElementById('convertBtn').addEventListener('click', function() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error'); return; } try { const convertedText = convertLasFile(inputText); document.getElementById('outputText').value = convertedText; document.getElementById('downloadBtn').disabled = false; showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success'); } catch (error) { showStatus('Ошибка при конвертации: ' + error.message, 'error'); console.error(error); } });
document.getElementById('downloadBtn').addEventListener('click', function() { const outputText = document.getElementById('outputText').value; if (!outputText.trim()) { showStatus('Нет данных для скачивания', 'error'); return; } // Добавляем BOM для UTF-8 const bom = '\uFEFF'; const textWithBom = bom + outputText; // Создаем Blob с правильной кодировкой const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'converted_file_utf8.las'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success'); });
document.getElementById('clearBtn').addEventListener('click', function() { document.getElementById('inputText').value = ''; document.getElementById('outputText').value = ''; document.getElementById('fileInput').value = ''; document.getElementById('downloadBtn').disabled = true; showStatus('Поля очищены', 'info'); });
function convertLasFile(inputText) { // Разделяем файл на секции const sections = splitIntoSections(inputText); // Обрабатываем каждую секцию let result = ''; // Обработка секции ~Version information if (sections.version) { result += '~Version information\r\n'; const lines = sections.version.split('\n'); for (let line of lines) { if (line.startsWith('VERS.')) { // Заменяем версию на 2.0 - исправленная строка result += line.replace('VERS. 2.20', 'VERS. 2.0').replace('VERS. 2.20:', 'VERS. 2.0:') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Обработка секции ~Well information if (sections.well) { result += '~Well information\r\n'; const lines = sections.well.split('\n'); // Извлекаем значения из исходного файла const values = extractWellValues(lines); // Формируем новую секцию result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n'; result += '# ====.================================: ===================\r\n'; result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`; result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`; result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`; result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`; result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`; result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`; result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`; result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`; result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`; result += '\r\n'; } // Обработка секции ~Curve information if (sections.curve) { result += '~Curve information\r\n'; const lines = sections.curve.split('\n'); for (let line of lines) { if (line.startsWith('DEPT.M')) { // Заменяем DEPT.M на MD.м result += line.replace('DEPT.M', 'MD.м') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Добавляем секцию ~Ascii Log Data if (sections.ascii) { result += '~Ascii Log Data\r\n'; // Сохраняем данные как есть, но с правильными окончаниями строк const asciiLines = sections.ascii.split('\n'); for (let line of asciiLines) { if (line.trim() !== '') { result += line + '\r\n'; } } } return result; }
function splitIntoSections(text) { const sections = {}; const lines = text.split('\n'); let currentSection = ''; let sectionContent = ''; for (let line of lines) { if (line.startsWith('~')) { // Сохраняем предыдущую секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } // Начинаем новую секцию currentSection = line.substring(1).toLowerCase().split(' ')[0]; sectionContent = ''; } else if (currentSection) { sectionContent += line + '\n'; } } // Сохраняем последнюю секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } return sections; }
function extractWellValues(lines) { const values = {}; for (let line of lines) { // Пропускаем комментарии и пустые строки if (line.startsWith('#') || line.trim() === '') continue; // Разбираем строку на части - исправленный парсинг const colonIndex = line.indexOf(':'); if (colonIndex !== -1) { const beforeColon = line.substring(0, colonIndex).trim(); const afterColon = line.substring(colonIndex + 1).trim(); // Разделяем часть до двоеточия на мнемонику и значение const parts = beforeColon.split(/\s+/); if (parts.length >= 2) { const mnem = parts[0].split('.')[0]; // Получаем мнемонику const value = parts[1]; // Получаем значение if (mnem) { // Сохраняем значение из поля (часть до двоеточия) values[mnem] = value; // Также сохраняем описание (часть после двоеточия) если нужно values[mnem + '_DESC'] = afterColon; } } else if (parts.length === 1) { // Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ") const mnem = parts[0].split('.')[0]; if (mnem) { // Значение берем из части после двоеточия values[mnem] = afterColon; values[mnem + '_DESC'] = afterColon; } } } } return values; }
function showStatus(message, type) { const statusDiv = document.getElementById('status'); statusDiv.textContent = message; statusDiv.className = 'status ' + type; // Автоматически скрыть сообщение через 5 секунд setTimeout(() => { statusDiv.textContent = ''; statusDiv.className = 'status'; }, 5000); } </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>Конвертер ЛАС файлов для OIS TERRA</title> <style> body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; background-color: #f5f5f5; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; padding-bottom: 10px; border-bottom: 2px solid #3498db; } .container { background-color: white; border-radius: 8px; padding: 25px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .input-section, .output-section { margin-bottom: 25px; } h2 { color: #3498db; margin-top: 0; } textarea { width: 100%; height: 200px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical; font-size: 14px; } .button-group { margin: 15px 0; display: flex; flex-wrap: wrap; gap: 10px; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button:disabled { background-color: #95a5a6; cursor: not-allowed; } .file-input { margin-bottom: 15px; } .specification { margin-top: 25px; padding: 20px; background-color: #e8f4fc; border-radius: 4px; font-size: 14px; border-left: 4px solid #3498db; } .specification h3 { margin-top: 0; color: #2c3e50; } .status { margin-top: 15px; padding: 12px; border-radius: 4px; font-weight: bold; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .section-title { display: flex; align-items: center; margin-bottom: 10px; } .section-title h2 { margin: 0; } .icon { margin-right: 10px; font-size: 20px; } .encoding-info { font-size: 12px; color: #7f8c8d; margin-top: 5px; } </style> </head> <body> <h1>Конвертер ЛАС файлов для OIS TERRA</h1> <div class="container"> <div class="input-section"> <div class="section-title"> <span class="icon">📥</span> <h2>Входной LAS файл (ANSI)</h2> </div> <input type="file" id="fileInput" class="file-input" accept=".las"> <textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea> <div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div> <div class="button-group"> <button id="convertBtn">Конвертировать</button> <button id="downloadBtn" disabled>Скачать результат</button> <button id="clearBtn">Очистить</button> </div> <div id="status" class="status"></div> </div> <div class="output-section"> <div class="section-title"> <span class="icon">📤</span> <h2>Результат конвертации (UTF-8 с BOM)</h2> </div> <textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea> <div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div> </div> <div class="specification"> <h3>Спецификация преобразования:</h3> <ul> <li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li> <li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li> <li><strong>Версия LAS:</strong> с 2.20 на 2.0</li> <li><strong>Переработка секции ~Well information:</strong> <ul> <li>Добавление поля DRILL.M со значением STOP.M</li> <li>Изменение описания NULL. на "Поле забоя"</li> <li>COMP. - данные берутся из исходного файла</li> <li>WELL. - данные берутся из исходного файла</li> <li>OBT. - данные берутся из поля FLD исходного файла</li> <li>SRVC. - данные берутся из поля COMP исходного файла</li> <li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li> </ul> </li> <li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li> <li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li> <li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li> </ul> </div> </div>
<script> document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { document.getElementById('inputText').value = e.target.result; showStatus('Файл загружен успешно', 'success'); }; reader.onerror = function() { showStatus('Ошибка при чтении файла', 'error'); }; // Чтение в кодировке ANSI (Windows-1251) reader.readAsText(file, 'windows-1251'); } });
document.getElementById('convertBtn').addEventListener('click', function() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error'); return; } try { const convertedText = convertLasFile(inputText); document.getElementById('outputText').value = convertedText; document.getElementById('downloadBtn').disabled = false; showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success'); } catch (error) { showStatus('Ошибка при конвертации: ' + error.message, 'error'); console.error(error); } });
document.getElementById('downloadBtn').addEventListener('click', function() { const outputText = document.getElementById('outputText').value; if (!outputText.trim()) { showStatus('Нет данных для скачивания', 'error'); return; } // Добавляем BOM для UTF-8 const bom = '\uFEFF'; const textWithBom = bom + outputText; // Создаем Blob с правильной кодировкой const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'converted_file_utf8.las'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showStatus('Файл успешно скачан в кодировке UTF-8 с BOM', 'success'); });
document.getElementById('clearBtn').addEventListener('click', function() { document.getElementById('inputText').value = ''; document.getElementById('outputText').value = ''; document.getElementById('fileInput').value = ''; document.getElementById('downloadBtn').disabled = true; showStatus('Поля очищены', 'info'); });
function convertLasFile(inputText) { // Разделяем файл на секции const sections = splitIntoSections(inputText); // Обрабатываем каждую секцию let result = ''; // Обработка секции ~Version information if (sections.version) { result += '~Version information\r\n'; const lines = sections.version.split('\n'); for (let line of lines) { if (line.startsWith('VERS.')) { // Заменяем версию на 2.0 - улучшенная замена let newLine = line; // Заменяем все возможные варианты записи версии 2.20 newLine = newLine.replace(/VERS\.\s*2\.20/, 'VERS. 2.0'); newLine = newLine.replace(/VERS\.\s*2\.20:/, 'VERS. 2.0:'); newLine = newLine.replace(/2\.20\s*:/, '2.0:'); newLine = newLine.replace(/2\.20\s*CWLS/, '2.0: CWLS'); result += newLine + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Обработка секции ~Well information if (sections.well) { result += '~Well information\r\n'; const lines = sections.well.split('\n'); // Извлекаем значения из исходного файла const values = extractWellValues(lines); // Формируем новую секцию result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n'; result += '# ====.================================: ===================\r\n'; result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`; result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`; result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`; result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`; result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`; result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`; result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`; result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`; result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`; result += '\r\n'; } // Обработка секции ~Curve information if (sections.curve) { result += '~Curve information\r\n'; const lines = sections.curve.split('\n'); for (let line of lines) { if (line.startsWith('DEPT.M')) { // Заменяем DEPT.M на MD.м result += line.replace('DEPT.M', 'MD.м') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Добавляем секцию ~Ascii Log Data if (sections.ascii) { result += '~Ascii Log Data\r\n'; // Сохраняем данные как есть, но с правильными окончаниями строк const asciiLines = sections.ascii.split('\n'); for (let line of asciiLines) { if (line.trim() !== '') { result += line + '\r\n'; } } } return result; }
function splitIntoSections(text) { const sections = {}; const lines = text.split('\n'); let currentSection = ''; let sectionContent = ''; for (let line of lines) { if (line.startsWith('~')) { // Сохраняем предыдущую секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } // Начинаем новую секцию currentSection = line.substring(1).toLowerCase().split(' ')[0]; sectionContent = ''; } else if (currentSection) { sectionContent += line + '\n'; } } // Сохраняем последнюю секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } return sections; }
function extractWellValues(lines) { const values = {}; for (let line of lines) { // Пропускаем комментарии и пустые строки if (line.startsWith('#') || line.trim() === '') continue; // Разбираем строку на части - исправленный парсинг const colonIndex = line.indexOf(':'); if (colonIndex !== -1) { const beforeColon = line.substring(0, colonIndex).trim(); const afterColon = line.substring(colonIndex + 1).trim(); // Разделяем часть до двоеточия на мнемонику и значение const parts = beforeColon.split(/\s+/); if (parts.length >= 2) { const mnem = parts[0].split('.')[0]; // Получаем мнемонику const value = parts[1]; // Получаем значение if (mnem) { // Сохраняем значение из поля (часть до двоеточия) values[mnem] = value; // Также сохраняем описание (часть после двоеточия) если нужно values[mnem + '_DESC'] = afterColon; } } else if (parts.length === 1) { // Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ") const mnem = parts[0].split('.')[0]; if (mnem) { // Значение берем из части после двоеточия values[mnem] = afterColon; values[mnem + '_DESC'] = afterColon; } } } } return values; }
function showStatus(message, type) { const statusDiv = document.getElementById('status'); statusDiv.textContent = message; statusDiv.className = 'status ' + type; // Автоматически скрыть сообщение через 5 секунд setTimeout(() => { statusDiv.textContent = ''; statusDiv.className = 'status'; }, 5000); } </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>Конвертер ЛАС файлов для OIS TERRA</title> <style> body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; background-color: #f5f5f5; } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; padding-bottom: 10px; border-bottom: 2px solid #3498db; } .container { background-color: white; border-radius: 8px; padding: 25px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .input-section, .output-section { margin-bottom: 25px; } h2 { color: #3498db; margin-top: 0; } textarea { width: 100%; height: 200px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; resize: vertical; font-size: 14px; } .button-group { margin: 15px 0; display: flex; flex-wrap: wrap; gap: 10px; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; } button:disabled { background-color: #95a5a6; cursor: not-allowed; } .file-input { margin-bottom: 15px; } .specification { margin-top: 25px; padding: 20px; background-color: #e8f4fc; border-radius: 4px; font-size: 14px; border-left: 4px solid #3498db; } .specification h3 { margin-top: 0; color: #2c3e50; } .status { margin-top: 15px; padding: 12px; border-radius: 4px; font-weight: bold; } .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; } .section-title { display: flex; align-items: center; margin-bottom: 10px; } .section-title h2 { margin: 0; } .icon { margin-right: 10px; font-size: 20px; } .encoding-info { font-size: 12px; color: #7f8c8d; margin-top: 5px; } .file-info { background-color: #f8f9fa; padding: 10px; border-radius: 4px; margin-bottom: 10px; font-size: 14px; } </style> </head> <body> <h1>Конвертер ЛАС файлов для OIS TERRA</h1> <div class="container"> <div class="input-section"> <div class="section-title"> <span class="icon">📥</span> <h2>Входной LAS файл (ANSI)</h2> </div> <input type="file" id="fileInput" class="file-input" accept=".las"> <div id="fileInfo" class="file-info" style="display: none;"> <strong>Загружен файл:</strong> <span id="fileName"></span> </div> <textarea id="inputText" placeholder="Загрузите LAS файл или вставьте его содержимое здесь..."></textarea> <div class="encoding-info">Текущая кодировка: ANSI (Windows-1251)</div> <div class="button-group"> <button id="convertBtn">Конвертировать</button> <button id="downloadBtn" disabled>Скачать результат</button> <button id="clearBtn">Очистить</button> </div> <div id="status" class="status"></div> </div> <div class="output-section"> <div class="section-title"> <span class="icon">📤</span> <h2>Результат конвертации (UTF-8 с BOM)</h2> </div> <textarea id="outputText" placeholder="Здесь появится результат конвертации..." readonly></textarea> <div class="encoding-info">Кодировка результата: UTF-8 с BOM, окончания строк: Windows (CR+LF)</div> </div> <div class="specification"> <h3>Спецификация преобразования:</h3> <ul> <li><strong>Изменение кодировки:</strong> с ANSI (Windows-1251) на UTF-8 с BOM</li> <li><strong>Окончания строк:</strong> преобразование в Windows формат (CR+LF)</li> <li><strong>Версия LAS:</strong> с 2.20 на 2.0</li> <li><strong>Переработка секции ~Well information:</strong> <ul> <li>Добавление поля DRILL.M со значением STOP.M</li> <li>Изменение описания NULL. на "Поле забоя"</li> <li>COMP. - данные берутся из исходного файла</li> <li>WELL. - данные берутся из исходного файла</li> <li>OBT. - данные берутся из поля FLD исходного файла</li> <li>SRVC. - данные берутся из поля COMP исходного файла</li> <li>Удаление полей: LOC, CNTY, STAT, CTRY, DATE, METD</li> </ul> </li> <li><strong>Секция ~Curve information:</strong> переименование DEPT.M в MD.м</li> <li><strong>Удаление секций:</strong> ~Parameter information block и ~Other information</li> <li><strong>Сохранение данных:</strong> всех данных из секции ~Ascii Log Data</li> </ul> </div> </div>
<script> let originalFileName = '';
document.getElementById('fileInput').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { originalFileName = file.name; // Показываем информацию о файле document.getElementById('fileName').textContent = originalFileName; document.getElementById('fileInfo').style.display = 'block'; const reader = new FileReader(); reader.onload = function(e) { document.getElementById('inputText').value = e.target.result; showStatus('Файл загружен успешно', 'success'); }; reader.onerror = function() { showStatus('Ошибка при чтении файла', 'error'); }; // Чтение в кодировке ANSI (Windows-1251) reader.readAsText(file, 'windows-1251'); } });
document.getElementById('convertBtn').addEventListener('click', function() { const inputText = document.getElementById('inputText').value; if (!inputText.trim()) { showStatus('Пожалуйста, загрузите или вставьте LAS файл', 'error'); return; } try { const convertedText = convertLasFile(inputText); document.getElementById('outputText').value = convertedText; document.getElementById('downloadBtn').disabled = false; showStatus('Файл успешно сконвертирован! Кодировка: UTF-8 с BOM', 'success'); } catch (error) { showStatus('Ошибка при конвертации: ' + error.message, 'error'); console.error(error); } });
document.getElementById('downloadBtn').addEventListener('click', function() { const outputText = document.getElementById('outputText').value; if (!outputText.trim()) { showStatus('Нет данных для скачивания', 'error'); return; } // Добавляем BOM для UTF-8 const bom = '\uFEFF'; const textWithBom = bom + outputText; // Формируем имя файла let downloadFileName; if (originalFileName) { // Убираем расширение .las если есть и добавляем приставку const baseName = originalFileName.replace(/\.las$/i, ''); downloadFileName = `converted_${baseName}.las`; } else { downloadFileName = 'converted_file_utf8.las'; } // Создаем Blob с правильной кодировкой const blob = new Blob([textWithBom], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = downloadFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showStatus(`Файл успешно скачан: ${downloadFileName}`, 'success'); });
document.getElementById('clearBtn').addEventListener('click', function() { document.getElementById('inputText').value = ''; document.getElementById('outputText').value = ''; document.getElementById('fileInput').value = ''; document.getElementById('fileInfo').style.display = 'none'; document.getElementById('downloadBtn').disabled = true; originalFileName = ''; showStatus('Поля очищены', 'info'); });
function convertLasFile(inputText) { // Разделяем файл на секции const sections = splitIntoSections(inputText); // Обрабатываем каждую секцию let result = ''; // Обработка секции ~Version information if (sections.version) { result += '~Version information\r\n'; const lines = sections.version.split('\n'); for (let line of lines) { if (line.startsWith('VERS.')) { // Заменяем версию на 2.0 - улучшенная замена let newLine = line; // Заменяем все возможные варианты записи версии 2.20 newLine = newLine.replace(/VERS\.\s*2\.20/, 'VERS. 2.0'); newLine = newLine.replace(/VERS\.\s*2\.20:/, 'VERS. 2.0:'); newLine = newLine.replace(/2\.20\s*:/, '2.0:'); newLine = newLine.replace(/2\.20\s*CWLS/, '2.0: CWLS'); result += newLine + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Обработка секции ~Well information if (sections.well) { result += '~Well information\r\n'; const lines = sections.well.split('\n'); // Извлекаем значения из исходного файла const values = extractWellValues(lines); // Формируем новую секцию result += '# MNEM.UNIT DATA TYPE INFORMATION\r\n'; result += '# ====.================================: ===================\r\n'; result += `STRT.M ${values.STRT || '2.20'}: Начальная глубина\r\n`; result += `STOP.M ${values.STOP || '51.00'}: Конечная глубина\r\n`; result += `DRILL.M ${values.STOP || '51.00'}: Глубина по бурению, м\r\n`; result += `STEP.M ${values.STEP || '0.09999999999999964'}: Шаг квантования по глубине\r\n`; result += `NULL. ${values.NULL || '-999999.00'}: Поле забоя\r\n`; result += `COMP. ${values.COMP || 'БГРЭ'}: Партия\r\n`; result += `WELL. ${values.WELL || '/М-563'}: Скважина\r\n`; result += `OBT. ${values.FLD || 'Хампинский-3/Бюгюехский-1'}: Объект\r\n`; result += `SRVC. ${values.COMP || 'БГРЭ'}: Предприятие-исполнитель\r\n`; result += '\r\n'; } // Обработка секции ~Curve information if (sections.curve) { result += '~Curve information\r\n'; const lines = sections.curve.split('\n'); for (let line of lines) { if (line.startsWith('DEPT.M')) { // Заменяем DEPT.M на MD.м result += line.replace('DEPT.M', 'MD.м') + '\r\n'; } else if (line.trim() !== '') { result += line + '\r\n'; } } result += '\r\n'; } // Добавляем секцию ~Ascii Log Data if (sections.ascii) { result += '~Ascii Log Data\r\n'; // Сохраняем данные как есть, но с правильными окончаниями строк const asciiLines = sections.ascii.split('\n'); for (let line of asciiLines) { if (line.trim() !== '') { result += line + '\r\n'; } } } return result; }
function splitIntoSections(text) { const sections = {}; const lines = text.split('\n'); let currentSection = ''; let sectionContent = ''; for (let line of lines) { if (line.startsWith('~')) { // Сохраняем предыдущую секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } // Начинаем новую секцию currentSection = line.substring(1).toLowerCase().split(' ')[0]; sectionContent = ''; } else if (currentSection) { sectionContent += line + '\n'; } } // Сохраняем последнюю секцию if (currentSection) { sections[currentSection] = sectionContent.trim(); } return sections; }
function extractWellValues(lines) { const values = {}; for (let line of lines) { // Пропускаем комментарии и пустые строки if (line.startsWith('#') || line.trim() === '') continue; // Разбираем строку на части - исправленный парсинг const colonIndex = line.indexOf(':'); if (colonIndex !== -1) { const beforeColon = line.substring(0, colonIndex).trim(); const afterColon = line.substring(colonIndex + 1).trim(); // Разделяем часть до двоеточия на мнемонику и значение const parts = beforeColon.split(/\s+/); if (parts.length >= 2) { const mnem = parts[0].split('.')[0]; // Получаем мнемонику const value = parts[1]; // Получаем значение if (mnem) { // Сохраняем значение из поля (часть до двоеточия) values[mnem] = value; // Также сохраняем описание (часть после двоеточия) если нужно values[mnem + '_DESC'] = afterColon; } } else if (parts.length === 1) { // Случай когда есть только мнемоника без значения (например "COMP. :БГРЭ") const mnem = parts[0].split('.')[0]; if (mnem) { // Значение берем из части после двоеточия values[mnem] = afterColon; values[mnem + '_DESC'] = afterColon; } } } } return values; }
function showStatus(message, type) { const statusDiv = document.getElementById('status'); statusDiv.textContent = message; statusDiv.className = 'status ' + type; // Автоматически скрыть сообщение через 5 секунд setTimeout(() => { statusDiv.textContent = ''; statusDiv.className = 'status'; }, 5000); } </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>Просмотр SVG иконок</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background-color: #f0f2f5; padding: 20px; }
.container { max-width: 1600px; margin: 0 auto; }
h1 { text-align: center; margin-bottom: 20px; color: #333; font-size: 24px; }
.controls { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; align-items: center; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
#folderInput { padding: 8px; border: 2px dashed #007bff; border-radius: 4px; background: white; cursor: pointer; font-size: 14px; }
#loadIcons { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
#loadIcons:hover { background: #0056b3; }
#searchInput { padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex: 1; min-width: 200px; font-size: 14px; }
.stats { margin-bottom: 15px; padding: 10px 15px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); font-size: 14px; }
/* Компактная сетка иконок */ .icons-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; margin-bottom: 20px; }
.icon-item { display: flex; flex-direction: column; align-items: center; padding: 8px; border: 1px solid #e1e5e9; border-radius: 6px; background: white; transition: all 0.2s ease; cursor: pointer; position: relative; }
.icon-item:hover { border-color: #007bff; background: #f8fbff; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,123,255,0.2); }
.icon-item.copied::after { content: '✓ Скопировано!'; position: absolute; top: -10px; background: #28a745; color: white; padding: 2px 6px; border-radius: 4px; font-size: 10px; z-index: 10; }
.icon { width: 32px; height: 32px; margin-bottom: 5px; object-fit: contain; }
.icon-name { font-size: 10px; color: #666; text-align: center; word-break: break-all; line-height: 1.2; max-width: 70px; }
.folder-badge { font-size: 9px; color: #888; background: #f0f0f0; padding: 1px 4px; border-radius: 3px; margin-top: 3px; max-width: 70px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.empty-state { text-align: center; padding: 40px; color: #999; font-style: italic; background: white; border-radius: 8px; grid-column: 1 / -1; }
.loading { text-align: center; padding: 20px; color: #007bff; grid-column: 1 / -1; }
/* Группировка по папкам */ .folder-group { margin-bottom: 20px; background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; }
.folder-header { background: #f8f9fa; padding: 12px 15px; border-bottom: 1px solid #e9ecef; font-weight: bold; color: #495057; font-size: 14px; display: flex; justify-content: between; align-items: center; }
.folder-count { background: #007bff; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: normal; margin-left: 10px; }
.folder-icons { display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 8px; padding: 12px; }
/* Режим просмотра */ .view-mode { display: flex; gap: 10px; align-items: center; }
.view-btn { padding: 6px 12px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; font-size: 12px; }
.view-btn.active { background: #007bff; color: white; border-color: #007bff; }
/* Уведомление */ .notification { position: fixed; top: 20px; right: 20px; background: #28a745; color: white; padding: 10px 15px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 1000; opacity: 0; transform: translateY(-20px); transition: all 0.3s ease; }
.notification.show { opacity: 1; transform: translateY(0); }
@media (max-width: 768px) { .icons-grid { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 6px; } .folder-icons { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 6px; } .controls { flex-direction: column; align-items: stretch; } #searchInput { min-width: auto; } }
@media (min-width: 1200px) { .icons-grid { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); } .folder-icons { grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); } } </style> </head> <body> <div class="container"> <h1>Просмотр SVG иконок</h1> <div class="controls"> <input type="file" id="folderInput" webkitdirectory directory multiple> <button id="loadIcons">Загрузить иконки</button> <input type="text" id="searchInput" placeholder="Поиск по имени..."> <div class="view-mode"> <button class="view-btn active" data-view="all">Все вместе</button> <button class="view-btn" data-view="grouped">По папкам</button> </div> </div>
<div class="stats" id="stats"> Выберите папку с SVG иконками </div>
<div id="iconsContainer"> <div class="empty-state"> Иконки появятся здесь после выбора папки </div> </div> </div>
<div id="notification" class="notification" style="display: none;"> SVG код скопирован в буфер обмена! </div>
<script> class IconViewer { constructor() { this.icons = []; this.filteredIcons = []; this.viewMode = 'all'; this.initializeEventListeners(); }
initializeEventListeners() { document.getElementById('loadIcons').addEventListener('click', () => { this.loadIconsFromFolder(); });
document.getElementById('searchInput').addEventListener('input', (e) => { this.filterIcons(e.target.value); });
document.getElementById('folderInput').addEventListener('change', () => { this.loadIconsFromFolder(); });
// Переключение режимов просмотра document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); this.viewMode = e.target.dataset.view; this.renderIcons(); }); }); }
async loadIconsFromFolder() { const folderInput = document.getElementById('folderInput'); const files = Array.from(folderInput.files); const svgFiles = files.filter(file => file.name.toLowerCase().endsWith('.svg') );
if (svgFiles.length === 0) { this.showMessage('SVG файлы не найдены в выбранной папке'); return; }
this.showLoading(`Найдено ${svgFiles.length} SVG файлов, загрузка...`);
this.icons = []; for (const file of svgFiles) { try { const iconData = await this.processSvgFile(file); this.icons.push(iconData); } catch (error) { console.error('Ошибка обработки файла:', file.name, error); } }
this.filteredIcons = [...this.icons]; this.renderIcons(); this.updateStats(); }
async processSvgFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { const svgContent = e.target.result; const imageUrl = URL.createObjectURL(file); // Получаем путь папки const fullPath = file.webkitRelativePath || file.name; let folderPath = 'корневая папка'; if (fullPath.includes('/')) { const parts = fullPath.split('/'); parts.pop(); // удаляем имя файла folderPath = parts.join('/'); }
// Преобразуем SVG в одну строку const singleLineSvg = this.svgToSingleLine(svgContent);
resolve({ name: file.name.replace('.svg', ''), fullName: file.name, url: imageUrl, folder: folderPath, fullPath: fullPath, size: file.size, svgCode: singleLineSvg, originalSvg: svgContent }); }; reader.onerror = reject; reader.readAsText(file); }); }
// Преобразует SVG в одну строку svgToSingleLine(svgContent) { return svgContent .replace(/[\r\n]+/g, ' ') // Заменяем переносы строк на пробелы .replace(/>\s+</g, '><') // Убираем пробелы между тегами .replace(/\s+/g, ' ') // Заменяем множественные пробелы на один .trim(); // Убираем пробелы в начале и конце }
// Копирование в буфер обмена async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); this.showNotification('SVG код скопирован в буфер обмена!'); return true; } catch (err) { // Fallback для старых браузеров const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); this.showNotification('SVG код скопирован в буфер обмена!'); return true; } catch (fallbackErr) { this.showNotification('Ошибка копирования!', true); return false; } finally { document.body.removeChild(textArea); } } }
// Показ уведомления showNotification(message, isError = false) { const notification = document.getElementById('notification'); notification.textContent = message; notification.style.background = isError ? '#dc3545' : '#28a745'; notification.style.display = 'block'; setTimeout(() => { notification.classList.add('show'); }, 10);
setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notification.style.display = 'none'; }, 300); }, 2000); }
filterIcons(searchTerm) { if (!searchTerm.trim()) { this.filteredIcons = [...this.icons]; } else { const term = searchTerm.toLowerCase(); this.filteredIcons = this.icons.filter(icon => icon.name.toLowerCase().includes(term) || icon.folder.toLowerCase().includes(term) || icon.fullName.toLowerCase().includes(term) ); } this.renderIcons(); this.updateStats(); }
renderIcons() { const container = document.getElementById('iconsContainer'); if (this.filteredIcons.length === 0) { container.innerHTML = '<div class="empty-state">Иконки не найдены</div>'; return; }
if (this.viewMode === 'all') { this.renderAllIcons(container); } else { this.renderGroupedIcons(container); } }
renderAllIcons(container) { container.innerHTML = '<div class="icons-grid" id="iconsGrid"></div>'; const grid = document.getElementById('iconsGrid'); this.filteredIcons.forEach(icon => { grid.appendChild(this.createIconElement(icon)); }); }
renderGroupedIcons(container) { container.innerHTML = ''; // Группируем иконки по папкам const folders = {}; this.filteredIcons.forEach(icon => { if (!folders[icon.folder]) { folders[icon.folder] = []; } folders[icon.folder].push(icon); });
// Сортируем папки по имени const sortedFolders = Object.keys(folders).sort(); sortedFolders.forEach(folderName => { const folderIcons = folders[folderName]; const folderGroup = document.createElement('div'); folderGroup.className = 'folder-group'; const folderHeader = document.createElement('div'); folderHeader.className = 'folder-header'; const folderNameSpan = document.createElement('span'); folderNameSpan.textContent = folderName; const folderCount = document.createElement('span'); folderCount.className = 'folder-count'; folderCount.textContent = folderIcons.length; folderHeader.appendChild(folderNameSpan); folderHeader.appendChild(folderCount); const folderIconsGrid = document.createElement('div'); folderIconsGrid.className = 'folder-icons'; folderIcons.forEach(icon => { const iconElement = this.createIconElement(icon); // Убираем бейдж папки в режиме группировки const folderBadge = iconElement.querySelector('.folder-badge'); if (folderBadge) { folderBadge.remove(); } folderIconsGrid.appendChild(iconElement); }); folderGroup.appendChild(folderHeader); folderGroup.appendChild(folderIconsGrid); container.appendChild(folderGroup); }); }
createIconElement(icon) { const div = document.createElement('div'); div.className = 'icon-item'; div.title = `Кликните для копирования SVG кода\n${icon.fullName}\nПапка: ${icon.folder}`;
div.innerHTML = ` <img src="${icon.url}" alt="${icon.name}" class="icon" onerror="this.style.display='none'; this.nextElementSibling.textContent='❌ ' + this.nextElementSibling.textContent"> <div class="icon-name">${this.truncateName(icon.name)}</div> ${this.viewMode === 'all' ? `<div class="folder-badge" title="${icon.folder}">${this.truncateFolder(icon.folder)}</div>` : ''} `;
div.addEventListener('click', async () => { const success = await this.copyToClipboard(icon.svgCode); if (success) { // Визуальная обратная связь div.classList.add('copied'); setTimeout(() => { div.classList.remove('copied'); }, 2000); } });
return div; }
truncateName(name) { return name.length > 12 ? name.substring(0, 10) + '...' : name; }
truncateFolder(folder) { if (folder === 'корневая папка') return 'корень'; return folder.length > 12 ? folder.substring(0, 10) + '...' : folder; }
formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + ['B', 'KB', 'MB'][i]; }
updateStats() { const stats = document.getElementById('stats'); if (this.icons.length === 0) { stats.textContent = 'Выберите папку с SVG иконками'; return; }
const total = this.icons.length; const filtered = this.filteredIcons.length; const folders = new Set(this.icons.map(icon => icon.folder)).size;
if (total === filtered) { stats.textContent = `Найдено ${total} иконок в ${folders} папках`; } else { stats.textContent = `Показано ${filtered} из ${total} иконок (${folders} папок)`; } }
showMessage(message) { document.getElementById('stats').textContent = message; document.getElementById('iconsContainer').innerHTML = `<div class="empty-state">${message}</div>`; }
showLoading(message) { document.getElementById('stats').textContent = message; document.getElementById('iconsContainer').innerHTML = `<div class="loading">${message}</div>`; } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { new IconViewer(); }); </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: 1200px; 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; }
.icon-search-input { width: 300px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
.icon-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; }
.show-groups-toggle { display: flex; align-items: center; gap: 8px; font-size: 12px; order: 2; }
.toggle-checkbox { width: 16px; height: 16px; }
.comparison-container { display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-start; justify-content: center; }
.group-section { break-inside: avoid; }
.group-header { background: #f8f9fa; padding: 6px 10px; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #495057; font-size: 13px; }
.icon-column { border: 1px solid #e0e0e0; border-radius: 5px; padding: 8px; background: #fafafa; width: 380px; 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; white-space: nowrap; /* Запрещаем перенос текста */ }
.icon-row { display: grid; grid-template-columns: 32px 20px 32px 1fr; gap: 8px; align-items: center; padding: 6px 3px; border-bottom: 1px solid #f0f0f0; min-height: 40px; }
.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 { filter: none; }
.arrow { text-align: center; color: #999; font-size: 14px; font-weight: bold; white-space: nowrap; /* Запрещаем перенос стрелки */ }
.icon-info { padding-left: 8px; padding-right: 5px; min-width: 0; width: 100%; }
.icon-name { color: #333; line-height: 1.3; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; text-align: left; font-size: 11px; max-width: 200px; }
.loading { text-align: center; padding: 20px; color: #666; width: 100%; }
.no-results { text-align: center; padding: 40px; color: #666; width: 100%; font-size: 14px; }
.columns-container { display: flex; flex-wrap: wrap; gap: 15px; width: 100%; justify-content: center; }
.masonry-column { display: flex; flex-direction: column; gap: 15px; }
/* Адаптивность для мобильных устройств */ @media (max-width: 768px) { .container { max-width: 100%; padding: 0 10px; } .icon-column { width: 100%; max-width: 380px; } .icon-search-input { width: 100%; max-width: 300px; } .controls-row { flex-wrap: wrap; gap: 10px; } /* На очень маленьких экранах уменьшаем отступы */ .column-header { gap: 4px; padding: 5px 2px; } .header-label { font-size: 10px; /* Чуть уменьшаем шрифт на мобильных */ } }
/* Для очень маленьких экранов */ @media (max-width: 480px) { .icon-column { width: 100%; max-width: 100%; } .column-header { grid-template-columns: 30px 16px 30px 1fr; gap: 3px; } .header-label { font-size: 9px; } .arrow { font-size: 12px; } } </style> </head> <body> <div class="container"> <h1>Сравнение иконок ИСИХОГИ старой и новой версий</h1> <div class="search-container"> <input type="text" id="iconSearchInput" class="icon-search-input" placeholder="Поиск по названию иконки..." autocomplete="off"> </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="show-groups-toggle"> <input type="checkbox" id="showGroupsToggle" class="toggle-checkbox" checked> <label for="showGroupsToggle">Показывать группы</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;
// Функция для разбивки больших групп на части function splitLargeGroup(icons, maxItems = 15) { if (icons.length <= maxItems) { return [icons]; } const chunks = []; for (let i = 0; i < icons.length; i += maxItems) { chunks.push(icons.slice(i, i + maxItems)); } return chunks; }
// Функция для расчета примерной высоты группы function calculateGroupHeight(iconsCount) { const headerHeight = 40; const rowHeight = 32; const padding = 16; return headerHeight + (iconsCount * rowHeight) + padding; }
// Функция для создания адаптивной masonry компоновки function createAdaptiveMasonryLayout(groupsChunks) { const container = document.getElementById('iconsContainer'); const containerWidth = container.clientWidth; const columnWidth = 380 + 15; const maxColumns = Math.floor(1200 / columnWidth); const availableColumns = Math.min(maxColumns, Math.max(1, Math.floor(containerWidth / columnWidth))); // Создаем колонки const columns = Array.from({ length: availableColumns }, () => { const col = document.createElement('div'); col.className = 'masonry-column'; return col; }); // Массив для отслеживания высоты каждой колонки const columnHeights = Array(availableColumns).fill(0); // Распределяем группы по колонкам (алгоритм "наименьшая высота") groupsChunks.forEach(group => { let minHeight = Math.min(...columnHeights); let columnIndex = columnHeights.indexOf(minHeight); const section = createGroupSection(group.name, group.icons); columns[columnIndex].appendChild(section); columnHeights[columnIndex] += group.height; }); return columns; }
// Функция для отображения иконок с группировкой (masonry компоновка) function renderIconsWithGroups() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } // Группируем иконки const { groups, noGroup } = groupIcons(filteredIcons); // Собираем все группы и разбиваем большие const allGroupChunks = []; Object.entries(groups).forEach(([groupName, icons]) => { const chunks = splitLargeGroup(icons, 15); chunks.forEach((chunk, index) => { const chunkName = chunks.length > 1 ? `${groupName} (${index + 1}/${chunks.length})` : groupName; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: groupName, height: calculateGroupHeight(chunk.length) }); }); }); // Добавляем иконки без группы if (noGroup.length > 0) { const noGroupChunks = splitLargeGroup(noGroup, 15); noGroupChunks.forEach((chunk, index) => { const chunkName = noGroupChunks.length > 1 ? `Прочие иконки (${index + 1}/${noGroupChunks.length})` : 'Прочие иконки'; allGroupChunks.push({ name: chunkName, icons: chunk, originalGroup: 'no_group', height: calculateGroupHeight(chunk.length) }); }); } // Сортируем по высоте (от высоких к низким для лучшего заполнения) allGroupChunks.sort((a, b) => b.height - a.height); // Создаем адаптивную masonry компоновку const columns = createAdaptiveMasonryLayout(allGroupChunks); // Очищаем контейнер и добавляем колонки container.innerHTML = ''; const columnsContainer = document.createElement('div'); columnsContainer.className = 'columns-container'; columns.forEach(col => { if (col.children.length > 0) { columnsContainer.appendChild(col); } }); container.appendChild(columnsContainer); updateStats(); }
// Функция для создания секции группы 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 = 'ДО'; beforeLabel.style.whiteSpace = 'nowrap'; const arrowSpace = document.createElement('div'); arrowSpace.className = 'arrow'; arrowSpace.textContent = '→'; arrowSpace.style.whiteSpace = 'nowrap'; const afterLabel = document.createElement('div'); afterLabel.className = 'header-label'; afterLabel.textContent = 'ПОСЛЕ'; afterLabel.style.whiteSpace = 'nowrap'; const nameLabel = document.createElement('div'); nameLabel.className = 'header-label'; nameLabel.textContent = 'Название'; nameLabel.style.textAlign = 'center'; nameLabel.style.whiteSpace = 'nowrap'; 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 setupResizeHandler() { let resizeTimeout; window.addEventListener('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (showGroups && filteredIcons.length > 0) { renderIconsWithGroups(); } }, 250); }); }
// Остальные функции function processBMPWithPinkBackground(img) { return new Promise((resolve) => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; 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) { if (data[i] === 255 && data[i + 1] === 0 && data[i + 2] === 255) { 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 }); } }); }
function loadImageWithBMPProcessing(src, alt) { return new Promise((resolve) => { const img = new Image(); img.onload = async function() { try { if (src.toLowerCase().endsWith('.bmp')) { const result = await processBMPWithPinkBackground(img); result.element.alt = alt; resolve(result); } else { img.alt = alt; resolve({ element: img, processed: false }); } } catch (error) { console.warn('Ошибка обработки изображения:', error, src); img.alt = alt; 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='; placeholder.alt = alt; resolve({ element: placeholder, processed: false }); }; img.src = src; }); }
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 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() { const container = document.getElementById('iconsContainer'); container.innerHTML = ''; if (filteredIcons.length === 0) { container.innerHTML = '<div class="no-results">Ничего не найдено. Попробуйте изменить поисковый запрос или выбрать другую группу.</div>'; updateStats(); return; } const sortedIcons = [...filteredIcons].sort((a, b) => { const groupA = a.group || 'zzz'; const groupB = b.group || 'zzz'; if (groupA !== groupB) { return groupA.localeCompare(groupB); } return a.name.localeCompare(b.name); }); const itemsPerColumn = 20; const columnCount = Math.ceil(sortedIcons.length / itemsPerColumn); for (let i = 0; i < columnCount; i++) { const startIndex = i * itemsPerColumn; const endIndex = startIndex + itemsPerColumn; const columnIcons = sortedIcons.slice(startIndex, endIndex); const column = createIconColumn(columnIcons); container.appendChild(column); } updateStats(); }
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 (showGroups) { renderIconsWithGroups(); } else { renderIconsWithoutGroups(); } }
function updateStats() { document.getElementById('totalCount').textContent = allIcons.length; document.getElementById('shownCount').textContent = filteredIcons.length; }
function initGroupSelect(groups) { const groupSelect = document.getElementById('groupSelect'); groupSelect.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group; option.textContent = group; groupSelect.appendChild(option); }); groupSelect.style.display = groups.length > 0 ? 'block' : 'none'; groupSelect.addEventListener('change', function() { const searchTerm = document.getElementById('iconSearchInput').value; filterIcons(searchTerm, this.value); }); }
function renderIcons(images, groups) { allIcons = images; filteredIcons = [...allIcons]; availableGroups = groups; initGroupSelect(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('iconSearchInput'); 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); } }); // Предотвращаем всплытие события, чтобы не активировался глобальный поиск Hugo searchInput.addEventListener('keypress', function(e) { e.stopPropagation(); }); searchInput.addEventListener('keydown', function(e) { e.stopPropagation(); }); }
function setupGroupsToggle() { const toggle = document.getElementById('showGroupsToggle'); toggle.addEventListener('change', function() { showGroups = this.checked; renderFilteredIcons(); }); }
window.addEventListener('DOMContentLoaded', function() { loadIcons(); setupSearch(); setupGroupsToggle(); setupResizeHandler(); }); </script> </body> </html>
|