mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge branch 'develop' into features/new_experiment_views
This commit is contained in:
commit
ea661f3c19
|
@ -273,7 +273,7 @@ Style/SymbolArray:
|
|||
EnforcedStyle: percent
|
||||
|
||||
Style/SymbolProc:
|
||||
IgnoredMethods:
|
||||
AllowedMethods:
|
||||
- respond_to
|
||||
- define_method
|
||||
|
||||
|
@ -352,7 +352,7 @@ Metrics/AbcSize:
|
|||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
IgnoredMethods: ['describe', 'context']
|
||||
AllowedMethods: ['describe', 'context']
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -73,7 +73,7 @@ gem 'i18n-js', '~> 3.6' # Localization in javascript files
|
|||
gem 'jbuilder' # JSON structures via a Builder-style DSL
|
||||
gem 'logging', '~> 2.0.0'
|
||||
gem 'nested_form_fields'
|
||||
gem 'nokogiri', '~> 1.13.6' # HTML/XML parser
|
||||
gem 'nokogiri', '~> 1.13.10' # HTML/XML parser
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
gem 'rgl' # Graph framework for project diagram calculations
|
||||
gem 'roo', '~> 2.8.2' # Spreadsheet parser
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -357,7 +357,7 @@ GEM
|
|||
logging (2.0.0)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
loofah (2.18.0)
|
||||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -384,7 +384,7 @@ GEM
|
|||
rails (>= 3.2.0)
|
||||
newrelic_rpm (6.15.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.7)
|
||||
nokogiri (1.13.10)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
oauth2 (1.4.4)
|
||||
|
@ -441,7 +441,7 @@ GEM
|
|||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.4)
|
||||
rack-attack (6.4.0)
|
||||
rack (>= 1.0, < 3)
|
||||
|
@ -473,8 +473,8 @@ GEM
|
|||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.3)
|
||||
loofah (~> 2.3)
|
||||
rails-html-sanitizer (1.4.4)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails_12factor (0.0.3)
|
||||
rails_serve_static_assets
|
||||
rails_stdout_logging
|
||||
|
@ -689,7 +689,7 @@ DEPENDENCIES
|
|||
momentjs-rails (~> 2.17.1)
|
||||
nested_form_fields
|
||||
newrelic_rpm
|
||||
nokogiri (~> 1.13.6)
|
||||
nokogiri (~> 1.13.10)
|
||||
omniauth
|
||||
omniauth-azure-activedirectory
|
||||
omniauth-linkedin-oauth2
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 43 KiB |
|
@ -56,8 +56,6 @@
|
|||
//= require_directory ./access_permissions
|
||||
//= require sidebar
|
||||
//= require turbolinks
|
||||
//= require 'BrowserPrint-3.0.216.min'
|
||||
//= require 'BrowserPrint-Zebra-1.0.216.min'
|
||||
|
||||
// Initialize links for submitting forms. This is useful for submitting
|
||||
// forms with clicking on links outside form in cases when other than
|
||||
|
@ -321,7 +319,7 @@ window.I18n = I18n
|
|||
|
||||
// Multiple modal support
|
||||
// https://stackoverflow.com/a/24914782
|
||||
$(document).on('show.bs.modal', '.modal', function() {
|
||||
$(document).on('shown.bs.modal', '.modal', function() {
|
||||
const zIndex = 1040 + 10 * $('.modal:visible').length;
|
||||
$(this).css('z-index', zIndex);
|
||||
setTimeout(() => $('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack'));
|
||||
|
|
|
@ -5,11 +5,18 @@ var DasboardRecentWorkWidget = (function() {
|
|||
function renderRecentWorkItem(data, container) {
|
||||
$.each(data, (i, item) => {
|
||||
var recentWorkItem = $($('#recent-work-item-template').html());
|
||||
var recentWorkItemType = recentWorkItem.find('.object-type span');
|
||||
recentWorkItem.attr('href', item.url);
|
||||
recentWorkItem.find('.object-name').html(item.name);
|
||||
recentWorkItem.find('.object-type').html(I18n.t('dashboard.recent_work.subject_type.' + item.subject_type));
|
||||
recentWorkItemType.html(item.code || item.type);
|
||||
recentWorkItem.find('.object-changed').html(item.last_change);
|
||||
container.append(recentWorkItem);
|
||||
|
||||
if (item.code) {
|
||||
recentWorkItemType.attr('data-toggle', 'tooltip');
|
||||
recentWorkItemType.attr('title', `${item.type} ID: ${item.code}`);
|
||||
recentWorkItemType.tooltip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global TinyMCE I18n animateSpinner importProtocolFromFile truncateLongString */
|
||||
/* global TinyMCE I18n animateSpinner importProtocolFromFile */
|
||||
/* global HelperModule GLOBAL_CONSTANTS */
|
||||
/* eslint-disable no-use-before-define, no-alert, no-restricted-globals, no-underscore-dangle */
|
||||
|
||||
|
@ -33,44 +33,6 @@ function initEditProtocolDescription() {
|
|||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
||||
// Initialize edit description modal window
|
||||
function initEditDescription() {
|
||||
var editDescriptionModal = $('#manage-module-description-modal');
|
||||
var editDescriptionModalBody = editDescriptionModal.find('.modal-body');
|
||||
$('.description-link')
|
||||
.on('ajax:success', function(ev, data) {
|
||||
var descriptionLink = $('.description-refresh');
|
||||
|
||||
// Set modal body & title
|
||||
editDescriptionModalBody.html(data.html);
|
||||
editDescriptionModal
|
||||
.find('#manage-module-description-modal-label')
|
||||
.text(data.title);
|
||||
|
||||
editDescriptionModalBody.find('form')
|
||||
.on('ajax:success', function(ev2, data2) {
|
||||
// Update module's description in the tab
|
||||
descriptionLink.html(data2.description_label);
|
||||
|
||||
// Close modal
|
||||
editDescriptionModal.modal('hide');
|
||||
})
|
||||
.on('ajax:error', function(ev2, data2) {
|
||||
// Display errors if needed
|
||||
$(this).renderFormErrors('my_module', data2.responseJSON);
|
||||
});
|
||||
|
||||
// Show modal
|
||||
editDescriptionModal.modal('show');
|
||||
});
|
||||
|
||||
|
||||
editDescriptionModal.on('hidden.bs.modal', function() {
|
||||
editDescriptionModalBody.find('form').off('ajax:success ajax:error');
|
||||
editDescriptionModalBody.html('');
|
||||
});
|
||||
}
|
||||
|
||||
function initCopyToRepository() {
|
||||
var link = "[data-action='copy-to-repository']";
|
||||
var modal = '#copy-to-repository-modal';
|
||||
|
@ -464,7 +426,6 @@ function initProtocolSectionOpenEvent() {
|
|||
function init() {
|
||||
initEditMyModuleDescription();
|
||||
initEditProtocolDescription();
|
||||
initEditDescription();
|
||||
initLinkUpdate();
|
||||
initLoadFromRepository();
|
||||
refreshProtocolStatusBar();
|
||||
|
|
|
@ -1131,7 +1131,7 @@ function updateModuleHtml(module, id, name, gridDistX, gridDistY) {
|
|||
|
||||
module.find(".panel-title").html(name);
|
||||
|
||||
module.find(".ep").html($("#drag-connections-placeholder").text().trim());
|
||||
module.find(".ep").html($("#drag-connections-placeholder").html());
|
||||
|
||||
// Add dropdown
|
||||
var dropdown = document.createElement("div");
|
||||
|
|
|
@ -519,7 +519,7 @@ var ProjectsIndex = (function() {
|
|||
|
||||
$(projectsPageSelector)
|
||||
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
|
||||
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
|
||||
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
|
||||
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
|
||||
$(ev.target).find('.button-to').addClass('selected');
|
||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
function initProjectsViewModeSwitch() {
|
||||
$(experimentsPage)
|
||||
.on('ajax:success', '.change-experiments-view-type-form', function(ev, data) {
|
||||
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
|
||||
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
|
||||
$(experimentsPage).find('.cards-switch .button-to').removeClass('selected');
|
||||
$(ev.target).find('.button-to').addClass('selected');
|
||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||
|
|
|
@ -53,7 +53,7 @@ function initProtocolsTable() {
|
|||
</div>`;
|
||||
}
|
||||
}, {
|
||||
targets: [ 1, 2, 3, 4, 5 ],
|
||||
targets: [ 1, 2, 3, 4, 5, 6 ],
|
||||
searchable: true,
|
||||
orderable: true
|
||||
}],
|
||||
|
@ -61,13 +61,14 @@ function initProtocolsTable() {
|
|||
{ data: "0" },
|
||||
{ data: "1" },
|
||||
{ data: "2" },
|
||||
{ data: "3" },
|
||||
{
|
||||
data: "3",
|
||||
data: "4",
|
||||
visible: repositoryType != "archive"
|
||||
},
|
||||
{ data: "4" },
|
||||
{ data: "5" },
|
||||
{ data: "6" }
|
||||
{ data: "6" },
|
||||
{ data: "7" }
|
||||
],
|
||||
oLanguage: {
|
||||
sSearch: I18n.t('general.filter')
|
||||
|
@ -78,11 +79,6 @@ function initProtocolsTable() {
|
|||
|
||||
$(row).attr("data-row-id", rowId);
|
||||
|
||||
// Append URLs if they exist
|
||||
if (data["DT_CanEdit"]) {
|
||||
$(row).attr("data-can-edit", "true");
|
||||
$(row).attr("data-edit-url", data["DT_EditUrl"]);
|
||||
}
|
||||
if (data["DT_CanClone"]) {
|
||||
$(row).attr("data-can-clone", "true");
|
||||
$(row).attr("data-clone-url", data["DT_CloneUrl"]);
|
||||
|
@ -384,7 +380,6 @@ function updateDataTableSelectAllCheckbox() {
|
|||
}
|
||||
|
||||
function updateButtons() {
|
||||
var editBtn = $("[data-action='edit']");
|
||||
var cloneBtn = $("[data-action='clone']");
|
||||
var makePrivateBtn = $("[data-action='make-private']");
|
||||
var publishBtn = $("[data-action='publish']");
|
||||
|
@ -396,13 +391,6 @@ function updateButtons() {
|
|||
|
||||
if (rowsSelected.length === 1) {
|
||||
// 1 ROW SELECTED
|
||||
if (row.is('[data-can-edit]')) {
|
||||
editBtn.removeClass('disabled hidden');
|
||||
editBtn.off('click').on('click', function() { editSelectedProtocol(); });
|
||||
} else {
|
||||
editBtn.removeClass('hidden').addClass('disabled');
|
||||
editBtn.off('click');
|
||||
}
|
||||
if (row.is('[data-can-clone]')) {
|
||||
cloneBtn.removeClass('disabled hidden');
|
||||
cloneBtn.off('click').on('click', function() { cloneSelectedProtocol(); });
|
||||
|
@ -447,8 +435,6 @@ function updateButtons() {
|
|||
}
|
||||
} else if (rowsSelected.length === 0) {
|
||||
// 0 ROWS SELECTED
|
||||
editBtn.addClass('disabled hidden');
|
||||
editBtn.off('click');
|
||||
cloneBtn.addClass('disabled hidden');
|
||||
cloneBtn.off('click');
|
||||
makePrivateBtn.addClass('disabled hidden');
|
||||
|
@ -470,8 +456,6 @@ function updateButtons() {
|
|||
|
||||
// Only enable button if all selected rows can
|
||||
// be published/archived/restored/exported
|
||||
editBtn.removeClass('hidden').addClass('disabled');
|
||||
editBtn.off('click');
|
||||
cloneBtn.removeClass('hidden').addClass('disabled');
|
||||
cloneBtn.off('click');
|
||||
if (!rows.is(':not([data-can-make-private])')) {
|
||||
|
@ -524,15 +508,6 @@ function exportProtocols(ids) {
|
|||
}
|
||||
}
|
||||
|
||||
function editSelectedProtocol() {
|
||||
if (rowsSelected.length && rowsSelected.length == 1) {
|
||||
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");
|
||||
|
||||
// Redirect to edit page
|
||||
$(location).attr("href", row.attr("data-edit-url"));
|
||||
}
|
||||
}
|
||||
|
||||
function cloneSelectedProtocol() {
|
||||
if (rowsSelected.length && rowsSelected.length == 1) {
|
||||
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");
|
||||
|
|
|
@ -3,216 +3,6 @@
|
|||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
// Sets callbacks for toggling checkboxes
|
||||
function applyCheckboxCallBack() {
|
||||
$("[data-action='check-item']").off().on('click', function (e) {
|
||||
var checkboxitem = $(this).find("input");
|
||||
if (checkboxitem.prop("disabled")) { return; }
|
||||
var checked = checkboxitem.is(":checked");
|
||||
$.ajax({
|
||||
url: checkboxitem.data("link-url"),
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {checklistitem_id: checkboxitem.data("id"), checked: checked},
|
||||
success: function (data) {
|
||||
checkboxitem.prop("checked", checked);
|
||||
},
|
||||
error: function (data) {
|
||||
checkboxitem.prop("checked", !checked);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sets callback for completing/uncompleting step
|
||||
function applyStepCompletedCallBack() {
|
||||
// First, remove old event handlers, as we use turbolinks
|
||||
$("[data-action='complete-step'], [data-action='uncomplete-step']")
|
||||
.off().on('click', function(e){
|
||||
var button = $(this);
|
||||
var step = $(this).parents(".step");
|
||||
var completed = !step.hasClass("completed");
|
||||
|
||||
$.ajax({
|
||||
url: button.data("link-url"),
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {completed: completed},
|
||||
success: function (data) {
|
||||
step.toggleClass("completed");
|
||||
step.find(".toggle-step-complete[data-action='complete-step']").toggleClass('hidden');
|
||||
step.find(".toggle-step-complete[data-action='uncomplete-step']").toggleClass('hidden');
|
||||
step.find(".button-step-complete[data-action='complete-step']").toggleClass('hidden');
|
||||
step.find(".button-step-complete[data-action='uncomplete-step']").toggleClass('hidden');
|
||||
},
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyCancelCallBack() {
|
||||
//Click on cancel button
|
||||
$("[data-action='cancel-edit']")
|
||||
.on("ajax:success", function(e, data) {
|
||||
var $form = $(this).closest("form");
|
||||
$form.after(data.html);
|
||||
var $new_step = $(this).next();
|
||||
$(this).remove();
|
||||
|
||||
initCallBacks();
|
||||
initHandsOnTable($new_step);
|
||||
toggleButtons(true);
|
||||
|
||||
setTimeout(function() {
|
||||
initStepsComments();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
TinyMCE.destroyAll();
|
||||
DragNDropSteps.clearFiles();
|
||||
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
.on("ajax:error", function(e, xhr, status, error) {
|
||||
// TODO: error handling
|
||||
});
|
||||
}
|
||||
|
||||
// Set callback for click on edit button
|
||||
function applyEditCallBack() {
|
||||
$("[data-action='edit-step']")
|
||||
.on("ajax:success", function(e, data) {
|
||||
var $step = $(this).closest(".step");
|
||||
var $edit_step = $step.after(data.html);
|
||||
var $form = $step.next();
|
||||
$step.remove();
|
||||
formCallback($form);
|
||||
initEditableHandsOnTable($form);
|
||||
applyCancelCallBack();
|
||||
formEditAjax($form);
|
||||
toggleButtons(false);
|
||||
initializeCheckboxSorting();
|
||||
animateSpinner(null, false);
|
||||
DragNDropSteps.clearFiles();
|
||||
if (tinyMCE.editors.step_description_textarea) tinyMCE.editors.step_description_textarea.remove();
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
$form.on('change', '.checklist-item-text, .checklist-name', function() {
|
||||
$form.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
|
||||
});
|
||||
$("#new-step-checklists fieldset.nested_step_checklists ul").each(function () {
|
||||
enableCheckboxSorting(this);
|
||||
});
|
||||
$("#step_name").focus();
|
||||
$("#new-step-main-tab a").on("shown.bs.tab", function() {
|
||||
$("#step_name").focus();
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
});
|
||||
})
|
||||
.on("ajax:error", function(e, response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set callback for click on move step
|
||||
function applyMoveStepCallBack() {
|
||||
$('#steps').on('ajax:success', "[data-action='move-step']", function(e, data) {
|
||||
if ($.isEmptyObject(data)) return;
|
||||
|
||||
let $step = $(this).closest('.step');
|
||||
let steps = $('#steps').find('.step');
|
||||
$('#steps').append($.map(data.steps_order, function(step_data) {
|
||||
let step = $('#steps').find(`.step[data-id=${step_data.id}]`);
|
||||
step.find('.step-number').html(`${step_data.position + 1}.`);
|
||||
return step;
|
||||
}));
|
||||
|
||||
$('html, body').animate({ scrollTop: $step.offset().top - window.innerHeight / 2 });
|
||||
|
||||
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
|
||||
})
|
||||
.on("ajax:error", function(e, xhr) {
|
||||
if (xhr.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formCallback($form) {
|
||||
$form
|
||||
.on("fields_added.nested_form_fields", function(e, param) {
|
||||
if (param.object_class == "table") {
|
||||
initEditableHandsOnTable($form);
|
||||
}
|
||||
})
|
||||
.on("fields_removed.nested_form_fields", function(e, param) {
|
||||
if (param.object_class == "asset") {
|
||||
// Clear file input
|
||||
$(e.target).find("input[type='file']").val("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Init ajax success/error for edit form
|
||||
function formEditAjax($form) {
|
||||
$form
|
||||
.on("ajax:beforeSend", function () {
|
||||
$(".nested_step_checklists ul").each(function () {
|
||||
reorderCheckboxData(this);
|
||||
});
|
||||
})
|
||||
.on("ajax:success", function(e, data) {
|
||||
$(this).after(data.html);
|
||||
var $new_step = $(this).next();
|
||||
$(this).remove();
|
||||
|
||||
initCallBacks();
|
||||
initHandsOnTable($new_step);
|
||||
toggleButtons(true);
|
||||
TinyMCE.destroyAll();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
// Show the edited step
|
||||
$new_step.find(".panel-collapse:first").addClass("collapse in");
|
||||
|
||||
//Rerender tables
|
||||
$new_step.find("[data-role='step-hot-table']").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
})
|
||||
.on("ajax:error", function(e, xhr, status, error) {
|
||||
$form.renderFormErrors('step', xhr.responseJSON, true, e);
|
||||
|
||||
formCallback($form);
|
||||
initEditableHandsOnTable($form);
|
||||
applyCancelCallBack();
|
||||
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
|
||||
//Rerender tables
|
||||
$form.find("[data-role='step-hot-table']").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleButtons(show) {
|
||||
if (show) {
|
||||
$("[data-action='new-step']").show();
|
||||
$("[data-action='edit-step']").show();
|
||||
if ($('#steps .step').length > 0) {
|
||||
$(".expand-all-steps").show();
|
||||
}
|
||||
} else {
|
||||
$("[data-action='new-step']").hide();
|
||||
$("[data-action='edit-step']").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Creates handsontable where needed
|
||||
function initHandsOnTable(root) {
|
||||
root.find("[data-role='hot-table']").each(function() {
|
||||
|
@ -249,383 +39,16 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Init handsontable which can be edited
|
||||
function initEditableHandsOnTable(root) {
|
||||
root.find("[data-role='editable-table']").each(function() {
|
||||
var $container = $(this).find(".hot");
|
||||
// On init
|
||||
|
||||
if ($container.is("[initialized]")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var contents = $(this).find('.hot-contents');
|
||||
var data = null;
|
||||
if (contents.attr("value")) {
|
||||
data = JSON.parse(contents.attr("value")).data;
|
||||
}
|
||||
if ($container.is(":visible")) {
|
||||
$(this).css("width", $("#new-step-tables").css("width"));
|
||||
}
|
||||
|
||||
$container.handsontable({
|
||||
data: data,
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
minRows: 1,
|
||||
minCols: 1,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
contextMenu: true,
|
||||
formulas: true,
|
||||
preventOverflow: 'horizontal',
|
||||
afterChange: function() {
|
||||
$container.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
|
||||
}
|
||||
});
|
||||
|
||||
$container.attr("initialized", true);
|
||||
renderTable($container);
|
||||
});
|
||||
|
||||
$("#new-step-tables-tab a")
|
||||
.on("shown.bs.tab", function() {
|
||||
$(this).parents("form").find("div.hot").each(function() {
|
||||
$(this).parent().css("width", $("#new-step-tables").css("width"));
|
||||
renderTable($(this));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyCancelOnNew() {
|
||||
$("[data-action='cancel-new']").click(function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
var $form = $(this).closest("form");
|
||||
$form.parent().remove().promise().done(function() {
|
||||
newStepHandler();
|
||||
});
|
||||
toggleButtons(true);
|
||||
DragNDropSteps.clearFiles();
|
||||
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteStep(){
|
||||
$("[data-action='delete-step']").on("confirm:complete", function (e, answer) {
|
||||
if (answer) {
|
||||
animateLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCallBacks() {
|
||||
if (typeof(applyCreateWopiFileCallback) === 'function') applyCreateWopiFileCallback();
|
||||
applyCheckboxCallBack();
|
||||
applyStepCompletedCallBack();
|
||||
applyEditCallBack();
|
||||
applyMoveStepCallBack();
|
||||
initDeleteStep();
|
||||
TinyMCE.highlight();
|
||||
}
|
||||
|
||||
/*
|
||||
* Correction for sorting with "Sortable.min" JS library to work correctly with
|
||||
* "nested_form_fields" gem.
|
||||
*/
|
||||
function reorderCheckboxData(checkboxUl) {
|
||||
// Make sure checkbox item insertion script is always at the bottom of "ul"
|
||||
// tag, otherwise item will not be inserted at bottom
|
||||
if(!$(checkboxUl).children().last().is('script')) {
|
||||
$(checkboxUl).find("script").appendTo(checkboxUl);
|
||||
}
|
||||
|
||||
var $checkboxes = $(checkboxUl).find(".nested_fields");
|
||||
$checkboxes.each(function (itemPos) {
|
||||
var $this = $(this);
|
||||
var $formGroup = $this.find(".form-group");
|
||||
var $label = $formGroup.find(".control-label");
|
||||
var $textInput = $formGroup.find(".checklist-item-text");
|
||||
var $posInput = $formGroup.parent().find(".checklist-item-pos");
|
||||
var $destroyLink = $this.find(".remove_nested_fields_link");
|
||||
|
||||
var labelFor = $label.attr("for");
|
||||
var textName = $textInput.attr("name");
|
||||
var textId = $textInput.attr("id");
|
||||
var posName = $posInput.attr("name");
|
||||
var posId = $posInput.attr("id");
|
||||
var destroyLink = $destroyLink.attr("data-delete-association-field-name");
|
||||
|
||||
labelFor = labelFor.replace(/\d+_text/, itemPos + "_text");
|
||||
textName = textName.replace(/\[\d+\]\[text\]/, "[" + itemPos + "][text]");
|
||||
textId = textId.replace(/\d+_text/, itemPos + "_text");
|
||||
posName = posName.replace(/\[\d+\]\[position\]/, "[" + itemPos + "][position]");
|
||||
posId = posId.replace(/\d+_position/, itemPos + "_position");
|
||||
destroyLink = destroyLink.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]");
|
||||
|
||||
$label.attr("for", labelFor);
|
||||
$textInput.attr("name", textName); // Actually needed for sorting to work
|
||||
$textInput.attr("id", textId);
|
||||
$posInput.attr("name", posName);
|
||||
$posInput.attr("id", posId);
|
||||
$posInput.val(itemPos);
|
||||
$destroyLink.attr("data-delete-association-field-name", destroyLink);
|
||||
|
||||
var $idInput = $this.find("> input");
|
||||
if ($idInput.length) {
|
||||
var idName = $idInput.attr("name");
|
||||
var idId = $idInput.attr("id");
|
||||
|
||||
idName = idName.replace(/\[\d+\]\[id\]/, "[" + itemPos + "][id]");
|
||||
idId = idId.replace(/\d+_id/, itemPos + "_id");
|
||||
|
||||
$idInput.attr("name", idName);
|
||||
$idInput.attr("id", idId);
|
||||
}
|
||||
|
||||
if ($this.css('display') == 'none') {
|
||||
// Actually needed for deleting to work
|
||||
var $destroyInput = $this.prev();
|
||||
var destroyName = $destroyInput.attr("name");
|
||||
destroyName = destroyName.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]");
|
||||
$destroyInput.attr("name", destroyName);
|
||||
}
|
||||
|
||||
$formGroup.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
|
||||
});
|
||||
}
|
||||
|
||||
function enableCheckboxSorting(el) {
|
||||
Sortable.create(el, {
|
||||
draggable: 'fieldset',
|
||||
handle: '.fa-circle',
|
||||
onUpdate: function () {
|
||||
reorderCheckboxData(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeCheckboxSorting() {
|
||||
var el = $("#new-step-checklists a[data-association-path=step_checklists]");
|
||||
|
||||
el.click(function () {
|
||||
// calling code below must be defered because at this step HTML is not
|
||||
// inserted into DOM.
|
||||
setTimeout(function () {
|
||||
var list = el.parent().find("fieldset.nested_step_checklists:last ul");
|
||||
enableCheckboxSorting(list.get(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// New step AJAX
|
||||
function newStepHandler() {
|
||||
$("[data-action='new-step']").off().on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
var $btn = $(this);
|
||||
$btn.off();
|
||||
animateSpinner(null, true);
|
||||
$('#protocol-container').collapse('show');
|
||||
|
||||
$.ajax({
|
||||
url: $btn.data('href'),
|
||||
method: 'GET',
|
||||
success: function(data) {
|
||||
var $form = $(data.html);
|
||||
$('#steps').append($form).promise().done(function() {
|
||||
animateSpinner(null, false);
|
||||
// Scroll to bottom
|
||||
$('html, body').animate({
|
||||
scrollTop: $(document).height() - $(window).height()
|
||||
});
|
||||
formCallback($form);
|
||||
applyCancelOnNew();
|
||||
toggleButtons(false);
|
||||
initializeCheckboxSorting();
|
||||
|
||||
$('#step_name').focus();
|
||||
$('#new-step-main-tab a')
|
||||
.on('shown.bs.tab', function() {
|
||||
$('#step_name').focus();
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
})
|
||||
|
||||
TinyMCE.init('#step_description_textarea');
|
||||
});
|
||||
|
||||
},
|
||||
error: function(response) {
|
||||
if (response.status === 403) {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
animateSpinner(null, false);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Needed because server-side validation failure clears locations of
|
||||
// files to be uploaded and checklist's items etc. Also user
|
||||
// experience is improved
|
||||
global.processStep = function processStep(ev, editMode) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
|
||||
var $form = $(ev.target.form);
|
||||
$form.clearFormErrors();
|
||||
$form.removeBlankFileForms();
|
||||
|
||||
var $checklists = $form.find(".nested_step_checklists");
|
||||
var checklistsValid = checklistsValidator(ev, $checklists, editMode);
|
||||
var $nameInput = $form.find("#step_name");
|
||||
var nameValid = textValidator(ev, $nameInput, 1,
|
||||
<%= Constants::NAME_MAX_LENGTH %>);
|
||||
var $descrTextarea = $form.find("#step_description_textarea");
|
||||
var $tinyMCEInput = tinyMCE.editors.step_description_textarea.getContent();
|
||||
var descriptionValid = textValidator(ev, $descrTextarea, 0,
|
||||
<%= Constants::RICH_TEXT_MAX_LENGTH %>, false, $tinyMCEInput);
|
||||
var tableNamesValidArray = [];
|
||||
// iterate over table titles and validate their length
|
||||
$form.find(".table_name").each(function(index , tableName) {
|
||||
if (tableName.value.length > 0) {
|
||||
tableNamesValidArray[index] = textValidator(ev, tableName, 1,
|
||||
<%= Constants::NAME_MAX_LENGTH %> );
|
||||
} else {
|
||||
tableNamesValidArray[index] = true;
|
||||
}
|
||||
});
|
||||
var tableNamesValid = true;
|
||||
// passes if all table names are valid
|
||||
for (var i=0;i<tableNamesValidArray.length;i++){
|
||||
if(tableNamesValidArray[i] !== true){
|
||||
tableNamesValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DragNDropSteps.filesStatus() &&
|
||||
checklistsValid &&
|
||||
nameValid &&
|
||||
descriptionValid &&
|
||||
tableNamesValid ) {
|
||||
|
||||
$form.find("[data-role='editable-table']").each(function() {
|
||||
let hot = $(this).find(".hot").handsontable('getInstance');
|
||||
let contents = $(this).find('.hot-contents');
|
||||
let tableData = JSON.stringify({data: hot.getData()});
|
||||
contents.attr("value", tableData);
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
initStepsComments();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
}, 1000);
|
||||
|
||||
animateSpinner(null, true);
|
||||
|
||||
$(".nested_step_checklists ul").each(function () {
|
||||
reorderCheckboxData(this);
|
||||
});
|
||||
|
||||
DragNDropSteps.appendFilesToForm(ev).then(formData => {
|
||||
formData.append('step[description]', $tinyMCEInput);
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
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');
|
||||
$new_step.find('.attachments').trigger('reorder');
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
|
||||
PdfPreview.initCanvas();
|
||||
|
||||
//Rerender tables
|
||||
$new_step.find("div.step-result-hot-table").each(function() {
|
||||
$(this).handsontable("render");
|
||||
});
|
||||
animateSpinner(null, false);
|
||||
DragNDropSteps.clearFiles();
|
||||
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('<span class="help-block">' + value + '</span>');
|
||||
});
|
||||
}
|
||||
animateSpinner(null, false);
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
}
|
||||
});
|
||||
newStepHandler();
|
||||
}).catch(() => {
|
||||
$('#new-step-assets-tab').addClass('has-error');
|
||||
$('#new-step-assets-group')
|
||||
.addClass('has-error')
|
||||
.prepend('<span class="help-block">' + I18n.t('general.file.upload_failure') + '</span>');
|
||||
animateSpinner(null, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Expand all steps
|
||||
function expandAllSteps() {
|
||||
$('.step .panel-collapse').collapse('show');
|
||||
$(document).find("[data-role='step-hot-table']").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
}
|
||||
|
||||
function expandStep(step) {
|
||||
$('.panel-collapse', step).collapse('show');
|
||||
$(step).find("div.step-result-hot-table").each(function() {
|
||||
renderTable($(this));
|
||||
});
|
||||
}
|
||||
|
||||
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%' });
|
||||
}
|
||||
}
|
||||
|
||||
function initStepsComments() {
|
||||
Comments.init();
|
||||
}
|
||||
|
||||
// On init
|
||||
initCallBacks();
|
||||
initHandsOnTable($(document));
|
||||
expandAllSteps();
|
||||
TinyMCE.highlight();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
newStepHandler();
|
||||
|
||||
$(function () {
|
||||
$("[data-action='collapse-steps']").click(function () {
|
||||
$('.step .panel-collapse').collapse('hide');
|
||||
});
|
||||
$("[data-action='expand-steps']").click(expandAllSteps);
|
||||
});
|
||||
|
||||
global.initHandsOnTable = initHandsOnTable;
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
if (data.archived) {
|
||||
$(row).addClass('archived');
|
||||
}
|
||||
if (data['3'].processing || data['4'].processing) {
|
||||
if (data['4'].processing || data['5'].processing) {
|
||||
$(row).addClass('processing');
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +225,7 @@
|
|||
var $table = $('#reports-table');
|
||||
REPORTS_TABLE = $table.DataTable({
|
||||
dom: "Rt<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
|
||||
order: [[8, 'desc']],
|
||||
order: [[9, 'desc']],
|
||||
sScrollX: '100%',
|
||||
sScrollXInner: '100%',
|
||||
processing: true,
|
||||
|
@ -244,13 +244,13 @@
|
|||
render: renderCheckboxHTML
|
||||
},
|
||||
{
|
||||
targets: 3,
|
||||
targets: 4,
|
||||
searchable: false,
|
||||
sWidth: '60',
|
||||
render: renderPdfFile
|
||||
},
|
||||
{
|
||||
targets: 4,
|
||||
targets: 5,
|
||||
searchable: false,
|
||||
sWidth: '60',
|
||||
render: renderDocxFile
|
||||
|
|
|
@ -32,12 +32,13 @@ var AssetColumnHelper = (function() {
|
|||
} else {
|
||||
$el
|
||||
.prev('.file-hidden-field-container')
|
||||
.html(`<input type="hidden"
|
||||
form="${$el.attr('form')}"
|
||||
name="repository_cells[${$el.data('col-id')}]"
|
||||
.html(`<input type="hidden"
|
||||
form="${$el.attr('form')}"
|
||||
name="repository_cells[${$el.data('col-id')}]"
|
||||
value="${blob.signed_id}"/>`);
|
||||
|
||||
filesUploadedCntr += 1;
|
||||
|
||||
if (filesUploadedCntr === filesToUploadCntr) {
|
||||
resolve('done');
|
||||
}
|
||||
|
@ -52,12 +53,13 @@ var AssetColumnHelper = (function() {
|
|||
let empty = $cell.is(':empty');
|
||||
let fileName = $cell.find('a.file-preview-link').text();
|
||||
let placeholder = I18n.t('repositories.table.assets.select_file_btn', { max_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB });
|
||||
let rowId = $cell.parent().attr('id');
|
||||
|
||||
$cell.html(`
|
||||
<div class="file-editing">
|
||||
<div class="file-hidden-field-container hidden"></div>
|
||||
<input class=""
|
||||
id="repository_file_${columnId}"
|
||||
id="repository_file_${columnId}_${rowId}"
|
||||
form="${formId}"
|
||||
type="file"
|
||||
data-col-id="${columnId}"
|
||||
|
@ -66,7 +68,7 @@ var AssetColumnHelper = (function() {
|
|||
data-type="RepositoryAssetValue">
|
||||
<div class="file-upload-button ${empty ? 'new-file' : ''}">
|
||||
<i class="fas fa-paperclip icon"></i>
|
||||
<label data-placeholder="${placeholder}" for="repository_file_${columnId}">${fileName}</label>
|
||||
<label data-placeholder="${placeholder}" for="repository_file_${columnId}_${rowId}">${fileName}</label>
|
||||
<span class="delete-action fas fa-trash"> </span>
|
||||
</div>
|
||||
</div>`);
|
||||
|
|
|
@ -38,7 +38,7 @@ var ChecklistColumnHelper = (function() {
|
|||
}
|
||||
|
||||
function initialChecklistEditMode(formId, columnId, cell, values) {
|
||||
var select = 'checklist-' + columnId;
|
||||
var select = `checklist-${columnId}-${cell.parent()[0].id}`;
|
||||
var checklistUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = checklistSelect(select, checklistUrl, values);
|
||||
var $hiddenField = checklistHiddenField(formId, columnId, values);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
var ListColumnHelper = (function() {
|
||||
function listSelect(select, url, value) {
|
||||
var selectedOption = '';
|
||||
var selectObject = $(`<select id="${select}"
|
||||
var selectObject = $(`<select id="${select}"
|
||||
data-placeholder = "${I18n.t('repositories.table.list.select_item')}"
|
||||
data-ajax-url = "${url}" >${selectedOption}</select>`);
|
||||
|
||||
|
@ -26,7 +26,7 @@ var ListColumnHelper = (function() {
|
|||
}
|
||||
|
||||
function initialListEditMode(formId, columnId, cell, value = null) {
|
||||
var select = 'list-' + columnId;
|
||||
var select = `list-${columnId}-${cell.parent()[0].id}`;
|
||||
var listUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = listSelect(select, listUrl, value);
|
||||
var $hiddenField = listHiddenField(formId, columnId, value);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
var StatusColumnHelper = (function() {
|
||||
function statusSelect(select, url, value) {
|
||||
var selectedOption = '';
|
||||
var selectObject = $(`<select id="${select}"
|
||||
var selectObject = $(`<select id="${select}"
|
||||
data-placeholder = "${I18n.t('repositories.table.status.set_status')}"
|
||||
data-ajax-url = "${url}" ></select>`);
|
||||
|
||||
|
@ -26,7 +26,7 @@ var StatusColumnHelper = (function() {
|
|||
}
|
||||
|
||||
function initialStatusEditMode(formId, columnId, cell, value = null) {
|
||||
var select = 'status-list-' + columnId;
|
||||
var select = `status-list-${columnId}-${cell.parent()[0].id}`;
|
||||
var listUrl = $('.repository-column#' + columnId).data('items-url');
|
||||
var $select = statusSelect(select, listUrl, value);
|
||||
var $hiddenField = statusHiddenField(formId, columnId, value);
|
||||
|
|
|
@ -40,7 +40,7 @@ $.fn.dataTable.render.editRepositoryTextValue = function(formId, columnId, cell)
|
|||
data-type="RepositoryTextValue">
|
||||
</div>`);
|
||||
$cell.find('input').val(text);
|
||||
SmartAnnotation.init($cell.find('input'));
|
||||
SmartAnnotation.init($cell.find('input'), true);
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.editRepositoryListValue = function(formId, columnId, cell) {
|
||||
|
|
|
@ -42,12 +42,24 @@ var RepositoryDatatable = (function(global) {
|
|||
return value;
|
||||
});
|
||||
|
||||
function allSelectedRowsAreOnPage() {
|
||||
let visibleRowIds = $(
|
||||
`#repository-table-${$(TABLE_ID).data('repository-id')} tbody tr`
|
||||
).toArray().map((r) => parseInt(r.id, 10));
|
||||
|
||||
return rowsSelected.every(r => visibleRowIds.includes(r));
|
||||
}
|
||||
|
||||
// Enable/disable edit button
|
||||
function updateButtons() {
|
||||
if (currentMode === 'viewMode') {
|
||||
$(TABLE_WRAPPER_ID).removeClass('editing');
|
||||
$('.repository-save-changes-link').off('click');
|
||||
$('.repository-edit-overlay').hide();
|
||||
$('#saveCancel').hide();
|
||||
$('.manage-repo-column-index').prop('disabled', false);
|
||||
$('#shareRepoBtn').removeClass('disabled');
|
||||
$('#viewSwitchButton').removeClass('disabled');
|
||||
$('#addRepositoryRecord').prop('disabled', false);
|
||||
$('.dataTables_length select').prop('disabled', false);
|
||||
$('#repository-acitons-dropdown').prop('disabled', false);
|
||||
|
@ -71,11 +83,7 @@ var RepositoryDatatable = (function(global) {
|
|||
$('#editDeleteCopy').hide();
|
||||
$('#toolbarPrintLabel').hide();
|
||||
} else {
|
||||
if (rowsSelected.length === 1) {
|
||||
$('#editRepositoryRecord').prop('disabled', false);
|
||||
} else {
|
||||
$('#editRepositoryRecord').prop('disabled', true);
|
||||
}
|
||||
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
|
||||
$('#exportRepositoriesButton').removeClass('disabled');
|
||||
$('#archiveRepositoryRecordsButton').prop('disabled', false);
|
||||
$('#copyRepositoryRecords').prop('disabled', false);
|
||||
|
@ -90,8 +98,13 @@ var RepositoryDatatable = (function(global) {
|
|||
$('#editDeleteCopy').show();
|
||||
$('#toolbarPrintLabel').show();
|
||||
}
|
||||
$('#team-switch').css({ 'pointer-events': 'auto', opacity: 1 });
|
||||
$('#navigationGoBtn').prop('disabled', false);
|
||||
} else if (currentMode === 'editMode') {
|
||||
$(TABLE_WRAPPER_ID).addClass('editing');
|
||||
$('.repository-save-changes-link').on('click', function() {
|
||||
$('#saveRecord').click();
|
||||
});
|
||||
$('#importRecordsButton').hide();
|
||||
$('#editDeleteCopy').hide();
|
||||
$('#addRepositoryRecord').hide();
|
||||
|
@ -101,6 +114,8 @@ var RepositoryDatatable = (function(global) {
|
|||
}
|
||||
$('#saveCancel').show();
|
||||
$('.manage-repo-column-index').prop('disabled', true);
|
||||
$('#shareRepoBtn').addClass('disabled');
|
||||
$('#viewSwitchButton').addClass('disabled');
|
||||
$('#repository-acitons-dropdown').prop('disabled', true);
|
||||
$('.dataTables_length select').prop('disabled', true);
|
||||
$('#addRepositoryRecord').prop('disabled', true);
|
||||
|
@ -113,6 +128,9 @@ var RepositoryDatatable = (function(global) {
|
|||
$('.repository-row-selector').prop('disabled', true);
|
||||
$('.dataTables_filter input').prop('disabled', true);
|
||||
$('#toolbarPrintLabel').hide();
|
||||
$('.repository-edit-overlay').show();
|
||||
$('#team-switch').css({ 'pointer-events': 'none', opacity: 0.6 });
|
||||
$('#navigationGoBtn').prop('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,10 +158,8 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
function changeToEditMode() {
|
||||
currentMode = 'editMode';
|
||||
// Table specific stuff
|
||||
TABLE.button(0).enable(false);
|
||||
|
||||
clearRowSelection();
|
||||
$(TABLE_WRAPPER_ID).find('tr:not(.editing)').addClass('blocked');
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
|
@ -259,9 +275,8 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
checkAvailableColumns();
|
||||
|
||||
$(TABLE_ID).find('.repository-row-edit-icon').remove();
|
||||
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(row);
|
||||
|
||||
changeToEditMode();
|
||||
});
|
||||
}
|
||||
|
@ -562,6 +577,10 @@ var RepositoryDatatable = (function(global) {
|
|||
// Show number of selected rows near pages info
|
||||
$('#repository-table_info').append('<span id="selected_info"></span>');
|
||||
$('#selected_info').html(' (' + rowsSelected.length + ' entries selected)');
|
||||
|
||||
// Hide edit button if not all selected rows are on the current page
|
||||
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
|
||||
|
||||
if ($('.repository-show').hasClass('archived')) {
|
||||
TABLE.columns([6, 7]).visible(true);
|
||||
}
|
||||
|
@ -717,6 +736,7 @@ var RepositoryDatatable = (function(global) {
|
|||
checkAvailableColumns();
|
||||
RepositoryDatatableRowEditor.addNewRow(TABLE);
|
||||
changeToEditMode();
|
||||
$('.tooltip').remove();
|
||||
})
|
||||
.on('click', '#copyRepositoryRecords', function() {
|
||||
animateSpinner();
|
||||
|
@ -795,15 +815,12 @@ var RepositoryDatatable = (function(global) {
|
|||
.on('click', '#editRepositoryRecord', function() {
|
||||
checkAvailableColumns();
|
||||
|
||||
if (rowsSelected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let row = TABLE.row('#' + rowsSelected[0]);
|
||||
|
||||
$(TABLE_ID).find('.repository-row-edit-icon').remove();
|
||||
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(row);
|
||||
rowsSelected.forEach(function(rowNumber) {
|
||||
RepositoryDatatableRowEditor.switchRowToEditMode(TABLE.row('#' + rowNumber));
|
||||
});
|
||||
|
||||
changeToEditMode();
|
||||
adjustTableHeader();
|
||||
})
|
||||
|
@ -825,6 +842,7 @@ var RepositoryDatatable = (function(global) {
|
|||
success: function() {
|
||||
$('#hideRepositoryReminders').remove();
|
||||
TABLE.ajax.reload();
|
||||
$('.tooltip').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ var RepositoryDatatableRowEditor = (function() {
|
|||
function initSmartAnnotation($row) {
|
||||
$row.find('[data-object="repository_cell"]').each(function(el) {
|
||||
if (el.data('atwho')) {
|
||||
SmartAnnotation.init(el);
|
||||
SmartAnnotation.init(el, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ var RepositoryDatatableRowEditor = (function() {
|
|||
let $row = $form.closest('tr');
|
||||
let valid = true;
|
||||
let directUrl = $table.data('direct-upload-url');
|
||||
let $files = $row.find('input[type=file]');
|
||||
let $files = $table.find('input[type=file]');
|
||||
$row.find('.has-error').removeClass('has-error').find('span').remove();
|
||||
|
||||
// Validations here
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
_formAjaxResultAsset($form);
|
||||
Results.initCancelFormButton($form, initNewResultAsset);
|
||||
Results.toggleResultEditButtons(false);
|
||||
dragNdropAssetsInit('results');
|
||||
dragNdropAssetsInit();
|
||||
},
|
||||
error: function(xhr, status, e) {
|
||||
$(this).renderFormErrors('result', xhr.responseJSON, true, e);
|
||||
|
|
140
app/assets/javascripts/session_end.js
Normal file
140
app/assets/javascripts/session_end.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/* globals I18n */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var expireOn;
|
||||
var timeoutID;
|
||||
const oneSecondTimeout = 1000;
|
||||
const expireLimit = 900000; // 15min in miliseconds
|
||||
|
||||
function padTimeStr(i) {
|
||||
var s = ('0' + Math.floor(i));
|
||||
return s.substring(s.length - 2);
|
||||
}
|
||||
|
||||
function newTimerStr(expirationTime) {
|
||||
var m = (expirationTime / 60) % 60;
|
||||
var s = (expirationTime % 60);
|
||||
return [m, s].map(padTimeStr).join(':');
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
var dateNow = new Date();
|
||||
return dateNow.getTime();
|
||||
}
|
||||
|
||||
function sessionExpireIn() {
|
||||
expireOn = window.localStorage.getItem('sessionEnd');
|
||||
return expireOn - getCurrentTime();
|
||||
}
|
||||
|
||||
function setSessionTimeout(functionCallback, timeoutTime) {
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
timeoutID = setTimeout(functionCallback, timeoutTime);
|
||||
}
|
||||
|
||||
function toogleDocumentTitle(timeString = null) {
|
||||
var sleepEmoticon = String.fromCodePoint(0x1F62A);
|
||||
var originalTitle = document.title.split(sleepEmoticon).pop().trim();
|
||||
|
||||
if (timeString) {
|
||||
document.title = timeString + ' ' + sleepEmoticon + ' ' + originalTitle;
|
||||
} else {
|
||||
document.title = originalTitle;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeSessionCountdown() {
|
||||
var expirationUrl = $('meta[name=\'expiration-url\']').attr('content');
|
||||
var expireIn;
|
||||
if (expirationUrl) {
|
||||
$.get(expirationUrl, function(data) {
|
||||
expireOn = Math.max(window.localStorage.getItem('sessionEnd'), getCurrentTime() + parseInt(data, 10));
|
||||
window.localStorage.setItem('sessionEnd', expireOn);
|
||||
expireIn = sessionExpireIn();
|
||||
|
||||
if (expireIn <= 0) {
|
||||
$('#session-finished').modal();
|
||||
} else if (expireIn <= expireLimit + oneSecondTimeout) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
setSessionTimeout(sessionCountdown, oneSecondTimeout);
|
||||
} else {
|
||||
setSessionTimeout(initializeSessionCountdown, (expireIn - expireLimit));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reviveSession() {
|
||||
$.post($('meta[name=\'revive-url\']').attr('content'));
|
||||
toogleDocumentTitle();
|
||||
window.localStorage.removeItem('sessionEnd');
|
||||
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
|
||||
}
|
||||
|
||||
function initializeSessionReviveCallbacks() {
|
||||
$('#session-expire').modal().off('hide.bs.modal').on('hide.bs.modal', function() {
|
||||
if (sessionExpireIn() > 0) {
|
||||
reviveSession();
|
||||
}
|
||||
});
|
||||
|
||||
// for manual page reload
|
||||
$(window).off('beforeunload').on('beforeunload', function() {
|
||||
if (sessionExpireIn() > 0) {
|
||||
reviveSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sessionCountdown() {
|
||||
var timeString;
|
||||
var expireIn = sessionExpireIn();
|
||||
|
||||
if (!expireOn) {
|
||||
initializeSessionCountdown();
|
||||
} else if (expireIn > 0 && expireIn <= expireLimit) {
|
||||
timeString = newTimerStr(expireIn / 1000);
|
||||
toogleDocumentTitle(timeString);
|
||||
$('.expiring').text(I18n.t('devise.sessions.expire_modal.session_end_in.header', { time: timeString }));
|
||||
|
||||
if (!$('#session-expire').hasClass('in')) {
|
||||
initializeSessionReviveCallbacks();
|
||||
}
|
||||
|
||||
setSessionTimeout(sessionCountdown, oneSecondTimeout);
|
||||
} else if (expireIn <= 0) {
|
||||
toogleDocumentTitle();
|
||||
$('#session-expire').modal('hide');
|
||||
$('#session-finished').modal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window.localStorage.removeItem('sessionEnd');
|
||||
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
|
||||
|
||||
$(document).on('click', '.session-login', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
$(window).on('storage', (event) => {
|
||||
if (event.originalEvent.storageArea !== localStorage) return;
|
||||
if (event.originalEvent.key === 'sessionEnd') {
|
||||
if (!expireOn && event.originalEvent.newValue) {
|
||||
if ($('#session-finished').hasClass('in')) {
|
||||
$('#session-finished').modal('hide');
|
||||
}
|
||||
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
|
||||
} else if (expireOn && !event.originalEvent.newValue) {
|
||||
toogleDocumentTitle();
|
||||
}
|
||||
|
||||
expireOn = event.originalEvent.newValue;
|
||||
$('#session-expire').modal().off('hide.bs.modal').modal('hide');
|
||||
}
|
||||
});
|
||||
}());
|
|
@ -11,7 +11,7 @@ var SmartAnnotation = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
function SetAtWho(field) {
|
||||
function SetAtWho(field, deferred) {
|
||||
var FilterTypeEnum = Object.freeze({
|
||||
USER: { tag: 'users', dataUrl: $(document.body).attr('data-atwho-users-url') },
|
||||
TASK: { tag: 'sa-tasks', dataUrl: $(document.body).attr('data-atwho-task-url') },
|
||||
|
@ -121,87 +121,96 @@ var SmartAnnotation = (function() {
|
|||
};
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(field)
|
||||
.on('shown.atwho', function() {
|
||||
var $currentAtWho = $('.atwho-view[style]:not(.old)');
|
||||
var atWhoId = $currentAtWho.find('.atwho-header-res').data('at-who-key');
|
||||
$currentAtWho.addClass('old').attr('data-at-who-id', atWhoId);
|
||||
$(field).attr('data-smart-annotation', atWhoId);
|
||||
function initAtwho() {
|
||||
if ($(this).data('atwho-initialized')) return;
|
||||
|
||||
$currentAtWho.find('.tab-button').off().on('shown.bs.tab', function() {
|
||||
$(field).click().focus();
|
||||
$(this).closest('.nav-tabs').find('.tab-button').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
$currentAtWho.find('.repository-object').off().on('click', function() {
|
||||
$(this).parent().find('.repository-object').removeClass('btn-primary')
|
||||
.addClass('btn-light');
|
||||
$(this).addClass('btn-primary').removeClass('btn-light');
|
||||
$(field).click().focus();
|
||||
});
|
||||
$(field).on('shown.atwho', function() {
|
||||
var $currentAtWho = $('.atwho-view[style]:not(.old)');
|
||||
var atWhoId = $currentAtWho.find('.atwho-header-res').data('at-who-key');
|
||||
$currentAtWho.addClass('old').attr('data-at-who-id', atWhoId);
|
||||
$(field).attr('data-smart-annotation', atWhoId);
|
||||
|
||||
if ($currentAtWho.find('.tab-pane.active').length === 0) {
|
||||
let filterType = DEFAULT_SEARCH_FILTER.tag;
|
||||
let teamId = $currentAtWho.find('.atwho-header-res').data('team-id');
|
||||
let remeberedState = localStorage.getItem('smart_annotation_states/teams/' + teamId);
|
||||
if (remeberedState) {
|
||||
try {
|
||||
remeberedState = JSON.parse(remeberedState);
|
||||
filterType = remeberedState.tag;
|
||||
$currentAtWho.find(`.repository-object[data-object-id=${remeberedState.repository}]`)
|
||||
.addClass('btn-primary');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
$currentAtWho.find('.tab-button').off().on('shown.bs.tab', function() {
|
||||
$(field).click().focus();
|
||||
$(this).closest('.nav-tabs').find('.tab-button').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
});
|
||||
$currentAtWho.find('.repository-object').off().on('click', function() {
|
||||
$(this).parent().find('.repository-object').removeClass('btn-primary')
|
||||
.addClass('btn-light');
|
||||
$(this).addClass('btn-primary').removeClass('btn-light');
|
||||
$(field).click().focus();
|
||||
});
|
||||
|
||||
if ($currentAtWho.find('.tab-pane.active').length === 0) {
|
||||
let filterType = DEFAULT_SEARCH_FILTER.tag;
|
||||
let teamId = $currentAtWho.find('.atwho-header-res').data('team-id');
|
||||
let remeberedState = localStorage.getItem('smart_annotation_states/teams/' + teamId);
|
||||
if (remeberedState) {
|
||||
try {
|
||||
remeberedState = JSON.parse(remeberedState);
|
||||
filterType = remeberedState.tag;
|
||||
$currentAtWho.find(`.repository-object[data-object-id=${remeberedState.repository}]`)
|
||||
.addClass('btn-primary');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
$currentAtWho.find(`.${filterType}`).click();
|
||||
}
|
||||
})
|
||||
.on('reposition.atwho', function(event, flag, query) {
|
||||
let inputFieldLeft = query.$inputor.offset().left;
|
||||
if (inputFieldLeft > $(window).width()) {
|
||||
let leftPosition;
|
||||
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
|
||||
leftPosition = inputFieldLeft;
|
||||
} else {
|
||||
leftPosition = flag.left + $(window).scrollLeft();
|
||||
}
|
||||
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
|
||||
$currentAtWho.find(`.${filterType}`).click();
|
||||
}
|
||||
}).on('reposition.atwho', function(event, flag, query) {
|
||||
let inputFieldLeft = query.$inputor.offset().left;
|
||||
if (inputFieldLeft > $(window).width()) {
|
||||
let leftPosition;
|
||||
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
|
||||
leftPosition = inputFieldLeft;
|
||||
} else {
|
||||
leftPosition = flag.left + $(window).scrollLeft();
|
||||
}
|
||||
if ($('.repository-show').length) {
|
||||
query.$el.find('.atwho-view').css('top', flag.top + 'px');
|
||||
}
|
||||
})
|
||||
.atwho({
|
||||
at: '@',
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
$.getJSON(FilterTypeEnum.USER.dataUrl, { query: query }, function(data) {
|
||||
callback(data.users);
|
||||
});
|
||||
},
|
||||
tplEval: function(_tpl, items) {
|
||||
var $items = $(items.name);
|
||||
$items.find('li').data('item-data', { 'atwho-at': '@' }); // Emulate at.js insertContentFor method
|
||||
return $items;
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return matchHighlighter(li, query);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
return `[@${li.attr('data-full-name')}~${li.attr('data-id')}]`;
|
||||
}
|
||||
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
|
||||
}
|
||||
if ($('.repository-show').length) {
|
||||
query.$el.find('.atwho-view').css('top', flag.top + 'px');
|
||||
}
|
||||
}).atwho({
|
||||
at: '@',
|
||||
callbacks: {
|
||||
remoteFilter: function(query, callback) {
|
||||
$.getJSON(FilterTypeEnum.USER.dataUrl, { query: query }, function(data) {
|
||||
callback(data.users);
|
||||
});
|
||||
},
|
||||
startsWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
})
|
||||
tplEval: function(_tpl, items) {
|
||||
var $items = $(items.name);
|
||||
$items.find('li').data('item-data', { 'atwho-at': '@' }); // Emulate at.js insertContentFor method
|
||||
return $items;
|
||||
},
|
||||
highlighter: function(li, query) {
|
||||
return matchHighlighter(li, query);
|
||||
},
|
||||
beforeInsert: function(value, li) {
|
||||
return `[@${li.attr('data-full-name')}~${li.attr('data-id')}]`;
|
||||
}
|
||||
},
|
||||
startsWithSpace: true,
|
||||
acceptSpaceBar: true,
|
||||
displayTimeout: 120000
|
||||
})
|
||||
.atwho(atWhoSettings('#'));
|
||||
// .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times
|
||||
// .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT))
|
||||
// .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT))
|
||||
// .atwho(atWhoSettings('sample#', FilterTypeEnum.REPOSITORY));
|
||||
|
||||
$(this).data('atwho-initialized', true);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (deferred) {
|
||||
$(field).on('focus', initAtwho);
|
||||
} else {
|
||||
initAtwho();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -214,8 +223,8 @@ var SmartAnnotation = (function() {
|
|||
$('.atwho-header-res').find('.fa-times').click();
|
||||
}
|
||||
|
||||
function initialize(field) {
|
||||
var atWho = new SetAtWho(field);
|
||||
function initialize(field, deferred) {
|
||||
var atWho = new SetAtWho(field, deferred);
|
||||
atWho.init();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* global Promise ActiveStorage animateSpinner copyFromClipboard I18n
|
||||
Results ResultAssets FilePreviewModal Comments truncateLongString
|
||||
DragNDropSteps DragNDropResults initFormSubmitLinks dragNdropAssetsInit
|
||||
DragNDropResults initFormSubmitLinks dragNdropAssetsInit
|
||||
GLOBAL_CONSTANTS */
|
||||
|
||||
(function(global) {
|
||||
|
@ -9,7 +9,6 @@
|
|||
// Copy from clipboard
|
||||
global.copyFromClipboard = (function() {
|
||||
var UPLOADED_IMAGE = {};
|
||||
var LOCATION = '';
|
||||
|
||||
function retrieveImageFromClipboardAsBlob(pasteEvent, callback) {
|
||||
if (pasteEvent.clipboardData === false) {
|
||||
|
@ -72,12 +71,7 @@
|
|||
|
||||
// close modal
|
||||
closeModal();
|
||||
// reuse file upload from drag'n drop :)
|
||||
if (LOCATION === 'steps') {
|
||||
DragNDropSteps.init(inputArray);
|
||||
} else {
|
||||
DragNDropResults.init(inputArray);
|
||||
}
|
||||
DragNDropResults.init(inputArray);
|
||||
// clear all uploaded images
|
||||
UPLOADED_IMAGE = {};
|
||||
});
|
||||
|
@ -155,8 +149,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
function init(location) {
|
||||
LOCATION = location;
|
||||
function init() {
|
||||
global.addEventListener('paste', listener, false);
|
||||
}
|
||||
|
||||
|
@ -170,194 +163,6 @@
|
|||
});
|
||||
}());
|
||||
|
||||
// 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 = [];
|
||||
|
@ -444,7 +249,6 @@
|
|||
},
|
||||
error: function() {
|
||||
animateSpinner();
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -584,7 +388,7 @@
|
|||
});
|
||||
}
|
||||
validateTotalSize();
|
||||
dragNdropAssetsInit('results');
|
||||
dragNdropAssetsInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -609,7 +413,7 @@
|
|||
});
|
||||
}());
|
||||
|
||||
global.dragNdropAssetsInit = function(location) {
|
||||
global.dragNdropAssetsInit = function() {
|
||||
var inWindow = true;
|
||||
|
||||
$('body')
|
||||
|
@ -632,13 +436,9 @@
|
|||
.on('drop', function(e) {
|
||||
$('.is-dragover').hide();
|
||||
let files = e.originalEvent.dataTransfer.files;
|
||||
if (location === 'steps') {
|
||||
DragNDropSteps.init(files);
|
||||
} else {
|
||||
DragNDropResults.init(files);
|
||||
}
|
||||
DragNDropResults.init(files);
|
||||
});
|
||||
|
||||
copyFromClipboard.init(location);
|
||||
copyFromClipboard.init();
|
||||
};
|
||||
}(window));
|
||||
|
|
|
@ -59,12 +59,14 @@
|
|||
|
||||
$(document).on('click', '.print-label-button', function() {
|
||||
var selectedRows = $(this).data('rows');
|
||||
PrintModalComponent.showModal = true;
|
||||
if (selectedRows.length) {
|
||||
$('#modal-info-repository-row').modal('hide');
|
||||
PrintModalComponent.row_ids = selectedRows;
|
||||
} else {
|
||||
PrintModalComponent.row_ids = RepositoryDatatable.selectedRows();
|
||||
if (typeof PrintModalComponent !== 'undefined') {
|
||||
PrintModalComponent.showModal = true;
|
||||
if (selectedRows.length) {
|
||||
$('#modal-info-repository-row').modal('hide');
|
||||
PrintModalComponent.row_ids = selectedRows;
|
||||
} else {
|
||||
PrintModalComponent.row_ids = RepositoryDatatable.selectedRows();
|
||||
}
|
||||
}
|
||||
});
|
||||
}());
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
$(document).on('turbolinks:load', function() {
|
||||
function initShowPassword() {
|
||||
$('.fas.fa-eye.show-password').remove();
|
||||
$.each($('input[type="password"]'), function(i, e) {
|
||||
$('<i class="fas fa-eye show-password" style="cursor: pointer; z-index: 10"></i>').insertAfter(e);
|
||||
$(`<i class="fas fa-eye show-password"
|
||||
style="
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
top: ${$(e).position().top}px
|
||||
"></i>`).insertAfter(e);
|
||||
$(e).parent().addClass('right-icon');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
initShowPassword();
|
||||
});
|
||||
|
||||
$(document).on('click', '.show-password', function() {
|
||||
|
|
2
app/assets/javascripts/sitewide/tiny_mce.js
vendored
2
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -234,6 +234,7 @@ var TinyMCE = (function() {
|
|||
setTimeout(() => {
|
||||
$(editor.editorContainer).addClass('show');
|
||||
$('.tinymce-placeholder').remove();
|
||||
updateScrollPosition($(editor.editorContainer).closest('.tinymce-container'));
|
||||
}, 400);
|
||||
|
||||
// Init saved status label
|
||||
|
@ -310,7 +311,6 @@ var TinyMCE = (function() {
|
|||
editorForm.find('.tinymce-status-badge').addClass('hidden');
|
||||
editorForm.find('.tinymce-view').removeClass('hidden');
|
||||
editor.remove();
|
||||
|
||||
updateScrollPosition(editorForm);
|
||||
if (onSaveCallback) { onSaveCallback(); }
|
||||
})
|
||||
|
|
|
@ -19,6 +19,7 @@ var zebraPrint = (function() {
|
|||
const PRINTER_STATUS_ERROR = 'error';
|
||||
const PRINTER_STATUS_PRINTING = 'printing';
|
||||
const PRINTER_STATUS_DONE = 'done';
|
||||
const PRINTER_STATUS_SEARCH = 'search';
|
||||
|
||||
var getPrinterStatus = function(device) {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -115,11 +116,13 @@ var zebraPrint = (function() {
|
|||
return selectedDevice && selectedDevice.length ? selectedDevice[0] : null;
|
||||
}
|
||||
|
||||
function updateProgressModalData(progressModal, printerName, printerStatus, printingStatus, numberOfCopies) {
|
||||
function updateProgressModalData(progressModal, printerName, printerStatus, printingStatus) {
|
||||
$(progressModal + ' .title').html(stripPrinterNameHtml(printerName));
|
||||
$(progressModal + ' .printer-status').attr('data-status', printerStatus);
|
||||
$(progressModal + ' .printer-status').html(I18n.t('label_printers.modal_printing_status.printer_status.'
|
||||
+ printerStatus));
|
||||
if (printerStatus !== PRINTER_STATUS_SEARCH) {
|
||||
$(progressModal + ' .printer-status').html(I18n.t('label_printers.modal_printing_status.printer_status.'
|
||||
+ printerStatus));
|
||||
}
|
||||
$(progressModal + ' .printing-status').attr('data-status', printingStatus);
|
||||
$(progressModal + ' .printing-status').html(I18n.t('label_printers.modal_printing_status.printing_status.'
|
||||
+ printingStatus));
|
||||
|
@ -190,21 +193,23 @@ var zebraPrint = (function() {
|
|||
modal.replaceWith(dataZebra.responseJSON.html);
|
||||
} else {
|
||||
$('body').append($(dataZebra.responseJSON.html));
|
||||
}
|
||||
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_READY, PRINTER_STATUS_PRINTING);
|
||||
device = findDevice(printData.printer_name);
|
||||
new Zebra.Printer(device).isPrinterReady(function() {
|
||||
$(document).on('click', progressModal, function() {
|
||||
$(this).closest(progressModal).remove();
|
||||
});
|
||||
}
|
||||
|
||||
print(
|
||||
device, progressModal, printData.number_of_copies,
|
||||
printData.printer_name, dataZebra.responseJSON.labels
|
||||
);
|
||||
}, function() {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_SEARCH, PRINTER_STATUS_SEARCH);
|
||||
device = findDevice(printData.printer_name);
|
||||
|
||||
getPrinterStatus(device).then((device) => {
|
||||
if (device.status === I18n.t('label_printers.modal_printing_status.printer_status.ready')) {
|
||||
print(
|
||||
device, progressModal, printData.number_of_copies,
|
||||
printData.printer_name, dataZebra.responseJSON.labels
|
||||
);
|
||||
} else {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* false to set form to view mode.
|
||||
*/
|
||||
|
||||
/* global _ filesValidator animateSpinner FileTypeEnum */
|
||||
/* global _ filesValidator animateSpinner FileTypeEnum initShowPassword */
|
||||
|
||||
var forms = $('form[data-for]');
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
|||
form.find("[data-part='view']").hide();
|
||||
form.find("[data-part='edit']").show();
|
||||
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
|
||||
initShowPassword();
|
||||
} else {
|
||||
form.find("[data-part='view']").show();
|
||||
form.find("[data-part='edit'] input").blur();
|
||||
|
|
|
@ -48,12 +48,13 @@
|
|||
.cards-wrapper {
|
||||
--card-min-width: 350px;
|
||||
--list-columns-number: 8;
|
||||
grid-auto-rows: auto;
|
||||
|
||||
.card {
|
||||
grid-row: span 7;
|
||||
align-items: center;
|
||||
|
||||
.experiment-code-cell {
|
||||
display: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.experiment-card {
|
||||
|
@ -85,31 +86,25 @@
|
|||
|
||||
.experiment-name-cell {
|
||||
@include font-h3;
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
height: 3em;
|
||||
-webkit-line-clamp: 2;
|
||||
margin: .25em 1.75em -.25em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
right: .2em;
|
||||
top: .2em;
|
||||
a {
|
||||
-webkit-box-orient: vertical;
|
||||
color: inherit;
|
||||
display: -webkit-box;
|
||||
height: 3em;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.dates-and-img-container {
|
||||
display: flex;
|
||||
height: 7em;
|
||||
height: 6em;
|
||||
width: 100%;
|
||||
|
||||
.dates-container {
|
||||
|
@ -117,11 +112,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
height: 36px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
line-height: 34px;
|
||||
|
||||
.card-label {
|
||||
color: $color-silver-chalice;
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@
|
|||
|
||||
&::after {
|
||||
background: linear-gradient(to right, $color-transparent, $color-white 50%);
|
||||
bottom: .75em;
|
||||
bottom: .4em;
|
||||
content: "";
|
||||
height: 1.75em;
|
||||
position: absolute;
|
||||
|
@ -167,7 +167,7 @@
|
|||
}
|
||||
|
||||
.more-button {
|
||||
bottom: .8em;
|
||||
bottom: .5em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
|
@ -248,7 +248,7 @@
|
|||
}
|
||||
|
||||
&.list {
|
||||
grid-auto-rows: 1px 5em;
|
||||
grid-auto-rows: 1px minmax(3em, auto);
|
||||
grid-template-columns: max-content minmax(100px, auto) minmax(80px, max-content) repeat(calc(var(--list-columns-number) - 4), minmax(100px, auto)) max-content;
|
||||
grid-template-rows: 3em;
|
||||
|
||||
|
@ -262,9 +262,10 @@
|
|||
|
||||
.workflow-img-wrapper {
|
||||
flex-shrink: 0;
|
||||
height: 3.5em;
|
||||
margin: .25em 1em .25em .5em;
|
||||
width: 3.5em;
|
||||
height: 3em;
|
||||
margin: 0 .75em 0 .25em;
|
||||
padding: .25em;
|
||||
width: 3em;
|
||||
|
||||
.archived-icon-plceholder {
|
||||
font-size: 2em;
|
||||
|
@ -275,7 +276,7 @@
|
|||
text-align: center;
|
||||
|
||||
img {
|
||||
max-height: 3em;
|
||||
max-height: 2.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,6 +309,8 @@
|
|||
.experiment-code-cell {
|
||||
display: block;
|
||||
grid-column: 3;
|
||||
line-height: 1.25em;
|
||||
padding-top: .5em;
|
||||
}
|
||||
|
||||
.start-date-cell {
|
||||
|
@ -327,8 +330,8 @@
|
|||
position: relative;
|
||||
|
||||
.description-text {
|
||||
height: 4.5em;
|
||||
-webkit-line-clamp: 3;
|
||||
height: 3em;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
&::after {
|
||||
bottom: .5em;
|
||||
|
@ -343,6 +346,7 @@
|
|||
|
||||
.actions-cell {
|
||||
grid-column: 8;
|
||||
height: 100%;
|
||||
padding-top: 3px;
|
||||
position: initial;
|
||||
}
|
||||
|
@ -422,12 +426,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.readonly {
|
||||
.experiment-name-cell {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.last-page {
|
||||
padding-bottom: 5em;
|
||||
position: relative;
|
||||
|
@ -474,7 +472,10 @@
|
|||
.cards-wrapper {
|
||||
.card.experiment-card {
|
||||
.workflow-img-wrapper {
|
||||
align-items: center;
|
||||
background-color: $color-alto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
|
|
|
@ -52,6 +52,21 @@
|
|||
}
|
||||
|
||||
.module-large {
|
||||
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.task-code {
|
||||
align-items: center;
|
||||
color: $color-volcano;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
margin-left: auto;
|
||||
padding-right: .5em;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.description-label {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
|
@ -63,6 +78,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.archived-task-card-code {
|
||||
bottom: 30px;
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
// Create wopi file
|
||||
.create-wopi-file-btn {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
.repositories-dropdown-menu {
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
.repository {
|
||||
@include font-button;
|
||||
cursor: pointer;
|
||||
|
@ -135,16 +135,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.complete-button-container {
|
||||
display: inline;
|
||||
float: right;
|
||||
width: 260px;
|
||||
|
||||
.my_module-state-buttons {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.task-details {
|
||||
.fas.block-icon {
|
||||
margin-right: 8px;
|
||||
|
@ -430,9 +420,31 @@
|
|||
max-width: 100vw;
|
||||
width: 650px;
|
||||
|
||||
.dropdown-header,
|
||||
.dropdown-body {
|
||||
padding: 10px 32px;
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
background: $color-white;
|
||||
border-bottom: $border-tertiary;
|
||||
|
||||
.protocol-name {
|
||||
color: $color-black;
|
||||
margin-top: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.protocol-header-info {
|
||||
color: $color-black;
|
||||
font-size: .875em;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-body {
|
||||
border-bottom: $border-tertiary;
|
||||
padding: 10px 32px;
|
||||
|
||||
.info-line {
|
||||
align-items: center;
|
||||
|
@ -657,3 +669,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-details-code {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
|
|
@ -199,12 +199,24 @@ path, ._jsPlumb_endpoint {
|
|||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.ep {
|
||||
font-style: italic;
|
||||
|
||||
.task-card-code {
|
||||
color: $color-volcano;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.new-my-module-canvas {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
|
@ -673,7 +685,6 @@ li.module-hover {
|
|||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -696,18 +707,13 @@ li.module-hover {
|
|||
.project-name-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
margin: 0 1.75em;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.name {
|
||||
line-height: 2em;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -720,6 +726,8 @@ li.module-hover {
|
|||
top: .2em;
|
||||
|
||||
.project-actions-menu {
|
||||
height: 40px;
|
||||
|
||||
a {
|
||||
@include font-button;
|
||||
padding: .5em 1em;
|
||||
|
@ -782,6 +790,7 @@ li.module-hover {
|
|||
height: 2em;
|
||||
line-height: 2em;
|
||||
margin-right: .25em;
|
||||
position: unset;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
|
@ -861,7 +870,8 @@ li.module-hover {
|
|||
}
|
||||
|
||||
&.list {
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 3), minmax(100px, auto)) max-content max-content;
|
||||
grid-auto-rows: minmax(3em, auto) 1px;
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content max-content;
|
||||
|
||||
.projects-group {
|
||||
display: contents;
|
||||
|
@ -909,7 +919,7 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.name {
|
||||
grid-column: 5 span;
|
||||
grid-column: 6 span;
|
||||
line-height: 3em;
|
||||
|
||||
&:before {
|
||||
|
@ -933,28 +943,39 @@ li.module-hover {
|
|||
grid-column: 2;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 5px 0;
|
||||
|
||||
h3 {
|
||||
line-height: 1.25em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $brand-primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
.start-date-cell {
|
||||
.project-code-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
.visibility-cell {
|
||||
.start-date-cell {
|
||||
grid-column: 4;
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
.visibility-cell {
|
||||
grid-column: 5;
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
grid-column: 6;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
grid-column: 6;
|
||||
grid-column: 7;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
@ -1024,8 +1045,11 @@ li.module-hover {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.projects-container {
|
||||
.project-actions-menu {
|
||||
height: 40px;
|
||||
|
||||
.btn-light:hover {
|
||||
background: $color-alto;
|
||||
}
|
||||
|
@ -1034,6 +1058,58 @@ li.module-hover {
|
|||
.cards-wrapper {
|
||||
grid-auto-rows: 2.5em;
|
||||
|
||||
.visibility-cell {
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.start-date-cell {
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.archived-date-cell {
|
||||
z-index: 2;
|
||||
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
&.last-page.cards {
|
||||
.project-card {
|
||||
.start-date-cell {
|
||||
top: 36px;
|
||||
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.archived-date-cell {
|
||||
top: 26px;
|
||||
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-cell {
|
||||
top: 16px;
|
||||
|
||||
.value {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
top: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card.project-card {
|
||||
.data-row {
|
||||
color: $color-silver-chalice;
|
||||
|
@ -1053,25 +1129,33 @@ li.module-hover {
|
|||
}
|
||||
|
||||
&.list {
|
||||
--list-columns-number: 7;
|
||||
grid-auto-rows: 3em 1px;
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content;
|
||||
--list-columns-number: 8;
|
||||
grid-auto-rows: minmax(3em, auto) 1px;
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 3), minmax(100px, auto)) max-content max-content;
|
||||
|
||||
.card {
|
||||
&.folder-card {
|
||||
.name {
|
||||
grid-column: 6 span;
|
||||
grid-column: 7 span;
|
||||
}
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
grid-column: 7;
|
||||
}
|
||||
|
||||
.archived-date-cell {
|
||||
grid-column: 6;
|
||||
grid-column: 5;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
grid-column: 7;
|
||||
grid-column: 8;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.visibility-cell {
|
||||
grid-column: 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1089,3 +1173,56 @@ li.module-hover {
|
|||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cards-wrapper.cards {
|
||||
grid-gap: 25px;
|
||||
|
||||
.project-card {
|
||||
.project-name-cell {
|
||||
align-items: start !important;
|
||||
top: 12%;
|
||||
min-height: 35px;
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 42px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.name {
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.project-code-cell {
|
||||
height: min-content;
|
||||
margin-top: 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
position: relative;
|
||||
top: 5%;
|
||||
|
||||
.card-label {
|
||||
color: #808080 !important;
|
||||
}
|
||||
|
||||
&.start-date-cell {
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
&.visibility-cell {
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
&.user-cell {
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -344,7 +344,6 @@
|
|||
.preview-holder {
|
||||
height: calc(100% - 40px);
|
||||
margin: 0 21px 40px;
|
||||
max-width: 900px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: calc(100% - 42px);
|
||||
|
@ -400,18 +399,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1520px) {
|
||||
.external-protocols-tab {
|
||||
|
||||
.main-protocol-panel {
|
||||
|
||||
.protocol-preview-panel {
|
||||
|
||||
.preview-holder {
|
||||
margin: 0 calc(50% - 450px) 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -541,3 +541,47 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.repository-table {
|
||||
.repository-edit-overlay {
|
||||
background: $color-white;
|
||||
color: $color-silver-chalice;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
|
||||
.repository-save-changes-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.repository-edit-overlay--toolbar {
|
||||
height: 3em;
|
||||
line-height: 2em;
|
||||
padding: .5em 1em .5em .5em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
top: 2em;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.repository-edit-overlay--pagination {
|
||||
bottom: 0;
|
||||
height: 5em;
|
||||
line-height: 5em;
|
||||
padding: .5em;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
width: calc(100% - var(--repository-sidebar-margin));
|
||||
|
||||
hr {
|
||||
margin-top: -2.5em;
|
||||
}
|
||||
|
||||
span {
|
||||
background: $color-white;
|
||||
display: inline-block;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
border-color: $brand-danger;
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
&[data-status="search"] {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,8 +68,14 @@
|
|||
background-color: $color-concrete;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-height: 300px;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: .5em 0;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.label-template-option {
|
||||
|
|
|
@ -345,6 +345,13 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
.assigned-column .repository-row-edit-icon {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editing {
|
||||
|
@ -363,10 +370,6 @@
|
|||
|
||||
tr:hover {
|
||||
.assigned-column {
|
||||
.repository-row-edit-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.assign-counter-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1299px) {
|
||||
@media (max-width: 1624px) {
|
||||
.repository-toolbar {
|
||||
|
||||
.btn:not(.prevent-shrink) {
|
||||
|
|
39
app/assets/stylesheets/session_expired.scss
Normal file
39
app/assets/stylesheets/session_expired.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
// scss-lint:disable NestingDepth ImportantRule
|
||||
|
||||
.session-modal {
|
||||
.modal-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
padding: .25em 0;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.instruction-session-collapse {
|
||||
cursor: pointer;
|
||||
margin-bottom: .5em;
|
||||
margin-top: 20px;
|
||||
|
||||
.fa-angle-up {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
|
||||
.fa-angle-up {
|
||||
@include rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -34,6 +34,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
.indented_row {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.zebra-settings-collapse {
|
||||
border-left: 3px solid $color-concrete;
|
||||
margin-top: 14px;
|
||||
|
@ -42,6 +54,10 @@
|
|||
|
||||
.collapse {
|
||||
padding-left: 2.5em;
|
||||
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-row {
|
||||
|
@ -99,6 +115,7 @@
|
|||
.row-title {
|
||||
@include font-h2;
|
||||
margin-left: .5em;
|
||||
margin-top: .25em;
|
||||
}
|
||||
|
||||
.api-key-container {
|
||||
|
|
|
@ -59,9 +59,6 @@
|
|||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
left: .5em;
|
||||
position: absolute;
|
||||
top: .5em;
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
|
40
app/assets/stylesheets/shared/datetime_picker.scss
Normal file
40
app/assets/stylesheets/shared/datetime_picker.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
// scss-lint:disable SelectorDepth NestingDepth
|
||||
|
||||
.bootstrap-datetimepicker-widget {
|
||||
.glyphicon-calendar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.timepicker-picker {
|
||||
padding: 0 60px;
|
||||
|
||||
td {
|
||||
height: initial;
|
||||
line-height: initial;
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.timepicker-hour,
|
||||
.timepicker-minute {
|
||||
height: initial;
|
||||
line-height: initial;
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.glyphicon-chevron-up,
|
||||
.glyphicon-chevron-down {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
}
|
6
app/assets/stylesheets/shared/form_errors.scss
Normal file
6
app/assets/stylesheets/shared/form_errors.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
.field_with_errors,
|
||||
.sci-input-container {
|
||||
.help-block {
|
||||
color: $brand-danger;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@
|
|||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sci-input-container {
|
||||
.sci-input-container:not(.field_with_errors) {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.minimum-password-length {
|
||||
|
|
|
@ -10,28 +10,6 @@
|
|||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
#new_step,
|
||||
.panel-step-attachment {
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
> div > span.pull-left {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-name-container,
|
||||
.table-name-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.remove-container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#steps {
|
||||
// hack only for firefox
|
||||
|
|
|
@ -83,7 +83,7 @@ module AccessPermissions
|
|||
|
||||
def update_default_public_user_role
|
||||
@project.update!(permitted_default_public_user_role_params)
|
||||
UserAssignments::GroupAssignmentJob.perform_later(current_team, @project, current_user)
|
||||
UserAssignments::ProjectGroupAssignmentJob.perform_later(current_team, @project, current_user)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -13,48 +13,6 @@ module StepsActions
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_new_checklists_data
|
||||
checklists = []
|
||||
new_checklists = step_params[:checklists_attributes]
|
||||
|
||||
if new_checklists
|
||||
new_checklists.to_h.each do |e|
|
||||
list = PreviousChecklist.new(
|
||||
e.second[:id].to_i,
|
||||
e.second[:name]
|
||||
)
|
||||
if e.second[:checklist_items_attributes]
|
||||
e.second[:checklist_items_attributes].each do |el|
|
||||
list.add_checklist(
|
||||
PreviousChecklistItem.new(el.second[:id].to_i, el.second[:text])
|
||||
)
|
||||
end
|
||||
end
|
||||
checklists << list
|
||||
end
|
||||
end
|
||||
checklists
|
||||
end
|
||||
|
||||
def fetch_old_checklists_data(step)
|
||||
checklists = []
|
||||
if step.checklists
|
||||
step.checklists.each do |e|
|
||||
list = PreviousChecklist.new(
|
||||
e.id,
|
||||
e.name
|
||||
)
|
||||
e.checklist_items.each do |el|
|
||||
list.add_checklist(
|
||||
PreviousChecklistItem.new(el.id, el.text)
|
||||
)
|
||||
end
|
||||
checklists << list
|
||||
end
|
||||
end
|
||||
checklists
|
||||
end
|
||||
|
||||
# used for step update action it traverse through the input params and
|
||||
# generates notifications
|
||||
def update_annotation_notifications(step,
|
||||
|
|
|
@ -475,6 +475,7 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_read_permissions
|
||||
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
|
||||
render_403 unless can_read_experiment?(@experiment) ||
|
||||
@experiment.archived? && can_read_archived_experiment?(@experiment)
|
||||
end
|
||||
|
|
|
@ -130,7 +130,9 @@ class LabelTemplatesController < ApplicationController
|
|||
|
||||
def zpl_preview
|
||||
service = LabelTemplatesPreviewService.new(params, current_user)
|
||||
payload = service.generate_zpl_preview!
|
||||
|
||||
# only render last generated label image
|
||||
payload = service.generate_zpl_preview!.split.last
|
||||
|
||||
if service.error.blank?
|
||||
render json: { base64_preview: payload }
|
||||
|
|
|
@ -71,12 +71,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController
|
|||
end
|
||||
|
||||
def full_view_sidebar
|
||||
@repository = Repository.find_by(id: params[:repository_id])
|
||||
|
||||
if @repository
|
||||
return render_403 unless can_read_repository?(@repository)
|
||||
end
|
||||
|
||||
@repository = Repository.viewable_by_user(current_user, current_team).find_by(id: params[:repository_id])
|
||||
@repository_snapshots = @my_module.repository_snapshots
|
||||
.where(parent_id: params[:repository_id])
|
||||
.order(created_at: :desc)
|
||||
|
|
|
@ -315,7 +315,6 @@ class MyModulesController < ApplicationController
|
|||
def protocols
|
||||
@protocol = @my_module.protocol
|
||||
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
|
||||
current_team_switch(@protocol.team)
|
||||
end
|
||||
|
||||
def protocol
|
||||
|
@ -338,27 +337,23 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def results
|
||||
current_team_switch(@my_module
|
||||
.experiment
|
||||
.project
|
||||
.team)
|
||||
|
||||
@results_order = params[:order] || 'new'
|
||||
|
||||
@results = @my_module.archived_branch? ? @my_module.results : @my_module.results.active
|
||||
@results = @results.page(params[:page]).per(Constants::RESULTS_PER_PAGE_LIMIT)
|
||||
|
||||
@results = case @results_order
|
||||
when 'old' then @results.order(updated_at: :asc)
|
||||
when 'old' then @results.order(created_at: :asc)
|
||||
when 'old_updated' then @results.order(updated_at: :asc)
|
||||
when 'new_updated' then @results.order(updated_at: :desc)
|
||||
when 'atoz' then @results.order(name: :asc)
|
||||
when 'ztoa' then @results.order(name: :desc)
|
||||
else @results.order(updated_at: :desc)
|
||||
else @results.order(created_at: :desc)
|
||||
end
|
||||
end
|
||||
|
||||
def archive
|
||||
@archived_results = @my_module.archived_results
|
||||
current_team_switch(@my_module.experiment.project.team)
|
||||
end
|
||||
|
||||
def restore_group
|
||||
|
@ -470,6 +465,7 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def check_read_permissions
|
||||
current_team_switch(@project.team) if current_team != @project.team
|
||||
render_403 unless can_read_my_module?(@my_module)
|
||||
end
|
||||
|
||||
|
|
|
@ -85,18 +85,18 @@ class RepositoryRowsController < ApplicationController
|
|||
label_template = LabelTemplate.where(team_id: current_team.id).find(params[:label_template_id])
|
||||
|
||||
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_row.first).render
|
||||
render json: { label_code: label_code }
|
||||
rescue StandardError => e
|
||||
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_row.first, true).render
|
||||
render json: { error: e, label_code: label_code }, status: :unprocessable_entity
|
||||
if label_code[:error].empty?
|
||||
render json: { label_code: label_code[:label] }
|
||||
else
|
||||
render json: { error: label_code[:error].first, label_code: label_code[:label] }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def print_zpl
|
||||
label_template = LabelTemplate.find_by(id: params[:label_template_id])
|
||||
labels = @repository_row.flat_map do |repository_row|
|
||||
LabelTemplates::RepositoryRowService.new(label_template,
|
||||
repository_row,
|
||||
true).render
|
||||
repository_row).render[:label]
|
||||
end
|
||||
|
||||
render(
|
||||
|
@ -128,8 +128,7 @@ class RepositoryRowsController < ApplicationController
|
|||
LabelPrinters::PrintJob.perform_later(
|
||||
label_printer,
|
||||
LabelTemplates::RepositoryRowService.new(label_template,
|
||||
repository_row,
|
||||
true).render,
|
||||
repository_row).render[:label],
|
||||
params[:copies].to_i
|
||||
).job_id
|
||||
end
|
||||
|
|
|
@ -43,6 +43,7 @@ module StepElements
|
|||
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
|
||||
element.update(position: element.position + 1)
|
||||
end
|
||||
@checklist.name += ' (1)'
|
||||
new_checklist = @checklist.duplicate(@step, current_user, position + 1)
|
||||
log_step_activity(:checklist_duplicated, { checklist_name: @checklist.name })
|
||||
render_step_orderable_element(new_checklist)
|
||||
|
|
|
@ -48,6 +48,7 @@ module StepElements
|
|||
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
|
||||
element.update(position: element.position + 1)
|
||||
end
|
||||
@table.name += ' (1)'
|
||||
new_table = @table.duplicate(@step, current_user, position + 1)
|
||||
log_step_activity(:table_duplicated, { table_name: new_table.name })
|
||||
render_step_orderable_element(new_table.step_table)
|
||||
|
|
|
@ -4,18 +4,18 @@ class StepsController < ApplicationController
|
|||
include StepsActions
|
||||
include MarvinJsActions
|
||||
|
||||
before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state
|
||||
move_up move_down update_asset_view_mode elements
|
||||
before_action :load_vars, only: %i(update destroy show toggle_step_state update_view_state
|
||||
update_asset_view_mode elements
|
||||
attachments upload_attachment duplicate)
|
||||
before_action :load_vars_nested, only: %i(new create index reorder)
|
||||
before_action :load_vars_nested, only: %i(create index reorder)
|
||||
before_action :convert_table_contents_to_utf8, only: %i(create update)
|
||||
|
||||
before_action :check_protocol_manage_permissions, only: %i(reorder)
|
||||
before_action :check_view_permissions, only: %i(show index attachments elements)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: %i(edit update destroy move_up move_down
|
||||
before_action :check_create_permissions, only: %i(create)
|
||||
before_action :check_manage_permissions, only: %i(update destroy
|
||||
update_view_state update_asset_view_mode upload_attachment)
|
||||
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state checklistitem_state)
|
||||
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state)
|
||||
|
||||
def index
|
||||
render json: @protocol.steps.in_order, each_serializer: StepSerializer, user: current_user
|
||||
|
@ -76,18 +76,6 @@ class StepsController < ApplicationController
|
|||
user: current_user
|
||||
end
|
||||
|
||||
def new
|
||||
@step = Step.new
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(partial: 'new.html.erb')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@step = Step.new(
|
||||
name: t('protocols.steps.default_name'),
|
||||
|
@ -106,86 +94,6 @@ class StepsController < ApplicationController
|
|||
render json: @step, serializer: StepSerializer, user: current_user
|
||||
end
|
||||
|
||||
def create_old
|
||||
@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
|
||||
|
||||
|
||||
# link tiny_mce_assets to the step
|
||||
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
|
||||
|
||||
@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], current_user)
|
||||
|
||||
create_annotation_notifications(@step)
|
||||
|
||||
# Generate activity
|
||||
if @protocol.in_module?
|
||||
log_activity(
|
||||
:create_step,
|
||||
@my_module.experiment.project,
|
||||
{ my_module: @my_module.id }.merge(step_message_items)
|
||||
)
|
||||
else
|
||||
log_activity(:add_step_to_protocol_repository, nil, { protocol: @protocol.id }.merge(step_message_items))
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if @step.errors.blank?
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'steps/step.html.erb',
|
||||
locals: { step: @step }
|
||||
)
|
||||
},
|
||||
status: :ok
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: @step.errors.to_json, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
@ -200,17 +108,6 @@ class StepsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(partial: 'edit.html.erb')
|
||||
},
|
||||
status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @step.update(step_params)
|
||||
# Generate activity
|
||||
|
@ -259,92 +156,6 @@ class StepsController < ApplicationController
|
|||
head :unprocessable_entity
|
||||
end
|
||||
|
||||
def update_old
|
||||
respond_to do |format|
|
||||
old_description = @step.description
|
||||
old_checklists = fetch_old_checklists_data(@step)
|
||||
new_checklists = fetch_new_checklists_data
|
||||
previous_size = @step.space_taken
|
||||
|
||||
step_params_all = step_params
|
||||
|
||||
# process only destroy update on step references. This prevents
|
||||
# skipping deleting reference in case update validation fails.
|
||||
# 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
|
||||
new_assets = []
|
||||
step_params_all[:assets_attributes]&.each do |key, value|
|
||||
next unless value[:signed_blob_id]
|
||||
|
||||
new_asset = @step.assets.create!(
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
team: current_team,
|
||||
view_mode: @step.assets_view_mode
|
||||
)
|
||||
new_asset.file
|
||||
.attach(value[:signed_blob_id])
|
||||
new_assets.push(new_asset.id)
|
||||
step_params_all[:assets_attributes].delete(key)
|
||||
end
|
||||
|
||||
@step.assign_attributes(step_params_all)
|
||||
@step.last_modified_by = current_user
|
||||
|
||||
@step.tables.each do |table|
|
||||
table.created_by = current_user if table.new_record?
|
||||
table.last_modified_by = current_user unless table.new_record?
|
||||
table.team = current_team
|
||||
end
|
||||
|
||||
update_checklist_items_without_callback(step_params_all)
|
||||
|
||||
if @step.save
|
||||
|
||||
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
|
||||
@step.reload
|
||||
|
||||
# generates notification on step upadate
|
||||
update_annotation_notifications(@step,
|
||||
old_description,
|
||||
new_checklists,
|
||||
old_checklists)
|
||||
|
||||
# Release team's space taken
|
||||
team = @protocol.team
|
||||
team.release_space(previous_size)
|
||||
team.save
|
||||
|
||||
# Post process step assets
|
||||
@step.assets.each do |asset|
|
||||
asset.post_process_file(team) if new_assets.include? asset.id
|
||||
end
|
||||
|
||||
# Generate activity
|
||||
if @protocol.in_module?
|
||||
log_activity(:edit_step, @my_module.experiment.project, my_module: @my_module.id)
|
||||
else
|
||||
log_activity(:edit_step_in_protocol_repository, nil, protocol: @protocol.id)
|
||||
end
|
||||
|
||||
format.json {
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: 'step.html.erb',
|
||||
locals: { step: @step }
|
||||
})
|
||||
}
|
||||
}
|
||||
else
|
||||
format.json {
|
||||
render json: @step.errors.to_json, status: :bad_request
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_view_state
|
||||
view_state = @step.current_view_state(current_user)
|
||||
view_state.state['assets']['sort'] = params.require(:assets).require(:order)
|
||||
|
@ -398,48 +209,6 @@ class StepsController < ApplicationController
|
|||
render json: @step, serializer: StepSerializer, user: current_user
|
||||
end
|
||||
|
||||
# Responds to checkbox toggling in steps view
|
||||
def checklistitem_state
|
||||
respond_to do |format|
|
||||
checked = params[:checked] == 'true'
|
||||
changed = @chk_item.checked != checked
|
||||
@chk_item.checked = checked
|
||||
|
||||
if @chk_item.save
|
||||
format.json { render json: {}, status: :accepted }
|
||||
|
||||
# Create activity
|
||||
if changed
|
||||
completed_items = @chk_item.checklist.checklist_items
|
||||
.where(checked: true).count
|
||||
all_items = @chk_item.checklist.checklist_items.count
|
||||
text_activity = smart_annotation_parser(@chk_item.text)
|
||||
.gsub(/\s+/, ' ')
|
||||
type_of = if checked
|
||||
:check_step_checklist_item
|
||||
else
|
||||
:uncheck_step_checklist_item
|
||||
end
|
||||
# This should always hold true (only in module can
|
||||
# check items be checked, but still check just in case)
|
||||
if @protocol.in_module?
|
||||
log_activity(type_of,
|
||||
@protocol.my_module.experiment.project,
|
||||
my_module: @my_module.id,
|
||||
step: @chk_item.checklist.step.id,
|
||||
step_position: { id: @chk_item.checklist.step.id,
|
||||
value_for: 'position_plus_one' },
|
||||
checkbox: text_activity,
|
||||
num_completed: completed_items.to_s,
|
||||
num_all: all_items.to_s)
|
||||
end
|
||||
end
|
||||
else
|
||||
format.json { render json: {}, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Complete/uncomplete step
|
||||
def toggle_step_state
|
||||
@step.completed = params[:completed] == 'true'
|
||||
|
@ -473,30 +242,6 @@ class StepsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def move_up
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
@step.move_up
|
||||
|
||||
render json: {
|
||||
steps_order: @protocol.steps.order(:position).select(:id, :position)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def move_down
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
@step.move_down
|
||||
|
||||
render json: {
|
||||
steps_order: @protocol.steps.order(:position).select(:id, :position)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reorder
|
||||
@protocol.with_lock do
|
||||
params[:step_positions].each do |id, position|
|
||||
|
@ -518,72 +263,6 @@ class StepsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
# This function is used for partial update of step references and
|
||||
# it's useful when you want to execute destroy action on attribute
|
||||
# collection separately from normal update action, for example if
|
||||
# you don't want that update validation interupt destroy action.
|
||||
# In case of step model you can delete checkboxes, assets or tables.
|
||||
def destroy_attributes(params)
|
||||
update_params = {}
|
||||
delete_step_tables(params)
|
||||
extract_destroy_params(params, update_params)
|
||||
@step.update(update_params) unless update_params.blank?
|
||||
end
|
||||
|
||||
# Delete the step table
|
||||
def delete_step_tables(params)
|
||||
return unless params[:tables_attributes].present?
|
||||
params[:tables_attributes].each do |_, table|
|
||||
next unless table['_destroy']
|
||||
table_to_destroy = Table.find_by_id(table['id'])
|
||||
next if table_to_destroy.nil?
|
||||
table_to_destroy.report_elements.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if hash contains destroy parameter '_destroy' and returns
|
||||
# boolean value.
|
||||
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
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Extracts part of hash that contains destroy parameters. It deletes
|
||||
# values that contains destroy parameters from original variable and
|
||||
# puts them into update_params variable.
|
||||
def extract_destroy_params(params, update_params)
|
||||
params.each do |key, values|
|
||||
next unless values.respond_to?(:each)
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@step = Step.find_by(id: params[:id])
|
||||
return render_404 unless @step
|
||||
|
@ -611,22 +290,6 @@ class StepsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def update_checklist_items_without_callback(params)
|
||||
params.dig('checklists_attributes')&.values&.each do |cl|
|
||||
ck = @step.checklists.find_by(id: cl[:id])
|
||||
next if ck.nil? # ck is new checklist, skip update positions
|
||||
|
||||
cl['checklist_items_attributes']&.each do |item|
|
||||
# Here item is somehow array of index and parameters [0, paramteters<Object>], should be fixed on FE also
|
||||
item_record = ck.checklist_items.find_by(id: item[1][:id])
|
||||
|
||||
next unless item_record
|
||||
|
||||
item_record.update_attribute('position', item[1][:position])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_view_permissions
|
||||
render_403 unless can_read_protocol_in_module?(@protocol) || can_read_protocol_in_repository?(@protocol)
|
||||
end
|
||||
|
@ -655,43 +318,6 @@ class StepsController < ApplicationController
|
|||
params.require(:step).permit(:name)
|
||||
end
|
||||
|
||||
def step_params_old
|
||||
params.require(:step).permit(
|
||||
:name,
|
||||
:description,
|
||||
checklists_attributes: [
|
||||
:id,
|
||||
:name,
|
||||
:_destroy,
|
||||
checklist_items_attributes: [
|
||||
:id,
|
||||
:text,
|
||||
:position,
|
||||
:_destroy
|
||||
]
|
||||
],
|
||||
assets_attributes: [
|
||||
:id,
|
||||
:_destroy,
|
||||
:signed_blob_id
|
||||
],
|
||||
tables_attributes: [
|
||||
:id,
|
||||
:name,
|
||||
:contents,
|
||||
:_destroy
|
||||
],
|
||||
marvin_js_assets_attributes: %i(
|
||||
id
|
||||
_destroy
|
||||
),
|
||||
bio_eddie_assets_attributes: %i(
|
||||
id
|
||||
_destroy
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def log_activity(type_of, project = nil, message_items = {})
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
|
|
|
@ -99,7 +99,7 @@ module Users
|
|||
result[:status] = :user_exists_and_in_team
|
||||
else
|
||||
# Also generate user team relation
|
||||
team.user_assignments.create(user: user, user_role: @user_role, assigned_by: current_user)
|
||||
team.user_assignments.create!(user: user, user_role: @user_role, assigned_by: current_user)
|
||||
|
||||
generate_notification(
|
||||
@user,
|
||||
|
|
|
@ -4,6 +4,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
layout :session_layout
|
||||
after_action :after_sign_in, only: %i(create authenticate_with_two_factor)
|
||||
before_action :remove_authenticate_mesasge_if_root_path, only: :new
|
||||
prepend_before_action :skip_timeout, only: :expire_in
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken do
|
||||
redirect_to new_user_session_path
|
||||
|
@ -34,6 +35,17 @@ class Users::SessionsController < Devise::SessionsController
|
|||
generate_templates_project
|
||||
end
|
||||
|
||||
def expire_in
|
||||
if current_user.remember_created_at.nil? || (current_user.remember_created_at + Devise.remember_for).past?
|
||||
render plain: (Devise.timeout_in.to_i - (Time.now.to_i - user_session['last_request_at']).round) * 1000
|
||||
else
|
||||
render plain: [(Devise.remember_for - (Time.now.to_i - current_user.remember_created_at.to_i).round) * 1000,
|
||||
(Devise.timeout_in.to_i - (Time.now.to_i - user_session['last_request_at']).round) * 1000].max
|
||||
end
|
||||
end
|
||||
|
||||
def revive_session; end
|
||||
|
||||
def two_factor_recovery
|
||||
unless session[:otp_user_id]
|
||||
redirect_to new_user_session_path
|
||||
|
@ -92,6 +104,10 @@ class Users::SessionsController < Devise::SessionsController
|
|||
|
||||
private
|
||||
|
||||
def skip_timeout
|
||||
request.env['devise.skip_trackable'] = true
|
||||
end
|
||||
|
||||
def remove_authenticate_mesasge_if_root_path
|
||||
if session[:user_return_to] == root_path && flash[:alert] == I18n.t('devise.failure.unauthenticated')
|
||||
flash[:alert] = nil
|
||||
|
|
|
@ -196,6 +196,8 @@ module Users
|
|||
protocol.archived_by = new_owner unless protocol.archived_by.nil?
|
||||
protocol.restored_by = new_owner unless protocol.restored_by.nil?
|
||||
protocol.save!
|
||||
protocol.user_assignments.find_by(user: new_owner)&.destroy!
|
||||
protocol.user_assignments.create!(user: new_owner, user_role: UserRole.find_predefined_owner_role)
|
||||
end
|
||||
|
||||
# Make new owner author of all inventory items that were added
|
||||
|
|
|
@ -23,6 +23,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
def sortable_columns
|
||||
@sortable_columns ||= [
|
||||
"Protocol.name",
|
||||
"Protocol.id",
|
||||
"protocol_keywords_str",
|
||||
"Protocol.nr_of_linked_children",
|
||||
"full_username_str",
|
||||
|
@ -34,6 +35,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
def searchable_columns
|
||||
@searchable_columns ||= [
|
||||
"Protocol.name",
|
||||
"Protocol.#{Protocol::PREFIXED_ID_SQL}",
|
||||
timestamp_db_column,
|
||||
"Protocol.updated_at"
|
||||
]
|
||||
|
@ -56,9 +58,11 @@ class ProtocolsDatatable < CustomDatatable
|
|||
# now the method checks if the column is the created_at or updated_at and generate a custom SQL to parse
|
||||
# it back to the caller method
|
||||
def new_search_condition(column, value)
|
||||
model, column = column.split('.')
|
||||
model, column = column.split('.', 2)
|
||||
model = model.constantize
|
||||
case column
|
||||
when Protocol::PREFIXED_ID_SQL
|
||||
casted_column = ::Arel::Nodes::SqlLiteral.new(Protocol::PREFIXED_ID_SQL)
|
||||
when 'published_on'
|
||||
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
|
||||
|
@ -81,12 +85,6 @@ class ProtocolsDatatable < CustomDatatable
|
|||
protocol = Protocol.find(record.id)
|
||||
result_data << {
|
||||
'DT_RowId': record.id,
|
||||
'DT_CanEdit': can_manage_protocol_in_repository?(protocol),
|
||||
'DT_EditUrl': if can_manage_protocol_in_repository?(protocol)
|
||||
edit_protocol_path(protocol,
|
||||
team: @team,
|
||||
type: @type)
|
||||
end,
|
||||
'DT_CanClone': can_clone_protocol_in_repository?(protocol),
|
||||
'DT_CloneUrl': if can_clone_protocol_in_repository?(protocol)
|
||||
clone_protocol_path(protocol,
|
||||
|
@ -103,11 +101,12 @@ class ProtocolsDatatable < CustomDatatable
|
|||
else
|
||||
name_html(record)
|
||||
end,
|
||||
'2': keywords_html(record),
|
||||
'3': modules_html(record),
|
||||
'4': escape_input(record.full_username_str),
|
||||
'5': timestamp_column_html(record),
|
||||
'6': I18n.l(record.updated_at, format: :full)
|
||||
'2': escape_input(record.code),
|
||||
'3': keywords_html(record),
|
||||
'4': modules_html(record),
|
||||
'5': escape_input(record.full_username_str),
|
||||
'6': timestamp_column_html(record),
|
||||
'7': I18n.l(record.updated_at, format: :full)
|
||||
}
|
||||
end
|
||||
result_data
|
||||
|
|
|
@ -7,6 +7,7 @@ class ReportDatatable < CustomDatatable
|
|||
TABLE_COLUMNS = %w(
|
||||
Report.project_name
|
||||
Report.name
|
||||
Report.code
|
||||
Report.pdf_file
|
||||
Report.docx_file
|
||||
Report.created_by_name
|
||||
|
@ -40,6 +41,9 @@ class ReportDatatable < CustomDatatable
|
|||
records.left_joins(:pdf_file_attachment)
|
||||
.order(active_storage_attachments: sort_direction(order_params))
|
||||
.order(pdf_file_status: sort_direction(order_params) == 'ASC' ? :desc : :asc)
|
||||
when 'reports.code'
|
||||
sort_by = "reports.id #{sort_direction(order_params)}"
|
||||
records.order(sort_by)
|
||||
else
|
||||
sort_by = "#{sort_column(order_params)} #{sort_direction(order_params)}"
|
||||
records.order(sort_by)
|
||||
|
@ -54,12 +58,13 @@ class ReportDatatable < CustomDatatable
|
|||
'0' => record.id,
|
||||
'1' => sanitize_input(record.project_name),
|
||||
'2' => sanitize_input(record.name),
|
||||
'3' => pdf_file(record),
|
||||
'4' => docx_file(record),
|
||||
'5' => sanitize_input(record.created_by_name),
|
||||
'6' => sanitize_input(record.modified_by_name),
|
||||
'7' => I18n.l(record.created_at, format: :full),
|
||||
'8' => I18n.l(record.updated_at, format: :full),
|
||||
'3' => sanitize_input(record.code),
|
||||
'4' => pdf_file(record),
|
||||
'5' => docx_file(record),
|
||||
'6' => sanitize_input(record.created_by_name),
|
||||
'7' => sanitize_input(record.modified_by_name),
|
||||
'8' => I18n.l(record.created_at, format: :full),
|
||||
'9' => I18n.l(record.updated_at, format: :full),
|
||||
'archived' => record.project.archived?,
|
||||
'edit' => edit_project_report_path(record.project_id, record.id),
|
||||
'status' => status_project_report_path(record.project_id, record.id),
|
||||
|
@ -106,7 +111,7 @@ class ReportDatatable < CustomDatatable
|
|||
|
||||
def filter_records(records)
|
||||
records.where_attributes_like(
|
||||
['project_name', 'reports.name', 'reports.description'],
|
||||
['project_name', 'reports.name', 'reports.description', "('RP' || reports.id)"],
|
||||
dt_params.dig(:search, :value)
|
||||
)
|
||||
end
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
module CardsViewHelper
|
||||
def cards_view_type_class(view_type)
|
||||
view_type == 'table' ? 'list' : ''
|
||||
view_type == 'table' ? 'list' : 'cards'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,6 +48,10 @@ module ProjectsHelper
|
|||
records.sort_by { |c| c.name.downcase }
|
||||
when 'ztoa'
|
||||
records.sort_by { |c| c.name.downcase }.reverse!
|
||||
when 'id_asc'
|
||||
records.sort_by(&:id)
|
||||
when 'id_desc'
|
||||
records.sort_by(&:id).reverse!
|
||||
when 'archived_old'
|
||||
records.sort_by(&:archived_on)
|
||||
when 'archived_new'
|
||||
|
|
|
@ -93,9 +93,13 @@ module ReportsHelper
|
|||
when 'ztoa'
|
||||
results.order(name: :desc)
|
||||
when 'new'
|
||||
results.order(created_at: :desc)
|
||||
when 'old_updated'
|
||||
results.order(updated_at: :asc)
|
||||
when 'new_updated'
|
||||
results.order(updated_at: :desc)
|
||||
else
|
||||
results.order(updated_at: :asc)
|
||||
results.order(created_at: :asc)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,35 +7,37 @@ Vue.prototype.i18n = window.I18n;
|
|||
|
||||
function initPrintModalComponent() {
|
||||
const container = $('.print-label-modal-container');
|
||||
window.PrintModalComponent = new Vue({
|
||||
el: '.print-label-modal-container',
|
||||
name: 'PrintModalComponent',
|
||||
components: {
|
||||
'print-modal-container': PrintModalContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
row_ids: [],
|
||||
zebraEnabled: container.data('zebra-enabled'),
|
||||
urls: {
|
||||
print: container.data('print-url'),
|
||||
zebraProgress: container.data('zebra-progress-url'),
|
||||
printers: container.data('printers-url'),
|
||||
labelTemplates: container.data('label-templates-url'),
|
||||
rows: container.data('rows-url'),
|
||||
fluicsInfo: container.data('fluics-info-url'),
|
||||
printValidation: container.data('print-validation-url'),
|
||||
labelPreview: container.data('label-preview-url')
|
||||
if (container.length) {
|
||||
window.PrintModalComponent = new Vue({
|
||||
el: '.print-label-modal-container',
|
||||
name: 'PrintModalComponent',
|
||||
components: {
|
||||
'print-modal-container': PrintModalContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
row_ids: [],
|
||||
zebraEnabled: container.data('zebra-enabled'),
|
||||
urls: {
|
||||
print: container.data('print-url'),
|
||||
zebraProgress: container.data('zebra-progress-url'),
|
||||
printers: container.data('printers-url'),
|
||||
labelTemplates: container.data('label-templates-url'),
|
||||
rows: container.data('rows-url'),
|
||||
fluicsInfo: container.data('fluics-info-url'),
|
||||
printValidation: container.data('print-validation-url'),
|
||||
labelPreview: container.data('label-preview-url')
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initPrintModalComponent();
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
DPMM_RESOLUTION_OPTIONS,
|
||||
DPI_RESOLUTION_OPTIONS,
|
||||
optionsOpen: false,
|
||||
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm ,
|
||||
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm,
|
||||
height: this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm,
|
||||
unit: this.template.attributes.unit,
|
||||
density: this.template.attributes.density,
|
||||
|
@ -125,17 +125,22 @@
|
|||
},
|
||||
watch: {
|
||||
unit() {
|
||||
this.recalculateUnits();
|
||||
this.setDefaults();
|
||||
},
|
||||
zpl() {
|
||||
this.refreshPreview();
|
||||
},
|
||||
template() {
|
||||
this.unit = this.template.attributes.unit
|
||||
this.width = this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm
|
||||
this.height = this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm
|
||||
this.density = this.template.attributes.density
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setDefaults() {
|
||||
!this.unit && (this.unit = 'in');
|
||||
!this.density && (this.density = 8);
|
||||
!this.density && (this.density = 12);
|
||||
!this.width && (this.width = this.unit === 'in' ? 2 : 50.8);
|
||||
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
|
||||
},
|
||||
|
@ -151,6 +156,8 @@
|
|||
refreshPreview() {
|
||||
if (this.zpl.length === 0) return;
|
||||
|
||||
this.base64Image = null;
|
||||
|
||||
$.ajax({
|
||||
url: this.previewUrl,
|
||||
type: 'GET',
|
||||
|
@ -176,7 +183,9 @@
|
|||
});
|
||||
},
|
||||
updateUnit(unit) {
|
||||
if (this.unit === unit) return;
|
||||
this.unit = unit;
|
||||
this.recalculateUnits();
|
||||
this.$emit('unit:update', this.unit);
|
||||
},
|
||||
updateDensity(density) {
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
this.previewContent = this.labelTemplate.attributes.content
|
||||
this.newLabelWidth = this.labelTemplate.attributes.width_mm
|
||||
this.newLabelHeight = this.labelTemplate.attributes.height_mm
|
||||
this.newLabelDensity = this.labelTemplate.attributes.newDensity
|
||||
this.newLabelDensity = this.labelTemplate.attributes.density
|
||||
this.newLabelUnit = this.labelTemplate.attributes.unit
|
||||
})
|
||||
},
|
||||
|
@ -227,6 +227,8 @@
|
|||
updateContent() {
|
||||
this.previewValid = true;
|
||||
|
||||
this.saveLabelDimensions();
|
||||
|
||||
if (!this.editingContent) return;
|
||||
|
||||
if (this.skipSave) {
|
||||
|
@ -242,20 +244,39 @@
|
|||
type: 'PATCH',
|
||||
data: {label_template: {
|
||||
content: this.newContent,
|
||||
width_mm: this.newLabelWidth,
|
||||
height_mm: this.newLabelHeight,
|
||||
density: this.newLabelDensity,
|
||||
unit: this.newLabelUnit
|
||||
}},
|
||||
success: (result) => {
|
||||
this.labelTemplate.attributes.content = result.data.attributes.content;
|
||||
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
|
||||
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
|
||||
this.editingContent = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
saveLabelDimensions() {
|
||||
if (this.newLabelWidth == this.labelTemplate.attributes.width_mm &&
|
||||
this.newLabelHeight == this.labelTemplate.attributes.height_mm &&
|
||||
this.newLabelDensity == this.labelTemplate.attributes.density &&
|
||||
this.newLabelUnit == this.labelTemplate.attributes.unit) {
|
||||
return
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: this.labelTemplate.attributes.urls.update,
|
||||
type: 'PATCH',
|
||||
data: {label_template: {
|
||||
width_mm: this.newLabelWidth,
|
||||
height_mm: this.newLabelHeight,
|
||||
density: this.newLabelDensity,
|
||||
unit: this.newLabelUnit
|
||||
}},
|
||||
success: (result) => {
|
||||
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
|
||||
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
|
||||
this.labelTemplate.attributes.density = result.data.attributes.density;
|
||||
this.labelTemplate.attributes.unit = result.data.attributes.unit;
|
||||
}
|
||||
});
|
||||
},
|
||||
generatePreview(skipSave = false) {
|
||||
this.skipSave = skipSave;
|
||||
if (!skipSave && this.previewContent === this.newContent && this.previewValid) {
|
||||
|
|
|
@ -57,12 +57,12 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="step-actions-container">
|
||||
<div ref="actionsDropdownButton" v-if="urls.update_url" class="dropdown">
|
||||
<div ref="elementsDropdownButton" v-if="urls.update_url" class="dropdown">
|
||||
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'stepInserMenu_' + step.id" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
{{ i18n.t('protocols.steps.insert.button') }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul ref="actionsDropdown" class="dropdown-menu insert-element-dropdown dropdown-menu-right" :aria-labelledby="'stepInserMenu_' + step.id">
|
||||
<ul ref="elementsDropdown" class="dropdown-menu insert-element-dropdown dropdown-menu-right" :aria-labelledby="'stepInserMenu_' + step.id">
|
||||
<li class="title">
|
||||
{{ i18n.t('protocols.steps.insert.title') }}
|
||||
</li>
|
||||
|
@ -101,11 +101,11 @@
|
|||
</span>
|
||||
</a>
|
||||
<div v-if="urls.update_url" class="step-actions-container">
|
||||
<div class="dropdown">
|
||||
<div ref="actionsDropdownButton" class="dropdown">
|
||||
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'stepOptionsMenu_' + step.id" data-toggle="dropdown" data-display="static" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right insert-element-dropdown" :aria-labelledby="'stepOptionsMenu_' + step.id">
|
||||
<ul ref="actionsDropdown" class="dropdown-menu dropdown-menu-right insert-element-dropdown" :aria-labelledby="'stepOptionsMenu_' + step.id">
|
||||
<li class="title">
|
||||
{{ i18n.t('protocols.steps.options_dropdown.title') }}
|
||||
</li>
|
||||
|
@ -249,7 +249,12 @@
|
|||
mounted() {
|
||||
$(this.$refs.comments).data('closeCallback', this.closeCommentsSidebar);
|
||||
$(this.$refs.comments).data('openCallback', this.closeCommentsSidebar);
|
||||
$(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', this.handleDropdownPosition);
|
||||
$(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', () => {
|
||||
this.handleDropdownPosition(this.$refs.actionsDropdownButton, this.$refs.actionsDropdown)
|
||||
});
|
||||
$(this.$refs.elementsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', () => {
|
||||
this.handleDropdownPosition(this.$refs.elementsDropdownButton, this.$refs.elementsDropdown)
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
reorderableElements() {
|
||||
|
@ -438,8 +443,8 @@
|
|||
closeReorderModal() {
|
||||
this.reordering = false;
|
||||
},
|
||||
handleDropdownPosition() {
|
||||
this.$refs.actionsDropdownButton.classList.toggle("dropup", !this.isInViewport(this.$refs.actionsDropdown));
|
||||
handleDropdownPosition(refButton, refDropdown) {
|
||||
refButton.classList.toggle("dropup", !this.isInViewport(refDropdown));
|
||||
},
|
||||
isInViewport(el) {
|
||||
let rect = el.getBoundingClientRect();
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
</ul>
|
||||
<deleteAttachmentModal
|
||||
v-if="deleteModal"
|
||||
fileName="attachment.attributes.file_name"
|
||||
:fileName="attachment.attributes.file_name"
|
||||
@confirm="deleteAttachment"
|
||||
@cancel="deleteModal = false"
|
||||
/>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
},
|
||||
mounted() {
|
||||
if (this.isNew) {
|
||||
this.enableEditMode()
|
||||
this.enableEditMode();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
<div ref="modal" class="modal fade" id="modal-print-repository-row-label" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div v-if="printers_dropdown.length > 0" class="printers-available">
|
||||
<div v-if="availablePrinters.length > 0" class="printers-available">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p class="modal-title">
|
||||
<template v-if="rows.length == 1">
|
||||
{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}
|
||||
<b>{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}</b>
|
||||
<span class="id-label">
|
||||
{{ i18n.t('repository_row.modal_print_label.id_label', {repository_row_id: rows[0].attributes.code}) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}
|
||||
<b>{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}</b>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@
|
|||
</label>
|
||||
<DropdownSelector
|
||||
:disableSearch="true"
|
||||
:options="printers_dropdown"
|
||||
:options="availablePrinters"
|
||||
:selectorId="`LabelPrinterSelector`"
|
||||
@dropdown:changed="selectPrinter"
|
||||
/>
|
||||
|
@ -38,7 +38,7 @@
|
|||
<DropdownSelector
|
||||
ref="labelTemplateDropdown"
|
||||
:disableSearch="true"
|
||||
:options="templates_dropdown"
|
||||
:options="availableTemplates"
|
||||
:selectorId="`LabelTemplateSelector`"
|
||||
:optionLabel="templateOption"
|
||||
:onOpen="initTooltip"
|
||||
|
@ -138,7 +138,7 @@
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
templates_dropdown() {
|
||||
availableTemplates() {
|
||||
let templates = this.templates;
|
||||
if (this.selectedPrinter && this.selectedPrinter.attributes.type_of === 'zebra') {
|
||||
templates = templates.filter(i => i.attributes.type === 'ZebraLabelTemplate')
|
||||
|
@ -155,7 +155,7 @@
|
|||
}
|
||||
})
|
||||
},
|
||||
printers_dropdown() {
|
||||
availablePrinters() {
|
||||
return this.printers.map(i => {
|
||||
return {
|
||||
value: i.id,
|
||||
|
|
|
@ -85,6 +85,8 @@
|
|||
inEditMode() {
|
||||
if (this.inEditMode) {
|
||||
this.initTinymce();
|
||||
} else {
|
||||
this.wrapTables();
|
||||
}
|
||||
},
|
||||
characterCount() {
|
||||
|
@ -113,6 +115,8 @@
|
|||
mounted() {
|
||||
if (this.inEditMode) {
|
||||
this.initTinymce();
|
||||
} else {
|
||||
this.wrapTables();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -130,7 +134,7 @@
|
|||
if (data) {
|
||||
this.$emit('update', data)
|
||||
}
|
||||
this.$emit('editingDisabled')
|
||||
this.$emit('editingDisabled');
|
||||
}).then(() => {
|
||||
this.active = true;
|
||||
this.initCharacterCount();
|
||||
|
@ -140,6 +144,11 @@
|
|||
getStaticUrl(name) {
|
||||
return $(`meta[name=\'${name}\']`).attr('content');
|
||||
},
|
||||
wrapTables() {
|
||||
this.$nextTick(() => {
|
||||
$(this.$el).find('.tinymce-view table').css('float', 'none').wrapAll('<div style="overflow: auto"></div>');
|
||||
});
|
||||
},
|
||||
initCharacterCount() {
|
||||
if (!this.editorInstance()) return;
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ module UserAssignments
|
|||
assign_users_to_my_module(object)
|
||||
when Repository
|
||||
assign_users_to_repository(object)
|
||||
when Protocol
|
||||
assign_users_to_protocol(object)
|
||||
when Report
|
||||
assign_users_to_report(object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -41,6 +45,19 @@ module UserAssignments
|
|||
end
|
||||
end
|
||||
|
||||
def assign_users_to_protocol(protocol)
|
||||
return unless protocol.in_repository_public?
|
||||
|
||||
protocol.add_team_users_as_viewers!(@assigned_by)
|
||||
end
|
||||
|
||||
def assign_users_to_report(report)
|
||||
team = report.team
|
||||
team.user_assignments.find_each do |user_assignment|
|
||||
create_or_update_user_assignment(user_assignment, report)
|
||||
end
|
||||
end
|
||||
|
||||
def create_or_update_user_assignment(parent_user_assignment, object)
|
||||
user_role = parent_user_assignment.user_role
|
||||
user = parent_user_assignment.user
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class GroupAssignmentJob < ApplicationJob
|
||||
class ProjectGroupAssignmentJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(team, project, assigned_by)
|
||||
|
@ -18,7 +18,7 @@ module UserAssignments
|
|||
next if user_assignment.manually_assigned?
|
||||
|
||||
user_assignment.update!(
|
||||
user_role: project.default_public_user_role,
|
||||
user_role: project.default_public_user_role || UserRole.find_predefined_viewer_role,
|
||||
assigned_by: @assigned_by
|
||||
)
|
||||
|
||||
|
@ -26,7 +26,7 @@ module UserAssignments
|
|||
UserAssignments::PropagateAssignmentJob.perform_later(
|
||||
project,
|
||||
user,
|
||||
project.default_public_user_role,
|
||||
project.default_public_user_role || UserRole.find_predefined_viewer_role,
|
||||
@assigned_by
|
||||
)
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class GroupUnAssignmentJob < ApplicationJob
|
||||
class ProjectGroupUnAssignmentJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(project)
|
|
@ -1,13 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class RemoveTeamUserAssignmentJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(user, team)
|
||||
ActiveRecord::Base.transaction do
|
||||
RemoveTeamUserAssignmentService.new(user, team).call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class RemoveTeamUserAssignmentsJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(team_user_assignment)
|
||||
ActiveRecord::Base.transaction do
|
||||
RemoveTeamUserAssignmentsService.new(team_user_assignment).call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class UpdateTeamUserAssignmentJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(user, team, user_role)
|
||||
ActiveRecord::Base.transaction do
|
||||
UpdateTeamUserAssignmentService.new(user, team, user_role).call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserAssignments
|
||||
class UpdateTeamUserAssignmentsJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(team_user_assignment)
|
||||
ActiveRecord::Base.transaction do
|
||||
UpdateTeamUserAssignmentsService.new(team_user_assignment).call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ class FluicsLabelTemplate < LabelTemplate
|
|||
height_mm: 12.7,
|
||||
content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl],
|
||||
unit: 0,
|
||||
density: 8
|
||||
density: 12
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ class LabelPrinter < ApplicationRecord
|
|||
validates :type_of, presence: true
|
||||
validates :language_type, presence: true
|
||||
|
||||
def self.zebra_print_enabled?
|
||||
RepositoryBase.stock_management_enabled?.present?
|
||||
end
|
||||
|
||||
def done?
|
||||
current_print_job_ids.blank? && ready?
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ class LabelTemplate < ApplicationRecord
|
|||
validate :ensure_single_default_template!
|
||||
|
||||
def self.enabled?
|
||||
ApplicationSettings.instance.values['label_templates_enabled']
|
||||
RepositoryBase.stock_management_enabled?
|
||||
end
|
||||
|
||||
def icon
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MyModule < ApplicationRecord
|
||||
SEARCHABLE_ATTRIBUTES = ['my_modules.name', 'my_modules.description']
|
||||
ID_PREFIX = 'TA'
|
||||
include PrefixedIdModel
|
||||
SEARCHABLE_ATTRIBUTES = ['my_modules.name', 'my_modules.description', PREFIXED_ID_SQL].freeze
|
||||
|
||||
include ArchivableModel
|
||||
include SearchableModel
|
||||
|
@ -11,6 +13,9 @@ class MyModule < ApplicationRecord
|
|||
include Assignable
|
||||
include Cloneable
|
||||
|
||||
ID_PREFIX = 'TA'
|
||||
include PrefixedIdModel
|
||||
|
||||
attr_accessor :transition_error_rollback
|
||||
|
||||
enum state: Extends::TASKS_STATES
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Project < ApplicationRecord
|
||||
ID_PREFIX = 'PR'
|
||||
include PrefixedIdModel
|
||||
SEARCHABLE_ATTRIBUTES = ['projects.name', PREFIXED_ID_SQL].freeze
|
||||
|
||||
include ArchivableModel
|
||||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
|
@ -80,7 +86,7 @@ class Project < ApplicationRecord
|
|||
)
|
||||
|
||||
new_query = Project.viewable_by_user(user, current_team || user.teams)
|
||||
.where_attributes_like('projects.name', query, options)
|
||||
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
|
||||
new_query = new_query.active unless include_archived
|
||||
|
||||
# Show all results if needed
|
||||
|
@ -127,8 +133,8 @@ class Project < ApplicationRecord
|
|||
|
||||
def validate_view_state(view_state)
|
||||
if %w(cards table).exclude?(view_state.state.dig('experiments', 'view_type')) ||
|
||||
%w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
|
||||
%w(new old atoz ztoa id_asc id_desc).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa id_asc id_desc archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
|
||||
view_state.errors.add(:state, :wrong_state)
|
||||
end
|
||||
end
|
||||
|
@ -171,6 +177,8 @@ class Project < ApplicationRecord
|
|||
when 'old' then { created_at: :asc }
|
||||
when 'atoz' then { name: :asc }
|
||||
when 'ztoa' then { name: :desc }
|
||||
when 'id_asc' then { id: :asc }
|
||||
when 'id_desc' then { id: :desc }
|
||||
when 'archived_new' then { archived_on: :desc }
|
||||
when 'archived_old' then { archived_on: :asc }
|
||||
else { created_at: :desc }
|
||||
|
@ -326,7 +334,7 @@ class Project < ApplicationRecord
|
|||
def auto_assign_project_members
|
||||
return if skip_user_assignments
|
||||
|
||||
UserAssignments::GroupAssignmentJob.perform_now(
|
||||
UserAssignments::ProjectGroupAssignmentJob.perform_now(
|
||||
team,
|
||||
self,
|
||||
last_modified_by || created_by
|
||||
|
@ -345,7 +353,7 @@ class Project < ApplicationRecord
|
|||
if visible?
|
||||
auto_assign_project_members
|
||||
else
|
||||
UserAssignments::GroupUnAssignmentJob.perform_now(self)
|
||||
UserAssignments::ProjectGroupUnAssignmentJob.perform_now(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Protocol < ApplicationRecord
|
||||
ID_PREFIX = 'PT'
|
||||
include PrefixedIdModel
|
||||
SEARCHABLE_ATTRIBUTES = ['protocols.name', 'protocols.description',
|
||||
'protocols.authors', 'protocol_keywords.name', PREFIXED_ID_SQL].freeze
|
||||
|
||||
include SearchableModel
|
||||
include RenamingUtil
|
||||
include SearchableByNameModel
|
||||
|
@ -8,6 +13,7 @@ class Protocol < ApplicationRecord
|
|||
include PermissionCheckableModel
|
||||
include TinyMceImages
|
||||
|
||||
after_update :update_user_assignments, if: -> { saved_change_to_protocol_type? && in_repository? }
|
||||
after_destroy :decrement_linked_children
|
||||
after_save :update_linked_children
|
||||
skip_callback :create, :after, :create_users_assignments, if: -> { in_module? }
|
||||
|
@ -179,15 +185,7 @@ class Protocol < ApplicationRecord
|
|||
.joins('LEFT JOIN protocol_keywords ' \
|
||||
'ON protocol_keywords.id = ' \
|
||||
'protocol_protocol_keywords.protocol_keyword_id')
|
||||
.where_attributes_like(
|
||||
[
|
||||
'protocols.name',
|
||||
'protocols.description',
|
||||
'protocols.authors',
|
||||
'protocol_keywords.name'
|
||||
],
|
||||
query, options
|
||||
)
|
||||
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
|
||||
|
||||
# Show all results if needed
|
||||
if page == Constants::SEARCH_NO_LIMIT
|
||||
|
@ -657,8 +655,29 @@ class Protocol < ApplicationRecord
|
|||
steps.map(&:can_destroy?).all?
|
||||
end
|
||||
|
||||
def add_team_users_as_viewers!(assigned_by)
|
||||
viewer_role = UserRole.find_predefined_viewer_role
|
||||
team.user_assignments.where.not(user: assigned_by).find_each do |user_assignment|
|
||||
new_user_assignment = UserAssignment.find_or_initialize_by(user: user_assignment.user, assignable: self)
|
||||
new_user_assignment.user_role = viewer_role
|
||||
new_user_assignment.assigned_by = assigned_by
|
||||
new_user_assignment.assigned = :automatically
|
||||
new_user_assignment.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_user_assignments
|
||||
case protocol_type
|
||||
when 'in_repository_public'
|
||||
assigned_by = protocol_type_was == 'in_repository_archived' && restored_by || added_by
|
||||
add_team_users_as_viewers!(assigned_by)
|
||||
when 'in_repository_private', 'in_repository_archived'
|
||||
automatic_user_assignments.where.not(user: added_by).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
def deep_clone(clone, current_user)
|
||||
# Save cloned protocol first
|
||||
success = clone.save
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Report < ApplicationRecord
|
||||
ID_PREFIX = 'RP'
|
||||
include PrefixedIdModel
|
||||
SEARCHABLE_ATTRIBUTES = ['reports.name', 'reports.description', PREFIXED_ID_SQL].freeze
|
||||
|
||||
include SettingsModel
|
||||
include Assignable
|
||||
include PermissionCheckableModel
|
||||
|
@ -55,7 +59,7 @@ class Report < ApplicationRecord
|
|||
table_results: true,
|
||||
text_results: true,
|
||||
result_comments: true,
|
||||
result_order: 'atoz',
|
||||
result_order: 'new',
|
||||
activities: true
|
||||
}
|
||||
}.freeze
|
||||
|
@ -74,7 +78,7 @@ class Report < ApplicationRecord
|
|||
|
||||
new_query = Report.distinct
|
||||
.where(reports: { project_id: project_ids })
|
||||
.where_attributes_like(%i(name description), query, options)
|
||||
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
|
||||
|
||||
# Show all results if needed
|
||||
if page == Constants::SEARCH_NO_LIMIT
|
||||
|
@ -85,7 +89,8 @@ class Report < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.viewable_by_user(user, teams)
|
||||
where(project: Project.viewable_by_user(user, teams))
|
||||
with_granted_permissions(user, ReportPermissions::READ)
|
||||
.where(project: Project.viewable_by_user(user, teams))
|
||||
end
|
||||
|
||||
def self.filter_by_teams(teams = [])
|
||||
|
|
|
@ -125,24 +125,6 @@ class Step < ApplicationRecord
|
|||
st
|
||||
end
|
||||
|
||||
def asset_position(asset)
|
||||
assets.order(:updated_at).each_with_index do |step_asset, i|
|
||||
return { count: assets.count, pos: i } if asset.id == step_asset.id
|
||||
end
|
||||
end
|
||||
|
||||
def move_up
|
||||
return if position.zero?
|
||||
|
||||
move_in_protocol(:up)
|
||||
end
|
||||
|
||||
def move_down
|
||||
return if position == protocol.steps.count - 1
|
||||
|
||||
move_in_protocol(:down)
|
||||
end
|
||||
|
||||
def comments
|
||||
step_comments
|
||||
end
|
||||
|
@ -194,32 +176,6 @@ class Step < ApplicationRecord
|
|||
end
|
||||
private
|
||||
|
||||
def move_in_protocol(direction)
|
||||
transaction do
|
||||
re_index_following_steps
|
||||
|
||||
case direction
|
||||
when :up
|
||||
new_position = position - 1
|
||||
when :down
|
||||
new_position = position + 1
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
step_to_swap = protocol.steps.find_by(position: new_position)
|
||||
position_to_swap = position
|
||||
|
||||
if step_to_swap
|
||||
step_to_swap.update!(position: -1)
|
||||
update!(position: new_position)
|
||||
step_to_swap.update!(position: position_to_swap)
|
||||
else
|
||||
update!(position: new_position)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def adjust_positions_after_destroy
|
||||
re_index_following_steps
|
||||
protocol.steps.where('position > ?', position).order(:position).each do |step|
|
||||
|
|
|
@ -5,7 +5,11 @@ class StepText < ApplicationRecord
|
|||
include ActionView::Helpers::TextHelper
|
||||
|
||||
auto_strip_attributes :text, nullify: false
|
||||
validates :text, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
|
||||
validates :text, length:
|
||||
{
|
||||
maximum:
|
||||
ENV['STEP_TEXT_MAX_LENGTH'].present? ? ENV['STEP_TEXT_MAX_LENGTH'].to_i : Constants::RICH_TEXT_MAX_LENGTH
|
||||
}
|
||||
|
||||
belongs_to :step, inverse_of: :step_texts, touch: true
|
||||
has_one :step_orderable_element, as: :orderable, dependent: :destroy
|
||||
|
|
|
@ -84,8 +84,8 @@ class Team < ApplicationRecord
|
|||
end
|
||||
|
||||
def validate_view_state(view_state)
|
||||
if %w(new old atoz ztoa).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
|
||||
if %w(new old atoz ztoa id_asc id_desc).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa id_asc id_desc archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
|
||||
%w(cards table).exclude?(view_state.state.dig('projects', 'view_type'))
|
||||
view_state.errors.add(:state, :wrong_state)
|
||||
end
|
||||
|
|
|
@ -32,15 +32,12 @@ class TeamSharedObject < ApplicationRecord
|
|||
def not_globally_shared
|
||||
errors.add(:shared_object_id, :is_globally_shared) if shared_object.globally_shared?
|
||||
end
|
||||
|
||||
def assign_shared_inventories
|
||||
viewer_role = UserRole.find_by(name: UserRole.public_send('viewer_role').name)
|
||||
normal_user_role = UserRole.find_by(name: UserRole.public_send('normal_user_role').name)
|
||||
|
||||
team.users.find_each do |user|
|
||||
def assign_shared_inventories
|
||||
team.user_assignments.find_each do |user_assignment|
|
||||
shared_object.user_assignments.create!(
|
||||
user: user,
|
||||
user_role: shared_write? ? normal_user_role : viewer_role,
|
||||
user: user_assignment.user,
|
||||
user_role: user_assignment.user_role,
|
||||
team: team
|
||||
)
|
||||
end
|
||||
|
|
|
@ -403,7 +403,7 @@ class User < ApplicationRecord
|
|||
result = result.where.not(confirmed_at: nil) if active_only
|
||||
|
||||
if team_to_ignore.present?
|
||||
ignored_ids = UserTeam.select(:user_id).where(team_id: team_to_ignore.id)
|
||||
ignored_ids = UserAssignment.select(:user_id).where(assignable: team_to_ignore)
|
||||
result = result.where.not(users: { id: ignored_ids })
|
||||
end
|
||||
|
||||
|
@ -629,7 +629,7 @@ class User < ApplicationRecord
|
|||
query_teams = teams.pluck(:id)
|
||||
query_teams &= filters[:teams].map(&:to_i) if filters[:teams]
|
||||
query_teams &= User.team_by_subject(filters[:subjects]) if filters[:subjects]
|
||||
User.where(id: UserTeam.where(team_id: query_teams).select(:user_id))
|
||||
User.where(id: UserAssignment.where(assignable_id: query_teams, assignable_type: 'Team').select(:user_id))
|
||||
.search(false, search_query)
|
||||
.select(:full_name, :id)
|
||||
.map { |i| { label: escape_input(i[:full_name]), value: i[:id] } }
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
|
||||
class UserAssignment < ApplicationRecord
|
||||
before_validation -> { self.team ||= (assignable.is_a?(Team) ? assignable : assignable.team) }
|
||||
after_create :assign_shared_inventories, if: -> { assignable.is_a?(Team) }
|
||||
after_create :assign_public_projects, if: -> { assignable.is_a?(Team) }
|
||||
after_create :assign_team_child_objects, if: -> { assignable.is_a?(Team) }
|
||||
after_update :update_team_children_assignments, if: -> { assignable.is_a?(Team) && saved_change_to_user_role_id? }
|
||||
before_destroy :unassign_shared_inventories, if: -> { assignable.is_a?(Team) }
|
||||
before_destroy :unassign_team_child_objects, if: -> { assignable.is_a?(Team) }
|
||||
|
||||
belongs_to :assignable, polymorphic: true, touch: true
|
||||
|
@ -20,46 +18,15 @@ class UserAssignment < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def assign_shared_inventories
|
||||
viewer_role = UserRole.find_by(name: UserRole.public_send('viewer_role').name)
|
||||
normal_user_role = UserRole.find_by(name: UserRole.public_send('normal_user_role').name)
|
||||
|
||||
assignable.team_shared_repositories.find_each do |team_shared_repository|
|
||||
assignable.repository_sharing_user_assignments.create!(
|
||||
user: user,
|
||||
user_role: team_shared_repository.shared_write? ? normal_user_role : viewer_role,
|
||||
assignable: team_shared_repository.shared_object
|
||||
)
|
||||
end
|
||||
|
||||
Repository.globally_shared.find_each do |repository|
|
||||
assignable.repository_sharing_user_assignments.create!(
|
||||
user: user,
|
||||
user_role: repository.shared_write? ? normal_user_role : viewer_role,
|
||||
assignable: repository
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def assign_public_projects
|
||||
assignable.projects.visible.find_each do |project|
|
||||
UserAssignments::GroupAssignmentJob.perform_later(
|
||||
assignable,
|
||||
project,
|
||||
assigned_by
|
||||
)
|
||||
end
|
||||
def assign_team_child_objects
|
||||
UserAssignments::CreateTeamUserAssignmentsService.new(self).call
|
||||
end
|
||||
|
||||
def update_team_children_assignments
|
||||
UserAssignments::UpdateTeamUserAssignmentService.new(user, assignable, user_role).call
|
||||
end
|
||||
|
||||
def unassign_shared_inventories
|
||||
assignable.repository_sharing_user_assignments.where(user: user).find_each(&:destroy!)
|
||||
UserAssignments::UpdateTeamUserAssignmentsService.new(self).call
|
||||
end
|
||||
|
||||
def unassign_team_child_objects
|
||||
UserAssignments::RemoveTeamUserAssignmentService.new(user, assignable).call
|
||||
UserAssignments::RemoveTeamUserAssignmentsService.new(self).call
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,10 @@ class UserRole < ApplicationRecord
|
|||
predefined.find_by(name: UserRole.public_send('normal_user_role').name)
|
||||
end
|
||||
|
||||
def self.find_predefined_viewer_role
|
||||
predefined.find_by(name: UserRole.public_send('viewer_role').name)
|
||||
end
|
||||
|
||||
def owner?
|
||||
predefined? && name == I18n.t('user_roles.predefined.owner')
|
||||
end
|
||||
|
|
|
@ -57,7 +57,7 @@ class UserTeam < ApplicationRecord
|
|||
|
||||
def assign_user_to_visible_projects
|
||||
team.projects.visible.each do |project|
|
||||
UserAssignments::GroupAssignmentJob.perform_later(
|
||||
UserAssignments::ProjectGroupAssignmentJob.perform_later(
|
||||
team,
|
||||
project,
|
||||
assigned_by
|
||||
|
|
|
@ -8,7 +8,7 @@ class ZebraLabelTemplate < LabelTemplate
|
|||
height_mm: 12.7,
|
||||
content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl],
|
||||
unit: 0,
|
||||
density: 8
|
||||
density: 12
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,8 @@ Canaid::Permissions.register_for(Project) do
|
|||
export_project)
|
||||
.each do |perm|
|
||||
can perm do |user, project|
|
||||
project.permission_granted?(user, ProjectPermissions::READ)
|
||||
project.permission_granted?(user, ProjectPermissions::READ) ||
|
||||
project.team.permission_granted?(user, TeamPermissions::MANAGE)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ Canaid::Permissions.register_for(RepositoryBase) do
|
|||
# repository: read/export
|
||||
can :read_repository do |user, repository|
|
||||
if repository.is_a?(RepositorySnapshot)
|
||||
user.teams.include?(repository.team)
|
||||
can_read_my_module?(user, repository.my_module)
|
||||
else
|
||||
user.teams.include?(repository.team) || repository.shared_with?(user.current_team)
|
||||
end
|
||||
|
@ -24,6 +24,16 @@ Canaid::Permissions.register_for(Repository) do
|
|||
end
|
||||
end
|
||||
|
||||
%i(create_repository_rows
|
||||
manage_repository_rows
|
||||
manage_repository_assets
|
||||
delete_repository_rows)
|
||||
.each do |perm|
|
||||
can perm do |user, repository|
|
||||
repository.shared_with?(user.current_team) ? repository.shared_with_write?(user.current_team) : true
|
||||
end
|
||||
end
|
||||
|
||||
# repository: update, delete
|
||||
can :manage_repository do |user, repository|
|
||||
!repository.shared_with?(user.current_team) && repository.permission_granted?(user, RepositoryPermissions::MANAGE)
|
||||
|
@ -61,12 +71,7 @@ Canaid::Permissions.register_for(Repository) do
|
|||
next false if repository.is_a?(BmtRepository)
|
||||
next false if repository.archived?
|
||||
|
||||
if repository.shared_with?(user.current_team)
|
||||
repository.shared_with_write?(user.current_team) &&
|
||||
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
|
||||
else
|
||||
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
|
||||
end
|
||||
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
|
||||
end
|
||||
|
||||
can :manage_repository_assets do |user, repository|
|
||||
|
|
|
@ -72,9 +72,7 @@ end
|
|||
Canaid::Permissions.register_for(Protocol) do
|
||||
# protocol in repository: read, export, read step, read/download step asset
|
||||
can :read_protocol_in_repository do |user, protocol|
|
||||
user.member_of_team?(protocol.team) &&
|
||||
(protocol.in_repository_public? ||
|
||||
protocol.in_repository_private? && user == protocol.added_by)
|
||||
protocol.in_repository_active? && protocol.permission_granted?(user, ProtocolPermissions::READ)
|
||||
end
|
||||
|
||||
# protocol in repository: update, create/update/delete/reorder step,
|
||||
|
@ -96,11 +94,11 @@ end
|
|||
|
||||
Canaid::Permissions.register_for(Report) do
|
||||
can :read_report do |user, report|
|
||||
report.permission_granted?(user, ReportPermissions::READ)
|
||||
can_read_project?(report.project) && report.permission_granted?(user, ReportPermissions::READ)
|
||||
end
|
||||
|
||||
can :manage_report do |user, report|
|
||||
report.permission_granted?(user, ReportPermissions::MANAGE)
|
||||
can_read_project?(report.project) && report.permission_granted?(user, ReportPermissions::MANAGE)
|
||||
end
|
||||
|
||||
can :manage_report_users do |user, report|
|
||||
|
|
|
@ -106,6 +106,7 @@ module Dashboard
|
|||
ordered_query = ordered_query.where(query_filter) unless @mode == 'all'
|
||||
|
||||
recent_objects = ordered_query.as_json.map do |recent_object|
|
||||
object_class = override_subject_type(recent_object).constantize
|
||||
recent_object.deep_symbolize_keys!
|
||||
recent_object.delete_if { |_k, v| v.nil? }
|
||||
|
||||
|
@ -115,6 +116,10 @@ module Dashboard
|
|||
)
|
||||
recent_object[:subject_type] = override_subject_type(recent_object)
|
||||
recent_object[:name] = escape_input(recent_object[:name])
|
||||
recent_object[:type] = I18n.t("activerecord.models.#{object_class.name.underscore}")
|
||||
if object_class.include?(PrefixedIdModel)
|
||||
recent_object[:code] = object_class::ID_PREFIX + recent_object[:group_id][3..]
|
||||
end
|
||||
recent_object[:url] = generate_url(recent_object)
|
||||
recent_object
|
||||
end
|
||||
|
@ -231,7 +236,7 @@ module Dashboard
|
|||
end
|
||||
|
||||
def generate_url(recent_object)
|
||||
object_id = recent_object[:group_id].gsub(/[^0-9]/, '')
|
||||
object_id = recent_object.with_indifferent_access[:group_id].gsub(/[^0-9]/, '')
|
||||
|
||||
case recent_object[:subject_type]
|
||||
when 'MyModule'
|
||||
|
@ -250,17 +255,19 @@ module Dashboard
|
|||
end
|
||||
|
||||
def override_subject_type(recent_object)
|
||||
if recent_object[:group_id].include?('pro')
|
||||
group_id = recent_object.with_indifferent_access[:group_id]
|
||||
|
||||
if group_id.include?('pro')
|
||||
'Project'
|
||||
elsif recent_object[:group_id].include?('exp')
|
||||
elsif group_id.include?('exp')
|
||||
'Experiment'
|
||||
elsif recent_object[:group_id].include?('tsk')
|
||||
elsif group_id.include?('tsk')
|
||||
'MyModule'
|
||||
elsif recent_object[:group_id].include?('prt')
|
||||
elsif group_id.include?('prt')
|
||||
'Protocol'
|
||||
elsif recent_object[:group_id].include?('inv')
|
||||
elsif group_id.include?('inv')
|
||||
'RepositoryBase'
|
||||
elsif recent_object[:group_id].include?('rpt')
|
||||
elsif group_id.include?('rpt')
|
||||
'Report'
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue