788 lines
35 KiB
Plaintext
788 lines
35 KiB
Plaintext
<!-- Content Header (Page header) -->
|
||
<section class="content-header">
|
||
<div class="container-fluid">
|
||
<div class="row mb-2">
|
||
<div class="col-sm-6">
|
||
<h1><i class="fas fa-images mr-2"></i>Медиа Галерея</h1>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<div class="float-sm-right">
|
||
<button id="refresh-btn" class="btn btn-secondary">
|
||
<i class="fas fa-sync-alt mr-1"></i>Обновить
|
||
</button>
|
||
<button id="upload-btn" class="btn btn-primary">
|
||
<i class="fas fa-upload mr-1"></i>Загрузить файлы
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Main content -->
|
||
<section class="content">
|
||
<div class="container-fluid">
|
||
<!-- Upload Zone -->
|
||
<div id="upload-zone" class="card" style="display: none;">
|
||
<div class="card-body text-center">
|
||
<div class="mb-4">
|
||
<i class="fas fa-cloud-upload-alt fa-6x text-muted mb-4"></i>
|
||
<p class="h5 text-muted mb-2">Перетащите файлы сюда или нажмите для выбора</p>
|
||
<p class="text-muted">Поддерживаются: JPG, PNG, GIF, SVG (максимум 10MB каждый)</p>
|
||
</div>
|
||
<input type="file" id="file-input" multiple accept="image/*" class="d-none">
|
||
<button type="button" onclick="document.getElementById('file-input').click()" class="btn btn-primary">
|
||
Выбрать файлы
|
||
</button>
|
||
<button id="cancel-upload" class="btn btn-secondary ml-3">
|
||
Отмена
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Upload Progress -->
|
||
<div id="upload-progress" class="card" style="display: none;">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Загрузка файлов</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="progress-list">
|
||
<!-- Progress items will be added here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter and Search -->
|
||
<div class="card">
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-md-3">
|
||
<div class="form-group">
|
||
<label>Тип файла</label>
|
||
<select id="file-type-filter" class="form-control">
|
||
<option value="">Все типы</option>
|
||
<option value="image/jpeg">JPEG</option>
|
||
<option value="image/png">PNG</option>
|
||
<option value="image/gif">GIF</option>
|
||
<option value="image/svg+xml">SVG</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="form-group">
|
||
<label>Размер</label>
|
||
<select id="size-filter" class="form-control">
|
||
<option value="">Любой размер</option>
|
||
<option value="small">Маленький (< 1MB)</option>
|
||
<option value="medium">Средний (1-5MB)</option>
|
||
<option value="large">Большой (> 5MB)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-group">
|
||
<label>Поиск</label>
|
||
<div class="input-group">
|
||
<input type="text" id="search-input" placeholder="Поиск по имени файла..." class="form-control">
|
||
<div class="input-group-append">
|
||
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Media Grid -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">Файлы</h3>
|
||
<div class="card-tools">
|
||
<span id="file-count" class="badge badge-secondary">Загрузка...</span>
|
||
<div class="btn-group ml-2">
|
||
<button id="grid-view" class="btn btn-sm btn-default">
|
||
<i class="fas fa-th-large"></i>
|
||
</button>
|
||
<button id="list-view" class="btn btn-sm btn-default">
|
||
<i class="fas fa-list"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<!-- Loading State -->
|
||
<div id="loading" class="text-center py-5">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="sr-only">Загрузка...</span>
|
||
</div>
|
||
<p class="mt-2 text-muted">Загрузка медиа файлов...</p>
|
||
</div>
|
||
|
||
<!-- Empty State -->
|
||
<div id="empty-state" class="text-center py-5" style="display: none;">
|
||
<i class="fas fa-images fa-6x text-muted mb-4"></i>
|
||
<h4 class="text-muted mb-2">Нет загруженных файлов</h4>
|
||
<p class="text-muted mb-4">Начните с загрузки ваших первых изображений</p>
|
||
<button onclick="document.getElementById('upload-btn').click()" class="btn btn-primary">
|
||
<i class="fas fa-upload mr-2"></i>Загрузить файлы
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Media Grid -->
|
||
<div id="media-grid" class="row">
|
||
<!-- Media items will be loaded here -->
|
||
</div>
|
||
|
||
<!-- Media List -->
|
||
<div id="media-list" style="display: none;">
|
||
<!-- List items will be loaded here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
<div class="card-footer" id="pagination" style="display: none;">
|
||
<nav aria-label="Page navigation">
|
||
<ul class="pagination justify-content-center m-0">
|
||
<li class="page-item">
|
||
<button id="prev-page" class="page-link">
|
||
<i class="fas fa-chevron-left"></i>
|
||
</button>
|
||
</li>
|
||
<div id="page-numbers" class="d-flex">
|
||
<!-- Page numbers will be added here -->
|
||
</div>
|
||
<li class="page-item">
|
||
<button id="next-page" class="page-link">
|
||
<i class="fas fa-chevron-right"></i>
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Media Preview Modal -->
|
||
<div class="modal fade" id="preview-modal" tabindex="-1" role="dialog">
|
||
<div class="modal-dialog modal-xl" role="document">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h4 id="modal-title" class="modal-title">Предпросмотр файла</h4>
|
||
<button type="button" class="close" data-dismiss="modal">
|
||
<span>×</span>
|
||
</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="row">
|
||
<div class="col-md-8">
|
||
<img id="modal-image" src="" alt="" class="img-fluid rounded">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="form-group">
|
||
<label>Имя файла</label>
|
||
<input id="modal-filename" type="text" class="form-control" readonly>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>URL</label>
|
||
<div class="input-group">
|
||
<input id="modal-url" type="text" class="form-control" readonly>
|
||
<div class="input-group-append">
|
||
<button onclick="copyToClipboard()" class="btn btn-outline-secondary" type="button">
|
||
<i class="fas fa-copy"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<div class="form-group">
|
||
<label>Размер</label>
|
||
<p id="modal-size" class="form-control-plaintext">-</p>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="form-group">
|
||
<label>Тип</label>
|
||
<p id="modal-type" class="form-control-plaintext">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<div class="form-group">
|
||
<label>Ширина</label>
|
||
<p id="modal-width" class="form-control-plaintext">-</p>
|
||
</div>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="form-group">
|
||
<label>Высота</label>
|
||
<p id="modal-height" class="form-control-plaintext">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Загружено</label>
|
||
<p id="modal-date" class="form-control-plaintext">-</p>
|
||
</div>
|
||
<div class="btn-group d-flex">
|
||
<button onclick="downloadFile()" class="btn btn-primary">
|
||
<i class="fas fa-download mr-1"></i>Скачать
|
||
</button>
|
||
<button onclick="deleteFile()" class="btn btn-danger">
|
||
<i class="fas fa-trash mr-1"></i>Удалить
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
class MediaGallery {
|
||
constructor() {
|
||
this.currentFiles = [];
|
||
this.filteredFiles = [];
|
||
this.currentView = 'grid';
|
||
this.currentPage = 1;
|
||
this.itemsPerPage = 24;
|
||
this.currentFile = null;
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.setupEventListeners();
|
||
this.loadMedia();
|
||
}
|
||
|
||
setupEventListeners() {
|
||
// Upload button
|
||
document.getElementById('upload-btn').addEventListener('click', () => {
|
||
this.showUploadZone();
|
||
});
|
||
|
||
// Cancel upload
|
||
document.getElementById('cancel-upload').addEventListener('click', () => {
|
||
this.hideUploadZone();
|
||
});
|
||
|
||
// File input
|
||
document.getElementById('file-input').addEventListener('change', (e) => {
|
||
this.handleFiles(e.target.files);
|
||
});
|
||
|
||
// Refresh button
|
||
document.getElementById('refresh-btn').addEventListener('click', () => {
|
||
this.loadMedia();
|
||
});
|
||
|
||
// View toggle
|
||
document.getElementById('grid-view').addEventListener('click', () => {
|
||
this.setView('grid');
|
||
});
|
||
|
||
document.getElementById('list-view').addEventListener('click', () => {
|
||
this.setView('list');
|
||
});
|
||
|
||
// Filters
|
||
document.getElementById('file-type-filter').addEventListener('change', () => {
|
||
this.applyFilters();
|
||
});
|
||
|
||
document.getElementById('size-filter').addEventListener('change', () => {
|
||
this.applyFilters();
|
||
});
|
||
|
||
document.getElementById('search-input').addEventListener('input', () => {
|
||
this.applyFilters();
|
||
});
|
||
|
||
// Modal
|
||
document.getElementById('close-modal').addEventListener('click', () => {
|
||
this.closeModal();
|
||
});
|
||
|
||
// Upload zone drag and drop
|
||
const uploadZone = document.getElementById('upload-zone');
|
||
uploadZone.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
uploadZone.classList.add('border-blue-500', 'bg-blue-50');
|
||
});
|
||
|
||
uploadZone.addEventListener('dragleave', () => {
|
||
uploadZone.classList.remove('border-blue-500', 'bg-blue-50');
|
||
});
|
||
|
||
uploadZone.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
uploadZone.classList.remove('border-blue-500', 'bg-blue-50');
|
||
this.handleFiles(e.dataTransfer.files);
|
||
});
|
||
}
|
||
|
||
async loadMedia() {
|
||
try {
|
||
document.getElementById('loading').style.display = 'block';
|
||
document.getElementById('empty-state').style.display = 'none';
|
||
document.getElementById('media-grid').style.display = 'none';
|
||
|
||
const response = await fetch('/api/media/list');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
this.currentFiles = data.images || [];
|
||
this.applyFilters();
|
||
} else {
|
||
throw new Error(data.message || 'Failed to load media');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading media:', error);
|
||
this.showError('Ошибка загрузки медиа файлов');
|
||
} finally {
|
||
document.getElementById('loading').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
applyFilters() {
|
||
const typeFilter = document.getElementById('file-type-filter').value;
|
||
const sizeFilter = document.getElementById('size-filter').value;
|
||
const searchQuery = document.getElementById('search-input').value.toLowerCase();
|
||
|
||
this.filteredFiles = this.currentFiles.filter(file => {
|
||
// Type filter
|
||
if (typeFilter && file.mimetype !== typeFilter) {
|
||
return false;
|
||
}
|
||
|
||
// Size filter
|
||
if (sizeFilter) {
|
||
const sizeInMB = file.size / (1024 * 1024);
|
||
if (sizeFilter === 'small' && sizeInMB >= 1) return false;
|
||
if (sizeFilter === 'medium' && (sizeInMB < 1 || sizeInMB > 5)) return false;
|
||
if (sizeFilter === 'large' && sizeInMB <= 5) return false;
|
||
}
|
||
|
||
// Search filter
|
||
if (searchQuery && !file.filename.toLowerCase().includes(searchQuery)) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
this.updateFileCount();
|
||
this.renderMedia();
|
||
}
|
||
|
||
updateFileCount() {
|
||
const total = this.currentFiles.length;
|
||
const filtered = this.filteredFiles.length;
|
||
const countText = filtered === total ?
|
||
`${total} файлов` :
|
||
`${filtered} из ${total} файлов`;
|
||
|
||
document.getElementById('file-count').textContent = countText;
|
||
}
|
||
|
||
renderMedia() {
|
||
if (this.filteredFiles.length === 0) {
|
||
document.getElementById('empty-state').style.display = 'block';
|
||
document.getElementById('media-grid').style.display = 'none';
|
||
document.getElementById('media-list').style.display = 'none';
|
||
document.getElementById('pagination').style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('empty-state').style.display = 'none';
|
||
|
||
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
|
||
const endIndex = startIndex + this.itemsPerPage;
|
||
const pageFiles = this.filteredFiles.slice(startIndex, endIndex);
|
||
|
||
if (this.currentView === 'grid') {
|
||
this.renderGrid(pageFiles);
|
||
} else {
|
||
this.renderList(pageFiles);
|
||
}
|
||
|
||
this.updatePagination();
|
||
}
|
||
|
||
renderGrid(files) {
|
||
document.getElementById('media-grid').style.display = 'grid';
|
||
document.getElementById('media-list').style.display = 'none';
|
||
|
||
const grid = document.getElementById('media-grid');
|
||
grid.innerHTML = files.map(file => `
|
||
<div class="group relative bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow"
|
||
onclick="mediaGallery.openModal('${file.filename}')">
|
||
<div class="aspect-square">
|
||
<img src="${file.url}" alt="${file.filename}"
|
||
class="w-full h-full object-cover">
|
||
</div>
|
||
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-25 transition-opacity flex items-center justify-center">
|
||
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
|
||
<button class="bg-white bg-opacity-90 text-gray-800 px-3 py-2 rounded-lg mr-2"
|
||
onclick="event.stopPropagation(); mediaGallery.downloadFile('${file.filename}')">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="bg-red-500 bg-opacity-90 text-white px-3 py-2 rounded-lg"
|
||
onclick="event.stopPropagation(); mediaGallery.deleteFile('${file.filename}')">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-75 text-white p-2">
|
||
<p class="text-xs truncate">${file.filename}</p>
|
||
<p class="text-xs text-gray-300">${this.formatFileSize(file.size)}</p>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
renderList(files) {
|
||
document.getElementById('media-grid').style.display = 'none';
|
||
document.getElementById('media-list').style.display = 'block';
|
||
|
||
const list = document.getElementById('media-list');
|
||
list.innerHTML = files.map(file => `
|
||
<div class="flex items-center p-4 bg-gray-50 rounded-lg hover:bg-gray-100 cursor-pointer"
|
||
onclick="mediaGallery.openModal('${file.filename}')">
|
||
<div class="w-16 h-16 flex-shrink-0 mr-4">
|
||
<img src="${file.url}" alt="${file.filename}"
|
||
class="w-full h-full object-cover rounded">
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<h4 class="text-sm font-medium text-gray-900 truncate">${file.filename}</h4>
|
||
<p class="text-sm text-gray-500">${this.formatFileSize(file.size)} • ${file.mimetype}</p>
|
||
<p class="text-xs text-gray-400">${new Date(file.uploadedAt).toLocaleDateString('ru-RU')}</p>
|
||
</div>
|
||
<div class="flex space-x-2">
|
||
<button class="text-blue-600 hover:text-blue-800 p-2"
|
||
onclick="event.stopPropagation(); mediaGallery.downloadFile('${file.filename}')">
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-800 p-2"
|
||
onclick="event.stopPropagation(); mediaGallery.deleteFile('${file.filename}')">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
setView(view) {
|
||
this.currentView = view;
|
||
|
||
// Update button states
|
||
document.getElementById('grid-view').classList.toggle('bg-blue-600', view === 'grid');
|
||
document.getElementById('grid-view').classList.toggle('text-white', view === 'grid');
|
||
document.getElementById('list-view').classList.toggle('bg-blue-600', view === 'list');
|
||
document.getElementById('list-view').classList.toggle('text-white', view === 'list');
|
||
|
||
this.renderMedia();
|
||
}
|
||
|
||
showUploadZone() {
|
||
document.getElementById('upload-zone').style.display = 'block';
|
||
}
|
||
|
||
hideUploadZone() {
|
||
document.getElementById('upload-zone').style.display = 'none';
|
||
document.getElementById('file-input').value = '';
|
||
}
|
||
|
||
async handleFiles(files) {
|
||
const validFiles = Array.from(files).filter(file => {
|
||
if (!file.type.startsWith('image/')) {
|
||
this.showError(`${file.name} не является изображением`);
|
||
return false;
|
||
}
|
||
if (file.size > 10 * 1024 * 1024) {
|
||
this.showError(`${file.name} слишком большой (максимум 10MB)`);
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
|
||
if (validFiles.length === 0) return;
|
||
|
||
this.hideUploadZone();
|
||
await this.uploadFiles(validFiles);
|
||
}
|
||
|
||
async uploadFiles(files) {
|
||
const progressContainer = document.getElementById('upload-progress');
|
||
const progressList = document.getElementById('progress-list');
|
||
|
||
progressContainer.style.display = 'block';
|
||
progressList.innerHTML = '';
|
||
|
||
for (const file of files) {
|
||
const progressItem = this.createProgressItem(file);
|
||
progressList.appendChild(progressItem);
|
||
|
||
try {
|
||
await this.uploadSingleFile(file, progressItem);
|
||
} catch (error) {
|
||
this.updateProgressItem(progressItem, 'error', error.message);
|
||
}
|
||
}
|
||
|
||
setTimeout(() => {
|
||
progressContainer.style.display = 'none';
|
||
this.loadMedia();
|
||
}, 2000);
|
||
}
|
||
|
||
createProgressItem(file) {
|
||
const div = document.createElement('div');
|
||
div.className = 'flex items-center justify-between p-3 bg-gray-50 rounded';
|
||
div.innerHTML = `
|
||
<div class="flex items-center space-x-3">
|
||
<i class="fas fa-image text-gray-400"></i>
|
||
<span class="text-sm text-gray-900">${file.name}</span>
|
||
<span class="text-xs text-gray-500">${this.formatFileSize(file.size)}</span>
|
||
</div>
|
||
<div class="flex items-center space-x-3">
|
||
<div class="w-32 bg-gray-200 rounded-full h-2">
|
||
<div class="bg-blue-600 h-2 rounded-full progress-bar" style="width: 0%"></div>
|
||
</div>
|
||
<span class="text-sm text-gray-600 status">0%</span>
|
||
</div>
|
||
`;
|
||
return div;
|
||
}
|
||
|
||
updateProgressItem(item, status, message = '') {
|
||
const statusElement = item.querySelector('.status');
|
||
const progressBar = item.querySelector('.progress-bar');
|
||
|
||
if (status === 'error') {
|
||
statusElement.textContent = 'Ошибка';
|
||
statusElement.className = 'text-sm text-red-600 status';
|
||
progressBar.className = 'bg-red-600 h-2 rounded-full progress-bar';
|
||
progressBar.style.width = '100%';
|
||
} else if (status === 'success') {
|
||
statusElement.textContent = 'Готово';
|
||
statusElement.className = 'text-sm text-green-600 status';
|
||
progressBar.className = 'bg-green-600 h-2 rounded-full progress-bar';
|
||
progressBar.style.width = '100%';
|
||
}
|
||
}
|
||
|
||
async uploadSingleFile(file, progressItem) {
|
||
const formData = new FormData();
|
||
formData.append('images', file);
|
||
|
||
const xhr = new XMLHttpRequest();
|
||
const progressBar = progressItem.querySelector('.progress-bar');
|
||
const status = progressItem.querySelector('.status');
|
||
|
||
return new Promise((resolve, reject) => {
|
||
xhr.upload.addEventListener('progress', (e) => {
|
||
if (e.lengthComputable) {
|
||
const percentComplete = (e.loaded / e.total) * 100;
|
||
progressBar.style.width = percentComplete + '%';
|
||
status.textContent = Math.round(percentComplete) + '%';
|
||
}
|
||
});
|
||
|
||
xhr.addEventListener('load', () => {
|
||
if (xhr.status === 200) {
|
||
const response = JSON.parse(xhr.responseText);
|
||
if (response.success) {
|
||
this.updateProgressItem(progressItem, 'success');
|
||
resolve();
|
||
} else {
|
||
reject(new Error(response.message));
|
||
}
|
||
} else {
|
||
reject(new Error('Upload failed'));
|
||
}
|
||
});
|
||
|
||
xhr.addEventListener('error', () => {
|
||
reject(new Error('Network error'));
|
||
});
|
||
|
||
xhr.open('POST', '/api/media/upload-multiple');
|
||
xhr.send(formData);
|
||
});
|
||
}
|
||
|
||
openModal(filename) {
|
||
const file = this.currentFiles.find(f => f.filename === filename);
|
||
if (!file) return;
|
||
|
||
this.currentFile = file;
|
||
|
||
document.getElementById('modal-title').textContent = file.filename;
|
||
document.getElementById('modal-image').src = file.url;
|
||
document.getElementById('modal-filename').value = file.filename;
|
||
document.getElementById('modal-url').value = window.location.origin + file.url;
|
||
document.getElementById('modal-size').textContent = this.formatFileSize(file.size);
|
||
document.getElementById('modal-type').textContent = file.mimetype;
|
||
document.getElementById('modal-date').textContent = new Date(file.uploadedAt).toLocaleDateString('ru-RU');
|
||
|
||
// Load image to get dimensions
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
document.getElementById('modal-width').textContent = img.width + 'px';
|
||
document.getElementById('modal-height').textContent = img.height + 'px';
|
||
};
|
||
img.src = file.url;
|
||
|
||
document.getElementById('preview-modal').style.display = 'flex';
|
||
}
|
||
|
||
closeModal() {
|
||
document.getElementById('preview-modal').style.display = 'none';
|
||
this.currentFile = null;
|
||
}
|
||
|
||
async deleteFile(filename) {
|
||
if (!confirm(`Вы уверены, что хотите удалить файл "${filename}"?`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/media/${filename}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
if (response.ok) {
|
||
this.showSuccess('Файл удален');
|
||
this.loadMedia();
|
||
if (this.currentFile && this.currentFile.filename === filename) {
|
||
this.closeModal();
|
||
}
|
||
} else {
|
||
throw new Error('Failed to delete file');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting file:', error);
|
||
this.showError('Ошибка удаления файла');
|
||
}
|
||
}
|
||
|
||
downloadFile(filename) {
|
||
const file = filename ?
|
||
this.currentFiles.find(f => f.filename === filename) :
|
||
this.currentFile;
|
||
|
||
if (!file) return;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = file.url;
|
||
link.download = file.filename;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
updatePagination() {
|
||
const totalPages = Math.ceil(this.filteredFiles.length / this.itemsPerPage);
|
||
|
||
if (totalPages <= 1) {
|
||
document.getElementById('pagination').style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('pagination').style.display = 'block';
|
||
|
||
// Update prev/next buttons
|
||
document.getElementById('prev-page').disabled = this.currentPage === 1;
|
||
document.getElementById('next-page').disabled = this.currentPage === totalPages;
|
||
|
||
// Update page numbers
|
||
const pageNumbers = document.getElementById('page-numbers');
|
||
pageNumbers.innerHTML = '';
|
||
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
if (i === 1 || i === totalPages || (i >= this.currentPage - 2 && i <= this.currentPage + 2)) {
|
||
const button = document.createElement('button');
|
||
button.className = `px-3 py-2 rounded ${
|
||
i === this.currentPage ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600 hover:bg-gray-300'
|
||
}`;
|
||
button.textContent = i;
|
||
button.onclick = () => this.goToPage(i);
|
||
pageNumbers.appendChild(button);
|
||
} else if (i === this.currentPage - 3 || i === this.currentPage + 3) {
|
||
const span = document.createElement('span');
|
||
span.className = 'px-2 py-2 text-gray-400';
|
||
span.textContent = '...';
|
||
pageNumbers.appendChild(span);
|
||
}
|
||
}
|
||
}
|
||
|
||
goToPage(page) {
|
||
this.currentPage = page;
|
||
this.renderMedia();
|
||
}
|
||
|
||
formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 B';
|
||
const k = 1024;
|
||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
showError(message) {
|
||
this.showNotification(message, 'error');
|
||
}
|
||
|
||
showSuccess(message) {
|
||
this.showNotification(message, 'success');
|
||
}
|
||
|
||
showNotification(message, type = 'info') {
|
||
const notification = document.createElement('div');
|
||
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg text-white z-50 ${
|
||
type === 'success' ? 'bg-green-500' :
|
||
type === 'error' ? 'bg-red-500' : 'bg-blue-500'
|
||
}`;
|
||
notification.textContent = message;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
setTimeout(() => {
|
||
notification.remove();
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
// Global functions for modal
|
||
function copyToClipboard() {
|
||
const urlInput = document.getElementById('modal-url');
|
||
urlInput.select();
|
||
document.execCommand('copy');
|
||
mediaGallery.showSuccess('URL скопирован в буфер обмена');
|
||
}
|
||
|
||
function downloadFile() {
|
||
mediaGallery.downloadFile();
|
||
}
|
||
|
||
function deleteFile() {
|
||
if (mediaGallery.currentFile) {
|
||
mediaGallery.deleteFile(mediaGallery.currentFile.filename);
|
||
}
|
||
}
|
||
|
||
// Initialize
|
||
let mediaGallery;
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
mediaGallery = new MediaGallery();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |