|
|
|
|
| import { Utils } from '../utils.js';
|
| import { StateManager } from '../services/state-manager.js';
|
| import { ApiService } from '../services/api-service.js';
|
| import { TranslationService } from '../services/translation-service.js';
|
|
|
| export const FileUploadComponent = {
|
| elements: {
|
| uploadFileBtn: null,
|
| uploadFileOverlay: null,
|
| fileDropZone: null,
|
| fileInput: null,
|
| doneFileUploadBtn: null,
|
| closeFileUploadBtn: null,
|
| fileListHtml: null
|
| },
|
|
|
| constants: {
|
| FILE_SIZE_LIMIT: 10 * 1024 * 1024,
|
| TOTAL_FILE_SIZE_LIMIT: 30 * 1024 * 1024,
|
| MAX_FILE_NAME_LENGTH: 50,
|
| ALLOWED_TYPES: ['.pdf', '.txt', '.docx', '.jpg', '.jpeg', '.png']
|
| },
|
|
|
| icons: {
|
| upload: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
| </svg>`,
|
| spinner: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="spinning">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
| </svg>`,
|
| check: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
| </svg>`,
|
| trash: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
| </svg>`
|
| },
|
|
|
| |
| |
|
|
| init() {
|
| this.elements.uploadFileBtn = document.getElementById('upload-file-btn');
|
| this.elements.uploadFileOverlay = document.getElementById('upload-file-overlay');
|
| this.elements.fileDropZone = document.getElementById('file-drop-zone');
|
| this.elements.fileInput = document.getElementById('file-input');
|
| this.elements.doneFileUploadBtn = document.getElementById('done-file-upload');
|
| this.elements.closeFileUploadBtn = document.getElementById('close-file-upload-btn');
|
| this.elements.fileListHtml = document.getElementById('file-list');
|
|
|
| this.attachOutsideClickListener();
|
| this.attachEventListeners();
|
| this.renderFiles();
|
| },
|
|
|
| attachOutsideClickListener() {
|
| this.elements.uploadFileOverlay.addEventListener('click', (e) => {
|
|
|
| if (e.target === this.elements.uploadFileOverlay) {
|
| this.closeOverlay();
|
| }
|
| });
|
| },
|
|
|
| |
| |
|
|
| attachEventListeners() {
|
| this.elements.uploadFileBtn.addEventListener('click', (e) => this.openOverlay(e));
|
| this.elements.fileDropZone.addEventListener('click', () => this.elements.fileInput.click());
|
| this.elements.closeFileUploadBtn.addEventListener('click', () => this.closeOverlay());
|
| this.elements.doneFileUploadBtn.addEventListener('click', () => this.closeOverlay());
|
|
|
|
|
| ['dragover', 'drop'].forEach(eventName => {
|
| this.elements.fileDropZone.addEventListener(eventName, (e) => e.preventDefault());
|
| });
|
|
|
| this.elements.fileDropZone.addEventListener('dragover', () => {
|
| this.elements.fileDropZone.classList.add('active');
|
| });
|
|
|
|
|
| this.elements.fileDropZone.addEventListener('drop', (e) => {
|
| this.elements.fileDropZone.classList.remove('active');
|
| const addedFiles = Array.from(e.dataTransfer.files);
|
| this.handleFileAddition(addedFiles);
|
| });
|
|
|
|
|
| this.elements.fileInput.addEventListener('change', (e) => {
|
| const addedFiles = Array.from(e.target.files);
|
| this.handleFileAddition(addedFiles);
|
| });
|
| },
|
|
|
| |
| |
|
|
| openOverlay(e) {
|
| e.preventDefault();
|
| this.elements.uploadFileOverlay.style.display = '';
|
| },
|
|
|
| |
| |
|
|
| closeOverlay() {
|
| this.elements.uploadFileOverlay.style.display = 'none';
|
| },
|
|
|
| |
| |
| |
|
|
| async handleFileAddition(newFiles) {
|
| const isProcessingSuccessful = this.processFiles(newFiles);
|
| if (!isProcessingSuccessful) {
|
| return;
|
| }
|
|
|
| newFiles.forEach(file => StateManager.addFile(file));
|
|
|
|
|
| newFiles.forEach(async (file) => {
|
| file.state = 'uploading';
|
| this.renderFiles();
|
| const isUploadSuccessful = await ApiService.uploadFile(file);
|
| file.state = isUploadSuccessful ? 'uploaded' : 'ready';
|
| this.renderFiles();
|
| });
|
|
|
| this.renderFiles();
|
| },
|
|
|
| |
| |
| |
| |
|
|
| processFiles(newFiles) {
|
|
|
| const unallowedFiles = newFiles.filter((file) =>
|
| !this.constants.ALLOWED_TYPES.some(ext => file.name.endsWith(ext))
|
| );
|
|
|
| if (unallowedFiles.length > 0) {
|
| newFiles.forEach((file) => Utils.removeFileFromInput(this.elements.fileInput, file));
|
| showSnackbar(translations[StateManager.currentLang]["error_file_format"], "error");
|
| return false;
|
| }
|
|
|
|
|
| const largeFiles = newFiles.filter((file) => file.size > this.constants.FILE_SIZE_LIMIT);
|
| if (largeFiles.length > 0) {
|
| newFiles.forEach((file) => Utils.removeFileFromInput(this.elements.fileInput, file));
|
| showSnackbar(translations[StateManager.currentLang]["error_file_size"], "error");
|
| return false;
|
| }
|
|
|
|
|
| const totalFileSize = [...newFiles, ...StateManager.getFiles()].reduce((sum, file) => sum + file.size, 0);
|
| if (totalFileSize > this.constants.TOTAL_FILE_SIZE_LIMIT) {
|
| newFiles.forEach((file) => Utils.removeFileFromInput(this.elements.fileInput, file));
|
| showSnackbar(translations[StateManager.currentLang]["error_total_file_size"], "error");
|
| return false;
|
| }
|
|
|
|
|
| const filesWithLongName = newFiles.filter((file) => file.name.length > this.constants.MAX_FILE_NAME_LENGTH);
|
| if (filesWithLongName.length > 0) {
|
| newFiles.forEach((file) => Utils.removeFileFromInput(this.elements.fileInput, file));
|
| showSnackbar(translations[StateManager.currentLang]["error_file_name_length"], "error");
|
| return false;
|
| }
|
|
|
| return true;
|
| },
|
|
|
| |
| |
|
|
| renderFiles() {
|
| this.elements.fileListHtml.innerHTML = '';
|
| const sessionFiles = StateManager.getFiles();
|
|
|
| if (sessionFiles.length === 0) {
|
| const noFileMessage = document.createElement('div');
|
| noFileMessage.classList.add('no-file');
|
| noFileMessage.dataset.i18n = "no_files";
|
| this.elements.fileListHtml.appendChild(noFileMessage);
|
| TranslationService.applyTranslation();
|
| return;
|
| }
|
|
|
| sessionFiles.forEach((f) => {
|
| const fileItem = document.createElement('div');
|
| fileItem.classList.add('file-item');
|
| fileItem.textContent = f.name;
|
|
|
| const fileActions = document.createElement('div');
|
| fileActions.classList.add('file-actions');
|
|
|
| const uploadButton = this.createUploadButton(f);
|
| const deleteButton = this.createDeleteButton(f);
|
|
|
| fileActions.appendChild(uploadButton);
|
| fileActions.appendChild(deleteButton);
|
| fileItem.appendChild(fileActions);
|
| this.elements.fileListHtml.appendChild(fileItem);
|
| });
|
|
|
| TranslationService.applyTranslation();
|
| },
|
|
|
| |
| |
| |
| |
|
|
| createUploadButton(file) {
|
| const uploadButton = document.createElement('button');
|
|
|
| if (file.state === 'uploaded') {
|
| uploadButton.innerHTML = this.icons.check + `<span data-i18n="file_uploaded"></span>`;
|
| uploadButton.classList.add('disabled-button');
|
| uploadButton.disabled = true;
|
| } else if (file.state === 'uploading') {
|
| uploadButton.innerHTML = this.icons.spinner + `<span data-i18n="file_uploading"></span>`;
|
| uploadButton.classList.add('disabled-button');
|
| uploadButton.disabled = true;
|
| } else if (file.state === 'ready') {
|
| uploadButton.innerHTML = this.icons.upload + `<span data-i18n="file_upload"></span>`;
|
| uploadButton.classList.add('ok-button');
|
| uploadButton.addEventListener('click', async () => {
|
| file.state = 'uploading';
|
| this.renderFiles();
|
| const isUploadSuccessful = await ApiService.uploadFile(file);
|
| file.state = isUploadSuccessful ? 'uploaded' : 'ready';
|
| this.renderFiles();
|
| });
|
| }
|
|
|
| return uploadButton;
|
| },
|
|
|
| |
| |
| |
| |
|
|
| createDeleteButton(file) {
|
| const deleteButton = document.createElement('button');
|
| deleteButton.innerHTML = this.icons.trash + `<span data-i18n="file_delete"></span>`;
|
| deleteButton.classList.add('no-button');
|
| deleteButton.addEventListener('click', async () => {
|
|
|
| const isDeletionSuccessful = file.state === 'uploaded'
|
| ? await ApiService.deleteFile(file)
|
| : true;
|
|
|
| if (isDeletionSuccessful) {
|
| Utils.removeFileFromInput(this.elements.fileInput, file);
|
| StateManager.removeFile(file);
|
| this.renderFiles();
|
| }
|
| });
|
|
|
| return deleteButton;
|
| }
|
| }; |