filesafe/public/js/home.js
Bobby Wibowo 3ca692d8c7
Updates
* Refactored all instances of "failedIds" and "albumIds" to "failedids" and "albumids" respectively. Abandoning camel case for these ones.

* Refactored the way it looks into which albums the files are supposed to be added into.
For /api/upload/finishchunks, you can add "albumid" to each object in files[] to specify which album you want the finsihed chunks to be added into. Each object may have different album IDs.
For /api/upload, which is regular uploads, unfortunately you can only choose one album at a time (by adding "albumid" to the request headers, like usual). It uses the same function as the one used for finishchunks to add the files into album, so it shouldn't be hard to extend this ability to regular uploads, someday in the future.

* Fixed a bug in /api/upload/finishchunks. Previously you couldn't ever get it to work.

* Updated error message when successful uploads could not be added to album.

* "albumid" will no longer be added to request headers if they are chunked uploads. They'd have been ignored anyways.
2018-04-05 19:54:24 +07:00

309 lines
9.9 KiB
JavaScript

/* eslint-disable no-unused-expressions */
/* global swal, axios, Dropzone, ClipboardJS */
const upload = {
private: undefined,
enableUserAccounts: undefined,
token: localStorage.token,
maxFileSize: undefined,
chunkedUploads: undefined,
// Add the album let to the upload so we can store the album id in there
album: undefined,
dropzone: undefined,
clipboardJS: undefined
}
const imageExtensions = ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png']
upload.checkIfPublic = () => {
axios.get('api/check')
.then(response => {
upload.private = response.data.private
upload.enableUserAccounts = response.data.enableUserAccounts
upload.maxFileSize = response.data.maxFileSize
upload.chunkedUploads = response.data.chunkedUploads
upload.preparePage()
})
.catch(error => {
console.log(error)
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
})
}
upload.preparePage = () => {
if (upload.private) {
if (upload.token) {
return upload.verifyToken(upload.token, true)
} else {
const button = document.getElementById('loginToUpload')
button.href = 'auth'
button.className = button.className.replace(' is-loading', '')
if (upload.enableUserAccounts) {
button.innerText = 'Anonymous upload is disabled. Log in to upload.'
} else {
button.innerText = 'Running in private mode. Log in to upload.'
}
}
} else {
return upload.prepareUpload()
}
}
upload.verifyToken = (token, reloadOnError) => {
if (reloadOnError === undefined) { reloadOnError = false }
axios.post('api/tokens/verify', { token })
.then(response => {
if (response.data.success === false) {
swal({
title: 'An error occurred!',
text: response.data.description,
icon: 'error'
}).then(() => {
if (reloadOnError) {
localStorage.removeItem('token')
location.reload()
}
})
return
}
localStorage.token = token
upload.token = token
return upload.prepareUpload()
})
.catch(error => {
console.log(error)
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
})
}
upload.prepareUpload = () => {
// I think this fits best here because we need to check for a valid token before we can get the albums
if (upload.token) {
const select = document.getElementById('albumSelect')
select.addEventListener('change', () => {
upload.album = parseInt(select.value)
})
axios.get('api/albums', { headers: { token: upload.token } })
.then(res => {
const albums = res.data.albums
// If the user doesn't have any albums we don't really need to display
// an album selection
if (albums.length === 0) { return }
// Loop through the albums and create an option for each album
for (const album of albums) {
const opt = document.createElement('option')
opt.value = album.id
opt.innerHTML = album.name
select.appendChild(opt)
}
// Display the album selection
document.getElementById('albumDiv').style.display = 'block'
})
.catch(e => {
console.log(e)
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
})
}
const div = document.createElement('div')
div.id = 'dropzone'
div.className = 'button'
div.innerHTML = `
<span class="icon">
<i class="icon-upload-cloud"></i>
</span>
<span>Click here or drag and drop files</span>
`
div.style.display = 'flex'
document.getElementById('maxFileSize').innerHTML = `Maximum upload size per file is ${upload.maxFileSize}`
document.getElementById('loginToUpload').style.display = 'none'
if (upload.token === undefined && upload.enableUserAccounts) {
document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
}
document.getElementById('uploadContainer').appendChild(div)
upload.prepareDropzone()
}
upload.prepareDropzone = () => {
const previewNode = document.querySelector('#template')
previewNode.id = ''
const previewTemplate = previewNode.parentNode.innerHTML
previewNode.parentNode.removeChild(previewNode)
upload.dropzone = new Dropzone('div#dropzone', {
url: 'api/upload',
paramName: 'files[]',
maxFilesize: parseInt(upload.maxFileSize),
parallelUploads: 2,
uploadMultiple: false,
previewsContainer: 'div#uploads',
previewTemplate,
createImageThumbnails: false,
maxFiles: 1000,
autoProcessQueue: true,
headers: { token: upload.token },
chunking: upload.chunkedUploads.enabled,
chunkSize: parseInt(upload.chunkedUploads.maxSize) * 1000000, // 1000000 B = 1 MB,
parallelChunkUploads: false, // when set to true, sometimes it often hangs with hundreds of parallel uploads
chunksUploaded: async (file, done) => {
file.previewElement.querySelector('.progress').setAttribute('value', 100)
file.previewElement.querySelector('.progress').innerHTML = '100%'
// The API supports an array of multiple files
const response = await axios.post(
'api/upload/finishchunks',
{
files: [
{
uuid: file.upload.uuid,
original: file.name,
size: file.size,
type: file.type,
count: file.upload.totalChunkCount,
albumid: upload.album
}
]
},
{
headers: { token: upload.token }
})
.then(response => response.data)
.catch(error => {
return {
success: false,
description: error.toString()
}
})
file.previewTemplate.querySelector('.progress').style.display = 'none'
if (response.success === false) {
file.previewTemplate.querySelector('.error').innerHTML = response.description
}
if (response.files && response.files[0] && response.files[0].url) {
upload.appendLink(file, response.files[0].url)
upload.showThumbnail(file, response.files[0].url)
}
return done()
}
})
upload.dropzone.on('addedfile', file => {
document.getElementById('uploads').style.display = 'block'
})
// Add the selected albumid, if an album is selected, as a header
upload.dropzone.on('sending', (file, xhr, formData) => {
if (file.upload.chunked) { return }
if (upload.album) { xhr.setRequestHeader('albumid', upload.album) }
})
// Update the total progress bar
upload.dropzone.on('uploadprogress', (file, progress, bytesSent) => {
if (file.upload.chunked && progress === 100) { return }
file.previewElement.querySelector('.progress').setAttribute('value', progress)
file.previewElement.querySelector('.progress').innerHTML = `${progress}%`
})
upload.dropzone.on('success', (file, response) => {
if (!response) { return }
file.previewTemplate.querySelector('.progress').style.display = 'none'
if (response.success === false) {
file.previewTemplate.querySelector('.error').innerHTML = response.description
}
if (response.files && response.files[0] && response.files[0].url) {
upload.appendLink(file, response.files[0].url)
upload.showThumbnail(file, response.files[0].url)
}
})
upload.dropzone.on('error', (file, error) => {
file.previewTemplate.querySelector('.progress').style.display = 'none'
file.previewTemplate.querySelector('.error').innerHTML = error
})
upload.prepareShareX()
}
upload.appendLink = (file, url) => {
const a = file.previewTemplate.querySelector('.link > a')
const clipboard = file.previewTemplate.querySelector('.clipboard-mobile > .clipboard-js')
a.href = a.innerHTML = clipboard.dataset['clipboardText'] = url
a.parentElement.style = clipboard.parentElement.style = ''
}
upload.showThumbnail = (file, url) => {
const exec = /.[\w]+(\?|$)/.exec(url)
if (exec && exec[0] && imageExtensions.includes(exec[0].toLowerCase())) {
upload.dropzone.emit('thumbnail', file, url)
}
}
upload.prepareShareX = () => {
if (upload.token) {
const sharexElement = document.getElementById('ShareX')
const sharexFile = `{\r\n\
"Name": "${location.hostname}",\r\n\
"DestinationType": "ImageUploader, FileUploader",\r\n\
"RequestType": "POST",\r\n\
"RequestURL": "${location.origin}/api/upload",\r\n\
"FileFormName": "files[]",\r\n\
"Headers": {\r\n\
"token": "${upload.token}"\r\n\
},\r\n\
"ResponseType": "Text",\r\n\
"URL": "$json:files[0].url$",\r\n\
"ThumbnailURL": "$json:files[0].url$"\r\n\
}`
const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' })
sharexElement.setAttribute('href', URL.createObjectURL(sharexBlob))
sharexElement.setAttribute('download', `${location.hostname}.sxcu`)
}
}
// Handle image paste event
window.addEventListener('paste', event => {
const items = (event.clipboardData || event.originalEvent.clipboardData).items
for (const index in items) {
const item = items[index]
if (item.kind === 'file') {
const blob = item.getAsFile()
console.log(blob.type)
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`)
file.type = blob.type
console.log(file)
upload.dropzone.addFile(file)
}
}
})
window.onload = () => {
upload.checkIfPublic()
upload.clipboardJS = new ClipboardJS('.clipboard-js')
upload.clipboardJS.on('success', () => {
return swal('Copied!', 'The link has been copied to clipboard.', 'success')
})
upload.clipboardJS.on('error', event => {
console.error(event)
return swal('An error occurred!', 'There was an error when trying to copy the link to clipboard, please check the console for more information.', 'error')
})
}