Update app.py
Browse files
app.py
CHANGED
|
@@ -317,15 +317,57 @@ USER_TEMPLATE = """
|
|
| 317 |
background-color: var(--card-bg);
|
| 318 |
padding: 12px var(--padding);
|
| 319 |
border-radius: var(--border-radius);
|
| 320 |
-
display: flex;
|
|
|
|
| 321 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 322 |
transition: background-color 0.2s ease;
|
| 323 |
word-break: break-word;
|
| 324 |
}
|
| 325 |
.file-item:hover { background-color: rgba(255, 255, 255, 0.08); }
|
| 326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
.file-name { font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 328 |
.file-meta { font-size: 0.8em; color: var(--tg-theme-hint-color); }
|
|
|
|
| 329 |
.file-actions a, .file-actions button {
|
| 330 |
display: inline-block;
|
| 331 |
padding: 6px 10px;
|
|
@@ -344,33 +386,6 @@ USER_TEMPLATE = """
|
|
| 344 |
.loading, .no-files { text-align: center; padding: 20px; color: var(--tg-theme-hint-color); }
|
| 345 |
.progress-bar { width: 100%; background-color: #ddd; border-radius: 4px; height: 8px; margin-top: 5px; display: none; }
|
| 346 |
.progress-bar-inner { height: 100%; width: 0%; background-color: var(--tg-theme-button-color); border-radius: 4px; transition: width 0.1s linear; }
|
| 347 |
-
.modal {
|
| 348 |
-
display: none; position: fixed; z-index: 1001;
|
| 349 |
-
left: 0; top: 0; width: 100%; height: 100%;
|
| 350 |
-
overflow: auto; background-color: rgba(0,0,0,0.8);
|
| 351 |
-
backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
|
| 352 |
-
animation: fadeIn 0.3s ease-out;
|
| 353 |
-
}
|
| 354 |
-
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 355 |
-
.modal-content {
|
| 356 |
-
margin: auto; display: block; max-width: 90%; max-height: 85%;
|
| 357 |
-
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
| 358 |
-
}
|
| 359 |
-
.modal-content img, .modal-content video, .modal-content audio {
|
| 360 |
-
display: block; width: auto; height: auto; max-width: 100%; max-height: 100%; margin: auto;
|
| 361 |
-
background-color: var(--tg-theme-bg-color);
|
| 362 |
-
}
|
| 363 |
-
.modal-close {
|
| 364 |
-
position: absolute; top: 15px; right: 35px; color: #f1f1f1; font-size: 40px;
|
| 365 |
-
font-weight: bold; transition: 0.3s; cursor: pointer; z-index: 1002;
|
| 366 |
-
text-shadow: 0 1px 3px rgba(0,0,0,0.5);
|
| 367 |
-
}
|
| 368 |
-
.modal-close:hover, .modal-close:focus { color: #bbb; text-decoration: none; }
|
| 369 |
-
.modal-caption {
|
| 370 |
-
margin: auto; display: block; width: 80%; max-width: 700px; text-align: center;
|
| 371 |
-
color: #ccc; padding: 10px 0; height: 50px; position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%);
|
| 372 |
-
background: rgba(0,0,0,0.5); border-radius: 8px;
|
| 373 |
-
}
|
| 374 |
.spinner {
|
| 375 |
border: 4px solid rgba(255, 255, 255, 0.3);
|
| 376 |
border-radius: 50%;
|
|
@@ -407,12 +422,6 @@ USER_TEMPLATE = """
|
|
| 407 |
</ul>
|
| 408 |
</section>
|
| 409 |
</div>
|
| 410 |
-
<div id="viewerModal" class="modal">
|
| 411 |
-
<span class="modal-close" id="modalCloseBtn">×</span>
|
| 412 |
-
<div id="modalContent" class="modal-content">
|
| 413 |
-
</div>
|
| 414 |
-
<div id="modalCaption" class="modal-caption"></div>
|
| 415 |
-
</div>
|
| 416 |
<script>
|
| 417 |
const tg = window.Telegram.WebApp;
|
| 418 |
const MAX_FILES = {{ max_files }};
|
|
@@ -428,10 +437,6 @@ USER_TEMPLATE = """
|
|
| 428 |
const loadingSpinner = document.getElementById('loading-spinner');
|
| 429 |
const progressBar = document.getElementById('progress-bar');
|
| 430 |
const progressBarInner = document.getElementById('progress-bar-inner');
|
| 431 |
-
const modal = document.getElementById('viewerModal');
|
| 432 |
-
const modalContent = document.getElementById('modalContent');
|
| 433 |
-
const modalCaption = document.getElementById('modalCaption');
|
| 434 |
-
const modalCloseBtn = document.getElementById('modalCloseBtn');
|
| 435 |
|
| 436 |
function applyTheme(themeParams) {
|
| 437 |
const root = document.documentElement;
|
|
@@ -470,9 +475,45 @@ USER_TEMPLATE = """
|
|
| 470 |
}
|
| 471 |
noFilesMessage.style.display = 'none';
|
| 472 |
files.sort((a, b) => b.uploaded_at_ts - a.uploaded_at_ts);
|
|
|
|
| 473 |
files.forEach(file => {
|
| 474 |
const li = document.createElement('li');
|
| 475 |
li.classList.add('file-item');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
const fileInfoDiv = document.createElement('div');
|
| 477 |
fileInfoDiv.classList.add('file-info');
|
| 478 |
const fileNameSpan = document.createElement('span');
|
|
@@ -485,31 +526,18 @@ USER_TEMPLATE = """
|
|
| 485 |
const size = file.size ? formatBytes(file.size) : '';
|
| 486 |
fileMetaSpan.textContent = `${date}${size ? ' - ' + size : ''}`;
|
| 487 |
fileInfoDiv.appendChild(fileMetaSpan);
|
|
|
|
|
|
|
| 488 |
const fileActionsDiv = document.createElement('div');
|
| 489 |
fileActionsDiv.classList.add('file-actions');
|
| 490 |
-
const mimeType = file.content_type || '';
|
| 491 |
-
if (mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('audio/')) {
|
| 492 |
-
const viewButton = document.createElement('button');
|
| 493 |
-
viewButton.textContent = '👁️';
|
| 494 |
-
viewButton.classList.add('view-btn');
|
| 495 |
-
viewButton.style.backgroundColor = '#6f42c1';
|
| 496 |
-
viewButton.style.color = 'white';
|
| 497 |
-
viewButton.title = 'Просмотр';
|
| 498 |
-
viewButton.onclick = (e) => {
|
| 499 |
-
e.stopPropagation();
|
| 500 |
-
openViewer(file);
|
| 501 |
-
};
|
| 502 |
-
fileActionsDiv.appendChild(viewButton);
|
| 503 |
-
}
|
| 504 |
const downloadLink = document.createElement('a');
|
| 505 |
downloadLink.classList.add('download-btn');
|
| 506 |
downloadLink.textContent = 'Скачать';
|
| 507 |
-
downloadLink.href =
|
| 508 |
-
downloadLink.
|
| 509 |
downloadLink.title = 'Скачать файл';
|
| 510 |
downloadLink.onclick = (e) => e.stopPropagation();
|
| 511 |
fileActionsDiv.appendChild(downloadLink);
|
| 512 |
-
li.appendChild(fileInfoDiv);
|
| 513 |
li.appendChild(fileActionsDiv);
|
| 514 |
fileListUl.appendChild(li);
|
| 515 |
});
|
|
@@ -620,67 +648,19 @@ USER_TEMPLATE = """
|
|
| 620 |
uploadStatusDiv.style.color = 'red';
|
| 621 |
} finally {
|
| 622 |
progressBar.style.display = 'none';
|
| 623 |
-
if (currentFiles.length > 0) {
|
| 624 |
uploadButton.classList.add('enabled');
|
| 625 |
uploadButton.disabled = false;
|
| 626 |
-
} else {
|
| 627 |
uploadButton.classList.remove('enabled');
|
| 628 |
uploadButton.disabled = true;
|
| 629 |
-
selectedFilesDiv.textContent = 'Файлы не выбраны';
|
|
|
|
|
|
|
| 630 |
}
|
| 631 |
}
|
| 632 |
}
|
| 633 |
|
| 634 |
-
function openViewer(file) {
|
| 635 |
-
modal.style.display = 'block';
|
| 636 |
-
modalContent.innerHTML = '';
|
| 637 |
-
modalCaption.textContent = file.filename;
|
| 638 |
-
const mimeType = file.content_type || '';
|
| 639 |
-
const downloadUrl = `/download/${encodeURIComponent(file.filename)}?initData=${encodeURIComponent(userInitData)}`;
|
| 640 |
-
let element;
|
| 641 |
-
if (mimeType.startsWith('image/')) {
|
| 642 |
-
element = document.createElement('img');
|
| 643 |
-
element.src = downloadUrl;
|
| 644 |
-
element.alt = file.filename;
|
| 645 |
-
} else if (mimeType.startsWith('video/')) {
|
| 646 |
-
element = document.createElement('video');
|
| 647 |
-
element.src = downloadUrl;
|
| 648 |
-
element.controls = true;
|
| 649 |
-
element.autoplay = true;
|
| 650 |
-
} else if (mimeType.startsWith('audio/')) {
|
| 651 |
-
element = document.createElement('audio');
|
| 652 |
-
element.src = downloadUrl;
|
| 653 |
-
element.controls = true;
|
| 654 |
-
element.autoplay = true;
|
| 655 |
-
element.style.padding = '20px';
|
| 656 |
-
}
|
| 657 |
-
if (element) {
|
| 658 |
-
modalContent.appendChild(element);
|
| 659 |
-
if (tg.HapticFeedback) {
|
| 660 |
-
tg.HapticFeedback.impactOccurred('light');
|
| 661 |
-
}
|
| 662 |
-
} else {
|
| 663 |
-
modalCaption.textContent = 'Предпросмотр недоступен для этого типа файла.';
|
| 664 |
-
}
|
| 665 |
-
}
|
| 666 |
-
|
| 667 |
-
function closeViewer() {
|
| 668 |
-
modal.style.display = 'none';
|
| 669 |
-
const mediaElement = modalContent.querySelector('video, audio');
|
| 670 |
-
if (mediaElement) {
|
| 671 |
-
mediaElement.pause();
|
| 672 |
-
mediaElement.src = '';
|
| 673 |
-
}
|
| 674 |
-
modalContent.innerHTML = '';
|
| 675 |
-
}
|
| 676 |
-
|
| 677 |
-
modalCloseBtn.onclick = closeViewer;
|
| 678 |
-
modal.onclick = function(event) {
|
| 679 |
-
if (event.target === modal) {
|
| 680 |
-
closeViewer();
|
| 681 |
-
}
|
| 682 |
-
};
|
| 683 |
-
|
| 684 |
function setupTelegram() {
|
| 685 |
if (!tg || !tg.initData) {
|
| 686 |
console.error("Telegram WebApp script not loaded or initData is missing.");
|
|
|
|
| 317 |
background-color: var(--card-bg);
|
| 318 |
padding: 12px var(--padding);
|
| 319 |
border-radius: var(--border-radius);
|
| 320 |
+
display: flex;
|
| 321 |
+
align-items: center;
|
| 322 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 323 |
transition: background-color 0.2s ease;
|
| 324 |
word-break: break-word;
|
| 325 |
}
|
| 326 |
.file-item:hover { background-color: rgba(255, 255, 255, 0.08); }
|
| 327 |
+
|
| 328 |
+
.file-preview-area {
|
| 329 |
+
flex-shrink: 0;
|
| 330 |
+
width: 80px;
|
| 331 |
+
height: 80px;
|
| 332 |
+
margin-right: var(--padding);
|
| 333 |
+
display: flex;
|
| 334 |
+
align-items: center;
|
| 335 |
+
justify-content: center;
|
| 336 |
+
background-color: rgba(0,0,0,0.05);
|
| 337 |
+
border-radius: 8px;
|
| 338 |
+
overflow: hidden;
|
| 339 |
+
}
|
| 340 |
+
.file-preview-image,
|
| 341 |
+
.file-preview-video {
|
| 342 |
+
max-width: 100%;
|
| 343 |
+
max-height: 100%;
|
| 344 |
+
object-fit: cover;
|
| 345 |
+
display: block;
|
| 346 |
+
}
|
| 347 |
+
.file-preview-video {
|
| 348 |
+
width: 100%;
|
| 349 |
+
height: 100%;
|
| 350 |
+
}
|
| 351 |
+
.file-preview-audio {
|
| 352 |
+
width: 100%;
|
| 353 |
+
transform: scale(0.85);
|
| 354 |
+
transform-origin: center;
|
| 355 |
+
}
|
| 356 |
+
.file-preview-icon {
|
| 357 |
+
font-size: 0.8em;
|
| 358 |
+
font-weight: bold;
|
| 359 |
+
color: var(--tg-theme-hint-color);
|
| 360 |
+
padding: 5px;
|
| 361 |
+
text-align: center;
|
| 362 |
+
text-transform: uppercase;
|
| 363 |
+
word-break: break-all;
|
| 364 |
+
line-height: 1.2;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
.file-info { flex-grow: 1; margin-right: 10px; overflow: hidden; min-width: 0;}
|
| 368 |
.file-name { font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 369 |
.file-meta { font-size: 0.8em; color: var(--tg-theme-hint-color); }
|
| 370 |
+
.file-actions { flex-shrink: 0; }
|
| 371 |
.file-actions a, .file-actions button {
|
| 372 |
display: inline-block;
|
| 373 |
padding: 6px 10px;
|
|
|
|
| 386 |
.loading, .no-files { text-align: center; padding: 20px; color: var(--tg-theme-hint-color); }
|
| 387 |
.progress-bar { width: 100%; background-color: #ddd; border-radius: 4px; height: 8px; margin-top: 5px; display: none; }
|
| 388 |
.progress-bar-inner { height: 100%; width: 0%; background-color: var(--tg-theme-button-color); border-radius: 4px; transition: width 0.1s linear; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
.spinner {
|
| 390 |
border: 4px solid rgba(255, 255, 255, 0.3);
|
| 391 |
border-radius: 50%;
|
|
|
|
| 422 |
</ul>
|
| 423 |
</section>
|
| 424 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
<script>
|
| 426 |
const tg = window.Telegram.WebApp;
|
| 427 |
const MAX_FILES = {{ max_files }};
|
|
|
|
| 437 |
const loadingSpinner = document.getElementById('loading-spinner');
|
| 438 |
const progressBar = document.getElementById('progress-bar');
|
| 439 |
const progressBarInner = document.getElementById('progress-bar-inner');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
|
| 441 |
function applyTheme(themeParams) {
|
| 442 |
const root = document.documentElement;
|
|
|
|
| 475 |
}
|
| 476 |
noFilesMessage.style.display = 'none';
|
| 477 |
files.sort((a, b) => b.uploaded_at_ts - a.uploaded_at_ts);
|
| 478 |
+
|
| 479 |
files.forEach(file => {
|
| 480 |
const li = document.createElement('li');
|
| 481 |
li.classList.add('file-item');
|
| 482 |
+
|
| 483 |
+
const previewDiv = document.createElement('div');
|
| 484 |
+
previewDiv.classList.add('file-preview-area');
|
| 485 |
+
const downloadUrl = `/download/${encodeURIComponent(file.filename)}?initData=${encodeURIComponent(userInitData)}`;
|
| 486 |
+
const mimeType = file.content_type || '';
|
| 487 |
+
|
| 488 |
+
if (mimeType.startsWith('image/')) {
|
| 489 |
+
const img = document.createElement('img');
|
| 490 |
+
img.src = downloadUrl;
|
| 491 |
+
img.alt = file.filename;
|
| 492 |
+
img.classList.add('file-preview-image');
|
| 493 |
+
previewDiv.appendChild(img);
|
| 494 |
+
} else if (mimeType.startsWith('video/')) {
|
| 495 |
+
const video = document.createElement('video');
|
| 496 |
+
video.src = downloadUrl;
|
| 497 |
+
video.controls = true;
|
| 498 |
+
video.classList.add('file-preview-video');
|
| 499 |
+
video.preload = "metadata";
|
| 500 |
+
previewDiv.appendChild(video);
|
| 501 |
+
} else if (mimeType.startsWith('audio/')) {
|
| 502 |
+
const audio = document.createElement('audio');
|
| 503 |
+
audio.src = downloadUrl;
|
| 504 |
+
audio.controls = true;
|
| 505 |
+
audio.classList.add('file-preview-audio');
|
| 506 |
+
audio.preload = "metadata";
|
| 507 |
+
previewDiv.appendChild(audio);
|
| 508 |
+
} else {
|
| 509 |
+
const iconPlaceholder = document.createElement('div');
|
| 510 |
+
iconPlaceholder.classList.add('file-preview-icon');
|
| 511 |
+
const ext = file.filename.split('.').pop().toLowerCase() || 'file';
|
| 512 |
+
iconPlaceholder.textContent = ext.substring(0,4);
|
| 513 |
+
previewDiv.appendChild(iconPlaceholder);
|
| 514 |
+
}
|
| 515 |
+
li.appendChild(previewDiv);
|
| 516 |
+
|
| 517 |
const fileInfoDiv = document.createElement('div');
|
| 518 |
fileInfoDiv.classList.add('file-info');
|
| 519 |
const fileNameSpan = document.createElement('span');
|
|
|
|
| 526 |
const size = file.size ? formatBytes(file.size) : '';
|
| 527 |
fileMetaSpan.textContent = `${date}${size ? ' - ' + size : ''}`;
|
| 528 |
fileInfoDiv.appendChild(fileMetaSpan);
|
| 529 |
+
li.appendChild(fileInfoDiv);
|
| 530 |
+
|
| 531 |
const fileActionsDiv = document.createElement('div');
|
| 532 |
fileActionsDiv.classList.add('file-actions');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
const downloadLink = document.createElement('a');
|
| 534 |
downloadLink.classList.add('download-btn');
|
| 535 |
downloadLink.textContent = 'Скачать';
|
| 536 |
+
downloadLink.href = downloadUrl;
|
| 537 |
+
downloadLink.setAttribute('download', file.filename);
|
| 538 |
downloadLink.title = 'Скачать файл';
|
| 539 |
downloadLink.onclick = (e) => e.stopPropagation();
|
| 540 |
fileActionsDiv.appendChild(downloadLink);
|
|
|
|
| 541 |
li.appendChild(fileActionsDiv);
|
| 542 |
fileListUl.appendChild(li);
|
| 543 |
});
|
|
|
|
| 648 |
uploadStatusDiv.style.color = 'red';
|
| 649 |
} finally {
|
| 650 |
progressBar.style.display = 'none';
|
| 651 |
+
if (currentFiles.length > 0 && fileInput.files.length > 0) { // Check also fileInput as currentFiles might not be reset on some errors
|
| 652 |
uploadButton.classList.add('enabled');
|
| 653 |
uploadButton.disabled = false;
|
| 654 |
+
} else { // Default to disabled if no files or error
|
| 655 |
uploadButton.classList.remove('enabled');
|
| 656 |
uploadButton.disabled = true;
|
| 657 |
+
selectedFilesDiv.textContent = 'Файлы не выбраны'; // Ensure this is accurate
|
| 658 |
+
fileInput.value = ''; // Clear file input on final step
|
| 659 |
+
currentFiles = [];
|
| 660 |
}
|
| 661 |
}
|
| 662 |
}
|
| 663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
function setupTelegram() {
|
| 665 |
if (!tg || !tg.initData) {
|
| 666 |
console.error("Telegram WebApp script not loaded or initData is missing.");
|