scinote-web/app/assets/javascripts/sitewide/drag_n_drop.js
2020-10-29 16:42:05 +01:00

644 lines
21 KiB
JavaScript

/* global Promise ActiveStorage animateSpinner copyFromClipboard I18n
Results ResultAssets FilePreviewModal Comments truncateLongString
DragNDropSteps DragNDropResults initFormSubmitLinks dragNdropAssetsInit
GLOBAL_CONSTANTS */
(function(global) {
'use strict';
// Copy from clipboard
global.copyFromClipboard = (function() {
var UPLOADED_IMAGE = {};
var LOCATION = '';
function retrieveImageFromClipboardAsBlob(pasteEvent, callback) {
if (pasteEvent.clipboardData === false) {
if ((typeof callback) === 'function') {
callback(undefined);
}
}
let items = pasteEvent.clipboardData.items;
if (items === undefined) {
if ((typeof callback) === 'function') {
callback(undefined);
}
}
for (let i = 0; i < items.length; i += 1) {
if (items[i].type.indexOf('image') !== -1) {
let blob = items[i].getAsFile();
if ((typeof callback) === 'function') {
callback(blob);
}
}
}
}
// $(..).modal('hide') don't work properly so here we manually remove the
// displayed modal
function hideModalForGood() {
$('#clipboardPreviewModal').removeClass('in');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
$('body').css('padding-right', '');
$('#clipboardPreviewModal').hide();
}
function closeModal() {
hideModalForGood();
$('#clipboardPreviewModal').remove();
}
function addImageCallback() {
$('[data-action="addImageFormClipboard"]').on('click', function() {
let inputArray = [];
let newName = $('#clipboardImageName').val();
// check if the name is set
if (newName && newName.length > 0) {
let extension = UPLOADED_IMAGE.name.slice(
(Math.max(0, UPLOADED_IMAGE.name.lastIndexOf('.')) || Infinity) + 1
);
// hack to inject custom name in File object
let name = newName + '.' + extension;
let blob = UPLOADED_IMAGE.slice(0, UPLOADED_IMAGE.size, UPLOADED_IMAGE.type);
// make new blob with the correct name;
let newFile = new File([blob], name, { type: UPLOADED_IMAGE.type });
inputArray.push(newFile);
} else { // return the default name
inputArray.push(UPLOADED_IMAGE);
}
// close modal
closeModal();
// reuse file upload from drag'n drop :)
if (LOCATION === 'steps') {
DragNDropSteps.init(inputArray);
} else {
DragNDropResults.init(inputArray);
}
// clear all uploaded images
UPLOADED_IMAGE = {};
});
}
// removes modal from dom
function destroyModalCallback() {
let modal = $('#clipboardPreviewModal');
modal.on('hidden.bs.modal', function() {
modal.modal('hide').promise().done(function() {
modal.remove();
});
UPLOADED_IMAGE = {};
});
}
// Generate modal html and hook callbacks
function clipboardPasteModal() {
var html = `<div id="clipboardPreviewModal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content"><div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">${I18n.t('assets.from_clipboard.modal_title')}</h4>
</div>
<div class="modal-body">
<p><strong>${I18n.t('assets.from_clipboard.image_preview')}</strong></p>
<canvas style="border:1px solid grey;max-width:400px;max-height:300px" id="clipboardPreview" />
<p><strong>${I18n.t('assets.from_clipboard.file_name')}</strong></p>
<div class="input-group">
<input id="clipboardImageName" type="text" class="form-control"
placeholder="${I18n.t('assets.from_clipboard.file_name_placeholder')}" aria-describedby="image-name">
<span class="input-group-addon" id="image-name"></span></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">${I18n.t('general.cancel')}</button>
<button type="button" class="btn btn-success" data-action="addImageFormClipboard">${I18n.t('assets.from_clipboard.add_image')}</button>
</div>
</div>
</div>
</div><!-- /.modal -->`;
return $(html).appendTo($('body')).promise().done(function() {
// display modal
$('#clipboardPreviewModal').modal('show');
// add callback to remove modal from DOM
destroyModalCallback();
// add callback on image submit
addImageCallback();
});
}
function listener(pasteEvent) {
retrieveImageFromClipboardAsBlob(pasteEvent, function(imageBlob) {
if (imageBlob) {
clipboardPasteModal().promise().done(function() {
var canvas = document.getElementById('clipboardPreview');
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(img, 0, 0);
};
let URLObj = window.URL || window.webkitURL;
img.src = URLObj.createObjectURL(imageBlob);
let extension = imageBlob.name.slice(
(Math.max(0, imageBlob.name.lastIndexOf('.')) || Infinity) + 1
);
$('#image-name').html('.' + extension); // add extension near file name
// temporary store image blob
UPLOADED_IMAGE = imageBlob;
});
}
});
}
function init(location) {
LOCATION = location;
global.addEventListener('paste', listener, false);
}
function destroy() {
global.removeEventListener('paste', listener, false);
}
return Object.freeze({
init: init,
destroy: destroy
});
}());
// Module to handle file uploading in Steps
global.DragNDropSteps = (function() {
var droppedFiles = [];
var filesValid = true;
var totalSize = 0;
var fileMaxSizeMb;
var fileMaxSize;
// return the status of files if they are ready to submit
function filesStatus() {
return filesValid;
}
function clearFiles() {
droppedFiles = [];
}
function dragNdropAssetsOff() {
$('body').off('drag dragstart dragend dragover dragenter dragleave drop');
$('.is-dragover').hide();
// remove listeners for clipboard images
copyFromClipboard.destroy();
}
// append the files to the form before submit
function appendFilesToForm(ev) {
return new Promise((resolve, reject) => {
const form = $(ev.target).closest('form').get(0);
const url = $(form).find('#drag-n-drop-assets').data('directUploadUrl');
const regex = /step\[assets_attributes\]\[[0-9]*\]\[id\]/;
const lastIndex = droppedFiles.length - 1;
let prevEls = $('input').filter(function() {
return this.name.match(regex);
});
let fd = new FormData(form);
let index = 0;
fd.delete('step[file][]');
if (droppedFiles.length === 0) {
resolve(fd);
return;
}
const uploadFile = (file) => {
let upload = new ActiveStorage.DirectUpload(file, url);
upload.create(function(error, blob) {
if (error) {
reject(error);
} else {
fd.append('step[assets_attributes][' + (index + prevEls.length) + '][signed_blob_id]', blob.signed_id);
if (index === lastIndex) {
resolve(fd);
return;
}
index += 1;
uploadFile(droppedFiles[index]);
}
});
};
uploadFile(droppedFiles[index]);
filesValid = true;
totalSize = 0;
dragNdropAssetsOff();
});
}
function disableSubmitButton() {
$('.step-save').prop('disabled', true);
}
function enableSubmitButton() {
$('.step-save').prop('disabled', false);
}
function filerAndCheckFiles() {
for (let i = 0; i < droppedFiles.length; i += 1) {
if (droppedFiles[i].isValid === false) {
return false;
}
}
return (droppedFiles.length > 0);
}
function validateFilesSize(file) {
var fileSize = file.size;
totalSize += parseInt(fileSize, 10);
if (fileSize > fileMaxSize) {
file.isValid = false;
disableSubmitButton();
return "<p class='dnd-error'>" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '</p>';
}
return '';
}
function validateTotalSize() {
if (totalSize > fileMaxSize) {
filesValid = false;
disableSubmitButton();
$.each($('.attachment-placeholder.new'), function() {
if (!$(this).find('p').hasClass('dnd-error')) {
$(this).append("<p class='dnd-total-error'>" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '</p>');
}
});
} else {
$('.dnd-total-error').remove();
if (filerAndCheckFiles()) {
filesValid = true;
enableSubmitButton();
}
}
}
function uploadedAssetPreview(asset, i) {
var html = `<div class="attachment-container pull-left new">
<div class="attachment-preview no-shadow new %>">
<i class="fas fa-image"></i>
</div>
<div class="attachment-label">
${truncateLongString(asset.name, GLOBAL_CONSTANTS.FILENAME_TRUNCATION_LENGTH)}
</div>
<div class="attachment-metadata"></div>
<div class="remove-icon pull-right">
<a data-item-id="${i}" href="#">
<span class="fas fa-trash"></span>
</a>
</div>
${validateFilesSize(asset)}
</div>`;
return html;
}
function removeItemHandler(id, callback) {
$('[data-item-id="' + id + '"]').off('click').on('click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
let $el = $(this);
let index = $el.data('item-id');
totalSize -= parseInt(droppedFiles[index].size, 10);
droppedFiles.splice(index, 1);
callback();
});
}
// loops through a list of files and display each file in a separate panel
function listItems() {
totalSize = 0;
enableSubmitButton();
$('.attachment-container.new').remove();
dragNdropAssetsOff();
for (let i = 0; i < droppedFiles.length; i += 1) {
$('.attachments.edit')
.append(uploadedAssetPreview(droppedFiles[i], i))
.promise()
.done(function() {
removeItemHandler(i, listItems);
});
}
validateTotalSize();
dragNdropAssetsInit('steps');
}
function init(files) {
fileMaxSizeMb = GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB;
fileMaxSize = fileMaxSizeMb * 1024 * 1024;
for (let i = 0; i < files.length; i += 1) {
files[i].uuid = Math.random().toString(36);
droppedFiles.push(files[i]);
}
listItems();
}
return Object.freeze({
init: init,
appendFilesToForm: appendFilesToForm,
listItems: listItems,
filesStatus: filesStatus,
clearFiles: clearFiles
});
}());
// Module to handle file uploading in Results
global.DragNDropResults = (function() {
var droppedFiles = [];
var isValid = true;
var totalSize = 0;
var fileMaxSizeMb;
var fileMaxSize;
function disableSubmitButton() {
$('.save-result').prop('disabled', true);
}
function enableSubmitButton() {
$('.save-result').prop('disabled', false);
}
function filerAndCheckFiles() {
for (let i = 0; i < droppedFiles.length; i += 1) {
if (droppedFiles[i].isValid === false) {
return false;
}
}
return (droppedFiles.length > 0);
}
function dragNdropAssetsOff() {
$('body').off('drag dragstart dragend dragover dragenter dragleave drop');
$('.is-dragover').hide();
}
function destroyAll() {
dragNdropAssetsOff();
droppedFiles = [];
isValid = true;
totalSize = 0;
}
// return the status of files if they are ready to submit
function filesStatus() {
return isValid;
}
function validateTotalSize() {
if (totalSize > fileMaxSize) {
isValid = false;
disableSubmitButton();
$.each($('.panel-result-attachment-new'), function() {
if (!$(this).find('p').hasClass('dnd-total-error')) {
$(this)
.find('.panel-body')
.append("<p class='dnd-total-error'>" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '</p>');
}
});
} else {
$('.dnd-total-error').remove();
if (filerAndCheckFiles()) {
isValid = true;
enableSubmitButton();
}
}
}
function submitResultForm(url, formData) {
$.ajax({
url: url,
method: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(data) {
animateSpinner(null, false);
$('#new-result-assets-select').parent().remove();
$(data.html).prependTo('#results').promise().done(function() {
$.each($('[data-container="new-reports"]').find('.result'), function() {
initFormSubmitLinks($(this));
ResultAssets.applyEditResultAssetCallback();
Results.toggleResultEditButtons(true);
Comments.init();
ResultAssets.initNewResultAsset();
Results.expandResult($(this));
});
});
$('#results-toolbar').show();
},
error: function() {
animateSpinner();
location.reload();
}
});
}
// appent the files to the form before submit
function appendFilesToForm(ev, fd) {
const form = $(ev.target.form);
const url = form.find('#drag-n-drop-assets').data('directUploadUrl');
const lastIndex = droppedFiles.length - 1;
let index = 0;
const uploadFile = (file) => {
const upload = new ActiveStorage.DirectUpload(file, url);
upload.create((error, blob) => {
if (error) {
$.each($('.panel-result-attachment-new'), function() {
if (!$(this).find('p').hasClass('dnd-total-error')) {
$(this)
.find('.panel-body')
.append("<p class='dnd-total-error'>" + I18n.t('general.file.upload_failure') + '</p>');
}
});
animateSpinner(null, false);
} else {
fd.append('results_names[' + index + ']', $('input[name="results[name][' + index + ']"]').val());
fd.append('results_files[' + index + '][signed_blob_id]', blob.signed_id);
if (index === lastIndex) {
submitResultForm($(ev.target).attr('data-href'), fd);
destroyAll();
return;
}
index += 1;
uploadFile(droppedFiles[index]);
}
});
};
uploadFile(droppedFiles[index]);
}
/* eslint no-param-reassign: ["error", { "props": false }] */
function validateFilesSize(file) {
var fileSize = file.size;
totalSize += parseInt(fileSize, 10);
if (fileSize > fileMaxSize) {
file.isValid = false;
disableSubmitButton();
return "<p class='dnd-error'>" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '</p>';
}
return '';
}
function validateTextSize(input) {
if (input.value.length > GLOBAL_CONSTANTS.NAME_MAX_LENGTH) {
$(input).parent().find('.dnd-error').remove();
$(input).after("<p class='dnd-error'>" + I18n.t('general.text.length_too_long', { max_length: GLOBAL_CONSTANTS.NAME_MAX_LENGTH }) + '</p>');
isValid = false;
} else {
$(input).parent().find('.dnd-error').remove();
isValid = true;
}
}
function uploadedAssetPreview(asset, i) {
var html = `<div class="panel panel-default panel-result-attachment-new">
<div class="panel-heading">
<span class="fas fa-paperclip"></span>
${I18n.t('assets.drag_n_drop.file_label')}
<div class="pull-right">
<a data-item-id="${asset.uuid}" href="#">
<span class="fas fa-times"></span>
</a>
</div>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Name</label>
<input type="text" class="form-control" onChange="DragNDropResults.validateTextSize(this)"
rel="results[name]" name="results[name][${i}]">
</div>
<div class="form-group">
<label class="control-label">${I18n.t('assets.drag_n_drop.file_label')}:</label>
${truncateLongString(asset.name, GLOBAL_CONSTANTS.FILENAME_TRUNCATION_LENGTH)}
${validateFilesSize(asset)}
</div>
</div>
</div>`;
return html;
}
function processResult(ev) {
ev.preventDefault();
ev.stopPropagation();
if (isValid && filerAndCheckFiles()) {
animateSpinner();
let formData = new FormData();
appendFilesToForm(ev, formData);
}
}
function removeItemHandler(uuid) {
$('[data-item-id="' + uuid + '"]').off('click').on('click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
let $el = $(this);
let index = droppedFiles.findIndex((file) => {
return file.uuid === $el.data('item-id');
});
totalSize -= parseInt(droppedFiles[index].size, 10);
droppedFiles.splice(index, 1);
validateTotalSize();
$el.closest('.panel-result-attachment-new').remove();
});
}
// loops through a list of files and display each file in a separate panel
function listItems() {
totalSize = 0;
$('.panel-result-attachment-new').remove();
if (droppedFiles.length < 1) {
disableSubmitButton();
} else {
dragNdropAssetsOff();
for (let i = 0; i < droppedFiles.length; i += 1) {
$('#new-result-assets-select')
.after(uploadedAssetPreview(droppedFiles[i], i))
.promise()
.done(function() {
removeItemHandler(droppedFiles[i].uuid);
});
}
validateTotalSize();
dragNdropAssetsInit('results');
}
}
function init(files) {
fileMaxSizeMb = GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB;
fileMaxSize = fileMaxSizeMb * 1024 * 1024;
for (let i = 0; i < files.length; i += 1) {
files[i].uuid = Math.random().toString(36);
droppedFiles.unshift(files[i]);
}
listItems();
}
return Object.freeze({
init: init,
listItems: listItems,
destroyAll: destroyAll,
filesStatus: filesStatus,
validateTextSize: validateTextSize,
processResult: processResult
});
}());
global.dragNdropAssetsInit = function(location) {
var inWindow = true;
$('body')
.on('drag dragstart dragend dragover dragenter dragleave drop', (e) => {
e.preventDefault();
e.stopPropagation();
})
.on('dragover', function() {
inWindow = true;
$('.is-dragover').show();
})
.on('dragleave', function() {
inWindow = false;
setTimeout(function() {
if (!inWindow) {
$('.is-dragover').hide();
}
}, 5000);
})
.on('drop', function(e) {
$('.is-dragover').hide();
let files = e.originalEvent.dataTransfer.files;
if (location === 'steps') {
DragNDropSteps.init(files);
} else {
DragNDropResults.init(files);
}
});
copyFromClipboard.init(location);
};
}(window));