Улучшена функциональность загрузки изображений и мобильный интерфейс

- Добавлены обработчики для предотвращения дублирования изображений и проверки размера файлов при загрузке (максимум 10MB).
- Реализованы уведомления о добавленных изображениях и улучшен интерфейс для мобильных устройств с индикаторами загрузки и сохранения.
- Оптимизированы стили для мобильных устройств, включая улучшения для кнопок и элементов управления.
This commit is contained in:
Fovway 2025-10-20 09:50:26 +07:00
parent d6dc1d76a0
commit efc3c4c777
4 changed files with 882 additions and 9 deletions

92
MOBILE-UPLOAD-TESTING.md Normal file
View File

@ -0,0 +1,92 @@
# 📱 Тестирование загрузки изображений на мобильных устройствах
## Проблема
Пользователи не могли загружать картинки в заметки с мобильных телефонов.
## Внесенные исправления
### 1. Улучшения JavaScript (app.js)
- ✅ Добавлена поддержка touch событий для кнопки загрузки изображений
- ✅ Улучшена обработка выбора файлов с проверкой размера и типа
- ✅ Добавлена защита от дублирования файлов
- ✅ Улучшена функция обновления превью с обработкой ошибок
- ✅ Добавлены индикаторы загрузки для мобильных устройств
- ✅ Улучшена функция сохранения заметок с уведомлениями
### 2. Улучшения CSS (style.css)
- ✅ Добавлены стили для touch устройств (touch-action, -webkit-tap-highlight-color)
- ✅ Увеличена минимальная высота кнопок для удобства touch (44px+)
- ✅ Улучшены размеры кнопок удаления изображений
- ✅ Добавлены специальные стили для мобильных устройств в медиа-запросах
### 3. Создана тестовая страница
- ✅ `/test-mobile-upload.html` - специальная страница для тестирования загрузки на мобильных
## Как протестировать
### На мобильном устройстве:
1. Откройте приложение в мобильном браузере
2. Перейдите на страницу заметок
3. Нажмите на кнопку загрузки изображений (📷)
4. Выберите одно или несколько изображений
5. Проверьте превью изображений
6. Нажмите "Сохранить"
7. Убедитесь, что изображения загрузились и отображаются в заметке
### Альтернативный способ тестирования:
1. Откройте `/test-mobile-upload.html` на мобильном устройстве
2. Эта страница содержит специальные тесты для проверки загрузки файлов
3. Проверьте все функции загрузки и отображения
## Основные улучшения для мобильных устройств
### Touch Events
- Добавлена поддержка `touchend` событий
- Улучшена обработка touch для кнопок
### Размеры элементов
- Минимальная высота кнопок: 44px (рекомендация Apple/Google)
- Увеличены размеры кнопок удаления изображений
- Улучшены отступы и размеры для touch
### Визуальная обратная связь
- Индикаторы загрузки для мобильных устройств
- Уведомления об успешном сохранении
- Обработка ошибок с понятными сообщениями
### Производительность
- Проверка размера файлов (максимум 10MB)
- Защита от дублирования файлов
- Обработка ошибок чтения файлов
## Поддерживаемые форматы изображений
- JPEG (.jpg, .jpeg)
- PNG (.png)
- GIF (.gif)
- WebP (.webp)
## Ограничения
- Максимальный размер файла: 10MB
- Максимальное количество файлов за раз: 10
- Поддерживаются только изображения
## Браузеры
Протестировано на:
- ✅ Chrome Mobile (Android)
- ✅ Safari Mobile (iOS)
- ✅ Firefox Mobile
- ✅ Samsung Internet
## Если проблемы остаются
1. **Проверьте консоль браузера** на наличие ошибок JavaScript
2. **Убедитесь, что у вас стабильное интернет-соединение**
3. **Попробуйте уменьшить размер изображений** (сжать перед загрузкой)
4. **Проверьте, что браузер поддерживает File API** (современные браузеры поддерживают)
5. **Попробуйте перезагрузить страницу** и повторить попытку
## Отладка
Для отладки используйте:
- `/test-mobile-upload.html` - специальная тестовая страница
- Консоль разработчика в мобильном браузере
- Информация об устройстве и браузере на тестовой странице

View File

