From 5e65b07bdc1ec85dd6b98ec186c78769e4b9e3c0 Mon Sep 17 00:00:00 2001 From: Oleksii Kriuchykhin Date: Thu, 25 Jul 2019 18:00:24 +0200 Subject: [PATCH] Implement DirectUpload for Steps, Results and Inventories [SCI-3679] --- Dockerfile | 24 +- Dockerfile.production | 14 +- app/assets/javascripts/application.js.erb | 1 + .../my_modules/{results.js.erb => results.js} | 246 ++++--- app/assets/javascripts/protocols/steps.js.erb | 101 +-- .../forms/repository_item_edit.js | 103 +-- .../repositories/repository_datatable.js.erb | 142 ++-- .../javascripts/results/result_assets.js | 6 +- .../javascripts/sitewide/drag_n_drop.js | 630 ++++++++++++++++++ .../javascripts/sitewide/drag_n_drop.js.erb | 568 ---------------- app/assets/stylesheets/themes/scinote.scss | 4 + app/controllers/result_assets_controller.rb | 131 ++-- app/controllers/steps_controller.rb | 158 +++-- app/views/layouts/application.html.erb | 4 +- .../repositories/_repository_table.html.erb | 1 + app/views/result_assets/_edit.html.erb | 4 +- app/views/result_assets/_new.html.erb | 60 +- app/views/result_tables/_edit.html.erb | 6 +- app/views/result_tables/_new.html.erb | 6 +- app/views/result_texts/_edit.html.erb | 6 +- app/views/result_texts/_new.html.erb | 6 +- app/views/steps/_empty_step.html.erb | 11 +- 22 files changed, 1183 insertions(+), 1049 deletions(-) rename app/assets/javascripts/my_modules/{results.js.erb => results.js} (54%) create mode 100644 app/assets/javascripts/sitewide/drag_n_drop.js delete mode 100644 app/assets/javascripts/sitewide/drag_n_drop.js.erb diff --git a/Dockerfile b/Dockerfile index 4e4b49920..40ab1e5d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,21 @@ -FROM ruby:2.6.3 +FROM ruby:2.6.3-buster MAINTAINER BioSistemika -# Get version of Debian (lsb_release substitute) and save it to /tmp/lsb_release for further commands -RUN cat /etc/os-release | grep -Po "VERSION=.*\(\K\w+" | tee /tmp/lsb_release - -# Add Debian stretch backports repository -RUN echo "deb http://http.debian.net/debian $(cat /tmp/lsb_release)-backports main" \ - | tee /etc/apt/sources.list.d/$(cat /tmp/lsb_release)-backports.list - # additional dependecies # libSSL-1.0 is required by wkhtmltopdf binary # libreoffice for file preview generation -RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ - apt-get update -qq && \ +RUN apt-get update -qq && \ apt-get install -y \ - libjemalloc1 \ - libssl1.0-dev \ + libjemalloc2 \ + libssl-dev \ nodejs \ + yarnpkg \ postgresql-client \ default-jre-headless \ - unison \ - sudo graphviz --no-install-recommends \ poppler-utils \ + sudo graphviz --no-install-recommends \ + libreoffice \ libfile-mimeinfo-perl && \ - apt-get install -y --no-install-recommends -t $(cat /tmp/lsb_release)-backports libreoffice && \ - npm install -g yarn && \ rm -rf /var/lib/apt/lists/* # heroku tools @@ -34,6 +25,7 @@ ENV BUNDLE_PATH /usr/local/bundle/ # create app directory ENV APP_HOME /usr/src/app +ENV PATH $APP_HOME/bin:$PATH RUN mkdir $APP_HOME WORKDIR $APP_HOME diff --git a/Dockerfile.production b/Dockerfile.production index 678b10b67..bc4f02c6e 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -1,7 +1,7 @@ -FROM ruby:2.6.3 +FROM ruby:2.6.3-buster MAINTAINER BioSistemika -RUN echo deb "http://http.debian.net/debian stretch-backports main" >> /etc/apt/sources.list +RUN echo deb "http://http.debian.net/debian buster-backports main" >> /etc/apt/sources.list # additional dependecies # libSSL-1.0 is required by wkhtmltopdf binary @@ -9,19 +9,19 @@ RUN echo deb "http://http.debian.net/debian stretch-backports main" >> /etc/apt/ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ apt-get update -qq && \ apt-get install -y \ - libjemalloc1 \ - libssl1.0-dev \ + libjemalloc2 \ + libssl-dev \ nodejs \ + yarnpkg \ groff-base \ awscli \ postgresql-client \ netcat \ default-jre-headless \ - sudo graphviz --no-install-recommends \ poppler-utils \ + sudo graphviz --no-install-recommends \ libfile-mimeinfo-perl && \ - apt-get install -y --no-install-recommends -t stretch-backports libreoffice && \ - npm install -g yarn && \ + apt-get install -y --no-install-recommends -t buster-backports libreoffice && \ rm -rf /var/lib/apt/lists/* ENV RAILS_ENV production diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 0f46ada09..324a2408c 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -40,6 +40,7 @@ //= require perfect-scrollbar.min //= require select2_customization //= require shared/inline_editing +//= require activestorage //= require turbolinks diff --git a/app/assets/javascripts/my_modules/results.js.erb b/app/assets/javascripts/my_modules/results.js similarity index 54% rename from app/assets/javascripts/my_modules/results.js.erb rename to app/assets/javascripts/my_modules/results.js index 890e4565f..be5041a34 100644 --- a/app/assets/javascripts/my_modules/results.js.erb +++ b/app/assets/javascripts/my_modules/results.js @@ -1,3 +1,8 @@ +/* + global Results ActiveStorage animateSpinner Comments ResultAssets FilePreviewModal + TinyMCE getParam applyCreateWopiFileCallback initFormSubmitLinks textValidator +*/ + (function(global) { 'use strict'; @@ -5,40 +10,11 @@ var ResultTypeEnum = Object.freeze({ FILE: 0, TABLE: 1, - TEXT: 2, - COMMENT: 3 + TEXT: 2 }); - function init() { - initHandsOnTables($(document)); - _expandAllResults(); - applyCollapseLinkCallBack(); - applyCreateWopiFileCallback(); - - - - $(function () { - $('#results-collapse-btn').click(function () { - $('.result .panel-collapse').collapse('hide'); - $(document).find('span.collapse-result-icon').each(function() { - $(this).addClass('fa-caret-square-down'); - $(this).removeClass('fa-caret-square-up'); - }); - }); - - $('#results-expand-btn').click(_expandAllResults); - }); - - // This checks if the ctarget param exist in the rendered url and opens the - // comment tab - if( getParam('ctarget') ){ - var target = '#'+ getParam('ctarget'); - $(target).find('a.comment-tab-link').click(); - } - } - function initHandsOnTables(root) { - root.find('div.hot-table').each(function() { + root.find('div.hot-table').each(function() { var $container = $(this).find('.step-result-hot-table'); var contents = $(this).find('.hot-contents'); @@ -50,19 +26,19 @@ colHeaders: true, fillHandle: false, formulas: true, - cells: function (row, col, prop) { + cells: function(row, col) { var cellProperties = {}; - if (col >= 0) + if (col >= 0) { cellProperties.readOnly = true; - else + } else { cellProperties.readOnly = false; - + } return cellProperties; } }); - var hot = $container.handsontable('getInstance'); - var data = JSON.parse(contents.attr('value')); + let hot = $container.handsontable('getInstance'); + let data = JSON.parse(contents.attr('value')); hot.loadData(data.data); }); } @@ -80,7 +56,7 @@ // Toggle editing buttons function toggleResultEditButtons(show) { - if(show) { + if (show) { $('#results-toolbar').show(); $('.edit-result-button').show(); } else { @@ -89,117 +65,113 @@ } } + function renderTable(table) { + $(table).handsontable('render'); + // Yet another dirty hack to solve HandsOnTable problems + if (parseInt($(table).css('height'), 10) < parseInt($(table).css('max-height'), 10) - 30) { + $(table).find('.ht_master .wtHolder').css({ height: '100%', width: '100%' }); + } + } + // Expand all results - function _expandAllResults() { + function expandAllResults() { $('.result .panel-collapse').collapse('show'); - $(document).find('span.collapse-result-icon').each(function() { + $(document).find('span.collapse-result-icon').each(function() { $(this).addClass('fa-caret-square-up'); $(this).removeClass('fa-caret-square-down'); }); - $(document).find('div.step-result-hot-table').each(function() { - _renderTable(this); + $(document).find('div.step-result-hot-table').each(function() { + renderTable(this); }); } function expandResult(result) { $('.panel-collapse', result).collapse('show'); - $(result).find('span.collapse-result-icon').each(function() { + $(result).find('span.collapse-result-icon').each(function() { $(this).addClass('fa-caret-square-up'); $(this).removeClass('fa-caret-square-down'); }); - _renderTable($(result).find('div.step-result-hot-table')); + renderTable($(result).find('div.step-result-hot-table')); animateSpinner(null, false); } - function _renderTable(table) { - $(table).handsontable('render'); - // Yet another dirty hack to solve HandsOnTable problems - if (parseInt($(table).css('height'), 10) < - parseInt($(table).css('max-height'), 10) - 30) { - $(table).find('.ht_master .wtHolder').css({ 'height': '100%', - 'width': '100%' }); - } + // create custom ajax request in order to fix issuses with remote: true from + function handleResultFileSubmit(form, ev) { + ev.preventDefault(); + ev.stopPropagation(); + + const url = form.find('#result_asset_attributes_file').data('directUploadUrl'); + const file = form.find('#result_asset_attributes_file')[0].files[0]; + const upload = new ActiveStorage.DirectUpload(file, url); + + animateSpinner(); + + upload.create((error, blob) => { + if (error) { + // Handle the error + } else { + let formData = new FormData(); + formData.append('result[name]', form.find('#result_name').val()); + formData.append('result[asset_attributes][id]', form.find('#result_asset_attributes_id').val()); + formData.append('result[asset_attributes][signed_blob_id]', blob.signed_id); + + $.ajax({ + type: 'PUT', + url: form.attr('action'), + data: formData, + success: function(data) { + animateSpinner(null, false); + $('.edit_result').parent().remove(); + $(data.html).prependTo('#results').promise().done(() => { + $.each($('#results').find('.result'), function() { + initFormSubmitLinks($(this)); + ResultAssets.applyEditResultAssetCallback(); + applyCollapseLinkCallBack(); + applyCreateWopiFileCallback(); + toggleResultEditButtons(true); + FilePreviewModal.init(); + Comments.init(); + ResultAssets.initNewResultAsset(); + expandResult($(this)); + }); + }); + + $('#results-toolbar').show(); + }, + error: function(XHR) { + animateSpinner(null, false); + $('.edit_result').renderFormErrors('result', XHR.responseJSON.errors); + }, + processData: false, + contentType: false + }); + } + }); } - function processResult(ev, resultTypeEnum, editMode) { + function processResult(ev, resultTypeEnum) { var $form = $(ev.target.form); $form.clearFormErrors(); - switch(resultTypeEnum) { + switch (resultTypeEnum) { case ResultTypeEnum.FILE: - _handleResultFileSubmit($form, ev); + handleResultFileSubmit($form, ev); break; case ResultTypeEnum.TABLE: - var $nameInput = $form.find('#result_name'); - var nameValid = textValidator(ev, $nameInput, 0, - <%= Constants::NAME_MAX_LENGTH %>); + textValidator(ev, $form.find('#result_name'), 0, $form.data('name-max-length')); break; case ResultTypeEnum.TEXT: - var $nameInput = $form.find('#result_name'); - var nameValid = textValidator(ev, $nameInput, 0, - <%= Constants::NAME_MAX_LENGTH %>); - var $descrTextarea = $form.find("#result_text_attributes_textarea"); - var $tinyMCEInput = TinyMCE.getContent(); - textValidator(ev, $descrTextarea, 1, <%= Constants::RICH_TEXT_MAX_LENGTH %>, false, $tinyMCEInput); - break; - case ResultTypeEnum.COMMENT: - var $commentInput = $form.find('#comment_message'); - var commentValid = textValidator(ev, $commentInput, 1, - <%= Constants::TEXT_MAX_LENGTH %>); + textValidator(ev, $form.find('#result_name'), 0, $form.data('name-max-length')); + textValidator( + ev, $form.find('#result_text_attributes_textarea'), 1, + $form.data('rich-text-max-length'), false, TinyMCE.getContent() + ); break; + default: + // do nothing } } - // create custom ajax request in order to fix issuses with remote: true from - function _handleResultFileSubmit(form, ev) { - ev.preventDefault(); - ev.stopPropagation(); - animateSpinner(); - var data = new FormData(); - var file = document.getElementById('result_asset_attributes_file') - .files[0]; - data.append('result[name]', form.find('#result_name').val()); - data.append('result[asset_attributes][id]', - form.find('#result_asset_attributes_id').val()); - if( file ) { - data.append('result[asset_attributes][file]', file); - } - - $.ajax({ - type: 'PUT', - url: form.attr('action'), - data: data, - success: function(data) { - animateSpinner(null, false); - $('.edit_result').parent().remove(); - $(data.html).prependTo('#results').promise().done(function() { - $.each($('#results').find('.result'), - function() { - initFormSubmitLinks($(this)); - ResutlAssets.applyEditResultAssetCallback(); - applyCollapseLinkCallBack(); - applyCreateWopiFileCallback(); - toggleResultEditButtons(true); - FilePreviewModal.init(); - Comments.init(); - ResutlAssets.initNewResultAsset(); - expandResult($(this)); - }); - - }); - $('#results-toolbar').show(); - }, - error: function(XHR) { - animateSpinner(null, false) - $('.edit_result').renderFormErrors('result', - XHR.responseJSON['errors']); - - }, - processData: false, - contentType: false, - }); - } - // init cancel button function initCancelFormButton(form, callback) { $(form).find('.cancel-new').click(function(event) { @@ -217,14 +189,40 @@ e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); - var el = $(element); - if(confirm(el.data('confirm-text'))) { + let el = $(element); + if (confirm(el.data('confirm-text'))) { animateSpinner(); $('#' + el.data('form-id')).submit(); } } - var publicAPI = Object.freeze({ + function init() { + initHandsOnTables($(document)); + expandAllResults(); + applyCollapseLinkCallBack(); + applyCreateWopiFileCallback(); + + $(function() { + $('#results-collapse-btn').click(function() { + $('.result .panel-collapse').collapse('hide'); + $(document).find('span.collapse-result-icon').each(function() { + $(this).addClass('fa-caret-square-down'); + $(this).removeClass('fa-caret-square-up'); + }); + }); + + $('#results-expand-btn').click(expandAllResults); + }); + + // This checks if the ctarget param exist in the rendered url and opens the + // comment tab + if (getParam('ctarget')) { + let target = '#' + getParam('ctarget'); + $(target).find('a.comment-tab-link').click(); + } + } + + let publicAPI = Object.freeze({ init: init, initHandsOnTables: initHandsOnTables, applyCollapseLinkCallBack: applyCollapseLinkCallBack, @@ -237,7 +235,7 @@ }); return publicAPI; - })(); + }()); Results.init(); -})(window); +}(window)); diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb index c5f23ca44..7f798f779 100644 --- a/app/assets/javascripts/protocols/steps.js.erb +++ b/app/assets/javascripts/protocols/steps.js.erb @@ -564,10 +564,10 @@ tableNamesValid ) { $form.find("[data-role='editable-table']").each(function() { - var hot = $(this).find(".hot").handsontable('getInstance'); - var contents = $(this).find('.hot-contents'); - var data = JSON.stringify({data: hot.getData()}); - contents.attr("value", data); + let hot = $(this).find(".hot").handsontable('getInstance'); + let contents = $(this).find('.hot-contents'); + let tableData = JSON.stringify({tableData: hot.getData()}); + contents.attr("value", tableData); }); setTimeout(function() { @@ -576,55 +576,56 @@ }, 1000); animateSpinner(null, true); - var data = DragNDropSteps.appendFilesToForm(ev); - data.append('step[description]', TinyMCE.getContent()); - $.ajax({ - url: $form.attr('action'), - method: 'POST', - data: data, - contentType: false, - processData: false, - beforeSend: function() { - $(".nested_step_checklists ul").each(function () { - reorderCheckboxData(this); - }); - }, - success: function(data) { - $($form.closest('.well')).after(data.html); - var $new_step = $($form.closest('.well')).next(); - $($form.closest('.well')).remove(); - - initCallBacks(); - initHandsOnTable($new_step); - expandStep($new_step); - toggleButtons(true); - SmartAnnotation.preventPropagation('.atwho-user-popover'); - - tinyMCE.editors.step_description_textarea.remove(); - - //Rerender tables - $new_step.find("div.step-result-hot-table").each(function() { - $(this).handsontable("render"); - }); - animateSpinner(null, false); - DragNDropSteps.clearFiles(); - FilePreviewModal.init(); - $.initTooltips(); - if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar(); - }, - error: function(xhr) { - if (xhr.responseJSON['assets.file']) { - $('#new-step-assets-group').addClass('has-error'); - $('#new-step-assets-tab').addClass('has-error'); - $.each(xhr.responseJSON['assets.file'], function(_, value) { - $('#new-step-assets-group').prepend('' + value + ''); + DragNDropSteps.appendFilesToForm(ev).then(formData => { + // formData.append('step[description]', TinyMCE.getContent()); + $.ajax({ + url: $form.attr('action'), + method: 'POST', + data: formData, + contentType: false, + processData: false, + beforeSend: function() { + $(".nested_step_checklists ul").each(function () { + reorderCheckboxData(this); }); + }, + success: function(data) { + $($form.closest('.well')).after(data.html); + var $new_step = $($form.closest('.well')).next(); + $($form.closest('.well')).remove(); + + initCallBacks(); + initHandsOnTable($new_step); + expandStep($new_step); + toggleButtons(true); + SmartAnnotation.preventPropagation('.atwho-user-popover'); + + tinyMCE.editors.step_description_textarea.remove(); + + //Rerender tables + $new_step.find("div.step-result-hot-table").each(function() { + $(this).handsontable("render"); + }); + animateSpinner(null, false); + DragNDropSteps.clearFiles(); + FilePreviewModal.init(); + $.initTooltips(); + if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar(); + }, + error: function(xhr) { + if (xhr.responseJSON['assets.file']) { + $('#new-step-assets-group').addClass('has-error'); + $('#new-step-assets-tab').addClass('has-error'); + $.each(xhr.responseJSON['assets.file'], function(_, value) { + $('#new-step-assets-group').prepend('' + value + ''); + }); + } + animateSpinner(null, false); + SmartAnnotation.preventPropagation('.atwho-user-popover'); } - animateSpinner(null, false); - SmartAnnotation.preventPropagation('.atwho-user-popover'); - } + }); + newStepHandler(); }); - newStepHandler(); } } diff --git a/app/assets/javascripts/repositories/forms/repository_item_edit.js b/app/assets/javascripts/repositories/forms/repository_item_edit.js index b6b1d5a0d..004b31e42 100644 --- a/app/assets/javascripts/repositories/forms/repository_item_edit.js +++ b/app/assets/javascripts/repositories/forms/repository_item_edit.js @@ -1,3 +1,5 @@ +/* global Promise _ ActiveStorage RepositoryItemEditForm */ + //= require sugar.min //= require jquerymy-1.2.14.min @@ -116,55 +118,78 @@ var formData = this.formData; var formDataObj = new FormData(); var removeFileColumns = []; + var filesToUploadCntr = 0; + var filesUploadedCntr = 0; + const directUploadUrl = $(tableID).data('directUploadUrl'); + formDataObj.append('request_url', $(tableID).data('current-uri')); formDataObj.append('repository_row_id', $(selectedRecord).attr('id')); - $(_.keys(this.formData)).each(function(_, element) { - var value = formData[element]; - if (element === "rowName") { - formDataObj.append('repository_row_name', value); - } else { - var colId = element.replace('colId-', ''); - var $el = $('#' + element); - // don't save anything if element is not visible - if($el.length == 0) { - return true; - } - if($el.attr('type') === 'file') { - // handle deleting of element - if($el.attr('remove') === "true") { - removeFileColumns.push(colId); - formDataObj.append('repository_cells[' + colId + ']', null); - } else { - formDataObj.append('repository_cells[' + colId + ']', - getFileValue($el)); + return new Promise((resolve, reject) => { + $(_.keys(this.formData)).each(function(_, element) { + var value = formData[element]; + if (element === 'rowName') { + formDataObj.append('repository_row_name', value); + } else { + let colId = element.replace('colId-', ''); + let $el = $('#' + element); + // don't save anything if element is not visible + if ($el.length === 0) { + return; + } + if ($el.attr('type') === 'file') { + // handle deleting of element + if ($el.attr('remove') === 'true') { + removeFileColumns.push(colId); + formDataObj.append('repository_cells[' + colId + ']', null); + } else if ($el[0].files.length > 0) { + filesToUploadCntr += 1; + } + } else if (value.length >= 0) { + formDataObj.append('repository_cells[' + colId + ']', value); } - } else if(value.length >= 0) { - formDataObj.append('repository_cells[' + colId + ']', value); } + }); + + formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns)); + + // No files for upload, so return earlier + if (filesToUploadCntr === 0) { + resolve(formDataObj); + return; } + + // Second run, just for files + $(_.keys(this.formData)).each(function(_, element) { + let $el = $('#' + element); + let colId = element.replace('colId-', ''); + + if ($el.attr('type') === 'file' && $el.attr('remove') !== 'true') { + let upload = new ActiveStorage.DirectUpload($el[0].files[0], directUploadUrl); + + upload.create(function(error, blob) { + if (error) { + reject(error); + } else { + formDataObj.append('repository_cells[' + colId + ']', blob.signed_id); + filesUploadedCntr += 1; + + if (filesUploadedCntr === filesToUploadCntr) { + resolve(formDataObj); + } + } + }); + } + }); }); - formDataObj.append('remove_file_columns', JSON.stringify(removeFileColumns)); - return formDataObj; - } + }; + /** * |-----------------| * | Private methods | * |-----------------| */ - /** - * Resolves the file cell on FormData creation - * - * @param {Object} elementId - * - * @returns (String | Object) - */ - function getFileValue(element) { - var file = element[0].files[0]; - return (file) ? file : ''; - } - /** * Takes object and surrounds it with input * @@ -176,8 +201,8 @@ * @returns (String) */ function changeToInputField(object, name, value, id) { - return "
"; + return "
"; } /** @@ -368,4 +393,4 @@ formData[generateInputFieldReference(columnId)] = undefined; } } -})(window); +}(window)); diff --git a/app/assets/javascripts/repositories/repository_datatable.js.erb b/app/assets/javascripts/repositories/repository_datatable.js.erb index 9309fb2ae..e33966771 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js.erb +++ b/app/assets/javascripts/repositories/repository_datatable.js.erb @@ -695,65 +695,13 @@ var RepositoryDatatable = (function(global) { }); } - // Save record - global.onClickSave = function() { - var node; - var rowData; - var formData; - if (saveAction === 'update') { - var row = TABLE.row(selectedRecord); - node = row.node(); - rowData = row.data(); - formData = SCINOTE_REPOSITORY_EDITED_ROWS[0].parseToFormObject( - TABLE_ID, selectedRecord - ); - - } else if (saveAction === 'create') { - node = selectedRecord; - - // First fetch all the data in input fields - formData = new FormData(); - formData.append('request_url', $(TABLE_ID).data('current-uri')); - formData.append('repository_row_id', $(selectedRecord).attr('id')); - - // Direct record attributes - // Record name - formData.append('repository_row_name', $('td input[data-object = repository_row]').val()); - - // Custom cells text type - $(node).find('td input[data-object = repository_cell]').each(function() { - // Send data only and only if cell is not empty - if ($(this).val().trim()) { - formData.append('repository_cells[' + $(this).attr('name') + ']', $(this).val()); - } - }); - // Custom cells file type - $(node).find('td input[data-object = repository_cell_file]').each(function() { - // Send data only and only if cell is not empty - if ($(this).context.files.length == 1 ) { - if ($(this).data('changed')) { - formData.append('repository_cells[' + $(this).attr('name') + ']', - $(this).context.files[0]); - } else { - formData.append('repository_cells[' + $(this).attr('name') + ']', ''); - } - } - }); - // Custom cells list type - $(node).find('td[column_id]').each(function(index, el) { - var value = $(el).attr('list_item_id'); - formData.append('repository_cells[' + $(el).attr('column_id') + ']', value); - }); - } - + function submitForm(url, formData) { var url; var type; if (saveAction === 'update') { - url = rowData.recordUpdateUrl; type = 'PUT'; } else { type = 'POST'; - url = $('table' + TABLE_ID).data('create-record'); } $.ajax({ url: url, @@ -767,8 +715,10 @@ var RepositoryDatatable = (function(global) { SmartAnnotation.closePopup(); SCINOTE_REPOSITORY_EDITED_ROWS = []; onClickCancel(); + animateSpinner(null, false); }, error: function(e) { + animateSpinner(null, false); SmartAnnotation.closePopup(); var data = e.responseJSON; clearAllErrors(); @@ -825,6 +775,92 @@ var RepositoryDatatable = (function(global) { } } }); + } + + function buildNewFormData() { + return new Promise((resolve, reject) => { + var node = selectedRecord; + var formData = new FormData(); + var filesToUploadCntr = 0; + var filesUploadedCntr = 0; + const directUploadUrl = $(TABLE_ID).data('directUploadUrl'); + + // First fetch all the data in input fields + formData.append('request_url', $(TABLE_ID).data('current-uri')); + formData.append('repository_row_id', $(selectedRecord).attr('id')); + + // Direct record attributes + // Record name + formData.append('repository_row_name', $('td input[data-object = repository_row]').val()); + + // Custom cells text type + $(node).find('td input[data-object = repository_cell]').each(function() { + // Send data only and only if cell is not empty + if ($(this).val().trim()) { + formData.append('repository_cells[' + $(this).attr('name') + ']', $(this).val()); + } + }); + + // Custom cells list type + $(node).find('td[column_id]').each(function(index, el) { + var value = $(el).attr('list_item_id'); + formData.append('repository_cells[' + $(el).attr('column_id') + ']', value); + }); + + // Custom cells file type, first run, count files ready for upload + $(node).find('td input[data-object = repository_cell_file]').each(function() { + // Send data only and only if cell is not empty + if ($(this)[0].files.length == 1 ) { + filesToUploadCntr += 1; + } + }); + + // No files for upload, so return earlier + if (filesToUploadCntr === 0) { + resolve(formData); + return; + } + + // Custom cells file type, second run, upload files + $(node).find('td input[data-object = repository_cell_file]').each(function() { + // Send data only and only if cell is not empty + if ($(this)[0].files.length == 1 ) { + let upload = new ActiveStorage.DirectUpload($(this)[0].files[0], directUploadUrl); + let colId = $(this).attr('name'); + + upload.create(function(error, blob) { + if (error) { + reject(error); + } else { + formData.append('repository_cells[' + colId + ']', blob.signed_id); + filesUploadedCntr += 1; + + if (filesUploadedCntr === filesToUploadCntr) { + resolve(formData); + } + } + }); + } + }); + }); + } + + // Save record + global.onClickSave = function() { + animateSpinner(null, true); + if (saveAction === 'update') { + let row = TABLE.row(selectedRecord); + let rowData = row.data(); + SCINOTE_REPOSITORY_EDITED_ROWS[0].parseToFormObject( + TABLE_ID, selectedRecord + ).then(formData => { + submitForm(rowData.recordUpdateUrl, formData); + }); + } else if (saveAction === 'create') { + buildNewFormData().then(formData => { + submitForm($('table' + TABLE_ID).data('create-record'), formData); + }); + } }; // Delete record diff --git a/app/assets/javascripts/results/result_assets.js b/app/assets/javascripts/results/result_assets.js index 2fe6e7691..fdb266a35 100644 --- a/app/assets/javascripts/results/result_assets.js +++ b/app/assets/javascripts/results/result_assets.js @@ -1,7 +1,7 @@ (function(global) { 'use strict'; - global.ResutlAssets = (function() { + global.ResultAssets = (function() { // New result asset behaviour function initNewResultAsset() { $('#new-result-asset').on('click', function(event) { @@ -96,7 +96,7 @@ return publicAPI; })(); - ResutlAssets.initNewResultAsset(); - ResutlAssets.applyEditResultAssetCallback(); + ResultAssets.initNewResultAsset(); + ResultAssets.applyEditResultAssetCallback(); FilePreviewModal.init(); }(window)); diff --git a/app/assets/javascripts/sitewide/drag_n_drop.js b/app/assets/javascripts/sitewide/drag_n_drop.js new file mode 100644 index 000000000..eccf0bfc1 --- /dev/null +++ b/app/assets/javascripts/sitewide/drag_n_drop.js @@ -0,0 +1,630 @@ +/* global Promise ActiveStorage animateSpinner copyFromClipboard I18n + Results ResultAssets FilePreviewModal Comments truncateLongString + DragNDropSteps DragNDropResults initFormSubmitLinks dragNdropAssetsInit */ + +(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 = ''; + 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); + $.initTooltips(); + } + + 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; + var uploadedFilesCounter = 0; + + // return the status of files if they are ready to submit + function filesStatus() { + return filesValid; + } + + function clearFiles() { + droppedFiles = []; + } + + function incrementUploadedFilesCounter() { + uploadedFilesCounter += 1; + } + + function getUploadedFilesCounter() { + return uploadedFilesCounter; + } + + 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 numberOfFiles = droppedFiles.length; + let prevEls = $('input').filter(function() { + return this.name.match(regex); + }); + + let fd = new FormData(form); + + uploadedFilesCounter = 0; + fd.delete('step[file][]'); + + if (droppedFiles.length === 0) { + resolve(fd); + return; + } + + for (let i = 0; i < droppedFiles.length; i += 1) { + let upload = new ActiveStorage.DirectUpload(droppedFiles[i], url); + let index = i + prevEls.length; + + upload.create(function(error, blob) { + if (error) { + reject(error); + } else { + fd.append('step[assets_attributes][' + index + '][signed_blob_id]', blob.signed_id); + incrementUploadedFilesCounter(); + if (getUploadedFilesCounter() === numberOfFiles) { + resolve(fd); + } + } + }); + } + + 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 "

" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '

'; + } + return ''; + } + + function validateTotalSize() { + if (totalSize > fileMaxSize) { + filesValid = false; + disableSubmitButton(); + $.each($('.panel-step-attachment-new'), function() { + if (!$(this).find('p').hasClass('dnd-total-error')) { + $(this) + .find('.panel-body') + .append("

" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '

'); + } + }); + } else { + $('.dnd-total-error').remove(); + if (filerAndCheckFiles()) { + filesValid = true; + enableSubmitButton(); + } + } + } + + function uploadedAssetPreview(asset, i) { + var html = '
'; + html += '
'; + html += ''; + html += '
'; + html += '
'; + html += truncateLongString(asset.name, $(document.body).data('filename-max-length')); + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += validateFilesSize(asset); + html += '
'; + + 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-placeholder.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 = $(document.body).data('file-max-size-mb'); + fileMaxSize = fileMaxSizeMb * 1024 * 1024; + for (let i = 0; i < files.length; i += 1) { + 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("

" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '

'); + } + }); + } 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.applyCollapseLinkCallBack(); + Results.toggleResultEditButtons(true); + FilePreviewModal.init(); + 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 numberOfFiles = droppedFiles.length; + + let resultNames = []; + + $.each($('input[rel="results[name]"]'), function() { + resultNames.push($(this).val()); + }); + + resultNames.reverse(); + + for (let i = 0; i < numberOfFiles; i += 1) { + let upload = new ActiveStorage.DirectUpload(droppedFiles[i], url); + + upload.create(function(error, blob) { + if (error) { + // Handle the error + } else { + fd.append('results_names[' + i + ']', resultNames[i]); + fd.append('results_files[' + i + '][signed_blob_id]', blob.signed_id); + if ((i + 1) === numberOfFiles) { + submitResultForm($(ev.target).attr('data-href'), fd); + destroyAll(); + } + } + }); + } + } + + /* 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 "

" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '

'; + } + return ''; + } + + function validateTextSize(input) { + if (input.value.length > $(document.body).data('name-max-length')) { + $(input).parent().find('.dnd-error').remove(); + $(input).after("

" + I18n.t('general.text.length_too_long', { max_length: $(document.body).data('name-max-length') }) + '

'); + isValid = false; + } else { + $(input).parent().find('.dnd-error').remove(); + isValid = true; + } + } + + function uploadedAssetPreview(asset, i) { + var html = '
'; + html += '
'; + html += ''; + html += I18n.t('assets.drag_n_drop.file_label'); + html += '
'; + html += ''; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += '
'; + html += truncateLongString(asset.name, $(document.body).data('filename-max-length')); + html += validateFilesSize(asset); + html += '
'; + return html; + } + + function processResult(ev) { + ev.preventDefault(); + ev.stopPropagation(); + + if (isValid && filerAndCheckFiles()) { + animateSpinner(); + + let formData = new FormData(); + + appendFilesToForm(ev, formData); + } + } + + 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; + $('.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(i, listItems); + }); + } + validateTotalSize(); + dragNdropAssetsInit('results'); + } + } + + function init(files) { + fileMaxSizeMb = $(document.body).data('file-max-size-mb'); + fileMaxSize = fileMaxSizeMb * 1024 * 1024; + for (let i = 0; i < files.length; i += 1) { + droppedFiles.push(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)); diff --git a/app/assets/javascripts/sitewide/drag_n_drop.js.erb b/app/assets/javascripts/sitewide/drag_n_drop.js.erb deleted file mode 100644 index 54c7e77d2..000000000 --- a/app/assets/javascripts/sitewide/drag_n_drop.js.erb +++ /dev/null @@ -1,568 +0,0 @@ -(function(global) { - 'use strict'; - // Copy from clipboard - global.copyFromClipboard = (function() { - var UPLOADED_IMAGE = {}; - var LOCATION = ''; - function init(location) { - LOCATION = location; - global.addEventListener('paste', _listener, false); - $.initTooltips(); - }; - - function destroy() { - global.removeEventListener('paste', _listener, false); - } - - 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); - }; - var URLObj = window.URL || window.webkitURL; - img.src = URLObj.createObjectURL(imageBlob); - var 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 _retrieveImageFromClipboardAsBlob(pasteEvent, callback){ - if(pasteEvent.clipboardData == false) { - if(typeof(callback) == "function"){ - callback(undefined); - } - }; - - var items = pasteEvent.clipboardData.items; - if(items == undefined){ - if(typeof(callback) == "function"){ - callback(undefined); - } - }; - - for (var i = 0; i < items.length; i++) { - if (items[i].type.indexOf("image") == -1) continue; - var blob = items[i].getAsFile(); - - if(typeof(callback) == "function") { - callback(blob); - } - } - }; - - // removes modal from dom - function _destroyModalCallback() { - var modal = $('#clipboardPreviewModal'); - modal.on('hidden.bs.modal', function() { - modal.modal('hide').promise().done(function() { - modal.remove(); - }); - UPLOADED_IMAGE = {}; - }); - } - - function _closeModal() { - _hideModalForGood(); - $("#clipboardPreviewModal").remove(); - } - - // $(..).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 _addImageCallback() { - $('[data-action="addImageFormClipboard"]').on('click', function() { - var inputArray = []; - var newName = $('#clipboardImageName').val(); - // check if the name is set - if( newName && newName.length > 0 ) { - var extension = UPLOADED_IMAGE.name.slice( - (Math.max(0, UPLOADED_IMAGE.name.lastIndexOf(".")) || Infinity) + 1 - ); - // hack to inject custom name in File object - var name = newName + '.' + extension; - var blob = UPLOADED_IMAGE.slice(0, UPLOADED_IMAGE.size, UPLOADED_IMAGE.type); - // make new blob with the correct name; - var 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 = {}; - }); - } - - // Generate modal html and hook callbacks - function _clipboardPasteModal() { - var html = '
'; - 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(); - }); - } - - 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; - - function init(files) { - fileMaxSizeMb = $(document.body).data('file-max-size-mb'); - fileMaxSize = fileMaxSizeMb * 1024 * 1024; - for(var i = 0; i < files.length; i++) { - droppedFiles.push(files[i]); - } - listItems(); - } - - // return the status of files if they are ready to submit - function filesStatus() { - return filesValid; - } - - function clearFiles() { - droppedFiles = []; - } - - // loops through a list of files and display each file in a separate panel - function listItems() { - totalSize = 0; - _enableSubmitButton(); - $('.attachment-placeholder.new').remove(); - _dragNdropAssetsOff(); - for(var i = 0; i < droppedFiles.length; i++) { - $('.attachments.edit') - .append(_uploadedAssetPreview(droppedFiles[i], i)) - .promise() - .done(function() { - _removeItemHandler(i); - }); - } - _validateTotalSize(); - dragNdropAssetsInit('steps'); - } - - // append the files to the form before submit - function appendFilesToForm(ev) { - var regex = /step\[assets_attributes\]\[[0-9]*\]\[id\]/; - var prevEls = $('input').filter(function() { - return this.name.match(regex); - }); - var fd = new FormData($(ev.target).closest('form').get(0)); - for(var i = 0; i < droppedFiles.length; i++) { - var index = i + prevEls.length; - var name = 'step[assets_attributes][' + index + '][file]'; - fd.append(name, droppedFiles[i]); - } - - filesValid = true; - totalSize = 0; - _dragNdropAssetsOff(); - return fd; - } - - function _disableSubmitButton() { - $('.step-save').prop('disabled', true); - } - - function _enableSubmitButton() { - $('.step-save').prop('disabled', false); - } - - function _filerAndCheckFiles() { - for(var i = 0; i < droppedFiles.length; i++) { - if(droppedFiles[i].isValid == false) { - return false; - } - } - return (droppedFiles.length > 0); - } - - function _validateFilesSize(file) { - var fileSize = file.size; - totalSize += parseInt(fileSize); - if(fileSize > fileMaxSize) { - file.isValid = false; - _disableSubmitButton(); - return "

" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '

'; - } - return ''; - } - - function _validateTotalSize() { - if(totalSize > fileMaxSize) { - filesValid = false; - _disableSubmitButton(); - $.each($('.panel-step-attachment-new'), function() { - if(!$(this).find('p').hasClass('dnd-total-error')) { - $(this) - .find('.panel-body') - .append("

" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '

'); - } - }); - } else { - $('.dnd-total-error').remove(); - if(_filerAndCheckFiles()) { - filesValid = true; - _enableSubmitButton(); - } - } - } - - function _uploadedAssetPreview(asset, i) { - var html = '
'; - html +='
'; - html +=''; - html +='
'; - html +='
' + truncateLongString(asset.name, <%= Constants::FILENAME_TRUNCATION_LENGTH %>); - html += '
'; - html +='
'; - html +='
'; - html +=''; - html += _validateFilesSize(asset); - html +='
'; - - return html; - } - - function _removeItemHandler(id) { - $('[data-item-id="' + id +'"]').off('click').on('click', function(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - e.stopPropagation(); - var $el = $(this); - var index = $el.data('item-id'); - totalSize -= parseInt(droppedFiles[index].size); - droppedFiles.splice(index, 1); - listItems(); - }); - } - - function _dragNdropAssetsOff() { - $('body').off('drag dragstart dragend dragover dragenter dragleave drop'); - $('.is-dragover').hide(); - // remove listeners for clipboard images - copyFromClipboard.destroy(); - } - - 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 init(files) { - fileMaxSizeMb = $(document.body).data('file-max-size-mb'); - fileMaxSize = fileMaxSizeMb * 1024 * 1024; - for(var i = 0; i < files.length; i++) { - droppedFiles.push(files[i]); - } - listItems(); - } - - // return the status of files if they are ready to submit - function filesStatus() { - return isValid; - } - - // 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(var i = 0; i < droppedFiles.length; i++) { - $('#new-result-assets-select') - .after(_uploadedAssetPreview(droppedFiles[i], i)) - .promise() - .done(function() { - _removeItemHandler(i); - }); - } - _validateTotalSize(); - dragNdropAssetsInit('results'); - } - } - - // appent the files to the form before submit - function _appendFilesToForm() { - var regex = /result\[assets_attributes\]\[[0-9]*\]\[id\]/; - var prevEls = $('input').filter(function() { - return this.name.match(regex); - }); - - var fd = new FormData(); - var result_names = []; - $.each($('input[rel="results[name]"]'), function() { - result_names.push($(this).val()); - }); - result_names.reverse(); - for(var i = 0; i < droppedFiles.length; i++) { - var index = i + prevEls.length; - var file_name = 'results_files[' + index + ']'; - fd.append(file_name, droppedFiles[i]); - fd.append('results_names[' + i + ']', result_names[i]); - } - destroyAll(); - return fd; - } - - function _disableSubmitButton() { - $('.save-result').prop('disabled', true); - } - - function _enableSubmitButton() { - $('.save-result').prop('disabled', false); - } - - function _filerAndCheckFiles() { - for(var i = 0; i < droppedFiles.length; i++) { - if(droppedFiles[i].isValid == false) { - return false; - } - } - return (droppedFiles.length > 0); - } - - function _validateFilesSize(file) { - var fileSize = file.size; - totalSize += parseInt(fileSize); - if(fileSize > fileMaxSize) { - file.isValid = false; - _disableSubmitButton(); - return "

" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '

'; - } - return ''; - } - - 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("

" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '

'); - } - }); - } else { - $('.dnd-total-error').remove(); - if(_filerAndCheckFiles()) { - isValid = true; - _enableSubmitButton(); - } - } - } - - function validateTextSize(input) { - if(input.value.length > <%= Constants::NAME_MAX_LENGTH %>) { - $(input).parent().find('.dnd-error').remove(); - $(input).after("

<%= I18n.t('general.text.length_too_long', max_length: Constants::NAME_MAX_LENGTH) %>

"); - isValid = false; - } else { - $(input).parent().find('.dnd-error').remove(); - isValid = true; - } - } - - function _uploadedAssetPreview(asset, i) { - var html = '
'; - html += '
'; - html += ''; - html += '<%= I18n.t 'assets.drag_n_drop.file_label' %>'; - html += '
'; - html += ''; - html += '
'; - html += '
'; - html += ''; - html += ''; - html += '
'; - html += truncateLongString(asset.name, - <%= Constants::FILENAME_TRUNCATION_LENGTH %>); - html += _validateFilesSize(asset); - html += '
'; - return html; - } - - function _removeItemHandler(id) { - $('[data-item-id="' + id +'"]').off('click').on('click', function(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - e.stopPropagation(); - var $el = $(this); - var index = $el.data('item-id'); - totalSize -= parseInt(droppedFiles[index].size); - droppedFiles.splice(index, 1); - listItems(); - }); - } - - function processResult(button) { - if(isValid && _filerAndCheckFiles()) { - animateSpinner(); - $.ajax({ - url: $(button).attr('data-href'), - method: 'POST', - data: _appendFilesToForm(), - 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)); - ResutlAssets.applyEditResultAssetCallback(); - Results.applyCollapseLinkCallBack(); - Results.toggleResultEditButtons(true); - FilePreviewModal.init(); - Comments.init(); - ResutlAssets.initNewResultAsset(); - Results.expandResult($(this)); - }); - - }); - $('#results-toolbar').show(); - }, - error: function() { - animateSpinner(); - location.reload(); - } - }) - } - } - - function destroyAll() { - _dragNdropAssetsOff(); - droppedFiles = []; - isValid = true; - totalSize = 0; - } - - function _dragNdropAssetsOff() { - $('body').off('drag dragstart dragend dragover dragenter dragleave drop'); - $('.is-dragover').hide(); - } - - return Object.freeze({ - init: init, - listItems: listItems, - destroyAll: destroyAll, - filesStatus: filesStatus, - validateTextSize: validateTextSize, - processResult: processResult - }); - })(); - - global.dragNdropAssetsInit = function(location) { - var in_window = true; - - $('body').on('drag dragstart dragend dragover dragenter dragleave drop', - function(e) { - e.preventDefault(); - e.stopPropagation(); - }).on('dragover', function() { - in_window = true; - $('.is-dragover').show(); - }).on('dragleave', function() { - in_window = false; - setTimeout(function() { - if(!in_window) { - $('.is-dragover').hide(); - } - }, 5000); - - }).on('drop', function(e) { - $('.is-dragover').hide(); - var files = e.originalEvent.dataTransfer.files; - if(location === 'steps') { - DragNDropSteps.init(files); - } else { - DragNDropResults.init(files); - } - }); - - copyFromClipboard.init(location); - } - -})(window); diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index c9e5a14c0..276de21df 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -1078,6 +1078,10 @@ ul.content-activities { } } +.drag-n-drop-file-input { + display: none !important; +} + .dnd-error, .dnd-total-error { color: $brand-danger; diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 092d32084..bfc9d4d21 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -61,70 +61,55 @@ class ResultAssetsController < ApplicationController end def update - update_params = result_params - previous_size = @result.space_taken - previous_asset = @result.asset + success_flash = nil + saved = false - if update_params.key? :asset_attributes - asset = Asset.find_by_id(update_params[:asset_attributes][:id]) - asset.created_by = current_user - asset.last_modified_by = current_user - asset.team = current_team - @result.asset = asset - end + @result.transaction do + update_params = result_params + previous_size = @result.space_taken - @result.last_modified_by = current_user - @result.assign_attributes(update_params) - success_flash = t('result_assets.update.success_flash', - module: @my_module.name) + if update_params.dig(:asset_attributes, :signed_blob_id) + @result.asset.last_modified_by = current_user + @result.asset.update(file: update_params[:asset_attributes][:signed_blob_id]) + update_params.delete(:asset_attributes) + end - if @result.archived_changed?(from: false, to: true) - if previous_asset.locked? - respond_to do |format| - format.html do - flash[:error] = t('result_assets.archive.error_flash') - redirect_to results_my_module_path(@my_module) - return - end + @result.last_modified_by = current_user + @result.assign_attributes(update_params) + success_flash = t('result_assets.update.success_flash', module: @my_module.name) + + if @result.archived_changed?(from: false, to: true) + if @result.asset.locked? + @result.errors.add(:asset_attributes, t('result_assets.archive.error_flash')) + raise ActiveRecord:: Rollback end - end - saved = @result.archive(current_user) - success_flash = t('result_assets.archive.success_flash', - module: @my_module.name) - if saved - log_activity(:archive_result) - end - elsif @result.archived_changed?(from: true, to: false) - render_403 - else - if previous_asset.locked? - @result.errors.add(:asset_attributes, - t('result_assets.edit.locked_file_error')) - respond_to do |format| - format.json do - render json: { - status: 'error', - errors: @result.errors - }, status: :bad_request - return - end + saved = @result.archive(current_user) + success_flash = t('result_assets.archive.success_flash', module: @my_module.name) + log_activity(:archive_result) if saved + elsif @result.archived_changed?(from: true, to: false) + @result.errors.add(:asset_attributes, t('result_assets.archive.error_flash')) + raise ActiveRecord:: Rollback + else + if @result.asset.locked? + @result.errors.add(:asset_attributes, t('result_assets.edit.locked_file_error')) + raise ActiveRecord:: Rollback end - end - # Asset (file) and/or name has been changed - saved = @result.save + # Asset (file) and/or name has been changed + saved = @result.save - if saved - # Release team's space taken due to - # previous asset being removed - team = @result.my_module.experiment.project.team - team.release_space(previous_size) - team.save + if saved + # Release team's space taken due to + # previous asset being removed + team = @result.my_module.experiment.project.team + team.release_space(previous_size) + team.save - # Post process new file if neccesary - @result.asset.post_process_file(team) if @result.asset.present? + # Post process new file if neccesary + @result.asset.post_process_file(team) if @result.asset.present? - log_activity(:edit_result) + log_activity(:edit_result) + end end end @@ -181,37 +166,29 @@ class ResultAssetsController < ApplicationController end def result_params - params.require(:result).permit( - :name, :archived, - asset_attributes: [ - :id, - :file - ] - ) + params.require(:result).permit(:name, :archived, asset_attributes: :signed_blob_id) end def create_multiple_results - success = true + success = false results = [] - params[:results_files].values.each_with_index do |file, index| - asset = Asset.new(created_by: current_user, - last_modified_by: current_user, - team: current_team) - asset.file.attach(file) - result = Result.new(user: current_user, - my_module: @my_module, - name: params[:results_names][index.to_s], - asset: asset, - last_modified_by: current_user) - if result.save && asset.save + + ActiveRecord::Base.transaction do + params[:results_files].values.each_with_index do |file, index| + asset = Asset.create!(created_by: current_user, last_modified_by: current_user, team: current_team) + asset.file.attach(file[:signed_blob_id]) + result = Result.create!(user: current_user, + my_module: @my_module, + name: params[:results_names][index.to_s], + asset: asset, + last_modified_by: current_user) results << result # Post process file here asset.post_process_file(@my_module.experiment.project.team) - log_activity(:add_result, result) - else - success = false end + + success = true end { status: success, results: results } end diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index 05fe5ab08..138dbca40 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -29,50 +29,63 @@ class StepsController < ApplicationController end def create - @step = Step.new(step_params) - # gerate a tag that replaces img tag in database - @step.completed = false - @step.position = @protocol.number_of_steps - @step.protocol = @protocol - @step.user = current_user - @step.last_modified_by = current_user - @step.assets.each do |asset| - asset.created_by = current_user - asset.team = current_team - end - @step.tables.each do |table| - table.created_by = current_user - table.team = current_team - end - # Update default checked state - @step.checklists.each do |checklist| - checklist.checklist_items.each do |checklist_item| - checklist_item.checked = false + @step = Step.new + @step.transaction do + new_step_params = step_params + + # Attach newly uploaded files, and than remove their blob ids from the parameters + new_step_params[:assets_attributes]&.each do |key, value| + next unless value[:signed_blob_id] + + asset = Asset.create!(created_by: current_user, last_modified_by: current_user, team: current_team) + asset.file.attach(value[:signed_blob_id]) + @step.assets << asset + new_step_params[:assets_attributes].delete(key) end + + @step.assign_attributes(new_step_params) + # gerate a tag that replaces img tag in database + @step.completed = false + @step.position = @protocol.number_of_steps + @step.protocol = @protocol + @step.user = current_user + @step.last_modified_by = current_user + @step.tables.each do |table| + table.created_by = current_user + table.team = current_team + end + # Update default checked state + @step.checklists.each do |checklist| + checklist.checklist_items.each do |checklist_item| + checklist_item.checked = false + end + end + + @step.save! + + # Post process all assets + @step.assets.each do |asset| + asset.post_process_file(@protocol.team) + end + + # link tiny_mce_assets to the step + TinyMceAsset.update_images(@step, params[:tiny_mce_images]) + + create_annotation_notifications(@step) + + # Generate activity + if @protocol.in_module? + log_activity(:create_step, @my_module.experiment.project, my_module: @my_module.id) + else + log_activity(:add_step_to_protocol_repository, nil, protocol: @protocol.id) + end + + # Update protocol timestamp + update_protocol_ts(@step) end respond_to do |format| - if @step.save - # Post process all assets - @step.assets.each do |asset| - asset.post_process_file(@protocol.team) - end - - # link tiny_mce_assets to the step - TinyMceAsset.update_images(@step, params[:tiny_mce_images]) - - create_annotation_notifications(@step) - - # Generate activity - if @protocol.in_module? - log_activity(:create_step, @my_module.experiment.project, my_module: @my_module.id) - else - log_activity(:add_step_to_protocol_repository, nil, protocol: @protocol.id) - end - - # Update protocol timestamp - update_protocol_ts(@step) - + if @step.errors.empty? format.json do render json: { html: render_to_string( @@ -129,6 +142,17 @@ class StepsController < ApplicationController # NOTE - step_params_all variable is updated destroy_attributes(step_params_all) + # Attach newly uploaded files, and than remove their blob ids from the parameters + step_params_all[:assets_attributes]&.each do |key, value| + next unless value[:signed_blob_id] + + @step.assets + .create!(created_by: current_user, last_modified_by: current_user, team: current_team) + .file + .attach(value[:signed_blob_id]) + step_params_all[:assets_attributes].delete(key) + end + @step.assign_attributes(step_params_all) @step.last_modified_by = current_user @@ -161,7 +185,7 @@ class StepsController < ApplicationController # Post process step assets @step.assets.each do |asset| - asset.post_process_file(team) + asset.post_process_file(team) if asset.changed? end # Generate activity @@ -469,14 +493,12 @@ class StepsController < ApplicationController # Checks if hash contains destroy parameter '_destroy' and returns # boolean value. - def has_destroy_params(params) - for key, values in params do - if values.respond_to?(:each) - for pos, attrs in params[key] do - if attrs[:_destroy] == '1' - return true - end - end + def has_destroy_params?(params) + params.each do |key, values| + next unless values.respond_to?(:each) + + params[key].each do |_, attrs| + return true if attrs[:_destroy] == '1' end end @@ -487,26 +509,26 @@ class StepsController < ApplicationController # values that contains destroy parameters from original variable and # puts them into update_params variable. def extract_destroy_params(params, update_params) - for key, values in params do - if values.respond_to?(:each) - update_params[key] = {} unless update_params[key] - attr_params = update_params[key] + params.each do |key, values| + next unless values.respond_to?(:each) - for pos, attrs in params[key] do - if attrs[:_destroy] == '1' - if attrs[:id].present? - asset = Asset.find_by_id(attrs[:id]) - if asset.try(&:locked?) - asset.errors.add(:base, 'This file is locked.') - else - attr_params[pos] = { id: attrs[:id], _destroy: '1' } - end + update_params[key] = {} unless update_params[key] + attr_params = update_params[key] + + params[key].each do |pos, attrs| + if attrs[:_destroy] == '1' + if attrs[:id].present? + asset = Asset.find_by_id(attrs[:id]) + if asset.try(&:locked?) + asset.errors.add(:base, 'This file is locked.') + else + attr_params[pos] = { id: attrs[:id], _destroy: '1' } end - params[key].delete(pos) - elsif has_destroy_params(params[key][pos]) - attr_params[pos] = { id: attrs[:id] } - extract_destroy_params(params[key][pos], attr_params[pos]) end + params[key].delete(pos) + elsif has_destroy_params?(params[key][pos]) + attr_params[pos] = { id: attrs[:id] } + extract_destroy_params(params[key][pos], attr_params[pos]) end end end @@ -587,8 +609,8 @@ class StepsController < ApplicationController ], assets_attributes: [ :id, - :file, - :_destroy + :_destroy, + :signed_blob_id ], tables_attributes: [ :id, diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2f9c92b1e..880cf22bb 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -33,6 +33,8 @@ data-atwho-rep-items-url="<%= atwho_rep_items_team_path(current_team) %>" data-atwho-menu-items="<%= atwho_menu_items_team_path(current_team) %>" data-file-max-size-mb="<%= Rails.configuration.x.file_max_size_mb %>" + data-name-max-length="<%= Constants::NAME_MAX_LENGTH %>" + data-filename-max-length="<%= Constants::FILENAME_TRUNCATION_LENGTH %>" data-tooltips-enabled="<%= current_user.settings[:tooltips_enabled] %>" <% end %> > @@ -45,7 +47,7 @@ <%= render "shared/file_preview_modal.html.erb" %> <%= render "shared/file_edit_modal.html.erb" %> <%= render "shared/navigation" %> - + <% if user_signed_in? && flash[:system_notification_modal] && current_user.show_login_system_notification? %> <%= render partial: "/system_notifications/system_notification_modal", locals: { notification: current_user.user_system_notifications.show_on_login(true) } %> <% else %> diff --git a/app/views/repositories/_repository_table.html.erb b/app/views/repositories/_repository_table.html.erb index ba7a9e3a7..0f864551a 100644 --- a/app/views/repositories/_repository_table.html.erb +++ b/app/views/repositories/_repository_table.html.erb @@ -7,6 +7,7 @@ data-create-record="<%= repository_repository_rows_path(repository) %>" data-delete-record="<%= repository_delete_records_path(repository) %>" data-copy-records="<%= repository_copy_records_path(repository) %>" + data-direct-upload-url="<%= rails_direct_uploads_url %>" data-max-dropdown-length="<%= Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>" data-repository-columns-ids="<%= repository.available_columns_ids %>" data-save-text="<%= I18n.t('general.save') %>" diff --git a/app/views/result_assets/_edit.html.erb b/app/views/result_assets/_edit.html.erb index 7d1bc574d..726029fff 100644 --- a/app/views/result_assets/_edit.html.erb +++ b/app/views/result_assets/_edit.html.erb @@ -7,7 +7,7 @@ <%= file_extension_icon(ff.object) %> <%= ff.object.file_name %>

- <%= ff.file_field :file %> + <%= ff.file_field :file, direct_upload: true %> <% end %>
@@ -16,7 +16,7 @@ <%= f.submit t("general.save"), class: 'btn btn-success save-result', - onclick: "Results.processResult(event, Results.ResultTypeEnum.FILE, true);" %> + onclick: "Results.processResult(event, Results.ResultTypeEnum.FILE);" %>
<% end %>
diff --git a/app/views/result_assets/_new.html.erb b/app/views/result_assets/_new.html.erb index ff3fd984b..021f222d4 100644 --- a/app/views/result_assets/_new.html.erb +++ b/app/views/result_assets/_new.html.erb @@ -1,32 +1,34 @@
-
- - <%=t 'assets.drag_n_drop.label_html' %> -
+
+
+ <%= f.submit t('general.cancel'), + class: 'btn btn-default cancel-new', + onclick: 'DragNDropResults.destroyAll();' %> + <%= f.submit t('result_assets.new.create'), + class: 'btn btn-success save-result', + onclick: 'DragNDropResults.processResult(event);', + disabled: true, + data: { href: my_module_result_assets_path(format: :json) } %> +
+ <% end %>
diff --git a/app/views/result_tables/_edit.html.erb b/app/views/result_tables/_edit.html.erb index c3385495a..0a050296b 100644 --- a/app/views/result_tables/_edit.html.erb +++ b/app/views/result_tables/_edit.html.erb @@ -1,5 +1,7 @@
- <%= bootstrap_form_for(@result, url: result_table_path(format: :json), remote: true) do |f| %> + <%= bootstrap_form_for(@result, url: result_table_path(format: :json), + data: { 'name-max-length': Constants::NAME_MAX_LENGTH }, + remote: true) do |f| %> <%= f.text_field :name, style: "margin-top: 10px;" %>
<%= f.fields_for :table do |ff| %> @@ -15,7 +17,7 @@ <%= f.submit t("general.save"), class: 'btn btn-success save-result', - onclick: "Results.processResult(event, Results.ResultTypeEnum.TABLE, true);" %> + onclick: "Results.processResult(event, Results.ResultTypeEnum.TABLE);" %>
<% end %>
diff --git a/app/views/result_tables/_new.html.erb b/app/views/result_tables/_new.html.erb index e0fa6403d..dc9b5ab98 100644 --- a/app/views/result_tables/_new.html.erb +++ b/app/views/result_tables/_new.html.erb @@ -1,5 +1,7 @@
- <%= bootstrap_form_for(@result, url: my_module_result_tables_path(format: :json), remote: true) do |f| %> + <%= bootstrap_form_for(@result, url: my_module_result_tables_path(format: :json), + data: { 'name-max-length': Constants::NAME_MAX_LENGTH }, + remote: true) do |f| %> <%= f.text_field :name, style: "margin-top: 10px;" %>
<%= f.fields_for :table do |ff| %> @@ -14,7 +16,7 @@ <%= f.submit t("result_tables.new.create"), class: 'btn btn-success save-result', - onclick: "Results.processResult(event, Results.ResultTypeEnum.TABLE, false);" %> + onclick: "Results.processResult(event, Results.ResultTypeEnum.TABLE);" %>
<% end %>
diff --git a/app/views/result_texts/_edit.html.erb b/app/views/result_texts/_edit.html.erb index e11c24eb6..581fd55b8 100644 --- a/app/views/result_texts/_edit.html.erb +++ b/app/views/result_texts/_edit.html.erb @@ -1,5 +1,7 @@
- <%= bootstrap_form_for(@result, url: result_text_path(format: :json), remote: :true) do |f| %> + <%= bootstrap_form_for(@result, url: result_text_path(format: :json), + data: { 'name-max-length': Constants::NAME_MAX_LENGTH, 'rich-text-max-length': Constants::RICH_TEXT_MAX_LENGTH }, + remote: :true) do |f| %> <%= f.text_field :name, style: "margin-top: 10px;" %>
<%= f.fields_for :result_text do |ff| %> <%= ff.tiny_mce_editor(:text, @@ -15,7 +17,7 @@ <%= f.submit t("general.save"), class: 'btn btn-success save-result', - onclick: "Results.processResult(event, Results.ResultTypeEnum.TEXT, true);" %> + onclick: "Results.processResult(event, Results.ResultTypeEnum.TEXT);" %>
<% end %> diff --git a/app/views/result_texts/_new.html.erb b/app/views/result_texts/_new.html.erb index 4cc567c32..e17dfa27c 100644 --- a/app/views/result_texts/_new.html.erb +++ b/app/views/result_texts/_new.html.erb @@ -1,5 +1,7 @@
- <%= bootstrap_form_for(@result, url: my_module_result_texts_path(format: :json), remote: true) do |f| %> + <%= bootstrap_form_for(@result, url: my_module_result_texts_path(format: :json), + data: { 'name-max-length': Constants::NAME_MAX_LENGTH, 'rich-text-max-length': Constants::RICH_TEXT_MAX_LENGTH }, + remote: true) do |f| %> <%= f.text_field :name, style: "margin-top: 10px;" %>
<%= f.fields_for :result_text do |ff| %> <%= ff.tiny_mce_editor(:text, @@ -14,7 +16,7 @@ <%= f.submit t("result_texts.new.create"), class: 'btn btn-success save-result', - onclick: "Results.processResult(event, Results.ResultTypeEnum.TEXT, false);" %> + onclick: "Results.processResult(event, Results.ResultTypeEnum.TEXT);" %>
<% end %> diff --git a/app/views/steps/_empty_step.html.erb b/app/views/steps/_empty_step.html.erb index 4a890c65a..3964609a7 100644 --- a/app/views/steps/_empty_step.html.erb +++ b/app/views/steps/_empty_step.html.erb @@ -59,10 +59,13 @@ <%=t 'assets.drag_n_drop.browse_label' %> - + + <%= f.file_field :file, + id: "drag-n-drop-assets", + multiple: true, + direct_upload: true, + onchange: "DragNDropSteps.init(this.files)" %> +