AdminLTE3
This commit is contained in:
@@ -1,241 +1,709 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Медиа Галерея - SmartSolTech Admin</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<link rel="stylesheet" href="/css/fixes.css">
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 p-8">
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
|
||||
<i class="fas fa-images mr-3 text-blue-600"></i>
|
||||
Медиа Галерея
|
||||
</h1>
|
||||
<p class="mt-2 text-gray-600">Управление изображениями и файлами сайта</p>
|
||||
</div>
|
||||
<div class="flex space-x-3">
|
||||
<button id="refresh-btn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
|
||||
<i class="fas fa-sync-alt mr-2"></i>
|
||||
Обновить
|
||||
</button>
|
||||
<button id="upload-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
|
||||
<i class="fas fa-upload mr-2"></i>
|
||||
Загрузить файлы
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Zone -->
|
||||
<div id="upload-zone" class="bg-white shadow rounded-lg p-8 border-2 border-dashed border-gray-300 text-center" style="display: none;">
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-cloud-upload-alt text-6xl text-gray-400 mb-4"></i>
|
||||
<p class="text-xl text-gray-600 mb-2">Перетащите файлы сюда или нажмите для выбора</p>
|
||||
<p class="text-gray-500">Поддерживаются: JPG, PNG, GIF, SVG (максимум 10MB каждый)</p>
|
||||
</div>
|
||||
<input type="file" id="file-input" multiple accept="image/*" class="hidden">
|
||||
<button type="button" onclick="document.getElementById('file-input').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg">
|
||||
Выбрать файлы
|
||||
<!-- 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="cancel-upload" class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-3 rounded-lg ml-3">
|
||||
Отмена
|
||||
<button id="upload-btn" class="btn btn-primary">
|
||||
<i class="fas fa-upload mr-1"></i>Загрузить файлы
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div id="upload-progress" class="bg-white shadow rounded-lg p-6" style="display: none;">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Загрузка файлов</h3>
|
||||
<div class="space-y-3" id="progress-list">
|
||||
<!-- Progress items will be added here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter and Search -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
||||
<div class="flex space-x-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Тип файла</label>
|
||||
<select id="file-type-filter" class="border border-gray-300 rounded-lg px-3 py-2">
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Размер</label>
|
||||
<select id="size-filter" class="border border-gray-300 rounded-lg px-3 py-2">
|
||||
<option value="">Любой размер</option>
|
||||
<option value="small">Маленький (< 1MB)</option>
|
||||
<option value="medium">Средний (1-5MB)</option>
|
||||
<option value="large">Большой (> 5MB)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Поиск</label>
|
||||
<div class="relative">
|
||||
<input type="text" id="search-input" placeholder="Поиск по имени файла..." class="border border-gray-300 rounded-lg px-3 py-2 pr-10 w-64">
|
||||
<i class="fas fa-search absolute right-3 top-3 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Grid -->
|
||||
<div class="bg-white shadow rounded-lg p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Файлы</h3>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span id="file-count" class="text-sm text-gray-600">Загрузка...</span>
|
||||
<div class="flex space-x-2">
|
||||
<button id="grid-view" class="p-2 text-gray-600 hover:text-gray-900 border border-gray-300 rounded">
|
||||
<i class="fas fa-th-large"></i>
|
||||
</button>
|
||||
<button id="list-view" class="p-2 text-gray-600 hover:text-gray-900 border border-gray-300 rounded">
|
||||
<i class="fas fa-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div id="loading" class="text-center py-12">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<p class="mt-2 text-gray-600">Загрузка медиа файлов...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div id="empty-state" class="text-center py-12" style="display: none;">
|
||||
<i class="fas fa-images text-6xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Нет загруженных файлов</h3>
|
||||
<p class="text-gray-600 mb-6">Начните с загрузки ваших первых изображений</p>
|
||||
<button onclick="document.getElementById('upload-btn').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg">
|
||||
<i class="fas fa-upload mr-2"></i>
|
||||
Загрузить файлы
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Media Grid -->
|
||||
<div id="media-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
|
||||
<!-- Media items will be loaded here -->
|
||||
</div>
|
||||
|
||||
<!-- Media List -->
|
||||
<div id="media-list" class="space-y-4" style="display: none;">
|
||||
<!-- List items will be loaded here -->
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div id="pagination" class="mt-8 flex justify-center" style="display: none;">
|
||||
<nav class="flex space-x-2">
|
||||
<button id="prev-page" class="px-3 py-2 bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
<div id="page-numbers" class="flex space-x-2">
|
||||
<!-- Page numbers will be added here -->
|
||||
</div>
|
||||
<button id="next-page" class="px-3 py-2 bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Media Preview Modal -->
|
||||
<div id="preview-modal" class="fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center" style="display: none;">
|
||||
<div class="bg-white rounded-lg shadow-lg max-w-4xl max-h-[90vh] w-full mx-4 overflow-hidden">
|
||||
<div class="p-4 border-b flex justify-between items-center">
|
||||
<h3 id="modal-title" class="text-lg font-semibold text-gray-900">Предпросмотр файла</h3>
|
||||
<button id="close-modal" class="text-gray-400 hover:text-gray-600">
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
<!-- 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 class="p-6">
|
||||
<div class="flex flex-col lg:flex-row space-y-6 lg:space-y-0 lg:space-x-6">
|
||||
<div class="flex-1">
|
||||
<img id="modal-image" src="" alt="" class="w-full h-auto rounded-lg shadow">
|
||||
</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="w-full lg:w-80">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Имя файла</label>
|
||||
<input id="modal-filename" type="text" class="w-full border border-gray-300 rounded-lg px-3 py-2" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">URL</label>
|
||||
<div class="flex">
|
||||
<input id="modal-url" type="text" class="flex-1 border border-gray-300 rounded-l-lg px-3 py-2" readonly>
|
||||
<button onclick="copyToClipboard()" class="bg-gray-100 border border-l-0 border-gray-300 rounded-r-lg px-3 py-2 hover:bg-gray-200">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<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 class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Размер</label>
|
||||
<p id="modal-size" class="text-sm text-gray-600">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Тип</label>
|
||||
<p id="modal-type" class="text-sm text-gray-600">-</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Ширина</label>
|
||||
<p id="modal-width" class="text-sm text-gray-600">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Высота</label>
|
||||
<p id="modal-height" class="text-sm text-gray-600">-</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Загружено</label>
|
||||
<p id="modal-date" class="text-sm text-gray-600">-</p>
|
||||
</div>
|
||||
<div class="border-t pt-4 space-y-3">
|
||||
<button onclick="downloadFile()" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-download mr-2"></i>
|
||||
Скачать
|
||||
</button>
|
||||
<button onclick="deleteFile()" class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
|
||||
<i class="fas fa-trash mr-2"></i>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/js/main.js"></script>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script>
|
||||
let currentFiles = [];
|
||||
let filteredFiles = [];
|
||||
let currentPage = 1;
|
||||
let filesPerPage = 24;
|
||||
let isGridView = true;
|
||||
let currentFile = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadFiles();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
function setupEventListeners() {
|
||||
// Upload button
|
||||
document.getElementById('upload-btn').addEventListener('click', function() {
|
||||
document.getElementById('upload-zone').style.display = 'block';
|
||||
});
|
||||
|
||||
// Cancel upload
|
||||
document.getElementById('cancel-upload').addEventListener('click', function() {
|
||||
document.getElementById('upload-zone').style.display = 'none';
|
||||
});
|
||||
|
||||
// File input
|
||||
document.getElementById('file-input').addEventListener('change', handleFileSelect);
|
||||
|
||||
// Refresh
|
||||
document.getElementById('refresh-btn').addEventListener('click', loadFiles);
|
||||
|
||||
// View toggle
|
||||
document.getElementById('grid-view').addEventListener('click', function() {
|
||||
isGridView = true;
|
||||
updateViewButtons();
|
||||
renderFiles();
|
||||
});
|
||||
|
||||
document.getElementById('list-view').addEventListener('click', function() {
|
||||
isGridView = false;
|
||||
updateViewButtons();
|
||||
renderFiles();
|
||||
});
|
||||
|
||||
// Filters
|
||||
document.getElementById('file-type-filter').addEventListener('change', applyFilters);
|
||||
document.getElementById('size-filter').addEventListener('change', applyFilters);
|
||||
document.getElementById('search-input').addEventListener('input', applyFilters);
|
||||
|
||||
// Drag and drop
|
||||
const uploadZone = document.getElementById('upload-zone');
|
||||
uploadZone.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.add('border-blue-500');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('dragleave', function(e) {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.remove('border-blue-500');
|
||||
});
|
||||
|
||||
uploadZone.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
uploadZone.classList.remove('border-blue-500');
|
||||
handleFileSelect({ target: { files: e.dataTransfer.files } });
|
||||
});
|
||||
}
|
||||
|
||||
function updateViewButtons() {
|
||||
const gridBtn = document.getElementById('grid-view');
|
||||
const listBtn = document.getElementById('list-view');
|
||||
|
||||
if (isGridView) {
|
||||
gridBtn.classList.add('bg-blue-500', 'text-white');
|
||||
gridBtn.classList.remove('btn-default');
|
||||
listBtn.classList.remove('bg-blue-500', 'text-white');
|
||||
listBtn.classList.add('btn-default');
|
||||
} else {
|
||||
listBtn.classList.add('bg-blue-500', 'text-white');
|
||||
listBtn.classList.remove('btn-default');
|
||||
gridBtn.classList.remove('bg-blue-500', 'text-white');
|
||||
gridBtn.classList.add('btn-default');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFiles() {
|
||||
try {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('empty-state').style.display = 'none';
|
||||
document.getElementById('media-grid').style.display = 'none';
|
||||
document.getElementById('media-list').style.display = 'none';
|
||||
|
||||
const response = await fetch('/api/admin/media');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
currentFiles = data.files;
|
||||
filteredFiles = [...currentFiles];
|
||||
updateFileCount();
|
||||
renderFiles();
|
||||
|
||||
if (currentFiles.length === 0) {
|
||||
document.getElementById('empty-state').style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading files:', error);
|
||||
alert('Ошибка загрузки файлов: ' + error.message);
|
||||
} finally {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const typeFilter = document.getElementById('file-type-filter').value;
|
||||
const sizeFilter = document.getElementById('size-filter').value;
|
||||
const searchQuery = document.getElementById('search-input').value.toLowerCase();
|
||||
|
||||
filteredFiles = currentFiles.filter(file => {
|
||||
let matches = true;
|
||||
|
||||
// Type filter
|
||||
if (typeFilter && file.mimetype !== typeFilter) {
|
||||
matches = false;
|
||||
}
|
||||
|
||||
// Size filter
|
||||
if (sizeFilter) {
|
||||
const sizeMB = file.size / (1024 * 1024);
|
||||
if (sizeFilter === 'small' && sizeMB >= 1) matches = false;
|
||||
if (sizeFilter === 'medium' && (sizeMB < 1 || sizeMB > 5)) matches = false;
|
||||
if (sizeFilter === 'large' && sizeMB <= 5) matches = false;
|
||||
}
|
||||
|
||||
// Search filter
|
||||
if (searchQuery && !file.filename.toLowerCase().includes(searchQuery)) {
|
||||
matches = false;
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
currentPage = 1;
|
||||
updateFileCount();
|
||||
renderFiles();
|
||||
}
|
||||
|
||||
function updateFileCount() {
|
||||
const countElement = document.getElementById('file-count');
|
||||
countElement.textContent = `${filteredFiles.length} файлов`;
|
||||
}
|
||||
|
||||
function renderFiles() {
|
||||
const gridContainer = document.getElementById('media-grid');
|
||||
const listContainer = document.getElementById('media-list');
|
||||
|
||||
if (isGridView) {
|
||||
gridContainer.style.display = 'block';
|
||||
listContainer.style.display = 'none';
|
||||
renderGridView();
|
||||
} else {
|
||||
gridContainer.style.display = 'none';
|
||||
listContainer.style.display = 'block';
|
||||
renderListView();
|
||||
}
|
||||
|
||||
renderPagination();
|
||||
}
|
||||
|
||||
function renderGridView() {
|
||||
const container = document.getElementById('media-grid');
|
||||
const startIndex = (currentPage - 1) * filesPerPage;
|
||||
const endIndex = startIndex + filesPerPage;
|
||||
const pageFiles = filteredFiles.slice(startIndex, endIndex);
|
||||
|
||||
container.innerHTML = pageFiles.map(file => `
|
||||
<div class="col-md-2 col-sm-3 col-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-img-top" style="height: 150px; background-image: url('${file.url}'); background-size: cover; background-position: center; cursor: pointer;" onclick="openPreview('${file._id}')"></div>
|
||||
<div class="card-body p-2">
|
||||
<p class="card-text small text-truncate" title="${file.filename}">${file.filename}</p>
|
||||
<small class="text-muted">${formatFileSize(file.size)}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderListView() {
|
||||
const container = document.getElementById('media-list');
|
||||
const startIndex = (currentPage - 1) * filesPerPage;
|
||||
const endIndex = startIndex + filesPerPage;
|
||||
const pageFiles = filteredFiles.slice(startIndex, endIndex);
|
||||
|
||||
container.innerHTML = pageFiles.map(file => `
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-1">
|
||||
<img src="${file.url}" alt="${file.filename}" class="img-thumbnail" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h6 class="mb-1">${file.filename}</h6>
|
||||
<small class="text-muted">${file.mimetype}</small>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<span class="badge badge-secondary">${formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<small class="text-muted">${new Date(file.uploadedAt).toLocaleDateString('ru-RU')}</small>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<div class="btn-group">
|
||||
<button onclick="openPreview('${file._id}')" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<button onclick="downloadFile('${file._id}')" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button onclick="deleteFile('${file._id}')" class="btn btn-sm btn-outline-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderPagination() {
|
||||
const totalPages = Math.ceil(filteredFiles.length / filesPerPage);
|
||||
const paginationContainer = document.getElementById('pagination');
|
||||
|
||||
if (totalPages <= 1) {
|
||||
paginationContainer.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
paginationContainer.style.display = 'block';
|
||||
|
||||
// Update prev/next buttons
|
||||
const prevBtn = document.getElementById('prev-page');
|
||||
const nextBtn = document.getElementById('next-page');
|
||||
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
nextBtn.disabled = currentPage === totalPages;
|
||||
|
||||
prevBtn.onclick = () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderFiles();
|
||||
}
|
||||
};
|
||||
|
||||
nextBtn.onclick = () => {
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderFiles();
|
||||
}
|
||||
};
|
||||
|
||||
// Update page numbers
|
||||
const pageNumbersContainer = document.getElementById('page-numbers');
|
||||
let pageNumbers = '';
|
||||
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
if (i === currentPage) {
|
||||
pageNumbers += `<li class="page-item active"><span class="page-link">${i}</span></li>`;
|
||||
} else {
|
||||
pageNumbers += `<li class="page-item"><button class="page-link" onclick="goToPage(${i})">${i}</button></li>`;
|
||||
}
|
||||
}
|
||||
|
||||
pageNumbersContainer.innerHTML = pageNumbers;
|
||||
}
|
||||
|
||||
function goToPage(page) {
|
||||
currentPage = page;
|
||||
renderFiles();
|
||||
}
|
||||
|
||||
function 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];
|
||||
}
|
||||
|
||||
function openPreview(fileId) {
|
||||
const file = currentFiles.find(f => f._id === fileId);
|
||||
if (!file) return;
|
||||
|
||||
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 = file.url;
|
||||
document.getElementById('modal-size').textContent = formatFileSize(file.size);
|
||||
document.getElementById('modal-type').textContent = file.mimetype;
|
||||
document.getElementById('modal-width').textContent = file.width || '-';
|
||||
document.getElementById('modal-height').textContent = file.height || '-';
|
||||
document.getElementById('modal-date').textContent = new Date(file.uploadedAt).toLocaleDateString('ru-RU');
|
||||
|
||||
$('#preview-modal').modal('show');
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const urlInput = document.getElementById('modal-url');
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
|
||||
// Show feedback
|
||||
const btn = event.target.closest('button');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.innerHTML = '<i class="fas fa-check"></i>';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalHtml;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function downloadFile() {
|
||||
if (currentFile) {
|
||||
const link = document.createElement('a');
|
||||
link.href = currentFile.url;
|
||||
link.download = currentFile.filename;
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile() {
|
||||
if (!currentFile) return;
|
||||
|
||||
if (!confirm(`Вы уверены, что хотите удалить файл "${currentFile.filename}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/media/${currentFile._id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
$('#preview-modal').modal('hide');
|
||||
loadFiles();
|
||||
alert('Файл успешно удален');
|
||||
} else {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting file:', error);
|
||||
alert('Ошибка удаления файла: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileSelect(event) {
|
||||
const files = Array.from(event.target.files);
|
||||
if (files.length === 0) return;
|
||||
|
||||
document.getElementById('upload-zone').style.display = 'none';
|
||||
document.getElementById('upload-progress').style.display = 'block';
|
||||
|
||||
const progressList = document.getElementById('progress-list');
|
||||
progressList.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const progressItem = createProgressItem(file.name, i);
|
||||
progressList.appendChild(progressItem);
|
||||
|
||||
try {
|
||||
await uploadFile(file, i);
|
||||
} catch (error) {
|
||||
updateProgress(i, 100, 'error', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('upload-progress').style.display = 'none';
|
||||
loadFiles();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function createProgressItem(filename, index) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'progress-item';
|
||||
div.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="filename">${filename}</span>
|
||||
<span class="status" id="status-${index}">Загрузка...</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" id="progress-${index}" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
|
||||
function updateProgress(index, percent, status, message) {
|
||||
const progressBar = document.getElementById(`progress-${index}`);
|
||||
const statusSpan = document.getElementById(`status-${index}`);
|
||||
|
||||
if (progressBar) {
|
||||
progressBar.style.width = percent + '%';
|
||||
|
||||
if (status === 'success') {
|
||||
progressBar.classList.add('bg-success');
|
||||
statusSpan.textContent = 'Готово';
|
||||
statusSpan.classList.add('text-success');
|
||||
} else if (status === 'error') {
|
||||
progressBar.classList.add('bg-danger');
|
||||
statusSpan.textContent = 'Ошибка: ' + message;
|
||||
statusSpan.classList.add('text-danger');
|
||||
} else {
|
||||
statusSpan.textContent = Math.round(percent) + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadFile(file, index) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.addEventListener('progress', function(e) {
|
||||
if (e.lengthComputable) {
|
||||
const percent = (e.loaded / e.total) * 100;
|
||||
updateProgress(index, percent, 'uploading');
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status === 200) {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
updateProgress(index, 100, 'success');
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(new Error(response.message));
|
||||
}
|
||||
} else {
|
||||
reject(new Error('HTTP ' + xhr.status));
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', function() {
|
||||
reject(new Error('Network error'));
|
||||
});
|
||||
|
||||
xhr.open('POST', '/api/admin/media/upload');
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
class MediaGallery {
|
||||
|
||||
Reference in New Issue
Block a user