Merge pull request #4884 from scinote-eln/develop

January 2023 Release
This commit is contained in:
artoscinote 2023-01-25 11:09:41 +01:00 committed by GitHub
commit 0c6e33e65b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
230 changed files with 5903 additions and 1656 deletions

View file

@ -64,11 +64,11 @@ gem 'aspector' # Aspect-oriented programming for Rails
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR
gem 'bcrypt', '~> 3.1.10'
gem 'caracal' # Build docx report
gem 'deface', '~> 1.0'
gem 'deface', '~> 1.9'
gem 'down', '~> 5.0'
gem 'faker' # Generate fake data
gem 'fastimage' # Light gem to get image resolution
gem 'httparty', '~> 0.17.3'
gem 'httparty', '~> 0.21.0'
gem 'i18n-js', '~> 3.6' # Localization in javascript files
gem 'jbuilder' # JSON structures via a Builder-style DSL
gem 'logging', '~> 2.0.0'
@ -97,15 +97,12 @@ gem 'devise-async',
git: 'https://github.com/mhfs/devise-async.git',
branch: 'devise-4.x'
gem 'image_processing', '~> 1.12'
gem 'img2zpl', git: 'https://github.com/scinote-eln/img2zpl'
gem 'rufus-scheduler', '~> 3.5'
gem 'discard', '~> 1.0'
gem 'graphviz'
gem 'tinymce-rails', '~> 4.9.10' # Rich text editor - SEE BELOW
# Any time you update tinymce-rails Gem, also update the cache_suffix parameter
# in sitewide/tiny_mce.js - to prevent browsers from loading old, cached .js
# TinyMCE files which might cause errors
gem 'base62' # Used for smart annotations
gem 'newrelic_rpm'

View file