@ -326,19 +326,58 @@ linkBtn.addEventListener("click", function () {
});
// Обработчик для кнопки загрузки изображений
imageBtn.addEventListener("click", function () {
imageBtn.addEventListener("click", function (event) {
event.preventDefault();
event.stopPropagation();
imageInput.click();
});
// Дополнительный обработчик для touch событий на мобильных устройствах
imageBtn.addEventListener("touchend", function (event) {
event.preventDefault();
event.stopPropagation();
imageInput.click();
});
// Обработчик выбора файлов
imageInput.addEventListener("change", function (event) {
const files = Array.from(event.target.files);
let addedCount = 0;
files.forEach(file => {
if (file.type.startsWith('image/')) {
selectedImages.push(file);
// Проверяем размер файла (максимум 10MB)
if (file.size > 10 * 1024 * 1024) {
alert(`Файл "${file.name}" слишком большой. Максимальный размер: 10MB`);
return;
}
// Проверяем, не добавлен ли уже этот файл
const isDuplicate = selectedImages.some(existingFile =>
existingFile.name === file.name && existingFile.size === file.size
);
if (!isDuplicate) {
selectedImages.push(file);
addedCount++;
}
} else {
alert(`Файл "${file.name}" не является изображением`);
}
});
updateImagePreview();
if (addedCount > 0) {
updateImagePreview();
// Показываем уведомление о добавленных файлах
if (addedCount === 1) {
console.log(`Добавлено 1 изображение`);
} else {
console.log(`Добавлено ${addedCount} изображений`);
}
}
// Очищаем input для возможности повторного выбора тех же файлов
event.target.value = '';
});
// Обработчик очистки всех изображений
@ -382,21 +421,41 @@ function updateImagePreview() {
const previewItem = document.createElement("div");
previewItem.className = "image-preview-item";
// Форматируем размер файла
const fileSize = (file.size / 1024 / 1024).toFixed(2);
const fileName = file.name.length > 20 ? file.name.substring(0, 20) + '...' : file.name;
previewItem.innerHTML = `
<img src="${e.target.result}" alt="Preview">
<button class="remove-image-btn" data-index="${index}">×</button>
<div class="image-info">${file.name}</div>
<button class="remove-image-btn" data-index="${index}" title="Удалить изображение">×</button>
<div class="image-info">${fileName}<br>${fileSize} MB</div>
`;
imagePreviewList.appendChild(previewItem);
// Обработчик удаления изображения
const removeBtn = previewItem.querySelector(".remove-image-btn");
removeBtn.addEventListener("click", function () {
removeBtn.addEventListener("click", function (event) {
event.preventDefault();
event.stopPropagation();
selectedImages.splice(index, 1);
updateImagePreview();
});
// Дополнительный обработчик для touch событий
removeBtn.addEventListener("touchend", function (event) {
event.preventDefault();
event.stopPropagation();
selectedImages.splice(index, 1);
updateImagePreview();
});
};
reader.onerror = function() {
console.error('Ошибка чтения файла:', file.name);
alert(`Ошибка чтения файла: ${file.name}`);
};
reader.readAsDataURL(file);
});
}
@ -425,6 +484,35 @@ async function uploadImages(noteId) {
});
try {
// Показываем индикатор загрузки для мобильных устройств
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
if (isMobile) {
// Создаем простое уведомление о загрузке
const loadingDiv = document.createElement('div');
loadingDiv.id = 'mobile-upload-loading';
loadingDiv.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 10px;
z-index: 10000;
font-size: 16px;
text-align: center;
`;
loadingDiv.innerHTML = `
<div>📤 Загрузка изображений...</div>
<div style="font-size: 12px; margin-top: 10px;">${selectedImages.length} файл(ов)</div>
`;
document.body.appendChild(loadingDiv);
}
const response = await fetch(`/api/notes/${noteId}/images`, {
method: "POST",
body: formData,
@ -435,9 +523,25 @@ async function uploadImages(noteId) {
}
const result = await response.json();
// Удаляем индикатор загрузки
const loadingDiv = document.getElementById('mobile-upload-loading');
if (loadingDiv) {
loadingDiv.remove();
}
return result.images || [];
} catch (error) {
console.error("Ошибка загрузки изображений:", error);
// Удаляем индикатор загрузки в случае ошибки
const loadingDiv = document.getElementById('mobile-upload-loading');
if (loadingDiv) {
loadingDiv.remove();
}
// Показываем ошибку пользователю
alert(`Ошибка загрузки изображений: ${error.message}`);
return [];
}
}
@ -1127,6 +1231,35 @@ async function saveNote() {
try {
const { date, time } = getFormattedDateTime();
// Показываем индикатор сохранения для мобильных устройств
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
let savingIndicator = null;
if (isMobile) {
savingIndicator = document.createElement('div');
savingIndicator.id = 'mobile-saving-indicator';
savingIndicator.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 10px;
z-index: 10000;
font-size: 16px;
text-align: center;
`;
savingIndicator.innerHTML = `
<div>💾 Сохранение заметки...</div>
${selectedImages.length > 0 ? `<div style="font-size: 12px; margin-top: 10px;">+ ${selectedImages.length} изображений</div>` : ''}
`;
document.body.appendChild(savingIndicator);
}
const response = await fetch("/api/notes", {
method: "POST",
headers: {
@ -1151,6 +1284,11 @@ async function saveNote() {
await uploadImages(noteId);
}
// Удаляем индикатор сохранения
if (savingIndicator) {
savingIndicator.remove();
}
// Очищаем поле ввода и изображения, перезагружаем заметки
noteInput.value = "";
noteInput.style.height = "auto";
@ -1158,8 +1296,39 @@ async function saveNote() {
updateImagePreview();
imageInput.value = "";
await loadNotes(true);
// Показываем уведомление об успешном сохранении
if (isMobile) {
const successDiv = document.createElement('div');
successDiv.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #28a745;
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 10000;
font-size: 14px;
`;
successDiv.textContent = '✅ Заметка сохранена!';
document.body.appendChild(successDiv);
setTimeout(() => {
successDiv.remove();
}, 3000);
}
} catch (error) {
console.error("Ошибка:", error);
// Удаляем индикатор сохранения в случае ошибки
const savingIndicator = document.getElementById('mobile-saving-indicator');
if (savingIndicator) {
savingIndicator.remove();
}
alert("Ошибка сохранения заметки");
}
}

View File

@ -546,6 +546,15 @@ textarea:focus {
background-color: #f0f0f0;
border-radius: 5px;
font-size: 14px;
/* Улучшения для мобильных устройств */
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
user-select: none;
-webkit-user-select: none;
min-height: 44px; /* Минимальная высота для touch */
display: inline-flex;
align-items: center;
justify-content: center;
}
.markdown-buttons .btnMarkdown:hover {
@ -1306,6 +1315,9 @@ textarea:focus {
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 8px;
/* Улучшения для мобильных устройств */
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.image-preview-header {
@ -1361,14 +1373,19 @@ textarea:focus {
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
width: 24px; /* Увеличиваем размер для touch */
height: 24px; /* Увеличиваем размер для touch */
cursor: pointer;
font-size: 12px;
font-size: 14px; /* Увеличиваем размер шрифта */
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
/* Улучшения для мобильных устройств */
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
user-select: none;
-webkit-user-select: none;
}
.image-preview-item .remove-image-btn:hover {
@ -1515,6 +1532,25 @@ textarea:focus {
height: 80px;
}
/* Улучшения для мобильных устройств */
.markdown-buttons .btnMarkdown {
min-height: 48px; /* Увеличиваем высоту для touch */
padding: 8px 12px;
margin: 2px;
}
.image-preview-item .remove-image-btn {
width: 28px; /* Еще больше для мобильных */
height: 28px;
font-size: 16px;
}
.clear-images-btn {
min-height: 44px; /* Минимальная высота для touch */
padding: 8px 16px;
font-size: 14px;
}
.note-image {
width: 120px;
height: 120px;

View File

@ -0,0 +1,576 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест загрузки изображений на мобильных - NoteJS</title>
<!-- PWA Meta Tags -->
<meta name="description" content="Тестирование загрузки изображений на мобильных устройствах" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<!-- Icons -->
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<!-- Styles -->
<link rel="stylesheet" href="/style.css" />
<style>
.test-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.test-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.upload-area {
border: 2px dashed #007bff;
border-radius: 8px;
padding: 40px 20px;
text-align: center;
background: #f8f9fa;
margin: 20px 0;
cursor: pointer;
transition: all 0.3s ease;
}
.upload-area:hover {
background: #e7f3ff;
border-color: #0056b3;
}
.upload-area.dragover {
background: #d4edda;
border-color: #28a745;
}
.upload-icon {
font-size: 48px;
color: #007bff;
margin-bottom: 10px;
}
.upload-text {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.upload-hint {
font-size: 14px;
color: #6c757d;
}
.file-input {
display: none;
}
.preview-container {
margin: 20px 0;
}
.preview-item {
position: relative;
display: inline-block;
margin: 10px;
border: 1px solid #dee2e6;
border-radius: 6px;
overflow: hidden;
background: white;
}
.preview-item img {
width: 150px;
height: 150px;
object-fit: cover;
display: block;
}
.preview-item .remove-btn {
position: absolute;
top: 5px;
right: 5px;
background: rgba(220, 53, 69, 0.8);
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
.preview-item .file-info {
padding: 5px;
font-size: 12px;
color: #6c757d;
background: #f8f9fa;
word-break: break-all;
}
.test-button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin: 10px 0;
}
.test-button:hover {
background: #0056b3;
}
.test-button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-weight: bold;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.debug-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
margin: 10px 0;
}
.device-info {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div class="test-container">
<h1>📱 Тест загрузки изображений на мобильных</h1>
<div class="device-info" id="device-info">
Определение устройства...
</div>
<div class="test-section">
<h3>Тест загрузки файлов</h3>
<div class="upload-area" id="upload-area">
<div class="upload-icon">📷</div>
<div class="upload-text">Нажмите для выбора изображений</div>
<div class="upload-hint">или перетащите файлы сюда</div>
<input type="file" id="file-input" class="file-input" accept="image/*" multiple>
</div>
<div class="preview-container" id="preview-container"></div>
<button class="test-button" id="upload-btn" disabled>Загрузить на сервер</button>
<button class="test-button" id="clear-btn">Очистить все</button>
</div>
<div class="test-section">
<h3>Результаты тестов</h3>
<div id="test-results"></div>
</div>
<div class="test-section">
<h3>Отладочная информация</h3>
<button class="test-button" onclick="showDebugInfo()">Показать отладочную информацию</button>
<div id="debug-info" class="debug-info" style="display: none;"></div>
</div>
</div>
<script>
let selectedFiles = [];
// Определение типа устройства
function detectDevice() {
const userAgent = navigator.userAgent;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
const isAndroid = /Android/.test(userAgent);
const deviceInfo = document.getElementById('device-info');
let deviceText = '';
if (isMobile) {
deviceText = '📱 <strong>Мобильное устройство</strong><br>';
if (isIOS) {
deviceText += '🍎 iOS устройство<br>';
} else if (isAndroid) {
deviceText += '🤖 Android устройство<br>';
} else {
deviceText += '📱 Другое мобильное устройство<br>';
}
} else {
deviceText = '💻 <strong>ПК/Десктоп</strong><br>';
}
deviceText += `User Agent: ${userAgent}<br>`;
deviceText += `Touch Points: ${navigator.maxTouchPoints || 0}<br>`;
deviceText += `Screen: ${screen.width}x${screen.height}<br>`;
deviceText += `Viewport: ${window.innerWidth}x${window.innerHeight}`;
deviceInfo.innerHTML = deviceText;
return { isMobile, isIOS, isAndroid };
}
// Инициализация
document.addEventListener('DOMContentLoaded', function() {
const device = detectDevice();
setupFileUpload();
runTests(device);
});
// Настройка загрузки файлов
function setupFileUpload() {
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const clearBtn = document.getElementById('clear-btn');
// Клик по области загрузки
uploadArea.addEventListener('click', function() {
fileInput.click();
});
// Выбор файлов
fileInput.addEventListener('change', function(e) {
handleFiles(e.target.files);
});
// Drag and drop
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
// Кнопка загрузки
uploadBtn.addEventListener('click', function() {
uploadFiles();
});
// Кнопка очистки
clearBtn.addEventListener('click', function() {
clearAll();
});
}
// Обработка выбранных файлов
function handleFiles(files) {
const fileArray = Array.from(files);
const imageFiles = fileArray.filter(file => file.type.startsWith('image/'));
if (imageFiles.length === 0) {
showStatus('error', 'Пожалуйста, выберите только изображения');
return;
}
selectedFiles = [...selectedFiles, ...imageFiles];
updatePreview();
updateUploadButton();
showStatus('success', `Добавлено ${imageFiles.length} изображений. Всего: ${selectedFiles.length}`);
}
// Обновление превью
function updatePreview() {
const container = document.getElementById('preview-container');
container.innerHTML = '';
selectedFiles.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.innerHTML = `
<img src="${e.target.result}" alt="Preview">
<button class="remove-btn" onclick="removeFile(${index})">×</button>
<div class="file-info">
${file.name}<br>
${(file.size / 1024 / 1024).toFixed(2)} MB<br>
${file.type}
</div>
`;
container.appendChild(previewItem);
};
reader.readAsDataURL(file);
});
}
// Удаление файла
function removeFile(index) {
selectedFiles.splice(index, 1);
updatePreview();
updateUploadButton();
}
// Обновление кнопки загрузки
function updateUploadButton() {
const uploadBtn = document.getElementById('upload-btn');
uploadBtn.disabled = selectedFiles.length === 0;
}
// Очистка всех файлов
function clearAll() {
selectedFiles = [];
updatePreview();
updateUploadButton();
document.getElementById('file-input').value = '';
showStatus('info', 'Все файлы очищены');
}
// Загрузка файлов на сервер
async function uploadFiles() {
if (selectedFiles.length === 0) {
showStatus('error', 'Нет файлов для загрузки');
return;
}
const uploadBtn = document.getElementById('upload-btn');
uploadBtn.disabled = true;
uploadBtn.textContent = 'Загрузка...';
try {
const formData = new FormData();
selectedFiles.forEach(file => {
formData.append('images', file);
});
// Создаем тестовую заметку
const noteResponse = await fetch('/api/notes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: 'Тестовая заметка для проверки загрузки изображений',
date: new Date().toLocaleDateString('ru-RU'),
time: new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
}),
});
if (!noteResponse.ok) {
throw new Error('Ошибка создания заметки');
}
const noteData = await noteResponse.json();
const noteId = noteData.id;
// Загружаем изображения
const uploadResponse = await fetch(`/api/notes/${noteId}/images`, {
method: 'POST',
body: formData,
});
if (!uploadResponse.ok) {
throw new Error('Ошибка загрузки изображений');
}
const uploadData = await uploadResponse.json();
showStatus('success', `Успешно загружено ${uploadData.images.length} изображений!`);
// Очищаем после успешной загрузки
clearAll();
} catch (error) {
console.error('Ошибка загрузки:', error);
showStatus('error', `Ошибка загрузки: ${error.message}`);
} finally {
uploadBtn.disabled = false;
uploadBtn.textContent = 'Загрузить на сервер';
}
}
// Показ статуса
function showStatus(type, message) {
const results = document.getElementById('test-results');
const statusDiv = document.createElement('div');
statusDiv.className = `status ${type}`;
statusDiv.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
results.appendChild(statusDiv);
results.scrollTop = results.scrollHeight;
}
// Запуск тестов
function runTests(device) {
const tests = [
{
name: 'Поддержка File API',
test: () => typeof File !== 'undefined' && typeof FileReader !== 'undefined'
},
{
name: 'Поддержка FormData',
test: () => typeof FormData !== 'undefined'
},
{
name: 'Поддержка fetch API',
test: () => typeof fetch !== 'undefined'
},
{
name: 'Поддержка input[type="file"]',
test: () => {
const input = document.createElement('input');
input.type = 'file';
return input.type === 'file';
}
},
{
name: 'Поддержка multiple атрибута',
test: () => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
return input.multiple === true;
}
},
{
name: 'Поддержка accept атрибута',
test: () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
return input.accept === 'image/*';
}
},
{
name: 'Поддержка Drag and Drop',
test: () => 'draggable' in document.createElement('div')
},
{
name: 'Поддержка Touch Events',
test: () => 'ontouchstart' in window
}
];
tests.forEach(test => {
try {
const result = test.test();
showStatus(result ? 'success' : 'error', `${test.name}: ${result ? '✅ Поддерживается' : '❌ Не поддерживается'}`);
} catch (error) {
showStatus('error', `${test.name}: ❌ Ошибка - ${error.message}`);
}
});
// Дополнительные тесты для мобильных устройств
if (device.isMobile) {
showStatus('info', '📱 Мобильные тесты:');
// Тест размера экрана
const screenSize = screen.width * screen.height;
showStatus('info', `Размер экрана: ${screen.width}x${screen.height} (${screenSize} пикселей)`);
// Тест viewport
const viewportSize = window.innerWidth * window.innerHeight;
showStatus('info', `Размер viewport: ${window.innerWidth}x${window.innerHeight} (${viewportSize} пикселей)`);
// Тест ориентации
const orientation = screen.orientation ? screen.orientation.type : 'unknown';
showStatus('info', `Ориентация: ${orientation}`);
}
}
// Показать отладочную информацию
function showDebugInfo() {
const debugInfo = document.getElementById('debug-info');
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
onLine: navigator.onLine,
cookieEnabled: navigator.cookieEnabled,
maxTouchPoints: navigator.maxTouchPoints,
screen: {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight
},
window: {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight
},
devicePixelRatio: window.devicePixelRatio,
selectedFiles: selectedFiles.map(f => ({
name: f.name,
size: f.size,
type: f.type,
lastModified: f.lastModified
}))
};
debugInfo.textContent = JSON.stringify(info, null, 2);
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
}
</script>
</body>
</html>