239 lines
8.5 KiB
HTML
239 lines
8.5 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="auto">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Библиотека файлов</title>
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<style>
|
||
.library-container { display: flex; gap: var(--kt-ai-space-6); }
|
||
.sidebar { width: 280px; flex-shrink: 0; }
|
||
.main-content { flex: 1; min-width: 0; }
|
||
.folder-item, .file-item {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: var(--kt-ai-space-3) var(--kt-ai-space-4);
|
||
border-radius: var(--kt-ai-radius-md);
|
||
cursor: pointer; transition: background 0.2s;
|
||
}
|
||
.folder-item:hover, .file-item:hover { background: var(--kt-ai-bg-soft); }
|
||
.folder-item.active { background: var(--kt-ai-primary); color: white; }
|
||
.folder-icon { width: 20px; height: 20px; margin-right: var(--kt-ai-space-3); fill: currentColor; }
|
||
.file-icon { width: 20px; height: 20px; margin-right: var(--kt-ai-space-3); fill: currentColor; }
|
||
.delete-btn {
|
||
opacity: 0; background: none; border: none; cursor: pointer;
|
||
padding: var(--kt-ai-space-1); color: var(--kt-ai-fg-muted);
|
||
}
|
||
.folder-item:hover .delete-btn, .file-item:hover .delete-btn { opacity: 1; }
|
||
.delete-btn:hover { color: var(--kt-ai-danger); }
|
||
.empty-state {
|
||
text-align: center; padding: var(--kt-ai-space-12);
|
||
color: var(--kt-ai-fg-muted);
|
||
}
|
||
.file-list { display: flex; flex-direction: column; gap: var(--kt-ai-space-2); }
|
||
.file-meta { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
|
||
.folder-item.active .file-meta { color: rgba(255,255,255,0.7); }
|
||
.controls { display: flex; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-6); }
|
||
.search-input { flex: 1; }
|
||
.sort-select { min-width: 180px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="kt-ai-pagehead">
|
||
<div class="kt-ai-wrap kt-ai-pagehead-in">
|
||
<strong>📁 Библиотека файлов</strong>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="kt-ai-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="library-container">
|
||
<aside class="sidebar">
|
||
<div class="kt-ai-card" style="padding: var(--kt-ai-space-4)">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--kt-ai-space-4)">
|
||
<h3 style="margin:0">Папки</h3>
|
||
<button class="kt-ai-btn" data-variant="primary" data-size="sm" id="newFolderBtn">+</button>
|
||
</div>
|
||
<div id="folderList"></div>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="main-content">
|
||
<div class="kt-ai-card" style="padding: var(--kt-ai-space-6)">
|
||
<div class="controls">
|
||
<input class="kt-ai-input search-input" id="searchInput" placeholder="Поиск по названию...">
|
||
<select class="kt-ai-select sort-select" id="sortSelect">
|
||
<option value="date-desc">Сначала новые</option>
|
||
<option value="date-asc">Сначала старые</option>
|
||
<option value="name-asc">По имени (А-Я)</option>
|
||
<option value="name-desc">По имени (Я-А)</option>
|
||
</select>
|
||
<button class="kt-ai-btn" data-variant="primary" id="uploadBtn">Загрузить файл</button>
|
||
</div>
|
||
|
||
<h2 id="currentFolderTitle" style="margin-bottom: var(--kt-ai-space-4)">Все файлы</h2>
|
||
<div id="fileList" class="file-list"></div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<footer class="kt-ai-wrap kt-ai-pagefoot">
|
||
<span>© 2026 Библиотека файлов</span>
|
||
</footer>
|
||
|
||
<script>
|
||
let folders = [];
|
||
let files = [];
|
||
let currentFolderId = null;
|
||
let searchTerm = '';
|
||
let sortMode = 'date-desc';
|
||
|
||
async function loadFolders() {
|
||
const res = await fetch('/api/folders');
|
||
folders = await res.json();
|
||
renderFolders();
|
||
}
|
||
|
||
async function loadFiles() {
|
||
const res = await fetch('/api/files');
|
||
files = await res.json();
|
||
renderFiles();
|
||
}
|
||
|
||
function renderFolders() {
|
||
const container = document.getElementById('folderList');
|
||
container.innerHTML = folders.map(f => `
|
||
<div class="folder-item ${currentFolderId === f.id ? 'active' : ''}" onclick="selectFolder(${f.id})">
|
||
<div style="display:flex;align-items:center;flex:1">
|
||
<svg class="folder-icon" viewBox="0 0 24 24"><path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
|
||
<span>${f.name}</span>
|
||
</div>
|
||
<button class="delete-btn" onclick="event.stopPropagation(); deleteFolder(${f.id})">✕</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function renderFiles() {
|
||
const container = document.getElementById('fileList');
|
||
let filtered = files.filter(f => {
|
||
if (currentFolderId !== null && f.folderId !== currentFolderId) return false;
|
||
if (searchTerm && !f.name.toLowerCase().includes(searchTerm.toLowerCase())) return false;
|
||
return true;
|
||
});
|
||
|
||
filtered.sort((a, b) => {
|
||
const dateA = new Date(a.createdAt);
|
||
const dateB = new Date(b.createdAt);
|
||
if (sortMode === 'date-desc') return dateB - dateA;
|
||
if (sortMode === 'date-asc') return dateA - dateB;
|
||
if (sortMode === 'name-asc') return a.name.localeCompare(b.name);
|
||
if (sortMode === 'name-desc') return b.name.localeCompare(a.name);
|
||
return 0;
|
||
});
|
||
|
||
if (filtered.length === 0) {
|
||
container.innerHTML = '<div class="empty-state">Нет файлов</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filtered.map(f => {
|
||
const folder = folders.find(fl => fl.id === f.folderId);
|
||
const size = formatSize(f.size);
|
||
const date = new Date(f.createdAt).toLocaleDateString('ru-RU');
|
||
return `
|
||
<div class="file-item">
|
||
<div style="display:flex;align-items:center;flex:1">
|
||
<svg class="file-icon" viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
|
||
<div>
|
||
<div>${f.name}</div>
|
||
<div class="file-meta">${size} • ${date}${folder ? ' • ' + folder.name : ''}</div>
|
||
</div>
|
||
</div>
|
||
<button class="delete-btn" onclick="deleteFile(${f.id})">✕</button>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function formatSize(bytes) {
|
||
if (bytes < 1024) return bytes + ' Б';
|
||
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' КБ';
|
||
return (bytes / 1048576).toFixed(1) + ' МБ';
|
||
}
|
||
|
||
function selectFolder(id) {
|
||
currentFolderId = id;
|
||
const folder = folders.find(f => f.id === id);
|
||
document.getElementById('currentFolderTitle').textContent = folder ? folder.name : 'Все файлы';
|
||
renderFolders();
|
||
renderFiles();
|
||
}
|
||
|
||
async function createFolder() {
|
||
const name = prompt('Название папки:');
|
||
if (!name) return;
|
||
await fetch('/api/folders', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name })
|
||
});
|
||
await loadFolders();
|
||
}
|
||
|
||
async function deleteFolder(id) {
|
||
if (!confirm('Удалить папку и все файлы в ней?')) return;
|
||
await fetch('/api/folders/' + id, { method: 'DELETE' });
|
||
if (currentFolderId === id) {
|
||
currentFolderId = null;
|
||
document.getElementById('currentFolderTitle').textContent = 'Все файлы';
|
||
}
|
||
await loadFolders();
|
||
await loadFiles();
|
||
}
|
||
|
||
async function deleteFile(id) {
|
||
if (!confirm('Удалить файл?')) return;
|
||
await fetch('/api/files/' + id, { method: 'DELETE' });
|
||
await loadFiles();
|
||
}
|
||
|
||
document.getElementById('newFolderBtn').addEventListener('click', createFolder);
|
||
|
||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||
searchTerm = e.target.value;
|
||
renderFiles();
|
||
});
|
||
|
||
document.getElementById('sortSelect').addEventListener('change', (e) => {
|
||
sortMode = e.target.value;
|
||
renderFiles();
|
||
});
|
||
|
||
document.getElementById('uploadBtn').addEventListener('click', () => {
|
||
const folderId = currentFolderId !== null ? currentFolderId : (folders[0]?.id || null);
|
||
if (!folderId && folders.length === 0) {
|
||
alert('Сначала создайте папку');
|
||
return;
|
||
}
|
||
const name = prompt('Название файла:');
|
||
if (!name) return;
|
||
const size = parseInt(prompt('Размер (байт):', '1000')) || 0;
|
||
fetch('/api/files', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ folderId: folderId || 1, name, size })
|
||
}).then(() => loadFiles());
|
||
});
|
||
|
||
loadFolders();
|
||
loadFiles();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|