mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-27 02:16:20 +08:00
commit
0c6e33e65b
9
Gemfile
9
Gemfile
|
@ -64,11 +64,11 @@ gem 'aspector' # Aspect-oriented programming for Rails
|
|||
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces AR
|
||||
gem 'bcrypt', '~> 3.1.10'
|
||||
gem 'caracal' # Build docx report
|
||||
gem 'deface', '~> 1.0'
|
||||
gem 'deface', '~> 1.9'
|
||||
gem 'down', '~> 5.0'
|
||||
gem 'faker' # Generate fake data
|
||||
gem 'fastimage' # Light gem to get image resolution
|
||||
gem 'httparty', '~> 0.17.3'
|
||||
gem 'httparty', '~> 0.21.0'
|
||||
gem 'i18n-js', '~> 3.6' # Localization in javascript files
|
||||
gem 'jbuilder' # JSON structures via a Builder-style DSL
|
||||
gem 'logging', '~> 2.0.0'
|
||||
|
@ -97,15 +97,12 @@ gem 'devise-async',
|
|||
git: 'https://github.com/mhfs/devise-async.git',
|
||||
branch: 'devise-4.x'
|
||||
gem 'image_processing', '~> 1.12'
|
||||
gem 'img2zpl', git: 'https://github.com/scinote-eln/img2zpl'
|
||||
gem 'rufus-scheduler', '~> 3.5'
|
||||
|
||||
gem 'discard', '~> 1.0'
|
||||
|
||||
gem 'graphviz'
|
||||
gem 'tinymce-rails', '~> 4.9.10' # Rich text editor - SEE BELOW
|
||||
# Any time you update tinymce-rails Gem, also update the cache_suffix parameter
|
||||
# in sitewide/tiny_mce.js - to prevent browsers from loading old, cached .js
|
||||
# TinyMCE files which might cause errors
|
||||
|
||||
gem 'base62' # Used for smart annotations
|
||||
gem 'newrelic_rpm'
|
||||
|
|
174
Gemfile.lock
174
Gemfile.lock
|
@ -39,43 +39,50 @@ GIT
|
|||
devise-async (0.10.2)
|
||||
devise (>= 4.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/scinote-eln/img2zpl
|
||||
revision: 23d61cfc3e90ac4caa62dd08546fa0d7590a5140
|
||||
specs:
|
||||
img2zpl (1.0.1)
|
||||
mini_magick (~> 4.9)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actioncable (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activejob (= 6.1.6.1)
|
||||
activerecord (= 6.1.6.1)
|
||||
activestorage (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actionmailbox (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
actionview (= 6.1.6.1)
|
||||
activejob (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actionmailer (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.6.1)
|
||||
actionview (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actionpack (6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activerecord (= 6.1.6.1)
|
||||
activestorage (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actiontext (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
actionview (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -85,24 +92,24 @@ GEM
|
|||
activemodel (>= 4.1, < 6.2)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
activejob (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
activerecord (6.1.6.1)
|
||||
activemodel (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
activemodel (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activerecord (6.1.7.1)
|
||||
activemodel (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activerecord-import (1.0.7)
|
||||
activerecord (>= 3.2)
|
||||
activestorage (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activejob (= 6.1.6.1)
|
||||
activerecord (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
activestorage (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.6.1)
|
||||
activesupport (6.1.7.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -245,11 +252,13 @@ GEM
|
|||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
debug_inspector (1.0.0)
|
||||
deface (1.6.1)
|
||||
deface (1.9.0)
|
||||
actionview (>= 5.2)
|
||||
nokogiri (>= 1.6)
|
||||
polyglot
|
||||
rails (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rainbow (>= 2.1.0)
|
||||
delayed_job (4.1.9)
|
||||
activesupport (>= 3.0, < 6.2)
|
||||
|
@ -273,7 +282,7 @@ GEM
|
|||
railties (>= 5)
|
||||
down (5.2.0)
|
||||
addressable (~> 2.5)
|
||||
erubi (1.10.0)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.4)
|
||||
tzinfo
|
||||
execjs (2.7.0)
|
||||
|
@ -298,17 +307,17 @@ GEM
|
|||
raabro (~> 1.4)
|
||||
generator (0.0.1)
|
||||
gherkin (5.1.0)
|
||||
globalid (1.0.0)
|
||||
globalid (1.0.1)
|
||||
activesupport (>= 5.0)
|
||||
graphviz (1.2.1)
|
||||
process-pipeline
|
||||
hammerjs-rails (2.0.8)
|
||||
hashdiff (1.0.1)
|
||||
hashie (5.0.0)
|
||||
httparty (0.17.3)
|
||||
mime-types (~> 3.0)
|
||||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.11.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.8.0)
|
||||
i18n (>= 0.6.6)
|
||||
|
@ -360,17 +369,20 @@ GEM
|
|||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mail (2.8.0.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.3.1)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2020.1104)
|
||||
mime-types-data (3.2022.0105)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.16.2)
|
||||
mini_portile2 (2.8.1)
|
||||
minitest (5.17.0)
|
||||
momentjs-rails (2.17.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.4.2)
|
||||
|
@ -382,6 +394,15 @@ GEM
|
|||
coffee-rails (>= 3.2.1)
|
||||
jquery-rails
|
||||
rails (>= 3.2.0)
|
||||
net-imap (0.3.4)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
newrelic_rpm (6.15.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.10)
|
||||
|
@ -441,8 +462,8 @@ GEM
|
|||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.4)
|
||||
racc (1.6.2)
|
||||
rack (2.2.6.2)
|
||||
rack-attack (6.4.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
|
@ -451,20 +472,20 @@ GEM
|
|||
rack
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
rails (6.1.6.1)
|
||||
actioncable (= 6.1.6.1)
|
||||
actionmailbox (= 6.1.6.1)
|
||||
actionmailer (= 6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
actiontext (= 6.1.6.1)
|
||||
actionview (= 6.1.6.1)
|
||||
activejob (= 6.1.6.1)
|
||||
activemodel (= 6.1.6.1)
|
||||
activerecord (= 6.1.6.1)
|
||||
activestorage (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
rails (6.1.7.1)
|
||||
actioncable (= 6.1.7.1)
|
||||
actionmailbox (= 6.1.7.1)
|
||||
actionmailer (= 6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
actiontext (= 6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activemodel (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.6.1)
|
||||
railties (= 6.1.7.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -482,13 +503,13 @@ GEM
|
|||
rails (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (6.1.6.1)
|
||||
actionpack (= 6.1.6.1)
|
||||
activesupport (= 6.1.6.1)
|
||||
railties (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
rainbow (3.0.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
|
@ -579,9 +600,9 @@ GEM
|
|||
simplecov_json_formatter (0.1.2)
|
||||
spinjs-rails (1.4)
|
||||
rails (>= 3.1)
|
||||
sprockets (4.1.1)
|
||||
sprockets (4.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
|
@ -591,12 +612,11 @@ GEM
|
|||
thor (1.2.1)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.2)
|
||||
tinymce-rails (4.9.11)
|
||||
railties (>= 3.1.1)
|
||||
timeout (0.3.1)
|
||||
turbolinks (5.1.1)
|
||||
turbolinks-source (~> 5.1)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (2.0.4)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
|
@ -624,7 +644,7 @@ GEM
|
|||
wkhtmltopdf-heroku (2.12.5.0)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.0)
|
||||
zeitwerk (2.6.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -657,7 +677,7 @@ DEPENDENCIES
|
|||
caracal
|
||||
cucumber-rails (~> 1.8)
|
||||
database_cleaner
|
||||
deface (~> 1.0)
|
||||
deface (~> 1.9)
|
||||
delayed_job_active_record
|
||||
devise (~> 4.7.1)
|
||||
devise-async!
|
||||
|
@ -671,9 +691,10 @@ DEPENDENCIES
|
|||
figaro
|
||||
graphviz
|
||||
hammerjs-rails
|
||||
httparty (~> 0.17.3)
|
||||
httparty (~> 0.21.0)
|
||||
i18n-js (~> 3.6)
|
||||
image_processing (~> 1.12)
|
||||
img2zpl!
|
||||
jbuilder
|
||||
jquery-rails
|
||||
jquery-scrollto-rails!
|
||||
|
@ -731,7 +752,6 @@ DEPENDENCIES
|
|||
sneaky-save!
|
||||
spinjs-rails
|
||||
timecop
|
||||
tinymce-rails (~> 4.9.10)
|
||||
turbolinks (~> 5.1.1)
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2016 BioSistemika USA, LLC <info@biosistemika.com>
|
||||
Copyright (c) 2016 SciNote, LLC <info@scinote.net>
|
||||
|
||||
SciNote is licensed under the following license:
|
||||
|
||||
|
@ -374,4 +374,4 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
|
3
app/assets/images/canvas/full_zoom.svg
Normal file
3
app/assets/images/canvas/full_zoom.svg
Normal 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 |
5
app/assets/images/canvas/medium_zoom.svg
Normal file
5
app/assets/images/canvas/medium_zoom.svg
Normal 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 |
3
app/assets/images/canvas/small_zoom.svg
Normal file
3
app/assets/images/canvas/small_zoom.svg
Normal 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 |
|
@ -1,21 +1,6 @@
|
|||
// turbolinks MUST BE THE LAST inclusion
|
||||
//= require jquery
|
||||
//= require jquery_ujs
|
||||
//= require jquery.mousewheel.min
|
||||
//= require jquery.scrollTo
|
||||
//= require jquery.autosize
|
||||
//= require jquery-ui/widget
|
||||
//= require jquery-ui/widgets/mouse
|
||||
//= require jquery-ui/widgets/draggable
|
||||
//= require jquery-ui/widgets/droppable
|
||||
//= require jquery.ui.touch-punch.min
|
||||
//= require jquery-ui/effects/effect-slide
|
||||
//= require jquery.caret.min
|
||||
//= require jquery.atwho.min
|
||||
//= require hammer
|
||||
//= require js.cookie
|
||||
//= require spin
|
||||
//= require jquery.spin
|
||||
//= require bootstrap-sprockets
|
||||
//= require moment
|
||||
//= require bootstrap-datetimepicker
|
||||
|
@ -25,8 +10,6 @@
|
|||
//= require typeahead.bundle.min
|
||||
//= require nested_form_fields
|
||||
//= require highlight.pack
|
||||
//= require tinymce-jquery
|
||||
//= require_tree ./tinymce/plugins
|
||||
//= require jsPlumb-2.0.4-min
|
||||
//= require jsnetworkx
|
||||
//= require bootstrap-select
|
||||
|
@ -257,9 +240,6 @@ var HelperModule = (function(){
|
|||
$(document).on('turbolinks:load', function() {
|
||||
/* Fix .selectpicker (bootstrap-select) to work with Turbolinks 5.x */
|
||||
$(window).trigger('load.bs.select.data-api');
|
||||
|
||||
/* Clean up TinyMCE */
|
||||
tinymce.remove();
|
||||
});
|
||||
|
||||
// Show warning if page has unsaved data
|
||||
|
|
129
app/assets/javascripts/experiments/show.js
Normal file
129
app/assets/javascripts/experiments/show.js
Normal 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();
|
||||
}());
|
832
app/assets/javascripts/experiments/table.js
Normal file
832
app/assets/javascripts/experiments/table.js
Normal 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
15
app/assets/javascripts/jquery_bundle.js
vendored
Normal 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
|
|
@ -89,155 +89,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
// Bind ajax for editing tags
|
||||
function bindEditTagsAjax() {
|
||||
var manageTagsModal = null;
|
||||
var manageTagsModalBody = null;
|
||||
|
||||
// Initialize reloading of manage tags modal content after posting new
|
||||
// tag.
|
||||
function initAddTagForm() {
|
||||
manageTagsModalBody.find('.add-tag-form')
|
||||
.submit(function() {
|
||||
var selectOptions = manageTagsModalBody.find('#new_my_module_tag .dropdown-menu li').length;
|
||||
if (selectOptions === 0 && this.id === 'new_my_module_tag') return false;
|
||||
return true;
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item').last();
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize edit tag & remove tag functionality from my_module links.
|
||||
function initTagRowLinks() {
|
||||
manageTagsModalBody.find('.edit-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
var editDiv = $(li.find('div.tag-edit'));
|
||||
|
||||
// Revert all rows to their original states
|
||||
manageTagsModalBody.find('li.list-group-item').each(function() {
|
||||
var li2 = $(this);
|
||||
li2.css('background-color', li2.data('color'));
|
||||
li2.find('.edit-tag-form').clearFormErrors();
|
||||
li2.find('input[type=text]').val(li2.data('name'));
|
||||
});
|
||||
|
||||
// Hide all other edit divs, show all show divs
|
||||
manageTagsModalBody.find('div.tag-edit').hide();
|
||||
manageTagsModalBody.find('div.tag-show').show();
|
||||
|
||||
editDiv.find('input[type=text]').val(li.data('name'));
|
||||
editDiv.find('.edit-tag-color').colorselector('setColor', li.data('color'));
|
||||
|
||||
li.find('div.tag-show').hide();
|
||||
editDiv.show();
|
||||
});
|
||||
manageTagsModalBody.find('div.tag-edit .dropdown-colorselector > .dropdown-menu li a')
|
||||
.on('click', function() {
|
||||
// Change background of the <li>
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
li.css('background-color', $this.data('value'));
|
||||
});
|
||||
manageTagsModalBody.find('.remove-tag-link')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.delete-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
manageTagsModalBody.find('.edit-tag-form')
|
||||
.on('ajax:success', function(e, data) {
|
||||
var newTag;
|
||||
initTagsModalBody(data);
|
||||
dropdownSelector.removeValue('#module-tags-selector', this.dataset.tagId, '', true);
|
||||
newTag = $('#manage-module-tags-modal .list-group-item[data-tag-id=' + this.dataset.tagId + ']');
|
||||
dropdownSelector.addValue('#module-tags-selector', {
|
||||
value: newTag.data('tag-id'),
|
||||
label: newTag.data('name'),
|
||||
params: {
|
||||
color: newTag.data('color')
|
||||
}
|
||||
}, true);
|
||||
})
|
||||
.on('ajax:error', function(e, data) {
|
||||
$(this).renderFormErrors('tag', data.responseJSON);
|
||||
});
|
||||
manageTagsModalBody.find('.cancel-tag-link')
|
||||
.on('click', function() {
|
||||
var $this = $(this);
|
||||
var li = $this.parents('li.list-group-item');
|
||||
|
||||
li.css('background-color', li.data('color'));
|
||||
li.find('.edit-tag-form').clearFormErrors();
|
||||
|
||||
li.find('div.tag-edit').hide();
|
||||
li.find('div.tag-show').show();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body. This
|
||||
// function must be called when modal body is changed.
|
||||
function initTagsModalBody(data) {
|
||||
manageTagsModalBody.html(data.html);
|
||||
manageTagsModalBody.find('.selectpicker').selectpicker();
|
||||
initAddTagForm();
|
||||
initTagRowLinks();
|
||||
}
|
||||
|
||||
manageTagsModal = $('#manage-module-tags-modal');
|
||||
manageTagsModalBody = manageTagsModal.find('.modal-body');
|
||||
|
||||
// Reload tags HTML element when modal is closed
|
||||
manageTagsModal.on('hide.bs.modal', function() {
|
||||
var tagsEl = $('#module-tags');
|
||||
|
||||
// Load HTML
|
||||
$.ajax({
|
||||
url: tagsEl.attr('data-module-tags-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var newOptions = $(data.html_module_header).find('option');
|
||||
$('#module-tags-selector').find('option').remove();
|
||||
$(newOptions).appendTo('#module-tags-selector').change();
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove modal content when modal window is closed.
|
||||
manageTagsModal.on('hidden.bs.modal', function() {
|
||||
manageTagsModalBody.html('');
|
||||
});
|
||||
// initialize my_module tab remote loading
|
||||
$('.edit-tags-link')
|
||||
.on('ajax:before', function() {
|
||||
manageTagsModal.modal('show');
|
||||
})
|
||||
.on('ajax:success', function(e, data) {
|
||||
$('#manage-module-tags-modal-module').text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
function checkStatusState() {
|
||||
$.getJSON($('.status-flow-dropdown').data('status-check-url'), (statusData) => {
|
||||
if (statusData.status_changing) {
|
||||
|
@ -297,9 +148,9 @@
|
|||
return `<span class="my-module-tags-color" style="background:${data.params.color}"></span>
|
||||
${data.label}`;
|
||||
}
|
||||
return `<span class="my-module-tags-color"></span>
|
||||
${data.label + ' '}
|
||||
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`;
|
||||
return `<span class="my-module-tags-color new"><i class="fas fa-plus"></i></span>
|
||||
${data.label + ' '}
|
||||
<span class="my-module-tags-create-new"> ${I18n.t('my_modules.details.create_new_tag')}</span>`;
|
||||
},
|
||||
onOpen: function() {
|
||||
$('.select-container .edit-button-container').removeClass('hidden');
|
||||
|
@ -440,7 +291,6 @@
|
|||
initTaskCollapseState();
|
||||
applyTaskStatusChangeCallBack();
|
||||
initTagsSelector();
|
||||
bindEditTagsAjax();
|
||||
initStartDatePicker();
|
||||
initDueDatePicker();
|
||||
initAssignedUsersSelector();
|
||||
|
|
|
@ -18,19 +18,21 @@ function initEditMyModuleDescription() {
|
|||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
|
||||
setTimeout(function() {
|
||||
TinyMCE.wrapTables(viewObject);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function initEditProtocolDescription() {
|
||||
var viewObject = $('#protocol_description_view');
|
||||
viewObject.on('click', function(e) {
|
||||
if ($(e.target).hasClass('record-info-link')) return;
|
||||
TinyMCE.init('#protocol_description_textarea', refreshProtocolStatusBar);
|
||||
TinyMCE.init('#protocol_description_textarea', { afterInitCallback: refreshProtocolStatusBar });
|
||||
}).on('click', 'a', function(e) {
|
||||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
||||
function initCopyToRepository() {
|
||||
|
|
|
@ -138,6 +138,7 @@
|
|||
}
|
||||
|
||||
function processResult(ev, resultTypeEnum) {
|
||||
var textWithoutImages;
|
||||
var $form = $(ev.target.form);
|
||||
$form.clearFormErrors();
|
||||
|
||||
|
@ -153,9 +154,11 @@
|
|||
.removeClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
|
||||
break;
|
||||
case ResultTypeEnum.TEXT:
|
||||
textWithoutImages = TinyMCE.getContent().replaceAll(/src="(data:image\/[^;]+;base64[^"]+)"/i, '');
|
||||
|
||||
textValidator(
|
||||
ev, $form.find('#result_text_attributes_textarea'), 1,
|
||||
$form.data('rich-text-max-length'), false, TinyMCE.getContent()
|
||||
$form.data('rich-text-max-length'), false, textWithoutImages
|
||||
);
|
||||
break;
|
||||
default:
|
||||
|
|
158
app/assets/javascripts/my_modules/tags.js
Normal file
158
app/assets/javascripts/my_modules/tags.js
Normal 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();
|
||||
}());
|
|
@ -332,6 +332,8 @@ function initializeFullZoom() {
|
|||
commentMenu.position({ top: $(this).parent().position().top });
|
||||
commentMenu.offset({ top: $(this).parent().offset().top + <%= Constants::DROPDOWN_TOP_OFFSET_PX %> });
|
||||
});
|
||||
|
||||
initializeCanvasViewNavigator();
|
||||
}
|
||||
|
||||
function destroyFullZoom() {
|
||||
|
@ -370,6 +372,7 @@ function initializeMediumZoom() {
|
|||
|
||||
// Restore draggable position
|
||||
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
|
||||
initializeCanvasViewNavigator();
|
||||
}
|
||||
|
||||
function destroyMediumZoom() {
|
||||
|
@ -397,6 +400,7 @@ function initializeSmallZoom() {
|
|||
|
||||
// Restore draggable position
|
||||
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
|
||||
initializeCanvasViewNavigator();
|
||||
}
|
||||
|
||||
function destroySmallZoom() {
|
||||
|
@ -595,7 +599,7 @@ function resizeContainer() {
|
|||
if (cont.length > 0) {
|
||||
cont.css(
|
||||
"height",
|
||||
($(window).height() - cont.offset().top - 15) + "px"
|
||||
($(window).height() - cont.offset().top) + "px"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2064,7 +2068,9 @@ function cloneModule(originalModule, gridDistX, gridDistY, left, top) {
|
|||
var newModule = createVirtualModule();
|
||||
elLeft(newModule, left);
|
||||
elTop(newModule, top);
|
||||
updateModuleHtml(newModule, id, originalModule.data("module-name"), gridDistX, gridDistY);
|
||||
updateModuleHtml(newModule, id,
|
||||
`${I18n.t('experiments.canvas.edit.clone_prefix')} ${originalModule.data('module-name')}`,
|
||||
gridDistX, gridDistY);
|
||||
newModule.removeClass("new");
|
||||
|
||||
// Add the cloned module id into the hidden input field
|
||||
|
@ -2661,6 +2667,9 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
|
|||
y_pos = y_el + (fastOffsetY - y_start);
|
||||
x_start = fastOffsetX;
|
||||
y_start = fastOffsetY;
|
||||
|
||||
drawRectangleCanvasNavigatorView(-x_pos, -y_pos)
|
||||
|
||||
if (draggable !== null) {
|
||||
elLeft(draggable, x_pos);
|
||||
elTop(draggable, y_pos);
|
||||
|
@ -2913,6 +2922,79 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
|
|||
}
|
||||
})();
|
||||
|
||||
function drawCanvasViewNavigatorImage(image_src){
|
||||
var canvasImage = $('.canvas-preview-img')[0];
|
||||
var canvasRect = $('.canvas-preview-rect')[0];
|
||||
var canvasImageTx = canvasImage.getContext('2d');
|
||||
var canvasRectTx = canvasRect.getContext('2d');
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function() {
|
||||
canvasImageTx.drawImage(image, 0, 0, canvasImage.width, canvasImage.height);
|
||||
drawRectangleCanvasNavigatorView(-(draggable.offset().left - draggable.parent().offset().left),
|
||||
-(draggable.offset().top - draggable.parent().offset().top));
|
||||
canvasRectTx.stroke();
|
||||
};
|
||||
image.src = image_src;
|
||||
|
||||
}
|
||||
|
||||
function initializeCanvasViewNavigator() {
|
||||
if ($('.canvas-preview-img').data('image-url')) {
|
||||
drawCanvasViewNavigatorImage($('.canvas-preview-img').data('image-url'));
|
||||
} else if ($('.canvas-preview-img').data('workflowimg-present') === false) {
|
||||
let imgUrl = $('.canvas-preview-img').data('workflowimg-url');
|
||||
$.ajax({
|
||||
url: imgUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
drawCanvasViewNavigatorImage($(data.workflowimg).attr('src'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function drawRoundRectangle(ctx, xPos, yPos, width, height, radius) {
|
||||
width = Math.max(width, 0)
|
||||
height = Math.max(height, 0)
|
||||
if (width < 2 * radius) radius = width / 2;
|
||||
if (height < 2 * radius) radius = height / 2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 4;
|
||||
ctx.strokeStyle = '#104DA9';
|
||||
ctx.moveTo(xPos + radius, yPos);
|
||||
ctx.arcTo(xPos + width, yPos, xPos + width, yPos + height, radius);
|
||||
ctx.arcTo(xPos + width, yPos + height, xPos, yPos + height, radius);
|
||||
ctx.arcTo(xPos, yPos + height, xPos, yPos, radius);
|
||||
ctx.arcTo(xPos, yPos, xPos + width, yPos, radius);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
function drawRectangleCanvasNavigatorView(xPos, yPos) {
|
||||
var adjustFactor = 10;
|
||||
var canvasSize = calculateDraggableSize(draggable);
|
||||
var ratioX = xPos / canvasSize.width;
|
||||
var ratioY = yPos / canvasSize.height;
|
||||
|
||||
var canvasPreviewRect = $('.canvas-preview-rect')[0];
|
||||
|
||||
if (canvasPreviewRect) {
|
||||
var canvasRectTx = canvasPreviewRect.getContext('2d');
|
||||
var canvasWidth = canvasRectTx.canvas.width;
|
||||
var canvasHeight = canvasRectTx.canvas.height;
|
||||
var previewWidth = canvasWidth * ($('#diagram-container').width() / canvasSize.width);
|
||||
var previewHeight = canvasHeight * ($('#diagram-container').height() / canvasSize.height);
|
||||
|
||||
canvasRectTx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
canvasRectTx.beginPath();
|
||||
drawRoundRectangle(canvasRectTx, canvasWidth * ratioX + adjustFactor, canvasHeight * ratioY + adjustFactor,
|
||||
previewWidth - adjustFactor, previewHeight - adjustFactor, 4)
|
||||
}
|
||||
}
|
||||
|
||||
/** prevent reload page */
|
||||
var preventCanvasReloadOnSave = (function() {
|
||||
'use strict';
|
||||
|
|
|
@ -317,11 +317,16 @@
|
|||
function initNewExperimentToolbarButton() {
|
||||
let forms = '.new-experiment-form';
|
||||
$(experimentsPage)
|
||||
.on('submit', forms, function() {
|
||||
$(this).find("button[type='submit']").prop('disabled', true);
|
||||
})
|
||||
.on('ajax:success', forms, function(ev, data) {
|
||||
appendActionModal($(data.html));
|
||||
$(this).find("button[type='submit']").prop('disabled', false);
|
||||
})
|
||||
.on('ajax:error', forms, function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
$(this).find("button[type='submit']").prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ var ProtocolRepositoryHeader = (function() {
|
|||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
TinyMCE.initIfHasDraft(viewObject);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -72,20 +72,8 @@ function importProtocolFromFile(
|
|||
return template;
|
||||
}
|
||||
|
||||
function addChildToPreviewElement(parentEl, name, childEl) {
|
||||
parentEl.find("[data-hold='" + name + "']").append(childEl);
|
||||
}
|
||||
|
||||
function hidePartOfElement(element, name) {
|
||||
element.find("[data-toggle='" + name + "']").hide();
|
||||
}
|
||||
|
||||
function showPartOfElement(element, name) {
|
||||
element.find("[data-toggle='" + name + "']").show();
|
||||
}
|
||||
|
||||
function newAssetElement(folder, stepGuid, fileRef, fileName, fileType) {
|
||||
var html = '<li>';
|
||||
var html = '<li class="col-xs-12">';
|
||||
var assetBytes;
|
||||
if ($.inArray(fileType, ['image/png', 'image/jpeg', 'image/gif', 'image/bmp']) > 0) {
|
||||
assetBytes = getAssetBytes(folder, stepGuid, fileRef);
|
||||
|
@ -176,6 +164,8 @@ function importProtocolFromFile(
|
|||
var stepName = node.children('name').text();
|
||||
var checklistNodes;
|
||||
var tableNodes;
|
||||
var assetNodes;
|
||||
var fileHeader;
|
||||
var stepDescription = displayTinyMceAssetInDescription(
|
||||
node,
|
||||
protocolFolders[position],
|
||||
|
@ -191,39 +181,6 @@ function importProtocolFromFile(
|
|||
}
|
||||
);
|
||||
|
||||
// Iterate through step assets
|
||||
var assetNodes = node.find('assets > asset');
|
||||
if (assetNodes.length > 0) {
|
||||
assetNodes.each(function() {
|
||||
var fileRef = $(this).attr('fileRef');
|
||||
var fileName = $(this).children('fileName').text();
|
||||
var fileType = $(this).children('fileType').text();
|
||||
|
||||
var assetEl = newAssetElement(
|
||||
protocolFolders[position],
|
||||
stepGuid,
|
||||
fileRef,
|
||||
fileName,
|
||||
fileType
|
||||
);
|
||||
|
||||
// Append asset element to step
|
||||
addChildToPreviewElement(stepEl, 'assets', assetEl);
|
||||
});
|
||||
} else {
|
||||
hidePartOfElement(stepEl, 'assets');
|
||||
}
|
||||
|
||||
// Iterate through step tables
|
||||
tableNodes = node.find('elnTables > elnTable');
|
||||
if (tableNodes.length > 0) {
|
||||
tableNodes.each(function() {
|
||||
addTablePreview(stepEl, this);
|
||||
});
|
||||
} else {
|
||||
hidePartOfElement(stepEl, 'tables');
|
||||
}
|
||||
|
||||
// Iterate through step checklists
|
||||
checklistNodes = node.find('checklists > checklist');
|
||||
if (checklistNodes.length > 0) {
|
||||
|
@ -232,21 +189,26 @@ function importProtocolFromFile(
|
|||
});
|
||||
}
|
||||
|
||||
// Iterate through step tables
|
||||
tableNodes = node.find('elnTables > elnTable');
|
||||
if (tableNodes.length > 0) {
|
||||
tableNodes.each(function() {
|
||||
addTablePreview(stepEl, this);
|
||||
});
|
||||
}
|
||||
|
||||
// Parse step elements
|
||||
$(this).find('stepElements > stepElement').each(function() {
|
||||
$(this).find('stepElements > stepElement').sort(stepComparator).each(function() {
|
||||
$element = $(this);
|
||||
switch ($(this).attr('type')) {
|
||||
case 'Checklist':
|
||||
addChecklistPreview(stepEl, $(this).find('checklist'));
|
||||
showPartOfElement(stepEl, 'checklists');
|
||||
break;
|
||||
case 'StepTable':
|
||||
addTablePreview(stepEl, $(this).find('elnTable'));
|
||||
showPartOfElement(stepEl, 'tables');
|
||||
break;
|
||||
case 'StepText':
|
||||
addStepTextPreview(stepEl, $(this).find('stepText'), protocolFolders[position], stepGuid);
|
||||
showPartOfElement(stepEl, 'step-texts');
|
||||
break;
|
||||
default:
|
||||
// nothing to do
|
||||
|
@ -254,6 +216,32 @@ function importProtocolFromFile(
|
|||
}
|
||||
});
|
||||
|
||||
// Iterate through step assets
|
||||
assetNodes = node.find('assets > asset');
|
||||
if (assetNodes.length > 0) {
|
||||
fileHeader = newPreviewElement('asset-file-name', null);
|
||||
|
||||
stepEl.append(fileHeader);
|
||||
|
||||
assetNodes.each(function() {
|
||||
var fileRef = $(this).attr('fileRef');
|
||||
var fileName = $(this).children('fileName').text();
|
||||
var fileType = $(this).children('fileType').text();
|
||||
var assetEl;
|
||||
|
||||
assetEl = newAssetElement(
|
||||
protocolFolders[position],
|
||||
stepGuid,
|
||||
fileRef,
|
||||
fileName,
|
||||
fileType
|
||||
);
|
||||
|
||||
// Append asset element to step
|
||||
stepEl.append(assetEl);
|
||||
});
|
||||
}
|
||||
|
||||
// Append step element to preview container
|
||||
previewContainer.append(stepEl);
|
||||
});
|
||||
|
@ -270,10 +258,10 @@ function importProtocolFromFile(
|
|||
{ name: tableName }
|
||||
);
|
||||
var elnTableEl = generateElnTable(tableId, tableContent);
|
||||
addChildToPreviewElement(tableEl, 'table', elnTableEl);
|
||||
tableEl.append(elnTableEl);
|
||||
|
||||
// Now, append table element to step
|
||||
addChildToPreviewElement(stepEl, 'tables', tableEl);
|
||||
stepEl.append(tableEl);
|
||||
}
|
||||
|
||||
function addChecklistPreview(stepEl, checklistNode) {
|
||||
|
@ -294,11 +282,11 @@ function importProtocolFromFile(
|
|||
'checklist-item',
|
||||
{ text: itemText }
|
||||
);
|
||||
addChildToPreviewElement(checklistEl, 'checklist-items', itemEl);
|
||||
checklistEl.append(itemEl);
|
||||
});
|
||||
|
||||
// Now, add checklist item to step
|
||||
addChildToPreviewElement(stepEl, 'checklists', checklistEl);
|
||||
stepEl.append(stepEl, checklistEl);
|
||||
}
|
||||
|
||||
function addStepTextPreview(stepEl, stepTextNode, folder, stepGuid) {
|
||||
|
@ -311,7 +299,7 @@ function importProtocolFromFile(
|
|||
{ text: itemText }
|
||||
);
|
||||
|
||||
addChildToPreviewElement(stepEl, 'step-texts', textEl);
|
||||
stepEl.append(textEl);
|
||||
}
|
||||
|
||||
// display tiny_mce_assets in step description
|
||||
|
@ -743,7 +731,7 @@ function importProtocolFromFile(
|
|||
// Parse step elements
|
||||
stepJson.stepElements = [];
|
||||
|
||||
$(this).find('stepElements > stepElement').each(function() {
|
||||
$(this).find('stepElements > stepElement').sort(stepComparator).each(function() {
|
||||
stepJson.stepElements.push(stepElementJson($(this), index, stepGuid));
|
||||
});
|
||||
|
||||
|
|
|
@ -42,7 +42,11 @@
|
|||
// On init
|
||||
|
||||
initHandsOnTable($(document));
|
||||
TinyMCE.highlight();
|
||||
|
||||
$('[class*=language]').each((i, block) => {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
|
||||
$(function () {
|
||||
|
@ -116,4 +120,4 @@
|
|||
|
||||
reorderAttachmentsInit();
|
||||
initAssetViewModeToggle();
|
||||
})();
|
||||
});
|
||||
|
|
|
@ -1020,7 +1020,7 @@ function reportHandsonTableConverter() {
|
|||
});
|
||||
|
||||
// Project content
|
||||
reportData.project_content = { experiments: [], repositories: [] };
|
||||
reportData.project_content = { experiments: [] };
|
||||
$.each($('.project-contents-container .experiment-element'), function(i, experiment) {
|
||||
let expCheckbox = $(experiment).find('.report-experiment-checkbox');
|
||||
if (!expCheckbox.prop('checked') && !expCheckbox.prop('indeterminate')) return;
|
||||
|
@ -1034,10 +1034,6 @@ function reportHandsonTableConverter() {
|
|||
reportData.project_content.experiments.push(experimentData);
|
||||
});
|
||||
|
||||
$.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), function(i, e) {
|
||||
reportData.project_content.repositories.push(parseInt(e.value, 10));
|
||||
});
|
||||
|
||||
// Settings
|
||||
reportData.report.settings.template = dropdownSelector.getValues('#templateSelector');
|
||||
reportData.report.settings.all_tasks = $('.project-contents-container .select-all-my-modules-checkbox')
|
||||
|
@ -1048,6 +1044,10 @@ function reportHandsonTableConverter() {
|
|||
$.each($('.task-contents-container .content-element .task-setting'), function(i, e) {
|
||||
reportData.report.settings.task[e.value] = e.checked;
|
||||
});
|
||||
reportData.report.settings.task.repositories = [];
|
||||
$.each($('.task-contents-container .repositories-contents .repositories-setting:checked'), function(i, e) {
|
||||
reportData.report.settings.task.repositories.push(parseInt(e.value, 10));
|
||||
});
|
||||
|
||||
reportData.report.settings.task.result_order = dropdownSelector.getValues('#taskResultsOrder');
|
||||
|
||||
|
|
|
@ -412,10 +412,10 @@ var RepositoryDatatable = (function(global) {
|
|||
// Adjust columns width in table header
|
||||
function adjustTableHeader() {
|
||||
TABLE.columns.adjust();
|
||||
$('.dropdown-menu').parent()
|
||||
.on('shown.bs.dropdown hidden.bs.dropdown', function() {
|
||||
TABLE.columns.adjust();
|
||||
});
|
||||
// $('.dropdown-menu').parent()
|
||||
// .on('shown.bs.dropdown hidden.bs.dropdown', function() {
|
||||
// TABLE.columns.adjust();
|
||||
// });
|
||||
}
|
||||
|
||||
function checkSnapshottingStatus() {
|
||||
|
@ -664,9 +664,9 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
initActiveRemindersFilter();
|
||||
renderFiltersDropdown();
|
||||
setTimeout(function() {
|
||||
adjustTableHeader();
|
||||
}, 500);
|
||||
// setTimeout(function() {
|
||||
// adjustTableHeader();
|
||||
// }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -699,11 +699,11 @@ var RepositoryDatatable = (function(global) {
|
|||
})
|
||||
|
||||
initRowSelection();
|
||||
$(window).resize(() => {
|
||||
setTimeout(() => {
|
||||
adjustTableHeader();
|
||||
}, 500);
|
||||
});
|
||||
// $(window).resize(() => {
|
||||
// setTimeout(() => {
|
||||
// adjustTableHeader();
|
||||
// }, 500);
|
||||
// });
|
||||
|
||||
return TABLE;
|
||||
}
|
||||
|
@ -822,7 +822,7 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
|
||||
changeToEditMode();
|
||||
adjustTableHeader();
|
||||
// adjustTableHeader();
|
||||
})
|
||||
.on('click', '#deleteRepositoryRecords', function() {
|
||||
$('#deleteRepositoryRecord').modal('show');
|
||||
|
@ -909,9 +909,9 @@ var RepositoryDatatable = (function(global) {
|
|||
document.documentElement.style.setProperty('--repository-sidebar-margin', '363px');
|
||||
});
|
||||
|
||||
$('#wrapper').on('sideBar::hidden sideBar::shown', function() {
|
||||
adjustTableHeader();
|
||||
});
|
||||
// $('#wrapper').on('sideBar::hidden sideBar::shown', function() {
|
||||
// adjustTableHeader();
|
||||
// });
|
||||
}
|
||||
|
||||
function renderFiltersDropdown() {
|
||||
|
|
|
@ -71,6 +71,7 @@ var inlineEditing = (function() {
|
|||
data: params,
|
||||
success: function(result) {
|
||||
var viewData;
|
||||
var parentContainer = container.parent();
|
||||
if (container.data('response-field')) {
|
||||
// If we want to modify preview element on backend
|
||||
// we can use this data field and we will take string from response
|
||||
|
@ -95,11 +96,15 @@ var inlineEditing = (function() {
|
|||
.attr('value', inputField(container).val());
|
||||
appendAfterLabel(container);
|
||||
|
||||
container.trigger('inlineEditing::updated', [inputField(container).val(), viewData])
|
||||
container.trigger('inlineEditing::updated', [inputField(container).val(), viewData]);
|
||||
|
||||
if (SIDEBAR_ITEM_TYPES.includes(paramsGroup)) {
|
||||
updateSideBarNav(paramsGroup, itemId, viewData);
|
||||
}
|
||||
|
||||
if (parentContainer.attr('data-original-title')) {
|
||||
parentContainer.attr('data-original-title', inputField(container).val());
|
||||
}
|
||||
},
|
||||
error: function(response) {
|
||||
var error = response.responseJSON[fieldToUpdate];
|
||||
|
@ -111,6 +116,7 @@ var inlineEditing = (function() {
|
|||
container.find('.error-block').html(error.join(', '));
|
||||
inputField(container).focus();
|
||||
container.data('disabled', false);
|
||||
$('.tooltip').hide();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
@ -127,26 +133,38 @@ var inlineEditing = (function() {
|
|||
|
||||
$(document)
|
||||
.off('click', editBlocks)
|
||||
.off('keyup', `${editBlocks}`)
|
||||
.off('click', `${editBlocks} .save-button`)
|
||||
.off('click', `${editBlocks} .cancel-button`)
|
||||
.off('blur', `${editBlocks} textarea, ${editBlocks} input`)
|
||||
.on('keyup', `${editBlocks}`, function(e) {
|
||||
var container = $(this);
|
||||
if (e.keyCode === 27) {
|
||||
$(`${editBlocks} .cancel-button`).click();
|
||||
} // Esc
|
||||
if (e.keyCode === 13 && !container.find('.view-mode').hasClass('hidden')) {
|
||||
$(editBlocks).click();
|
||||
}
|
||||
})
|
||||
.on('click', editBlocks, function(e) {
|
||||
// 'A' mean that, if we click on <a></a> element we will not go in edit mode
|
||||
var container = $(this);
|
||||
if (e.target.tagName === 'A') return true;
|
||||
if (inputField(container).attr('disabled')) {
|
||||
saveAllEditFields();
|
||||
|
||||
inputField(container)
|
||||
.attr('disabled', false)
|
||||
let input = inputField(container);
|
||||
input.attr('disabled', false)
|
||||
.removeClass('hidden')
|
||||
.focus();
|
||||
input[0].selectionStart = input[0].value.length;
|
||||
input[0].selectionEnd = input[0].value.length;
|
||||
container
|
||||
.attr('data-edit-mode', '1');
|
||||
container.find('.view-mode')
|
||||
.addClass('hidden')
|
||||
.closest('.inline_scroll_block')
|
||||
.scrollTop(container.offsetTop);
|
||||
$('.tooltip').hide();
|
||||
}
|
||||
e.stopPropagation();
|
||||
return true;
|
||||
|
|
|
@ -20,6 +20,7 @@ var CommentsSidebar = (function() {
|
|||
$(SIDEBAR).find('.comment-input-container').removeClass('hidden');
|
||||
} else {
|
||||
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
|
||||
$(SIDEBAR).find('.comment-input-container').addClass('update-only');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -28,7 +29,8 @@ var CommentsSidebar = (function() {
|
|||
var commentsAmount = $(SIDEBAR).find('.comments-list .comment-container').length;
|
||||
if (commentsCounter.length) {
|
||||
// Replace the number in comment element
|
||||
commentsCounter.text(commentsCounter.text().replace(/\d+/g, commentsAmount));
|
||||
commentsCounter.text(commentsCounter.text().replace(/[\d\\+]+/g, commentsAmount));
|
||||
commentsCounter.removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +39,7 @@ var CommentsSidebar = (function() {
|
|||
commentsCounter = $(`#comment-count-${$(this).data('objectId')}`);
|
||||
closeCallback = $(this).data('closeCallback');
|
||||
CommentsSidebar.open($(this).data('objectType'), $(this).data('objectId'));
|
||||
$(this).parent().find('.unseen-comments').remove();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
@ -86,6 +89,9 @@ var CommentsSidebar = (function() {
|
|||
}
|
||||
$(SIDEBAR).find('.comment-input-field').val('');
|
||||
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
|
||||
if ($(SIDEBAR).find('.comment-input-container').hasClass('update-only')) {
|
||||
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
|
||||
}
|
||||
$('.error-container').empty();
|
||||
updateCounter();
|
||||
},
|
||||
|
@ -100,6 +106,9 @@ var CommentsSidebar = (function() {
|
|||
$(document).on('click', `${SIDEBAR} .cancel-button`, function() {
|
||||
$(SIDEBAR).find('.comment-input-field').val('');
|
||||
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
|
||||
if ($(SIDEBAR).find('.comment-input-container').hasClass('update-only')) {
|
||||
$(SIDEBAR).find('.comment-input-container').addClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -127,6 +136,9 @@ var CommentsSidebar = (function() {
|
|||
$('.comment-container').removeClass('edit');
|
||||
$(this).closest('.comment-container').addClass('edit');
|
||||
$(SIDEBAR).find('.sidebar-footer').addClass('update');
|
||||
if ($(SIDEBAR).find('.comment-input-container').hasClass('hidden')) {
|
||||
$(SIDEBAR).find('.comment-input-container').removeClass('hidden');
|
||||
}
|
||||
$(SIDEBAR).find('.comment-input-field')
|
||||
.val($(this).data('comment-raw'))
|
||||
.data('update-url', $(this).data('update-url'));
|
||||
|
@ -152,7 +164,7 @@ var CommentsSidebar = (function() {
|
|||
open: function(objectType, objectId) {
|
||||
$(SIDEBAR).find('.comments-subject-title').empty();
|
||||
$(SIDEBAR).find('.comments-list').empty();
|
||||
$(SIDEBAR).find('.comment-input-field').val('');
|
||||
$(SIDEBAR).find('.comment-input-field').val('').focus();
|
||||
$('.error-container').empty();
|
||||
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
|
||||
$(SIDEBAR).data('object-type', objectType).data('object-id', objectId);
|
||||
|
|
|
@ -6,16 +6,21 @@
|
|||
ev.stopPropagation();
|
||||
|
||||
let dt = $(this);
|
||||
let options = { ignoreReadonly: true };
|
||||
|
||||
if (dt.data('DateTimePicker')) {
|
||||
dt.data('DateTimePicker').destroy();
|
||||
}
|
||||
|
||||
dt.datetimepicker({ ignoreReadonly: true });
|
||||
if (dt.data('positioningVertical')) {
|
||||
options.widgetPositioning = { vertical: dt.data('positioningVertical') };
|
||||
}
|
||||
|
||||
dt.datetimepicker(options);
|
||||
dt.data('DateTimePicker').show();
|
||||
});
|
||||
|
||||
$(document).on('click', '[data-toggle="clear-date-time-picker"]', function() {
|
||||
$(document).on('mousedown', '[data-toggle="clear-date-time-picker"]', function() {
|
||||
let dt = $(`#${$(this).data('target')}`);
|
||||
if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false });
|
||||
dt.data('DateTimePicker').clear();
|
||||
|
|
|
@ -44,18 +44,20 @@ var filterDropdown = (function() {
|
|||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}).on('hide.bs.dropdown', function() {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$('.apply-filters', $filterContainer).click();
|
||||
}).on('hide.bs.dropdown', function(e) {
|
||||
if (e.target === e.currentTarget) {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$('.apply-filters', $filterContainer).click();
|
||||
}
|
||||
});
|
||||
|
||||
$textFilter.click(function(e) {
|
||||
e.stopPropagation();
|
||||
$('#textSearchFilterHistory').toggle();
|
||||
$(this).closest('.dropdown').toggleClass('open');
|
||||
}).on('input', () => {
|
||||
$(e.currentTarget).closest('.dropdown').toggleClass('open');
|
||||
}).on('input', (e) => {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$(this).closest('.dropdown').removeClass('open');
|
||||
$(e.currentTarget).closest('.dropdown').removeClass('open');
|
||||
});
|
||||
|
||||
$filterContainer.on('click', '.projects-search-keyword', function(e) {
|
||||
|
|
|
@ -161,7 +161,7 @@ var MarvinJsEditorApi = (function() {
|
|||
} else if (config.objectType === 'Result') {
|
||||
location.reload();
|
||||
} else if (config.objectType === 'TinyMceAsset') {
|
||||
json = tinymce.util.JSON.parse(result);
|
||||
json = JSON.parse(result);
|
||||
config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json));
|
||||
TinyMCE.updateImages(config.editor);
|
||||
}
|
||||
|
@ -210,6 +210,20 @@ var MarvinJsEditorApi = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
function createNewMarvinContainer(dataset) {
|
||||
var objectId = dataset.objectId;
|
||||
var objectType = dataset.objectType;
|
||||
var marvinUrl = dataset.marvinUrl;
|
||||
var container = dataset.sketchContainer;
|
||||
MarvinJsEditor.open({
|
||||
mode: 'new',
|
||||
objectId: objectId,
|
||||
objectType: objectType,
|
||||
marvinUrl: marvinUrl,
|
||||
container: container
|
||||
});
|
||||
}
|
||||
|
||||
// MarvinJS Methods
|
||||
|
||||
return {
|
||||
|
@ -254,17 +268,13 @@ var MarvinJsEditorApi = (function() {
|
|||
|
||||
initNewButton: function(selector, saveCallback) {
|
||||
$(selector).off('click').on('click', function() {
|
||||
var objectId = this.dataset.objectId;
|
||||
var objectType = this.dataset.objectType;
|
||||
var marvinUrl = this.dataset.marvinUrl;
|
||||
var container = this.dataset.sketchContainer;
|
||||
MarvinJsEditor.open({
|
||||
mode: 'new',
|
||||
objectId: objectId,
|
||||
objectType: objectType,
|
||||
marvinUrl: marvinUrl,
|
||||
container: container
|
||||
});
|
||||
createNewMarvinContainer(this.dataset);
|
||||
});
|
||||
|
||||
$(selector).off('keypress').on('keypress', function(e) {
|
||||
if (e.which === 13) {
|
||||
createNewMarvinContainer(this.dataset);
|
||||
}
|
||||
});
|
||||
|
||||
MarvinJsEditor.saveCallback = saveCallback;
|
||||
|
@ -280,47 +290,6 @@ var MarvinJsEditorApi = (function() {
|
|||
};
|
||||
});
|
||||
|
||||
// TinyMCE plugin
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
tinymce.PluginManager.requireLangPack('MarvinJsPlugin');
|
||||
|
||||
tinymce.create('tinymce.plugins.MarvinJsPlugin', {
|
||||
MarvinJsPlugin: function(ed) {
|
||||
var editor = ed;
|
||||
|
||||
function openMarvinJs() {
|
||||
MarvinJsEditor.open({
|
||||
mode: 'new-tinymce',
|
||||
marvinUrl: '/tiny_mce_assets/marvinjs',
|
||||
editor: editor
|
||||
});
|
||||
}
|
||||
// Add a button that opens a window
|
||||
editor.addButton('marvinjsplugin', {
|
||||
tooltip: I18n.t('marvinjs.new_button'),
|
||||
icon: 'marvinjs',
|
||||
onclick: openMarvinJs
|
||||
});
|
||||
|
||||
// Adds a menu item to the tools menu
|
||||
editor.addMenuItem('marvinjsplugin', {
|
||||
text: I18n.t('marvinjs.new_button'),
|
||||
icon: 'marvinjs',
|
||||
context: 'insert',
|
||||
onclick: openMarvinJs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tinymce.PluginManager.add(
|
||||
'marvinjsplugin',
|
||||
tinymce.plugins.MarvinJsPlugin
|
||||
);
|
||||
})();
|
||||
|
||||
// Initialization
|
||||
$(document).on('click', '.marvinjs-edit-button', function() {
|
||||
var editButton = $(this);
|
||||
|
@ -337,7 +306,7 @@ $(document).on('click', '.marvinjs-edit-button', function() {
|
|||
$(document).on('turbolinks:load', function() {
|
||||
MarvinJsEditor = MarvinJsEditorApi();
|
||||
if (MarvinJsEditor.enabled()) {
|
||||
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote' && typeof(ChemicalizeMarvinJs) !== 'undefined') {
|
||||
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote' && typeof (ChemicalizeMarvinJs) !== 'undefined') {
|
||||
ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
|
||||
marvin.setDisplaySettings({ toolbars: 'reporting' });
|
||||
marvinJsRemoteEditor = marvin;
|
||||
|
|
446
app/assets/javascripts/sitewide/tiny_mce.js
vendored
446
app/assets/javascripts/sitewide/tiny_mce.js
vendored
|
@ -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;
|
||||
});
|
||||
});
|
|
@ -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
|
||||
);
|
||||
}());
|
|
@ -1,11 +1,12 @@
|
|||
/* global tinymce */
|
||||
|
||||
tinymce.PluginManager.add('placeholder', function(editor) {
|
||||
var Label = function() {
|
||||
var editorForm = $(editor.getContainer()).closest('form');
|
||||
var editorToolbar = editorForm.find('.mce-top-part');
|
||||
var placeholderText = editor.getElement().getAttribute('placeholder') || editor.settings.placeholder;
|
||||
var placeholderAttrs = editor.settings.placeholder_attrs || {
|
||||
style: {
|
||||
var placeholderAttrs = {
|
||||
style: `
|
||||
position: 'absolute',
|
||||
top: (editorToolbar.height()) + 'px',
|
||||
left: 0,
|
||||
|
@ -14,7 +15,7 @@ tinymce.PluginManager.add('placeholder', function(editor) {
|
|||
width: 'calc(100% - 50px)',
|
||||
overflow: 'hidden',
|
||||
'white-space': 'pre-wrap'
|
||||
}
|
||||
`
|
||||
};
|
||||
var contentAreaContainer = editor.getContentAreaContainer();
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
@import "protocols/*";
|
||||
@import "dashboard/*";
|
||||
@import "repository/*";
|
||||
@import "experiment/*";
|
||||
@import "repository_columns/*";
|
||||
@import "label_templates/*";
|
||||
@import "reports/*";
|
||||
|
|
75
app/assets/stylesheets/experiment/canvas.scss
Normal file
75
app/assets/stylesheets/experiment/canvas.scss
Normal 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;
|
||||
}
|
||||
}
|
133
app/assets/stylesheets/experiment/show.scss
Normal file
133
app/assets/stylesheets/experiment/show.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
469
app/assets/stylesheets/experiment/table.scss
Normal file
469
app/assets/stylesheets/experiment/table.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.project-show-toolbar {
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
margin-right: .25em;
|
||||
}
|
||||
}
|
||||
|
||||
.content-header {
|
||||
.project-name {
|
||||
align-items: center;
|
||||
|
@ -541,3 +549,28 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tasks-no-results-container {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 8;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-results-img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-height: 230px;
|
||||
}
|
||||
|
||||
.no-results-title {
|
||||
@include font-h1;
|
||||
margin-bottom: .25em;
|
||||
margin-top: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-results-description {
|
||||
@include font-main;
|
||||
color: $color-silver-chalice;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -455,18 +455,19 @@
|
|||
}
|
||||
|
||||
.calendar-input {
|
||||
@include font-button;
|
||||
background-color: transparent !important;
|
||||
border-color: $color-silver-chalice;
|
||||
box-shadow: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding-left: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 34px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-silver-chalice;
|
||||
color: $color-alto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.inser-field-dropdown {
|
||||
.insert-field-dropdown {
|
||||
.dimensions-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
margin-top: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
.open-dropdown-button:not(.collapsed) {
|
||||
.fas {
|
||||
@include rotate(-180deg);
|
||||
|
@ -171,7 +180,12 @@
|
|||
display: flex;
|
||||
padding: 10px 10px 10px 24px;
|
||||
|
||||
.fas {
|
||||
.fas:not(.fa-plus-square) {
|
||||
margin-left: -1.25em;
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
.fa-plus-square {
|
||||
@include font-main;
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
|
@ -180,7 +194,7 @@
|
|||
&:hover {
|
||||
background-color: $color-concrete;
|
||||
|
||||
.fas {
|
||||
.fa-plus-square {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,17 +235,16 @@
|
|||
}
|
||||
|
||||
|
||||
#experiment-canvas {
|
||||
[data-view-mode="archived"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#module-archive {
|
||||
[data-view-mode="active"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.module-container {
|
||||
min-width: 220px;
|
||||
|
||||
|
|
|
@ -239,15 +239,21 @@
|
|||
}
|
||||
|
||||
.my-module-tags-color {
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
margin-right: 5px;
|
||||
width: 16px;
|
||||
|
||||
&.new {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
|
||||
.my-module-tags-create-new {
|
||||
opacity: .6;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
|
|
|
@ -21,7 +21,7 @@ $color-module-hover: $brand-primary;
|
|||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
#edit-canvas-button {
|
||||
#edit-canvas-button, .new-my-module-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
@ -48,9 +48,8 @@ $color-module-hover: $brand-primary;
|
|||
}
|
||||
|
||||
#update-canvas {
|
||||
|
||||
.canvas-header {
|
||||
margin-bottom: 5px;
|
||||
padding: 1em 2em;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +88,14 @@ $color-module-hover: $brand-primary;
|
|||
overflow: hidden;
|
||||
// for IE10+ touch devices
|
||||
touch-action: none;
|
||||
|
||||
.empty-canvas {
|
||||
color: $color-volcano;
|
||||
display: flex;
|
||||
font-size: 22px;
|
||||
justify-content: center;
|
||||
margin-top: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.diagram {
|
||||
|
@ -542,6 +549,26 @@ li.module-hover {
|
|||
margin-right: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#manage-module-tags-modal-intro {
|
||||
padding-left: 15px;
|
||||
border-top: 0;
|
||||
width: 568px;
|
||||
height: 30px;
|
||||
|
||||
font-family: 'Lato';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
|
||||
color: $color-volcano;
|
||||
|
||||
flex: none;
|
||||
order: 1;
|
||||
align-self: stretch;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -609,17 +636,6 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-option.users-dropdown-list {
|
||||
padding: 8px 10px;
|
||||
|
||||
.item-avatar {
|
||||
border-radius: 50%;
|
||||
height: 32px;
|
||||
margin: 0 16px 0 0;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.projects-toolbar {
|
||||
|
|
|
@ -303,7 +303,6 @@
|
|||
#dropdownAssetContextMenu {
|
||||
background: $color-white;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
width: 2em;
|
||||
justify-content: left;
|
||||
width: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,6 +111,7 @@
|
|||
|
||||
.checkbox-cell {
|
||||
grid-column: 1;
|
||||
justify-content: center;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
top: var(--navbar-height);
|
||||
transition: width .3s;
|
||||
width: 0;
|
||||
z-index: 1000;
|
||||
z-index: 10000;
|
||||
|
||||
&.open {
|
||||
width: var(--comments-sidebar-width);
|
||||
|
@ -131,6 +131,10 @@
|
|||
.update-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.send-comment {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: auto;
|
||||
min-width: 190px;
|
||||
|
||||
.divider-label {
|
||||
@include font-small;
|
||||
|
@ -74,18 +74,41 @@
|
|||
padding: .25em 1em;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 1em;
|
||||
padding: .5em 1em;
|
||||
white-space: nowrap;
|
||||
|
||||
.button-icon {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover:not(.divider-label) {
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
margin: -1em;
|
||||
padding: .5em 1em;
|
||||
width: calc(100% + 2em);
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
line-height: 28px;
|
||||
min-width: 0;
|
||||
outline: 0;
|
||||
padding: 0 0 0 5px;
|
||||
padding: 0 0 0 10px;
|
||||
|
||||
&::placeholder {
|
||||
opacity: .7;
|
||||
|
|
|
@ -44,6 +44,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item-avatar {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dropdown-option.users-dropdown-list {
|
||||
padding: 8px 10px;
|
||||
|
||||
.item-avatar {
|
||||
height: 32px;
|
||||
margin: 0 16px 0 0;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.recent-searches {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
|
|
@ -23,6 +23,7 @@ $font-fas-angle-double-left: "\f100";
|
|||
$font-fas-angle-double-right: "\f101";
|
||||
$font-fas-exclamation-circle: "\f06a";
|
||||
$font-fas-caret-up: "\f0d8";
|
||||
$font-fas-plus: "\f02b";
|
||||
|
||||
@mixin font-h1 {
|
||||
font-size: 24px;
|
||||
|
|
|
@ -75,7 +75,8 @@
|
|||
}
|
||||
|
||||
.change-projects-view-type-form,
|
||||
.change-experiments-view-type-form {
|
||||
.change-experiments-view-type-form,
|
||||
.change-my-modules-view-type-form {
|
||||
.button-to {
|
||||
float: unset !important;
|
||||
height: 48px;
|
||||
|
|
|
@ -81,6 +81,10 @@
|
|||
&.error {
|
||||
padding-bottom: 6px;
|
||||
|
||||
label {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
.sci-input-field {
|
||||
border: $border-danger;
|
||||
}
|
||||
|
|
|
@ -95,6 +95,11 @@ input[type="checkbox"].sci-toggle-checkbox {
|
|||
transition: .2s;
|
||||
width: 48px;
|
||||
|
||||
svg,
|
||||
svg path {
|
||||
fill: $color-black;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
border-left-color: $color-silver-chalice;
|
||||
border-radius: $border-radius-default 0 0 $border-radius-default;
|
||||
|
@ -109,6 +114,11 @@ input[type="checkbox"].sci-toggle-checkbox {
|
|||
background: $brand-primary;
|
||||
border: 1px solid $brand-primary;
|
||||
color: $color-white;
|
||||
|
||||
svg,
|
||||
svg path {
|
||||
fill: $color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,11 @@
|
|||
|
||||
.step {
|
||||
.panel {
|
||||
border: 0;
|
||||
margin-left: 0;
|
||||
|
||||
.panel-body {
|
||||
padding: 15px 5px;
|
||||
padding: 15px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
.enable-edit-mode {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
opacity: 0;
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
@ -49,6 +49,7 @@
|
|||
|
||||
.enable-edit-mode {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
$color-concrete 100%
|
||||
);
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
padding-left: 2em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
@ -55,6 +55,7 @@
|
|||
|
||||
.buttons-container {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.step-element-grip {
|
||||
|
|
|
@ -201,16 +201,6 @@ mark,.mark {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
a[data-toggle="tooltip"] {
|
||||
color: inherit;
|
||||
border-bottom: 1px dashed $color-emperor;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
margin-bottom: 15px;
|
||||
|
||||
|
@ -686,47 +676,6 @@ ul.double-line > li {
|
|||
}
|
||||
}
|
||||
|
||||
#canvas-container,
|
||||
#module-archive {
|
||||
.panel-heading {
|
||||
padding: 10px 15px 4px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 6px 15px;
|
||||
|
||||
.status-label {
|
||||
background-color: var(--state-color);
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
margin: 3px 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 2px 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
.nav > li > a {
|
||||
padding: 6px 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.badge-indicator {
|
||||
background: $brand-accent;
|
||||
border-radius: $border-radius-tag;
|
||||
color: $color-black;
|
||||
font-size: 10px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-options {
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// scss-lint:disable ImportantRule
|
||||
// scss-lint:disable ImportantRule SelectorDepth
|
||||
@import "constants";
|
||||
|
||||
@font-face {
|
||||
|
@ -22,6 +22,11 @@
|
|||
color: $color-silver-chalice;
|
||||
content: attr(data-placeholder);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mce-tinymce {
|
||||
|
@ -38,6 +43,16 @@
|
|||
position: relative !important;
|
||||
}
|
||||
|
||||
.tox.tox-tinymce {
|
||||
left: -100000px;
|
||||
position: absolute;
|
||||
|
||||
&.tox-tinymce--loaded {
|
||||
left: 0;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.tinymce-placeholder {
|
||||
background: $color-concrete;
|
||||
opacity: .7;
|
||||
|
@ -45,8 +60,15 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.tinymce-save-button,
|
||||
.tinymce-cancel-button {
|
||||
.tox-edit-area {
|
||||
label {
|
||||
color: $color-silver-chalice !important;
|
||||
padding: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tinymce-save-button.tox-mbtn,
|
||||
.tinymce-cancel-button.tox-mbtn {
|
||||
cursor: pointer;
|
||||
|
||||
.fas {
|
||||
|
@ -54,6 +76,15 @@
|
|||
font-weight: 900;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tinymce-save-controls {
|
||||
display: flex;
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
.tinymce-status-badge {
|
||||
|
@ -69,18 +100,17 @@
|
|||
background: $color-white !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.restore-draft-notification {
|
||||
align-items: center;
|
||||
background: $state-info-bg !important;
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
height: 30px !important;
|
||||
padding: 0 10px !important;
|
||||
padding: 10px !important;
|
||||
|
||||
.notification-text {
|
||||
flex-grow: 1;
|
||||
max-width: 75%;
|
||||
max-width: 85%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
@ -152,25 +182,6 @@
|
|||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mce-container-body.mce-abs-layout {
|
||||
background: $brand-primary;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
|
||||
.mce-container,
|
||||
.mce-widget {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.mce-btn:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.mce-ico {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mce-window {
|
||||
|
@ -193,4 +204,62 @@
|
|||
}
|
||||
}
|
||||
|
||||
// scss-lint:enable ImportantRule
|
||||
// fix for TinyMCE 6 vs Boostrap 3 .show conflict
|
||||
.tox.tox-tinymce.show {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.tox .tox-pop {
|
||||
margin-top: -12px;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tox-pop__dialog {
|
||||
border: 0;
|
||||
border-radius: 0 0 3px 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tox-toolbar {
|
||||
background: $brand-primary !important;
|
||||
top: -10px;
|
||||
|
||||
button {
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.tox-icon svg {
|
||||
fill: $color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tox-edit-area__iframe {
|
||||
background-color: transparent !important;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tox-sidebar-wrap {
|
||||
flex-direction: column !important;
|
||||
|
||||
.restore-draft-notification {
|
||||
flex-basis: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.tox-editor-header {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
.tox-dialog-wrap {
|
||||
.tox-dialog__body-nav {
|
||||
.tox-dialog__body-nav-item:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scss-lint:enable ImportantRule SelectorDepth
|
||||
|
|
|
@ -3,8 +3,16 @@
|
|||
module Api
|
||||
module V1
|
||||
class UsersController < BaseController
|
||||
before_action :load_team, only: :index
|
||||
before_action :load_user, only: :show
|
||||
|
||||
def index
|
||||
users = @team.users
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
render jsonapi: users, each_serializer: UserSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi: @user, serializer: UserSerializer
|
||||
end
|
||||
|
|
|
@ -218,7 +218,7 @@ class AssetsController < ApplicationController
|
|||
log_step_activity(
|
||||
:task_step_file_deleted,
|
||||
@assoc,
|
||||
@assoc.my_module.experiment.project,
|
||||
@assoc.my_module.project,
|
||||
my_module: @assoc.my_module.id,
|
||||
file: @asset.file_name
|
||||
)
|
||||
|
@ -298,7 +298,7 @@ class AssetsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: step.protocol,
|
||||
team: current_team,
|
||||
team: step.protocol.team,
|
||||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
@ -308,8 +308,8 @@ class AssetsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: result,
|
||||
team: result.my_module.experiment.project.team,
|
||||
project: result.my_module.experiment.project,
|
||||
team: result.my_module.team,
|
||||
project: result.my_module.project,
|
||||
message_items: {
|
||||
result: result.id,
|
||||
type_of_result: t('activities.result_type.text')
|
||||
|
|
|
@ -41,8 +41,8 @@ module AssetsActions
|
|||
.call(activity_type: :edit_image_on_result,
|
||||
owner: current_user,
|
||||
subject: asset.result,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
team: my_module.team,
|
||||
project: my_module.project,
|
||||
message_items: {
|
||||
result: asset.result.id,
|
||||
asset_name: { id: asset.id, value_for: 'file_name' },
|
||||
|
|
|
@ -104,8 +104,8 @@ module BioEddieActions
|
|||
.call(activity_type: "#{activity}_molecule_on_result".to_sym,
|
||||
owner: current_user,
|
||||
subject: result,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
team: my_module.team,
|
||||
project: my_module.project,
|
||||
message_items: message_items)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,8 +118,8 @@ module MarvinJsActions
|
|||
.call(activity_type: (activity + '_chemical_structure_on_result').to_sym,
|
||||
owner: current_user,
|
||||
subject: result,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
team: my_module.team,
|
||||
project: my_module.project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
||||
|
@ -137,8 +137,8 @@ module MarvinJsActions
|
|||
.call(activity_type: (activity + '_chemical_structure_on_task').to_sym,
|
||||
owner: current_user,
|
||||
subject: my_module,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
team: my_module.team,
|
||||
project: my_module.project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
||||
|
|
|
@ -55,6 +55,17 @@ module StepsActions
|
|||
)
|
||||
end
|
||||
|
||||
def step_text_annotation(step, step_text, old_text = nil)
|
||||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
new_text: step_text.text,
|
||||
title: t('notifications.step_text_title',
|
||||
user: current_user.full_name,
|
||||
step: step.name),
|
||||
message: annotation_message(step)
|
||||
)
|
||||
end
|
||||
|
||||
def checklist_name_annotation(step, checklist, old_text = nil)
|
||||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
|
@ -85,7 +96,7 @@ module StepsActions
|
|||
),
|
||||
experiment: link_to(
|
||||
step.my_module.experiment.name,
|
||||
canvas_experiment_url(step.my_module.experiment)
|
||||
my_modules_experiment_url(step.my_module.experiment)
|
||||
),
|
||||
my_module: link_to(
|
||||
step.my_module.name,
|
||||
|
|
|
@ -12,12 +12,12 @@ class ExperimentsController < ApplicationController
|
|||
before_action :check_read_permissions, except: %i(edit archive clone move new create archive_group restore_group)
|
||||
before_action :check_canvas_read_permissions, only: %i(canvas)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: %i(edit)
|
||||
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
|
||||
before_action :check_update_permissions, only: %i(update)
|
||||
before_action :check_archive_permissions, only: :archive
|
||||
before_action :check_clone_permissions, only: %i(clone_modal clone)
|
||||
before_action :check_move_permissions, only: %i(move_modal move)
|
||||
before_action :set_inline_name_editing, only: %i(canvas module_archive)
|
||||
before_action :set_inline_name_editing, only: %i(canvas table module_archive)
|
||||
|
||||
layout 'fluid'
|
||||
|
||||
|
@ -46,7 +46,7 @@ class ExperimentsController < ApplicationController
|
|||
experiment: @experiment.name)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { path: canvas_experiment_url(@experiment) }, status: :ok
|
||||
render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -88,6 +88,37 @@ class ExperimentsController < ApplicationController
|
|||
.select('my_modules.*').group(:id)
|
||||
end
|
||||
|
||||
def table
|
||||
@project = @experiment.project
|
||||
@experiment.current_view_state(current_user)
|
||||
@my_module_visible_table_columns = current_user.my_module_visible_table_columns
|
||||
end
|
||||
|
||||
def load_table
|
||||
my_modules = @experiment.my_modules.readable_by_user(current_user)
|
||||
|
||||
unless @experiment.archived_branch?
|
||||
my_modules = params[:view_mode] == 'archived' ? my_modules.archived : my_modules.active
|
||||
end
|
||||
|
||||
render json: Experiments::TableViewService.new(@experiment, my_modules, current_user, params).call
|
||||
end
|
||||
|
||||
def my_modules
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_type = view_state.state['my_modules']['view_type'] || 'canvas'
|
||||
|
||||
redirect_to view_mode_redirect_url(view_type)
|
||||
end
|
||||
|
||||
def view_type
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_state.state['my_modules']['view_type'] = view_type_params
|
||||
view_state.save!
|
||||
|
||||
redirect_to view_mode_redirect_url(view_type_params)
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
@ -208,7 +239,8 @@ class ExperimentsController < ApplicationController
|
|||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'clone_modal.html.erb'
|
||||
partial: 'clone_modal.html.erb',
|
||||
locals: { view_mode: params[:view_mode] }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -249,6 +281,21 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def search_tags
|
||||
tags = @experiment.project.tags.where.not(id: JSON.parse(params[:selected_tags]))
|
||||
.where_attributes_like(:name, params[:query])
|
||||
.select(:id, :name, :color)
|
||||
|
||||
tags = tags.map do |tag|
|
||||
{ value: tag.id, label: sanitize_input(tag.name), params: { color: sanitize_input(tag.color) } }
|
||||
end
|
||||
|
||||
if params[:query].present? && tags.select { |tag| tag[:label] == params[:query] }.blank?
|
||||
tags << { value: 0, label: sanitize_input(params[:query]), params: { color: nil } }
|
||||
end
|
||||
render json: tags
|
||||
end
|
||||
|
||||
# POST: move_experiment(id)
|
||||
def move
|
||||
service = Experiments::MoveToProjectService
|
||||
|
@ -258,8 +305,10 @@ class ExperimentsController < ApplicationController
|
|||
if service.succeed?
|
||||
flash[:success] = t('experiments.move.success_flash',
|
||||
experiment: @experiment.name)
|
||||
path = canvas_experiment_url(@experiment)
|
||||
status = :ok
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_type = view_state.state['my_modules']['view_type'] || 'canvas'
|
||||
path = view_mode_redirect_url(view_type)
|
||||
else
|
||||
message = service.errors.values.join(', ')
|
||||
status = :unprocessable_entity
|
||||
|
@ -268,7 +317,53 @@ class ExperimentsController < ApplicationController
|
|||
render json: { message: message, path: path }, status: status
|
||||
end
|
||||
|
||||
def move_modules_modal
|
||||
@experiments = @experiment.project.experiments.active.where.not(id: @experiment)
|
||||
.managable_by_user(current_user).order(name: :asc)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'move_modules_modal.html.erb'
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def move_modules
|
||||
modules_to_move = {}
|
||||
dst_experiment = @experiment.project.experiments.find(params[:to_experiment_id])
|
||||
return render_403 unless can_manage_experiment?(dst_experiment)
|
||||
|
||||
@experiment.transaction do
|
||||
params[:my_module_ids].each do |id|
|
||||
my_module = @experiment.my_modules.find(id)
|
||||
return render_403 unless can_move_my_module?(my_module)
|
||||
|
||||
modules_to_move[id] = dst_experiment.id
|
||||
end
|
||||
# Make sure that locks are acquired always in the same order
|
||||
if dst_experiment.id < @experiment.id
|
||||
dst_experiment.lock! && @experiment.lock!
|
||||
else
|
||||
@experiment.lock! && dst_experiment.lock!
|
||||
end
|
||||
@experiment.move_modules(modules_to_move, current_user)
|
||||
@experiment.workflowimg.purge
|
||||
|
||||
render json: { message: t('experiments.table.modal_move_modules.success_flash',
|
||||
experiment: sanitize_input(dst_experiment.name)) }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
render json: {
|
||||
message: t('experiments.table.modal_move_modules.error_flash', experiment: sanitize_input(dst_experiment.name))
|
||||
}, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def module_archive
|
||||
@project = @experiment.project
|
||||
@my_modules = @experiment.archived_branch? ? @experiment.my_modules : @experiment.my_modules.archived
|
||||
@my_modules = @my_modules.with_granted_permissions(current_user, MyModulePermissions::READ_ARCHIVED)
|
||||
.left_outer_joins(:designated_users, :task_comments)
|
||||
|
@ -299,11 +394,27 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
def sidebar
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_mode = params[:view_mode].presence || 'active'
|
||||
default_sort = view_state.state.dig('my_modules', view_mode, 'sort') || 'atoz'
|
||||
my_modules = if @experiment.archived_branch?
|
||||
@experiment.my_modules
|
||||
elsif params[:view_mode] == 'archived'
|
||||
@experiment.my_modules.archived
|
||||
else
|
||||
@experiment.my_modules.active
|
||||
end
|
||||
|
||||
my_modules = sort_my_modules(my_modules, params[:sort].presence || default_sort)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/sidebar/my_modules.html.erb', locals: { experiment: @experiment }
|
||||
partial: if params[:view_mode] == 'archived'
|
||||
'shared/sidebar/archived_my_modules.html.erb'
|
||||
else
|
||||
'shared/sidebar/my_modules.html.erb'
|
||||
end, locals: { experiment: @experiment, my_modules: my_modules }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -321,6 +432,73 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def assigned_users_to_tasks
|
||||
users = current_team.users.where(id: @experiment.my_modules.joins(:user_my_modules).select(:user_id))
|
||||
.search(false, params[:query]).map do |u|
|
||||
{ value: u.id, label: sanitize_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } }
|
||||
end
|
||||
|
||||
render json: users, status: :ok
|
||||
end
|
||||
|
||||
def archive_my_modules
|
||||
my_modules = @experiment.my_modules.where(id: params[:my_modules])
|
||||
counter = 0
|
||||
my_modules.each do |my_module|
|
||||
next unless can_archive_my_module?(my_module)
|
||||
|
||||
my_module.transaction do
|
||||
connect_my_modules_before_archive(my_module)
|
||||
|
||||
my_module.archive!(current_user)
|
||||
log_my_module_activity(:archive_module, my_module)
|
||||
counter += 1
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
if counter.positive?
|
||||
render json: { message: t('experiments.table.archive_group.success_flash', number: counter) }
|
||||
else
|
||||
render json: { message: t('experiments.table.archive_group.error_flash') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def batch_clone_my_modules
|
||||
MyModule.transaction do
|
||||
@my_modules =
|
||||
@experiment.my_modules
|
||||
.readable_by_user(current_user)
|
||||
.where(id: params[:my_module_ids])
|
||||
|
||||
@my_modules.find_each do |my_module|
|
||||
new_my_module = my_module.dup
|
||||
new_my_module.my_module_status = MyModuleStatus.first
|
||||
new_my_module.update!(
|
||||
{
|
||||
provisioning_status: :in_progress,
|
||||
name: my_module.next_clone_name,
|
||||
created_by: current_user,
|
||||
due_date: nil,
|
||||
started_on: nil,
|
||||
state: 'uncompleted',
|
||||
completed_on: nil
|
||||
}.merge(new_my_module.get_new_position)
|
||||
)
|
||||
new_my_module.designated_users << current_user
|
||||
MyModules::CopyContentJob.perform_later(current_user, my_module.id, new_my_module.id)
|
||||
end
|
||||
@experiment.workflowimg.purge
|
||||
end
|
||||
|
||||
render(
|
||||
json: {
|
||||
provisioning_status_urls: @my_modules.map { |m| provisioning_status_my_module_url(m) }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_experiment
|
||||
|
@ -341,10 +519,14 @@ class ExperimentsController < ApplicationController
|
|||
params.require(:experiment).require(:project_id)
|
||||
end
|
||||
|
||||
def view_type_params
|
||||
params.require(:experiment).require(:view_type)
|
||||
end
|
||||
|
||||
def check_read_permissions
|
||||
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
|
||||
render_403 unless can_read_experiment?(@experiment) ||
|
||||
@experiment.archived? && can_read_archived_experiment?(@experiment)
|
||||
(@experiment.archived? && can_read_archived_experiment?(@experiment))
|
||||
end
|
||||
|
||||
def check_canvas_read_permissions
|
||||
|
@ -402,7 +584,7 @@ class ExperimentsController < ApplicationController
|
|||
project: link_to(@experiment.project.name,
|
||||
project_url(@experiment.project)),
|
||||
experiment: link_to(@experiment.name,
|
||||
canvas_experiment_url(@experiment)))
|
||||
my_modules_experiment_url(@experiment)))
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -410,9 +592,61 @@ class ExperimentsController < ApplicationController
|
|||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
team: experiment.project.team,
|
||||
team: experiment.team,
|
||||
project: experiment.project,
|
||||
subject: experiment,
|
||||
message_items: { experiment: experiment.id })
|
||||
end
|
||||
|
||||
def log_my_module_activity(type_of, my_module)
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
subject: my_module,
|
||||
message_items: { my_module: my_module.id })
|
||||
end
|
||||
|
||||
def view_mode_redirect_url(view_type)
|
||||
if params[:view_mode] == 'archived' || @experiment.archived_branch?
|
||||
case view_type
|
||||
when 'canvas'
|
||||
module_archive_experiment_path(@experiment)
|
||||
else
|
||||
table_experiment_path(@experiment, view_mode: :archived)
|
||||
end
|
||||
else
|
||||
view_type == 'canvas' ? canvas_experiment_path(@experiment) : table_experiment_path(@experiment)
|
||||
end
|
||||
end
|
||||
|
||||
def sort_my_modules(records, sort)
|
||||
case sort
|
||||
when 'due_first'
|
||||
records.order(:due_date, :name)
|
||||
when 'due_last'
|
||||
records.order(Arel.sql("COALESCE(due_date, DATE '2100-01-01') DESC"), :name)
|
||||
when 'atoz'
|
||||
records.order(:name)
|
||||
when 'ztoa'
|
||||
records.order(name: :desc)
|
||||
when 'archived_old'
|
||||
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) ASC'))
|
||||
when 'archived_new'
|
||||
records.order(Arel.sql('COALESCE(my_modules.archived_on, my_modules.archived_on) DESC'))
|
||||
else
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
def connect_my_modules_before_archive(my_module)
|
||||
return if my_module.my_modules.empty? || my_module.my_module_antecessors.empty?
|
||||
|
||||
my_module.my_modules.each do |destination_my_module|
|
||||
my_module.my_module_antecessors.each do |source_my_module|
|
||||
Connection.create!(input_id: destination_my_module.id, output_id: source_my_module.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class LabelTemplatesController < ApplicationController
|
|||
before_action :check_view_permissions, except: %i(create duplicate set_default delete update)
|
||||
before_action :check_manage_permissions, only: %i(create duplicate set_default delete update)
|
||||
before_action :load_label_templates, only: %i(index datatable)
|
||||
before_action :load_label_template, only: %i(show set_default update)
|
||||
before_action :load_label_template, only: %i(show set_default update template_tags)
|
||||
|
||||
layout 'fluid'
|
||||
|
||||
|
@ -125,7 +125,7 @@ class LabelTemplatesController < ApplicationController
|
|||
end
|
||||
|
||||
def template_tags
|
||||
render json: LabelTemplates::TagService.new(current_team).tags
|
||||
render json: LabelTemplates::TagService.new(current_team, @label_template).tags
|
||||
end
|
||||
|
||||
def zpl_preview
|
||||
|
|
|
@ -135,7 +135,7 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
activity_type: :export_inventory_items_assigned_to_task,
|
||||
owner: current_user,
|
||||
subject: @my_module,
|
||||
team: current_team,
|
||||
team: @repository.team,
|
||||
message_items: {
|
||||
my_module: @my_module.id,
|
||||
repository: @repository.id
|
||||
|
@ -240,7 +240,7 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_consumption_comment_annotation_message_html',
|
||||
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
|
||||
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
|
||||
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
|
||||
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
|
||||
)
|
||||
end
|
||||
|
@ -251,7 +251,7 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
owner: current_user,
|
||||
subject: @my_module,
|
||||
team: @repository.team,
|
||||
project: @my_module.experiment.project,
|
||||
project: @my_module.project,
|
||||
message_items: {
|
||||
repository: @repository.id,
|
||||
repository_row: module_repository_row.repository_row_id,
|
||||
|
|
|
@ -109,7 +109,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController
|
|||
activity_type: :export_inventory_snapshot_items_assigned_to_task,
|
||||
owner: current_user,
|
||||
subject: @my_module,
|
||||
team: current_team,
|
||||
team: @my_module.team,
|
||||
message_items: {
|
||||
my_module: @my_module.id,
|
||||
repository_snapshot: @repository_snapshot.id,
|
||||
|
|
|
@ -70,9 +70,8 @@ class MyModuleTagsController < ApplicationController
|
|||
.call(activity_type: :add_task_tag,
|
||||
owner: current_user,
|
||||
subject: my_module,
|
||||
project:
|
||||
my_module.experiment.project,
|
||||
team: current_team,
|
||||
project: my_module.project,
|
||||
team: my_module.team,
|
||||
message_items: {
|
||||
my_module: my_module.id,
|
||||
tag: @mt.tag.id
|
||||
|
@ -95,9 +94,8 @@ class MyModuleTagsController < ApplicationController
|
|||
.call(activity_type: :remove_task_tag,
|
||||
owner: current_user,
|
||||
subject: @mt.my_module,
|
||||
project:
|
||||
@mt.my_module.experiment.project,
|
||||
team: current_team,
|
||||
project: @mt.my_module.project,
|
||||
team: @mt.my_module.team,
|
||||
message_items: {
|
||||
my_module: @mt.my_module.id,
|
||||
tag: @mt.tag.id
|
||||
|
@ -139,9 +137,8 @@ class MyModuleTagsController < ApplicationController
|
|||
.call(activity_type: :remove_task_tag,
|
||||
owner: current_user,
|
||||
subject: tag.my_module,
|
||||
project:
|
||||
tag.my_module.experiment.project,
|
||||
team: current_team,
|
||||
project: tag.my_module.project,
|
||||
team: tag.my_module.team,
|
||||
message_items: {
|
||||
my_module: tag.my_module.id,
|
||||
tag: tag.tag.id
|
||||
|
|
|
@ -5,19 +5,69 @@ class MyModulesController < ApplicationController
|
|||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ApplicationHelper
|
||||
include MyModulesHelper
|
||||
|
||||
before_action :load_vars, except: %i(restore_group)
|
||||
before_action :load_vars, except: %i(restore_group create new save_table_state)
|
||||
before_action :load_experiment, only: %i(create new)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_archive_permissions, only: %i(update)
|
||||
before_action :check_manage_permissions, only: %i(
|
||||
description due_date update_description update_protocol_description update_protocol
|
||||
)
|
||||
before_action :check_read_permissions, except: %i(update update_description update_protocol_description restore_group)
|
||||
before_action :check_read_permissions, except: %i(create new update update_description
|
||||
update_protocol_description restore_group save_table_state)
|
||||
before_action :check_update_state_permissions, only: :update_state
|
||||
before_action :set_inline_name_editing, only: %i(protocols results activities archive)
|
||||
before_action :load_experiment_my_modules, only: %i(protocols results activities archive)
|
||||
|
||||
layout 'fluid'.freeze
|
||||
|
||||
def new
|
||||
@my_module = @experiment.my_modules.new
|
||||
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
|
||||
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'my_modules/modals/new_modal.html.erb', locals: { view_mode: params[:view_mode],
|
||||
users: assigned_users }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def create
|
||||
@my_module = @experiment.my_modules.new(my_module_params)
|
||||
new_pos = @my_module.get_new_position
|
||||
@my_module.assign_attributes(
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
x: new_pos[:x],
|
||||
y: new_pos[:y]
|
||||
)
|
||||
@my_module.transaction do
|
||||
if my_module_tags_params[:tag_ids].present?
|
||||
@my_module.tags << @experiment.project.tags.where(id: JSON.parse(my_module_tags_params[:tag_ids]))
|
||||
end
|
||||
if my_module_designated_users_params[:user_ids].present? && can_designate_users_to_new_task?(@experiment)
|
||||
@my_module.designated_users << @experiment.users.where(id: my_module_designated_users_params[:user_ids])
|
||||
elsif !can_designate_users_to_new_task?(@experiment)
|
||||
@my_module.designated_users << current_user
|
||||
end
|
||||
@my_module.save!
|
||||
Activities::CreateActivityService.call(
|
||||
activity_type: :create_module,
|
||||
owner: current_user,
|
||||
team: @my_module.experiment.project.team,
|
||||
project: @my_module.experiment.project,
|
||||
subject: @my_module,
|
||||
message_items: { my_module: @my_module.id }
|
||||
)
|
||||
redirect_to canvas_experiment_path(@experiment) if params[:my_module][:view_mode] == 'canvas'
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: @my_module.errors, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
|
@ -46,6 +96,11 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def save_table_state
|
||||
current_user.settings.update(visible_my_module_table_columns: params[:columns])
|
||||
current_user.save!
|
||||
end
|
||||
|
||||
def status_state
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
@ -195,6 +250,11 @@ class MyModulesController < ApplicationController
|
|||
partial: 'my_modules/card_due_date_label.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
table_due_date_label: {
|
||||
html: render_to_string(partial: 'experiments/table_due_date_label.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user }),
|
||||
due_status: my_module_due_status(@my_module)
|
||||
},
|
||||
module_header_due_date: render_to_string(
|
||||
partial: 'my_modules/module_header_due_date.html.erb',
|
||||
locals: { my_module: @my_module }
|
||||
|
@ -276,12 +336,14 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def update_protocol
|
||||
protocol = @my_module.protocol
|
||||
old_description = protocol.description
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
protocol.update!(protocol_params)
|
||||
log_activity(:protocol_name_in_task_edited) if protocol.saved_change_to_name?
|
||||
log_activity(:protocol_description_in_task_edited) if protocol.saved_change_to_description?
|
||||
TinyMceAsset.update_images(protocol, params[:tiny_mce_images], current_user)
|
||||
protocol_annotation_notification(old_description)
|
||||
end
|
||||
|
||||
render json: protocol, serializer: ProtocolSerializer, user: current_user
|
||||
|
@ -334,7 +396,12 @@ class MyModulesController < ApplicationController
|
|||
else
|
||||
flash[:error] = t('my_modules.restore_group.error_flash')
|
||||
end
|
||||
redirect_to module_archive_experiment_path(experiment)
|
||||
|
||||
if params[:view] == 'table'
|
||||
redirect_to table_experiment_path(experiment, view_mode: :archived)
|
||||
else
|
||||
redirect_to module_archive_experiment_path(experiment)
|
||||
end
|
||||
end
|
||||
|
||||
def update_state
|
||||
|
@ -349,6 +416,32 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def permissions
|
||||
if stale?(@my_module)
|
||||
render json: {
|
||||
editable: can_manage_my_module?(@my_module),
|
||||
moveable: can_move_my_module?(@my_module),
|
||||
archivable: can_archive_my_module?(@my_module),
|
||||
restorable: can_restore_my_module?(@my_module)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def actions_dropdown
|
||||
if stale?(@my_module)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'experiments/table_row_actions',
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def provisioning_status
|
||||
render json: { provisioning_status: @my_module.provisioning_status }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
|
@ -361,6 +454,11 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def load_experiment
|
||||
@experiment = Experiment.preload(user_assignments: %i(user user_role)).find_by(id: params[:id])
|
||||
render_404 unless @experiment
|
||||
end
|
||||
|
||||
def load_experiment_my_modules
|
||||
@experiment_my_modules = if @my_module.experiment.archived_branch?
|
||||
@my_module.experiment.my_modules.order(:name)
|
||||
|
@ -369,6 +467,10 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 && return unless can_manage_experiment?(@experiment)
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
render_403 && return unless can_manage_my_module?(@my_module)
|
||||
end
|
||||
|
@ -401,18 +503,26 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def my_module_params
|
||||
update_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
|
||||
permitted_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
|
||||
|
||||
if update_params[:started_on].present?
|
||||
update_params[:started_on] =
|
||||
Time.zone.strptime(update_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
|
||||
if permitted_params[:started_on].present?
|
||||
permitted_params[:started_on] =
|
||||
Time.zone.strptime(permitted_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
|
||||
end
|
||||
if update_params[:due_date].present?
|
||||
update_params[:due_date] =
|
||||
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
|
||||
if permitted_params[:due_date].present?
|
||||
permitted_params[:due_date] =
|
||||
Time.zone.strptime(permitted_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
|
||||
end
|
||||
|
||||
update_params
|
||||
permitted_params
|
||||
end
|
||||
|
||||
def my_module_tags_params
|
||||
params.require(:my_module).permit(:tag_ids)
|
||||
end
|
||||
|
||||
def my_module_designated_users_params
|
||||
params.require(:my_module).permit(user_ids: [])
|
||||
end
|
||||
|
||||
def protocol_params
|
||||
|
@ -458,8 +568,8 @@ class MyModulesController < ApplicationController
|
|||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
team: my_module.experiment.project.team,
|
||||
project: my_module.experiment.project,
|
||||
team: my_module.team,
|
||||
project: my_module.project,
|
||||
subject: my_module,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
@ -479,7 +589,7 @@ class MyModulesController < ApplicationController
|
|||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_description_annotation_message_html',
|
||||
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
|
||||
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
|
||||
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
|
||||
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
|
||||
)
|
||||
end
|
||||
|
@ -493,7 +603,7 @@ class MyModulesController < ApplicationController
|
|||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_protocol_annotation_message_html',
|
||||
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
|
||||
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
|
||||
experiment: link_to(@my_module.experiment.name, my_modules_experiment_url(@my_module.experiment)),
|
||||
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
|
||||
)
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
before_action :switch_team_with_param, only: :index
|
||||
before_action :load_vars, only: %i(show permissions edit update notifications
|
||||
sidebar experiments_cards view_type actions_dropdown)
|
||||
sidebar experiments_cards view_type actions_dropdown create_tag)
|
||||
before_action :load_current_folder, only: %i(index cards new show)
|
||||
before_action :check_view_permissions, except: %i(index cards new create edit update archive_group restore_group
|
||||
users_filter actions_dropdown)
|
||||
|
@ -264,6 +264,24 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def create_tag
|
||||
render_403 unless can_manage_project_tags?(@project)
|
||||
|
||||
@tag = @project.tags.create(tag_params.merge({
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
color: Constants::TAG_COLORS.sample
|
||||
}))
|
||||
|
||||
render json: {
|
||||
tag: {
|
||||
id: @tag.id,
|
||||
name: @tag.name,
|
||||
color: @tag.color
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def restore_group
|
||||
projects = current_team.projects.archived.where(id: params[:projects_ids])
|
||||
counter = 0
|
||||
|
@ -287,9 +305,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
# This is the "info" view
|
||||
current_team_switch(@project.team)
|
||||
|
||||
view_state = @project.current_view_state(current_user)
|
||||
@current_sort = view_state.state.dig('experiments', experiments_view_mode(@project), 'sort') || 'atoz'
|
||||
@current_view_type = view_state.state.dig('experiments', 'view_type')
|
||||
|
@ -372,11 +387,15 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def load_vars
|
||||
@project = Project.find_by(id: params[:id])
|
||||
@project = Project.find_by(id: params[:id] || params[:project_id])
|
||||
|
||||
render_404 unless @project
|
||||
end
|
||||
|
||||
def tag_params
|
||||
params.require(:tag).permit(:name)
|
||||
end
|
||||
|
||||
def load_current_folder
|
||||
if current_team && params[:project_folder_id].present?
|
||||
@current_folder = current_team.project_folders.find_by(id: params[:project_folder_id])
|
||||
|
@ -386,6 +405,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def check_view_permissions
|
||||
current_team_switch(@project.team) if current_team != @project.team
|
||||
render_403 unless can_read_project?(@project)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
class ProtocolsController < ApplicationController
|
||||
include RenamingUtil
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
@ -16,6 +15,7 @@ class ProtocolsController < ApplicationController
|
|||
before_action :check_clone_permissions, only: [:clone]
|
||||
before_action :check_view_permissions, only: %i(
|
||||
show
|
||||
edit
|
||||
protocol_status_bar
|
||||
updated_at_label
|
||||
preview
|
||||
|
@ -30,7 +30,6 @@ class ProtocolsController < ApplicationController
|
|||
# For update_from_parent and update_from_parent_modal we don't need to check
|
||||
# read permission for the parent protocol
|
||||
before_action :check_manage_permissions, only: %i(
|
||||
edit
|
||||
update_keywords
|
||||
update_description
|
||||
update_name
|
||||
|
@ -161,14 +160,10 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
# Switch to correct team
|
||||
current_team_switch(@protocol.team)
|
||||
render :show
|
||||
end
|
||||
|
||||
def show
|
||||
# Switch to correct team
|
||||
current_team_switch(@protocol.team)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @protocol, serializer: ProtocolSerializer, user: current_user }
|
||||
format.html
|
||||
|
@ -256,9 +251,7 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def delete_steps
|
||||
@protocol.my_module.lock!
|
||||
|
||||
Protocol.transaction do
|
||||
@protocol.with_lock do
|
||||
team = @protocol.team
|
||||
previous_size = 0
|
||||
@protocol.steps.each do |step|
|
||||
|
@ -276,7 +269,6 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
# skip adjusting positions after destroy as this is a bulk delete
|
||||
step.skip_position_adjust = true
|
||||
|
||||
step.destroy!
|
||||
end
|
||||
|
||||
|
@ -622,7 +614,7 @@ class ProtocolsController < ApplicationController
|
|||
.call(activity_type: :import_protocol_in_repository,
|
||||
owner: current_user,
|
||||
subject: protocol,
|
||||
team: current_team,
|
||||
team: protocol.team,
|
||||
message_items: {
|
||||
protocol: protocol.id
|
||||
})
|
||||
|
@ -820,15 +812,15 @@ class ProtocolsController < ApplicationController
|
|||
file_name = 'protocols.eln'
|
||||
end
|
||||
|
||||
@protocols.each do |p|
|
||||
@protocols.each do |protocol|
|
||||
if params[:my_module_id]
|
||||
my_module = MyModule.find(params[:my_module_id])
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: :export_protocol_from_task,
|
||||
owner: current_user,
|
||||
project: my_module.experiment.project,
|
||||
project: my_module.project,
|
||||
subject: my_module,
|
||||
team: current_team,
|
||||
team: my_module.team,
|
||||
message_items: {
|
||||
my_module: params[:my_module_id].to_i
|
||||
})
|
||||
|
@ -836,10 +828,10 @@ class ProtocolsController < ApplicationController
|
|||
Activities::CreateActivityService
|
||||
.call(activity_type: :export_protocol_in_repository,
|
||||
owner: current_user,
|
||||
subject: p,
|
||||
team: current_team,
|
||||
subject: protocol,
|
||||
team: protocol.team,
|
||||
message_items: {
|
||||
protocol: p.id
|
||||
protocol: protocol.id
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -1102,6 +1094,7 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def check_view_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
current_team_switch(@protocol.team) if current_team != @protocol.team
|
||||
unless @protocol.present? &&
|
||||
(can_read_protocol_in_module?(@protocol) ||
|
||||
can_read_protocol_in_repository?(@protocol))
|
||||
|
@ -1232,7 +1225,7 @@ class ProtocolsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @protocol,
|
||||
team: current_team,
|
||||
team: @protocol.team,
|
||||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
@ -1245,7 +1238,7 @@ class ProtocolsController < ApplicationController
|
|||
user: current_user.full_name,
|
||||
protocol: @protocol.name),
|
||||
message: t('notifications.protocol_description_annotation_message_html',
|
||||
protocol: link_to(@protocol.name, edit_protocol_url(@protocol)))
|
||||
protocol: link_to(@protocol.name, protocol_url(@protocol)))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ class ReportsController < ApplicationController
|
|||
@project_contents = {
|
||||
experiments: @report.report_elements.order(:position).experiment.pluck(:experiment_id),
|
||||
my_modules: @report.report_elements.order(:position).my_module.pluck(:my_module_id),
|
||||
repositories: @report.report_elements.my_module_repository.distinct(:repository_id).pluck(:repository_id)
|
||||
repositories: @report.settings.dig(:task, :repositories)
|
||||
}
|
||||
render :new
|
||||
end
|
||||
|
|
|
@ -521,7 +521,7 @@ class RepositoriesController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @repository,
|
||||
team: current_team,
|
||||
team: @repository.team,
|
||||
message_items: message_items)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -167,7 +167,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @repository,
|
||||
team: current_team,
|
||||
team: @repository.team,
|
||||
message_items: {
|
||||
repository_column: @repository_column.id,
|
||||
repository: @repository.id
|
||||
|
|
|
@ -432,7 +432,7 @@ class RepositoryRowsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: repository_row,
|
||||
team: current_team,
|
||||
team: @repository.team,
|
||||
message_items: {
|
||||
repository_row: repository_row.id,
|
||||
repository: @repository.id
|
||||
|
|
|
@ -191,8 +191,8 @@ class ResultAssetsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: result,
|
||||
team: @my_module.experiment.project.team,
|
||||
project: @my_module.experiment.project,
|
||||
team: @my_module.team,
|
||||
project: @my_module.project,
|
||||
message_items: {
|
||||
result: result.id,
|
||||
type_of_result: t('activities.result_type.asset')
|
||||
|
|
|
@ -180,8 +180,8 @@ class ResultTablesController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @result,
|
||||
team: @my_module.experiment.project.team,
|
||||
project: @my_module.experiment.project,
|
||||
team: @my_module.team,
|
||||
project: @my_module.project,
|
||||
message_items: {
|
||||
result: @result.id,
|
||||
type_of_result: t('activities.result_type.table')
|
||||
|
|
|
@ -192,8 +192,8 @@ class ResultTextsController < ApplicationController
|
|||
.experiment
|
||||
.project)),
|
||||
experiment: link_to(@result.my_module.experiment.name,
|
||||
canvas_experiment_url(@result.my_module
|
||||
.experiment)),
|
||||
my_modules_experiment_url(@result.my_module
|
||||
.experiment)),
|
||||
my_module: link_to(@result.my_module.name,
|
||||
protocols_my_module_url(
|
||||
@result.my_module
|
||||
|
@ -206,8 +206,8 @@ class ResultTextsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @result,
|
||||
team: @my_module.experiment.project.team,
|
||||
project: @my_module.experiment.project,
|
||||
team: @my_module.team,
|
||||
project: @my_module.project,
|
||||
message_items: {
|
||||
result: @result.id,
|
||||
type_of_result: t('activities.result_type.text')
|
||||
|
|
|
@ -14,8 +14,8 @@ class ResultsController < ApplicationController
|
|||
.call(activity_type: :destroy_result,
|
||||
owner: current_user,
|
||||
subject: @result,
|
||||
team: @my_module.experiment.project.team,
|
||||
project: @my_module.experiment.project,
|
||||
team: @my_module.team,
|
||||
project: @my_module.project,
|
||||
message_items: { result: @result.id,
|
||||
type_of_result: result_type })
|
||||
flash[:success] = t('my_modules.module_archive.delete_flash',
|
||||
|
|
|
@ -78,8 +78,8 @@ class StepCommentsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @protocol,
|
||||
team: current_team,
|
||||
project: @step.my_module.experiment.project,
|
||||
team: @step.my_module.team,
|
||||
project: @step.my_module.project,
|
||||
message_items: {
|
||||
my_module: @step.my_module.id,
|
||||
step: @step.id,
|
||||
|
|
|
@ -40,8 +40,8 @@ module StepElements
|
|||
Activities::CreateActivityService.call(
|
||||
activity_type: "#{!@step.protocol.in_module? ? 'protocol_step_' : 'task_step_'}#{element_type_of}",
|
||||
owner: current_user,
|
||||
team: @protocol.in_module? ? @protocol.my_module.experiment.project.team : @protocol.team,
|
||||
project: @protocol.in_module? ? @protocol.my_module.experiment.project : nil,
|
||||
team: @protocol.team,
|
||||
project: @protocol.in_module? ? @protocol.my_module.project : nil,
|
||||
subject: @protocol,
|
||||
message_items: {
|
||||
step: @step.id,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module StepElements
|
||||
class ChecklistItemsController < ApplicationController
|
||||
include ApplicationHelper
|
||||
include StepsActions
|
||||
|
||||
before_action :load_vars
|
||||
before_action :load_checklist_item, only: %i(update toggle destroy)
|
||||
|
@ -21,6 +22,7 @@ module StepElements
|
|||
checklist_name: @checklist.name
|
||||
}
|
||||
)
|
||||
checklist_item_annotation(@step, checklist_item)
|
||||
end
|
||||
|
||||
render json: checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||
|
@ -31,6 +33,7 @@ module StepElements
|
|||
end
|
||||
|
||||
def update
|
||||
old_text = @checklist_item.text
|
||||
@checklist_item.assign_attributes(
|
||||
checklist_item_params.merge(last_modified_by: current_user)
|
||||
)
|
||||
|
@ -41,6 +44,7 @@ module StepElements
|
|||
checklist_item: @checklist_item.text,
|
||||
checklist_name: @checklist.name
|
||||
)
|
||||
checklist_item_annotation(@step, @checklist_item, old_text)
|
||||
end
|
||||
|
||||
render json: @checklist_item, serializer: ChecklistItemSerializer, user: current_user
|
||||
|
@ -129,6 +133,8 @@ module StepElements
|
|||
@step = Step.find_by(id: params[:step_id])
|
||||
return render_404 unless @step
|
||||
|
||||
@protocol = @step.protocol
|
||||
|
||||
@checklist = @step.checklists.find_by(id: params[:checklist_id])
|
||||
return render_404 unless @checklist
|
||||
end
|
||||
|
@ -144,7 +150,7 @@ module StepElements
|
|||
owner: current_user,
|
||||
subject: @step.protocol,
|
||||
team: @step.protocol.team,
|
||||
project: @step.protocol.in_module? ? @step.protocol.my_module.experiment.project : nil,
|
||||
project: @step.protocol.in_module? ? @step.protocol.my_module.project : nil,
|
||||
message_items: message_items.merge(step_message_items)
|
||||
)
|
||||
end
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
module StepElements
|
||||
class ChecklistsController < BaseController
|
||||
include ApplicationHelper
|
||||
include StepsActions
|
||||
before_action :load_checklist, only: %i(update destroy duplicate)
|
||||
|
||||
def create
|
||||
checklist = @step.checklists.build(
|
||||
name: t('protocols.steps.checklist.default_name', position: @step.checklists.length + 1)
|
||||
|
@ -11,6 +12,7 @@ module StepElements
|
|||
ActiveRecord::Base.transaction do
|
||||
create_in_step!(@step, checklist)
|
||||
log_step_activity(:checklist_added, { checklist_name: checklist.name })
|
||||
checklist_name_annotation(@step, checklist)
|
||||
end
|
||||
render_step_orderable_element(checklist)
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
|
@ -18,9 +20,11 @@ module StepElements
|
|||
end
|
||||
|
||||
def update
|
||||
old_name = @checklist.name
|
||||
ActiveRecord::Base.transaction do
|
||||
@checklist.update!(checklist_params)
|
||||
log_step_activity(:checklist_edited, { checklist_name: @checklist.name })
|
||||
checklist_name_annotation(@step, @checklist, old_name)
|
||||
end
|
||||
|
||||
render json: @checklist, serializer: ChecklistSerializer, user: current_user
|
||||
|
@ -43,6 +47,7 @@ module StepElements
|
|||
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
|
||||
element.update(position: element.position + 1)
|
||||
end
|
||||
@checklist.name += ' (1)'
|
||||
new_checklist = @checklist.duplicate(@step, current_user, position + 1)
|
||||
log_step_activity(:checklist_duplicated, { checklist_name: @checklist.name })
|
||||
render_step_orderable_element(new_checklist)
|
||||
|
|
|
@ -48,6 +48,7 @@ module StepElements
|
|||
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
|
||||
element.update(position: element.position + 1)
|
||||
end
|
||||
@table.name += ' (1)'
|
||||
new_table = @table.duplicate(@step, current_user, position + 1)
|
||||
log_step_activity(:table_duplicated, { table_name: new_table.name })
|
||||
render_step_orderable_element(new_table.step_table)
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
module StepElements
|
||||
class TextsController < BaseController
|
||||
include ApplicationHelper
|
||||
include StepsActions
|
||||
|
||||
before_action :load_step_text, only: %i(update destroy duplicate)
|
||||
|
||||
def create
|
||||
|
@ -18,10 +21,12 @@ module StepElements
|
|||
end
|
||||
|
||||
def update
|
||||
old_text = @step_text.text
|
||||
ActiveRecord::Base.transaction do
|
||||
@step_text.update!(step_text_params)
|
||||
TinyMceAsset.update_images(@step_text, params[:tiny_mce_images], current_user)
|
||||
log_step_activity(:text_edited, { text_name: @step_text.name })
|
||||
step_text_annotation(@step, @step_text, old_text)
|
||||
end
|
||||
|
||||
render json: @step_text, serializer: StepTextSerializer, user: current_user
|
||||
|
|
|
@ -48,7 +48,7 @@ class StepOrderableElementsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @protocol,
|
||||
team: current_team,
|
||||
team: @protocol.team,
|
||||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
|
|
@ -38,7 +38,7 @@ class StepsController < ApplicationController
|
|||
@asset = @step.assets.create!(
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
team: current_team,
|
||||
team: @protocol.team,
|
||||
view_mode: @step.assets_view_mode
|
||||
)
|
||||
@asset.file.attach(params[:signed_blob_id])
|
||||
|
@ -323,7 +323,7 @@ class StepsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: @protocol,
|
||||
team: current_team,
|
||||
team: @protocol.team,
|
||||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
|
|
@ -168,7 +168,7 @@ class TagsController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: subject,
|
||||
team: current_team,
|
||||
team: @tag.project.team,
|
||||
project: @tag.project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
|
|
@ -97,7 +97,7 @@ class TeamRepositoriesController < ApplicationController
|
|||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
subject: team_shared_object.shared_repository,
|
||||
team: current_team,
|
||||
team: @repository.team,
|
||||
message_items: { repository: team_shared_object.shared_repository.id,
|
||||
team: team_shared_object.team.id,
|
||||
permission_level:
|
||||
|
|
|
@ -61,14 +61,22 @@ class UserMyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
user: {
|
||||
id: @um.user.id,
|
||||
full_name: @um.user.full_name,
|
||||
avatar_url: avatar_path(@um.user, :icon_small),
|
||||
user_module_id: @um.id
|
||||
}, status: :ok
|
||||
}
|
||||
if params[:table]
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user, skip_unassigned: false }),
|
||||
unassign_url: my_module_user_my_module_path(@my_module, @um)
|
||||
}
|
||||
else
|
||||
render json: {
|
||||
user: {
|
||||
id: @um.user.id,
|
||||
full_name: @um.user.full_name,
|
||||
avatar_url: avatar_path(@um.user, :icon_small),
|
||||
user_module_id: @um.id
|
||||
}, status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -88,7 +96,14 @@ class UserMyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {}, status: :ok
|
||||
if params[:table]
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/assigned_users.html.erb',
|
||||
locals: { my_module: @my_module, user: current_user, skip_unassigned: false })
|
||||
}
|
||||
else
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -104,19 +119,37 @@ class UserMyModulesController < ApplicationController
|
|||
|
||||
def search
|
||||
users = @my_module.users
|
||||
.where.not(id: @my_module.designated_users.select(:id))
|
||||
.joins("LEFT OUTER JOIN user_my_modules ON user_my_modules.user_id = users.id "\
|
||||
"AND user_my_modules.my_module_id = #{@my_module.id}")
|
||||
.search(false, params[:query])
|
||||
.order(:full_name)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select('users.*', 'user_my_modules.id as user_my_module_id')
|
||||
.select('CASE WHEN user_my_modules.id IS NOT NULL '\
|
||||
'THEN true ELSE false END as designated')
|
||||
|
||||
users = users.map do |user|
|
||||
{
|
||||
next if params[:skip_assigned] && user.designated
|
||||
next if ActiveModel::Type::Boolean.new.cast(params[:skip_unassigned]) && !user.designated
|
||||
|
||||
user_hash = {
|
||||
value: user.id,
|
||||
label: sanitize_input(user.full_name),
|
||||
params: { avatar_url: avatar_path(user, :icon_small) }
|
||||
params: {
|
||||
avatar_url: avatar_path(user, :icon_small),
|
||||
designated: user.designated,
|
||||
assign_url: my_module_user_my_modules_path(@my_module)
|
||||
}
|
||||
}
|
||||
|
||||
if user.designated
|
||||
user_hash[:params][:unassign_url] = my_module_user_my_module_path(@my_module, user.user_my_module_id)
|
||||
end
|
||||
|
||||
user_hash
|
||||
end
|
||||
|
||||
render json: users
|
||||
render json: users.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -157,8 +157,7 @@ module CommentHelper
|
|||
.experiment
|
||||
.project)),
|
||||
experiment: link_to(result.my_module.experiment.name,
|
||||
canvas_experiment_url(result.my_module
|
||||
.experiment)),
|
||||
my_modules_experiment_url(result.my_module.experiment)),
|
||||
my_module: link_to(result.my_module.name,
|
||||
protocols_my_module_url(
|
||||
result.my_module
|
||||
|
@ -193,8 +192,7 @@ module CommentHelper
|
|||
.experiment
|
||||
.project)),
|
||||
experiment: link_to(step.my_module.experiment.name,
|
||||
canvas_experiment_url(step.my_module
|
||||
.experiment)),
|
||||
my_modules_experiment_url(step.my_module.experiment)),
|
||||
my_module: link_to(step.my_module.name,
|
||||
protocols_my_module_url(
|
||||
step.my_module
|
||||
|
@ -218,8 +216,7 @@ module CommentHelper
|
|||
.experiment
|
||||
.project)),
|
||||
experiment: link_to(my_module.experiment.name,
|
||||
canvas_experiment_url(my_module
|
||||
.experiment)),
|
||||
my_modules_experiment_url(my_module.experiment)),
|
||||
my_module: link_to(my_module.name,
|
||||
protocols_my_module_url(
|
||||
my_module
|
||||
|
@ -278,4 +275,8 @@ module CommentHelper
|
|||
def has_unseen_comments?(commentable)
|
||||
commentable.comments.any? { |comment| comment.unseen_by.include?(current_user.id) }
|
||||
end
|
||||
|
||||
def count_unseen_comments(commentable, current_user)
|
||||
commentable.comments.count { |comment| comment.unseen_by.include?(current_user.id) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,12 +72,12 @@ module GlobalActivitiesHelper
|
|||
when Experiment
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : canvas_experiment_path(obj)
|
||||
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : my_modules_experiment_path(obj)
|
||||
when MyModule
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
path = if obj.archived?
|
||||
module_archive_experiment_path(obj.experiment)
|
||||
my_modules_experiment_path(obj.experiment, view_mode: :archived)
|
||||
else
|
||||
protocols_my_module_path(obj)
|
||||
end
|
||||
|
|
|
@ -28,11 +28,14 @@ module InputSanitizeHelper
|
|||
base64_encoded_imgs = options.fetch(:base64_encoded_imgs, false)
|
||||
text = sanitize_input(text, tags)
|
||||
text = simple_format(text, {}, format_opt) if simple_f
|
||||
if text =~ SmartAnnotations::TagToHtml::USER_REGEX || text =~ SmartAnnotations::TagToHtml::REGEX
|
||||
text = smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)
|
||||
end
|
||||
auto_link(
|
||||
custom_link_open_new_tab(smart_annotation_parser(text, team, base64_encoded_imgs, preview_repository)),
|
||||
text,
|
||||
html: { target: '_blank' },
|
||||
link: :urls,
|
||||
sanitize: false,
|
||||
html: { target: '_blank' }
|
||||
sanitize: false
|
||||
).html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,7 @@ module MyModulesHelper
|
|||
|
||||
def get_task_alert_color(my_module)
|
||||
alert = ''
|
||||
if my_module.active? && !my_module.completed?
|
||||
if !my_module.archived_branch? && !my_module.completed?
|
||||
alert = ' alert-yellow' if my_module.is_one_day_prior?
|
||||
alert = ' alert-red' if my_module.is_overdue?
|
||||
end
|
||||
|
@ -100,4 +100,16 @@ module MyModulesHelper
|
|||
my_module.experiment.project.archived_on
|
||||
end
|
||||
end
|
||||
|
||||
def my_module_due_status(my_module, datetime = DateTime.current)
|
||||
return if my_module.archived_branch? || my_module.completed?
|
||||
|
||||
if my_module.is_overdue?(datetime)
|
||||
I18n.t('my_modules.details.overdue')
|
||||
elsif my_module.is_one_day_prior?(datetime)
|
||||
I18n.t('my_modules.details.due_soon')
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def user_names_with_roles(user_assignments)
|
||||
user_assignments.map { |up| user_name_with_role(up) }.join('
').html_safe
|
||||
user_assignments.map { |up| user_name_with_role(up) }.join('
')
|
||||
end
|
||||
|
||||
def user_name_with_role(user_assignment)
|
||||
|
|
|
@ -6,22 +6,25 @@ module RepositoryDatatableHelper
|
|||
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
|
||||
has_stock_management = repository.has_stock_management?
|
||||
reminders_enabled = Repository.reminders_enabled?
|
||||
reminder_row_ids = reminders_enabled ? repository_reminder_row_ids(repository_rows, repository) : []
|
||||
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
|
||||
|
||||
repository_rows.map do |record|
|
||||
default_cells = public_send("#{repository.class.name.underscore}_default_columns", record)
|
||||
row = {
|
||||
row = public_send("#{repository.class.name.underscore}_default_columns", record)
|
||||
row.merge!(
|
||||
DT_RowId: record.id,
|
||||
DT_RowAttr: { 'data-state': row_style(record) },
|
||||
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(repository, record),
|
||||
hasActiveReminders: reminder_row_ids.include?(record.id),
|
||||
rowRemindersUrl:
|
||||
Rails.application.routes.url_helpers
|
||||
.active_reminder_repository_cells_repository_repository_row_url(
|
||||
repository,
|
||||
record
|
||||
)
|
||||
}.merge(default_cells)
|
||||
)
|
||||
|
||||
if reminders_enabled
|
||||
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
||||
end
|
||||
|
||||
if has_stock_management
|
||||
row['manageStockUrl'] = if record.has_stock?
|
||||
|
@ -100,7 +103,7 @@ module RepositoryDatatableHelper
|
|||
def prepare_simple_view_row_columns(repository_rows, repository, my_module, options = {})
|
||||
has_stock_management = repository.has_stock_management?
|
||||
reminders_enabled = Repository.reminders_enabled?
|
||||
reminder_row_ids = reminders_enabled ? repository_reminder_row_ids(repository_rows, repository) : []
|
||||
repository_rows = reminders_enabled ? with_reminders_status(repository_rows, repository) : repository_rows
|
||||
|
||||
repository_rows.map do |record|
|
||||
row = {
|
||||
|
@ -108,7 +111,6 @@ module RepositoryDatatableHelper
|
|||
DT_RowAttr: { 'data-state': row_style(record) },
|
||||
'0': escape_input(record.name),
|
||||
recordInfoUrl: Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record),
|
||||
hasActiveReminders: reminder_row_ids.include?(record.id),
|
||||
rowRemindersUrl:
|
||||
Rails.application.routes.url_helpers
|
||||
.active_reminder_repository_cells_repository_repository_row_url(
|
||||
|
@ -117,6 +119,10 @@ module RepositoryDatatableHelper
|
|||
)
|
||||
}
|
||||
|
||||
if reminders_enabled
|
||||
row['hasActiveReminders'] = record.has_active_stock_reminders || record.has_active_datetime_reminders
|
||||
end
|
||||
|
||||
if has_stock_management
|
||||
stock_present = record.repository_stock_cell.present?
|
||||
# Always disabled in a simple view
|
||||
|
@ -275,14 +281,33 @@ module RepositoryDatatableHelper
|
|||
''
|
||||
end
|
||||
|
||||
def repository_reminder_row_ids(repository_rows, repository)
|
||||
# don't load reminders for archived repositories
|
||||
return [] if repository_rows.blank? || repository.archived?
|
||||
def with_reminders_status(repository_rows, repository)
|
||||
# don't load reminders for archived repositories or snapshots
|
||||
if repository.archived? || repository.is_a?(RepositorySnapshot)
|
||||
return repository_rows.select('FALSE AS has_active_stock_reminders')
|
||||
.select('FALSE AS has_active_datetime_reminders')
|
||||
end
|
||||
|
||||
# don't load reminders for snapshots
|
||||
return [] if repository.is_a?(RepositorySnapshot)
|
||||
repository_cells = RepositoryCell.joins(
|
||||
"INNER JOIN repository_columns ON repository_columns.id = repository_cells.repository_column_id " \
|
||||
"AND repository_columns.repository_id = #{repository.id}"
|
||||
)
|
||||
|
||||
repository_rows.active.with_active_reminders(current_user).to_a.pluck(:id).uniq
|
||||
repository_rows
|
||||
.joins(
|
||||
"LEFT OUTER JOIN (#{RepositoryCell.stock_reminder_repository_cells_scope(repository_cells, current_user)
|
||||
.select(:id, :repository_row_id).to_sql}) " \
|
||||
"AS repository_cells_with_active_stock_reminders " \
|
||||
"ON repository_cells_with_active_stock_reminders.repository_row_id = repository_rows.id"
|
||||
)
|
||||
.joins(
|
||||
"LEFT OUTER JOIN (#{RepositoryCell.date_time_reminder_repository_cells_scope(repository_cells, current_user)
|
||||
.select(:id, :repository_row_id).to_sql}) " \
|
||||
"AS repository_cells_with_active_datetime_reminders " \
|
||||
"ON repository_cells_with_active_datetime_reminders.repository_row_id = repository_rows.id"
|
||||
)
|
||||
.select('COUNT(repository_cells_with_active_stock_reminders.id) > 0 AS has_active_stock_reminders')
|
||||
.select('COUNT(repository_cells_with_active_datetime_reminders.id) > 0 AS has_active_datetime_reminders')
|
||||
end
|
||||
|
||||
def stock_consumption_permitted?(repository, my_module)
|
||||
|
|
489
app/javascript/packs/tiny_mce.js
vendored
Normal file
489
app/javascript/packs/tiny_mce.js
vendored
Normal 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;
|
||||
});
|
1
app/javascript/packs/tiny_mce_styles.scss
Normal file
1
app/javascript/packs/tiny_mce_styles.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "tinymce/skins/ui/tinymce-5/skin.min.css";
|
122
app/javascript/packs/tinymce/custom_image_uploader/plugin.js
Normal file
122
app/javascript/packs/tinymce/custom_image_uploader/plugin.js
Normal 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'
|
||||
})
|
||||
};
|
||||
});
|
55
app/javascript/packs/tinymce/image_toolbar/plugin.js
Normal file
55
app/javascript/packs/tinymce/image_toolbar/plugin.js
Normal 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'
|
||||
});
|
||||
});
|
41
app/javascript/packs/tinymce/marvinjs/plugin.js
Normal file
41
app/javascript/packs/tinymce/marvinjs/plugin.js
Normal 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'
|
||||
})
|
||||
};
|
||||
});
|
|
@ -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">×</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
Loading…
Reference in a new issue