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 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR
gem 'bcrypt', '~> 3.1.10' gem 'bcrypt', '~> 3.1.10'
gem 'caracal' # Build docx report gem 'caracal' # Build docx report
gem 'deface', '~> 1.0' gem 'deface', '~> 1.9'
gem 'down', '~> 5.0' gem 'down', '~> 5.0'
gem 'faker' # Generate fake data gem 'faker' # Generate fake data
gem 'fastimage' # Light gem to get image resolution 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 'i18n-js', '~> 3.6' # Localization in javascript files
gem 'jbuilder' # JSON structures via a Builder-style DSL gem 'jbuilder' # JSON structures via a Builder-style DSL
gem 'logging', '~> 2.0.0' gem 'logging', '~> 2.0.0'
@ -97,15 +97,12 @@ gem 'devise-async',
git: 'https://github.com/mhfs/devise-async.git', git: 'https://github.com/mhfs/devise-async.git',
branch: 'devise-4.x' branch: 'devise-4.x'
gem 'image_processing', '~> 1.12' gem 'image_processing', '~> 1.12'
gem 'img2zpl', git: 'https://github.com/scinote-eln/img2zpl'
gem 'rufus-scheduler', '~> 3.5' gem 'rufus-scheduler', '~> 3.5'
gem 'discard', '~> 1.0' gem 'discard', '~> 1.0'
gem 'graphviz' 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 'base62' # Used for smart annotations
gem 'newrelic_rpm' gem 'newrelic_rpm'

View file

@ -39,43 +39,50 @@ GIT
devise-async (0.10.2) devise-async (0.10.2)
devise (>= 4.0) devise (>= 4.0)
GIT
remote: https://github.com/scinote-eln/img2zpl
revision: 23d61cfc3e90ac4caa62dd08546fa0d7590a5140
specs:
img2zpl (1.0.1)
mini_magick (~> 4.9)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
actioncable (6.1.6.1) actioncable (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.1.6.1) actionmailbox (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
activejob (= 6.1.6.1) activejob (= 6.1.7.1)
activerecord (= 6.1.6.1) activerecord (= 6.1.7.1)
activestorage (= 6.1.6.1) activestorage (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.1.6.1) actionmailer (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
actionview (= 6.1.6.1) actionview (= 6.1.7.1)
activejob (= 6.1.6.1) activejob (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.1.6.1) actionpack (6.1.7.1)
actionview (= 6.1.6.1) actionview (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
rack (~> 2.0, >= 2.0.9) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.6.1) actiontext (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
activerecord (= 6.1.6.1) activerecord (= 6.1.7.1)
activestorage (= 6.1.6.1) activestorage (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.1.6.1) actionview (6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -85,24 +92,24 @@ GEM
activemodel (>= 4.1, < 6.2) activemodel (>= 4.1, < 6.2)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (6.1.6.1) activejob (6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.1.6.1) activemodel (6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
activerecord (6.1.6.1) activerecord (6.1.7.1)
activemodel (= 6.1.6.1) activemodel (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
activerecord-import (1.0.7) activerecord-import (1.0.7)
activerecord (>= 3.2) activerecord (>= 3.2)
activestorage (6.1.6.1) activestorage (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
activejob (= 6.1.6.1) activejob (= 6.1.7.1)
activerecord (= 6.1.6.1) activerecord (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (6.1.6.1) activesupport (6.1.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -245,11 +252,13 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.3.3)
debug_inspector (1.0.0) debug_inspector (1.0.0)
deface (1.6.1) deface (1.9.0)
actionview (>= 5.2)
nokogiri (>= 1.6) nokogiri (>= 1.6)
polyglot polyglot
rails (>= 5.2) railties (>= 5.2)
rainbow (>= 2.1.0) rainbow (>= 2.1.0)
delayed_job (4.1.9) delayed_job (4.1.9)
activesupport (>= 3.0, < 6.2) activesupport (>= 3.0, < 6.2)
@ -273,7 +282,7 @@ GEM
railties (>= 5) railties (>= 5)
down (5.2.0) down (5.2.0)
addressable (~> 2.5) addressable (~> 2.5)
erubi (1.10.0) erubi (1.12.0)
et-orbi (1.2.4) et-orbi (1.2.4)
tzinfo tzinfo
execjs (2.7.0) execjs (2.7.0)
@ -298,17 +307,17 @@ GEM
raabro (~> 1.4) raabro (~> 1.4)
generator (0.0.1) generator (0.0.1)
gherkin (5.1.0) gherkin (5.1.0)
globalid (1.0.0) globalid (1.0.1)
activesupport (>= 5.0) activesupport (>= 5.0)
graphviz (1.2.1) graphviz (1.2.1)
process-pipeline process-pipeline
hammerjs-rails (2.0.8) hammerjs-rails (2.0.8)
hashdiff (1.0.1) hashdiff (1.0.1)
hashie (5.0.0) hashie (5.0.0)
httparty (0.17.3) httparty (0.21.0)
mime-types (~> 3.0) mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
i18n (1.11.0) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-js (3.8.0) i18n-js (3.8.0)
i18n (>= 0.6.6) i18n (>= 0.6.6)
@ -360,17 +369,20 @@ GEM
loofah (2.19.1) loofah (2.19.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.8.0.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.2) marcel (1.0.2)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.4.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104) mime-types-data (3.2022.0105)
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.1.2) mini_mime (1.1.2)
mini_portile2 (2.8.0) mini_portile2 (2.8.1)
minitest (5.16.2) minitest (5.17.0)
momentjs-rails (2.17.1) momentjs-rails (2.17.1)
railties (>= 3.1) railties (>= 3.1)
msgpack (1.4.2) msgpack (1.4.2)
@ -382,6 +394,15 @@ GEM
coffee-rails (>= 3.2.1) coffee-rails (>= 3.2.1)
jquery-rails jquery-rails
rails (>= 3.2.0) 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) newrelic_rpm (6.15.0)
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.10) nokogiri (1.13.10)
@ -441,8 +462,8 @@ GEM
puma (5.6.4) puma (5.6.4)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.6.1) racc (1.6.2)
rack (2.2.4) rack (2.2.6.2)
rack-attack (6.4.0) rack-attack (6.4.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-cors (1.1.1) rack-cors (1.1.1)
@ -451,20 +472,20 @@ GEM
rack rack
rack-test (2.0.2) rack-test (2.0.2)
rack (>= 1.3) rack (>= 1.3)
rails (6.1.6.1) rails (6.1.7.1)
actioncable (= 6.1.6.1) actioncable (= 6.1.7.1)
actionmailbox (= 6.1.6.1) actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.6.1) actionmailer (= 6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
actiontext (= 6.1.6.1) actiontext (= 6.1.7.1)
actionview (= 6.1.6.1) actionview (= 6.1.7.1)
activejob (= 6.1.6.1) activejob (= 6.1.7.1)
activemodel (= 6.1.6.1) activemodel (= 6.1.7.1)
activerecord (= 6.1.6.1) activerecord (= 6.1.7.1)
activestorage (= 6.1.6.1) activestorage (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 6.1.6.1) railties (= 6.1.7.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -482,13 +503,13 @@ GEM
rails (> 3.1) rails (> 3.1)
rails_serve_static_assets (0.0.5) rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5) rails_stdout_logging (0.0.5)
railties (6.1.6.1) railties (6.1.7.1)
actionpack (= 6.1.6.1) actionpack (= 6.1.7.1)
activesupport (= 6.1.6.1) activesupport (= 6.1.7.1)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
rainbow (3.0.0) rainbow (3.1.1)
rake (13.0.6) rake (13.0.6)
rb-fsevent (0.10.4) rb-fsevent (0.10.4)
rb-inotify (0.10.1) rb-inotify (0.10.1)
@ -579,9 +600,9 @@ GEM
simplecov_json_formatter (0.1.2) simplecov_json_formatter (0.1.2)
spinjs-rails (1.4) spinjs-rails (1.4)
rails (>= 3.1) rails (>= 3.1)
sprockets (4.1.1) sprockets (4.2.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2) sprockets-rails (3.4.2)
actionpack (>= 5.2) actionpack (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
@ -591,12 +612,11 @@ GEM
thor (1.2.1) thor (1.2.1)
tilt (2.0.10) tilt (2.0.10)
timecop (0.9.2) timecop (0.9.2)
tinymce-rails (4.9.11) timeout (0.3.1)
railties (>= 3.1.1)
turbolinks (5.1.1) turbolinks (5.1.1)
turbolinks-source (~> 5.1) turbolinks-source (~> 5.1)
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
tzinfo (2.0.4) tzinfo (2.0.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uglifier (4.2.0) uglifier (4.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
@ -624,7 +644,7 @@ GEM
wkhtmltopdf-heroku (2.12.5.0) wkhtmltopdf-heroku (2.12.5.0)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.0) zeitwerk (2.6.6)
PLATFORMS PLATFORMS
ruby ruby
@ -657,7 +677,7 @@ DEPENDENCIES
caracal caracal
cucumber-rails (~> 1.8) cucumber-rails (~> 1.8)
database_cleaner database_cleaner
deface (~> 1.0) deface (~> 1.9)
delayed_job_active_record delayed_job_active_record
devise (~> 4.7.1) devise (~> 4.7.1)
devise-async! devise-async!
@ -671,9 +691,10 @@ DEPENDENCIES
figaro figaro
graphviz graphviz
hammerjs-rails hammerjs-rails
httparty (~> 0.17.3) httparty (~> 0.21.0)
i18n-js (~> 3.6) i18n-js (~> 3.6)
image_processing (~> 1.12) image_processing (~> 1.12)
img2zpl!
jbuilder jbuilder
jquery-rails jquery-rails
jquery-scrollto-rails! jquery-scrollto-rails!
@ -731,7 +752,6 @@ DEPENDENCIES
sneaky-save! sneaky-save!
spinjs-rails spinjs-rails
timecop timecop
tinymce-rails (~> 4.9.10)
turbolinks (~> 5.1.1) turbolinks (~> 5.1.1)
tzinfo-data tzinfo-data
uglifier (>= 1.3.0) 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: 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 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 // 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 hammer
//= require js.cookie //= require js.cookie
//= require spin
//= require jquery.spin
//= require bootstrap-sprockets //= require bootstrap-sprockets
//= require moment //= require moment
//= require bootstrap-datetimepicker //= require bootstrap-datetimepicker
@ -25,8 +10,6 @@
//= require typeahead.bundle.min //= require typeahead.bundle.min
//= require nested_form_fields //= require nested_form_fields
//= require highlight.pack //= require highlight.pack
//= require tinymce-jquery
//= require_tree ./tinymce/plugins
//= require jsPlumb-2.0.4-min //= require jsPlumb-2.0.4-min
//= require jsnetworkx //= require jsnetworkx
//= require bootstrap-select //= require bootstrap-select
@ -257,9 +240,6 @@ var HelperModule = (function(){
$(document).on('turbolinks:load', function() { $(document).on('turbolinks:load', function() {
/* Fix .selectpicker (bootstrap-select) to work with Turbolinks 5.x */ /* Fix .selectpicker (bootstrap-select) to work with Turbolinks 5.x */
$(window).trigger('load.bs.select.data-api'); $(window).trigger('load.bs.select.data-api');
/* Clean up TinyMCE */
tinymce.remove();
}); });
// Show warning if page has unsaved data // 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() { function checkStatusState() {
$.getJSON($('.status-flow-dropdown').data('status-check-url'), (statusData) => { $.getJSON($('.status-flow-dropdown').data('status-check-url'), (statusData) => {
if (statusData.status_changing) { if (statusData.status_changing) {
@ -297,9 +148,9 @@
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span> return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
${data.label}`; ${data.label}`;
} }
return `<span class="my-module-tags-color"></span> return `<span class="my-module-tags-color new"><i class="fas fa-plus"></i></span>
${data.label + ' '} ${data.label + ' '}
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`; <span class="my-module-tags-create-new"> ${I18n.t('my_modules.details.create_new_tag')}</span>`;
}, },
onOpen: function() { onOpen: function() {
$('.select-container .edit-button-container').removeClass('hidden'); $('.select-container .edit-button-container').removeClass('hidden');
@ -440,7 +291,6 @@
initTaskCollapseState(); initTaskCollapseState();
applyTaskStatusChangeCallBack(); applyTaskStatusChangeCallBack();
initTagsSelector(); initTagsSelector();
bindEditTagsAjax();
initStartDatePicker(); initStartDatePicker();
initDueDatePicker(); initDueDatePicker();
initAssignedUsersSelector(); initAssignedUsersSelector();

View file

@ -18,19 +18,21 @@ function initEditMyModuleDescription() {
if ($(this).hasClass('record-info-link')) return; if ($(this).hasClass('record-info-link')) return;
e.stopPropagation(); e.stopPropagation();
}); });
TinyMCE.initIfHasDraft(viewObject);
setTimeout(function() {
TinyMCE.wrapTables(viewObject);
}, 100);
} }
function initEditProtocolDescription() { function initEditProtocolDescription() {
var viewObject = $('#protocol_description_view'); var viewObject = $('#protocol_description_view');
viewObject.on('click', function(e) { viewObject.on('click', function(e) {
if ($(e.target).hasClass('record-info-link')) return; 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) { }).on('click', 'a', function(e) {
if ($(this).hasClass('record-info-link')) return; if ($(this).hasClass('record-info-link')) return;
e.stopPropagation(); e.stopPropagation();
}); });
TinyMCE.initIfHasDraft(viewObject);
} }
function initCopyToRepository() { function initCopyToRepository() {

View file

@ -138,6 +138,7 @@
} }
function processResult(ev, resultTypeEnum) { function processResult(ev, resultTypeEnum) {
var textWithoutImages;
var $form = $(ev.target.form); var $form = $(ev.target.form);
$form.clearFormErrors(); $form.clearFormErrors();
@ -153,9 +154,11 @@
.removeClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME); .removeClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
break; break;
case ResultTypeEnum.TEXT: case ResultTypeEnum.TEXT:
textWithoutImages = TinyMCE.getContent().replaceAll(/src="(data:image\/[^;]+;base64[^"]+)"/i, '');
textValidator( textValidator(
ev, $form.find('#result_text_attributes_textarea'), 1, 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; break;
default: 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.position({ top: $(this).parent().position().top });
commentMenu.offset({ top: $(this).parent().offset().top + <%= Constants::DROPDOWN_TOP_OFFSET_PX %> }); commentMenu.offset({ top: $(this).parent().offset().top + <%= Constants::DROPDOWN_TOP_OFFSET_PX %> });
}); });
initializeCanvasViewNavigator();
} }
function destroyFullZoom() { function destroyFullZoom() {
@ -370,6 +372,7 @@ function initializeMediumZoom() {
// Restore draggable position // Restore draggable position
restoreDraggablePosition($("#diagram"), $("#canvas-container")); restoreDraggablePosition($("#diagram"), $("#canvas-container"));
initializeCanvasViewNavigator();
} }
function destroyMediumZoom() { function destroyMediumZoom() {
@ -397,6 +400,7 @@ function initializeSmallZoom() {
// Restore draggable position // Restore draggable position
restoreDraggablePosition($("#diagram"), $("#canvas-container")); restoreDraggablePosition($("#diagram"), $("#canvas-container"));
initializeCanvasViewNavigator();
} }
function destroySmallZoom() { function destroySmallZoom() {
@ -595,7 +599,7 @@ function resizeContainer() {
if (cont.length > 0) { if (cont.length > 0) {
cont.css( cont.css(
"height", "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(); var newModule = createVirtualModule();
elLeft(newModule, left); elLeft(newModule, left);
elTop(newModule, top); 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"); newModule.removeClass("new");
// Add the cloned module id into the hidden input field // 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); y_pos = y_el + (fastOffsetY - y_start);
x_start = fastOffsetX; x_start = fastOffsetX;
y_start = fastOffsetY; y_start = fastOffsetY;
drawRectangleCanvasNavigatorView(-x_pos, -y_pos)
if (draggable !== null) { if (draggable !== null) {
elLeft(draggable, x_pos); elLeft(draggable, x_pos);
elTop(draggable, y_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 */ /** prevent reload page */
var preventCanvasReloadOnSave = (function() { var preventCanvasReloadOnSave = (function() {
'use strict'; 'use strict';

View file

@ -317,11 +317,16 @@
function initNewExperimentToolbarButton() { function initNewExperimentToolbarButton() {
let forms = '.new-experiment-form'; let forms = '.new-experiment-form';
$(experimentsPage) $(experimentsPage)
.on('submit', forms, function() {
$(this).find("button[type='submit']").prop('disabled', true);
})
.on('ajax:success', forms, function(ev, data) { .on('ajax:success', forms, function(ev, data) {
appendActionModal($(data.html)); appendActionModal($(data.html));
$(this).find("button[type='submit']").prop('disabled', false);
}) })
.on('ajax:error', forms, function(ev, data) { .on('ajax:error', forms, function(ev, data) {
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger'); 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; if ($(this).hasClass('record-info-link')) return;
e.stopPropagation(); e.stopPropagation();
}); });
TinyMCE.initIfHasDraft(viewObject);
} }
return { return {

View file

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

View file

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

View file

@ -1020,7 +1020,7 @@ function reportHandsonTableConverter() {
}); });
// Project content // Project content
reportData.project_content = { experiments: [], repositories: [] }; reportData.project_content = { experiments: [] };
$.each($('.project-contents-container .experiment-element'), function(i, experiment) { $.each($('.project-contents-container .experiment-element'), function(i, experiment) {
let expCheckbox = $(experiment).find('.report-experiment-checkbox'); let expCheckbox = $(experiment).find('.report-experiment-checkbox');
if (!expCheckbox.prop('checked') && !expCheckbox.prop('indeterminate')) return; if (!expCheckbox.prop('checked') && !expCheckbox.prop('indeterminate')) return;
@ -1034,10 +1034,6 @@ function reportHandsonTableConverter() {
reportData.project_content.experiments.push(experimentData); 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 // Settings
reportData.report.settings.template = dropdownSelector.getValues('#templateSelector'); reportData.report.settings.template = dropdownSelector.getValues('#templateSelector');
reportData.report.settings.all_tasks = $('.project-contents-container .select-all-my-modules-checkbox') 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) { $.each($('.task-contents-container .content-element .task-setting'), function(i, e) {
reportData.report.settings.task[e.value] = e.checked; 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'); 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 // Adjust columns width in table header
function adjustTableHeader() { function adjustTableHeader() {
TABLE.columns.adjust(); TABLE.columns.adjust();
$('.dropdown-menu').parent() // $('.dropdown-menu').parent()
.on('shown.bs.dropdown hidden.bs.dropdown', function() { // .on('shown.bs.dropdown hidden.bs.dropdown', function() {
TABLE.columns.adjust(); // TABLE.columns.adjust();
}); // });
} }
function checkSnapshottingStatus() { function checkSnapshottingStatus() {
@ -664,9 +664,9 @@ var RepositoryDatatable = (function(global) {
initActiveRemindersFilter(); initActiveRemindersFilter();
renderFiltersDropdown(); renderFiltersDropdown();
setTimeout(function() { // setTimeout(function() {
adjustTableHeader(); // adjustTableHeader();
}, 500); // }, 500);
} }
}); });
@ -699,11 +699,11 @@ var RepositoryDatatable = (function(global) {
}) })
initRowSelection(); initRowSelection();
$(window).resize(() => { // $(window).resize(() => {
setTimeout(() => { // setTimeout(() => {
adjustTableHeader(); // adjustTableHeader();
}, 500); // }, 500);
}); // });
return TABLE; return TABLE;
} }
@ -822,7 +822,7 @@ var RepositoryDatatable = (function(global) {
}); });
changeToEditMode(); changeToEditMode();
adjustTableHeader(); // adjustTableHeader();
}) })
.on('click', '#deleteRepositoryRecords', function() { .on('click', '#deleteRepositoryRecords', function() {
$('#deleteRepositoryRecord').modal('show'); $('#deleteRepositoryRecord').modal('show');
@ -909,9 +909,9 @@ var RepositoryDatatable = (function(global) {
document.documentElement.style.setProperty('--repository-sidebar-margin', '363px'); document.documentElement.style.setProperty('--repository-sidebar-margin', '363px');
}); });
$('#wrapper').on('sideBar::hidden sideBar::shown', function() { // $('#wrapper').on('sideBar::hidden sideBar::shown', function() {
adjustTableHeader(); // adjustTableHeader();
}); // });
} }
function renderFiltersDropdown() { function renderFiltersDropdown() {

View file

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

View file

@ -20,6 +20,7 @@ var CommentsSidebar = (function() {
$(SIDEBAR).find('.comment-input-container').removeClass('hidden'); $(SIDEBAR).find('.comment-input-container').removeClass('hidden');
} else { } else {
$(SIDEBAR).find('.comment-input-container').addClass('hidden'); $(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; var commentsAmount = $(SIDEBAR).find('.comments-list .comment-container').length;
if (commentsCounter.length) { if (commentsCounter.length) {
// Replace the number in comment element // 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')}`); commentsCounter = $(`#comment-count-${$(this).data('objectId')}`);
closeCallback = $(this).data('closeCallback'); closeCallback = $(this).data('closeCallback');
CommentsSidebar.open($(this).data('objectType'), $(this).data('objectId')); CommentsSidebar.open($(this).data('objectType'), $(this).data('objectId'));
$(this).parent().find('.unseen-comments').remove();
e.preventDefault(); e.preventDefault();
}); });
} }
@ -86,6 +89,9 @@ var CommentsSidebar = (function() {
} }
$(SIDEBAR).find('.comment-input-field').val(''); $(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update'); $(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(); $('.error-container').empty();
updateCounter(); updateCounter();
}, },
@ -100,6 +106,9 @@ var CommentsSidebar = (function() {
$(document).on('click', `${SIDEBAR} .cancel-button`, function() { $(document).on('click', `${SIDEBAR} .cancel-button`, function() {
$(SIDEBAR).find('.comment-input-field').val(''); $(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update'); $(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'); $('.comment-container').removeClass('edit');
$(this).closest('.comment-container').addClass('edit'); $(this).closest('.comment-container').addClass('edit');
$(SIDEBAR).find('.sidebar-footer').addClass('update'); $(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') $(SIDEBAR).find('.comment-input-field')
.val($(this).data('comment-raw')) .val($(this).data('comment-raw'))
.data('update-url', $(this).data('update-url')); .data('update-url', $(this).data('update-url'));
@ -152,7 +164,7 @@ var CommentsSidebar = (function() {
open: function(objectType, objectId) { open: function(objectType, objectId) {
$(SIDEBAR).find('.comments-subject-title').empty(); $(SIDEBAR).find('.comments-subject-title').empty();
$(SIDEBAR).find('.comments-list').empty(); $(SIDEBAR).find('.comments-list').empty();
$(SIDEBAR).find('.comment-input-field').val(''); $(SIDEBAR).find('.comment-input-field').val('').focus();
$('.error-container').empty(); $('.error-container').empty();
$(SIDEBAR).find('.sidebar-footer').removeClass('update'); $(SIDEBAR).find('.sidebar-footer').removeClass('update');
$(SIDEBAR).data('object-type', objectType).data('object-id', objectId); $(SIDEBAR).data('object-type', objectType).data('object-id', objectId);

View file

@ -6,16 +6,21 @@
ev.stopPropagation(); ev.stopPropagation();
let dt = $(this); let dt = $(this);
let options = { ignoreReadonly: true };
if (dt.data('DateTimePicker')) { if (dt.data('DateTimePicker')) {
dt.data('DateTimePicker').destroy(); 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(); 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')}`); let dt = $(`#${$(this).data('target')}`);
if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false }); if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false });
dt.data('DateTimePicker').clear(); dt.data('DateTimePicker').clear();

View file

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

View file

@ -161,7 +161,7 @@ var MarvinJsEditorApi = (function() {
} else if (config.objectType === 'Result') { } else if (config.objectType === 'Result') {
location.reload(); location.reload();
} else if (config.objectType === 'TinyMceAsset') { } else if (config.objectType === 'TinyMceAsset') {
json = tinymce.util.JSON.parse(result); json = JSON.parse(result);
config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json)); config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json));
TinyMCE.updateImages(config.editor); 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 // MarvinJS Methods
return { return {
@ -254,17 +268,13 @@ var MarvinJsEditorApi = (function() {
initNewButton: function(selector, saveCallback) { initNewButton: function(selector, saveCallback) {
$(selector).off('click').on('click', function() { $(selector).off('click').on('click', function() {
var objectId = this.dataset.objectId; createNewMarvinContainer(this.dataset);
var objectType = this.dataset.objectType; });
var marvinUrl = this.dataset.marvinUrl;
var container = this.dataset.sketchContainer; $(selector).off('keypress').on('keypress', function(e) {
MarvinJsEditor.open({ if (e.which === 13) {
mode: 'new', createNewMarvinContainer(this.dataset);
objectId: objectId, }
objectType: objectType,
marvinUrl: marvinUrl,
container: container
});
}); });
MarvinJsEditor.saveCallback = saveCallback; 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 // Initialization
$(document).on('click', '.marvinjs-edit-button', function() { $(document).on('click', '.marvinjs-edit-button', function() {
var editButton = $(this); var editButton = $(this);
@ -337,7 +306,7 @@ $(document).on('click', '.marvinjs-edit-button', function() {
$(document).on('turbolinks:load', function() { $(document).on('turbolinks:load', function() {
MarvinJsEditor = MarvinJsEditorApi(); MarvinJsEditor = MarvinJsEditorApi();
if (MarvinJsEditor.enabled()) { 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) { ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
marvin.setDisplaySettings({ toolbars: 'reporting' }); marvin.setDisplaySettings({ toolbars: 'reporting' });
marvinJsRemoteEditor = marvin; 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 */ /* global tinymce */
tinymce.PluginManager.add('placeholder', function(editor) { tinymce.PluginManager.add('placeholder', function(editor) {
var Label = function() { var Label = function() {
var editorForm = $(editor.getContainer()).closest('form'); var editorForm = $(editor.getContainer()).closest('form');
var editorToolbar = editorForm.find('.mce-top-part'); var editorToolbar = editorForm.find('.mce-top-part');
var placeholderText = editor.getElement().getAttribute('placeholder') || editor.settings.placeholder; var placeholderText = editor.getElement().getAttribute('placeholder') || editor.settings.placeholder;
var placeholderAttrs = editor.settings.placeholder_attrs || { var placeholderAttrs = {
style: { style: `
position: 'absolute', position: 'absolute',
top: (editorToolbar.height()) + 'px', top: (editorToolbar.height()) + 'px',
left: 0, left: 0,
@ -14,7 +15,7 @@ tinymce.PluginManager.add('placeholder', function(editor) {
width: 'calc(100% - 50px)', width: 'calc(100% - 50px)',
overflow: 'hidden', overflow: 'hidden',
'white-space': 'pre-wrap' 'white-space': 'pre-wrap'
} `
}; };
var contentAreaContainer = editor.getContentAreaContainer(); var contentAreaContainer = editor.getContentAreaContainer();

View file

@ -35,6 +35,7 @@
@import "protocols/*"; @import "protocols/*";
@import "dashboard/*"; @import "dashboard/*";
@import "repository/*"; @import "repository/*";
@import "experiment/*";
@import "repository_columns/*"; @import "repository_columns/*";
@import "label_templates/*"; @import "label_templates/*";
@import "reports/*"; @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 { .content-header {
.project-name { .project-name {
align-items: center; 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 { .calendar-input {
@include font-button;
background-color: transparent !important; background-color: transparent !important;
border-color: $color-silver-chalice;
box-shadow: none; box-shadow: none;
color: inherit; color: inherit;
cursor: pointer; cursor: pointer;
font-size: 13px; padding-left: 10px;
padding-left: 5px;
padding-right: 34px; padding-right: 34px;
position: relative; position: relative;
z-index: 3; z-index: 3;
&::placeholder { &::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) { .open-dropdown-button:not(.collapsed) {
.fas { .fas {
@include rotate(-180deg); @include rotate(-180deg);
@ -171,7 +180,12 @@
display: flex; display: flex;
padding: 10px 10px 10px 24px; padding: 10px 10px 10px 24px;
.fas { .fas:not(.fa-plus-square) {
margin-left: -1.25em;
margin-right: .25em;
}
.fa-plus-square {
@include font-main; @include font-main;
display: none; display: none;
margin-left: auto; margin-left: auto;
@ -180,7 +194,7 @@
&:hover { &:hover {
background-color: $color-concrete; background-color: $color-concrete;
.fas { .fa-plus-square {
display: inline-block; display: inline-block;
} }
} }

View file

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

View file

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

View file

@ -21,7 +21,7 @@ $color-module-hover: $brand-primary;
align-items: center; align-items: center;
display: flex; display: flex;
#edit-canvas-button { #edit-canvas-button, .new-my-module-button {
margin-right: 5px; margin-right: 5px;
} }
@ -48,9 +48,8 @@ $color-module-hover: $brand-primary;
} }
#update-canvas { #update-canvas {
.canvas-header { .canvas-header {
margin-bottom: 5px; padding: 1em 2em;
} }
} }
@ -89,6 +88,14 @@ $color-module-hover: $brand-primary;
overflow: hidden; overflow: hidden;
// for IE10+ touch devices // for IE10+ touch devices
touch-action: none; touch-action: none;
.empty-canvas {
color: $color-volcano;
display: flex;
font-size: 22px;
justify-content: center;
margin-top: 48px;
}
} }
.diagram { .diagram {
@ -542,6 +549,26 @@ li.module-hover {
margin-right: 15px; margin-right: 15px;
margin-top: 10px; 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 { .projects-toolbar {

View file

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

View file

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

View file

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

View file

@ -66,7 +66,7 @@
.dropdown-menu { .dropdown-menu {
@include font-button; @include font-button;
min-width: auto; min-width: 190px;
.divider-label { .divider-label {
@include font-small; @include font-small;
@ -74,18 +74,41 @@
padding: .25em 1em; padding: .25em 1em;
} }
.divider {
margin: 0;
}
li { li {
cursor: pointer; cursor: pointer;
padding: 1em; padding: .5em 1em;
white-space: nowrap; white-space: nowrap;
.button-icon { .button-icon {
margin-right: .5em; margin-right: .5em;
} }
&:hover { &:hover:not(.divider-label) {
background: $color-concrete; 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; line-height: 28px;
min-width: 0; min-width: 0;
outline: 0; outline: 0;
padding: 0 0 0 5px; padding: 0 0 0 10px;
&::placeholder { &::placeholder {
opacity: .7; 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 { .recent-searches {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-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-angle-double-right: "\f101";
$font-fas-exclamation-circle: "\f06a"; $font-fas-exclamation-circle: "\f06a";
$font-fas-caret-up: "\f0d8"; $font-fas-caret-up: "\f0d8";
$font-fas-plus: "\f02b";
@mixin font-h1 { @mixin font-h1 {
font-size: 24px; font-size: 24px;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -201,16 +201,6 @@ mark,.mark {
text-align: right; text-align: right;
} }
a[data-toggle="tooltip"] {
color: inherit;
border-bottom: 1px dashed $color-emperor;
&:hover {
text-decoration: none;
cursor: help;
}
}
.nav-tabs { .nav-tabs {
margin-bottom: 15px; 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 { .panel-options {
position: relative; position: relative;
bottom: 8px; bottom: 8px;

View file

@ -1,4 +1,4 @@
// scss-lint:disable ImportantRule // scss-lint:disable ImportantRule SelectorDepth
@import "constants"; @import "constants";
@font-face { @font-face {
@ -22,6 +22,11 @@
color: $color-silver-chalice; color: $color-silver-chalice;
content: attr(data-placeholder); content: attr(data-placeholder);
} }
p {
margin: 0;
padding: 0;
}
} }
.mce-tinymce { .mce-tinymce {
@ -38,6 +43,16 @@
position: relative !important; position: relative !important;
} }
.tox.tox-tinymce {
left: -100000px;
position: absolute;
&.tox-tinymce--loaded {
left: 0;
position: relative;
}
}
.tinymce-placeholder { .tinymce-placeholder {
background: $color-concrete; background: $color-concrete;
opacity: .7; opacity: .7;
@ -45,8 +60,15 @@
width: 100%; width: 100%;
} }
.tinymce-save-button, .tox-edit-area {
.tinymce-cancel-button { label {
color: $color-silver-chalice !important;
padding: 5px !important;
}
}
.tinymce-save-button.tox-mbtn,
.tinymce-cancel-button.tox-mbtn {
cursor: pointer; cursor: pointer;
.fas { .fas {
@ -54,6 +76,15 @@
font-weight: 900; font-weight: 900;
margin-top: 3px; margin-top: 3px;
} }
&:hover {
background: transparent !important;
}
}
.tinymce-save-controls {
display: flex;
margin-left: auto !important;
} }
.tinymce-status-badge { .tinymce-status-badge {
@ -69,18 +100,17 @@
background: $color-white !important; background: $color-white !important;
} }
.restore-draft-notification { .restore-draft-notification {
align-items: center; align-items: center;
background: $state-info-bg !important; background: $state-info-bg !important;
display: flex; display: flex;
flex-basis: 100%;
height: 30px !important; height: 30px !important;
padding: 0 10px !important; padding: 10px !important;
.notification-text { .notification-text {
flex-grow: 1; flex-grow: 1;
max-width: 75%; max-width: 85%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@ -152,25 +182,6 @@
&::after { &::after {
display: none; 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 { .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 Api
module V1 module V1
class UsersController < BaseController class UsersController < BaseController
before_action :load_team, only: :index
before_action :load_user, only: :show 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 def show
render jsonapi: @user, serializer: UserSerializer render jsonapi: @user, serializer: UserSerializer
end end

View file

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

View file

@ -41,8 +41,8 @@ module AssetsActions
.call(activity_type: :edit_image_on_result, .call(activity_type: :edit_image_on_result,
owner: current_user, owner: current_user,
subject: asset.result, subject: asset.result,
team: my_module.experiment.project.team, team: my_module.team,
project: my_module.experiment.project, project: my_module.project,
message_items: { message_items: {
result: asset.result.id, result: asset.result.id,
asset_name: { id: asset.id, value_for: 'file_name' }, 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, .call(activity_type: "#{activity}_molecule_on_result".to_sym,
owner: current_user, owner: current_user,
subject: result, subject: result,
team: my_module.experiment.project.team, team: my_module.team,
project: my_module.experiment.project, project: my_module.project,
message_items: message_items) message_items: message_items)
end end
end end

View file

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

View file

@ -55,6 +55,17 @@ module StepsActions
) )
end 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) def checklist_name_annotation(step, checklist, old_text = nil)
smart_annotation_notification( smart_annotation_notification(
old_text: old_text, old_text: old_text,
@ -85,7 +96,7 @@ module StepsActions
), ),
experiment: link_to( experiment: link_to(
step.my_module.experiment.name, step.my_module.experiment.name,
canvas_experiment_url(step.my_module.experiment) my_modules_experiment_url(step.my_module.experiment)
), ),
my_module: link_to( my_module: link_to(
step.my_module.name, 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_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_canvas_read_permissions, only: %i(canvas)
before_action :check_create_permissions, only: %i(new create) 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_update_permissions, only: %i(update)
before_action :check_archive_permissions, only: :archive before_action :check_archive_permissions, only: :archive
before_action :check_clone_permissions, only: %i(clone_modal clone) before_action :check_clone_permissions, only: %i(clone_modal clone)
before_action :check_move_permissions, only: %i(move_modal move) 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' layout 'fluid'
@ -46,7 +46,7 @@ class ExperimentsController < ApplicationController
experiment: @experiment.name) experiment: @experiment.name)
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: { path: canvas_experiment_url(@experiment) }, status: :ok render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
end end
end end
else else
@ -88,6 +88,37 @@ class ExperimentsController < ApplicationController
.select('my_modules.*').group(:id) .select('my_modules.*').group(:id)
end 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 def edit
respond_to do |format| respond_to do |format|
format.json do format.json do
@ -208,7 +239,8 @@ class ExperimentsController < ApplicationController
format.json do format.json do
render json: { render json: {
html: render_to_string( html: render_to_string(
partial: 'clone_modal.html.erb' partial: 'clone_modal.html.erb',
locals: { view_mode: params[:view_mode] }
) )
} }
end end
@ -249,6 +281,21 @@ class ExperimentsController < ApplicationController
end end
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) # POST: move_experiment(id)
def move def move
service = Experiments::MoveToProjectService service = Experiments::MoveToProjectService
@ -258,8 +305,10 @@ class ExperimentsController < ApplicationController
if service.succeed? if service.succeed?
flash[:success] = t('experiments.move.success_flash', flash[:success] = t('experiments.move.success_flash',
experiment: @experiment.name) experiment: @experiment.name)
path = canvas_experiment_url(@experiment)
status = :ok 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 else
message = service.errors.values.join(', ') message = service.errors.values.join(', ')
status = :unprocessable_entity status = :unprocessable_entity
@ -268,7 +317,53 @@ class ExperimentsController < ApplicationController
render json: { message: message, path: path }, status: status render json: { message: message, path: path }, status: status
end 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 def module_archive
@project = @experiment.project
@my_modules = @experiment.archived_branch? ? @experiment.my_modules : @experiment.my_modules.archived @my_modules = @experiment.archived_branch? ? @experiment.my_modules : @experiment.my_modules.archived
@my_modules = @my_modules.with_granted_permissions(current_user, MyModulePermissions::READ_ARCHIVED) @my_modules = @my_modules.with_granted_permissions(current_user, MyModulePermissions::READ_ARCHIVED)
.left_outer_joins(:designated_users, :task_comments) .left_outer_joins(:designated_users, :task_comments)
@ -299,11 +394,27 @@ class ExperimentsController < ApplicationController
end end
def sidebar 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| respond_to do |format|
format.json do format.json do
render json: { render json: {
html: render_to_string( 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 end
@ -321,6 +432,73 @@ class ExperimentsController < ApplicationController
end end
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 private
def load_experiment def load_experiment
@ -341,10 +519,14 @@ class ExperimentsController < ApplicationController
params.require(:experiment).require(:project_id) params.require(:experiment).require(:project_id)
end end
def view_type_params
params.require(:experiment).require(:view_type)
end
def check_read_permissions def check_read_permissions
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
render_403 unless can_read_experiment?(@experiment) || render_403 unless can_read_experiment?(@experiment) ||
@experiment.archived? && can_read_archived_experiment?(@experiment) (@experiment.archived? && can_read_archived_experiment?(@experiment))
end end
def check_canvas_read_permissions def check_canvas_read_permissions
@ -402,7 +584,7 @@ class ExperimentsController < ApplicationController
project: link_to(@experiment.project.name, project: link_to(@experiment.project.name,
project_url(@experiment.project)), project_url(@experiment.project)),
experiment: link_to(@experiment.name, experiment: link_to(@experiment.name,
canvas_experiment_url(@experiment))) my_modules_experiment_url(@experiment)))
) )
end end
@ -410,9 +592,61 @@ class ExperimentsController < ApplicationController
Activities::CreateActivityService Activities::CreateActivityService
.call(activity_type: type_of, .call(activity_type: type_of,
owner: current_user, owner: current_user,
team: experiment.project.team, team: experiment.team,
project: experiment.project, project: experiment.project,
subject: experiment, subject: experiment,
message_items: { experiment: experiment.id }) message_items: { experiment: experiment.id })
end 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 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_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 :check_manage_permissions, only: %i(create duplicate set_default delete update)
before_action :load_label_templates, only: %i(index datatable) 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' layout 'fluid'
@ -125,7 +125,7 @@ class LabelTemplatesController < ApplicationController
end end
def template_tags def template_tags
render json: LabelTemplates::TagService.new(current_team).tags render json: LabelTemplates::TagService.new(current_team, @label_template).tags
end end
def zpl_preview def zpl_preview

View file

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

View file

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

View file

@ -5,19 +5,69 @@ class MyModulesController < ApplicationController
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include ActionView::Helpers::UrlHelper include ActionView::Helpers::UrlHelper
include ApplicationHelper 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_archive_permissions, only: %i(update)
before_action :check_manage_permissions, only: %i( before_action :check_manage_permissions, only: %i(
description due_date update_description update_protocol_description update_protocol 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 :check_update_state_permissions, only: :update_state
before_action :set_inline_name_editing, only: %i(protocols results activities archive) before_action :set_inline_name_editing, only: %i(protocols results activities archive)
before_action :load_experiment_my_modules, only: %i(protocols results activities archive) before_action :load_experiment_my_modules, only: %i(protocols results activities archive)
layout 'fluid'.freeze 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 def show
respond_to do |format| respond_to do |format|
format.json { format.json {
@ -46,6 +96,11 @@ class MyModulesController < ApplicationController
end end
end end
def save_table_state
current_user.settings.update(visible_my_module_table_columns: params[:columns])
current_user.save!
end
def status_state def status_state
respond_to do |format| respond_to do |format|
format.json do format.json do
@ -195,6 +250,11 @@ class MyModulesController < ApplicationController
partial: 'my_modules/card_due_date_label.html.erb', partial: 'my_modules/card_due_date_label.html.erb',
locals: { my_module: @my_module } 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( module_header_due_date: render_to_string(
partial: 'my_modules/module_header_due_date.html.erb', partial: 'my_modules/module_header_due_date.html.erb',
locals: { my_module: @my_module } locals: { my_module: @my_module }
@ -276,12 +336,14 @@ class MyModulesController < ApplicationController
def update_protocol def update_protocol
protocol = @my_module.protocol protocol = @my_module.protocol
old_description = protocol.description
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
protocol.update!(protocol_params) protocol.update!(protocol_params)
log_activity(:protocol_name_in_task_edited) if protocol.saved_change_to_name? 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? log_activity(:protocol_description_in_task_edited) if protocol.saved_change_to_description?
TinyMceAsset.update_images(protocol, params[:tiny_mce_images], current_user) TinyMceAsset.update_images(protocol, params[:tiny_mce_images], current_user)
protocol_annotation_notification(old_description)
end end
render json: protocol, serializer: ProtocolSerializer, user: current_user render json: protocol, serializer: ProtocolSerializer, user: current_user
@ -334,7 +396,12 @@ class MyModulesController < ApplicationController
else else
flash[:error] = t('my_modules.restore_group.error_flash') flash[:error] = t('my_modules.restore_group.error_flash')
end 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 end
def update_state def update_state
@ -349,6 +416,32 @@ class MyModulesController < ApplicationController
end end
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 private
def load_vars def load_vars
@ -361,6 +454,11 @@ class MyModulesController < ApplicationController
end end
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 def load_experiment_my_modules
@experiment_my_modules = if @my_module.experiment.archived_branch? @experiment_my_modules = if @my_module.experiment.archived_branch?
@my_module.experiment.my_modules.order(:name) @my_module.experiment.my_modules.order(:name)
@ -369,6 +467,10 @@ class MyModulesController < ApplicationController
end end
end end
def check_create_permissions
render_403 && return unless can_manage_experiment?(@experiment)
end
def check_manage_permissions def check_manage_permissions
render_403 && return unless can_manage_my_module?(@my_module) render_403 && return unless can_manage_my_module?(@my_module)
end end
@ -401,18 +503,26 @@ class MyModulesController < ApplicationController
end end
def my_module_params 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? if permitted_params[:started_on].present?
update_params[:started_on] = permitted_params[:started_on] =
Time.zone.strptime(update_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M') Time.zone.strptime(permitted_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end end
if update_params[:due_date].present? if permitted_params[:due_date].present?
update_params[:due_date] = permitted_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M') Time.zone.strptime(permitted_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end 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 end
def protocol_params def protocol_params
@ -458,8 +568,8 @@ class MyModulesController < ApplicationController
Activities::CreateActivityService Activities::CreateActivityService
.call(activity_type: type_of, .call(activity_type: type_of,
owner: current_user, owner: current_user,
team: my_module.experiment.project.team, team: my_module.team,
project: my_module.experiment.project, project: my_module.project,
subject: my_module, subject: my_module,
message_items: message_items) message_items: message_items)
end end
@ -479,7 +589,7 @@ class MyModulesController < ApplicationController
user: current_user.full_name), user: current_user.full_name),
message: t('notifications.my_module_description_annotation_message_html', message: t('notifications.my_module_description_annotation_message_html',
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)), 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))) my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
) )
end end
@ -493,7 +603,7 @@ class MyModulesController < ApplicationController
user: current_user.full_name), user: current_user.full_name),
message: t('notifications.my_module_protocol_annotation_message_html', message: t('notifications.my_module_protocol_annotation_message_html',
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)), 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))) my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
) )
end end

View file

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

View file

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

View file

@ -110,7 +110,7 @@ class ReportsController < ApplicationController
@project_contents = { @project_contents = {
experiments: @report.report_elements.order(:position).experiment.pluck(:experiment_id), experiments: @report.report_elements.order(:position).experiment.pluck(:experiment_id),
my_modules: @report.report_elements.order(:position).my_module.pluck(:my_module_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 render :new
end end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -61,14 +61,22 @@ class UserMyModulesController < ApplicationController
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: { if params[:table]
user: { render json: {
id: @um.user.id, html: render_to_string(partial: 'experiments/assigned_users.html.erb',
full_name: @um.user.full_name, locals: { my_module: @my_module, user: current_user, skip_unassigned: false }),
avatar_url: avatar_path(@um.user, :icon_small), unassign_url: my_module_user_my_module_path(@my_module, @um)
user_module_id: @um.id }
}, status: :ok 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
end end
else else
@ -88,7 +96,14 @@ class UserMyModulesController < ApplicationController
respond_to do |format| respond_to do |format|
format.json do 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
end end
else else
@ -104,19 +119,37 @@ class UserMyModulesController < ApplicationController
def search def search
users = @my_module.users 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]) .search(false, params[:query])
.order(:full_name)
.limit(Constants::SEARCH_LIMIT) .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| 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, value: user.id,
label: sanitize_input(user.full_name), 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 end
render json: users render json: users.compact
end end
private private

View file

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

View file

@ -72,12 +72,12 @@ module GlobalActivitiesHelper
when Experiment when Experiment
return current_value unless obj.navigable? 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 when MyModule
return current_value unless obj.navigable? return current_value unless obj.navigable?
path = if obj.archived? path = if obj.archived?
module_archive_experiment_path(obj.experiment) my_modules_experiment_path(obj.experiment, view_mode: :archived)
else else
protocols_my_module_path(obj) protocols_my_module_path(obj)
end end

View file

@ -28,11 +28,14 @@ module InputSanitizeHelper
base64_encoded_imgs = options.fetch(:base64_encoded_imgs, false) base64_encoded_imgs = options.fetch(:base64_encoded_imgs, false)
text = sanitize_input(text, tags) text = sanitize_input(text, tags)
text = simple_format(text, {}, format_opt) if simple_f 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( auto_link(
custom_link_open_new_tab(smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)), text,
html: { target: '_blank' },
link: :urls, link: :urls,
sanitize: false, sanitize: false
html: { target: '_blank' }
).html_safe ).html_safe
end end
end end

View file

@ -31,7 +31,7 @@ module MyModulesHelper
def get_task_alert_color(my_module) def get_task_alert_color(my_module)
alert = '' 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-yellow' if my_module.is_one_day_prior?
alert = ' alert-red' if my_module.is_overdue? alert = ' alert-red' if my_module.is_overdue?
end end
@ -100,4 +100,16 @@ module MyModulesHelper
my_module.experiment.project.archived_on my_module.experiment.project.archived_on
end end
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 end

View file

@ -14,7 +14,7 @@ module ProjectsHelper
end end
def user_names_with_roles(user_assignments) 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 end
def user_name_with_role(user_assignment) 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 = {}) def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
has_stock_management = repository.has_stock_management? has_stock_management = repository.has_stock_management?
reminders_enabled = Repository.reminders_enabled? 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| repository_rows.map do |record|
default_cells = public_send("#{repository.class.name.underscore}_default_columns", record) row = public_send("#{repository.class.name.underscore}_default_columns", record)
row = { row.merge!(
DT_RowId: record.id, DT_RowId: record.id,
DT_RowAttr: { 'data-state': row_style(record) }, DT_RowAttr: { 'data-state': row_style(record) },
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record), recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
hasActiveReminders: reminder_row_ids.include?(record.id),
rowRemindersUrl: rowRemindersUrl:
Rails.application.routes.url_helpers Rails.application.routes.url_helpers
.active_reminder_repository_cells_repository_repository_row_url( .active_reminder_repository_cells_repository_repository_row_url(
repository, repository,
record record
) )
}.merge(default_cells) )
if reminders_enabled
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
end
if has_stock_management if has_stock_management
row['manageStockUrl'] = if record.has_stock? row['manageStockUrl'] = if record.has_stock?
@ -100,7 +103,7 @@ module RepositoryDatatableHelper
def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {}) def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {})
has_stock_management = repository.has_stock_management? has_stock_management = repository.has_stock_management?
reminders_enabled = Repository.reminders_enabled? 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| repository_rows.map do |record|
row = { row = {
@ -108,7 +111,6 @@ module RepositoryDatatableHelper
DT_RowAttr: { 'data-state': row_style(record) }, DT_RowAttr: { 'data-state': row_style(record) },
'0': escape_input(record.name), '0': escape_input(record.name),
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record), recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record),
hasActiveReminders: reminder_row_ids.include?(record.id),
rowRemindersUrl: rowRemindersUrl:
Rails.application.routes.url_helpers Rails.application.routes.url_helpers
.active_reminder_repository_cells_repository_repository_row_url( .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 if has_stock_management
stock_present = record.repository_stock_cell.present? stock_present = record.repository_stock_cell.present?
# Always disabled in a simple view # Always disabled in a simple view
@ -275,14 +281,33 @@ module RepositoryDatatableHelper
'' ''
end end
def repository_reminder_row_ids(repository_rows, repository) def with_reminders_status(repository_rows, repository)
# don't load reminders for archived repositories # don't load reminders for archived repositories or snapshots
return [] if repository_rows.blank? || repository.archived? 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 repository_cells = RepositoryCell.joins(
return [] if repository.is_a?(RepositorySnapshot) "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 end
def stock_consumption_permitted?(repository, my_module) 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