diff --git a/static/close-icon.png b/static/close-icon.png new file mode 100644 index 0000000..72dd33d Binary files /dev/null and b/static/close-icon.png differ diff --git a/static/upload-icon.png b/static/upload-icon.png new file mode 100644 index 0000000..3f28e28 Binary files /dev/null and b/static/upload-icon.png differ diff --git a/static/upload.css b/static/upload.css new file mode 100644 index 0000000..c2ded20 --- /dev/null +++ b/static/upload.css @@ -0,0 +1,158 @@ +.upload-container { + display: inline-block; + max-width: 100%; + margin-top: 50px; +} + +.upload-wrapper { + padding: 20px; + background-color: #282a2e; + border: 1px solid #1e1e1e; + border-radius: 20px; + font-family: 'Trebuchet MS', Arial, sans-serif; +} + +.upload-form { + border: 5px dashed #676867; + border-radius: 10px; + cursor: pointer; +} + +.upload-box { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + justify-content: center; + align-items: center; + min-width: 300px; + min-height: 300px; +} + +.upload-box span { + color: #c5c8c6; + font-weight: bold; +} + +.upload-details { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 0 0 20px 0; +} + +.upload-details > * { + margin-top: 20px; +} + +.upload-details span { + color: #c5c8c6; + font-weight: bold; +} + +.upload-file { + display: none; + width: 100%; + padding: 0 20px; + justify-content: space-between; + align-items: center; +} + +.upload-message { + white-space: normal; + padding: 0 20px; + word-wrap: break-word; + max-width: 300px; +} + +.upload-message.error { + color: #e82e57; +} + +.upload-button-wrapper { + display: none; + width: 100%; + padding: 0 20px; +} + +.upload-button { + border: 0; + box-shadow: none; + border-radius: 0px; + + width: 100%; + padding: 15px; + background: #191919 !important; + font-family: 'Trebuchet MS', Arial, sans-serif; + color: #c5c8c6; + border-radius: 10px; + cursor: pointer; +} + +.upload-button.transparent { + background: transparent !important; + padding: 10px; +} + +.upload-progress-container { + display: none; + width: 100%; + padding: 0 20px; +} + +.upload-progress { + height: 30px; + background: linear-gradient(45deg, #c7c7c7, #ae81ff); + border-radius: 5px; + display: flex; + justify-content: center; + align-items: center; +} + +.upload-progress-label { + color: #282a2e !important; +} + +/* SPINNER */ + +.loader, +.loader:after { + border-radius: 50%; + width: 3em; + height: 3em; +} +.loader { + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 0.5em solid rgba(255, 255, 255, 0.2); + border-right: 0.5em solid rgba(255, 255, 255, 0.2); + border-bottom: 0.5em solid rgba(255, 255, 255, 0.2); + border-left: 0.5em solid #ffffff; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load8 1.1s infinite linear; + animation: load8 1.1s infinite linear; +} +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/static/upload.js b/static/upload.js index 7353c47..6bf1aa0 100644 --- a/static/upload.js +++ b/static/upload.js @@ -1,145 +1,237 @@ // common variables -var iBytesUploaded = 0; -var iBytesTotal = 0; -var iPreviousBytesLoaded = 0; -var iMaxFilesize = 104857600; // 100MB -var oTimer = 0; -var sResultFileSize = ''; -function secondsToTime(secs) { // we will use this function to convert seconds in normal time format - var hr = Math.floor(secs / 3600); - var min = Math.floor((secs - (hr * 3600))/60); - var 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; -}; -function bytesToSize(bytes) { - var sizes = ['Bytes', 'KB', 'MB']; - if (bytes == 0) return 'n/a'; - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); - return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; -}; -function fileSelected() { - // hide different warnings - document.getElementById('upload_response').style.display = 'none'; - document.getElementById('error').style.display = 'none'; - document.getElementById('error2').style.display = 'none'; - document.getElementById('abort').style.display = 'none'; - document.getElementById('warnsize').style.display = 'none'; - // get selected file element - var oFile = document.getElementById('image_file').files[0]; - // filter for image files - var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i; - if (! rFilter.test(oFile.type)) { - document.getElementById('error').style.display = 'block'; - return; - } - // little test for filesize - if (oFile.size > iMaxFilesize) { - document.getElementById('warnsize').style.display = 'block'; - return; - } - // get preview element - var oImage = document.getElementById('preview'); - // prepare HTML5 FileReader - var oReader = new FileReader(); - oReader.onload = function(e){ - // e.target.result contains the DataURL which we will use as a source of the image - oImage.src = e.target.result; - oImage.onload = function () { // binding onload event - // we are going to display some custom image information here - sResultFileSize = bytesToSize(oFile.size); - document.getElementById('fileinfo').style.display = 'block'; - document.getElementById('filename').innerHTML = 'Name: ' + oFile.name; - document.getElementById('filesize').innerHTML = 'Size: ' + sResultFileSize; - document.getElementById('filetype').innerHTML = 'Type: ' + oFile.type; - document.getElementById('filedim').innerHTML = 'Dimension: ' + oImage.naturalWidth + ' x ' + oImage.naturalHeight; - }; - }; - // read selected file as DataURL - oReader.readAsDataURL(oFile); +let iBytesUploaded = 0 +let iBytesTotal = 0 +let iPreviousBytesLoaded = 0 +let iMaxFilesize = 104857600 // 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 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}%` : '' } -function startUploading() { - // cleanup all temp states - iPreviousBytesLoaded = 0; - document.getElementById('upload_response').style.display = 'none'; - document.getElementById('error').style.display = 'none'; - document.getElementById('error2').style.display = 'none'; - document.getElementById('abort').style.display = 'none'; - document.getElementById('warnsize').style.display = 'none'; - document.getElementById('progress_percent').innerHTML = ''; - var oProgress = document.getElementById('progress'); - oProgress.style.display = 'block'; - oProgress.style.width = '0px'; - // get form data for POSTing - //var vFD = document.getElementById('upload_form').getFormData(); // for FF3 - var vFD = new FormData(document.getElementById('upload_form')); - // create XMLHttpRequest object, adding few event listeners, and POSTing our data - var oXHR = new XMLHttpRequest(); - oXHR.upload.addEventListener('progress', uploadProgress, false); - oXHR.addEventListener('load', uploadFinish, false); - oXHR.addEventListener('error', uploadError, false); - oXHR.addEventListener('abort', uploadAbort, false); - oXHR.open('POST', '/upload'); - oXHR.send(vFD); - // set inner timer - oTimer = setInterval(doInnerUpdates, 300); + +const setMessage = (_message, isError) => { + uploadMessageLabel.style.display = _message ? 'block' : 'none' + uploadMessageLabel.innerHTML = _message + if (isError) { + uploadMessageLabel.classList.add('error') + } else { + uploadMessageLabel.classList.remove('error') + } } -function doInnerUpdates() { // we will use this function to display upload speed - var iCB = iBytesUploaded; - var iDiff = iCB - iPreviousBytesLoaded; + +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) { + e.preventDefault() + return false + } +} + +const fileSelected = (_file) => { + file = _file || videoInput.files[0] + if (_file) videoInput.value = '' + if (!file) return + + if (file.size > iMaxFilesize) { + setMessage('Your file is very big. We can\'t accept it. Please select more small 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) + 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 = iCB; - iDiff = iDiff * 2; - var iBytesRem = iBytesTotal - iPreviousBytesLoaded; - var secondsRemaining = iBytesRem / iDiff; + return + iPreviousBytesLoaded = iBytesUploaded + iDiff = iDiff * 2 + const iBytesRem = iBytesTotal - iPreviousBytesLoaded + const secondsRemaining = iBytesRem / iDiff // update speed info - var iSpeed = iDiff.toString() + 'B/s'; + let iSpeed = iDiff.toString() + 'B/s' if (iDiff > 1024 * 1024) { - iSpeed = (Math.round(iDiff * 100/(1024*1024))/100).toString() + 'MB/s'; + 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'; + iSpeed = (Math.round(iDiff * 100/1024)/100).toString() + 'KB/s' } - document.getElementById('speed').innerHTML = iSpeed; - document.getElementById('remaining').innerHTML = '| ' + secondsToTime(secondsRemaining); + + const speedMessage = `${iSpeed} | ${secondsToTime(secondsRemaining)}` + setMessage(speedMessage) } + function uploadProgress(e) { // upload process in progress if (e.lengthComputable) { - iBytesUploaded = e.loaded; - iBytesTotal = e.total; - var iPercentComplete = Math.round(e.loaded * 100 / e.total); - var iBytesTransfered = bytesToSize(iBytesUploaded); - document.getElementById('progress_percent').innerHTML = iPercentComplete.toString() + '%'; - document.getElementById('progress').style.width = (iPercentComplete * 4).toString() + 'px'; - document.getElementById('b_transfered').innerHTML = iBytesTransfered; - if (iPercentComplete == 100) { - var oUploadResponse = document.getElementById('upload_response'); - oUploadResponse.innerHTML = '