tube/static/upload.js
Heinrich 'Henrik' Langos 0a793f7d3f feat: Make upload path selectable (#39)
This works for me, but for a more public site, I think I'll also add a boolean attribute named "upload_allowed" and "writable" to Config.Library..

Something to allow you to configure which directories can receive new uploads, and which directories we consider writable for other purposes (like editing meta data in yml, creating new thumbnails, ...)

Co-authored-by: Heinrich Langos <gumbo2000@noreply@mills.io>
Reviewed-on: https://git.mills.io/prologic/tube/pulls/39
Co-authored-by: Heinrich 'Henrik' Langos <gumbo2000@noreply@mills.io>
Co-committed-by: Heinrich 'Henrik' Langos <gumbo2000@noreply@mills.io>
2022-11-25 02:48:06 +00:00

246 lines
7.6 KiB
JavaScript

// common variables
let configMaxUploadSize = window['MAX_UPLOAD_SIZE'];
let iBytesUploaded = 0
let iBytesTotal = 0
let iPreviousBytesLoaded = 0
let iMaxFilesize = configMaxUploadSize ? (+configMaxUploadSize) : (100 * 1024 * 1024); // 100MB
let timer = 0
let uploadInProgress = 'n/a'
let isProcessing = false
let file = null
/* CACHED ELEMENTS */
const uploadForm = document.getElementById('upload-form')
const videoInput = document.getElementById('video-input')
const targetLibraryPath = document.getElementById('target-library-path')
const videoTitle = document.getElementById('video-title')
const videoDescription = document.getElementById('video-description')
const uploadMessageLabel = document.getElementById('upload-message')
const uploadFileContainer = document.getElementById('upload-file')
const uploadFilenameLabel = document.getElementById('upload-filename')
const uploadButtonWrapper = document.getElementById('upload-button-wrapper')
const uploadButton = document.getElementById('upload-button')
const uploadProgressContainer = document.getElementById('upload-progress-container')
const uploadProgressBar = document.getElementById('upload-progress')
const uploadProgressLabel = document.getElementById('upload-progress-label')
const uploadStopped = document.getElementById('upload-stopped')
const uploadStarted = document.getElementById('upload-started')
/* HELPERS */
const setProgress = (_progress) => {
uploadProgressContainer.style.display = _progress > 0 ? 'flex' : 'none'
uploadProgressBar.style.width = `${_progress}%`
uploadProgressLabel.innerText = _progress >= 15 ? `${_progress}%` : ''
}
const setMessage = (_message, isError) => {
uploadMessageLabel.style.display = _message ? 'block' : 'none'
uploadMessageLabel.innerHTML = _message
if (isError) {
uploadMessageLabel.classList.add('error')
} else {
uploadMessageLabel.classList.remove('error')
}
}
const setUploadState = (_uploadInProgress) => {
uploadInProgress = _uploadInProgress
uploadStarted.style.display = _uploadInProgress ? 'inline-block' : 'none'
uploadStopped.style.display = _uploadInProgress ? 'none' : 'block'
if (_uploadInProgress) {
uploadButton.classList.add('transparent')
timer = setInterval(doInnerUpdates, 300)
} else {
uploadButton.classList.remove('transparent')
clearInterval(timer)
}
}
const secondsToTime = (secs) => {
let hr = Math.floor(secs / 3600)
let min = Math.floor((secs - (hr * 3600)) / 60)
let sec = Math.floor(secs - (hr * 3600) - (min * 60))
if (hr < 10) hr = `0${hr}`
if (min < 10) min = `0${min}`
if (sec < 10) sec = `0${sec}`
if (hr) hr = '00'
return `${hr}:${min}:${sec}`
}
const bytesToSize = (bytes) => {
const sizes = ['Bytes', 'KB', 'MB']
if (bytes == 0) return 'n/a'
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]
}
const determineDragAndDropCapable = () => {
const div = document.createElement('div')
return (('draggable' in div)
|| ('ondragstart' in div && 'ondrop' in div))
&& 'FormData' in window
&& 'FileReader' in window
}
/* MAIN */
document.addEventListener('DOMContentLoaded', () => {
['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave']
.forEach((evt) => {
uploadForm.addEventListener(evt, (e) => {
e.preventDefault()
e.stopPropagation()
})
})
uploadForm.addEventListener('drop', (e) => {
console.log(111)
e.preventDefault()
e.stopPropagation()
const _file = e.dataTransfer.files[0]
if (!_file) return false
fileSelected(_file)
return false
})
}, false)
const labelClicked = (e) => {
if (uploadInProgress === true || file != null) {
e.preventDefault()
return false
}
}
const fileSelected = (_file) => {
const fileObj = _file || videoInput.files[0]
if (_file) videoInput.value = ''
if (fileObj) file = fileObj
if (!file) return
if (file.size > iMaxFilesize) {
setMessage('Your file is too big. It can\'t be accepted. Please select a smaller file.', true)
return
}
setMessage('')
setProgress(0)
const filename = file.name.length <= 20 ? file.name : `${file.name.substring(0, 14)}...${file.name.substring(file.name.length - 3)}`
uploadFilenameLabel.innerText = filename
uploadFileContainer.style.display = 'flex'
uploadButtonWrapper.style.display = 'block'
setUploadState(false)
}
const removeFile = (e, keepMessage) => {
if (e) e.preventDefault()
if (uploadInProgress === true) return
uploadFileContainer.style.display = 'none'
uploadButtonWrapper.style.display = 'none'
videoInput.value = ''
file = null
if (!keepMessage) setMessage('No file selected')
return false
}
const startUploading = () => {
if (uploadInProgress === true) return
if (!file) return
isProcessing = false
iPreviousBytesLoaded = 0
setMessage('')
setProgress(0)
setUploadState(true)
const formData = new FormData()
formData.append('video_file', file)
formData.append('target_library_path', targetLibraryPath.value)
formData.append('video_title', videoTitle.value)
formData.append('video_description', videoDescription.value)
const xhr = new XMLHttpRequest()
xhr.upload.addEventListener('progress', uploadProgress, false)
xhr.addEventListener('load', uploadFinish, false)
xhr.addEventListener('error', uploadError, false)
xhr.addEventListener('abort', uploadAbort, false)
xhr.open('POST', '/upload')
xhr.send(formData)
timer = setInterval(doInnerUpdates, 300)
}
const doInnerUpdates = () => { // we will use this function to display upload speed
if (isProcessing) {
clearInterval(timer)
return
}
let iDiff = iBytesUploaded - iPreviousBytesLoaded
// if nothing new loaded - exit
if (iDiff == 0)
return
iPreviousBytesLoaded = iBytesUploaded
iDiff = iDiff * 2
const iBytesRem = iBytesTotal - iPreviousBytesLoaded
const secondsRemaining = iBytesRem / iDiff
// update speed info
let iSpeed = iDiff.toString() + 'B/s'
if (iDiff > 1024 * 1024) {
iSpeed = (Math.round(iDiff * 100/(1024*1024))/100).toString() + 'MB/s'
} else if (iDiff > 1024) {
iSpeed = (Math.round(iDiff * 100/1024)/100).toString() + 'KB/s'
}
const speedMessage = `${iSpeed} | ${secondsToTime(secondsRemaining)}`
setMessage(speedMessage)
}
function uploadProgress(e) { // upload process in progress
if (e.lengthComputable) {
iBytesUploaded = e.loaded
iBytesTotal = e.total
const iPercentComplete = Math.round(iBytesUploaded / iBytesTotal * 100)
setProgress(iPercentComplete)
if (iPercentComplete === 100) {
isProcessing = true
setMessage('Processing video... please wait')
}
} else {
setMessage('Unable to compute progress.')
}
}
const uploadFinish = (e) => { // upload successfully finished
const message = e.target.responseText
const isSuccess = e.target.status < 400
setProgress(isSuccess ? 100 : 0)
setMessage(message, !isSuccess)
setUploadState(false)
if (isSuccess) removeFile(null, true)
}
const uploadError = () => { // upload error
setMessage('An error occurred while uploading the file.', true)
setProgress(0)
setUploadState(false)
}
const uploadAbort = () => { // upload abort
setMessage('The upload has been canceled by the user or the browser dropped the connection.', true)
setProgress(0)
setUploadState(false)
}