Files
term_portal/express/public/javascripts/extraction-files-edit.js
T
2023-03-10 12:50:23 +01:00

288 lines
9.0 KiB
JavaScript

/* 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 = '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 })
}
}