Add adaptive frontend
This commit is contained in:
+8
-2
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Аналог DropMeFiles</title>
|
<title>Аналог DropMeFiles</title>
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="./style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uploadPage">
|
<div id="uploadPage">
|
||||||
@@ -34,6 +34,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status" id="status"></div>
|
<div class="status" id="status"></div>
|
||||||
|
<div id="uploadProgress" class="download-progress" style="display: none;">
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div class="progress-bar" id="uploadProgressBar"></div>
|
||||||
|
</div>
|
||||||
|
<p class="progress-text" id="uploadProgressText">0%</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,6 +60,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/script.js"></script>
|
<script src="./script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+52
-15
@@ -366,25 +366,57 @@ function initUploadPage() {
|
|||||||
uploadData.fields["key"] = fileUuid;
|
uploadData.fields["key"] = fileUuid;
|
||||||
delete uploadData.fields["Content-Type"];
|
delete uploadData.fields["Content-Type"];
|
||||||
|
|
||||||
setStatus('Загрузка файла на сервер...', '');
|
// Hide status, show progress bar
|
||||||
|
statusDiv.style.display = 'none';
|
||||||
|
const uploadProgress = document.getElementById('uploadProgress');
|
||||||
|
const uploadProgressBar = document.getElementById('uploadProgressBar');
|
||||||
|
const uploadProgressText = document.getElementById('uploadProgressText');
|
||||||
|
uploadProgress.style.display = 'block';
|
||||||
|
uploadProgressBar.style.width = '0%';
|
||||||
|
uploadProgressText.textContent = '0%';
|
||||||
|
|
||||||
// Step 2: Upload to S3 using form-data
|
// Step 2: Upload to S3 using XMLHttpRequest for progress tracking
|
||||||
// Add all fields from the API response (order matters for S3, file should be last)
|
await new Promise((resolve, reject) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.keys(uploadData.fields).forEach(key => {
|
Object.keys(uploadData.fields).forEach(key => {
|
||||||
formData.append(key, uploadData.fields[key]);
|
formData.append(key, uploadData.fields[key]);
|
||||||
});
|
});
|
||||||
// File must be appended last
|
// File must be appended last
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
const uploadResponse = await fetch(uploadData.url, {
|
const xhr = new XMLHttpRequest();
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
// Track upload progress
|
||||||
|
xhr.upload.addEventListener('progress', (event) => {
|
||||||
|
if (event.lengthComputable) {
|
||||||
|
const percent = Math.round((event.loaded / event.total) * 100);
|
||||||
|
uploadProgressBar.style.width = percent + '%';
|
||||||
|
uploadProgressText.textContent = `${percent}% (${formatBytes(event.loaded)} / ${formatBytes(event.total)})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('load', () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Ошибка загрузки: ${xhr.status}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('error', () => {
|
||||||
|
reject(new Error('Ошибка при загрузке файла на сервер'));
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('abort', () => {
|
||||||
|
reject(new Error('Загрузка прервана'));
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.open('POST', uploadData.url);
|
||||||
|
xhr.send(formData);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
// Hide progress bar after successful upload
|
||||||
throw new Error('Ошибка при загрузке файла на сервер');
|
uploadProgress.style.display = 'none';
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
// Success!
|
||||||
const downloadUrl = `${window.location.origin}/get/${fileUuid}`;
|
const downloadUrl = `${window.location.origin}/get/${fileUuid}`;
|
||||||
@@ -405,6 +437,11 @@ function initUploadPage() {
|
|||||||
}
|
}
|
||||||
showErrorPopup(errorMessage);
|
showErrorPopup(errorMessage);
|
||||||
setStatus('', '');
|
setStatus('', '');
|
||||||
|
// Hide progress bar on error
|
||||||
|
const uploadProgress = document.getElementById('uploadProgress');
|
||||||
|
if (uploadProgress) {
|
||||||
|
uploadProgress.style.display = 'none';
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
uploadButton.disabled = false;
|
uploadButton.disabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ body {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.page-content {
|
.page-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -30,6 +37,13 @@ h1 {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(24px, 6vw, 32px);
|
||||||
|
margin: 0 0 24px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.upload-container {
|
.upload-container {
|
||||||
width: min(640px, 100%);
|
width: min(640px, 100%);
|
||||||
background: #ffffffdd;
|
background: #ffffffdd;
|
||||||
@@ -42,6 +56,14 @@ h1 {
|
|||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.upload-container {
|
||||||
|
padding: 20px;
|
||||||
|
gap: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dropzone {
|
.dropzone {
|
||||||
border: 2px dashed #6366f1;
|
border: 2px dashed #6366f1;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -55,6 +77,13 @@ h1 {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dropzone {
|
||||||
|
padding: 32px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dropzone:focus,
|
.dropzone:focus,
|
||||||
.dropzone:focus-within {
|
.dropzone:focus-within {
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -130,6 +159,17 @@ button {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
button {
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
min-height: 48px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover:not(:disabled) {
|
button:hover:not(:disabled) {
|
||||||
@@ -204,6 +244,16 @@ button:disabled {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.download-button {
|
||||||
|
padding: 16px 24px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button:hover {
|
.download-button:hover {
|
||||||
@@ -246,6 +296,53 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.error-popup {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
transform: none;
|
||||||
|
max-width: none;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p.strong {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Success content */
|
/* Success content */
|
||||||
.success-content {
|
.success-content {
|
||||||
animation: fadeIn 0.3s ease-in;
|
animation: fadeIn 0.3s ease-in;
|
||||||
@@ -282,6 +379,15 @@ button:disabled {
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.download-link-input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.download-link-input:focus {
|
.download-link-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #6366f1;
|
border-color: #6366f1;
|
||||||
@@ -299,6 +405,16 @@ button:disabled {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.copy-link-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 24px;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-link-button:hover {
|
.copy-link-button:hover {
|
||||||
@@ -325,6 +441,16 @@ button:disabled {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.upload-another-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 24px;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-another-button:hover {
|
.upload-another-button:hover {
|
||||||
@@ -363,3 +489,82 @@ button:disabled {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.status {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone p.strong {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-popup {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
transform: none;
|
||||||
|
max-width: none;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
body {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-container {
|
||||||
|
padding: 16px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone {
|
||||||
|
padding: 24px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user