/* global axios, removeAllChildNodes, bootstrap, i18next */ const fileUploadContainerEl = document.getElementById('upload-files-container') const fileUploadForm = document.forms['upload-files'] const fileInputEl = fileUploadForm.querySelector('input[type="file"]') const filesListEl = document.getElementById('files-list') const dropArea = document.querySelector('.drag-area') const dragButton = dropArea.querySelector('button') const dragInput = dropArea.querySelector('input') const modalAlert = new bootstrap.Modal(document.getElementById('alert-modal')) let file let fileListElCount = filesListEl.childElementCount let isUploadInProgress = false let isDeletionInProgress = false const existingFileNames = [...filesListEl.children].map( fileListEl => fileListEl.querySelector('.file-name').textContent ) dragButton.onclick = () => { dragInput.click() } dragInput.addEventListener('change', function () { file = this.files[0] dropArea.classList.add('active') // showFile(file) }) dropArea.addEventListener('dragover', event => { event.preventDefault() }) dropArea.addEventListener('dragleave', () => {}) dropArea.addEventListener('drop', event => { event.preventDefault() fileInputEl.files = event.dataTransfer.files submitFiles() }) // offsetMain.addEventListener('click', handleClick) // function handleClick({ target }) { // const row = target.closest('.delete-btn-table') // if (row) { // deleteRow(row) // } // function deleteRow(ele) { // // console.log(ele) // const element = ele.closest('tr') // element.remove() // } // } const extractionId = +fileUploadForm.extractionId.value let apiEndpointBase switch (location.pathname.split('/').at(-1)) { case 'besedila': apiEndpointBase = `/api/v1/extraction/${extractionId}/documents` break case 'stop-termini': apiEndpointBase = `/api/v1/extraction/${extractionId}/stop-terms` break default: throw Error("apiEndpointBase couldn't be determined") } fileInputEl.addEventListener('change', submitFiles) filesListEl.addEventListener('click', handleFileClick) async function submitFiles() { if (isUploadInProgress) return isUploadInProgress = true fileUploadContainerEl.hidden = true const MAX_FILE_SIZE = 10 ** 9 // 1 GB const filesToUpload = [] const failedUploads = [] const succeededFileListEls = [] let didRemoveAnyFileListEls = false for (const file of fileInputEl.files) { const filename = file.name const indexOfSameNamed = existingFileNames.indexOf(filename) if (indexOfSameNamed !== -1) { filesListEl.children[indexOfSameNamed].remove() existingFileNames.splice(indexOfSameNamed, 1) didRemoveAnyFileListEls = true } const fileListEl = createFileListEl(filename) filesListEl.appendChild(fileListEl) existingFileNames.push(filename) if (file.size > MAX_FILE_SIZE) { const failedUpload = { filename, message: 'File too large. Must not be over 1 GB.' } failedUploads.push(failedUpload) updateFileListEl(fileListEl, { status: 'failed' }) continue } filesToUpload.push([file, fileListEl]) } if (didRemoveAnyFileListEls) reindexFileListEls() for (const [file, fileListEl] of filesToUpload) { try { const payload = new FormData() payload.set(fileInputEl.name, file) const { data: fileStats } = await axios.put(apiEndpointBase, payload, { onUploadProgress: displayUploadProgress(fileListEl) }) succeededFileListEls.push(fileListEl) updateFileListEl(fileListEl, { status: 'success', fileStats }) } catch (error) { const failedUpload = { filename: file.name, message: error.response.data } failedUploads.push(failedUpload) updateFileListEl(fileListEl, { status: 'failed' }) } } fileInputEl.value = '' succeededFileListEls.forEach(el => updateFileListEl(el, { status: 'done' })) if (failedUploads.length) displayFailedUploads(failedUploads) isUploadInProgress = false fileUploadContainerEl.hidden = false } function createFileListEl(filename) { const indexEl = document.createElement('td') indexEl.className = 'file-index' indexEl.textContent = ++fileListElCount const nameEl = document.createElement('td') nameEl.className = 'file-name' nameEl.textContent = filename const sizeEl = document.createElement('td') sizeEl.className = 'file-size' const dateEl = document.createElement('td') dateEl.className = 'file-date-modified' const progressBarId = `progress-bar-${fileListElCount}` const progressLabelEl = document.createElement('label') progressLabelEl.className = 'me-1' progressLabelEl.for = progressBarId progressLabelEl.textContent = '0%' const progressBarEl = document.createElement('progress') progressBarEl.id = progressBarId progressBarEl.className = 'upload-progress' progressBarEl.value = 0 progressBarEl.max = 100 const lastEl = document.createElement('td') lastEl.className = 'file-last-cell' lastEl.append(progressLabelEl, progressBarEl) const fileListEl = document.createElement('tr') fileListEl.append(indexEl, nameEl, sizeEl, dateEl, lastEl) return fileListEl } function updateFileListEl(fileListEl, { status, fileStats, index }) { if (status === 'success') { const nameEl = fileListEl.querySelector('.file-name') nameEl.textContent = fileStats.filename const sizeEl = fileListEl.querySelector('.file-size') sizeEl.textContent = fileStats.size const dateEl = fileListEl.querySelector('.file-date-modified') const formattedDate = new Date(fileStats.timeModified).toLocaleDateString( 'sl-SL' ) dateEl.textContent = formattedDate const successImgEl = document.createElement('img') successImgEl.src = '/images/valid.svg' successImgEl.alt = i18next.t('Datoteka uspešno naložena') const lastEl = fileListEl.querySelector('.file-last-cell') removeAllChildNodes(lastEl) lastEl.append(successImgEl) } else if (status === 'done') { const deleteImgEl = document.createElement('img') deleteImgEl.src = '/images/red-trash-icon.svg' deleteImgEl.alt = '' const deleteSpanEl = document.createElement('span') deleteSpanEl.className = 'ms-2' deleteSpanEl.textContent = i18next.t('Briši') const deleteButtonEl = document.createElement('button') deleteButtonEl.className = 'p-0 delete-file delete-btn-table' deleteButtonEl.append(deleteImgEl, deleteSpanEl) const lastEl = fileListEl.querySelector('.file-last-cell') removeAllChildNodes(lastEl) lastEl.append(deleteButtonEl) } else if (status === 'reindex') { const indexEl = fileListEl.querySelector('.file-index') indexEl.textContent = index + 1 } else if (status === 'failed') { const failImgEl = document.createElement('img') failImgEl.src = '/images/x_red.svg' failImgEl.alt = i18next.t('Napaka pri nalaganju datoteke') const lastEl = fileListEl.querySelector('.file-last-cell') removeAllChildNodes(lastEl) lastEl.append(failImgEl) } } function displayFailedUploads(failedUploads) { const modalContentEl = modalAlert._element.querySelector( '.modal-alert-content' ) modalContentEl.classList.remove('d-flex') const descriptionEl = modalAlert._element.querySelector('#alert-text') descriptionEl.textContent = i18next.t( 'Naslednje datoteke niso bile naložene zaradi navedenih razlogov:' ) modalContentEl.querySelector('ul')?.remove() const listEl = document.createElement('ul') modalContentEl.append(listEl) failedUploads.forEach(({ filename, message }) => { const bulletEl = document.createElement('li') bulletEl.textContent = `${filename} - ${message}` listEl.appendChild(bulletEl) }) modalAlert.toggle() } async function handleFileClick(e) { if (e.target.closest('.delete-file')) { if (isUploadInProgress) { alert( i18next.t('Brisanje je onemogočeno, doker se nalagajo nove datoteke.') ) return } if (isDeletionInProgress) { alert(i18next.t('Brisanje je onemogočeno, saj je eno še v procesu.')) return } isDeletionInProgress = true const fileEl = e.target.closest('tr') const filename = fileEl.querySelector('.file-name').textContent try { await axios.delete(`${apiEndpointBase}/${filename}`) fileEl.remove() existingFileNames.splice(existingFileNames.indexOf(filename), 1) reindexFileListEls() isDeletionInProgress = false } catch { alert(i18next.t('Pri brisanju datoteke je prišlo do napake.')) } } } function displayUploadProgress(fileListEl) { const progressEl = fileListEl.querySelector('.upload-progress') return function ({ loaded, total }) { const percentCompleted = Math.round((loaded / total) * 100) progressEl.setAttribute('value', percentCompleted) progressEl.previousElementSibling.textContent = `${percentCompleted}%` } } function reindexFileListEls() { fileListElCount = filesListEl.childElementCount for (let i = 0; i < fileListElCount; i++) { const fileListEl = filesListEl.children[i] updateFileListEl(fileListEl, { status: 'reindex', index: i }) } }