Frontend improvements

This commit is contained in:
IgorVolochay
2026-01-06 19:03:19 +03:00
parent 6e7741c946
commit fd347cf12a
3 changed files with 316 additions and 21 deletions
+25 -7
View File
@@ -8,25 +8,43 @@
</head> </head>
<body> <body>
<div id="uploadPage"> <div id="uploadPage">
<h1>Аналог DropMeFiles</h1> <div id="errorPopup" class="error-popup" style="display: none;"></div>
<div class="upload-container"> <div class="page-content">
<h1>Аналог DropMeFiles</h1>
<div class="upload-container">
<form id="uploadForm"> <form id="uploadForm">
<label class="dropzone" id="dropzone"> <label class="dropzone" id="dropzone">
<input id="fileInput" type="file"> <input id="fileInput" type="file">
<p class="strong">Перетащите файл сюда или нажмите, чтобы выбрать</p> <p class="strong">Перетащите файл сюда или нажмите, чтобы выбрать</p>
<p>Максимальный размер файла: <span id="maxFileSize">загрузка...</span></p> <p>Максимальный размер файла: <span id="maxFileSize">загрузка...</span></p>
</label> </label>
<button type="submit" id="uploadButton" disabled>Загрузить</button> <div class="button-container">
<button type="submit" id="uploadButton" disabled>Загрузить</button>
</div>
</form> </form>
<div id="successContent" class="success-content" style="display: none;">
<div class="file-info">
<h2 id="successFileName"></h2>
<p class="file-size" id="successFileSize"></p>
<div class="download-link-container">
<input type="text" id="downloadLinkInput" readonly class="download-link-input">
<button type="button" id="copyLinkButton" class="copy-link-button">Копировать</button>
</div>
<button type="button" id="uploadAnotherButton" class="upload-another-button">Загрузить другой файл</button>
</div>
</div>
<div class="status" id="status"></div> <div class="status" id="status"></div>
</div>
</div> </div>
</div> </div>
<div id="downloadPage" style="display: none;"> <div id="downloadPage" style="display: none;">
<h1>Скачать файл</h1> <div class="page-content">
<div class="upload-container"> <h1>Скачать файл</h1>
<div id="downloadContent"> <div class="upload-container">
<div class="status" id="downloadStatus">Загрузка...</div> <div id="downloadContent">
<div class="status" id="downloadStatus">Загрузка...</div>
</div>
</div> </div>
</div> </div>
</div> </div>
+107 -11
View File
@@ -34,10 +34,24 @@ async function handleDownloadPage(fileUuid) {
try { try {
const response = await fetch(`/api/get_download_link/${fileUuid}`); const response = await fetch(`/api/get_download_link/${fileUuid}`);
// Check for network/connection errors
if (!response.ok && (response.status === 0 || response.status >= 500)) {
downloadStatus.textContent = 'Сервис временно недоступен, приносим наши извинения.';
downloadStatus.className = 'status error';
downloadStatus.style.display = 'block';
return;
}
const data = await response.json(); const data = await response.json();
if (data.error || !data.result || !data.result.data) { if (data.error || !data.result || !data.result.data) {
downloadStatus.textContent = data.result?.comment || data.result || 'Ошибка при получении файла'; // Check if it's a 404 or file not found error
if (response.status === 404 || data.result?.comment?.includes('not found') || data.result?.comment?.includes('не найден')) {
downloadStatus.textContent = 'Мы не смогли ничего найти. Перепроверьте введенный адрес.';
} else {
downloadStatus.textContent = 'Мы не смогли ничего найти. Перепроверьте введенный адрес.';
}
downloadStatus.className = 'status error'; downloadStatus.className = 'status error';
downloadStatus.style.display = 'block'; downloadStatus.style.display = 'block';
return; return;
@@ -87,12 +101,25 @@ async function handleDownloadPage(fileUuid) {
downloadStatus.style.display = 'none'; downloadStatus.style.display = 'none';
} catch (error) { } catch (error) {
downloadStatus.textContent = `Ошибка: ${error.message}`; // Network error or other connection issues
downloadStatus.textContent = 'Сервис временно недоступен, приносим наши извинения.';
downloadStatus.className = 'status error'; downloadStatus.className = 'status error';
downloadStatus.style.display = 'block'; downloadStatus.style.display = 'block';
} }
} }
// Show error popup
function showErrorPopup(message) {
const popup = document.getElementById('errorPopup');
popup.textContent = message;
popup.style.display = 'block';
// Auto-hide after 5 seconds
setTimeout(() => {
popup.style.display = 'none';
}, 5000);
}
// Upload page initialization // Upload page initialization
function initUploadPage() { function initUploadPage() {
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
@@ -101,6 +128,8 @@ function initUploadPage() {
const uploadForm = document.getElementById('uploadForm'); const uploadForm = document.getElementById('uploadForm');
const statusDiv = document.getElementById('status'); const statusDiv = document.getElementById('status');
const maxFileSizeSpan = document.getElementById('maxFileSize'); const maxFileSizeSpan = document.getElementById('maxFileSize');
const successContent = document.getElementById('successContent');
const uploadFormContainer = uploadForm.parentElement;
let maxFileSizeBytes = 0; let maxFileSizeBytes = 0;
@@ -133,7 +162,8 @@ function initUploadPage() {
const file = fileInput.files[0]; const file = fileInput.files[0];
if (file.size > maxFileSizeBytes && maxFileSizeBytes > 0) { if (file.size > maxFileSizeBytes && maxFileSizeBytes > 0) {
setStatus(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`, 'error'); const errorMsg = `Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`;
showErrorPopup(errorMsg);
uploadButton.disabled = true; uploadButton.disabled = true;
return; return;
} }
@@ -145,6 +175,52 @@ function initUploadPage() {
const setStatus = (message, type = '') => { const setStatus = (message, type = '') => {
statusDiv.textContent = message; statusDiv.textContent = message;
statusDiv.className = type ? `status ${type}` : 'status'; statusDiv.className = type ? `status ${type}` : 'status';
// Hide status div when empty to avoid unnecessary spacing
if (!message || message.trim() === '') {
statusDiv.style.display = 'none';
} else {
statusDiv.style.display = 'block';
}
};
const showSuccessState = (fileName, fileSize, downloadUrl) => {
// Hide the form
uploadForm.style.display = 'none';
statusDiv.style.display = 'none';
// Show success content
document.getElementById('successFileName').textContent = fileName;
document.getElementById('successFileSize').textContent = `Размер: ${formatBytes(fileSize)}`;
document.getElementById('downloadLinkInput').value = downloadUrl;
successContent.style.display = 'block';
// Setup copy button
const copyButton = document.getElementById('copyLinkButton');
copyButton.onclick = () => {
const input = document.getElementById('downloadLinkInput');
input.select();
input.setSelectionRange(0, 99999); // For mobile devices
document.execCommand('copy');
const originalText = copyButton.textContent;
copyButton.textContent = 'Скопировано!';
copyButton.classList.add('copied');
setTimeout(() => {
copyButton.textContent = originalText;
copyButton.classList.remove('copied');
}, 2000);
};
// Setup "upload another" button
const uploadAnotherButton = document.getElementById('uploadAnotherButton');
uploadAnotherButton.onclick = () => {
// Reset form
uploadForm.style.display = 'block';
successContent.style.display = 'none';
fileInput.value = '';
updateButtonState();
};
}; };
dropzone.addEventListener('dragover', (event) => { dropzone.addEventListener('dragover', (event) => {
@@ -177,14 +253,14 @@ function initUploadPage() {
event.preventDefault(); event.preventDefault();
if (!fileInput.files || fileInput.files.length === 0) { if (!fileInput.files || fileInput.files.length === 0) {
setStatus('Выберите файл для загрузки.', 'error'); showErrorPopup('Выберите файл для загрузки.');
return; return;
} }
const file = fileInput.files[0]; const file = fileInput.files[0];
if (maxFileSizeBytes > 0 && file.size > maxFileSizeBytes) { if (maxFileSizeBytes > 0 && file.size > maxFileSizeBytes) {
setStatus(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`, 'error'); showErrorPopup(`Файл слишком большой. Максимальный размер: ${formatBytes(maxFileSizeBytes)}`);
return; return;
} }
@@ -199,13 +275,22 @@ function initUploadPage() {
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
const errorData = await tokenResponse.json().catch(() => ({})); const errorData = await tokenResponse.json().catch(() => ({}));
throw new Error(errorData.result?.comment || `Ошибка сервера: ${tokenResponse.status}`); // Extract error message without status codes
let errorMessage = errorData.result || 'Ошибка при загрузке файла';
if (typeof errorMessage === 'object' && errorMessage.comment) {
errorMessage = errorMessage.comment;
}
if (typeof errorMessage === 'string' && errorMessage.includes('too large')) {
errorMessage = 'Файл слишком большой';
}
throw new Error(errorMessage);
} }
const tokenData = await tokenResponse.json(); const tokenData = await tokenResponse.json();
if (tokenData.error || !tokenData.result || !tokenData.result.data) { if (tokenData.error || !tokenData.result || !tokenData.result.data) {
throw new Error(tokenData.result?.comment || 'Ошибка при получении токена загрузки'); let errorMessage = tokenData.result?.comment || 'Ошибка при получении токена загрузки';
throw new Error(errorMessage);
} }
const uploadData = tokenData.result.data; const uploadData = tokenData.result.data;
@@ -230,17 +315,28 @@ function initUploadPage() {
}); });
if (!uploadResponse.ok) { if (!uploadResponse.ok) {
throw new Error(`Ошибка загрузки на S3: ${uploadResponse.status}`); throw new Error('Ошибка при загрузке файла на сервер');
} }
// Success! // Success!
const downloadUrl = `${window.location.origin}/get/${fileUuid}`; const downloadUrl = `${window.location.origin}/get/${fileUuid}`;
setStatus(`Файл успешно загружен! UUID: ${fileUuid}. Ссылка для скачивания: ${downloadUrl}`, 'success'); showSuccessState(file.name, file.size, downloadUrl);
fileInput.value = ''; fileInput.value = '';
updateButtonState();
} catch (error) { } catch (error) {
setStatus(`Не удалось загрузить файл: ${error.message}`, 'error'); // Extract clean error message without status codes
let errorMessage = error.message;
if (errorMessage.includes('status') || errorMessage.match(/\d{3}/)) {
if (errorMessage.includes('too large') || errorMessage.includes('413')) {
errorMessage = 'Файл слишком большой';
} else if (errorMessage.includes('500')) {
errorMessage = 'Ошибка сервера. Попробуйте позже';
} else {
errorMessage = 'Не удалось загрузить файл';
}
}
showErrorPopup(errorMessage);
setStatus('', '');
} finally { } finally {
uploadButton.disabled = false; uploadButton.disabled = false;
} }
+184 -3
View File
@@ -15,10 +15,19 @@ body {
padding: 24px; padding: 24px;
} }
.page-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 640px;
}
h1 { h1 {
font-size: clamp(32px, 4vw, 48px); font-size: clamp(32px, 4vw, 48px);
margin-bottom: 32px;
text-align: center; text-align: center;
margin: 0 0 32px 0;
width: 100%;
} }
.upload-container { .upload-container {
@@ -42,6 +51,14 @@ h1 {
background: rgba(99, 102, 241, 0.04); background: rgba(99, 102, 241, 0.04);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
display: block;
overflow: hidden;
}
.dropzone:focus,
.dropzone:focus-within {
outline: none;
border: 2px dashed #6366f1;
} }
.dropzone.dragover { .dropzone.dragover {
@@ -52,9 +69,43 @@ h1 {
.dropzone input[type="file"] { .dropzone input[type="file"] {
position: absolute; position: absolute;
inset: 0; top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0; opacity: 0;
cursor: pointer; cursor: pointer;
outline: none !important;
border: none !important;
box-shadow: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
font-size: 0 !important;
line-height: 0 !important;
z-index: 1;
}
.dropzone input[type="file"]:focus,
.dropzone input[type="file"]:active,
.dropzone input[type="file"]:hover,
.dropzone input[type="file"]:focus-visible,
.dropzone input[type="file"]::-webkit-file-upload-button {
outline: none !important;
border: none !important;
box-shadow: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
}
.dropzone input[type="file"]::-webkit-file-upload-button {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
} }
.dropzone p { .dropzone p {
@@ -92,12 +143,23 @@ button:disabled {
box-shadow: none; box-shadow: none;
} }
.button-container {
display: flex;
justify-content: center;
width: 100%;
margin-top: 24px;
}
.status { .status {
min-height: 24px;
font-size: 15px; font-size: 15px;
color: #374151; color: #374151;
line-height: 1.5; line-height: 1.5;
word-break: break-all; word-break: break-all;
min-height: 0;
}
.status:empty {
display: none;
} }
.status.error { .status.error {
@@ -148,3 +210,122 @@ button:disabled {
.download-button:active { .download-button:active {
transform: translateY(0); transform: translateY(0);
} }
/* Error popup */
.error-popup {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #fee2e2;
border: 2px solid #dc2626;
border-radius: 12px;
padding: 16px 24px;
color: #991b1b;
font-weight: 600;
font-size: 15px;
z-index: 1000;
box-shadow: 0 10px 25px -5px rgba(220, 38, 38, 0.3);
animation: slideDown 0.3s ease-out;
max-width: 90%;
text-align: center;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* Success content */
.success-content {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.download-link-container {
display: flex;
gap: 12px;
margin-top: 24px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.download-link-input {
flex: 1;
min-width: 200px;
padding: 12px 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
font-family: monospace;
background: #f9fafb;
color: #111827;
cursor: text;
}
.download-link-input:focus {
outline: none;
border-color: #6366f1;
background: #ffffff;
}
.copy-link-button {
padding: 12px 24px;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
white-space: nowrap;
}
.copy-link-button:hover {
transform: translateY(-1px);
box-shadow: 0 8px 16px -4px rgba(16, 185, 129, 0.4);
}
.copy-link-button:active {
transform: translateY(0);
}
.copy-link-button.copied {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
.upload-another-button {
margin-top: 24px;
padding: 12px 24px;
background: transparent;
color: #6366f1;
border: 2px solid #6366f1;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.upload-another-button:hover {
background: #6366f1;
color: white;
transform: translateY(-1px);
box-shadow: 0 8px 16px -4px rgba(99, 102, 241, 0.4);
}