@ -39,43 +39,50 @@ GIT
devise-async (0.10.2)
devise (>= 4.0)
GIT
remote: https://github.com/scinote-eln/img2zpl
revision: 23d61cfc3e90ac4caa62dd08546fa0d7590a5140
specs:
img2zpl (1.0.1)
mini_magick (~> 4.9)
GEM
remote: http://rubygems.org/
specs:
actioncable (6.1.6.1)
actionpack (= 6.1.6.1)
activesupport (= 6.1.6.1)
actioncable (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.6.1)
actionpack (= 6.1.6.1)
activejob (= 6.1.6.1)
activerecord (= 6.1.6.1)
activestorage (= 6.1.6.1)
activesupport (= 6.1.6.1)
actionmailbox (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (>= 2.7.1)
actionmailer (6.1.6.1)
actionpack (= 6.1.6.1)
actionview (= 6.1.6.1)
activejob (= 6.1.6.1)
activesupport (= 6.1.6.1)
actionmailer (6.1.7.1)
actionpack (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.6.1)
actionview (= 6.1.6.1)
activesupport (= 6.1.6.1)
actionpack (6.1.7.1)
actionview (= 6.1.7.1)
activesupport (= 6.1.7.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.6.1)
actionpack (= 6.1.6.1)
activerecord (= 6.1.6.1)
activestorage (= 6.1.6.1)
activesupport (= 6.1.6.1)
actiontext (6.1.7.1)
actionpack (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
nokogiri (>= 1.8.5)
actionview (6.1.6.1)
activesupport (= 6.1.6.1)
actionview (6.1.7.1)
activesupport (= 6.1.7.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -85,24 +92,24 @@ GEM
activemodel (>= 4.1, < 6.2)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (6.1.6.1)
activesupport (= 6.1.6.1)
activejob (6.1.7.1)
activesupport (= 6.1.7.1)
globalid (>= 0.3.6)
activemodel (6.1.6.1)
activesupport (= 6.1.6.1)
activerecord (6.1.6.1)
activemodel (= 6.1.6.1)
activesupport (= 6.1.6.1)
activemodel (6.1.7.1)
activesupport (= 6.1.7.1)
activerecord (6.1.7.1)
activemodel (= 6.1.7.1)
activesupport (= 6.1.7.1)
activerecord-import (1.0.7)
activerecord (>= 3.2)
activestorage (6.1.6.1)
actionpack (= 6.1.6.1)
activejob (= 6.1.6.1)
activerecord (= 6.1.6.1)
activesupport (= 6.1.6.1)
activestorage (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activesupport (= 6.1.7.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.6.1)
activesupport (6.1.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -245,11 +252,13 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.3)
debug_inspector (1.0.0)
deface (1.6.1)
deface (1.9.0)
actionview (>= 5.2)
nokogiri (>= 1.6)
polyglot
rails (>= 5.2)
railties (>= 5.2)
rainbow (>= 2.1.0)
delayed_job (4.1.9)
activesupport (>= 3.0, < 6.2)
@ -273,7 +282,7 @@ GEM
railties (>= 5)
down (5.2.0)
addressable (~> 2.5)
erubi (1.10.0)
erubi (1.12.0)
et-orbi (1.2.4)
tzinfo
execjs (2.7.0)
@ -298,17 +307,17 @@ GEM
raabro (~> 1.4)
generator (0.0.1)
gherkin (5.1.0)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
graphviz (1.2.1)
process-pipeline
hammerjs-rails (2.0.8)
hashdiff (1.0.1)
hashie (5.0.0)
httparty (0.17.3)
mime-types (~> 3.0)
httparty (0.21.0)
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.11.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
i18n-js (3.8.0)
i18n (>= 0.6.6)
@ -360,17 +369,20 @@ GEM
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mail (2.8.0.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.2)
method_source (1.0.0)
mime-types (3.3.1)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104)
mime-types-data (3.2022.0105)
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.2)
mini_portile2 (2.8.1)
minitest (5.17.0)
momentjs-rails (2.17.1)
railties (>= 3.1)
msgpack (1.4.2)
@ -382,6 +394,15 @@ GEM
coffee-rails (>= 3.2.1)
jquery-rails
rails (>= 3.2.0)
net-imap (0.3.4)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
timeout
net-smtp (0.3.3)
net-protocol
newrelic_rpm (6.15.0)
nio4r (2.5.8)
nokogiri (1.13.10)
@ -441,8 +462,8 @@ GEM
puma (5.6.4)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.1)
rack (2.2.4)
racc (1.6.2)
rack (2.2.6.2)
rack-attack (6.4.0)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
@ -451,20 +472,20 @@ GEM
rack
rack-test (2.0.2)
rack (>= 1.3)
rails (6.1.6.1)
actioncable (= 6.1.6.1)
actionmailbox (= 6.1.6.1)
actionmailer (= 6.1.6.1)
actionpack (= 6.1.6.1)
actiontext (= 6.1.6.1)
actionview (= 6.1.6.1)
activejob (= 6.1.6.1)
activemodel (= 6.1.6.1)
activerecord (= 6.1.6.1)
activestorage (= 6.1.6.1)
activesupport (= 6.1.6.1)
rails (6.1.7.1)
actioncable (= 6.1.7.1)
actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.7.1)
actionpack (= 6.1.7.1)
actiontext (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activemodel (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
bundler (>= 1.15.0)
railties (= 6.1.6.1)
railties (= 6.1.7.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -482,13 +503,13 @@ GEM
rails (> 3.1)
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (6.1.6.1)
actionpack (= 6.1.6.1)
activesupport (= 6.1.6.1)
railties (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
rainbow (3.0.0)
rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
@ -579,9 +600,9 @@ GEM
simplecov_json_formatter (0.1.2)
spinjs-rails (1.4)
rails (>= 3.1)
sprockets (4.1.1)
sprockets (4.2.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
@ -591,12 +612,11 @@ GEM
thor (1.2.1)
tilt (2.0.10)
timecop (0.9.2)
tinymce-rails (4.9.11)
railties (>= 3.1.1)
timeout (0.3.1)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks-source (5.2.0)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
@ -624,7 +644,7 @@ GEM
wkhtmltopdf-heroku (2.12.5.0)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.0)
zeitwerk (2.6.6)
PLATFORMS
ruby
@ -657,7 +677,7 @@ DEPENDENCIES
caracal
cucumber-rails (~> 1.8)
database_cleaner
deface (~> 1.0)
deface (~> 1.9)
delayed_job_active_record
devise (~> 4.7.1)
devise-async!
@ -671,9 +691,10 @@ DEPENDENCIES
figaro
graphviz
hammerjs-rails
httparty (~> 0.17.3)
httparty (~> 0.21.0)
i18n-js (~> 3.6)
image_processing (~> 1.12)
img2zpl!
jbuilder
jquery-rails
jquery-scrollto-rails!
@ -731,7 +752,6 @@ DEPENDENCIES
sneaky-save!
spinjs-rails
timecop
tinymce-rails (~> 4.9.10)
turbolinks (~> 5.1.1)
tzinfo-data
uglifier (>= 1.3.0)

View file

@ -1,4 +1,4 @@
Copyright (c) 2016 BioSistemika USA, LLC <info@biosistemika.com>
Copyright (c) 2016 SciNote, LLC <info@scinote.net>
SciNote is licensed under the following license:
@ -374,4 +374,4 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
defined by the Mozilla Public License, v. 2.0.

View file

@ -1 +1 @@
1.26.4
1.26.5

View file

@ -0,0 +1,3 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.447715 0.447715 0 1 0H13C13.5523 0 14 0.447715 14 1V7C14 7.55228 13.5523 8 13 8H1C0.447716 8 0 7.55228 0 7V1ZM2 6C2 5.44772 2.44772 5 3 5C3.55228 5 4 5.44772 4 6C4 6.55228 3.55228 7 3 7C2.44772 7 2 6.55228 2 6ZM7 5C6.44772 5 6 5.44772 6 6C6 6.55228 6.44772 7 7 7C7.55228 7 8 6.55228 8 6C8 5.44772 7.55228 5 7 5ZM10 6C10 5.44772 10.4477 5 11 5C11.5523 5 12 5.44772 12 6C12 6.55228 11.5523 7 11 7C10.4477 7 10 6.55228 10 6ZM3 1C2.44772 1 2 1.44772 2 2C2 2.55228 2.44772 3 3 3H11C11.5523 3 12 2.55228 12 2C12 1.44772 11.5523 1 11 1H3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 707 B

View file

@ -0,0 +1,5 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1C0 0.447715 0.447715 0 1 0H5C5.55228 0 6 0.447715 6 1V2C6 2.55228 5.55228 3 5 3H1C0.447715 3 0 2.55228 0 2V1Z" fill="#404048"/>
<path d="M0 6C0 5.44772 0.447715 5 1 5H5C5.55228 5 6 5.44772 6 6V7C6 7.55228 5.55228 8 5 8H1C0.447715 8 0 7.55228 0 7V6Z" fill="#404048"/>
<path d="M8 1C8 0.447715 8.44772 0 9 0H13C13.5523 0 14 0.447715 14 1V2C14 2.55228 13.5523 3 13 3H9C8.44772 3 8 2.55228 8 2V1Z" fill="#404048"/>
</svg>

After

Width:  |  Height:  |  Size: 525 B

View file

@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 1.5C11 2.32843 10.3284 3 9.5 3C8.84689 3 8.29127 2.5826 8.08535 2H2.91465C2.764 2.42621 2.42621 2.764 2 2.91465V8.08535C2.5826 8.29127 3 8.84689 3 9.5C3 10.3284 2.32843 11 1.5 11C0.671573 11 0 10.3284 0 9.5C0 8.84689 0.417404 8.29127 1 8.08535V2.91465C0.417404 2.70873 0 2.15311 0 1.5C0 0.671573 0.671573 0 1.5 0C2.15311 0 2.70873 0.417404 2.91465 1H8.08535C8.29127 0.417404 8.84689 0 9.5 0C10.3284 0 11 0.671573 11 1.5Z" fill="#404048"/>
</svg>

After

Width:  |  Height:  |  Size: 555 B

View file

@ -1,21 +1,6 @@
// turbolinks MUST BE THE LAST inclusion
//= require jquery
//= require jquery_ujs
//= require jquery.mousewheel.min
//= require jquery.scrollTo
//= require jquery.autosize
//= require jquery-ui/widget
//= require jquery-ui/widgets/mouse
//= require jquery-ui/widgets/draggable
//= require jquery-ui/widgets/droppable
//= require jquery.ui.touch-punch.min
//= require jquery-ui/effects/effect-slide
//= require jquery.caret.min
//= require jquery.atwho.min
//= require hammer
//= require js.cookie
//= require spin
//= require jquery.spin
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
@ -25,8 +10,6 @@
//= require typeahead.bundle.min
//= require nested_form_fields
//= require highlight.pack
//= require tinymce-jquery
//= require_tree ./tinymce/plugins
//= require jsPlumb-2.0.4-min
//= require jsnetworkx
//= require bootstrap-select
@ -257,9 +240,6 @@ var HelperModule = (function(){
$(document).on('turbolinks:load', function() {
/* Fix .selectpicker (bootstrap-select) to work with Turbolinks 5.x */
$(window).trigger('load.bs.select.data-api');
/* Clean up TinyMCE */
tinymce.remove();
});
// Show warning if page has unsaved data

View file

@ -0,0 +1,129 @@
/* global dropdownSelector initBSTooltips I18n */
(function() {
function initNewMyModuleModal() {
let experimentWrapper = '.experiment-new-my_module';
let newMyModuleModal = '#new-my-module-modal';
let myModuleUserSelector = '#my_module_user_ids';
var myModuleTagsSelector = '#module-tags-selector';
// Modal's submit handler function
$(experimentWrapper)
.on('ajax:success', newMyModuleModal, function() {
$(this).find('sci-input-container').removeClass('error');
$(newMyModuleModal).modal('hide');
})
.on('ajax:error', newMyModuleModal, function(ev, data) {
let errors = data.responseJSON;
$(this).find('sci-input-container').removeClass('error');
if (errors.name) {
$(this).find('#my_module_name')
.parent()
.addClass('error')
.attr('data-error-text', errors.name.join(', '));
}
})
.on('submit', newMyModuleModal, function() {
// To submit correct assigned user ids to new modal
// Clear default selected user in dropdown
$(`${myModuleUserSelector} option[value=${$('#new-my-module-modal').data('user-id')}]`)
.prop('selected', false);
$.map(dropdownSelector.getValues(myModuleUserSelector), function(val) {
$(`${myModuleUserSelector} option[value=${val}]`).prop('selected', true);
});
})
.on('ajax:success', '.new-my-module-button', function(ev, result) {
// Add and show modal
$(experimentWrapper).append($.parseHTML(result.html));
$(newMyModuleModal).modal('show');
$(newMyModuleModal).find("input[type='text']").focus();
// Remove modal when it gets closed
$(newMyModuleModal).on('hidden.bs.modal', function() {
$(newMyModuleModal).remove();
});
// initiaize user assing dropdown menu
dropdownSelector.init(myModuleUserSelector, {
closeOnSelect: true,
labelHTML: true,
tagClass: 'my-module-user-tags',
tagLabel: (data) => {
return `<img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/>
<span style="user-full-name block-inline">${data.label}</span>`;
},
optionLabel: (data) => {
if (data.params.avatar_url) {
return `<span class="global-avatar-container" style="margin-top: 10px">
<img src="${data.params.avatar_url}" alt="${data.label}"/></span>
<span style="margin-left: 10px">${data.label}</span>`;
}
return data.label;
}
});
dropdownSelector.selectValues(myModuleUserSelector, $('#new-my-module-modal').data('user-id'));
dropdownSelector.init($(myModuleTagsSelector), {
closeOnSelect: true,
tagClass: 'my-module-white-tags',
tagStyle: (data) => {
return `background: ${data.params.color}`;
},
customDropdownIcon: () => {
return '';
},
optionLabel: (data) => {
if (data.value > 0) {
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
${data.label}`;
}
return `<span class="my-module-tags-color new"><i class="fas fa-plus"></i></span>
${data.label + ' '}
<span class="my-module-tags-create-new"> ${I18n.t('my_modules.details.create_new_tag')}</span>`;
},
ajaxParams: function(params) {
let newParams = params;
newParams.selected_tags = JSON.stringify(dropdownSelector.getValues(myModuleTagsSelector));
return newParams;
},
onSelect: function() {
var selectElement = $(myModuleTagsSelector);
var lastTag = selectElement.next().find('.ds-tags').last();
var lastTagId = lastTag.find('.tag-label').data('ds-tag-id');
if (lastTagId > 0) {
$('#my_module_tag_ids').val(JSON.stringify(dropdownSelector.getValues(myModuleTagsSelector)));
} else {
let newTag = {
tag: {
name: lastTag.find('.tag-label').html(),
project_id: selectElement.data('project-id'),
color: null
},
simple_creation: true
};
$.post(selectElement.data('tags-create-url'), newTag, function(res) {
dropdownSelector.removeValue(myModuleTagsSelector, 0, '', true);
dropdownSelector.addValue(myModuleTagsSelector, {
value: res.tag.id,
label: res.tag.name,
params: {
color: res.tag.color
}
}, true);
$('#my_module_tag_ids').val(JSON.stringify(dropdownSelector.getValues(myModuleTagsSelector)));
}).fail(function() {
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
});
}
}
});
});
initBSTooltips();
}
initNewMyModuleModal();
}());

View file

@ -0,0 +1,832 @@
/* global I18n GLOBAL_CONSTANTS InfiniteScroll
initBSTooltips filterDropdown dropdownSelector Sidebar HelperModule notTurbolinksPreview */
var ExperimnetTable = {
permissions: ['editable', 'archivable', 'restorable', 'moveable'],
selectedId: [],
table: '.experiment-table',
render: {},
selectedMyModules: [],
activeFilters: {},
filters: [], // Filter {name: '', init(), closeFilter(), apply(), active(), clearFilter()}
myModulesCurrentSort: '',
pageSize: GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE,
provisioningStatusTimeout: '',
getUrls: function(id) {
return $(`.table-row[data-id="${id}"]`).data('urls');
},
loadPlaceholder: function() {
let placeholder = '';
$.each(Array(this.pageSize), function() {
placeholder += $('#experimentTablePlaceholder').html();
});
$(placeholder).insertAfter($(this.table).find('.table-body'));
},
appendRows: function(result) {
$.each(result, (_j, data) => {
let row;
const isProvisioning = data.provisioning_status === 'in_progress';
const provisioningTooltipAttrs = `title="${I18n.t('experiments.duplicate_tasks.duplicating')}"
data-toggle="tooltip"`;
// Checkbox selector
row = `
<div class="table-body-cell">
<div class="sci-checkbox-container">
<div title="${I18n.t('experiments.duplicate_tasks.duplicating')}"
class="loading-overlay" data-toggle="tooltip" data-placement="right"></div>
<input type="checkbox" class="sci-checkbox my-module-selector" data-my-module="${data.id}">
<span class="sci-checkbox-label"></span>
</div>
</div>`;
// Task columns
$.each(data.columns, (_i, cell) => {
let hidden = '';
if ($(`.table-display-modal .fa-eye-slash[data-column="${cell.column_type}"]`).length === 1) {
hidden = 'hidden';
}
row += `
<div class="table-body-cell ${cell.column_type}-column ${hidden}"
${cell.column_type === 'task_name' && isProvisioning ? provisioningTooltipAttrs : ''}>
${ExperimnetTable.render[cell.column_type](cell.data)}
</div>
`;
});
// Menu
row += `
<div class="table-body-cell">
<div ref="dropdown" class="dropdown my-module-menu" data-url="${data.urls.actions_dropdown}">
<div class="btn btn-ligh icon-btn open-my-module-menu" tabindex="0"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" >
<i class="fas fa-ellipsis-h"></i>
</div>
<div class="dropdown-menu dropdown-menu-right">
<a class="open-access-modal hidden" data-action="remote-modal" href="${data.urls.access}"></a>
</div>
</div>
</div>`;
let tableRowClass = `table-row ${isProvisioning ? 'table-row-provisioning' : ''}`;
$(`<div class="${tableRowClass}" data-urls='${JSON.stringify(data.urls)}' data-id="${data.id}">${row}</div>`)
.appendTo(`${this.table} .table-body`);
});
},
initDueDatePicker: function(data) {
// eslint-disable-next-line no-unused-vars
$.each(data, (_, row) => {
let element = `#calendarDueDate${row.id}`;
let dueDateContainer = $(element).closest('#dueDateContainer');
let dateText = $(element).closest('.date-text');
let clearDate = $(element).closest('.datetime-container').find('.clear-date');
$(element).on('dp.change', function() {
$.ajax({
url: dueDateContainer.data('update-url'),
type: 'PATCH',
dataType: 'json',
data: { my_module: { due_date: $(element).val() } },
success: function(result) {
dueDateContainer.find('#dueDateLabelContainer').html(result.table_due_date_label.html);
dateText.data('due-status', result.table_due_date_label.due_status);
if ($(result.table_due_date_label.html).data('due-date')) {
clearDate.addClass('open');
}
}
});
});
$(element).on('dp.hide', function() {
dateText.attr('data-original-title', dateText.data('due-status'));
clearDate.removeClass('open');
});
$(element).on('dp.show', function() {
var datePicker = $('.bootstrap-datetimepicker-widget.dropdown-menu')[0];
// show full datepicker menu for due date
if (datePicker.getBoundingClientRect().bottom > window.innerHeight) {
datePicker.scrollIntoView(false);
} else if (datePicker.getBoundingClientRect().top < 0) {
datePicker.scrollIntoView();
}
dateText.attr('data-original-title', '').tooltip('hide');
if (dueDateContainer.find('.due-date-label').data('due-date')) {
clearDate.addClass('open');
}
});
});
},
initMyModuleActions: function() {
$(this.table).on('show.bs.dropdown', '.my-module-menu', (e) => {
let menu = $(e.target).find('.dropdown-menu');
$.get(e.currentTarget.dataset.url, (result) => {
$(menu).find('li').remove();
$(result.html).appendTo(menu);
});
});
$(this.table).on('click', '.archive-my-module', (e) => {
e.preventDefault();
this.archiveMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
});
$(this.table).on('click', '.restore-my-module', (e) => {
e.preventDefault();
this.restoreMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
});
$(this.table).on('click', '.duplicate-my-module', (e) => {
e.preventDefault();
this.duplicateMyModules($('#duplicateTasks').data('url'), e.currentTarget.dataset.id);
});
$(this.table).on('click', '.move-my-module', (e) => {
e.preventDefault();
this.openMoveModulesModal([e.currentTarget.dataset.id]);
});
$(this.table).on('click', '.edit-my-module', (e) => {
e.preventDefault();
$('#modal-edit-module').modal('show');
$('#modal-edit-module').data('id', e.currentTarget.dataset.id);
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
});
},
initDuplicateMyModules: function() {
$('#duplicateTasks').on('click', (e) => {
this.duplicateMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
duplicateMyModules: function(url, ids) {
$.post(url, { my_module_ids: ids }, () => {
this.loadTable();
}).error((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
},
initArchiveMyModules: function() {
$('#archiveTask').on('click', (e) => {
this.archiveMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
archiveMyModules: function(url, ids) {
$.post(url, { my_modules: ids }, (data) => {
HelperModule.flashAlertMsg(data.message, 'success');
this.loadTable();
}).error((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
},
initRestoreMyModules: function() {
$('#restoreTask').on('click', (e) => {
this.restoreMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
});
},
restoreMyModules: function(url, ids) {
$.post(url, { my_modules_ids: ids, view: 'table' });
},
initRenameModal: function() {
$('#editTask').on('click', () => {
$('#modal-edit-module').modal('show');
$('#modal-edit-module').data('id', this.selectedMyModules[0]);
$('#edit-module-name-input').val($(`#taskName${$('#modal-edit-module').data('id')}`).data('full-name'));
});
const handleRenameModal = () => {
let id = $('#modal-edit-module').data('id');
let newValue = $('#edit-module-name-input').val();
$(`.my-module-selector[data-my-module="${id}"]`).trigger('click');
if (newValue === $(`#taskName${id}`).data('full-name')) {
$('#modal-edit-module').modal('hide');
return false;
}
$.ajax({
url: this.getUrls(id).name_update,
type: 'PATCH',
dataType: 'json',
data: { my_module: { name: $('#edit-module-name-input').val() } },
success: () => {
$(`#taskName${id}`).text(newValue);
$(`#taskName${id}`).data('full-name', newValue);
$('#edit-module-name-input').closest('.sci-input-container').removeClass('error');
$('#modal-edit-module').modal('hide');
},
error: function(response) {
let error = response.responseJSON.name.join(', ');
$('#edit-module-name-input')
.closest('.sci-input-container')
.addClass('error')
.attr('data-error-text', error);
}
});
if ($(`.my-module-selector[data-my-module="${id}"]`).prop('checked')) {
$(`.my-module-selector[data-my-module="${id}"]`).trigger('click');
}
this.clearRowTaskSelection();
return true;
};
$('#modal-edit-module')
.on('click', 'button[data-action="confirm"]', handleRenameModal)
.on('submit', 'form', (e) => {
e.preventDefault();
handleRenameModal();
});
},
initManageUsersDropdown: function() {
$(this.table).on('show.bs.dropdown', '.assign-users-dropdown', (e) => {
let usersList = $(e.target).find('.users-list');
let isArchivedView = $('#experimentTable').hasClass('archived');
let viewOnly = $(e.target).data('view-only');
let checkbox = '';
usersList.find('.user-container').remove();
$.get(usersList.data('list-url'), (result) => {
$.each(result, (_i, user) => {
if (!isArchivedView && !viewOnly) {
checkbox = `<div class="sci-checkbox-container">
<input type="checkbox"
class="sci-checkbox user-selector"
${user.params.designated ? 'checked' : ''}
value="${user.value}"
data-assign-url="${user.params.assign_url}"
data-unassign-url="${user.params.unassign_url}"
>
<span class="sci-checkbox-label"></span>
</div>`;
}
$(`
<div class="user-container">
${checkbox}
<div class="user-avatar ${isArchivedView ? 'archived' : ''}">
<img src="${user.params.avatar_url}"></img>
</div>
<div class="user-name">
${user.label}
</div>
</div>
`).appendTo(usersList);
});
});
});
$(this.table).on('click', '.assign-users-dropdown .dropdown-menu', (e) => {
if (e.target.tagName === 'INPUT') return;
e.preventDefault();
e.stopPropagation();
});
$(this.table).on('keyup', '.assigned-users-container, .open-my-module-menu, .calendar-input', (e) => {
if (e.keyCode === 13) { // Enter
e.currentTarget.click();
}
});
$(this.table).on('change keyup', '.assign-users-dropdown .user-search', (e) => {
let query = e.currentTarget.value;
let usersList = $(e.target).closest('.dropdown-menu').find('.user-container');
$.each(usersList, (_i, user) => {
$(user).removeClass('hidden');
if (query.length && !$(user).find('.user-name').text().toLowerCase()
.includes(query.toLowerCase())) {
$(user).addClass('hidden');
}
});
});
$(this.table).on('change', '.assign-users-dropdown .user-selector', (e) => {
let checkbox = e.target;
if (checkbox.checked) {
$.post(checkbox.dataset.assignUrl, {
table: true,
user_my_module: {
my_module_id: $(checkbox).closest('.table-row').data('id'),
user_id: checkbox.value
}
}, (result) => {
checkbox.dataset.unassignUrl = result.unassign_url;
$(checkbox).closest('.table-row').find('.assigned-users-container')
.replaceWith($(result.html).find('.assigned-users-container'));
}).error((data) => {
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
});
} else {
$.ajax({
url: checkbox.dataset.unassignUrl,
method: 'DELETE',
data: { table: true },
success: (result) => {
$(checkbox).closest('.table-row').find('.assigned-users-container')
.replaceWith($(result.html).find('.assigned-users-container'));
},
error: (data) => {
HelperModule.flashAlertMsg(data.responseJSON.errors, 'danger');
}
});
}
});
},
initMoveModulesModal: function() {
$('#moveTask').on('click', () => {
this.openMoveModulesModal(this.selectedMyModules);
});
},
openMoveModulesModal: function(ids) {
let table = $(this.table);
$.get(table.data('move-modules-modal-url'), (modalData) => {
if ($('#modal-move-modules').length > 0) {
$('#modal-move-modules').replaceWith(modalData.html);
} else {
$('#experimentTable').append(modalData.html);
}
$('#modal-move-modules').on('shown.bs.modal', function() {
$(this).find('.selectpicker').selectpicker().focus();
});
$('#modal-move-modules').on('click', 'button[data-action="confirm"]', () => {
let moveParams = {
to_experiment_id: $('#modal-move-modules').find('.selectpicker').val(),
my_module_ids: ids
};
$.post(table.data('move-modules-url'), moveParams, (data) => {
HelperModule.flashAlertMsg(data.message, 'success');
this.loadTable();
}).error((data) => {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
});
$('#modal-move-modules').modal('hide');
});
$('#modal-move-modules').modal('show');
});
},
checkActionPermission: function(permission) {
let allMyModules;
allMyModules = this.selectedMyModules.every((id) => {
return $(`.table-row[data-id="${id}"]`).data(permission);
});
return allMyModules;
},
initSelectAllCheckbox: function() {
$(this.table).on('click', '.select-all-checkboxes .sci-checkbox', (e1) => {
$.each($('.my-module-selector'), (_i, e2) => {
if (e1.target.checked !== e2.checked) e2.click();
});
});
},
loadPermission: function(id) {
let row = $(`.table-row[data-id="${id}"]`);
$.get(this.getUrls(id).permissions, (result) => {
this.permissions.forEach((permission) => {
row.data(permission, result[permission]);
});
this.updateExperimentToolbar();
});
},
initSelector: function() {
$(this.table).on('click', '.my-module-selector', (e) => {
let checkbox = e.target;
let myModuleId = checkbox.dataset.myModule;
let index = $.inArray(myModuleId, this.selectedMyModules);
// If checkbox is checked and row ID is not in list of selected project IDs
if (checkbox.checked && index === -1) {
$(checkbox).closest('.table-row').addClass('selected');
this.selectedMyModules.push(myModuleId);
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
} else if (!this.checked && index !== -1) {
$(checkbox).closest('.table-row').removeClass('selected');
this.selectedMyModules.splice(index, 1);
}
if (checkbox.checked) {
this.loadPermission(myModuleId);
} else {
this.updateExperimentToolbar();
}
});
},
updateExperimentToolbar: function() {
let experimentToolbar = $('.toolbar-row');
if (this.selectedMyModules.length === 0) {
experimentToolbar.find('.single-object-action, .multiple-object-action').addClass('hidden');
} else if (this.selectedMyModules.length === 1) {
experimentToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
} else {
experimentToolbar.find('.single-object-action').addClass('hidden');
experimentToolbar.find('.multiple-object-action').removeClass('hidden');
}
this.permissions.forEach((permission) => {
if (!this.checkActionPermission(permission)) {
experimentToolbar.find(`.btn[data-for="${permission}"]`).addClass('hidden');
}
});
if ($('#experimentTable').hasClass('archived')) {
experimentToolbar.find('.only-active').addClass('hidden');
}
},
selectDate: function($field) {
var datePicker = $field.data('DateTimePicker');
if (datePicker && datePicker.date()) {
return datePicker.date()._d.toUTCString();
}
return null;
},
initManageColumnsModal: function() {
$.each($('.table-display-modal .fa-eye-slash'), (_i, column) => {
$(column).parent().removeClass('visible');
});
$('.experiment-table')[0].style
.setProperty('--columns-count', $('.table-display-modal .fa-eye:not(.disabled)').length + 1);
$('.table-display-modal').on('click', '.column-container .fas', (e) => {
let icon = $(e.target);
if (icon.hasClass('fa-eye')) {
$(`.experiment-table .${icon.data('column')}-column`).addClass('hidden');
icon.removeClass('fa-eye').addClass('fa-eye-slash');
icon.parent().removeClass('visible');
} else {
$(`.experiment-table .${icon.data('column')}-column`).removeClass('hidden');
icon.addClass('fa-eye').removeClass('fa-eye-slash');
icon.parent().addClass('visible');
}
let visibleColumns = $('.table-display-modal .fa-eye').map((_i, col) => col.dataset.column).toArray();
// Update columns on backend - $.post('', { columns: visibleColumns }, () => {});
$.post($('.table-display-modal').data('column-state-url'), { columns: visibleColumns }, () => {});
$('.experiment-table')[0].style
.setProperty('--columns-count', $('.table-display-modal .fa-eye:not(.disabled)').length + 1);
});
},
clearRowTaskSelection: function() {
this.selectedMyModules = [];
$('.select-all-checkboxes .sci-checkbox').prop('checked', false);
this.updateExperimentToolbar();
},
initNewTaskModal: function(table) {
$('.experiment-new-my_module').on('ajax:success', '#new-my-module-modal', function() {
table.loadTable();
});
},
initSorting: function(table) {
$('#sortMenuDropdown a').click(function() {
if (table.myModulesCurrentSort !== $(this).data('sort')) {
$('#sortMenuDropdown a').removeClass('selected');
// eslint-disable-next-line no-param-reassign
table.myModulesCurrentSort = $(this).data('sort');
table.loadTable();
$(this).addClass('selected');
$('#sortMenu').dropdown('toggle');
}
});
},
initFilters: function() {
this.filterDropdown = filterDropdown.init();
let $experimentFilter = $('#experimentTable .my-modules-filters');
$.each(this.filters, (_i, filter) => {
filter.init($experimentFilter);
});
this.filterDropdown.on('filter:apply', () => {
$.each(this.filters, (_i, filter) => {
this.activeFilters[filter.name] = filter.apply($experimentFilter);
});
// filters are active if they have any non-empty value
let filtersEmpty = Object.values(this.activeFilters).every(value => /^\s+$/.test(value) || value === null || value === undefined || value && value.length === 0);
this.filtersActive = !filtersEmpty;
filterDropdown.toggleFilterMark(
this.filterDropdown,
this.filters.some((filter) => {
return filter.active(this.activeFilters[filter.name]);
})
);
this.loadTable();
});
this.filterDropdown.on('filter:clickBody', () => {
$.each(this.filters, (_i, filter) => {
filter.closeFilter($experimentFilter);
});
});
this.filterDropdown.on('filter:clear', () => {
$.each(this.filters, (_i, filter) => {
filter.clearFilter($experimentFilter);
});
});
},
loadTable: function() {
var tableParams = {
filters: this.activeFilters,
sort: this.myModulesCurrentSort
};
var dataUrl = $(this.table).data('my-modules-url');
$(this.table).find('.table-row').remove();
this.loadPlaceholder();
Sidebar.reload({
sort: this.myModulesCurrentSort,
view_mode: $('#experimentTable').hasClass('archived') ? 'archived' : ''
});
$.get(dataUrl, tableParams, (result) => {
$(this.table).find('.table-row-placeholder, .table-row-placeholder-divider').remove();
this.appendRows(result.data);
this.initDueDatePicker(result.data);
this.handleNoResults();
InfiniteScroll.init(this.table, {
url: dataUrl,
eventTarget: window,
placeholderTemplate: '#experimentTablePlaceholder',
endOfListTemplate: '#experimentTableEndOfList',
pageSize: this.pageSize,
lastPage: !result.next_page,
customResponse: (response) => {
this.appendRows(response.data);
this.initDueDatePicker(response.data);
this.initProvisioningStatusPolling();
},
customParams: (params) => {
return { ...params, ...tableParams };
}
});
initBSTooltips();
this.clearRowTaskSelection();
this.initProvisioningStatusPolling();
});
},
initProvisioningStatusPolling: function() {
let provisioningStatusUrls = $('.table-row-provisioning').toArray()
.map((u) => $(u).data('urls').provisioning_status)
.filter((u) => !!u);
this.provisioningMyModulesCount = provisioningStatusUrls.length;
if (this.provisioningMyModulesCount > 0) this.pollProvisioningStatuses(provisioningStatusUrls);
},
handleNoResults: function() {
let tableRowLength = document.getElementsByClassName('table-row').length;
let noResultsContainer = document.getElementById('tasksNoResultsContainer');
if (this.filtersActive && tableRowLength === 0) {
noResultsContainer.style.display = 'block';
} else {
noResultsContainer.style.display = 'none';
}
},
pollProvisioningStatuses: function(provisioningStatusUrls) {
let remainingUrls = [];
provisioningStatusUrls.forEach((url) => {
jQuery.ajax({
url: url,
success: (data) => {
if (data.provisioning_status === 'in_progress') remainingUrls.push(url);
},
async: false
});
});
if (remainingUrls.length > 0) {
clearTimeout(this.provisioningStatusTimeout);
this.provisioningStatusTimeout = setTimeout(() => {
this.pollProvisioningStatuses(remainingUrls);
}, 10000);
} else {
HelperModule.flashAlertMsg(
I18n.t('experiments.duplicate_tasks.success', { count: this.provisioningMyModulesCount }),
'success'
);
this.loadTable();
}
},
init: function() {
this.initSelector();
this.initSelectAllCheckbox();
this.initFilters();
this.initSorting(this);
this.loadTable();
this.initRenameModal();
this.initDuplicateMyModules();
this.initMoveModulesModal();
this.initArchiveMyModules();
this.initManageColumnsModal();
this.initNewTaskModal(this);
this.initMyModuleActions();
this.initRestoreMyModules();
this.initManageUsersDropdown();
}
};
ExperimnetTable.render.task_name = function(data) {
let tooltip = ` title="${data.name}" data-toggle="tooltip" data-placement="bottom"`;
if (data.provisioning_status === 'in_progress') {
return `<span data-full-name="${data.name}">${data.name}</span>`;
}
return `<a
href="${data.url}"
${tooltip}
title="${data.name}"
id="taskName${data.id}"
data-full-name="${data.name}">${data.name}</a>`;
};
ExperimnetTable.render.id = function(data) {
return `
<div>${data.id}</div>
`;
};
ExperimnetTable.render.due_date = function(data) {
return data.data;
};
ExperimnetTable.render.archived = function(data) {
return data;
};
ExperimnetTable.render.age = function(data) {
return data;
};
ExperimnetTable.render.results = function(data) {
return `<a href="${data.url}">${data.count}</a>`;
};
ExperimnetTable.render.status = function(data) {
return `<div class="my-module-status" style="background-color: ${data.color}">${data.name}</div>`;
};
ExperimnetTable.render.assigned = function(data) {
return data.html;
};
ExperimnetTable.render.tags = function(data) {
const value = parseInt(data.tags, 10) === 0 ? I18n.t('experiments.table.add_tag') : data.tags;
if (data.tags === 0 && !data.can_create) {
return `<span class="disabled">${I18n.t('experiments.table.not_set')}</span>`;
}
return `<a href="${data.edit_url}"
id="myModuleTags${data.my_module_id}"
data-remote="true"
class="edit-tags-link">${value}</a>`;
};
ExperimnetTable.render.comments = function(data) {
if (data.count === 0 && !data.can_create) return '<span class="disabled">0</span>';
return `<a href="#"
class="open-comments-sidebar" tabindex=0 id="comment-count-${data.id}"
data-object-type="MyModule" data-object-id="${data.id}">
${data.count > 0 ? data.count : '+'}
${data.count_unseen > 0 ? `<span class="unseen-comments"> ${data.count_unseen} </span>` : ''}
</a>`;
};
// Filters
ExperimnetTable.filters.push({
name: 'name',
init: () => {},
closeFilter: ($container) => {
$('#textSearchFilterHistory').hide();
$('#textSearchFilterInput', $container).closest('.dropdown').removeClass('open');
},
apply: ($container) => {
return $('#textSearchFilterInput', $container).val();
},
active: (value) => { return value; },
clearFilter: ($container) => {
$('#textSearchFilterInput', $container).val('');
}
});
ExperimnetTable.filters.push({
name: 'due_date_from',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.due-date-filter .from-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
if ($('.due-date-filter .from-date', $container).data('DateTimePicker')) {
$('.due-date-filter .from-date', $container).data('DateTimePicker').clear();
}
}
});
ExperimnetTable.filters.push({
name: 'due_date_to',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.due-date-filter .to-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
if ($('.due-date-filter .to-date', $container).data('DateTimePicker')) {
$('.due-date-filter .to-date', $container).data('DateTimePicker').clear();
}
}
});
ExperimnetTable.filters.push({
name: 'archived_on_from',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.archived-on-filter .from-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
if ($('.archived-on-filter .from-date', $container).data('DateTimePicker')) {
$('.archived-on-filter .from-date', $container).data('DateTimePicker').clear();
}
}
});
ExperimnetTable.filters.push({
name: 'archived_on_to',
init: () => {},
closeFilter: () => {},
apply: ($container) => {
return ExperimnetTable.selectDate($('.archived-on-filter .to-date', $container));
},
active: (value) => { return value; },
clearFilter: ($container) => {
if ($('.archived-on-filter .to-date', $container).data('DateTimePicker')) {
$('.archived-on-filter .to-date', $container).data('DateTimePicker').clear();
}
}
});
ExperimnetTable.filters.push({
name: 'assigned_users',
init: ($container) => {
dropdownSelector.init($('.assigned-filter', $container), {
optionClass: 'checkbox-icon users-dropdown-list',
optionLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
tagLabel: (data) => {
return `<img class="item-avatar" src="${data.params.avatar_url}"/> ${data.label}`;
},
labelHTML: true,
tagClass: 'users-dropdown-list'
});
},
closeFilter: ($container) => {
dropdownSelector.closeDropdown($('.assigned-filter', $container));
},
apply: ($container) => {
return dropdownSelector.getValues($('.assigned-filter', $container));
},
active: (value) => { return value && value.length !== 0; },
clearFilter: ($container) => {
dropdownSelector.clearData($('.assigned-filter', $container));
}
});
ExperimnetTable.filters.push({
name: 'statuses',
init: ($container) => {
dropdownSelector.init($('.status-filter', $container), {
singleSelect: true,
closeOnSelect: true,
selectAppearance: 'simple'
});
},
closeFilter: ($container) => {
dropdownSelector.closeDropdown($('.status-filter', $container));
},
apply: ($container) => {
return dropdownSelector.getValues($('.status-filter', $container));
},
active: (value) => { return value && value.length !== 0; },
clearFilter: ($container) => {
dropdownSelector.clearData($('.status-filter', $container));
}
});
if (notTurbolinksPreview()) {
ExperimnetTable.init();
}

15
app/assets/javascripts/jquery_bundle.js vendored Normal file
View file

@ -0,0 +1,15 @@
//= require jquery
//= require jquery_ujs
//= require jquery.mousewheel.min
//= require jquery.scrollTo
//= require jquery.autosize
//= require jquery-ui/widget
//= require jquery-ui/widgets/mouse
//= require jquery-ui/widgets/draggable
//= require jquery-ui/widgets/droppable
//= require jquery.ui.touch-punch.min
//= require jquery-ui/effects/effect-slide
//= require jquery.caret.min
//= require jquery.atwho.min
//= require spin
//= require jquery.spin

View file

@ -89,155 +89,6 @@
});
}
// Bind ajax for editing tags
function bindEditTagsAjax() {
var manageTagsModal = null;
var manageTagsModalBody = null;
// Initialize reloading of manage tags modal content after posting new
// tag.
function initAddTagForm() {
manageTagsModalBody.find('.add-tag-form')
.submit(function() {
var selectOptions = manageTagsModalBody.find('#new_my_module_tag .dropdown-menu li').length;
if (selectOptions === 0 && this.id === 'new_my_module_tag') return false;
return true;
})
.on('ajax:success', function(e, data) {
var newTag;
initTagsModalBody(data);
newTag = $('#manage-module-tags-modal .list-group-item').last();
dropdownSelector.addValue('#module-tags-selector', {
value: newTag.data('tag-id'),
label: newTag.data('name'),
params: {
color: newTag.data('color')
}
}, true);
});
}
// Initialize edit tag & remove tag functionality from my_module links.
function initTagRowLinks() {
manageTagsModalBody.find('.edit-tag-link')
.on('click', function() {
var $this = $(this);
var li = $this.parents('li.list-group-item');
var editDiv = $(li.find('div.tag-edit'));
// Revert all rows to their original states
manageTagsModalBody.find('li.list-group-item').each(function() {
var li2 = $(this);
li2.css('background-color', li2.data('color'));
li2.find('.edit-tag-form').clearFormErrors();
li2.find('input[type=text]').val(li2.data('name'));
});
// Hide all other edit divs, show all show divs
manageTagsModalBody.find('div.tag-edit').hide();
manageTagsModalBody.find('div.tag-show').show();
editDiv.find('input[type=text]').val(li.data('name'));
editDiv.find('.edit-tag-color').colorselector('setColor', li.data('color'));
li.find('div.tag-show').hide();
editDiv.show();
});
manageTagsModalBody.find('div.tag-edit .dropdown-colorselector > .dropdown-menu li a')
.on('click', function() {
// Change background of the <li>
var $this = $(this);
var li = $this.parents('li.list-group-item');
li.css('background-color', $this.data('value'));
});
manageTagsModalBody.find('.remove-tag-link')
.on('ajax:success', function(e, data) {
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
initTagsModalBody(data);
});
manageTagsModalBody.find('.delete-tag-form')
.on('ajax:success', function(e, data) {
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
initTagsModalBody(data);
});
manageTagsModalBody.find('.edit-tag-form')
.on('ajax:success', function(e, data) {
var newTag;
initTagsModalBody(data);
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
newTag = $('#manage-module-tags-modal .list-group-item[data-tag-id=' + this.dataset.tagId + ']');
dropdownSelector.addValue('#module-tags-selector', {
value: newTag.data('tag-id'),
label: newTag.data('name'),
params: {
color: newTag.data('color')
}
}, true);
})
.on('ajax:error', function(e, data) {
$(this).renderFormErrors('tag', data.responseJSON);
});
manageTagsModalBody.find('.cancel-tag-link')
.on('click', function() {
var $this = $(this);
var li = $this.parents('li.list-group-item');
li.css('background-color', li.data('color'));
li.find('.edit-tag-form').clearFormErrors();
li.find('div.tag-edit').hide();
li.find('div.tag-show').show();
});
}
// Initialize ajax listeners and elements style on modal body. This
// function must be called when modal body is changed.
function initTagsModalBody(data) {
manageTagsModalBody.html(data.html);
manageTagsModalBody.find('.selectpicker').selectpicker();
initAddTagForm();
initTagRowLinks();
}
manageTagsModal = $('#manage-module-tags-modal');
manageTagsModalBody = manageTagsModal.find('.modal-body');
// Reload tags HTML element when modal is closed
manageTagsModal.on('hide.bs.modal', function() {
var tagsEl = $('#module-tags');
// Load HTML
$.ajax({
url: tagsEl.attr('data-module-tags-url'),
type: 'GET',
dataType: 'json',
success: function(data) {
var newOptions = $(data.html_module_header).find('option');
$('#module-tags-selector').find('option').remove();
$(newOptions).appendTo('#module-tags-selector').change();
},
error: function() {
// TODO
}
});
});
// Remove modal content when modal window is closed.
manageTagsModal.on('hidden.bs.modal', function() {
manageTagsModalBody.html('');
});
// initialize my_module tab remote loading
$('.edit-tags-link')
.on('ajax:before', function() {
manageTagsModal.modal('show');
})
.on('ajax:success', function(e, data) {
$('#manage-module-tags-modal-module').text(data.my_module.name);
initTagsModalBody(data);
});
}
function checkStatusState() {
$.getJSON($('.status-flow-dropdown').data('status-check-url'), (statusData) => {
if (statusData.status_changing) {
@ -297,9 +148,9 @@
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
${data.label}`;
}
return `<span class="my-module-tags-color"></span>
${data.label + ' '}
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`;
return `<span class="my-module-tags-color new"><i class="fas fa-plus"></i></span>
${data.label + ' '}
<span class="my-module-tags-create-new"> ${I18n.t('my_modules.details.create_new_tag')}</span>`;
},
onOpen: function() {
$('.select-container .edit-button-container').removeClass('hidden');
@ -440,7 +291,6 @@
initTaskCollapseState();
applyTaskStatusChangeCallBack();
initTagsSelector();
bindEditTagsAjax();
initStartDatePicker();
initDueDatePicker();
initAssignedUsersSelector();

View file

@ -18,19 +18,21 @@ function initEditMyModuleDescription() {
if ($(this).hasClass('record-info-link')) return;
e.stopPropagation();
});
TinyMCE.initIfHasDraft(viewObject);
setTimeout(function() {
TinyMCE.wrapTables(viewObject);
}, 100);
}
function initEditProtocolDescription() {
var viewObject = $('#protocol_description_view');
viewObject.on('click', function(e) {
if ($(e.target).hasClass('record-info-link')) return;
TinyMCE.init('#protocol_description_textarea', refreshProtocolStatusBar);
TinyMCE.init('#protocol_description_textarea', { afterInitCallback: refreshProtocolStatusBar });
}).on('click', 'a', function(e) {
if ($(this).hasClass('record-info-link')) return;
e.stopPropagation();
});
TinyMCE.initIfHasDraft(viewObject);
}
function initCopyToRepository() {

View file

@ -138,6 +138,7 @@
}
function processResult(ev, resultTypeEnum) {
var textWithoutImages;
var $form = $(ev.target.form);
$form.clearFormErrors();
@ -153,9 +154,11 @@
.removeClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
break;
case ResultTypeEnum.TEXT:
textWithoutImages = TinyMCE.getContent().replaceAll(/src="(data:image\/[^;]+;base64[^"]+)"/i, '');
textValidator(
ev, $form.find('#result_text_attributes_textarea'), 1,
$form.data('rich-text-max-length'), false, TinyMCE.getContent()
$form.data('rich-text-max-length'), false, textWithoutImages
);
break;
default:

View file

@ -0,0 +1,158 @@
/* global dropdownSelector I18n */
/* eslint-disable no-use-before-define */
(function() {
// Bind ajax for editing tags
function bindEditTagsAjax() {
var manageTagsModal = null;
var manageTagsModalBody = null;
// Initialize reloading of manage tags modal content after posting new
// tag.
function initAddTagForm() {
manageTagsModalBody.find('.add-tag-form')
.submit(function() {
var selectOptions = manageTagsModalBody.find('#new_my_module_tag .dropdown-menu li').length;
if (selectOptions === 0 && this.id === 'new_my_module_tag') return false;
return true;
})
.on('ajax:success', function(e, data) {
var newTag;
initTagsModalBody(data);
newTag = $('#manage-module-tags-modal .list-group-item').last();
dropdownSelector.addValue('#module-tags-selector', {
value: newTag.data('tag-id'),
label: newTag.data('name'),
params: {
color: newTag.data('color')
}
}, true);
});
}
// Initialize edit tag & remove tag functionality from my_module links.
function initTagRowLinks() {
manageTagsModalBody.find('.edit-tag-link')
.on('click', function() {
var $this = $(this);
var li = $this.parents('li.list-group-item');
var editDiv = $(li.find('div.tag-edit'));
// Revert all rows to their original states
manageTagsModalBody.find('li.list-group-item').each(function() {
var li2 = $(this);
li2.css('background-color', li2.data('color'));
li2.find('.edit-tag-form').clearFormErrors();
li2.find('input[type=text]').val(li2.data('name'));
});
// Hide all other edit divs, show all show divs
manageTagsModalBody.find('div.tag-edit').hide();
manageTagsModalBody.find('div.tag-show').show();
editDiv.find('input[type=text]').val(li.data('name'));
editDiv.find('.edit-tag-color').colorselector('setColor', li.data('color'));
li.find('div.tag-show').hide();
editDiv.show();
});
manageTagsModalBody.find('div.tag-edit .dropdown-colorselector > .dropdown-menu li a')
.on('click', function() {
// Change background of the <li>
var $this = $(this);
var li = $this.parents('li.list-group-item');
li.css('background-color', $this.data('value'));
});
manageTagsModalBody.find('.remove-tag-link')
.on('ajax:success', function(e, data) {
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
initTagsModalBody(data);
});
manageTagsModalBody.find('.delete-tag-form')
.on('ajax:success', function(e, data) {
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
initTagsModalBody(data);
});
manageTagsModalBody.find('.edit-tag-form')
.on('ajax:success', function(e, data) {
var newTag;
initTagsModalBody(data);
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
newTag = $('#manage-module-tags-modal .list-group-item[data-tag-id=' + this.dataset.tagId + ']');
dropdownSelector.addValue('#module-tags-selector', {
value: newTag.data('tag-id'),
label: newTag.data('name'),
params: {
color: newTag.data('color')
}
}, true);
})
.on('ajax:error', function(e, data) {
$(this).renderFormErrors('tag', data.responseJSON);
});
manageTagsModalBody.find('.cancel-tag-link')
.on('click', function() {
var $this = $(this);
var li = $this.parents('li.list-group-item');
li.css('background-color', li.data('color'));
li.find('.edit-tag-form').clearFormErrors();
li.find('div.tag-edit').hide();
li.find('div.tag-show').show();
});
}
// Initialize ajax listeners and elements style on modal body. This
// function must be called when modal body is changed.
function initTagsModalBody(data) {
manageTagsModalBody.html(data.html);
manageTagsModalBody.find('.selectpicker').selectpicker();
initAddTagForm();
initTagRowLinks();
}
manageTagsModal = $('#manage-module-tags-modal');
manageTagsModalBody = manageTagsModal.find('.modal-body');
// Reload tags HTML element when modal is closed
manageTagsModal.on('hide.bs.modal', function() {
var tagsEl = $('#module-tags');
if ($('#experimentTable').length) {
let tags = $('.tag-show').length;
$(`#myModuleTags${$('#tags_modal_my_module_id').val()}`).html(
tags === 0 ? I18n.t('experiments.table.add_tag') : tags
);
}
// Load HTML
$.ajax({
url: tagsEl.attr('data-module-tags-url'),
type: 'GET',
dataType: 'json',
success: function(data) {
var newOptions = $(data.html_module_header).find('option');
$('#module-tags-selector').find('option').remove();
$(newOptions).appendTo('#module-tags-selector').change();
},
error: function() {
// TODO
}
});
});
// Remove modal content when modal window is closed.
manageTagsModal.on('hidden.bs.modal', function() {
manageTagsModalBody.html('');
});
// initialize my_module tab remote loading
$('#experimentTable, .my-modules-protocols-index')
.on('ajax:before', '.edit-tags-link', function() {
manageTagsModal.modal('show');
})
.on('ajax:success', '.edit-tags-link', function(e, data) {
$('#manage-module-tags-modal-module').text(data.my_module.name);
initTagsModalBody(data);
});
}
bindEditTagsAjax();
}());

View file

@ -332,6 +332,8 @@ function initializeFullZoom() {
commentMenu.position({ top: $(this).parent().position().top });
commentMenu.offset({ top: $(this).parent().offset().top + <%= Constants::DROPDOWN_TOP_OFFSET_PX %> });
});
initializeCanvasViewNavigator();
}
function destroyFullZoom() {
@ -370,6 +372,7 @@ function initializeMediumZoom() {
// Restore draggable position
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
initializeCanvasViewNavigator();
}
function destroyMediumZoom() {
@ -397,6 +400,7 @@ function initializeSmallZoom() {
// Restore draggable position
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
initializeCanvasViewNavigator();
}
function destroySmallZoom() {
@ -595,7 +599,7 @@ function resizeContainer() {
if (cont.length > 0) {
cont.css(
"height",
($(window).height() - cont.offset().top - 15) + "px"
($(window).height() - cont.offset().top) + "px"
);
}
}
@ -2064,7 +2068,9 @@ function cloneModule(originalModule, gridDistX, gridDistY, left, top) {
var newModule = createVirtualModule();
elLeft(newModule, left);
elTop(newModule, top);
updateModuleHtml(newModule, id, originalModule.data("module-name"), gridDistX, gridDistY);
updateModuleHtml(newModule, id,
`${I18n.t('experiments.canvas.edit.clone_prefix')} ${originalModule.data('module-name')}`,
gridDistX, gridDistY);
newModule.removeClass("new");
// Add the cloned module id into the hidden input field
@ -2661,6 +2667,9 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
y_pos = y_el + (fastOffsetY - y_start);
x_start = fastOffsetX;
y_start = fastOffsetY;
drawRectangleCanvasNavigatorView(-x_pos, -y_pos)
if (draggable !== null) {
elLeft(draggable, x_pos);
elTop(draggable, y_pos);
@ -2913,6 +2922,79 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
}
})();
function drawCanvasViewNavigatorImage(image_src){
var canvasImage = $('.canvas-preview-img')[0];
var canvasRect = $('.canvas-preview-rect')[0];
var canvasImageTx = canvasImage.getContext('2d');
var canvasRectTx = canvasRect.getContext('2d');
var image = new Image();
image.onload = function() {
canvasImageTx.drawImage(image, 0, 0, canvasImage.width, canvasImage.height);
drawRectangleCanvasNavigatorView(-(draggable.offset().left - draggable.parent().offset().left),
-(draggable.offset().top - draggable.parent().offset().top));
canvasRectTx.stroke();
};
image.src = image_src;
}
function initializeCanvasViewNavigator() {
if ($('.canvas-preview-img').data('image-url')) {
drawCanvasViewNavigatorImage($('.canvas-preview-img').data('image-url'));
} else if ($('.canvas-preview-img').data('workflowimg-present') === false) {
let imgUrl = $('.canvas-preview-img').data('workflowimg-url');
$.ajax({
url: imgUrl,
type: 'GET',
dataType: 'json',
success: function(data) {
drawCanvasViewNavigatorImage($(data.workflowimg).attr('src'));
}
});
}
}
function drawRoundRectangle(ctx, xPos, yPos, width, height, radius) {
width = Math.max(width, 0)
height = Math.max(height, 0)
if (width < 2 * radius) radius = width / 2;
if (height < 2 * radius) radius = height / 2;
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = '#104DA9';
ctx.moveTo(xPos + radius, yPos);
ctx.arcTo(xPos + width, yPos, xPos + width, yPos + height, radius);
ctx.arcTo(xPos + width, yPos + height, xPos, yPos + height, radius);
ctx.arcTo(xPos, yPos + height, xPos, yPos, radius);
ctx.arcTo(xPos, yPos, xPos + width, yPos, radius);
ctx.stroke();
ctx.closePath();
}
function drawRectangleCanvasNavigatorView(xPos, yPos) {
var adjustFactor = 10;
var canvasSize = calculateDraggableSize(draggable);
var ratioX = xPos / canvasSize.width;
var ratioY = yPos / canvasSize.height;
var canvasPreviewRect = $('.canvas-preview-rect')[0];
if (canvasPreviewRect) {
var canvasRectTx = canvasPreviewRect.getContext('2d');
var canvasWidth = canvasRectTx.canvas.width;
var canvasHeight = canvasRectTx.canvas.height;
var previewWidth = canvasWidth * ($('#diagram-container').width() / canvasSize.width);
var previewHeight = canvasHeight * ($('#diagram-container').height() / canvasSize.height);
canvasRectTx.clearRect(0, 0, canvasWidth, canvasHeight);
canvasRectTx.beginPath();
drawRoundRectangle(canvasRectTx, canvasWidth * ratioX + adjustFactor, canvasHeight * ratioY + adjustFactor,
previewWidth - adjustFactor, previewHeight - adjustFactor, 4)
}
}
/** prevent reload page */
var preventCanvasReloadOnSave = (function() {
'use strict';

View file

@ -317,11 +317,16 @@
function initNewExperimentToolbarButton() {
let forms = '.new-experiment-form';
$(experimentsPage)
.on('submit', forms, function() {
$(this).find("button[type='submit']").prop('disabled', true);
})
.on('ajax:success', forms, function(ev, data) {
appendActionModal($(data.html));
$(this).find("button[type='submit']").prop('disabled', false);
})
.on('ajax:error', forms, function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
$(this).find("button[type='submit']").prop('disabled', false);
});
}

View file

@ -29,7 +29,6 @@ var ProtocolRepositoryHeader = (function() {
if ($(this).hasClass('record-info-link')) return;
e.stopPropagation();
});
TinyMCE.initIfHasDraft(viewObject);
}
return {

View file

@ -72,20 +72,8 @@ function importProtocolFromFile(
return template;
}
function addChildToPreviewElement(parentEl, name, childEl) {
parentEl.find("[data-hold='" + name + "']").append(childEl);
}
function hidePartOfElement(element, name) {
element.find("[data-toggle='" + name + "']").hide();
}
function showPartOfElement(element, name) {
element.find("[data-toggle='" + name + "']").show();
}
function newAssetElement(folder, stepGuid, fileRef, fileName, fileType) {
var html = '<li>';
var html = '<li class="col-xs-12">';
var assetBytes;
if ($.inArray(fileType, ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']) > 0) {
assetBytes = getAssetBytes(folder, stepGuid, fileRef);
@ -176,6 +164,8 @@ function importProtocolFromFile(
var stepName = node.children('name').text();
var checklistNodes;
var tableNodes;
var assetNodes;
var fileHeader;
var stepDescription = displayTinyMceAssetInDescription(
node,
protocolFolders[position],
@ -191,39 +181,6 @@ function importProtocolFromFile(
}
);
// Iterate through step assets
var assetNodes = node.find('assets > asset');
if (assetNodes.length > 0) {
assetNodes.each(function() {
var fileRef = $(this).attr('fileRef');
var fileName = $(this).children('fileName').text();
var fileType = $(this).children('fileType').text();
var assetEl = newAssetElement(
protocolFolders[position],
stepGuid,
fileRef,
fileName,
fileType
);
// Append asset element to step
addChildToPreviewElement(stepEl, 'assets', assetEl);
});
} else {
hidePartOfElement(stepEl, 'assets');
}
// Iterate through step tables
tableNodes = node.find('elnTables > elnTable');
if (tableNodes.length > 0) {
tableNodes.each(function() {
addTablePreview(stepEl, this);
});
} else {
hidePartOfElement(stepEl, 'tables');
}
// Iterate through step checklists
checklistNodes = node.find('checklists > checklist');
if (checklistNodes.length > 0) {
@ -232,21 +189,26 @@ function importProtocolFromFile(
});
}
// Iterate through step tables
tableNodes = node.find('elnTables > elnTable');
if (tableNodes.length > 0) {
tableNodes.each(function() {
addTablePreview(stepEl, this);
});
}
// Parse step elements
$(this).find('stepElements > stepElement').each(function() {
$(this).find('stepElements > stepElement').sort(stepComparator).each(function() {
$element = $(this);
switch ($(this).attr('type')) {
case 'Checklist':
addChecklistPreview(stepEl, $(this).find('checklist'));
showPartOfElement(stepEl, 'checklists');
break;
case 'StepTable':
addTablePreview(stepEl, $(this).find('elnTable'));
showPartOfElement(stepEl, 'tables');
break;
case 'StepText':
addStepTextPreview(stepEl, $(this).find('stepText'), protocolFolders[position], stepGuid);
showPartOfElement(stepEl, 'step-texts');
break;
default:
// nothing to do
@ -254,6 +216,32 @@ function importProtocolFromFile(
}
});
// Iterate through step assets
assetNodes = node.find('assets > asset');
if (assetNodes.length > 0) {
fileHeader = newPreviewElement('asset-file-name', null);
stepEl.append(fileHeader);
assetNodes.each(function() {
var fileRef = $(this).attr('fileRef');
var fileName = $(this).children('fileName').text();
var fileType = $(this).children('fileType').text();
var assetEl;
assetEl = newAssetElement(
protocolFolders[position],
stepGuid,
fileRef,
fileName,
fileType
);
// Append asset element to step
stepEl.append(assetEl);
});
}
// Append step element to preview container
previewContainer.append(stepEl);
});
@ -270,10 +258,10 @@ function importProtocolFromFile(
{ name: tableName }
);
var elnTableEl = generateElnTable(tableId, tableContent);
addChildToPreviewElement(tableEl, 'table', elnTableEl);
tableEl.append(elnTableEl);
// Now, append table element to step
addChildToPreviewElement(stepEl, 'tables', tableEl);
stepEl.append(tableEl);
}
function addChecklistPreview(stepEl, checklistNode) {
@ -294,11 +282,11 @@ function importProtocolFromFile(
'checklist-item',
{ text: itemText }
);
addChildToPreviewElement(checklistEl, 'checklist-items', itemEl);
checklistEl.append(itemEl);
});
// Now, add checklist item to step
addChildToPreviewElement(stepEl, 'checklists', checklistEl);
stepEl.append(stepEl, checklistEl);
}
function addStepTextPreview(stepEl, stepTextNode, folder, stepGuid) {
@ -311,7 +299,7 @@ function importProtocolFromFile(
{ text: itemText }
);
addChildToPreviewElement(stepEl, 'step-texts', textEl);
stepEl.append(textEl);
}
// display tiny_mce_assets in step description
@ -743,7 +731,7 @@ function importProtocolFromFile(
// Parse step elements
stepJson.stepElements = [];
$(this).find('stepElements > stepElement').each(function() {
$(this).find('stepElements > stepElement').sort(stepComparator).each(function() {
stepJson.stepElements.push(stepElementJson($(this), index, stepGuid));
});

View file

@ -42,7 +42,11 @@
// On init
initHandsOnTable($(document));
TinyMCE.highlight();
$('[class*=language]').each((i, block) => {
hljs.highlightBlock(block);
});
SmartAnnotation.preventPropagation('.atwho-user-popover');
$(function () {
@ -116,4 +120,4 @@
reorderAttachmentsInit();
initAssetViewModeToggle();
})();
});

View file

@ -1020,7 +1020,7 @@ function reportHandsonTableConverter() {
});
// Project content
reportData.project_content = { experiments: [], repositories: [] };
reportData.project_content = { experiments: [] };
$.each($('.project-contents-container .experiment-element'), function(i, experiment) {
let expCheckbox = $(experiment).find('.report-experiment-checkbox');
if (!expCheckbox.prop('checked') && !expCheckbox.prop('indeterminate')) return;
@ -1034,10 +1034,6 @@ function reportHandsonTableConverter() {
reportData.project_content.experiments.push(experimentData);
});
$.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), function(i, e) {
reportData.project_content.repositories.push(parseInt(e.value, 10));
});
// Settings
reportData.report.settings.template = dropdownSelector.getValues('#templateSelector');
reportData.report.settings.all_tasks = $('.project-contents-container .select-all-my-modules-checkbox')
@ -1048,6 +1044,10 @@ function reportHandsonTableConverter() {
$.each($('.task-contents-container .content-element .task-setting'), function(i, e) {
reportData.report.settings.task[e.value] = e.checked;
});
reportData.report.settings.task.repositories = [];
$.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), function(i, e) {
reportData.report.settings.task.repositories.push(parseInt(e.value, 10));
});
reportData.report.settings.task.result_order = dropdownSelector.getValues('#taskResultsOrder');

View file

@ -412,10 +412,10 @@ var RepositoryDatatable = (function(global) {
// Adjust columns width in table header
function adjustTableHeader() {
TABLE.columns.adjust();
$('.dropdown-menu').parent()
.on('shown.bs.dropdown hidden.bs.dropdown', function() {
TABLE.columns.adjust();
});
// $('.dropdown-menu').parent()
// .on('shown.bs.dropdown hidden.bs.dropdown', function() {
// TABLE.columns.adjust();
// });
}
function checkSnapshottingStatus() {
@ -664,9 +664,9 @@ var RepositoryDatatable = (function(global) {
initActiveRemindersFilter();
renderFiltersDropdown();
setTimeout(function() {
adjustTableHeader();
}, 500);
// setTimeout(function() {
// adjustTableHeader();
// }, 500);
}
});
@ -699,11 +699,11 @@ var RepositoryDatatable = (function(global) {
})
initRowSelection();
$(window).resize(() => {
setTimeout(() => {
adjustTableHeader();
}, 500);
});
// $(window).resize(() => {
// setTimeout(() => {
// adjustTableHeader();
// }, 500);
// });
return TABLE;
}
@ -822,7 +822,7 @@ var RepositoryDatatable = (function(global) {
});
changeToEditMode();
adjustTableHeader();
// adjustTableHeader();
})
.on('click', '#deleteRepositoryRecords', function() {
$('#deleteRepositoryRecord').modal('show');
@ -909,9 +909,9 @@ var RepositoryDatatable = (function(global) {
document.documentElement.style.setProperty('--repository-sidebar-margin', '363px');
});
$('#wrapper').on('sideBar::hidden sideBar::shown', function() {
adjustTableHeader();
});
// $('#wrapper').on('sideBar::hidden sideBar::shown', function() {
// adjustTableHeader();
// });
}
function renderFiltersDropdown() {

View file

@ -71,6 +71,7 @@ var inlineEditing = (function() {
data: params,
success: function(result) {
var viewData;
var parentContainer = container.parent();
if (container.data('response-field')) {
// If we want to modify preview element on backend
// we can use this data field and we will take string from response
@ -95,11 +96,15 @@ var inlineEditing = (function() {
.attr('value', inputField(container).val());
appendAfterLabel(container);
container.trigger('inlineEditing::updated', [inputField(container).val(), viewData])
container.trigger('inlineEditing::updated', [inputField(container).val(), viewData]);
if (SIDEBAR_ITEM_TYPES.includes(paramsGroup)) {
updateSideBarNav(paramsGroup, itemId, viewData);
}
if (parentContainer.attr('data-original-title')) {
parentContainer.attr('data-original-title', inputField(container).val());
}
},
error: function(response) {
var error = response.responseJSON[fieldToUpdate];
@ -111,6 +116,7 @@ var inlineEditing = (function() {
container.find('.error-block').html(error.join(', '));
inputField(container).focus();
container.data('disabled', false);
$('.tooltip').hide();
}
});
return true;
@ -127,26 +133,38 @@ var inlineEditing = (function() {
$(document)
.off('click', editBlocks)
.off('keyup', `${editBlocks}`)
.off('click', `${editBlocks} .save-button`)
.off('click', `${editBlocks} .cancel-button`)
.off('blur', `${editBlocks} textarea, ${editBlocks} input`)
.on('keyup', `${editBlocks}`, function(e) {
var container = $(this);
if (e.keyCode === 27) {
$(`${editBlocks} .cancel-button`).click();
} // Esc
if (e.keyCode === 13 && !container.find('.view-mode').hasClass('hidden')) {
$(editBlocks).click();
}
})
.on('click', editBlocks, function(e) {
// 'A' mean that, if we click on <a></a> element we will not go in edit mode
var container = $(this);
if (e.target.tagName === 'A') return true;
if (inputField(container).attr('disabled')) {
saveAllEditFields();
inputField(container)
.attr('disabled', false)
let input = inputField(container);
input.attr('disabled', false)
.removeClass('hidden')
.focus();
input[0].selectionStart = input[0].value.length;
input[0].selectionEnd = input[0].value.length;
container
.attr('data-edit-mode', '1');
container.find('.view-mode')
.addClass('hidden')
.closest('.inline_scroll_block')
.scrollTop(container.offsetTop);
$('.tooltip').hide();
}
e.stopPropagation();
return true;

View file

@ -20,6 +20,7 @@ var CommentsSidebar = (function() {
$(SIDEBAR).find('.comment-input-container').removeClass('hidden');
} else {
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
$(SIDEBAR).find('.comment-input-container').addClass('update-only');
}
});
}
@ -28,7 +29,8 @@ var CommentsSidebar = (function() {
var commentsAmount = $(SIDEBAR).find('.comments-list .comment-container').length;
if (commentsCounter.length) {
// Replace the number in comment element
commentsCounter.text(commentsCounter.text().replace(/\d+/g, commentsAmount));
commentsCounter.text(commentsCounter.text().replace(/[\d\\+]+/g, commentsAmount));
commentsCounter.removeClass('hidden');
}
}
@ -37,6 +39,7 @@ var CommentsSidebar = (function() {
commentsCounter = $(`#comment-count-${$(this).data('objectId')}`);
closeCallback = $(this).data('closeCallback');
CommentsSidebar.open($(this).data('objectType'), $(this).data('objectId'));
$(this).parent().find('.unseen-comments').remove();
e.preventDefault();
});
}
@ -86,6 +89,9 @@ var CommentsSidebar = (function() {
}
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
if ($(SIDEBAR).find('.comment-input-container').hasClass('update-only')) {
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
}
$('.error-container').empty();
updateCounter();
},
@ -100,6 +106,9 @@ var CommentsSidebar = (function() {
$(document).on('click', `${SIDEBAR} .cancel-button`, function() {
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
if ($(SIDEBAR).find('.comment-input-container').hasClass('update-only')) {
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
}
});
}
@ -127,6 +136,9 @@ var CommentsSidebar = (function() {
$('.comment-container').removeClass('edit');
$(this).closest('.comment-container').addClass('edit');
$(SIDEBAR).find('.sidebar-footer').addClass('update');
if ($(SIDEBAR).find('.comment-input-container').hasClass('hidden')) {
$(SIDEBAR).find('.comment-input-container').removeClass('hidden');
}
$(SIDEBAR).find('.comment-input-field')
.val($(this).data('comment-raw'))
.data('update-url', $(this).data('update-url'));
@ -152,7 +164,7 @@ var CommentsSidebar = (function() {
open: function(objectType, objectId) {
$(SIDEBAR).find('.comments-subject-title').empty();
$(SIDEBAR).find('.comments-list').empty();
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.comment-input-field').val('').focus();
$('.error-container').empty();
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
$(SIDEBAR).data('object-type', objectType).data('object-id', objectId);

View file

@ -6,16 +6,21 @@
ev.stopPropagation();
let dt = $(this);
let options = { ignoreReadonly: true };
if (dt.data('DateTimePicker')) {
dt.data('DateTimePicker').destroy();
}
dt.datetimepicker({ ignoreReadonly: true });
if (dt.data('positioningVertical')) {
options.widgetPositioning = { vertical: dt.data('positioningVertical') };
}
dt.datetimepicker(options);
dt.data('DateTimePicker').show();
});
$(document).on('click', '[data-toggle="clear-date-time-picker"]', function() {
$(document).on('mousedown', '[data-toggle="clear-date-time-picker"]', function() {
let dt = $(`#${$(this).data('target')}`);
if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false });
dt.data('DateTimePicker').clear();

View file

@ -44,18 +44,20 @@ var filterDropdown = (function() {
} catch (error) {
console.error(error);
}
}).on('hide.bs.dropdown', function() {
$('#textSearchFilterHistory').hide();
$('.apply-filters', $filterContainer).click();
}).on('hide.bs.dropdown', function(e) {
if (e.target === e.currentTarget) {
$('#textSearchFilterHistory').hide();
$('.apply-filters', $filterContainer).click();
}
});
$textFilter.click(function(e) {
e.stopPropagation();
$('#textSearchFilterHistory').toggle();
$(this).closest('.dropdown').toggleClass('open');
}).on('input', () => {
$(e.currentTarget).closest('.dropdown').toggleClass('open');
}).on('input', (e) => {
$('#textSearchFilterHistory').hide();
$(this).closest('.dropdown').removeClass('open');
$(e.currentTarget).closest('.dropdown').removeClass('open');
});
$filterContainer.on('click', '.projects-search-keyword', function(e) {

View file

@ -161,7 +161,7 @@ var MarvinJsEditorApi = (function() {
} else if (config.objectType === 'Result') {
location.reload();
} else if (config.objectType === 'TinyMceAsset') {
json = tinymce.util.JSON.parse(result);
json = JSON.parse(result);
config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json));
TinyMCE.updateImages(config.editor);
}
@ -210,6 +210,20 @@ var MarvinJsEditorApi = (function() {
});
}
function createNewMarvinContainer(dataset) {
var objectId = dataset.objectId;
var objectType = dataset.objectType;
var marvinUrl = dataset.marvinUrl;
var container = dataset.sketchContainer;
MarvinJsEditor.open({
mode: 'new',
objectId: objectId,
objectType: objectType,
marvinUrl: marvinUrl,
container: container
});
}
// MarvinJS Methods
return {
@ -254,17 +268,13 @@ var MarvinJsEditorApi = (function() {
initNewButton: function(selector, saveCallback) {
$(selector).off('click').on('click', function() {
var objectId = this.dataset.objectId;
var objectType = this.dataset.objectType;
var marvinUrl = this.dataset.marvinUrl;
var container = this.dataset.sketchContainer;
MarvinJsEditor.open({
mode: 'new',
objectId: objectId,
objectType: objectType,
marvinUrl: marvinUrl,
container: container
});
createNewMarvinContainer(this.dataset);
});
$(selector).off('keypress').on('keypress', function(e) {
if (e.which === 13) {
createNewMarvinContainer(this.dataset);
}
});
MarvinJsEditor.saveCallback = saveCallback;
@ -280,47 +290,6 @@ var MarvinJsEditorApi = (function() {
};
});
// TinyMCE plugin
(function() {
'use strict';
tinymce.PluginManager.requireLangPack('MarvinJsPlugin');
tinymce.create('tinymce.plugins.MarvinJsPlugin', {
MarvinJsPlugin: function(ed) {
var editor = ed;
function openMarvinJs() {
MarvinJsEditor.open({
mode: 'new-tinymce',
marvinUrl: '/tiny_mce_assets/marvinjs',
editor: editor
});
}
// Add a button that opens a window
editor.addButton('marvinjsplugin', {
tooltip: I18n.t('marvinjs.new_button'),
icon: 'marvinjs',
onclick: openMarvinJs
});
// Adds a menu item to the tools menu
editor.addMenuItem('marvinjsplugin', {
text: I18n.t('marvinjs.new_button'),
icon: 'marvinjs',
context: 'insert',
onclick: openMarvinJs
});
}
});
tinymce.PluginManager.add(
'marvinjsplugin',
tinymce.plugins.MarvinJsPlugin
);
})();
// Initialization
$(document).on('click', '.marvinjs-edit-button', function() {
var editButton = $(this);
@ -337,7 +306,7 @@ $(document).on('click', '.marvinjs-edit-button', function() {
$(document).on('turbolinks:load', function() {
MarvinJsEditor = MarvinJsEditorApi();
if (MarvinJsEditor.enabled()) {
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote' && typeof(ChemicalizeMarvinJs) !== 'undefined') {
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote' && typeof (ChemicalizeMarvinJs) !== 'undefined') {
ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
marvin.setDisplaySettings({ toolbars: 'reporting' });
marvinJsRemoteEditor = marvin;

View file

@ -1,446 +0,0 @@
/* global _ hljs tinyMCE SmartAnnotation I18n GLOBAL_CONSTANTS HelperModule */
/* eslint-disable no-unused-vars */
var TinyMCE = (function() {
'use strict';
function initHighlightjs() {
$('[class*=language]').each(function(i, block) {
hljs.highlightBlock(block);
});
}
function initHighlightjsIframe(iframe) {
$('[class*=language]', iframe).each(function(i, block) {
hljs.highlightBlock(block);
});
}
// Get LocalStorage auto save path
function getAutoSavePrefix(editor) {
var prefix = editor.getParam('autosave_prefix', 'tinymce-autosave-{path}{query}{hash}-{id}-');
prefix = prefix.replace(/\{path\}/g, document.location.pathname);
prefix = prefix.replace(/\{query\}/g, document.location.search);
prefix = prefix.replace(/\{hash\}/g, document.location.hash);
prefix = prefix.replace(/\{id\}/g, editor.id);
return prefix;
}
// Handles autosave notification if draft is available in the local storage
function restoreDraftNotification(selector, editor) {
var prefix = getAutoSavePrefix(editor);
var lastDraftTime = parseInt(tinyMCE.util.LocalStorage.getItem(prefix + 'time'), 10);
var lastUpdated = $(selector).data('last-updated');
var notificationBar;
var restoreBtn = $('<button class="btn restore-draft-btn">Restore Draft</button>');
var cancelBtn = $('<span class="fas fa-times"></span>');
// Check whether we have draft stored
if (editor.plugins.autosave.hasDraft()) {
notificationBar = $('<div class="restore-draft-notification"></div>');
if (lastDraftTime < lastUpdated) {
notificationBar.html(`<span class="notification-text">${I18n.t('tiny_mce.older_version_available')}</span>`);
} else {
notificationBar.html(`<span class="notification-text">${I18n.t('tiny_mce.newer_version_available')}</span>`);
}
// Add notification bar
$(notificationBar).append(restoreBtn).append(cancelBtn);
$(editor.contentAreaContainer).before(notificationBar);
$(restoreBtn).click(function() {
editor.plugins.autosave.restoreDraft();
makeItDirty(editor);
notificationBar.remove();
});
$(cancelBtn).click(function() {
notificationBar.remove();
});
}
}
function initImageToolBar(editor) {
var editorIframe = $('#' + editor.id).prev().find('.mce-edit-area iframe');
var primaryColor = '#104da9';
editorIframe.contents().find('head').append(`<style type="text/css">
img::-moz-selection{background:0 0}
img::selection{background:0 0}
.mce-content-body img[data-mce-selected]{outline:2px solid ${primaryColor}}
.mce-content-body div.mce-resizehandle{background:transparent;border-color:transparent;box-sizing:border-box;height:10px;width:10px}
.mce-content-body div.mce-resizehandle:hover{background:transparent}
.mce-content-body div#mceResizeHandlenw{border-left: 2px solid ${primaryColor}; border-top: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlene{border-right: 2px solid ${primaryColor}; border-top: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlesw{border-left: 2px solid ${primaryColor}; border-bottom: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlese{border-right: 2px solid ${primaryColor}; border-bottom: 2px solid ${primaryColor}}
</style>`);
}
function makeItDirty(editor) {
var editorForm = $(editor.getContainer()).closest('form');
editorForm.find('.tinymce-status-badge').addClass('hidden');
$(editor.getContainer()).find('.tinymce-save-button').removeClass('hidden');
}
function draftLocation() {
return 'tinymce-drafts-' + document.location.pathname;
}
function removeDraft(editor, textAreaObject) {
var location = draftLocation();
var storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]');
var draftId = storedDrafts.indexOf(textAreaObject.data('tinymce-object'));
if (draftId > -1) {
storedDrafts.splice(draftId, 1);
}
if (storedDrafts.length) {
sessionStorage.setItem(location, JSON.stringify(storedDrafts));
} else {
sessionStorage.removeItem(location);
}
}
// Update scroll position after exit
function updateScrollPosition(editorForm) {
if (editorForm.offset().top < $(window).scrollTop()) {
$(window).scrollTop(editorForm.offset().top - 150);
}
}
function saveAction(editor) {
var editorForm = $(editor.getContainer()).closest('form');
editorForm.clearFormErrors();
editor.setProgressState(1);
editor.save();
editorForm.submit();
updateScrollPosition(editorForm);
}
// returns a public API for TinyMCE editor
return Object.freeze({
init: function(selector, onSaveCallback) {
var editorInstancePromise;
var tinyMceContainer;
var tinyMceInitSize;
var plugins;
var textAreaObject = $(selector);
if (typeof tinyMCE !== 'undefined') {
// Hide element containing HTML view of RTE field
tinyMceContainer = $(selector).closest('form').find('.tinymce-view');
tinyMceInitSize = tinyMceContainer.height();
$(selector).closest('.form-group')
.before('<div class="tinymce-placeholder" style="height:' + tinyMceInitSize + 'px"></div>');
tinyMceContainer.addClass('hidden');
plugins = 'custom_image_toolbar table autosave autoresize customimageuploader link advlist codesample autolink lists charmap hr anchor searchreplace wordcount visualblocks visualchars insertdatetime nonbreaking save directionality paste textcolor colorpicker textpattern placeholder';
if (typeof (MarvinJsEditor) !== 'undefined') plugins += ' marvinjsplugin';
if (textAreaObject.data('objectType') === 'step'
|| textAreaObject.data('objectType') === 'result_text') {
document.location.hash = textAreaObject.data('objectType') + '_' + textAreaObject.data('objectId');
}
editorInstancePromise = tinyMCE.init({
cache_suffix: '?v=4.9.10', // This suffix should be changed any time library is updated
selector: selector,
convert_urls: false,
menubar: 'file edit view insert format table',
toolbar: 'undo redo restoredraft | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table | link | forecolor backcolor | customimageuploader marvinjsplugin | codesample',
plugins: plugins,
autoresize_bottom_margin: 20,
codesample_languages: [
{ text: 'R', value: 'r' },
{ text: 'MATLAB', value: 'matlab' },
{ text: 'Python', value: 'python' },
{ text: 'JSON', value: 'javascript' },
{ text: 'HTML/XML', value: 'markup' },
{ text: 'JavaScript', value: 'javascript' },
{ text: 'CSS', value: 'css' },
{ text: 'PHP', value: 'php' },
{ text: 'Ruby', value: 'ruby' },
{ text: 'Java', value: 'java' },
{ text: 'C', value: 'c' },
{ text: 'C#', value: 'csharp' },
{ text: 'C++', value: 'cpp' }
],
browser_spellcheck: true,
branding: false,
fixed_toolbar_container: '#mytoolbar',
autosave_restore_when_empty: false,
autosave_interval: '1s',
autosave_retention: '1440m',
removed_menuitems: 'newdocument',
object_resizing: true,
elementpath: false,
forced_root_block: 'div',
force_p_newlines: false,
default_link_target: '_blank',
target_list: [
{ title: 'New page', value: '_blank' },
{ title: 'Same page', value: '_self' }
],
style_formats: [
{
title: 'Headers',
items: [
{ title: 'Header 1', format: 'h1' },
{ title: 'Header 2', format: 'h2' },
{ title: 'Header 3', format: 'h3' },
{ title: 'Header 4', format: 'h4' },
{ title: 'Header 5', format: 'h5' },
{ title: 'Header 6', format: 'h6' }
]
},
{
title: 'Inline',
items: [
{ title: 'Bold', icon: 'bold', format: 'bold' },
{ title: 'Italic', icon: 'italic', format: 'italic' },
{ title: 'Underline', icon: 'underline', format: 'underline' },
{ title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough' },
{ title: 'Superscript', icon: 'superscript', format: 'superscript' },
{ title: 'Subscript', icon: 'subscript', format: 'subscript' },
{ title: 'Code', icon: 'code', format: 'code' }
]
},
{
title: 'Blocks',
items: [
{ title: 'Paragraph', format: 'p' },
{ title: 'Blockquote', format: 'blockquote' }
]
},
{
title: 'Alignment',
items: [
{ title: 'Left', icon: 'alignleft', format: 'alignleft' },
{ title: 'Center', icon: 'aligncenter', format: 'aligncenter' },
{ title: 'Right', icon: 'alignright', format: 'alignright' },
{ title: 'Justify', icon: 'alignjustify', format: 'alignjustify' }
]
}
],
init_instance_callback: function(editor) {
var editorForm = $(editor.getContainer()).closest('form');
var menuBar = editorForm.find('.mce-menubar.mce-toolbar.mce-first .mce-flow-layout');
var editorToolbar = editorForm.find('.mce-top-part');
var editorToolbaroffset;
$('.tinymce-placeholder').css('height', $(editor.editorContainer).height() + 'px');
setTimeout(() => {
$(editor.editorContainer).addClass('show');
$('.tinymce-placeholder').remove();
updateScrollPosition($(editor.editorContainer).closest('.tinymce-container'));
}, 400);
// Init saved status label
if (editor.getContent() !== '') {
editorForm.find('.tinymce-status-badge').removeClass('hidden');
}
if ($('.navbar-secondary').length) {
editorToolbaroffset = $('.navbar-secondary').position().top + $('.navbar-secondary').height();
} else if ($('#main-nav').length) {
editorToolbaroffset = $('#main-nav').height();
} else {
editorToolbaroffset = 0;
}
if (GLOBAL_CONSTANTS.IS_SAFARI) {
editorToolbar.css('position', '-webkit-sticky');
} else {
editorToolbar.css('position', 'sticky');
}
editorToolbar.css('top', editorToolbaroffset + 'px').css('z-index', '100');
// Init image toolbar
initImageToolBar(editor);
// Init Save button
editorForm
.find('.tinymce-save-button')
.clone()
.appendTo(menuBar)
.on('click', function(event) {
event.preventDefault();
saveAction(editor);
});
// After save action
editorForm
.on('ajax:success', function(ev, data) {
editor.save();
editor.setProgressState(0);
editorForm.find('.tinymce-status-badge').removeClass('hidden');
editor.remove();
editorForm.find('.tinymce-view').html(data.html).removeClass('hidden');
editor.plugins.autosave.removeDraft();
removeDraft(editor, textAreaObject);
if (onSaveCallback) { onSaveCallback(data); }
}).on('ajax:error', function(ev, data) {
var model = editor.getElement().dataset.objectType;
if (data.status === 422 && 'description' in data.responseJSON) {
// eslint-disable-next-line no-param-reassign
data.responseJSON.description = data.responseJSON.description.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
$(this).renderFormErrors(model, data.responseJSON);
editor.setProgressState(0);
if (data.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
});
// Init Cancel button
editorForm
.find('.tinymce-cancel-button')
.clone()
.appendTo(menuBar)
.on('click', function(event) {
$(editorForm).find('.form-group').removeClass('has-error');
$(editorForm).find('.help-block').remove();
event.preventDefault();
if (editor.isDirty()) {
editor.setContent($(selector).val());
}
editorForm.find('.tinymce-status-badge').addClass('hidden');
editorForm.find('.tinymce-view').removeClass('hidden');
editor.remove();
updateScrollPosition(editorForm);
if (onSaveCallback) { onSaveCallback(); }
})
.removeClass('hidden');
// Set cursor to the end of the content
if (editor.settings.id !== 'step_description_textarea') {
editor.focus();
}
editor.selection.select(editor.getBody(), true);
editor.selection.collapse(false);
SmartAnnotation.init($(editor.contentDocument.activeElement));
SmartAnnotation.preventPropagation('.atwho-user-popover');
initHighlightjsIframe($(this.iframeElement).contents());
},
setup: function(editor) {
editor.on('keydown', function(e) {
if (e.keyCode === 13 && $(editor.contentDocument.activeElement).atwho('isSelecting')) {
return false;
}
return true;
});
editor.on('NodeChange', function(e) {
var node = e.element;
setTimeout(function() {
if ($(node).is('pre') && !editor.isHidden()) {
initHighlightjsIframe($(editor.iframeElement).contents());
}
}, 200);
});
editor.on('Dirty', function() {
makeItDirty(editor);
});
editor.on('StoreDraft', function() {
var location = draftLocation();
var storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]');
var draftName = textAreaObject.data('tinymce-object');
if (storedDrafts.includes(draftName) || !draftName) return;
storedDrafts.push(draftName);
sessionStorage.setItem(location, JSON.stringify(storedDrafts));
});
editor.on('remove', function() {
var menuBar = $(editor.getContainer()).find('.mce-menubar.mce-toolbar.mce-first .mce-flow-layout');
menuBar.find('.tinymce-save-button').remove();
menuBar.find('.tinymce-cancel-button').remove();
});
editor.on('blur', function(e) {
if (editor.blurDisabled) return false;
if ($('.atwho-view:visible').length || $('#MarvinJsModal:visible').length) return false;
setTimeout(() => {
if (editor.isNotDirty === false) {
$(editor.container).find('.tinymce-save-button').click();
} else {
$(editor.container).find('.tinymce-cancel-button').click();
}
}, 0);
return true;
});
editor.on('init', function(e) {
restoreDraftNotification(selector, editor);
});
},
codesample_content_css: $(selector).data('highlightjs-path'),
save_onsavecallback: function(editor) { saveAction(editor); }
});
}
return editorInstancePromise;
},
destroyAll: function() {
_.each(tinyMCE.editors, function(editor) {
if (editor) {
editor.remove();
initHighlightjs();
}
});
},
refresh: function() {
this.destroyAll();
this.init();
},
getContent: function() {
return tinyMCE.editors[0].getContent();
},
updateImages(editor) {
var images;
var iframe = $('#' + editor.id).prev().find('.mce-edit-area iframe').contents();
images = $.map($('img', iframe), e => {
return e.dataset.mceToken;
});
$('#' + editor.id).next()[0].value = JSON.stringify(images);
return JSON.stringify(images);
},
makeItDirty: function(editor) {
makeItDirty(editor);
},
highlight: initHighlightjs,
initIfHasDraft: function(viewObject) {
var storedDrafts = sessionStorage.getItem(draftLocation());
var draftName = viewObject.data('tinymce-init');
if (storedDrafts && JSON.parse(storedDrafts)[0] === draftName) {
let top = viewObject.offset().top;
setTimeout(() => {
viewObject.click();
}, 0);
setTimeout(() => {
window.scrollTo(0, top - 150);
}, 2000);
}
}
});
}());
$(document).on('turbolinks:before-visit', function(e) {
_.each(tinyMCE.editors, function(editor) {
if (editor.isNotDirty === false) {
if (confirm(I18n.t('tiny_mce.leaving_warning'))) {
return false;
}
e.preventDefault();
return false;
}
return false;
});
});

View file

@ -1,133 +0,0 @@
/* eslint no-underscore-dangle: "off" */
/* eslint no-use-before-define: "off" */
/* eslint no-restricted-syntax: ["off", "BinaryExpression[operator='in']"] */
/* global tinymce I18n HelperModule validateFileSize */
(function() {
'use strict';
tinymce.PluginManager.requireLangPack('customimageuploader');
tinymce.create('tinymce.plugins.CustomImageUploader', {
CustomImageUploader: function(ed) {
var iframe;
var editor = ed;
var textAreaElement = $('#' + ed.id);
function loadFiles() {
let $fileInput;
let hitFileLimit;
$('#tinymce_current_upload').remove();
$fileInput = $('<input type="file" multiple accept="image/*" id="tinymce_current_upload" style="display: none;">').prependTo(editor.container);
$fileInput.click();
$fileInput.change(function() {
let formData = new FormData();
let files = $('#tinymce_current_upload')[0].files;
Array.from(files).forEach(file => formData.append('files[]', file, file.name));
Array.from(files).every(file => {
if (!validateFileSize(file, true)) {
hitFileLimit = true;
return false;
}
});
if (hitFileLimit) {
return;
}
$.post({
url: textAreaElement.data('tinymce-asset-path'),
data: formData,
processData: false,
contentType: false,
success: function(data) {
handleResponse(data);
$('#tinymce_current_upload').remove();
},
error: function(response) {
HelperModule.flashAlertMsg(response.responseJSON.errors, 'danger');
$('#tinymce_current_upload').remove();
}
});
});
}
function handleResponse(response) {
if (response.errors) {
handleError(response.errors.join('<br>'));
} else {
response.images.forEach(el => editor.execCommand('mceInsertContent', false, buildHTML(el)));
updateActiveImages(ed);
}
}
function handleError(error) {
HelperModule.flashAlertMsg(error, 'danger');
}
function buildHTML(image) {
return `<img src="${image.url}"
data-mce-token="${image.token}"
alt="description-${image.token}" />`;
}
// Create hidden field for images
function createImageHiddenField() {
textAreaElement.parent().find('input#tiny-mce-images').remove();
$('<input type="hidden" id="tiny-mce-images" name="tiny_mce_images" value="[]">').insertAfter(textAreaElement);
}
// Finding images in text
function updateActiveImages() {
var images;
var imageContainer = $('#' + editor.id).next()[0];
iframe = $('#' + editor.id).prev().find('.mce-edit-area iframe').contents();
images = $.map($('img', iframe), e => {
return e.dataset.mceToken;
});
if (imageContainer === undefined) {
createImageHiddenField();
}
// Small fix for ResultText when you cancel after change MarvinJS
if (imageContainer === undefined) return [];
imageContainer.value = JSON.stringify(images);
return JSON.stringify(images);
}
// Add a button that opens a window
editor.addButton('customimageuploader', {
tooltip: I18n.t('tiny_mce.upload_window_label'),
icon: 'image',
onclick: loadFiles
});
// Adds a menu item to the tools menu
editor.addMenuItem('customimageuploader', {
text: I18n.t('tiny_mce.upload_window_label'),
icon: 'image',
context: 'insert',
onclick: loadFiles
});
ed.on('NodeChange', function() {
// Check editor status
if (this.initialized) {
updateActiveImages(ed);
}
});
createImageHiddenField();
}
});
tinymce.PluginManager.add(
'customimageuploader',
tinymce.plugins.CustomImageUploader
);
}());

View file

@ -1,11 +1,12 @@
/* global tinymce */
tinymce.PluginManager.add('placeholder', function(editor) {
var Label = function() {
var editorForm = $(editor.getContainer()).closest('form');
var editorToolbar = editorForm.find('.mce-top-part');
var placeholderText = editor.getElement().getAttribute('placeholder') || editor.settings.placeholder;
var placeholderAttrs = editor.settings.placeholder_attrs || {
style: {
var placeholderAttrs = {
style: `
position: 'absolute',
top: (editorToolbar.height()) + 'px',
left: 0,
@ -14,7 +15,7 @@ tinymce.PluginManager.add('placeholder', function(editor) {
width: 'calc(100% - 50px)',
overflow: 'hidden',
'white-space': 'pre-wrap'
}
`
};
var contentAreaContainer = editor.getContentAreaContainer();

View file

@ -35,6 +35,7 @@
@import "protocols/*";
@import "dashboard/*";
@import "repository/*";
@import "experiment/*";
@import "repository_columns/*";
@import "label_templates/*";
@import "reports/*";

View file

@ -0,0 +1,75 @@
// scss-lint:disable SelectorDepth NestingDepth IdSelector
#canvas-container,
#module-archive {
.experimnet-name {
max-width: calc(100% - 300px);
}
.panel-heading {
padding: 10px 15px 4px;
}
.panel-body {
padding: 6px 15px;
.status-label {
background-color: var(--state-color);
color: $color-white;
display: inline-block;
margin: 3px 0;
max-width: 100%;
overflow: hidden;
padding: 2px 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.panel-footer {
.nav > li > a {
padding: 6px 15px;
}
.btn {
height: 30px;
}
.badge-indicator {
background: $brand-accent;
border-radius: $border-radius-tag;
color: $color-black;
font-size: 10px;
margin-left: -8px;
}
}
}
#canvas-container {
margin: 0 -28px;
}
.canvas-preview-img,
.canvas-preview-rect {
border-radius: 4px;
bottom: 24px;
box-shadow: 0 0 0 8px $color-white;
display: flex;
height: 64px;
position: absolute;
right: 24px;
width: 68px;
z-index: 9999;
&.empty {
background-color: $color-concrete;
box-shadow: inset 0 0 0 2px $brand-primary;
}
&.processing {
background-color: $color-concrete;
background-image: url("/images/medium/loading.svg");
background-position: center;
background-repeat: no-repeat;
}
}

View file

@ -0,0 +1,133 @@
// scss-lint:disable SelectorDepth NestingDepth IdSelector
#experimentTable,
#experiment-canvas,
#module-archive {
.experimnet-name {
max-width: calc(100% - 300px);
}
}
#experimentTable {
&.archived {
[data-view-mode="active"] {
display: none !important;
}
}
}
#experiment-canvas {
[data-view-mode="archived"] {
display: none;
}
.toolbar-row {
align-items: center;
display: flex;
margin: 10px 0;
.toolbar-right-block {
align-items: center;
display: flex;
margin-left: auto;
}
.zoom-text {
margin-right: .5em;
}
}
}
#new-my-module-modal {
.my-module-user-tags {
img {
border-radius: 50%;
display: inline;
margin-right: .5em;
max-height: 20px;
max-width: 20px;
}
}
.dropdown-selector-container {
.my-module-white-tags {
color: $color-white;
}
.my-module-tags-color {
align-items: center;
border-radius: 8px;
display: inline-flex;
height: 16px;
justify-content: center;
margin-right: 5px;
width: 16px;
&.new {
color: $color-silver-chalice;
}
}
.my-module-tags-create-new {
margin-left: 3px;
}
&.open {
.input-field {
border: 1px solid $color-alto;
}
}
&:not(.view-mode):hover {
.input-field {
border: 1px solid $color-alto;
}
}
}
.datetime-picker-container {
width: 45%;
.fa-calendar-alt {
color: $color-volcano !important;
font-size: 14px !important;
}
}
}
.dropdown-experiment-actions,
.my-module-menu {
.divider-label {
@include font-small;
color: $color-silver-chalice;
padding: .25em 1em;
&.footer {
border-top: 1px solid $color-concrete;
padding-top: .5em;
}
}
li {
@include font-button;
cursor: pointer;
padding: .5em 1em;
white-space: nowrap;
.fas {
display: inline-block;
margin-right: .25em;
width: 18px;
}
&:hover:not(.divider-label) {
background: $color-concrete;
}
a {
display: inline-block;
margin: -.5em -1em;
padding: .5em 1em;
width: calc(100% + 2em);
}
}
}

View file

@ -0,0 +1,469 @@
// scss-lint:disable SelectorDepth NestingDepth IdSelector
#experimentTable {
--content-header-size: 5em;
--toolbar-height: 4.5em;
position: relative;
.title-row {
.header-actions {
&.experiment-header {
column-gap: .25em;
}
.sort-task-menu {
&:not(.archived) {
[data-view-mode="archived"] {
display: none;
}
}
}
}
}
.experiment-table-container {
height: calc(100vh - var(--content-header-size) - var(--navbar-height) - var(--toolbar-height));
overflow: auto;
}
.toolbar-row {
align-items: center;
display: flex;
height: var(--toolbar-height);
.toolbar-left-block {
display: flex;
.btn {
margin-right: .25em;
}
}
.toolbar-right-block {
margin-left: auto;
}
}
.experiment-table {
display: grid;
grid-auto-rows: 3em 1px;
grid-template-columns: max-content repeat(calc(var(--columns-count)), minmax(max-content, auto)) max-content;
min-width: 100%;
.table-header-cell {
align-items: center;
background-color: $color-concrete;
border: 1px solid $color-white;
display: flex;
height: 3em;
padding: 0 .5em;
position: sticky;
position: -webkit-sticky;
top: 0;
z-index: 7;
&.select-all-checkboxes {
justify-content: center;
}
.fa-comment {
color: $color-silver-chalice;
}
}
.table-header {
display: contents;
height: 10px;
&::after {
content: "";
grid-column: 1/-1;
}
}
.table-body {
display: contents;
}
.loading-overlay {
display: none;
}
.table-row-provisioning {
.loading-overlay {
display: block;
}
.sci-checkbox-container {
height: 1.5em;
width: 1.5em;
.loading-overlay::after {
background-size: 1.5em;
cursor: default;
}
.sci-checkbox,
.sci-checkbox-label {
display: none;
}
}
}
.table-body-cell {
align-items: center;
display: flex;
padding: 0 .5em;
.my-module-users-link {
color: $color-silver-chalice;
&:hover {
text-decoration: none;
}
}
.global-avatar-container {
color: $color-silver-chalice;
height: 2em;
line-height: 2em;
margin-right: .25em;
width: 2em;
}
.more-users {
background: $color-volcano;
border-radius: 50%;
color: $color-white;
height: 2em;
line-height: 2em;
margin-right: .25em;
text-align: center;
text-decoration: none;
width: 2em;
}
.new-user {
background: $color-concrete;
text-align: center;
}
}
.archived-column {
display: none;
}
.comments-column .disabled {
color: $color-silver-chalice;
}
.table-row {
display: contents;
&:hover {
.table-body-cell {
background-color: $color-concrete;
}
}
&::after {
background: $color-concrete;
content: "";
display: inline-block;
grid-column: 1/-1;
height: 1px;
}
}
.open-my-module-menu:focus {
box-shadow: 0 0 0 1px $brand-focus;
}
.assign-users-dropdown {
.dropdown-menu {
padding: .5em;
width: 280px;
}
.users-list {
max-height: 300px;
overflow: auto;
}
.user-container {
align-items: center;
display: flex;
padding: .5em;
.user-avatar {
padding: 0 .75em;
&.archived {
padding-left: 0;
}
img {
border-radius: 50%;
}
}
}
.assigned-users-container {
cursor: pointer;
display: flex;
}
.avatar-container {
border: 1px solid $color-white;
border-radius: 50%;
display: inline-block;
height: 26px;
margin-right: -5px;
width: 26px;
img {
border-radius: 50%;
max-height: 100%;
max-width: 100%;
}
}
.more-users {
font-size: 10px;
line-height: 24px;
}
.new-user {
color: $color-silver-chalice;
line-height: 24px;
&:not(:first-child) {
margin-left: 5px;
}
}
}
.my-module-status {
color: $color-white;
display: inline-block;
margin: 3px 0;
max-width: 100%;
overflow: hidden;
padding: 2px 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-row-placeholder-divider {
background: $color-concrete;
display: inline-block;
grid-column: 1/-1;
height: 1px;
}
.table-row-placeholder {
align-items: center;
background-color: $color-white;
border-radius: $border-radius-default;
box-shadow: $flyout-shadow;
display: grid;
grid-column: 1 / -1;
grid-template-columns: 32px repeat(9, minmax(max-content, auto));
.placeholder-cell {
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: placeholder-pulsing;
background-color: $color-alto;
border-radius: $border-radius-default;
display: block;
height: 18px;
margin: auto;
width: 90%;
&.circle-0 {
border-radius: 100%;
height: 24px;
width: 24px;
}
@keyframes placeholder-pulsing {
0% {
opacity: 1;
}
50% {
opacity: .5;
}
100% {
opacity: 1;
}
}
}
}
&.last-page {
padding-bottom: 5em;
position: relative;
}
.experiment-table-list-end-placeholder {
align-items: center;
background-color: $color-concrete;
bottom: 1em;
display: flex;
height: 3em;
left: calc(50% - 150px);
margin: 0 auto;
padding: 1em;
position: absolute;
width: 300px;
> * {
flex-grow: 1;
text-align: center;
}
}
}
.unseen-comments {
@include font-small;
align-items: center;
background-color: $brand-complementary;
border: 2px solid $color-white;
border-radius: 50%;
color: $color-black;
display: flex;
font-weight: bold;
height: 16px;
justify-content: center;
margin-bottom: 10px;
margin-left: -1px;
min-width: 16px;
}
.datetime-container {
width: 100%;
.clear-date {
cursor: pointer;
left: calc(100% - 16px);
position: absolute;
text-align: center;
top: 0;
visibility: hidden;
width: 16px;
&.open {
visibility: visible;
}
}
.date-text {
display: block;
position: relative;
.alert-yellow {
color: $brand-warning;
margin-left: 4px;
}
.alert-red {
color: $brand-danger;
margin-left: 4px;
}
}
.datetime-picker-container {
left: 0;
position: absolute;
top: 0;
width: calc(100% - 16px);
.calendar-due-date {
opacity: 0;
}
}
&:hover {
.date-text[data-editable=true] {
background-color: $color-concrete;
border-radius: 4px;
}
}
}
.open-comments-sidebar {
margin-bottom: 0;
}
&.archived {
.table-body-cell {
background-color: $color-concrete;
}
.archived-column {
display: flex;
}
}
.task_name-column {
a {
display: inline-block;
max-width: 320px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
span {
color: $color-silver-chalice;
}
}
}
.table-display-modal {
.column-container {
align-items: center;
border-bottom: $border-default;
display: flex;
padding: .5em 1em;
&:not(.visible) {
color: $color-alto;
}
&:last-child {
border: 0;
}
.fas {
cursor: pointer;
margin-right: 1em;
&.disabled {
color: $color-alto;
pointer-events: none;
}
}
&.task_name {
padding-left: 3em;
.fas {
display: none;
}
}
}
}
@media (max-width: 1000px) {
.toolbar-row {
.button-text {
display: none;
}
}
}

View file

@ -21,6 +21,14 @@
}
}
.project-show-toolbar {
display: flex;
.btn {
margin-right: .25em;
}
}
.content-header {
.project-name {
align-items: center;
@ -541,3 +549,28 @@
}
}
}
.tasks-no-results-container {
grid-column: 1 / -1;
grid-row: 8;
display: none;
}
.no-results-img {
display: block;
margin: auto;
max-height: 230px;
}
.no-results-title {
@include font-h1;
margin-bottom: .25em;
margin-top: 1.25em;
text-align: center;
}
.no-results-description {
@include font-main;
color: $color-silver-chalice;
text-align: center;
}

View file

@ -455,18 +455,19 @@
}
.calendar-input {
@include font-button;
background-color: transparent !important;
border-color: $color-silver-chalice;
box-shadow: none;
color: inherit;
cursor: pointer;
font-size: 13px;
padding-left: 5px;
padding-left: 10px;
padding-right: 34px;
position: relative;
z-index: 3;
&::placeholder {
color: $color-silver-chalice;
color: $color-alto;
}
}
}

View file

@ -139,7 +139,16 @@
}
}
.inser-field-dropdown {
.insert-field-dropdown {
.dimensions-container {
align-items: center;
display: flex;
img {
margin-top: 27px;
}
}
.open-dropdown-button:not(.collapsed) {
.fas {
@include rotate(-180deg);
@ -171,7 +180,12 @@
display: flex;
padding: 10px 10px 10px 24px;
.fas {
.fas:not(.fa-plus-square) {
margin-left: -1.25em;
margin-right: .25em;
}
.fa-plus-square {
@include font-main;
display: none;
margin-left: auto;
@ -180,7 +194,7 @@
&:hover {
background-color: $color-concrete;
.fas {
.fa-plus-square {
display: inline-block;
}
}

View file

@ -235,17 +235,16 @@
}
#experiment-canvas {
[data-view-mode="archived"] {
display: none;
}
}
#module-archive {
[data-view-mode="active"] {
display: none;
}
.toolbar {
margin-top: 1em;
}
.module-container {
min-width: 220px;

View file

@ -239,15 +239,21 @@
}
.my-module-tags-color {
align-items: center;
border-radius: 8px;
display: inline-block;
display: inline-flex;
height: 16px;
justify-content: center;
margin-right: 5px;
width: 16px;
&.new {
color: $color-silver-chalice;
}
}
.my-module-tags-create-new {
opacity: .6;
margin-left: 3px;
}
.input-field {

View file

@ -21,7 +21,7 @@ $color-module-hover: $brand-primary;
align-items: center;
display: flex;
#edit-canvas-button {
#edit-canvas-button, .new-my-module-button {
margin-right: 5px;
}
@ -48,9 +48,8 @@ $color-module-hover: $brand-primary;
}
#update-canvas {
.canvas-header {
margin-bottom: 5px;
padding: 1em 2em;
}
}
@ -89,6 +88,14 @@ $color-module-hover: $brand-primary;
overflow: hidden;
// for IE10+ touch devices
touch-action: none;
.empty-canvas {
color: $color-volcano;
display: flex;
font-size: 22px;
justify-content: center;
margin-top: 48px;
}
}
.diagram {
@ -542,6 +549,26 @@ li.module-hover {
margin-right: 15px;
margin-top: 10px;
}
#manage-module-tags-modal-intro {
padding-left: 15px;
border-top: 0;
width: 568px;
height: 30px;
font-family: 'Lato';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 21px;
color: $color-volcano;
flex: none;
order: 1;
align-self: stretch;
flex-grow: 0;
}
}
@ -609,17 +636,6 @@ li.module-hover {
}
}
}
.dropdown-option.users-dropdown-list {
padding: 8px 10px;
.item-avatar {
border-radius: 50%;
height: 32px;
margin: 0 16px 0 0;
width: 32px;
}
}
}
.projects-toolbar {

View file

@ -303,7 +303,6 @@
#dropdownAssetContextMenu {
background: $color-white;
&:focus,
&:active {
box-shadow: none;
}

View file

@ -58,8 +58,8 @@
align-items: center;
display: flex;
height: 2em;
justify-content: center;
width: 2em;
justify-content: left;
width: 1.5em;
}
}
@ -111,6 +111,7 @@
.checkbox-cell {
grid-column: 1;
justify-content: center;
position: initial;
}
}

View file

@ -9,7 +9,7 @@
top: var(--navbar-height);
transition: width .3s;
width: 0;
z-index: 1000;
z-index: 10000;
&.open {
width: var(--comments-sidebar-width);
@ -131,6 +131,10 @@
.update-buttons {
display: block;
}
.send-comment {
display: none;
}
}
}
}

View file

@ -66,7 +66,7 @@
.dropdown-menu {
@include font-button;
min-width: auto;
min-width: 190px;
.divider-label {
@include font-small;
@ -74,18 +74,41 @@
padding: .25em 1em;
}
.divider {
margin: 0;
}
li {
cursor: pointer;
padding: 1em;
padding: .5em 1em;
white-space: nowrap;
.button-icon {
margin-right: .5em;
}
&:hover {
&:hover:not(.divider-label) {
background: $color-concrete;
}
.btn {
height: 36px;
}
a {
display: inline-block;
margin: -1em;
padding: .5em 1em;
width: calc(100% + 2em);
&.selected::after {
@include font-awesome;
content: $font-fas-check;
margin-left: auto;
position: absolute;
right: 1em;
}
}
}
}

View file

@ -60,7 +60,7 @@
line-height: 28px;
min-width: 0;
outline: 0;
padding: 0 0 0 5px;
padding: 0 0 0 10px;
&::placeholder {
opacity: .7;

View file

@ -44,6 +44,20 @@
}
}
.item-avatar {
border-radius: 50%;
}
.dropdown-option.users-dropdown-list {
padding: 8px 10px;
.item-avatar {
height: 32px;
margin: 0 16px 0 0;
width: 32px;
}
}
.recent-searches {
border-top-left-radius: 0;
border-top-right-radius: 0;

View file

@ -23,6 +23,7 @@ $font-fas-angle-double-left: "\f100";
$font-fas-angle-double-right: "\f101";
$font-fas-exclamation-circle: "\f06a";
$font-fas-caret-up: "\f0d8";
$font-fas-plus: "\f02b";
@mixin font-h1 {
font-size: 24px;

View file

@ -75,7 +75,8 @@
}
.change-projects-view-type-form,
.change-experiments-view-type-form {
.change-experiments-view-type-form,
.change-my-modules-view-type-form {
.button-to {
float: unset !important;
height: 48px;

View file

@ -81,6 +81,10 @@
&.error {
padding-bottom: 6px;
label {
color: $brand-danger;
}
.sci-input-field {
border: $border-danger;
}

View file

@ -95,6 +95,11 @@ input[type="checkbox"].sci-toggle-checkbox {
transition: .2s;
width: 48px;
svg,
svg path {
fill: $color-black;
}
&:first-of-type {
border-left-color: $color-silver-chalice;
border-radius: $border-radius-default 0 0 $border-radius-default;
@ -109,6 +114,11 @@ input[type="checkbox"].sci-toggle-checkbox {
background: $brand-primary;
border: 1px solid $brand-primary;
color: $color-white;
svg,
svg path {
fill: $color-white;
}
}
}
}

View file

@ -25,10 +25,11 @@
.step {
.panel {
border: 0;
margin-left: 0;
.panel-body {
padding: 15px 5px;
padding: 15px 24px;
}
}
}

View file

@ -10,8 +10,8 @@
.enable-edit-mode {
cursor: pointer;
display: none;
justify-content: flex-end;
opacity: 0;
padding: 12px;
position: absolute;
right: 0;
@ -49,6 +49,7 @@
.enable-edit-mode {
display: flex;
opacity: 1;
}
}
}

View file

@ -28,7 +28,7 @@
$color-concrete 100%
);
border-radius: 4px;
display: none;
opacity: 0;
padding-left: 2em;
position: absolute;
right: 0;
@ -55,6 +55,7 @@
.buttons-container {
display: flex;
opacity: 1;
}
.step-element-grip {

View file

@ -201,16 +201,6 @@ mark,.mark {
text-align: right;
}
a[data-toggle="tooltip"] {
color: inherit;
border-bottom: 1px dashed $color-emperor;
&:hover {
text-decoration: none;
cursor: help;
}
}
.nav-tabs {
margin-bottom: 15px;
@ -686,47 +676,6 @@ ul.double-line > li {
}
}
#canvas-container,
#module-archive {
.panel-heading {
padding: 10px 15px 4px;
}
.panel-body {
padding: 6px 15px;
.status-label {
background-color: var(--state-color);
color: $color-white;
display: inline-block;
margin: 3px 0;
max-width: 100%;
overflow: hidden;
padding: 2px 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.panel-footer {
.nav > li > a {
padding: 6px 15px;
}
.btn {
height: 30px;
}
.badge-indicator {
background: $brand-accent;
border-radius: $border-radius-tag;
color: $color-black;
font-size: 10px;
margin-left: -8px;
}
}
}
.panel-options {
position: relative;
bottom: 8px;

View file

@ -1,4 +1,4 @@
// scss-lint:disable ImportantRule
// scss-lint:disable ImportantRule SelectorDepth
@import "constants";
@font-face {
@ -22,6 +22,11 @@
color: $color-silver-chalice;
content: attr(data-placeholder);
}
p {
margin: 0;
padding: 0;
}
}
.mce-tinymce {
@ -38,6 +43,16 @@
position: relative !important;
}
.tox.tox-tinymce {
left: -100000px;
position: absolute;
&.tox-tinymce--loaded {
left: 0;
position: relative;
}
}
.tinymce-placeholder {
background: $color-concrete;
opacity: .7;
@ -45,8 +60,15 @@
width: 100%;
}
.tinymce-save-button,
.tinymce-cancel-button {
.tox-edit-area {
label {
color: $color-silver-chalice !important;
padding: 5px !important;
}
}
.tinymce-save-button.tox-mbtn,
.tinymce-cancel-button.tox-mbtn {
cursor: pointer;
.fas {
@ -54,6 +76,15 @@
font-weight: 900;
margin-top: 3px;
}
&:hover {
background: transparent !important;
}
}
.tinymce-save-controls {
display: flex;
margin-left: auto !important;
}
.tinymce-status-badge {
@ -69,18 +100,17 @@
background: $color-white !important;
}
.restore-draft-notification {
align-items: center;
background: $state-info-bg !important;
display: flex;
flex-basis: 100%;
height: 30px !important;
padding: 0 10px !important;
padding: 10px !important;
.notification-text {
flex-grow: 1;
max-width: 75%;
max-width: 85%;
overflow: hidden;
text-overflow: ellipsis;
}
@ -152,25 +182,6 @@
&::after {
display: none;
}
.mce-container-body.mce-abs-layout {
background: $brand-primary;
position: relative;
top: -10px;
.mce-container,
.mce-widget {
background: transparent !important;
}
.mce-btn:hover {
border-color: transparent;
}
.mce-ico {
color: $color-white;
}
}
}
.mce-window {
@ -193,4 +204,62 @@
}
}
// scss-lint:enable ImportantRule
// fix for TinyMCE 6 vs Boostrap 3 .show conflict
.tox.tox-tinymce.show {
display: flex !important;
}
.tox .tox-pop {
margin-top: -12px;
&::after,
&::before {
display: none !important;
}
.tox-pop__dialog {
border: 0;
border-radius: 0 0 3px 3px;
box-shadow: none;
}
.tox-toolbar {
background: $brand-primary !important;
top: -10px;
button {
color: $color-white;
}
.tox-icon svg {
fill: $color-white;
}
}
}
.tox-edit-area__iframe {
background-color: transparent !important;
z-index: 1;
}
.tox-sidebar-wrap {
flex-direction: column !important;
.restore-draft-notification {
flex-basis: 30px;
}
}
.tox-editor-header {
z-index: 2 !important;
}
.tox-dialog-wrap {
.tox-dialog__body-nav {
.tox-dialog__body-nav-item:nth-child(3) {
display: none;
}
}
}
// scss-lint:enable ImportantRule SelectorDepth

View file

@ -3,8 +3,16 @@
module Api
module V1
class UsersController < BaseController
before_action :load_team, only: :index
before_action :load_user, only: :show
def index
users = @team.users
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: users, each_serializer: UserSerializer
end
def show
render jsonapi: @user, serializer: UserSerializer
end

View file

@ -218,7 +218,7 @@ class AssetsController < ApplicationController
log_step_activity(
:task_step_file_deleted,
@assoc,
@assoc.my_module.experiment.project,
@assoc.my_module.project,
my_module: @assoc.my_module.id,
file: @asset.file_name
)
@ -298,7 +298,7 @@ class AssetsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: step.protocol,
team: current_team,
team: step.protocol.team,
project: project,
message_items: message_items)
end
@ -308,8 +308,8 @@ class AssetsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: result,
team: result.my_module.experiment.project.team,
project: result.my_module.experiment.project,
team: result.my_module.team,
project: result.my_module.project,
message_items: {
result: result.id,
type_of_result: t('activities.result_type.text')

View file

@ -41,8 +41,8 @@ module AssetsActions
.call(activity_type: :edit_image_on_result,
owner: current_user,
subject: asset.result,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
team: my_module.team,
project: my_module.project,
message_items: {
result: asset.result.id,
asset_name: { id: asset.id, value_for: 'file_name' },

View file

@ -104,8 +104,8 @@ module BioEddieActions
.call(activity_type: "#{activity}_molecule_on_result".to_sym,
owner: current_user,
subject: result,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
team: my_module.team,
project: my_module.project,
message_items: message_items)
end
end

View file

@ -118,8 +118,8 @@ module MarvinJsActions
.call(activity_type: (activity + '_chemical_structure_on_result').to_sym,
owner: current_user,
subject: result,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
team: my_module.team,
project: my_module.project,
message_items: message_items)
end
@ -137,8 +137,8 @@ module MarvinJsActions
.call(activity_type: (activity + '_chemical_structure_on_task').to_sym,
owner: current_user,
subject: my_module,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
team: my_module.team,
project: my_module.project,
message_items: message_items)
end

View file

@ -55,6 +55,17 @@ module StepsActions
)
end
def step_text_annotation(step, step_text, old_text = nil)
smart_annotation_notification(
old_text: old_text,
new_text: step_text.text,
title: t('notifications.step_text_title',
user: current_user.full_name,
step: step.name),
message: annotation_message(step)
)
end
def checklist_name_annotation(step, checklist, old_text = nil)
smart_annotation_notification(
old_text: old_text,
@ -85,7 +96,7 @@ module StepsActions
),
experiment: link_to(
step.my_module.experiment.name,
canvas_experiment_url(step.my_module.experiment)
my_modules_experiment_url(step.my_module.experiment)
),
my_module: link_to(
step.my_module.name,

View file

@ -12,12 +12,12 @@ class ExperimentsController < ApplicationController
before_action :check_read_permissions, except: %i(edit archive clone move new create archive_group restore_group)
before_action :check_canvas_read_permissions, only: %i(canvas)
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: %i(edit)
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
before_action :check_update_permissions, only: %i(update)
before_action :check_archive_permissions, only: :archive
before_action :check_clone_permissions, only: %i(clone_modal clone)
before_action :check_move_permissions, only: %i(move_modal move)
before_action :set_inline_name_editing, only: %i(canvas module_archive)
before_action :set_inline_name_editing, only: %i(canvas table module_archive)
layout 'fluid'
@ -46,7 +46,7 @@ class ExperimentsController < ApplicationController
experiment: @experiment.name)
respond_to do |format|
format.json do
render json: { path: canvas_experiment_url(@experiment) }, status: :ok
render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
end
end
else
@ -88,6 +88,37 @@ class ExperimentsController < ApplicationController
.select('my_modules.*').group(:id)
end
def table
@project = @experiment.project
@experiment.current_view_state(current_user)
@my_module_visible_table_columns = current_user.my_module_visible_table_columns
end
def load_table
my_modules = @experiment.my_modules.readable_by_user(current_user)
unless @experiment.archived_branch?
my_modules = params[:view_mode] == 'archived' ? my_modules.archived : my_modules.active
end
render json: Experiments::TableViewService.new(@experiment, my_modules, current_user, params).call
end
def my_modules
view_state = @experiment.current_view_state(current_user)
view_type = view_state.state['my_modules']['view_type'] || 'canvas'
redirect_to view_mode_redirect_url(view_type)
end
def view_type
view_state = @experiment.current_view_state(current_user)
view_state.state['my_modules']['view_type'] = view_type_params
view_state.save!
redirect_to view_mode_redirect_url(view_type_params)
end
def edit
respond_to do |format|
format.json do
@ -208,7 +239,8 @@ class ExperimentsController < ApplicationController
format.json do
render json: {
html: render_to_string(
partial: 'clone_modal.html.erb'
partial: 'clone_modal.html.erb',
locals: { view_mode: params[:view_mode] }
)
}
end
@ -249,6 +281,21 @@ class ExperimentsController < ApplicationController
end
end
def search_tags
tags = @experiment.project.tags.where.not(id: JSON.parse(params[:selected_tags]))
.where_attributes_like(:name, params[:query])
.select(:id, :name, :color)
tags = tags.map do |tag|
{ value: tag.id, label: sanitize_input(tag.name), params: { color: sanitize_input(tag.color) } }
end
if params[:query].present? && tags.select { |tag| tag[:label] == params[:query] }.blank?
tags << { value: 0, label: sanitize_input(params[:query]), params: { color: nil } }
end
render json: tags
end
# POST: move_experiment(id)
def move
service = Experiments::MoveToProjectService
@ -258,8 +305,10 @@ class ExperimentsController < ApplicationController
if service.succeed?
flash[:success] = t('experiments.move.success_flash',
experiment: @experiment.name)
path = canvas_experiment_url(@experiment)
status = :ok
view_state = @experiment.current_view_state(current_user)
view_type = view_state.state['my_modules']['view_type'] || 'canvas'
path = view_mode_redirect_url(view_type)
else
message = service.errors.values.join(', ')
status = :unprocessable_entity
@ -268,7 +317,53 @@ class ExperimentsController < ApplicationController
render json: { message: message, path: path }, status: status
end
def move_modules_modal
@experiments = @experiment.project.experiments.active.where.not(id: @experiment)
.managable_by_user(current_user).order(name: :asc)
render json: {
html: render_to_string(
partial: 'move_modules_modal.html.erb'
)
}
end
def move_modules
modules_to_move = {}
dst_experiment = @experiment.project.experiments.find(params[:to_experiment_id])
return render_403 unless can_manage_experiment?(dst_experiment)
@experiment.transaction do
params[:my_module_ids].each do |id|
my_module = @experiment.my_modules.find(id)
return render_403 unless can_move_my_module?(my_module)
modules_to_move[id] = dst_experiment.id
end
# Make sure that locks are acquired always in the same order
if dst_experiment.id < @experiment.id
dst_experiment.lock! && @experiment.lock!
else
@experiment.lock! && dst_experiment.lock!
end
@experiment.move_modules(modules_to_move, current_user)
@experiment.workflowimg.purge
render json: { message: t('experiments.table.modal_move_modules.success_flash',
experiment: sanitize_input(dst_experiment.name)) }
rescue StandardError => e
Rails.logger.error(e.message)
Rails.logger.error(e.backtrace.join("\n"))
render json: {
message: t('experiments.table.modal_move_modules.error_flash', experiment: sanitize_input(dst_experiment.name))
}, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
rescue ActiveRecord::RecordNotFound
render_404
end
def module_archive
@project = @experiment.project
@my_modules = @experiment.archived_branch? ? @experiment.my_modules : @experiment.my_modules.archived
@my_modules = @my_modules.with_granted_permissions(current_user, MyModulePermissions::READ_ARCHIVED)
.left_outer_joins(:designated_users, :task_comments)
@ -299,11 +394,27 @@ class ExperimentsController < ApplicationController
end
def sidebar
view_state = @experiment.current_view_state(current_user)
view_mode = params[:view_mode].presence || 'active'
default_sort = view_state.state.dig('my_modules', view_mode, 'sort') || 'atoz'
my_modules = if @experiment.archived_branch?
@experiment.my_modules
elsif params[:view_mode] == 'archived'
@experiment.my_modules.archived
else
@experiment.my_modules.active
end
my_modules = sort_my_modules(my_modules, params[:sort].presence || default_sort)
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'shared/sidebar/my_modules.html.erb', locals: { experiment: @experiment }
partial: if params[:view_mode] == 'archived'
'shared/sidebar/archived_my_modules.html.erb'
else
'shared/sidebar/my_modules.html.erb'
end, locals: { experiment: @experiment, my_modules: my_modules }
)
}
end
@ -321,6 +432,73 @@ class ExperimentsController < ApplicationController
end
end
def assigned_users_to_tasks
users = current_team.users.where(id: @experiment.my_modules.joins(:user_my_modules).select(:user_id))
.search(false, params[:query]).map do |u|
{ value: u.id, label: sanitize_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } }
end
render json: users, status: :ok
end
def archive_my_modules
my_modules = @experiment.my_modules.where(id: params[:my_modules])
counter = 0
my_modules.each do |my_module|
next unless can_archive_my_module?(my_module)
my_module.transaction do
connect_my_modules_before_archive(my_module)
my_module.archive!(current_user)
log_my_module_activity(:archive_module, my_module)
counter += 1
rescue StandardError => e
Rails.logger.error e.message
raise ActiveRecord::Rollback
end
end
if counter.positive?
render json: { message: t('experiments.table.archive_group.success_flash', number: counter) }
else
render json: { message: t('experiments.table.archive_group.error_flash') }, status: :unprocessable_entity
end
end
def batch_clone_my_modules
MyModule.transaction do
@my_modules =
@experiment.my_modules
.readable_by_user(current_user)
.where(id: params[:my_module_ids])
@my_modules.find_each do |my_module|
new_my_module = my_module.dup
new_my_module.my_module_status = MyModuleStatus.first
new_my_module.update!(
{
provisioning_status: :in_progress,
name: my_module.next_clone_name,
created_by: current_user,
due_date: nil,
started_on: nil,
state: 'uncompleted',
completed_on: nil
}.merge(new_my_module.get_new_position)
)
new_my_module.designated_users << current_user
MyModules::CopyContentJob.perform_later(current_user, my_module.id, new_my_module.id)
end
@experiment.workflowimg.purge
end
render(
json: {
provisioning_status_urls: @my_modules.map { |m| provisioning_status_my_module_url(m) }
}
)
end
private
def load_experiment
@ -341,10 +519,14 @@ class ExperimentsController < ApplicationController
params.require(:experiment).require(:project_id)
end
def view_type_params
params.require(:experiment).require(:view_type)
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)
(@experiment.archived? && can_read_archived_experiment?(@experiment))
end
def check_canvas_read_permissions
@ -402,7 +584,7 @@ class ExperimentsController < ApplicationController
project: link_to(@experiment.project.name,
project_url(@experiment.project)),
experiment: link_to(@experiment.name,
canvas_experiment_url(@experiment)))
my_modules_experiment_url(@experiment)))
)
end
@ -410,9 +592,61 @@ class ExperimentsController < ApplicationController
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: experiment.project.team,
team: experiment.team,
project: experiment.project,
subject: experiment,
message_items: { experiment: experiment.id })
end
def log_my_module_activity(type_of, my_module)
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
subject: my_module,
message_items: { my_module: my_module.id })
end
def view_mode_redirect_url(view_type)
if params[:view_mode] == 'archived' || @experiment.archived_branch?
case view_type
when 'canvas'
module_archive_experiment_path(@experiment)
else
table_experiment_path(@experiment, view_mode: :archived)
end
else
view_type == 'canvas' ? canvas_experiment_path(@experiment) : table_experiment_path(@experiment)
end
end
def sort_my_modules(records, sort)
case sort
when 'due_first'
records.order(:due_date, :name)
when 'due_last'
records.order(Arel.sql("COALESCE(due_date, DATE '2100-01-01') DESC"), :name)
when 'atoz'
records.order(:name)
when 'ztoa'
records.order(name: :desc)
when 'archived_old'
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) ASC'))
when 'archived_new'
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) DESC'))
else
records
end
end
def connect_my_modules_before_archive(my_module)
return if my_module.my_modules.empty? || my_module.my_module_antecessors.empty?
my_module.my_modules.each do |destination_my_module|
my_module.my_module_antecessors.each do |source_my_module|
Connection.create!(input_id: destination_my_module.id, output_id: source_my_module.id)
end
end
end
end

View file

@ -7,7 +7,7 @@ class LabelTemplatesController < ApplicationController
before_action :check_view_permissions, except: %i(create duplicate set_default delete update)
before_action :check_manage_permissions, only: %i(create duplicate set_default delete update)
before_action :load_label_templates, only: %i(index datatable)
before_action :load_label_template, only: %i(show set_default update)
before_action :load_label_template, only: %i(show set_default update template_tags)
layout 'fluid'
@ -125,7 +125,7 @@ class LabelTemplatesController < ApplicationController
end
def template_tags
render json: LabelTemplates::TagService.new(current_team).tags
render json: LabelTemplates::TagService.new(current_team, @label_template).tags
end
def zpl_preview

View file

@ -135,7 +135,7 @@ class MyModuleRepositoriesController < ApplicationController
activity_type: :export_inventory_items_assigned_to_task,
owner: current_user,
subject: @my_module,
team: current_team,
team: @repository.team,
message_items: {
my_module: @my_module.id,
repository: @repository.id
@ -240,7 +240,7 @@ class MyModuleRepositoriesController < ApplicationController
user: current_user.full_name),
message: t('notifications.my_module_consumption_comment_annotation_message_html',
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
)
end
@ -251,7 +251,7 @@ class MyModuleRepositoriesController < ApplicationController
owner: current_user,
subject: @my_module,
team: @repository.team,
project: @my_module.experiment.project,
project: @my_module.project,
message_items: {
repository: @repository.id,
repository_row: module_repository_row.repository_row_id,

View file

@ -109,7 +109,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController
activity_type: :export_inventory_snapshot_items_assigned_to_task,
owner: current_user,
subject: @my_module,
team: current_team,
team: @my_module.team,
message_items: {
my_module: @my_module.id,
repository_snapshot: @repository_snapshot.id,

View file

@ -70,9 +70,8 @@ class MyModuleTagsController < ApplicationController
.call(activity_type: :add_task_tag,
owner: current_user,
subject: my_module,
project:
my_module.experiment.project,
team: current_team,
project: my_module.project,
team: my_module.team,
message_items: {
my_module: my_module.id,
tag: @mt.tag.id
@ -95,9 +94,8 @@ class MyModuleTagsController < ApplicationController
.call(activity_type: :remove_task_tag,
owner: current_user,
subject: @mt.my_module,
project:
@mt.my_module.experiment.project,
team: current_team,
project: @mt.my_module.project,
team: @mt.my_module.team,
message_items: {
my_module: @mt.my_module.id,
tag: @mt.tag.id
@ -139,9 +137,8 @@ class MyModuleTagsController < ApplicationController
.call(activity_type: :remove_task_tag,
owner: current_user,
subject: tag.my_module,
project:
tag.my_module.experiment.project,
team: current_team,
project: tag.my_module.project,
team: tag.my_module.team,
message_items: {
my_module: tag.my_module.id,
tag: tag.tag.id

View file

@ -5,19 +5,69 @@ class MyModulesController < ApplicationController
include Rails.application.routes.url_helpers
include ActionView::Helpers::UrlHelper
include ApplicationHelper
include MyModulesHelper
before_action :load_vars, except: %i(restore_group)
before_action :load_vars, except: %i(restore_group create new save_table_state)
before_action :load_experiment, only: %i(create new)
before_action :check_create_permissions, only: %i(new create)
before_action :check_archive_permissions, only: %i(update)
before_action :check_manage_permissions, only: %i(
description due_date update_description update_protocol_description update_protocol
)
before_action :check_read_permissions, except: %i(update update_description update_protocol_description restore_group)
before_action :check_read_permissions, except: %i(create new update update_description
update_protocol_description restore_group save_table_state)
before_action :check_update_state_permissions, only: :update_state
before_action :set_inline_name_editing, only: %i(protocols results activities archive)
before_action :load_experiment_my_modules, only: %i(protocols results activities archive)
layout 'fluid'.freeze
def new
@my_module = @experiment.my_modules.new
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
render json: {
html: render_to_string(
partial: 'my_modules/modals/new_modal.html.erb', locals: { view_mode: params[:view_mode],
users: assigned_users }
)
}
end
def create
@my_module = @experiment.my_modules.new(my_module_params)
new_pos = @my_module.get_new_position
@my_module.assign_attributes(
created_by: current_user,
last_modified_by: current_user,
x: new_pos[:x],
y: new_pos[:y]
)
@my_module.transaction do
if my_module_tags_params[:tag_ids].present?
@my_module.tags << @experiment.project.tags.where(id: JSON.parse(my_module_tags_params[:tag_ids]))
end
if my_module_designated_users_params[:user_ids].present? && can_designate_users_to_new_task?(@experiment)
@my_module.designated_users << @experiment.users.where(id: my_module_designated_users_params[:user_ids])
elsif !can_designate_users_to_new_task?(@experiment)
@my_module.designated_users << current_user
end
@my_module.save!
Activities::CreateActivityService.call(
activity_type: :create_module,
owner: current_user,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
subject: @my_module,
message_items: { my_module: @my_module.id }
)
redirect_to canvas_experiment_path(@experiment) if params[:my_module][:view_mode] == 'canvas'
rescue ActiveRecord::RecordInvalid
render json: @my_module.errors, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
def show
respond_to do |format|
format.json {
@ -46,6 +96,11 @@ class MyModulesController < ApplicationController
end
end
def save_table_state
current_user.settings.update(visible_my_module_table_columns: params[:columns])
current_user.save!
end
def status_state
respond_to do |format|
format.json do
@ -195,6 +250,11 @@ class MyModulesController < ApplicationController
partial: 'my_modules/card_due_date_label.html.erb',
locals: { my_module: @my_module }
),
table_due_date_label: {
html: render_to_string(partial: 'experiments/table_due_date_label.html.erb',
locals: { my_module: @my_module, user: current_user }),
due_status: my_module_due_status(@my_module)
},
module_header_due_date: render_to_string(
partial: 'my_modules/module_header_due_date.html.erb',
locals: { my_module: @my_module }
@ -276,12 +336,14 @@ class MyModulesController < ApplicationController
def update_protocol
protocol = @my_module.protocol
old_description = protocol.description
ActiveRecord::Base.transaction do
protocol.update!(protocol_params)
log_activity(:protocol_name_in_task_edited) if protocol.saved_change_to_name?
log_activity(:protocol_description_in_task_edited) if protocol.saved_change_to_description?
TinyMceAsset.update_images(protocol, params[:tiny_mce_images], current_user)
protocol_annotation_notification(old_description)
end
render json: protocol, serializer: ProtocolSerializer, user: current_user
@ -334,7 +396,12 @@ class MyModulesController < ApplicationController
else
flash[:error] = t('my_modules.restore_group.error_flash')
end
redirect_to module_archive_experiment_path(experiment)
if params[:view] == 'table'
redirect_to table_experiment_path(experiment, view_mode: :archived)
else
redirect_to module_archive_experiment_path(experiment)
end
end
def update_state
@ -349,6 +416,32 @@ class MyModulesController < ApplicationController
end
end
def permissions
if stale?(@my_module)
render json: {
editable: can_manage_my_module?(@my_module),
moveable: can_move_my_module?(@my_module),
archivable: can_archive_my_module?(@my_module),
restorable: can_restore_my_module?(@my_module)
}
end
end
def actions_dropdown
if stale?(@my_module)
render json: {
html: render_to_string(
partial: 'experiments/table_row_actions',
locals: { my_module: @my_module }
)
}
end
end
def provisioning_status
render json: { provisioning_status: @my_module.provisioning_status }
end
private
def load_vars
@ -361,6 +454,11 @@ class MyModulesController < ApplicationController
end
end
def load_experiment
@experiment = Experiment.preload(user_assignments: %i(user user_role)).find_by(id: params[:id])
render_404 unless @experiment
end
def load_experiment_my_modules
@experiment_my_modules = if @my_module.experiment.archived_branch?
@my_module.experiment.my_modules.order(:name)
@ -369,6 +467,10 @@ class MyModulesController < ApplicationController
end
end
def check_create_permissions
render_403 && return unless can_manage_experiment?(@experiment)
end
def check_manage_permissions
render_403 && return unless can_manage_my_module?(@my_module)
end
@ -401,18 +503,26 @@ class MyModulesController < ApplicationController
end
def my_module_params
update_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
permitted_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
if update_params[:started_on].present?
update_params[:started_on] =
Time.zone.strptime(update_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
if permitted_params[:started_on].present?
permitted_params[:started_on] =
Time.zone.strptime(permitted_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
if update_params[:due_date].present?
update_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
if permitted_params[:due_date].present?
permitted_params[:due_date] =
Time.zone.strptime(permitted_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
update_params
permitted_params
end
def my_module_tags_params
params.require(:my_module).permit(:tag_ids)
end
def my_module_designated_users_params
params.require(:my_module).permit(user_ids: [])
end
def protocol_params
@ -458,8 +568,8 @@ class MyModulesController < ApplicationController
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
team: my_module.team,
project: my_module.project,
subject: my_module,
message_items: message_items)
end
@ -479,7 +589,7 @@ class MyModulesController < ApplicationController
user: current_user.full_name),
message: t('notifications.my_module_description_annotation_message_html',
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
)
end
@ -493,7 +603,7 @@ class MyModulesController < ApplicationController
user: current_user.full_name),
message: t('notifications.my_module_protocol_annotation_message_html',
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
)
end

View file

@ -13,7 +13,7 @@ class ProjectsController < ApplicationController
before_action :switch_team_with_param, only: :index
before_action :load_vars, only: %i(show permissions edit update notifications
sidebar experiments_cards view_type actions_dropdown)
sidebar experiments_cards view_type actions_dropdown create_tag)
before_action :load_current_folder, only: %i(index cards new show)
before_action :check_view_permissions, except: %i(index cards new create edit update archive_group restore_group
users_filter actions_dropdown)
@ -264,6 +264,24 @@ class ProjectsController < ApplicationController
end
end
def create_tag
render_403 unless can_manage_project_tags?(@project)
@tag = @project.tags.create(tag_params.merge({
created_by: current_user,
last_modified_by: current_user,
color: Constants::TAG_COLORS.sample
}))
render json: {
tag: {
id: @tag.id,
name: @tag.name,
color: @tag.color
}
}
end
def restore_group
projects = current_team.projects.archived.where(id: params[:projects_ids])
counter = 0
@ -287,9 +305,6 @@ class ProjectsController < ApplicationController
end
def show
# This is the "info" view
current_team_switch(@project.team)
view_state = @project.current_view_state(current_user)
@current_sort = view_state.state.dig('experiments', experiments_view_mode(@project), 'sort') || 'atoz'
@current_view_type = view_state.state.dig('experiments', 'view_type')
@ -372,11 +387,15 @@ class ProjectsController < ApplicationController
end
def load_vars
@project = Project.find_by(id: params[:id])
@project = Project.find_by(id: params[:id] || params[:project_id])
render_404 unless @project
end
def tag_params
params.require(:tag).permit(:name)
end
def load_current_folder
if current_team && params[:project_folder_id].present?
@current_folder = current_team.project_folders.find_by(id: params[:project_folder_id])
@ -386,6 +405,7 @@ class ProjectsController < ApplicationController
end
def check_view_permissions
current_team_switch(@project.team) if current_team != @project.team
render_403 unless can_read_project?(@project)
end

View file

@ -1,4 +1,3 @@
class ProtocolsController < ApplicationController
include RenamingUtil
include ActionView::Helpers::TextHelper
@ -16,6 +15,7 @@ class ProtocolsController < ApplicationController
before_action :check_clone_permissions, only: [:clone]
before_action :check_view_permissions, only: %i(
show
edit
protocol_status_bar
updated_at_label
preview
@ -30,7 +30,6 @@ class ProtocolsController < ApplicationController
# For update_from_parent and update_from_parent_modal we don't need to check
# read permission for the parent protocol
before_action :check_manage_permissions, only: %i(
edit
update_keywords
update_description
update_name
@ -161,14 +160,10 @@ class ProtocolsController < ApplicationController
end
def edit
# Switch to correct team
current_team_switch(@protocol.team)
render :show
end
def show
# Switch to correct team
current_team_switch(@protocol.team)
respond_to do |format|
format.json { render json: @protocol, serializer: ProtocolSerializer, user: current_user }
format.html
@ -256,9 +251,7 @@ class ProtocolsController < ApplicationController
end
def delete_steps
@protocol.my_module.lock!
Protocol.transaction do
@protocol.with_lock do
team = @protocol.team
previous_size = 0
@protocol.steps.each do |step|
@ -276,7 +269,6 @@ class ProtocolsController < ApplicationController
# skip adjusting positions after destroy as this is a bulk delete
step.skip_position_adjust = true
step.destroy!
end
@ -622,7 +614,7 @@ class ProtocolsController < ApplicationController
.call(activity_type: :import_protocol_in_repository,
owner: current_user,
subject: protocol,
team: current_team,
team: protocol.team,
message_items: {
protocol: protocol.id
})
@ -820,15 +812,15 @@ class ProtocolsController < ApplicationController
file_name = 'protocols.eln'
end
@protocols.each do |p|
@protocols.each do |protocol|
if params[:my_module_id]
my_module = MyModule.find(params[:my_module_id])
Activities::CreateActivityService
.call(activity_type: :export_protocol_from_task,
owner: current_user,
project: my_module.experiment.project,
project: my_module.project,
subject: my_module,
team: current_team,
team: my_module.team,
message_items: {
my_module: params[:my_module_id].to_i
})
@ -836,10 +828,10 @@ class ProtocolsController < ApplicationController
Activities::CreateActivityService
.call(activity_type: :export_protocol_in_repository,
owner: current_user,
subject: p,
team: current_team,
subject: protocol,
team: protocol.team,
message_items: {
protocol: p.id
protocol: protocol.id
})
end
end
@ -1102,6 +1094,7 @@ class ProtocolsController < ApplicationController
def check_view_permissions
@protocol = Protocol.find_by_id(params[:id])
current_team_switch(@protocol.team) if current_team != @protocol.team
unless @protocol.present? &&
(can_read_protocol_in_module?(@protocol) ||
can_read_protocol_in_repository?(@protocol))
@ -1232,7 +1225,7 @@ class ProtocolsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: current_team,
team: @protocol.team,
project: project,
message_items: message_items)
end
@ -1245,7 +1238,7 @@ class ProtocolsController < ApplicationController
user: current_user.full_name,
protocol: @protocol.name),
message: t('notifications.protocol_description_annotation_message_html',
protocol: link_to(@protocol.name, edit_protocol_url(@protocol)))
protocol: link_to(@protocol.name, protocol_url(@protocol)))
)
end
end

View file

@ -110,7 +110,7 @@ class ReportsController < ApplicationController
@project_contents = {
experiments: @report.report_elements.order(:position).experiment.pluck(:experiment_id),
my_modules: @report.report_elements.order(:position).my_module.pluck(:my_module_id),
repositories: @report.report_elements.my_module_repository.distinct(:repository_id).pluck(:repository_id)
repositories: @report.settings.dig(:task, :repositories)
}
render :new
end

View file

@ -521,7 +521,7 @@ class RepositoriesController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @repository,
team: current_team,
team: @repository.team,
message_items: message_items)
end
end

View file

@ -167,7 +167,7 @@ class RepositoryColumnsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @repository,
team: current_team,
team: @repository.team,
message_items: {
repository_column: @repository_column.id,
repository: @repository.id

View file

@ -432,7 +432,7 @@ class RepositoryRowsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: repository_row,
team: current_team,
team: @repository.team,
message_items: {
repository_row: repository_row.id,
repository: @repository.id

View file

@ -191,8 +191,8 @@ class ResultAssetsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: result,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
team: @my_module.team,
project: @my_module.project,
message_items: {
result: result.id,
type_of_result: t('activities.result_type.asset')

View file

@ -180,8 +180,8 @@ class ResultTablesController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @result,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
team: @my_module.team,
project: @my_module.project,
message_items: {
result: @result.id,
type_of_result: t('activities.result_type.table')

View file

@ -192,8 +192,8 @@ class ResultTextsController < ApplicationController
.experiment
.project)),
experiment: link_to(@result.my_module.experiment.name,
canvas_experiment_url(@result.my_module
.experiment)),
my_modules_experiment_url(@result.my_module
.experiment)),
my_module: link_to(@result.my_module.name,
protocols_my_module_url(
@result.my_module
@ -206,8 +206,8 @@ class ResultTextsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @result,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
team: @my_module.team,
project: @my_module.project,
message_items: {
result: @result.id,
type_of_result: t('activities.result_type.text')

View file

@ -14,8 +14,8 @@ class ResultsController < ApplicationController
.call(activity_type: :destroy_result,
owner: current_user,
subject: @result,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
team: @my_module.team,
project: @my_module.project,
message_items: { result: @result.id,
type_of_result: result_type })
flash[:success] = t('my_modules.module_archive.delete_flash',

View file

@ -78,8 +78,8 @@ class StepCommentsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: current_team,
project: @step.my_module.experiment.project,
team: @step.my_module.team,
project: @step.my_module.project,
message_items: {
my_module: @step.my_module.id,
step: @step.id,

View file

@ -40,8 +40,8 @@ module StepElements
Activities::CreateActivityService.call(
activity_type: "#{!@step.protocol.in_module? ? 'protocol_step_' : 'task_step_'}#{element_type_of}",
owner: current_user,
team: @protocol.in_module? ? @protocol.my_module.experiment.project.team : @protocol.team,
project: @protocol.in_module? ? @protocol.my_module.experiment.project : nil,
team: @protocol.team,
project: @protocol.in_module? ? @protocol.my_module.project : nil,
subject: @protocol,
message_items: {
step: @step.id,

View file

@ -3,6 +3,7 @@
module StepElements
class ChecklistItemsController < ApplicationController
include ApplicationHelper
include StepsActions
before_action :load_vars
before_action :load_checklist_item, only: %i(update toggle destroy)
@ -21,6 +22,7 @@ module StepElements
checklist_name: @checklist.name
}
)
checklist_item_annotation(@step, checklist_item)
end
render json: checklist_item, serializer: ChecklistItemSerializer, user: current_user
@ -31,6 +33,7 @@ module StepElements
end
def update
old_text = @checklist_item.text
@checklist_item.assign_attributes(
checklist_item_params.merge(last_modified_by: current_user)
)
@ -41,6 +44,7 @@ module StepElements
checklist_item: @checklist_item.text,
checklist_name: @checklist.name
)
checklist_item_annotation(@step, @checklist_item, old_text)
end
render json: @checklist_item, serializer: ChecklistItemSerializer, user: current_user
@ -129,6 +133,8 @@ module StepElements
@step = Step.find_by(id: params[:step_id])
return render_404 unless @step
@protocol = @step.protocol
@checklist = @step.checklists.find_by(id: params[:checklist_id])
return render_404 unless @checklist
end
@ -144,7 +150,7 @@ module StepElements
owner: current_user,
subject: @step.protocol,
team: @step.protocol.team,
project: @step.protocol.in_module? ? @step.protocol.my_module.experiment.project : nil,
project: @step.protocol.in_module? ? @step.protocol.my_module.project : nil,
message_items: message_items.merge(step_message_items)
)
end

View file

@ -2,8 +2,9 @@
module StepElements
class ChecklistsController < BaseController
include ApplicationHelper
include StepsActions
before_action :load_checklist, only: %i(update destroy duplicate)
def create
checklist = @step.checklists.build(
name: t('protocols.steps.checklist.default_name', position: @step.checklists.length + 1)
@ -11,6 +12,7 @@ module StepElements
ActiveRecord::Base.transaction do
create_in_step!(@step, checklist)
log_step_activity(:checklist_added, { checklist_name: checklist.name })
checklist_name_annotation(@step, checklist)
end
render_step_orderable_element(checklist)
rescue ActiveRecord::RecordInvalid
@ -18,9 +20,11 @@ module StepElements
end
def update
old_name = @checklist.name
ActiveRecord::Base.transaction do
@checklist.update!(checklist_params)
log_step_activity(:checklist_edited, { checklist_name: @checklist.name })
checklist_name_annotation(@step, @checklist, old_name)
end
render json: @checklist, serializer: ChecklistSerializer, user: current_user
@ -43,6 +47,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)

View file

@ -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)

View file

@ -2,6 +2,9 @@
module StepElements
class TextsController < BaseController
include ApplicationHelper
include StepsActions
before_action :load_step_text, only: %i(update destroy duplicate)
def create
@ -18,10 +21,12 @@ module StepElements
end
def update
old_text = @step_text.text
ActiveRecord::Base.transaction do
@step_text.update!(step_text_params)
TinyMceAsset.update_images(@step_text, params[:tiny_mce_images], current_user)
log_step_activity(:text_edited, { text_name: @step_text.name })
step_text_annotation(@step, @step_text, old_text)
end
render json: @step_text, serializer: StepTextSerializer, user: current_user

View file

@ -48,7 +48,7 @@ class StepOrderableElementsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: current_team,
team: @protocol.team,
project: project,
message_items: message_items)
end

View file

@ -38,7 +38,7 @@ class StepsController < ApplicationController
@asset = @step.assets.create!(
created_by: current_user,
last_modified_by: current_user,
team: current_team,
team: @protocol.team,
view_mode: @step.assets_view_mode
)
@asset.file.attach(params[:signed_blob_id])
@ -323,7 +323,7 @@ class StepsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: current_team,
team: @protocol.team,
project: project,
message_items: message_items)
end

View file

@ -168,7 +168,7 @@ class TagsController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: subject,
team: current_team,
team: @tag.project.team,
project: @tag.project,
message_items: message_items)
end

View file

@ -97,7 +97,7 @@ class TeamRepositoriesController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: team_shared_object.shared_repository,
team: current_team,
team: @repository.team,
message_items: { repository: team_shared_object.shared_repository.id,
team: team_shared_object.team.id,
permission_level:

View file

@ -61,14 +61,22 @@ class UserMyModulesController < ApplicationController
respond_to do |format|
format.json do
render json: {
user: {
id: @um.user.id,
full_name: @um.user.full_name,
avatar_url: avatar_path(@um.user, :icon_small),
user_module_id: @um.id
}, status: :ok
}
if params[:table]
render json: {
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
locals: { my_module: @my_module, user: current_user, skip_unassigned: false }),
unassign_url: my_module_user_my_module_path(@my_module, @um)
}
else
render json: {
user: {
id: @um.user.id,
full_name: @um.user.full_name,
avatar_url: avatar_path(@um.user, :icon_small),
user_module_id: @um.id
}, status: :ok
}
end
end
end
else
@ -88,7 +96,14 @@ class UserMyModulesController < ApplicationController
respond_to do |format|
format.json do
render json: {}, status: :ok
if params[:table]
render json: {
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
locals: { my_module: @my_module, user: current_user, skip_unassigned: false })
}
else
render json: {}, status: :ok
end
end
end
else
@ -104,19 +119,37 @@ class UserMyModulesController < ApplicationController
def search
users = @my_module.users
.where.not(id: @my_module.designated_users.select(:id))
.joins("LEFT OUTER JOIN user_my_modules ON user_my_modules.user_id = users.id "\
"AND user_my_modules.my_module_id = #{@my_module.id}")
.search(false, params[:query])
.order(:full_name)
.limit(Constants::SEARCH_LIMIT)
.select('users.*', 'user_my_modules.id as user_my_module_id')
.select('CASE WHEN user_my_modules.id IS NOT NULL '\
'THEN true ELSE false END as designated')
users = users.map do |user|
{
next if params[:skip_assigned] && user.designated
next if ActiveModel::Type::Boolean.new.cast(params[:skip_unassigned]) && !user.designated
user_hash = {
value: user.id,
label: sanitize_input(user.full_name),
params: { avatar_url: avatar_path(user, :icon_small) }
params: {
avatar_url: avatar_path(user, :icon_small),
designated: user.designated,
assign_url: my_module_user_my_modules_path(@my_module)
}
}
if user.designated
user_hash[:params][:unassign_url] = my_module_user_my_module_path(@my_module, user.user_my_module_id)
end
user_hash
end
render json: users
render json: users.compact
end
private

View file

@ -157,8 +157,7 @@ module CommentHelper
.experiment
.project)),
experiment: link_to(result.my_module.experiment.name,
canvas_experiment_url(result.my_module
.experiment)),
my_modules_experiment_url(result.my_module.experiment)),
my_module: link_to(result.my_module.name,
protocols_my_module_url(
result.my_module
@ -193,8 +192,7 @@ module CommentHelper
.experiment
.project)),
experiment: link_to(step.my_module.experiment.name,
canvas_experiment_url(step.my_module
.experiment)),
my_modules_experiment_url(step.my_module.experiment)),
my_module: link_to(step.my_module.name,
protocols_my_module_url(
step.my_module
@ -218,8 +216,7 @@ module CommentHelper
.experiment
.project)),
experiment: link_to(my_module.experiment.name,
canvas_experiment_url(my_module
.experiment)),
my_modules_experiment_url(my_module.experiment)),
my_module: link_to(my_module.name,
protocols_my_module_url(
my_module
@ -278,4 +275,8 @@ module CommentHelper
def has_unseen_comments?(commentable)
commentable.comments.any? { |comment| comment.unseen_by.include?(current_user.id) }
end
def count_unseen_comments(commentable, current_user)
commentable.comments.count { |comment| comment.unseen_by.include?(current_user.id) }
end
end

View file

@ -72,12 +72,12 @@ module GlobalActivitiesHelper
when Experiment
return current_value unless obj.navigable?
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : canvas_experiment_path(obj)
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : my_modules_experiment_path(obj)
when MyModule
return current_value unless obj.navigable?
path = if obj.archived?
module_archive_experiment_path(obj.experiment)
my_modules_experiment_path(obj.experiment, view_mode: :archived)
else
protocols_my_module_path(obj)
end

View file

@ -28,11 +28,14 @@ module InputSanitizeHelper
base64_encoded_imgs = options.fetch(:base64_encoded_imgs, false)
text = sanitize_input(text, tags)
text = simple_format(text, {}, format_opt) if simple_f
if text =~ SmartAnnotations::TagToHtml::USER_REGEX || text =~ SmartAnnotations::TagToHtml::REGEX
text = smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)
end
auto_link(
custom_link_open_new_tab(smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)),
text,
html: { target: '_blank' },
link: :urls,
sanitize: false,
html: { target: '_blank' }
sanitize: false
).html_safe
end
end

View file

@ -31,7 +31,7 @@ module MyModulesHelper
def get_task_alert_color(my_module)
alert = ''
if my_module.active? && !my_module.completed?
if !my_module.archived_branch? && !my_module.completed?
alert = ' alert-yellow' if my_module.is_one_day_prior?
alert = ' alert-red' if my_module.is_overdue?
end
@ -100,4 +100,16 @@ module MyModulesHelper
my_module.experiment.project.archived_on
end
end
def my_module_due_status(my_module, datetime = DateTime.current)
return if my_module.archived_branch? || my_module.completed?
if my_module.is_overdue?(datetime)
I18n.t('my_modules.details.overdue')
elsif my_module.is_one_day_prior?(datetime)
I18n.t('my_modules.details.due_soon')
else
''
end
end
end

View file

@ -14,7 +14,7 @@ module ProjectsHelper
end
def user_names_with_roles(user_assignments)
user_assignments.map { |up| user_name_with_role(up) }.join('&#013;').html_safe
user_assignments.map { |up| user_name_with_role(up) }.join('&#013;')
end
def user_name_with_role(user_assignment)

View file

@ -6,22 +6,25 @@ module RepositoryDatatableHelper
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
has_stock_management = repository.has_stock_management?
reminders_enabled = Repository.reminders_enabled?
reminder_row_ids = reminders_enabled ? repository_reminder_row_ids(repository_rows, repository) : []
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
repository_rows.map do |record|
default_cells = public_send("#{repository.class.name.underscore}_default_columns", record)
row = {
row = public_send("#{repository.class.name.underscore}_default_columns", record)
row.merge!(
DT_RowId: record.id,
DT_RowAttr: { 'data-state': row_style(record) },
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
hasActiveReminders: reminder_row_ids.include?(record.id),
rowRemindersUrl:
Rails.application.routes.url_helpers
.active_reminder_repository_cells_repository_repository_row_url(
repository,
record
)
}.merge(default_cells)
)
if reminders_enabled
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
end
if has_stock_management
row['manageStockUrl'] = if record.has_stock?
@ -100,7 +103,7 @@ module RepositoryDatatableHelper
def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {})
has_stock_management = repository.has_stock_management?
reminders_enabled = Repository.reminders_enabled?
reminder_row_ids = reminders_enabled ? repository_reminder_row_ids(repository_rows, repository) : []
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
repository_rows.map do |record|
row = {
@ -108,7 +111,6 @@ module RepositoryDatatableHelper
DT_RowAttr: { 'data-state': row_style(record) },
'0': escape_input(record.name),
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record),
hasActiveReminders: reminder_row_ids.include?(record.id),
rowRemindersUrl:
Rails.application.routes.url_helpers
.active_reminder_repository_cells_repository_repository_row_url(
@ -117,6 +119,10 @@ module RepositoryDatatableHelper
)
}
if reminders_enabled
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
end
if has_stock_management
stock_present = record.repository_stock_cell.present?
# Always disabled in a simple view
@ -275,14 +281,33 @@ module RepositoryDatatableHelper
''
end
def repository_reminder_row_ids(repository_rows, repository)
# don't load reminders for archived repositories
return [] if repository_rows.blank? || repository.archived?
def with_reminders_status(repository_rows, repository)
# don't load reminders for archived repositories or snapshots
if repository.archived? || repository.is_a?(RepositorySnapshot)
return repository_rows.select('FALSE AS has_active_stock_reminders')
.select('FALSE AS has_active_datetime_reminders')
end
# don't load reminders for snapshots
return [] if repository.is_a?(RepositorySnapshot)
repository_cells = RepositoryCell.joins(
"INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id " \
"AND repository_columns.repository_id = #{repository.id}"
)
repository_rows.active.with_active_reminders(current_user).to_a.pluck(:id).uniq
repository_rows
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.stock_reminder_repository_cells_scope(repository_cells, current_user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_stock_reminders " \
"ON repository_cells_with_active_stock_reminders.repository_row_id = repository_rows.id"
)
.joins(
"LEFT OUTER JOIN (#{RepositoryCell.date_time_reminder_repository_cells_scope(repository_cells, current_user)
.select(:id, :repository_row_id).to_sql}) " \
"AS repository_cells_with_active_datetime_reminders " \
"ON repository_cells_with_active_datetime_reminders.repository_row_id = repository_rows.id"
)
.select('COUNT(repository_cells_with_active_stock_reminders.id) > 0 AS has_active_stock_reminders')
.select('COUNT(repository_cells_with_active_datetime_reminders.id) > 0 AS has_active_datetime_reminders')
end
def stock_consumption_permitted?(repository, my_module)

489
app/javascript/packs/tiny_mce.js vendored Normal file
View file

@ -0,0 +1,489 @@
/* global I18n hljs GLOBAL_CONSTANTS HelperModule SmartAnnotation TinyMCE */
import tinyMCE from 'tinymce/tinymce';
import 'tinymce/models/dom';
import 'tinymce/icons/default';
import 'tinymce/themes/silver';
import 'tinymce/plugins/table';
import 'tinymce/plugins/autosave';
import 'tinymce/plugins/autoresize';
import 'tinymce/plugins/link';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/codesample';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/charmap';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/wordcount';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/visualchars';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/nonbreaking';
import 'tinymce/plugins/save';
import 'tinymce/plugins/help';
import 'tinymce/plugins/quickbars';
import 'tinymce/plugins/directionality';
import './tinymce/custom_image_uploader/plugin';
import './tinymce/marvinjs/plugin';
import './tinymce/image_toolbar/plugin';
// Content styles, including inline UI like fake cursors
// All the above CSS files are loaded on to the page but these two must
// be loaded into the editor iframe so they are loaded as strings and passed
// to the init function.
import 'raw-loader';
import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css';
import contentUiCss from '!!raw-loader!tinymce/skins/ui/tinymce-5/content.min.css';
const contentPStyle = 'p { margin: 0; padding: 0 }';
const contentStyle = [contentCss, contentUiCss, contentPStyle].map((s) => s.toString()).join('\n');
window.TinyMCE = (() => {
function initHighlightjs() {
$('[class*=language]').each((i, block) => {
hljs.highlightBlock(block);
});
}
function initHighlightjsIframe(iframe) {
$('[class*=language]', iframe).each((i, block) => {
hljs.highlightBlock(block);
});
}
function makeItDirty(editor) {
const editorForm = $(editor.getContainer()).closest('form');
editorForm.find('.tinymce-status-badge').addClass('hidden');
$(editor.getContainer()).find('.tinymce-save-button').removeClass('hidden');
}
// Get LocalStorage auto save path
function getAutoSavePrefix(editor) {
let prefix = editor.getParam('autosave_prefix', 'tinymce-autosave-{path}{query}{hash}-{id}-');
prefix = prefix.replace(/\{path\}/g, document.location.pathname);
prefix = prefix.replace(/\{query\}/g, document.location.search);
prefix = prefix.replace(/\{hash\}/g, document.location.hash);
prefix = prefix.replace(/\{id\}/g, editor.id);
return prefix;
}
// Handles autosave notification if draft is available in the local storage
function restoreDraftNotification(selector, editor) {
const prefix = getAutoSavePrefix(editor);
const lastDraftTime = parseInt(tinyMCE.util.LocalStorage.getItem(`${prefix}time`), 10);
const lastUpdated = $(selector).data('last-updated');
let notificationBar;
const restoreBtn = $('<button class="btn restore-draft-btn">Restore Draft</button>');
const cancelBtn = $('<span class="fas fa-times"></span>');
// Check whether we have draft stored
if (editor.plugins.autosave.hasDraft()) {
notificationBar = $('<div class="restore-draft-notification"></div>');
if (lastDraftTime < lastUpdated) {
notificationBar.html(`<span class="notification-text">${I18n.t('tiny_mce.older_version_available')}</span>`);
} else {
notificationBar.html(`<span class="notification-text">${I18n.t('tiny_mce.newer_version_available')}</span>`);
}
// Add notification bar
$(notificationBar).append(restoreBtn).append(cancelBtn);
$(editor.contentAreaContainer).before(notificationBar);
// Prevents save on blur if clicking draft notification
$('.restore-draft-notification').on('mousedown', () => {
editor.isBlurTempDisabled = true;
setTimeout(() => {
editor.isBlurTempDisabled = false;
}, 500);
});
$(restoreBtn).click(() => {
editor.plugins.autosave.restoreDraft();
makeItDirty(editor);
notificationBar.remove();
});
$(cancelBtn).click(() => {
notificationBar.remove();
});
}
setTimeout(() => { tinyMCE.activeEditor.execCommand('mceAutoResize') }, 500);
}
function initImageToolBar(editor) {
const editorIframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe');
const primaryColor = '#104da9';
editorIframe.contents().find('head').append(`<style type="text/css">
img::-moz-selection{background:0 0}
img::selection{background:0 0}
.mce-content-body img[data-mce-selected]{outline:2px solid ${primaryColor}}
.mce-content-body div.mce-resizehandle{background:transparent;border-color:transparent;box-sizing:border-box;height:10px;width:10px; position:absolute}
.mce-content-body div.mce-resizehandle:hover{background:transparent}
.mce-content-body div#mceResizeHandlenw{border-left: 2px solid ${primaryColor}; border-top: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlene{border-right: 2px solid ${primaryColor}; border-top: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlesw{border-left: 2px solid ${primaryColor}; border-bottom: 2px solid ${primaryColor}}
.mce-content-body div#mceResizeHandlese{border-right: 2px solid ${primaryColor}; border-bottom: 2px solid ${primaryColor}}
</style>`);
}
function draftLocation() {
return `tinymce-drafts-${document.location.pathname}`;
}
function removeDraft(editor, textAreaObject) {
const location = draftLocation();
const storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]');
const draftId = storedDrafts.indexOf(textAreaObject.data('tinymce-object'));
if (draftId > -1) {
storedDrafts.splice(draftId, 1);
}
if (storedDrafts.length) {
sessionStorage.setItem(location, JSON.stringify(storedDrafts));
} else {
sessionStorage.removeItem(location);
}
}
// Update scroll position after exit
function updateScrollPosition(editorForm) {
if (editorForm.offset().top < $(window).scrollTop()) {
$(window).scrollTop(editorForm.offset().top - 150);
}
}
function saveAction(editor) {
const editorForm = $(editor.getContainer()).closest('form');
editorForm.clearFormErrors();
editor.setProgressState(1);
editor.save();
editorForm.submit();
updateScrollPosition(editorForm);
}
// returns a public API for TinyMCE editor
return {
init: (selector, options = {}) => {
const textAreaObject = $(selector);
let editorToolbaroffset = 0;
if (typeof tinyMCE !== 'undefined') {
// Hide element containing HTML view of RTE field
const tinyMceContainer = $(selector).closest('form').find('.tinymce-view');
const tinyMceInitSize = tinyMceContainer.height();
$(selector).closest('.form-group')
.before(`<div class="tinymce-placeholder" style="height:${tinyMceInitSize}px"></div>`);
tinyMceContainer.addClass('hidden');
const plugins = `
table autosave autoresize link advlist codesample autolink lists
charmap anchor searchreplace wordcount visualblocks visualchars
insertdatetime nonbreaking save directionality customimageuploader
marvinjs custom_image_toolbar help quickbars
`;
// if (typeof (MarvinJsEditor) !== 'undefined') plugins += ' marvinjsplugin';
if (textAreaObject.data('objectType') === 'step'
|| textAreaObject.data('objectType') === 'result_text') {
document.location.hash = `${textAreaObject.data('objectType')}_${textAreaObject.data('objectId')}`;
}
if ($('.navbar-secondary').length) {
editorToolbaroffset = $('.navbar-secondary').position().top + $('.navbar-secondary').height();
} else if ($('#main-nav').length) {
editorToolbaroffset = $('#main-nav').height();
}
return tinyMCE.init({
cache_suffix: '?v=6.3.1', // This suffix should be changed any time library is updated
selector,
skin: false,
content_css: false,
content_style: contentStyle,
convert_urls: false,
promotion: false,
menu: {
insert: { title: 'Insert', items: 'link codesample inserttable | charmap hr | nonbreaking anchor | insertdatetime customimageuploader marvinjs' },
},
menubar: 'file edit view insert format table',
toolbar: 'undo redo restoredraft | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table | link | forecolor backcolor | codesample | customimageuploader marvinjs | help',
plugins,
autoresize_bottom_margin: 20,
placeholder: options.placeholder,
toolbar_sticky: true,
toolbar_sticky_offset: editorToolbaroffset,
codesample_languages: [
{ text: 'R', value: 'r' },
{ text: 'MATLAB', value: 'matlab' },
{ text: 'Python', value: 'python' },
{ text: 'JSON', value: 'javascript' },
{ text: 'HTML/XML', value: 'markup' },
{ text: 'JavaScript', value: 'javascript' },
{ text: 'CSS', value: 'css' },
{ text: 'PHP', value: 'php' },
{ text: 'Ruby', value: 'ruby' },
{ text: 'Java', value: 'java' },
{ text: 'C', value: 'c' },
{ text: 'C#', value: 'csharp' },
{ text: 'C++', value: 'cpp' }
],
browser_spellcheck: true,
branding: false,
fixed_toolbar_container: '#mytoolbar',
autosave_restore_when_empty: false,
autosave_interval: '1s',
autosave_retention: '1440m',
removed_menuitems: 'newdocument',
object_resizing: true,
elementpath: false,
quickbars_insert_toolbar: false,
default_link_target: '_blank',
target_list: [
{ title: 'New page', value: '_blank' },
{ title: 'Same page', value: '_self' }
],
style_formats: [
{
title: 'Headers',
items: [
{ title: 'Header 1', format: 'h1' },
{ title: 'Header 2', format: 'h2' },
{ title: 'Header 3', format: 'h3' },
{ title: 'Header 4', format: 'h4' },
{ title: 'Header 5', format: 'h5' },
{ title: 'Header 6', format: 'h6' }
]
},
{
title: 'Inline',
items: [
{ title: 'Bold', icon: 'bold', format: 'bold' },
{ title: 'Italic', icon: 'italic', format: 'italic' },
{ title: 'Underline', icon: 'underline', format: 'underline' },
{ title: 'Strikethrough', icon: 'strike-through', format: 'strikethrough' },
{ title: 'Superscript', icon: 'superscript', format: 'superscript' },
{ title: 'Subscript', icon: 'subscript', format: 'subscript' },
{ title: 'Code', icon: 'sourcecode', format: 'code' }
]
},
{
title: 'Blocks',
items: [
{ title: 'Paragraph', format: 'p' },
{ title: 'Blockquote', format: 'blockquote' }
]
},
{
title: 'Alignment',
items: [
{ title: 'Left', icon: 'align-left', format: 'alignleft' },
{ title: 'Center', icon: 'align-center', format: 'aligncenter' },
{ title: 'Right', icon: 'align-right', format: 'alignright' },
{ title: 'Justify', icon: 'align-justify', format: 'alignjustify' }
]
}
],
init_instance_callback: (editor) => {
const editorContainer = $(editor.getContainer());
const editorForm = editorContainer.closest('form');
const menuBar = editorForm.find('.tox-menubar');
$('.tinymce-placeholder').css('height', `${$(editor.editorContainer).height()}px`);
setTimeout(() => {
editorContainer.addClass('tox-tinymce--loaded');
$('.tinymce-placeholder').remove();
}, 400);
// Init saved status label
if (editor.getContent() !== '') {
editorForm.find('.tinymce-status-badge').removeClass('hidden');
}
// Init image toolbar
initImageToolBar(editor);
// Init save/cancel button wrapper
$('<div class="tinymce-save-controls"></div>').appendTo(menuBar);
// Init Save button
editorForm
.find('.tinymce-save-button')
.clone()
.appendTo(menuBar.find('.tinymce-save-controls'))
.on('click', (event) => {
event.preventDefault();
saveAction(editor);
});
// After save action
editorForm
.on('ajax:success', (_ev, data) => {
editor.save();
editor.setProgressState(0);
editorForm.find('.tinymce-status-badge').removeClass('hidden');
editor.remove();
editorForm.find('.tinymce-view').html(data.html).removeClass('hidden');
TinyMCE.wrapTables(editorForm.find('.tinymce-view'));
editor.plugins.autosave.removeDraft();
removeDraft(editor, textAreaObject);
if (options.onSaveCallback) { options.onSaveCallback(data); }
}).on('ajax:error', (_ev, data) => {
const model = editor.getElement().dataset.objectType;
$(this).renderFormErrors(model, data.responseJSON);
editor.setProgressState(0);
if (data.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
});
// Init Cancel button
editorForm
.find('.tinymce-cancel-button')
.clone()
.prependTo(menuBar.find('.tinymce-save-controls'))
.on('click', (event) => {
$(editorForm).find('.form-group').removeClass('has-error');
$(editorForm).find('.help-block').remove();
event.preventDefault();
if (editor.isDirty()) {
editor.setContent($(selector).val());
}
editorForm.find('.tinymce-status-badge').addClass('hidden');
editorForm.find('.tinymce-view').removeClass('hidden');
editor.remove();
updateScrollPosition(editorForm);
if (options.onSaveCallback) { options.onSaveCallback($(selector).val()); }
})
.removeClass('hidden');
editor.selection.select(editor.getBody(), true);
editor.selection.collapse(false);
SmartAnnotation.init($(editor.contentDocument.activeElement));
SmartAnnotation.preventPropagation('.atwho-user-popover');
initHighlightjsIframe($(editor.iframeElement).contents());
if (options.afterInitCallback) { options.afterInitCallback(); }
},
setup: (editor) => {
editor.isBlurTempDisabled = false;
editor.on('keydown', (e) => {
if (e.key === 'Enter' && $(editor.contentDocument.activeElement).atwho('isSelecting')) {
return false;
}
return true;
});
editor.on('NodeChange', (e) => {
const node = e.element;
setTimeout(() => {
if ($(node).is('pre') && !editor.isHidden()) {
initHighlightjsIframe($(editor.iframeElement).contents());
}
}, 200);
});
editor.on('Dirty', () => {
makeItDirty(editor);
});
editor.on('StoreDraft', () => {
const location = draftLocation();
const storedDrafts = JSON.parse(sessionStorage.getItem(location) || '[]');
const draftName = textAreaObject.data('tinymce-object');
if (storedDrafts.includes(draftName) || !draftName) return;
storedDrafts.push(draftName);
sessionStorage.setItem(location, JSON.stringify(storedDrafts));
});
editor.on('remove', () => {
const menuBar = $(editor.getContainer()).find('.tox-menubar');
menuBar.find('.tinymce-save-button').remove();
menuBar.find('.tinymce-cancel-button').remove();
});
editor.on('blur', () => {
if (editor.isBlurTempDisabled || editor.blurDisabled) return false;
if ($('.atwho-view:visible').length || $('#MarvinJsModal:visible').length) return false;
setTimeout(() => {
if (editor.isNotDirty === false) {
$(editor.container).find('.tinymce-save-button').click();
} else {
$(editor.container).find('.tinymce-cancel-button').click();
}
}, 0);
return true;
});
editor.on('init', () => {
restoreDraftNotification(selector, editor);
});
},
codesample_content_css: $(selector).data('highlightjs-path'),
save_onsavecallback: (editor) => { saveAction(editor); }
});
}
return null;
},
destroyAll: () => {
if (tinyMCE.activeEditor) {
tinyMCE.activeEditor.remove();
initHighlightjs();
}
},
refresh: () => {
this.destroyAll();
this.init();
},
getContent: () => tinyMCE.activeEditor && tinyMCE.activeEditor.getContent(),
updateImages: (editor) => {
const iframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe').contents();
const images = $.map($('img', iframe), e => e.dataset.mceToken);
$(`#${editor.id}`).parent().find('input.tiny-mce-images').val(JSON.stringify(images));
return JSON.stringify(images);
},
makeItDirty: (editor) => {
makeItDirty(editor);
},
highlight: initHighlightjs,
wrapTables: (container) => {
container.find('table').toArray().forEach((table) => {
if ($(table).parent().hasClass('table-wrapper')) return;
$(table).css('float', 'none').wrapAll(`
<div class="table-wrapper" style="overflow: auto; width: ${container.width()}px"></div>
`);
});
}
};
})();
$(document).on('turbolinks:before-visit', (e) => {
const editor = tinyMCE.activeEditor;
if (editor === null) return true;
if (editor.isDirty()) {
// eslint-disable-next-line no-alert
if (confirm(I18n.t('tiny_mce.leaving_warning'))) {
$('.atwho-container').remove();
tinyMCE.activeEditor.remove();
return true;
}
e.preventDefault();
return false;
}
return true;
});

View file

@ -0,0 +1 @@
@import "tinymce/skins/ui/tinymce-5/skin.min.css";

View file

@ -0,0 +1,122 @@
/* eslint no-underscore-dangle: "off" */
/* eslint no-use-before-define: "off" */
/* eslint no-restricted-syntax: ["off", "BinaryExpression[operator='in']"] */
/* global tinymce I18n HelperModule validateFileSize */
tinymce.PluginManager.add('customimageuploader', (editor) => {
var iframe;
var textAreaElement = $('#' + editor.id);
function loadFiles() {
let $fileInput;
let hitFileLimit;
$('#tinymce_current_upload').remove();
$fileInput = $('<input type="file" multiple accept="image/*" id="tinymce_current_upload" style="display: none;">')
.prependTo(editor.container);
$fileInput.click();
$fileInput.change(function() {
let formData = new FormData();
let files = $('#tinymce_current_upload')[0].files;
Array.from(files).forEach(file => formData.append('files[]', file, file.name));
Array.from(files).every(file => {
if (!validateFileSize(file, true)) {
hitFileLimit = true;
return false;
}
});
if (hitFileLimit) {
return;
}
$.post({
url: textAreaElement.data('tinymce-asset-path'),
data: formData,
processData: false,
contentType: false,
success: function(data) {
handleResponse(data);
$('#tinymce_current_upload').remove();
},
error: function(response) {
HelperModule.flashAlertMsg(response.responseJSON.errors, 'danger');
$('#tinymce_current_upload').remove();
}
});
});
}
function handleResponse(response) {
if (response.errors) {
handleError(response.errors.join('<br>'));
} else {
response.images.forEach(el => editor.execCommand('mceInsertContent', false, buildHTML(el)));
updateActiveImages();
}
}
function handleError(error) {
HelperModule.flashAlertMsg(error, 'danger');
}
function buildHTML(image) {
return `<img src="${image.url}"
data-mce-token="${image.token}"
alt="description-${image.token}" />`;
}
// Create hidden field for images
function createImageHiddenField() {
textAreaElement.parent().find('input.tiny-mce-images').remove();
$('<input type="hidden" class="tiny-mce-images" name="tiny_mce_images" value="[]">').appendTo(textAreaElement.parent());
}
// Finding images in text
function updateActiveImages() {
const imageContainer = $(`#${editor.id}`).parent().find('input.tiny-mce-images');
iframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe').contents();
const images = $.map($('img', iframe), e => e.dataset.mceToken);
if (imageContainer === undefined) {
createImageHiddenField();
}
// Small fix for ResultText when you cancel after change MarvinJS
if (imageContainer === undefined) return [];
imageContainer.val(JSON.stringify(images));
return JSON.stringify(images);
}
// Add a button that opens a window
editor.ui.registry.addButton('customimageuploader', {
tooltip: I18n.t('tiny_mce.upload_window_label'),
icon: 'image',
onAction: loadFiles
});
// Adds a menu item to the tools menu
editor.ui.registry.addMenuItem('customimageuploader', {
text: I18n.t('tiny_mce.upload_window_label'),
icon: 'image',
context: 'insert',
onAction: loadFiles
});
editor.on('NodeChange', function() {
// Check editor status
if (this.initialized) {
updateActiveImages();
}
});
createImageHiddenField();
return {
getMetadata: () => ({
name: 'Custom Image Uploader Plugin'
})
};
});

View file

@ -0,0 +1,55 @@
/* global tinymce MarvinJsEditor */
tinymce.PluginManager.add('custom_image_toolbar', (editor) => {
editor.ui.registry.addIcon(
'download',
`<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" stroke="none" stroke-linecap="round" stroke-linejoin="round" fill-rule="nonzero" focusable="false">
<path d="M26.835 17.1193c-.4295-.0002-.8415.1703-1.1452.474a1.618 1.618 0 0 0-.474 1.1453v4.8579H5.7843v-4.8579c0-.8943-.725-1.6193-1.6193-1.6193s-1.6193.725-1.6193 1.6193v6.4771c-.0002.4296.1703.8416.474 1.1453a1.618 1.618 0 0 0 1.1453.474h22.67c.4296.0003.8416-.1702 1.1453-.474s.4743-.7157.474-1.1453v-6.4771a1.618 1.618 0 0 0-.474-1.1453c-.3037-.3037-.7157-.4742-1.1453-.474zm-12.4799 2.7642a1.619 1.619 0 0 0 2.2898 0l4.8579-4.8579c.6293-.6328.6279-1.6555-.0032-2.2866s-1.6538-.6325-2.2866-.0032l-2.0937 2.0937V5.7843c0-.8943-.725-1.6193-1.6193-1.6193s-1.6193.725-1.6193 1.6193v9.0452l-2.0937-2.0937c-.6328-.6293-1.6555-.6278-2.2866.0032s-.6325 1.6538-.0032 2.2866z"></path>
</svg>`
);
editor.ui.registry.addButton('image_download', {
icon: 'download',
onAction: () => {
const editorIframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe');
const image = editorIframe.contents().find('img[data-mce-selected="1"]');
window.open(`/tiny_mce_assets/${image.data('mce-token')}/download`, '_blank');
}
});
editor.ui.registry.addButton('marvinjs_edit', {
icon: 'edit-block',
onAction: () => {
const editorIframe = $(`#${editor.id}`).next().find('.tox-edit-area iframe');
const image = editorIframe.contents().find('img[data-mce-selected="1"]');
MarvinJsEditor.open({
mode: 'edit-tinymce',
marvinUrl: `/tiny_mce_assets/${image[0].dataset.mceToken}/marvinjs`,
editor,
image
});
}
});
function isImage(elem) {
return editor.dom.is(elem, 'img') && elem.dataset.mceToken;
}
function isMarvinJs(elem) {
return elem.dataset.sourceType === 'marvinjs';
}
editor.ui.registry.addContextToolbar('marvinJsToolbar', {
predicate: (node) => isMarvinJs(node),
items: 'marvinjs_edit',
position: 'node',
scope: 'node'
});
editor.ui.registry.addContextToolbar('ImageToolbar', {
predicate: (node) => isImage(node),
items: 'image_download',
position: 'node',
scope: 'node'
});
});

View file

@ -0,0 +1,41 @@
/* global tinymce I18n MarvinJsEditor */
// TinyMCE plugin
tinymce.PluginManager.add('marvinjs', (editor) => {
function openMarvinJs() {
MarvinJsEditor.open({
mode: 'new-tinymce',
marvinUrl: '/tiny_mce_assets/marvinjs',
editor
});
}
// Add marvinjs button
editor.ui.registry.addIcon(
'marvinjs',
`<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill="#595959" d="M15.875 6l8.875 4.438v7L23 15.625v-4.063l-7.125-3.563-7.125 3.563v9.375l5.625 2.813-.75 1.563L7 22V10.437L15.875 6zm-.813 4l.875-.5 6.125 3.063v.938l-7-3.501zm-4.5 10.437l-.938-.438v-7.563l.938-.438v8.439zm17.875 1.938c.459 0 .844.156 1.156.469.312.313.468.698.469 1.156v2.375c0 .209-.104.313-.313.313-.209 0-.313-.104-.313-.313V24c0-.25-.104-.48-.313-.688-.209-.209-.438-.313-.688-.313h-1v1c0 .25-.115.375-.344.375-.23 0-.344-.125-.344-.375v-1.313a.974.974 0 0 0-.281-.719.974.974 0 0 0-.719-.281h-1v1.688c0 .209-.115.313-.344.313-.23 0-.344-.104-.344-.313v-1.375c0-.25-.094-.48-.281-.688a.923.923 0 0 0-.719-.313h-1v3c0 .25-.104.375-.313.375-.209 0-.313-.125-.313-.375v-6c0-.25-.104-.48-.313-.688a.989.989 0 0 0-.719-.313c-.27 0-.5.104-.688.313a1.002 1.002 0 0 0-.281.688v8.375c0 .209-.115.313-.344.313-.23 0-.344-.104-.344-.313V25.31l-.938-.438c-.417-.209-.833-.24-1.25-.094-.417.146-.73.427-.938.844l-.188.25c-.083.209-.02.355.188.438.375.209.802.459 1.281.75.479.291 1.083.781 1.813 1.469.73.688 1.115 1.323 1.156 1.906.042.25-.041.375-.25.375h-.063c-.209 0-.313-.083-.313-.25-.083-.625-.563-1.282-1.438-1.969s-1.709-1.24-2.5-1.656a.95.95 0 0 1-.5-.594 1.015 1.015 0 0 1 .063-.781l.125-.25c.292-.583.74-.98 1.344-1.188a2.215 2.215 0 0 1 1.781.125l.625.313v-6.563c0-.459.167-.844.5-1.156.333-.312.73-.468 1.188-.469.459 0 .844.156 1.156.469.312.313.468.699.469 1.156v2.375h1c.583 0 1.042.208 1.375.625h1.313c.417 0 .771.125 1.063.375.292.25.48.583.563 1h1.063l.005.003z"/>
</svg>`
);
// Add a button that opens a window
editor.ui.registry.addButton('marvinjs', {
tooltip: I18n.t('marvinjs.new_button'),
icon: 'marvinjs',
onAction: openMarvinJs
});
// Adds a menu item to the tools menu
editor.ui.registry.addMenuItem('marvinjs', {
text: I18n.t('marvinjs.new_button'),
icon: 'marvinjs',
context: 'insert',
onAction: openMarvinJs
});
return {
getMetadata: () => ({
name: 'MarvinJs Plugin'
})
};
});

View file

@ -0,0 +1,72 @@
<template>
<div ref="modal" @keydown.esc="cancel" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.title') }}
</h4>
</div>
<div class="modal-body">
<p>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.description') }}</p>
<div class="dimensions-container">
<div class="sci-input-container">
<label>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.width', {unit: unit}) }}</label>
<input type="number" min="0" v-model="width" class="sci-input-field" @change="updateHeight">
</div>
<img src="/images/icon_small/link.svg"/>
<div class="sci-input-container">
<label>{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.height', {unit: unit}) }}</label>
<input type="number" min="0" v-model="height" class="sci-input-field" @change="updateWidth">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
<button class="btn btn-primary" @click="confirm">{{ i18n.t('label_templates.show.insert_dropdown.logo_modal.insert') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'logoInsertModal',
props: {
unit: { type: String, required: true },
dimension: { type: Array, required: true }
},
data() {
return {
width: 0,
height: 0,
ratio: 1
}
},
mounted() {
$(this.$refs.modal).modal('show');
$(this.$refs.modal).on('hidden.bs.modal', () => {
this.$emit('cancel');
});
this.width = this.dimension[0]
this.height = this.dimension[1]
this.ratio = this.dimension[0] / this.dimension[1]
},
methods: {
updateHeight() {
this.height = Math.round(this.width * 10 / this.ratio) / 10
},
updateWidth() {
this.width = Math.round(this.height * this.ratio * 10) / 10
},
confirm() {
this.$emit('insert:tag', {tag: `{{LOGO, ${this.unit}, ${this.width}, ${this.height}}}`});
$(this.$refs.modal).modal('hide');
},
cancel() {
$(this.$refs.modal).modal('hide');
}
}
}
</script>

Some files were not shown because too many files have changed in this diff Show more