mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Merge branch 'develop' into features/sso-improvements
This commit is contained in:
commit
ac9c16d459
2
Gemfile
2
Gemfile
|
@ -12,7 +12,7 @@ gem 'figaro'
|
|||
gem 'pg', '~> 1.5'
|
||||
gem 'pg_search' # PostgreSQL full text search
|
||||
gem 'psych', '< 4.0'
|
||||
gem 'rails', '~> 7.0.5'
|
||||
gem 'rails', '~> 7.0.8'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize'
|
||||
gem 'sprockets-rails'
|
||||
|
|
170
Gemfile.lock
170
Gemfile.lock
|
@ -60,47 +60,47 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actioncable (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionmailbox (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionmailer (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionpack (7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actiontext (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
actionview (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -110,14 +110,14 @@ GEM
|
|||
activemodel (>= 4.1, < 7.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activejob (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activerecord (7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activemodel (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord (7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activerecord-session_store (2.1.0)
|
||||
|
@ -127,14 +127,14 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 6.1)
|
||||
activestorage (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
activestorage (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.5.1)
|
||||
activesupport (7.0.8.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -197,12 +197,13 @@ GEM
|
|||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
base62 (1.0.0)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.18)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
rouge (>= 1.0.0)
|
||||
bindata (2.4.15)
|
||||
bindata (2.5.0)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.16.0)
|
||||
|
@ -240,7 +241,7 @@ GEM
|
|||
combine_pdf (1.0.23)
|
||||
matrix
|
||||
ruby-rc4 (>= 0.1.5)
|
||||
concurrent-ruby (1.2.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
|
@ -284,7 +285,7 @@ GEM
|
|||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
date (3.3.4)
|
||||
debug_inspector (1.1.0)
|
||||
deface (1.9.0)
|
||||
actionview (>= 5.2)
|
||||
|
@ -303,7 +304,7 @@ GEM
|
|||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_invitable (2.0.8)
|
||||
devise_invitable (2.0.9)
|
||||
actionmailer (>= 5.0)
|
||||
devise (>= 4.6)
|
||||
diff-lcs (1.5.0)
|
||||
|
@ -327,12 +328,12 @@ GEM
|
|||
railties (>= 5.0.0)
|
||||
faker (3.2.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.7.6)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
fastimage (2.2.7)
|
||||
ffi (1.15.5)
|
||||
ffi-compiler (1.0.1)
|
||||
|
@ -343,8 +344,8 @@ GEM
|
|||
fugit (1.8.1)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
graphviz (1.2.1)
|
||||
process-pipeline
|
||||
grover (1.1.5)
|
||||
|
@ -382,9 +383,10 @@ GEM
|
|||
jsbundling-rails (1.1.1)
|
||||
railties (>= 6.0.0)
|
||||
json (2.6.3)
|
||||
json-jwt (1.16.3)
|
||||
json-jwt (1.16.6)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
bindata
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
|
@ -415,7 +417,7 @@ GEM
|
|||
logging (2.0.0)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
loofah (2.21.3)
|
||||
loofah (2.22.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
|
@ -430,9 +432,9 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.20.0)
|
||||
minitest (5.22.2)
|
||||
msgpack (1.7.1)
|
||||
multi_json (1.15.0)
|
||||
multi_test (1.1.0)
|
||||
|
@ -441,14 +443,16 @@ GEM
|
|||
coffee-rails (>= 3.2.1)
|
||||
jquery-rails
|
||||
rails (>= 3.2.0)
|
||||
net-imap (0.3.6)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
net-imap (0.4.10)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-smtp (0.4.0.1)
|
||||
net-protocol
|
||||
newrelic_rpm (9.2.2)
|
||||
nio4r (2.7.0)
|
||||
|
@ -475,7 +479,7 @@ GEM
|
|||
rack-protection
|
||||
omniauth-azure-activedirectory-v2 (2.0.1)
|
||||
omniauth-oauth2 (~> 1.8)
|
||||
omniauth-linkedin-oauth2 (1.0.0)
|
||||
omniauth-linkedin-oauth2 (1.0.1)
|
||||
omniauth-oauth2
|
||||
omniauth-oauth2 (1.8.0)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
|
@ -537,10 +541,10 @@ GEM
|
|||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.7)
|
||||
rack (2.2.8.1)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (2.0.1)
|
||||
rack-cors (2.0.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (2.2.0)
|
||||
activesupport
|
||||
|
@ -553,25 +557,25 @@ GEM
|
|||
rack
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.5.1)
|
||||
actioncable (= 7.0.5.1)
|
||||
actionmailbox (= 7.0.5.1)
|
||||
actionmailer (= 7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
actiontext (= 7.0.5.1)
|
||||
actionview (= 7.0.5.1)
|
||||
activejob (= 7.0.5.1)
|
||||
activemodel (= 7.0.5.1)
|
||||
activerecord (= 7.0.5.1)
|
||||
activestorage (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
rails (7.0.8.1)
|
||||
actioncable (= 7.0.8.1)
|
||||
actionmailbox (= 7.0.8.1)
|
||||
actionmailer (= 7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actiontext (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.5.1)
|
||||
railties (= 7.0.8.1)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.1.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
|
@ -587,22 +591,22 @@ GEM
|
|||
railties (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (7.0.5.1)
|
||||
actionpack (= 7.0.5.1)
|
||||
activesupport (= 7.0.5.1)
|
||||
railties (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
zeitwerk (~> 2.5)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rake (13.1.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rdoc (6.3.3)
|
||||
recaptcha (5.14.0)
|
||||
regexp_parser (2.8.1)
|
||||
responders (3.1.0)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.2.5)
|
||||
|
@ -613,7 +617,7 @@ GEM
|
|||
roo (2.10.0)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 1.3.0, < 3.0.0)
|
||||
rotp (6.2.2)
|
||||
rotp (6.3.0)
|
||||
rouge (4.1.2)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
|
@ -662,7 +666,6 @@ GEM
|
|||
rexml
|
||||
ruby-vips (2.1.4)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
|
@ -711,10 +714,10 @@ GEM
|
|||
railties (>= 6.0.0)
|
||||
tailwindcss-rails (2.0.29-x86_64-linux)
|
||||
railties (>= 6.0.0)
|
||||
thor (1.2.2)
|
||||
thor (1.3.1)
|
||||
tilt (2.2.0)
|
||||
timecop (0.9.6)
|
||||
timeout (0.4.0)
|
||||
timeout (0.4.1)
|
||||
turbolinks (5.2.1)
|
||||
turbolinks-source (~> 5.2)
|
||||
turbolinks-source (5.2.0)
|
||||
|
@ -734,6 +737,7 @@ GEM
|
|||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
uri (0.13.0)
|
||||
version_gem (1.1.3)
|
||||
view_component (3.9.0)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
|
@ -751,7 +755,7 @@ GEM
|
|||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.8.1)
|
||||
websocket (1.2.9)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
whacamole (1.2.0)
|
||||
|
@ -759,7 +763,7 @@ GEM
|
|||
activesupport
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.8)
|
||||
zeitwerk (2.6.13)
|
||||
zip-zip (0.3)
|
||||
rubyzip (>= 1.0.0)
|
||||
|
||||
|
@ -842,7 +846,7 @@ DEPENDENCIES
|
|||
puma
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (~> 7.0.5)
|
||||
rails (~> 7.0.8)
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
rails_autolink (~> 1.1, >= 1.1.6)
|
||||
|
|
4
Makefile
4
Makefile
|
@ -96,9 +96,7 @@ tests-ci:
|
|||
-e CORE_API_RATE_LIMIT=1000000 \
|
||||
-e PROTOCOLS_IO_ACCESS_TOKEN=PROTOCOLS_IO_ACCESS_TOKEN \
|
||||
-e ENABLE_WEBHOOKS=true \
|
||||
--rm web bash -c "rake db:create && rake db:migrate && \
|
||||
yarn install && yarn build && yarn build:css && rails tailwindcss:build && \
|
||||
bundle exec rspec ./spec/"
|
||||
--rm web bash -c "rake db:create && rake db:migrate && bundle exec rspec ./spec/"
|
||||
|
||||
console:
|
||||
@$(MAKE) rails cmd="rails console"
|
||||
|
|
4
app/assets/images/checkbox/checked.svg
Normal file
4
app/assets/images/checkbox/checked.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1C0 0.447715 0.447715 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447715 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
|
||||
<path d="M6.56358 11.8292L3 8.32244L3.70142 7.60969L6.55139 10.4143L12.8641 4L13.5775 4.70204L6.56358 11.8292Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 381 B |
4
app/assets/images/checkbox/default.svg
Normal file
4
app/assets/images/checkbox/default.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#1D2939"/>
|
||||
<path d="M1 1H15V15H1V1Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 351 B |
4
app/assets/images/checkbox/disabled.svg
Normal file
4
app/assets/images/checkbox/disabled.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM1 0C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V1C16 0.447715 15.5523 0 15 0H1Z" fill="#EAECF0"/>
|
||||
<path d="M1 1H15V15H1V1Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 351 B |
4
app/assets/images/checkbox/indeterminate.svg
Normal file
4
app/assets/images/checkbox/indeterminate.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1C0 0.447715 0.447716 0 1 0H15C15.5523 0 16 0.447715 16 1V15C16 15.5523 15.5523 16 15 16H1C0.447716 16 0 15.5523 0 15V1Z" fill="#3B99FD"/>
|
||||
<path d="M13 8H3V9H13V8Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 295 B |
|
@ -27,7 +27,6 @@
|
|||
//= require shared/barcode_search
|
||||
//= require activestorage
|
||||
//= require global_activities/side_pane
|
||||
//= require protocols/header
|
||||
//= require protocols/print
|
||||
//= require marvinjslauncher
|
||||
//= require jstree.min
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
/* globals renderFormError animateSpinner */
|
||||
|
||||
(function() {
|
||||
function clearModal(id) {
|
||||
// Completely remove modal when it gets closed
|
||||
$(id).on('hidden.bs.modal', function() {
|
||||
$(id).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Reload after successfully updated experiment
|
||||
function validateExperimentForm(element) {
|
||||
if ($(element)) {
|
||||
let form = $(element).find('form');
|
||||
form.on('ajax:success', function(e, data) {
|
||||
animateSpinner(form, true);
|
||||
if (element.match(/#new-experiment-modal/)) {
|
||||
window.location.replace(data.path);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}).on('ajax:error', function(e, error) {
|
||||
let msg = JSON.parse(error.responseText);
|
||||
if ('name' in msg) {
|
||||
renderFormError(e, $(element).find('#experiment-name'), msg.name.toString(), true);
|
||||
} else if ('description' in msg) {
|
||||
renderFormError(e, $(element).find('#experiment-description'), msg.description.toString(), true);
|
||||
} else {
|
||||
renderFormError(e, $(element).find('#experiment-name'), error.statusText, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Validates move action
|
||||
function validateMoveModal(modal) {
|
||||
if (modal.match(/#move-experiment-modal-[0-9]*/)) {
|
||||
let form = $(modal).find('form');
|
||||
form.on('ajax:success', function(e, data) {
|
||||
animateSpinner(form, true);
|
||||
window.location.replace(data.path);
|
||||
}).on('ajax:error', function(e, error) {
|
||||
form.clearFormErrors();
|
||||
let msg = JSON.parse(error.responseText);
|
||||
renderFormError(e, form.find('#experiment_project_id'), msg.message.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create ajax hook on given 'element', which should return modal with 'id' =>
|
||||
// show that modal
|
||||
function initializeModal(element, id) {
|
||||
// Initialize new experiment modal listener
|
||||
$(element).on('ajax:beforeSend', function() {
|
||||
animateSpinner();
|
||||
}).on('ajax:success', function(e, data) {
|
||||
$('body').append($.parseHTML(data.html));
|
||||
$(id).modal('show', {
|
||||
backdrop: true,
|
||||
keyboard: false
|
||||
});
|
||||
validateMoveModal(id);
|
||||
clearModal($(id));
|
||||
validateExperimentForm(id);
|
||||
}).on('ajax:error', function() {
|
||||
animateSpinner(null, false);
|
||||
// TODO
|
||||
}).on('ajax:complete', function() {
|
||||
animateSpinner(null, false);
|
||||
$(id).find('.selectpicker').selectpicker();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize dropdown actions on experiment:
|
||||
// - edit
|
||||
// - clone
|
||||
function initializeDropdownActions() {
|
||||
// { buttonClass: modalName } mappings
|
||||
// click on buttonClass summons modalName dialog
|
||||
let modals = {
|
||||
'.edit-experiment': '#edit-experiment-modal-',
|
||||
'.clone-experiment': '#clone-experiment-modal-',
|
||||
'.move-experiment': '#move-experiment-modal-'
|
||||
};
|
||||
|
||||
$.each($('.dropdown-experiment-actions'), function() {
|
||||
var $dropdown = $(this);
|
||||
$.each(modals, function(buttonClass, modalName) {
|
||||
var id = modalName + $dropdown.data('id');
|
||||
initializeModal($dropdown.find(buttonClass), id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize no description edit link
|
||||
function initEditNoDescription() {
|
||||
var modal = '#edit-experiment-modal-';
|
||||
$.each($('.experiment-no-description'), function() {
|
||||
var id = modal + $(this).data('id');
|
||||
initializeModal($(this), id);
|
||||
});
|
||||
}
|
||||
|
||||
// Bind modal to new-experiment action
|
||||
initializeModal($('#new-experiment'), '#new-experiment-modal');
|
||||
|
||||
// Bind modal to big-plus new experiment actions
|
||||
initializeModal('.big-plus', '#new-experiment-modal');
|
||||
|
||||
// Bind modal to new-exp-title action
|
||||
initializeModal('.new-exp-title', '#new-experiment-modal');
|
||||
|
||||
// Bind modals to all clone-experiment actions
|
||||
$.each($('.clone-experiment'), function() {
|
||||
var id = $(this).closest('.experiment-panel').data('id');
|
||||
initializeModal($(this), '#clone-experiment-modal-' + id);
|
||||
});
|
||||
|
||||
// Bind modal to all actions listed on dropdown accesible from experiment
|
||||
// panel
|
||||
initializeDropdownActions();
|
||||
|
||||
// init
|
||||
initEditNoDescription();
|
||||
}());
|
|
@ -7,7 +7,22 @@
|
|||
let myModuleUserSelector = '#my_module_user_ids';
|
||||
var myModuleTagsSelector = '#module-tags-selector';
|
||||
|
||||
$(document).on('submit', '#new-my-module-modal form', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
$.post({
|
||||
url: $('#new-my-module-modal form').attr('action'),
|
||||
data: {
|
||||
my_module: {
|
||||
name: $('#new-my-module-modal input[name="my_module[name]"]').val(),
|
||||
view_mode: $('#new-my-module-modal input[name="my_module[view_mode]"]').val(),
|
||||
due_date: $('#new-my-module-modal input[name="my_module[due-date]"]').val(),
|
||||
tag_ids: dropdownSelector.getValues(myModuleTagsSelector),
|
||||
user_ids: dropdownSelector.getValues(myModuleUserSelector)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// Modal's submit handler function
|
||||
$(experimentWrapper)
|
||||
.on('ajax:success', newMyModuleModal, function() {
|
||||
|
@ -126,5 +141,28 @@
|
|||
initBSTooltips();
|
||||
}
|
||||
|
||||
function initAccessModal() {
|
||||
$('#experiment-canvas').on('click', '.openAccessModal', (e) => {
|
||||
e.preventDefault();
|
||||
const { target } = e;
|
||||
$.get(target.dataset.url, (data) => {
|
||||
const object = {
|
||||
...data.data.attributes,
|
||||
id: data.data.id,
|
||||
type: data.data.type
|
||||
};
|
||||
const { rolesUrl } = target.dataset;
|
||||
const params = {
|
||||
object: object,
|
||||
roles_path: rolesUrl
|
||||
};
|
||||
const modal = $('#accessModalComponent').data('accessModal');
|
||||
modal.params = params;
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initNewMyModuleModal();
|
||||
initAccessModal();
|
||||
}());
|
||||
|
|
|
@ -1,836 +0,0 @@
|
|||
/* global I18n GLOBAL_CONSTANTS InfiniteScroll
|
||||
initBSTooltips filterDropdown dropdownSelector Sidebar HelperModule notTurbolinksPreview _ */
|
||||
|
||||
var ExperimnetTable = {
|
||||
selectedId: [],
|
||||
table: '.experiment-table',
|
||||
tableContainer: '.experiment-table-container',
|
||||
selectedMyModules: [],
|
||||
activeFilters: {},
|
||||
filters: [], // Filter {name: '', init(), closeFilter(), apply(), active(), clearFilter()}
|
||||
myModulesCurrentSort: '',
|
||||
pageSize: GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE,
|
||||
provisioningStatusTimeout: '',
|
||||
render: {
|
||||
task_name: function(data) {
|
||||
let tooltip = ` title="${_.escape(data.name)}" data-toggle="tooltip" data-placement="bottom"`;
|
||||
if (data.provisioning_status === 'in_progress') {
|
||||
return `<span data-full-name="${_.escape(data.name)}">${data.name}</span>`;
|
||||
}
|
||||
|
||||
return `<a
|
||||
href="${data.url}"
|
||||
${tooltip}
|
||||
title="${_.escape(data.name)}"
|
||||
id="taskName${data.id}"
|
||||
data-full-name="${_.escape(data.name)}">${data.name}</a>`;
|
||||
},
|
||||
id: function(data) {
|
||||
return `
|
||||
<div>${data.id}</div>
|
||||
`;
|
||||
},
|
||||
due_date: function(data) {
|
||||
return data.data;
|
||||
},
|
||||
archived: function(data) {
|
||||
return data;
|
||||
},
|
||||
age: function(data) {
|
||||
return data;
|
||||
},
|
||||
results: function(data) {
|
||||
return `<a href="${data.url}">${data.count}</a>`;
|
||||
},
|
||||
status: function(data) {
|
||||
return `<div class="my-module-status ${data.light_color ? 'status-light' : ''}"
|
||||
style="background-color: ${data.color}">${data.name}</div>`;
|
||||
},
|
||||
assigned: function(data) {
|
||||
return data.html;
|
||||
},
|
||||
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>`;
|
||||
},
|
||||
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>`;
|
||||
}
|
||||
},
|
||||
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 .sn-icon-visibility-hide[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-light btn-xs icon-btn open-my-module-menu" tabindex="0"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" >
|
||||
<i class="sn-icon sn-icon-more-hori"></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');
|
||||
|
||||
$(`#calendarDueDateContainer${row.id}`).parent().on('dp:ready', () => {
|
||||
$(element).data('dateTimePicker').onChange = () => {
|
||||
$.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.removeClass('tw-hidden');
|
||||
} else {
|
||||
clearDate.addClass('tw-hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearDate.on('click', () => {
|
||||
$(element).data('dateTimePicker').clearDate();
|
||||
});
|
||||
});
|
||||
|
||||
if ($(`#calendarDueDateContainer${row.id}`).length > 0) {
|
||||
window.initDateTimePickerComponent(`#calendarDueDateContainer${row.id}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
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();
|
||||
e.stopPropagation();
|
||||
this.archiveMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
|
||||
});
|
||||
|
||||
|
||||
$(this.table).on('click', '.restore-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.restoreMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
|
||||
});
|
||||
|
||||
$(this.table).on('click', '.duplicate-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.duplicateMyModules(e.currentTarget.href, e.currentTarget.dataset.id);
|
||||
});
|
||||
|
||||
$(this.table).on('click', '.move-my-module', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
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() {
|
||||
$(this.tableContainer).on('click', '#duplicateTasks', (e) => {
|
||||
e.stopPropagation();
|
||||
this.duplicateMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
|
||||
});
|
||||
},
|
||||
duplicateMyModules: function(url, ids) {
|
||||
$.post(url, { my_module_ids: ids }, () => {
|
||||
this.loadTable();
|
||||
window.navigatorContainer.reloadChildrenLevel = true;
|
||||
}).fail((data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
},
|
||||
initArchiveMyModules: function() {
|
||||
$(this.tableContainer).on('click', '#archiveTask', (e) => {
|
||||
e.stopPropagation();
|
||||
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();
|
||||
window.navigatorContainer.reloadChildrenLevel = true;
|
||||
}).fail((data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
},
|
||||
initRestoreMyModules: function() {
|
||||
$(this.tableContainer).on('click', '#restoreTask', (e) => {
|
||||
e.stopPropagation();
|
||||
this.restoreMyModules(e.currentTarget.dataset.url, this.selectedMyModules);
|
||||
});
|
||||
},
|
||||
restoreMyModules: function(url, ids) {
|
||||
$.post(url, { my_modules_ids: ids, view: 'table' });
|
||||
},
|
||||
initRenameModal: function() {
|
||||
$(this.tableContainer).on('click', '#editTask', (e) => {
|
||||
e.preventDefault();
|
||||
$('#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) => {
|
||||
setTimeout(() => {
|
||||
$('.sci-input-field.user-search').each(function() {
|
||||
this.focus();
|
||||
});
|
||||
}, 200);
|
||||
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'));
|
||||
}).fail((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');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
initModalInputFocus: function() {
|
||||
$(document).on('shown.bs.modal', function() {
|
||||
var inputField = $('#edit-module-name-input');
|
||||
var value = inputField.val();
|
||||
inputField.focus().val('').val(value);
|
||||
});
|
||||
},
|
||||
initMoveModulesModal: function() {
|
||||
$(this.tableContainer).on('click', '#moveTask', (e) => {
|
||||
e.stopPropagation();
|
||||
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();
|
||||
window.navigatorContainer.reloadChildrenLevel = true;
|
||||
}).fail((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;
|
||||
},
|
||||
updateSelectAllCheckbox: function() {
|
||||
const tableWrapper = $(this.table);
|
||||
const checkboxesCount = $('.sci-checkbox.my-module-selector', tableWrapper).length;
|
||||
const selectedCheckboxesCount = this.selectedMyModules.length;
|
||||
const selectAllCheckbox = $('.select-all-checkboxes .sci-checkbox', tableWrapper);
|
||||
|
||||
selectAllCheckbox.prop('indeterminate', false);
|
||||
if (selectedCheckboxesCount === 0) {
|
||||
selectAllCheckbox.prop('checked', false);
|
||||
} else if (selectedCheckboxesCount === checkboxesCount) {
|
||||
selectAllCheckbox.prop('checked', true);
|
||||
} else {
|
||||
selectAllCheckbox.prop('indeterminate', true);
|
||||
}
|
||||
},
|
||||
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();
|
||||
});
|
||||
});
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
this.updateSelectAllCheckbox();
|
||||
this.updateExperimentToolbar();
|
||||
});
|
||||
},
|
||||
updateExperimentToolbar: function() {
|
||||
if (window.actionToolbarComponent) {
|
||||
window.actionToolbarComponent.fetchActions({ my_module_ids: this.selectedMyModules });
|
||||
}
|
||||
},
|
||||
selectDate: function($field) {
|
||||
var datePicker = $field.data('dateTimePicker');
|
||||
if (datePicker && datePicker.date) {
|
||||
return datePicker.date.toString();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
initManageColumnsModal: function() {
|
||||
$.each($('.table-display-modal .sn-icon-visibility-hide'), (_i, column) => {
|
||||
$(column).parent().removeClass('visible');
|
||||
});
|
||||
$('.experiment-table')[0].style
|
||||
.setProperty('--columns-count', $('.table-display-modal .sn-icon-visibility-show:not(.disabled)').length + 1);
|
||||
|
||||
$('.table-display-modal').on('click', '.column-container .sn-icon', (e) => {
|
||||
let icon = $(e.target);
|
||||
if (icon.hasClass('sn-icon-visibility-show')) {
|
||||
$(`.experiment-table .${icon.data('column')}-column`).addClass('hidden');
|
||||
icon.removeClass('sn-icon-visibility-show').addClass('sn-icon-visibility-hide');
|
||||
icon.parent().removeClass('visible');
|
||||
} else {
|
||||
if (icon.hasClass('disabled')) return;
|
||||
$(`.experiment-table .${icon.data('column')}-column`).removeClass('hidden');
|
||||
icon.addClass('sn-icon-visibility-show').removeClass('sn-icon-visibility-hide');
|
||||
icon.parent().addClass('visible');
|
||||
}
|
||||
|
||||
let visibleColumns = $('.table-display-modal .sn-icon-visibility-show').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 .sn-icon-visibility-show:not(.disabled)').length + 1);
|
||||
});
|
||||
},
|
||||
clearRowTaskSelection: function() {
|
||||
this.selectedMyModules = [];
|
||||
this.updateSelectAllCheckbox();
|
||||
this.updateExperimentToolbar();
|
||||
},
|
||||
initNewTaskModal: function(table) {
|
||||
$('.experiment-new-my_module').on('ajax:success', '#new-my-module-modal', function() {
|
||||
table.loadTable();
|
||||
window.navigatorContainer.reloadChildrenLevel = true;
|
||||
});
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
},
|
||||
getFilterValues: function() {
|
||||
let $experimentFilter = $('#experimentTable .my-modules-filters');
|
||||
$.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;
|
||||
},
|
||||
filtersEnabled: function() {
|
||||
this.getFilterValues();
|
||||
|
||||
return this.filters.some((filter) => {
|
||||
return filter.active(this.activeFilters[filter.name]);
|
||||
});
|
||||
},
|
||||
initFilters: function() {
|
||||
let $experimentFilter = $('#experimentTable .my-modules-filters');
|
||||
|
||||
this.filterDropdown = filterDropdown.init(() => this.filtersEnabled());
|
||||
|
||||
$.each(this.filters, (_i, filter) => {
|
||||
filter.init($experimentFilter);
|
||||
});
|
||||
|
||||
this.filterDropdown.on('filter:apply', () => {
|
||||
filterDropdown.toggleFilterMark(this.filterDropdown, this.filtersEnabled());
|
||||
|
||||
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();
|
||||
|
||||
$.get(dataUrl, tableParams, (result) => {
|
||||
$(this.table).find('.table-row-placeholder, .table-row-placeholder-divider').remove();
|
||||
setTimeout(() => {
|
||||
this.appendRows(result.data);
|
||||
this.initDueDatePicker(result.data);
|
||||
this.handleNoResults();
|
||||
this.initProvisioningStatusPolling();
|
||||
}, 100);
|
||||
|
||||
InfiniteScroll.init(this.table, {
|
||||
url: dataUrl,
|
||||
eventTarget: window,
|
||||
placeholderTemplate: '#experimentTablePlaceholder',
|
||||
endOfListTemplate: '#experimentTableEndOfList',
|
||||
pageSize: this.pageSize,
|
||||
lastPage: !result.next_page,
|
||||
customResponse: (response) => {
|
||||
setTimeout(() => {
|
||||
this.appendRows(response.data);
|
||||
this.initDueDatePicker(response.data);
|
||||
this.initProvisioningStatusPolling();
|
||||
}, 100);
|
||||
},
|
||||
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');
|
||||
$('.no-data-container').hide();
|
||||
if (this.filtersActive && tableRowLength === 0) {
|
||||
noResultsContainer.style.display = 'block';
|
||||
} else if (tableRowLength === 0) {
|
||||
$(this.table).find('.table-header').hide();
|
||||
$(this.table).addClass('no-data');
|
||||
$('.no-data-container').show();
|
||||
} else {
|
||||
noResultsContainer.style.display = 'none';
|
||||
$(this.table).find('.table-header').show();
|
||||
}
|
||||
},
|
||||
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() {
|
||||
window.initActionToolbar();
|
||||
|
||||
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();
|
||||
this.initModalInputFocus();
|
||||
}
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
$('.due-date-filter .from-date', $container).data('dateTimePicker')?.clearDate();
|
||||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
$('.due-date-filter .to-date', $container).data('dateTimePicker')?.clearDate();
|
||||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
$('.archived-on-filter .from-date', $container).data('dateTimePicker')?.clearDate();
|
||||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
$('.archived-on-filter .to-date', $container).data('dateTimePicker')?.clearDate();
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
|
@ -1,306 +0,0 @@
|
|||
/* global I18n DataTableHelpers HelperModule */
|
||||
/* eslint-disable no-use-before-define no-param-reassign */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var LABEL_TEMPLATE_TABLE;
|
||||
var rowsSelected = [];
|
||||
|
||||
function rowsSelectedIDs() {
|
||||
return rowsSelected.map(i => i.id);
|
||||
}
|
||||
|
||||
function renderCheckboxHTML(data) {
|
||||
return `<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox label-row-checkbox" data-action='toggle'
|
||||
data-label-template-id="${data}">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderDefaultTemplateHTML(data) {
|
||||
return data ? '<i class="fas fa-thumbtack"></i>' : '';
|
||||
}
|
||||
|
||||
function renderNameHTML(data, type, row) {
|
||||
return `<div class="flex gap-2">${data.icon_image_tag}<a
|
||||
href='${row.DT_RowAttr['data-edit-url']}'
|
||||
class='label-info-link'
|
||||
>${data.name}</a></div>`;
|
||||
}
|
||||
|
||||
function addAttributesToRow(row, data) {
|
||||
$(row).addClass('label-template-row')
|
||||
.attr('data-id', data['0']);
|
||||
}
|
||||
|
||||
function initNameClick() {
|
||||
$('.label-info-link', '.dataTables_scrollBody').on('click', function() {
|
||||
window.location.href = this.href;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function initToggleAllCheckboxes() {
|
||||
$('input[name="select_all"]').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$("[data-action='toggle']").prop('checked', true);
|
||||
$('.label-template-row').addClass('selected');
|
||||
$('.label-template-row [data-action="toggle"]').change();
|
||||
} else {
|
||||
$("[data-action='toggle']").prop('checked', false);
|
||||
$('.label-template-row').removeClass('selected');
|
||||
$('.label-template-row [data-action="toggle"]').change();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCreateButton() {
|
||||
$('#newLabelTemplate').on('click', function() {
|
||||
$.post(this.dataset.url);
|
||||
});
|
||||
}
|
||||
|
||||
function initSetDefaultButton() {
|
||||
$(document).on('click', '#setZplDefaultLabelTemplate, #setFluicsDefaultLabelTemplate', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (rowsSelected.length === 1) {
|
||||
$.post(rowsSelected[0].setDefaultUrl, function(response) {
|
||||
reloadTable();
|
||||
HelperModule.flashAlertMsg(response.message, 'success');
|
||||
}).fail((response) => {
|
||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initEditButton() {
|
||||
$('#editTemplate').on('click', function() {
|
||||
if (rowsSelected.length === 1) {
|
||||
window.location.href = rowsSelected[0].editUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initDuplicateButton() {
|
||||
$(document).on('click', '#duplicateLabelTemplate', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (rowsSelected.length > 0) {
|
||||
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
|
||||
reloadTable();
|
||||
HelperModule.flashAlertMsg(response.message, 'success');
|
||||
}).fail((response) => {
|
||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteModal() {
|
||||
$(document).on('click', '#deleteLabelTemplate', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$('#deleteLabelTemplatesModal').modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteButton() {
|
||||
$('#confirmLabeleDeletion').on('click', function() {
|
||||
if (rowsSelected.length > 0) {
|
||||
$.post(this.dataset.url, { selected_ids: rowsSelectedIDs() }, function(response) {
|
||||
reloadTable();
|
||||
HelperModule.flashAlertMsg(response.message, 'success');
|
||||
$('#deleteLabelTemplatesModal').modal('hide');
|
||||
}).fail((response) => {
|
||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
||||
$('#deleteLabelTemplatesModal').modal('hide');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initRefreshFluicsButton() {
|
||||
$('#syncFluicsTemplates').on('click', function() {
|
||||
$.post(this.dataset.url, function(response) {
|
||||
reloadTable();
|
||||
HelperModule.flashAlertMsg(response.message, 'success');
|
||||
}).fail((response) => {
|
||||
HelperModule.flashAlertMsg(response.responseJSON.error, 'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function tableDrawCallback() {
|
||||
initToggleAllCheckboxes();
|
||||
initRowSelection();
|
||||
initNameClick();
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
if (window.actionToolbarComponent) {
|
||||
window.actionToolbarComponent.fetchActions({ label_template_ids: rowsSelectedIDs() });
|
||||
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelectedIDs().length > 0 ? 54 : 0}px`);
|
||||
}
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
LABEL_TEMPLATE_TABLE.ajax.reload(null, false);
|
||||
rowsSelected = [];
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function updateDataTableSelectAllCtrl() {
|
||||
var $table = LABEL_TEMPLATE_TABLE.table().node();
|
||||
var $header = LABEL_TEMPLATE_TABLE.table().header();
|
||||
var $chkboxAll = $('.label-row-checkbox', $table);
|
||||
var $chkboxChecked = $('.label-row-checkbox:checked', $table);
|
||||
var chkboxSelectAll = $('input[name="select_all"]', $header).get(0);
|
||||
|
||||
// If none of the checkboxes are checked
|
||||
if ($chkboxChecked.length === 0) {
|
||||
chkboxSelectAll.checked = false;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
|
||||
// If all of the checkboxes are checked
|
||||
} else if ($chkboxChecked.length === $chkboxAll.length) {
|
||||
chkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
|
||||
// If some of the checkboxes are checked
|
||||
} else {
|
||||
chkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initRowSelection() {
|
||||
// Handle clicks on checkbox
|
||||
$('#label-templates-table').on('change', '.label-row-checkbox', function(ev) {
|
||||
var rowId;
|
||||
var index;
|
||||
var row;
|
||||
|
||||
rowId = this.dataset.labelTemplateId;
|
||||
row = $(this).closest('tr')[0];
|
||||
|
||||
// Determine whether row ID is in the list of selected row IDs
|
||||
index = rowsSelected.findIndex(v => v.id === rowId);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected row IDs
|
||||
if (this.checked && index === -1) {
|
||||
rowsSelected.push({
|
||||
id: rowId,
|
||||
default: row.dataset.default,
|
||||
editUrl: row.dataset.editUrl,
|
||||
setDefaultUrl: row.dataset.setDefaultUrl,
|
||||
format: row.dataset.format
|
||||
});
|
||||
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
|
||||
} else if (!this.checked && index !== -1) {
|
||||
rowsSelected.splice(index, 1);
|
||||
}
|
||||
|
||||
if (this.checked) {
|
||||
$(this).closest('.label-template-row').addClass('selected');
|
||||
} else {
|
||||
$(this).closest('.label-template-row').removeClass('selected');
|
||||
}
|
||||
|
||||
updateDataTableSelectAllCtrl();
|
||||
|
||||
ev.stopPropagation();
|
||||
updateButtons();
|
||||
});
|
||||
}
|
||||
// INIT
|
||||
|
||||
function initDatatable() {
|
||||
var $table = $('#label-templates-table');
|
||||
LABEL_TEMPLATE_TABLE = $table.DataTable({
|
||||
dom: "R<'label-toolbar'<'label-buttons-container'><'label-search-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
|
||||
order: [[2, 'desc']],
|
||||
stateSave: true,
|
||||
sScrollX: '100%',
|
||||
sScrollXInner: '100%',
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: $table.data('source'),
|
||||
pagingType: 'simple_numbers',
|
||||
colReorder: {
|
||||
fixedColumnsLeft: 1000000 // Disable reordering
|
||||
},
|
||||
columnDefs: [{
|
||||
targets: 0,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'dt-body-center',
|
||||
sWidth: '1%',
|
||||
render: renderCheckboxHTML
|
||||
}, {
|
||||
targets: 1,
|
||||
searchable: false,
|
||||
orderable: true,
|
||||
width: '1.5rem',
|
||||
render: renderDefaultTemplateHTML
|
||||
}, {
|
||||
targets: 2,
|
||||
className: 'label-template-name',
|
||||
render: renderNameHTML
|
||||
}, {
|
||||
targets: 4,
|
||||
className: 'whitespace-break-spaces',
|
||||
render: data => `<span class='whitespace-break-spaces'>${data}</span>`
|
||||
}],
|
||||
oLanguage: {
|
||||
sSearch: I18n.t('general.filter')
|
||||
},
|
||||
fnDrawCallback: tableDrawCallback,
|
||||
createdRow: addAttributesToRow,
|
||||
fnInitComplete: function() {
|
||||
DataTableHelpers.initLengthAppearance($table.closest('.dataTables_wrapper'));
|
||||
DataTableHelpers.initSearchField(
|
||||
$table.closest('.dataTables_wrapper'),
|
||||
I18n.t('label_templates.index.search_templates')
|
||||
);
|
||||
$('.pagination-row').removeClass('hidden');
|
||||
|
||||
let toolBar = $($('#labelTemplatesToolbar').html());
|
||||
$('.label-buttons-container').html(toolBar);
|
||||
|
||||
initCreateButton();
|
||||
initEditButton();
|
||||
initSetDefaultButton();
|
||||
initDuplicateButton();
|
||||
initDeleteModal();
|
||||
initRefreshFluicsButton();
|
||||
window.initActionToolbar();
|
||||
window.actionToolbarComponent.setBottomOffset(68);
|
||||
},
|
||||
stateLoadParams: function(_, state) {
|
||||
state.search.search = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#wrapper').on('sideBar::shown sideBar::hidden', function() {
|
||||
if (LABEL_TEMPLATE_TABLE) {
|
||||
LABEL_TEMPLATE_TABLE.columns.adjust();
|
||||
}
|
||||
});
|
||||
|
||||
initDatatable();
|
||||
initDeleteButton();
|
||||
}());
|
|
@ -61,7 +61,9 @@
|
|||
updateStartDate();
|
||||
};
|
||||
});
|
||||
window.initDateTimePickerComponent('#calendarStartDateContainer');
|
||||
if ($('#calendarStartDateContainer').length) {
|
||||
window.initDateTimePickerComponent('#calendarStartDateContainer');
|
||||
}
|
||||
}
|
||||
|
||||
function updateDueDate() {
|
||||
|
@ -90,7 +92,10 @@
|
|||
updateDueDate();
|
||||
};
|
||||
});
|
||||
window.initDateTimePickerComponent('#calendarDueDateContainer');
|
||||
|
||||
if ($('#calendarDueDateContainer').length) {
|
||||
window.initDateTimePickerComponent('#calendarDueDateContainer');
|
||||
}
|
||||
}
|
||||
|
||||
function initTagsSelector() {
|
||||
|
@ -135,7 +140,7 @@
|
|||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (lastTag.length > 0) {
|
||||
newTag = {
|
||||
tag: {
|
||||
name: lastTag.find('.tag-label').html(),
|
||||
|
@ -161,7 +166,7 @@
|
|||
},
|
||||
onUnSelect: (id) => {
|
||||
$.post(`${$(myModuleTagsSelector).data('update-module-tags-url')}/${id}/destroy_by_tag_id`)
|
||||
.success(function() {
|
||||
.done(() => {
|
||||
dropdownSelector.closeDropdown(myModuleTagsSelector);
|
||||
})
|
||||
.fail(function(r) {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
// Currently selected row in "load from protocol" modal
|
||||
var selectedRow = null;
|
||||
|
||||
|
||||
function initEditMyModuleDescription() {
|
||||
var viewObject = $('#my_module_description_view');
|
||||
viewObject.on('click', function(e) {
|
||||
|
@ -305,6 +304,39 @@ function initProtocolSectionOpenEvent() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initAccessModal() {
|
||||
$('#openAccessModal').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
const container = document.getElementById('accessModalContainer');
|
||||
$.get(container.dataset.url, (data) => {
|
||||
const object = {
|
||||
...data.data.attributes,
|
||||
id: data.data.id,
|
||||
type: data.data.type
|
||||
};
|
||||
const { rolesUrl } = container.dataset;
|
||||
const params = {
|
||||
object: object,
|
||||
roles_path: rolesUrl
|
||||
};
|
||||
const modal = $('#accessModalComponent').data('accessModal');
|
||||
modal.params = params;
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initWrapTables() {
|
||||
const viewMode = new URLSearchParams(window.location.search).get('view_mode');
|
||||
if (['archived', 'locked', 'active'].includes(viewMode)) {
|
||||
setTimeout(() => {
|
||||
const notesContainerEl = document.getElementById('notes-container');
|
||||
window.wrapTables(notesContainerEl);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
|
@ -315,6 +347,8 @@ function init() {
|
|||
initLoadFromRepository();
|
||||
initProtocolSectionOpenEvent();
|
||||
initDetailsDropdown();
|
||||
initAccessModal();
|
||||
initWrapTables();
|
||||
}
|
||||
|
||||
init();
|
||||
|
|
|
@ -146,13 +146,11 @@
|
|||
});
|
||||
// initialize my_module tab remote loading
|
||||
$('#experimentTable, .my-modules-protocols-index, #experiment-canvas')
|
||||
.on('ajax:before', '.edit-tags-link', function() {
|
||||
manageTagsModal.modal('show');
|
||||
.on('click', '.edit-tags-link', function() {
|
||||
if($('#tagsModalComponent').length) {
|
||||
$('#tagsModalComponent').data('tagsModal').open()
|
||||
}
|
||||
})
|
||||
.on('ajax:success', '.edit-tags-link', function(e, data) {
|
||||
$('#manage-module-tags-modal-module').text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
});
|
||||
}
|
||||
|
||||
bindEditTagsAjax();
|
||||
|
|
|
@ -545,6 +545,40 @@ function bindTouchDropdowns(selector) {
|
|||
});
|
||||
}
|
||||
|
||||
function handleAnchorClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// check if the clicked element is an anchor tag with an href
|
||||
if (event.target.tagName === 'A' && event.target.href) {
|
||||
const targetUrl = event.target.href;
|
||||
|
||||
const alertText = $("#update-canvas").attr("data-unsaved-work-text");
|
||||
if (!alertText) {
|
||||
window.location.href = targetUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
const exit = confirm(alertText);
|
||||
|
||||
if (exit) {
|
||||
// remove unload listeners and navigate to location
|
||||
$(window).off("beforeunload");
|
||||
$(document).off("page:before-change");
|
||||
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// listen to clicks on links in navigator and leftMenuContainer
|
||||
$(document).ready(function() {
|
||||
const navigatorEl = $('.sci--layout-navigation-navigator');
|
||||
const leftMenuContainerEl = $('.sci--layout--left-menu-container');
|
||||
|
||||
navigatorEl.on('click', 'a', handleAnchorClick);
|
||||
leftMenuContainerEl.on('click', 'a', handleAnchorClick);
|
||||
});
|
||||
|
||||
function bindEditModeCloseWindow() {
|
||||
var alertText = $("#update-canvas").attr("data-unsaved-work-text");
|
||||
|
||||
|
@ -933,17 +967,9 @@ function bindEditTagsAjax(elements) {
|
|||
|
||||
// initialize my_module tab remote loading
|
||||
$(elements).find("a.edit-tags-link")
|
||||
.on("ajax:before", function () {
|
||||
var moduleId = $(this).closest(".panel-default").attr("data-module-id");
|
||||
manageTagsModal.attr("data-module-id", moduleId);
|
||||
manageTagsModal.modal('show');
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
$("#manage-module-tags-modal-module").text(data.my_module.name);
|
||||
initTagsModalBody(data);
|
||||
})
|
||||
.on('click', function(){
|
||||
$(this).addClass('updated-module-tags');
|
||||
var modal = $(this).closest(".panel-default").find('.tags-modal-component').data('tagsModal').open();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,723 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
||||
|
||||
// TODO
|
||||
// - error handling of assigning user to project, check XHR data.errors
|
||||
// - error handling of removing user from project, check XHR data.errors
|
||||
// - refresh project users tab after manage user modal is closed
|
||||
// - refactor view handling using library, ex. backbone.js
|
||||
|
||||
/* global HelperModule dropdownSelector Turbolinks filterDropdown InfiniteScroll
|
||||
AsyncDropdown GLOBAL_CONSTANTS loadPlaceHolder */
|
||||
/* eslint-disable no-use-before-define */
|
||||
|
||||
var ProjectsIndex = (function() {
|
||||
var projectsWrapper = '#projectsWrapper';
|
||||
var cardsWrapper = '#cardsWrapper';
|
||||
var editProjectModal = '#edit-modal';
|
||||
var moveToModal = '#move-to-modal';
|
||||
var pageSize = GLOBAL_CONSTANTS.DEFAULT_ELEMENTS_PER_PAGE;
|
||||
|
||||
var exportProjectsModal = null;
|
||||
var exportProjectsModalHeader = null;
|
||||
var exportProjectsModalBody = null;
|
||||
var exportProjectsBtn = '.export-projects-btn';
|
||||
var exportProjectsSubmit = '#export-projects-modal-submit';
|
||||
|
||||
let projectsCurrentSort;
|
||||
let projectsViewSearch;
|
||||
let createdOnFromFilter;
|
||||
let createdOnToFilter;
|
||||
let membersFilter;
|
||||
let lookInsideFolders;
|
||||
let archivedOnFromFilter;
|
||||
let archivedOnToFilter;
|
||||
let currentFilters;
|
||||
|
||||
// Arrays with selected project and folder IDs shared between both views
|
||||
var selectedProjects = [];
|
||||
var selectedProjectFolders = [];
|
||||
var singleSelectedProject;
|
||||
var destinationFolder;
|
||||
|
||||
// Init new project folder modal function
|
||||
function initNewProjectFolderModal() {
|
||||
var newProjectFolderModal = '#new-project-folder-modal';
|
||||
|
||||
// Modal's submit handler function
|
||||
$(projectsWrapper)
|
||||
.on('ajax:success', newProjectFolderModal, function(ev, data) {
|
||||
$(newProjectFolderModal).modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', newProjectFolderModal, function(e, data) {
|
||||
let form = $(this).find('.new_project_folder');
|
||||
form.renderFormErrors('project_folder', data.responseJSON);
|
||||
});
|
||||
|
||||
$(projectsWrapper)
|
||||
.on('ajax:success', '.new-project-folder-btn', function(e, data) {
|
||||
// Add and show modal
|
||||
$(projectsWrapper).append($.parseHTML(data.html));
|
||||
$(newProjectFolderModal).modal('show');
|
||||
$(newProjectFolderModal).find("input[type='text']").focus();
|
||||
// Remove modal when it gets closed
|
||||
$(newProjectFolderModal).on('hidden.bs.modal', function() {
|
||||
$(newProjectFolderModal).remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the JS for new project modal to work.
|
||||
*/
|
||||
function initNewProjectModal() {
|
||||
var newProjectModal = '#new-project-modal';
|
||||
|
||||
// Modal's submit handler function
|
||||
$(projectsWrapper)
|
||||
.on('submit', '#new_project', function() {
|
||||
$('#new-project-modal button[type="submit"]').prop('disabled', true);
|
||||
})
|
||||
.on('ajax:complete', '#new_project', function() {
|
||||
$('#new-project-modal button[type="submit"]').prop('disabled', false);
|
||||
})
|
||||
.on('ajax:success', newProjectModal, function(_ev, data) {
|
||||
$(newProjectModal).modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', newProjectModal, function(_ev, data) {
|
||||
$(this).renderFormErrors('project', data.responseJSON);
|
||||
});
|
||||
|
||||
$(projectsWrapper)
|
||||
.on('ajax:success', '.new-project-btn', function(_ev, data) {
|
||||
// Add and show modal
|
||||
$(projectsWrapper).append($.parseHTML(data.html));
|
||||
$(newProjectModal).modal('show');
|
||||
$(newProjectModal).find("input[type='text']").focus();
|
||||
// init select picker
|
||||
$(newProjectModal).find('.selectpicker').selectpicker();
|
||||
// Remove modal when it gets closed
|
||||
$(newProjectModal).on('hidden.bs.modal', function() {
|
||||
$(newProjectModal).remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// init delete project folders
|
||||
function initDeleteFoldersToolbarButton() {
|
||||
$(document)
|
||||
.on('ajax:success', '.delete-folders-form', function(ev, data) {
|
||||
$('.modal-project-folder-delete').modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', '.delete-folders-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// init project toolbar archive/restore functions
|
||||
function initArchiveRestoreToolbarButtons() {
|
||||
$(projectsWrapper)
|
||||
.on('ajax:before', '.archive-projects-form, .restore-projects-form', function() {
|
||||
let buttonForm = $(this);
|
||||
buttonForm.find('input[name="projects_ids[]"]').remove();
|
||||
selectedProjects.forEach(function(id) {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'projects_ids[]',
|
||||
value: id
|
||||
}).appendTo(buttonForm);
|
||||
});
|
||||
})
|
||||
.on('ajax:success', '.archive-projects-form, .restore-projects-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
// Project saved, reload view
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', '.archive-projects-form, .restore-projects-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// init project card archive/restore function
|
||||
function initArchiveRestoreButton() {
|
||||
$(projectsWrapper)
|
||||
.on('ajax:success', '.project-archive-restore-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', '.project-archive-restore-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Initialize the JS for export projects modal to work.
|
||||
*/
|
||||
function initExportProjectsModal() {
|
||||
$(projectsWrapper).on('click', exportProjectsBtn, function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const projectId = $(this).data('projectId');
|
||||
singleSelectedProject = projectId;
|
||||
|
||||
// Load HTML to refresh users list
|
||||
$.ajax({
|
||||
url: $(exportProjectsBtn).data('url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
project_ids: projectId ? [projectId] : selectedProjects,
|
||||
project_folder_ids: projectId ? [] : selectedProjectFolders
|
||||
},
|
||||
success: function(data) {
|
||||
// Update modal title
|
||||
exportProjectsModalHeader.text(data.title);
|
||||
|
||||
// Set modal body
|
||||
exportProjectsModalBody.html(data.html);
|
||||
|
||||
// Update modal footer (show/hide buttons)
|
||||
if (data.status === 'error') {
|
||||
$('#export-projects-modal-close').show();
|
||||
$('#export-projects-modal-cancel').hide();
|
||||
$(exportProjectsSubmit).hide();
|
||||
} else {
|
||||
$('#export-projects-modal-close').hide();
|
||||
$('#export-projects-modal-cancel').show();
|
||||
$(exportProjectsSubmit).show();
|
||||
}
|
||||
// Show the modal
|
||||
exportProjectsModal.modal('show');
|
||||
},
|
||||
error: function(data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove modal content when modal window is closed.
|
||||
exportProjectsModal.on('hidden.bs.modal', function() {
|
||||
exportProjectsModalHeader.html('');
|
||||
exportProjectsModalBody.html('');
|
||||
});
|
||||
}
|
||||
|
||||
function initSelectAllCheckbox() {
|
||||
$(projectsWrapper).on('click', '.sci-checkbox.select-all', function() {
|
||||
var selectAll = this.checked;
|
||||
$.each($('.folder-card-selector, .project-card-selector'), function() {
|
||||
if (this.checked !== selectAll) this.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initExportProjects() {
|
||||
// Submit the export projects
|
||||
$(exportProjectsSubmit).click(function() {
|
||||
$.ajax({
|
||||
url: $(exportProjectsSubmit).data('export-projects-submit-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
project_ids: selectedProjects.length > 0 ? selectedProjects : [singleSelectedProject],
|
||||
project_folder_ids: selectedProjectFolders
|
||||
},
|
||||
success: function(data) {
|
||||
// Hide modal and show success flash
|
||||
exportProjectsModal.modal('hide');
|
||||
HelperModule.flashAlertMsg(data.flash, 'success');
|
||||
},
|
||||
error: function() {
|
||||
HelperModule.flashAlertMsg(I18n.t('projects.export_projects.error_flash'), 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectedCards() {
|
||||
$('.project-card').removeClass('selected');
|
||||
$.each(selectedProjects, function(index, value) {
|
||||
let selectedCard = $('.project-card[data-id="' + value + '"]');
|
||||
selectedCard.addClass('selected');
|
||||
});
|
||||
}
|
||||
|
||||
function updateProjectsToolbar() {
|
||||
if (window.actionToolbarComponent) {
|
||||
window.actionToolbarComponent.fetchActions({
|
||||
project_ids: selectedProjects,
|
||||
project_folder_ids: selectedProjectFolders
|
||||
});
|
||||
window.actionToolbarComponent.setReloadCallback(refreshCurrentView);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshCurrentView() {
|
||||
loadCardsView();
|
||||
window.navigatorContainer.reloadCurrentLevel = true;
|
||||
}
|
||||
|
||||
function initEditButton() {
|
||||
function loadEditModal(url) {
|
||||
$.get(url, function(result) {
|
||||
$(editProjectModal).find('.modal-content').html(result.html);
|
||||
$(editProjectModal).modal('show');
|
||||
|
||||
['project_name', 'project_folder_name'].forEach(function(inputId) {
|
||||
var inputField = $('#' + inputId);
|
||||
var value = inputField.val();
|
||||
inputField.focus().val('').val(value);
|
||||
})
|
||||
|
||||
$(editProjectModal).find('.selectpicker').selectpicker();
|
||||
$(editProjectModal).find('form')
|
||||
.on('ajax:success', function(ev, data) {
|
||||
$(editProjectModal).modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
refreshCurrentView();
|
||||
}).on('ajax:error', function(ev, data) {
|
||||
if ($(this).hasClass('edit_project')) {
|
||||
$(this).renderFormErrors('project', data.responseJSON.errors);
|
||||
} else {
|
||||
$(this).renderFormErrors('project_folder', data.responseJSON.errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(projectsWrapper).on('click', '.edit-btn', function(ev) {
|
||||
var editUrl = $(`.project-card[data-id=${selectedProjects[0]}]`).data('edit-url') ||
|
||||
$(`.folder-card[data-id=${selectedProjectFolders[0]}]`).data('edit-url');
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
loadEditModal(editUrl);
|
||||
});
|
||||
|
||||
$('.projects-container').on('click', '.edit-project-btn', function(ev) {
|
||||
var editUrl = $(this).attr('href');
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
loadEditModal(editUrl);
|
||||
});
|
||||
}
|
||||
|
||||
function initMoveButton() {
|
||||
function initializeJSTree(foldersTree) {
|
||||
var timeOut = false;
|
||||
|
||||
foldersTree.jstree({
|
||||
core: {
|
||||
multiple: false,
|
||||
themes: {
|
||||
dots: false,
|
||||
variant: 'large'
|
||||
}
|
||||
},
|
||||
search: {
|
||||
show_only_matches: true,
|
||||
show_only_matches_children: true
|
||||
},
|
||||
plugins: ['wholerow', 'search']
|
||||
});
|
||||
|
||||
foldersTree.on('changed.jstree', function(e, data) {
|
||||
destinationFolder = data.instance.get_node(data.selected[0]).id.replace('folder_', '');
|
||||
});
|
||||
|
||||
// Search timeout
|
||||
$('#searchFolderTree').keyup(function() {
|
||||
if (timeOut) { clearTimeout(timeOut); }
|
||||
timeOut = setTimeout(function() {
|
||||
foldersTree.jstree(true).search($('#searchFolderTree').val());
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoveToModal(url, projectId) {
|
||||
let items;
|
||||
let projects;
|
||||
let folders;
|
||||
|
||||
if (projectId) {
|
||||
items = 'projects';
|
||||
} else if ((selectedProjects.length) && (selectedProjectFolders.length)) {
|
||||
items = 'project_and_folders';
|
||||
} else if (selectedProjectFolders.length) {
|
||||
items = 'folders';
|
||||
} else {
|
||||
items = 'projects';
|
||||
}
|
||||
|
||||
let movables;
|
||||
if (projectId) {
|
||||
movables = [{
|
||||
id: projectId,
|
||||
type: 'project'
|
||||
}];
|
||||
} else {
|
||||
projects = selectedProjects.map(e => ({ id: e, type: 'project' }));
|
||||
folders = selectedProjectFolders.map(e => ({ id: e, type: 'project_folder' }));
|
||||
movables = projects.concat(folders);
|
||||
}
|
||||
|
||||
$.get(url, { items: items, sort: projectsCurrentSort, view_mode: $('.projects-index').data('view-mode') }, function(result) {
|
||||
$(moveToModal).find('.modal-content').html(result.html);
|
||||
$(moveToModal).modal('show');
|
||||
initializeJSTree($(moveToModal).find('#moveToFolders'));
|
||||
$('#searchFolderTree').focus();
|
||||
$(moveToModal).find('form')
|
||||
.on('ajax:before', function() {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'destination_folder_id',
|
||||
value: destinationFolder
|
||||
}).appendTo($(this));
|
||||
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'movables',
|
||||
value: JSON.stringify(movables)
|
||||
}).appendTo($(this));
|
||||
})
|
||||
.on('ajax:success', function(ev, data) {
|
||||
$(moveToModal).modal('hide');
|
||||
HelperModule.flashAlertMsg(data.flash, 'success');
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', function(ev, data) {
|
||||
$(moveToModal).modal('hide');
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(projectsWrapper).on('click', '.move-projects-btn', function(e) {
|
||||
e.preventDefault();
|
||||
loadMoveToModal($(this).data('url'), $(this).data('projectId'));
|
||||
});
|
||||
}
|
||||
|
||||
function initCardData(viewContainer, data) {
|
||||
viewContainer.data('projects-cards-url', data.projects_cards_url);
|
||||
viewContainer.removeClass('no-results no-data');
|
||||
viewContainer.find('.card, .projects-group, .no-results-container, .no-data-container').remove();
|
||||
|
||||
viewContainer.append(data.cards_html);
|
||||
|
||||
if (viewContainer.hasClass('list')) {
|
||||
viewContainer.find('.table-header').show();
|
||||
}
|
||||
|
||||
if (viewContainer.find('.no-results-container').length) {
|
||||
viewContainer.addClass('no-results');
|
||||
}
|
||||
|
||||
if (viewContainer.find('.no-data-container').length) {
|
||||
viewContainer.addClass('no-data');
|
||||
viewContainer.find('.table-header').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function loadCardsView() {
|
||||
var requestParams = {
|
||||
view_mode: $('.projects-index').data('view-mode'),
|
||||
sort: projectsCurrentSort,
|
||||
search: projectsViewSearch,
|
||||
members: membersFilter && membersFilter.map(m => m.value),
|
||||
created_on_from: createdOnFromFilter,
|
||||
created_on_to: createdOnToFilter,
|
||||
folders_search: lookInsideFolders,
|
||||
archived_on_from: archivedOnFromFilter,
|
||||
archived_on_to: archivedOnToFilter
|
||||
};
|
||||
var viewContainer = $(cardsWrapper);
|
||||
var cardsUrl = viewContainer.data('projects-cards-url');
|
||||
|
||||
loadPlaceHolder($(cardsWrapper), $('#projectPlaceholder'), '.project-placeholder');
|
||||
$.ajax({
|
||||
url: cardsUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: { ...requestParams, ...{ page: 1 } },
|
||||
success: function(data) {
|
||||
$(projectsWrapper).find('.projects-title').html(data.title_html);
|
||||
|
||||
initCardData(viewContainer, data);
|
||||
|
||||
selectedProjects.length = 0;
|
||||
selectedProjectFolders.length = 0;
|
||||
|
||||
updateProjectsToolbar();
|
||||
|
||||
if (data.filtered) {
|
||||
$(projectsWrapper).find('.project-list-end-placeholder').remove();
|
||||
if (window.innerWidth > document.body.clientWidth) {
|
||||
$($($(cardsWrapper).data('config').endOfListTemplate).html()).appendTo($(cardsWrapper));
|
||||
}
|
||||
} else {
|
||||
InfiniteScroll.init(cardsWrapper, {
|
||||
url: cardsUrl,
|
||||
eventTarget: window,
|
||||
placeholderTemplate: '#projectPlaceholder',
|
||||
endOfListTemplate: '#projectEndOfList',
|
||||
pageSize: pageSize,
|
||||
lastPage: !data.next_page,
|
||||
customResponse: (response) => {
|
||||
$(response.cards_html).appendTo(cardsWrapper);
|
||||
},
|
||||
customParams: (params) => {
|
||||
return { ...params, ...requestParams };
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
viewContainer.html('Error loading project list');
|
||||
},
|
||||
complete: function() {
|
||||
updateSelectAllCheckbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initProjectsViewModeSwitch() {
|
||||
let projectsPageSelector = '.projects-index';
|
||||
$('.button-to.selected').removeClass('btn-light');
|
||||
$('.button-to.selected').addClass('form-dropdown-state-item');
|
||||
$('.button-to.selected .view-switch-list-span').css('color', 'white');
|
||||
$('.button-to.selected').removeClass('selected');
|
||||
|
||||
$(projectsPageSelector)
|
||||
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
|
||||
$('.view-switch-btn-name').text($(ev.target).find('.button-to').text());
|
||||
|
||||
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
|
||||
|
||||
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('form-dropdown-state-item');
|
||||
$(projectsPageSelector).find('.cards-switch .button-to').addClass('btn-light');
|
||||
$(projectsPageSelector).find('.cards-switch .button-to .view-switch-list-span').css('color', 'black');
|
||||
|
||||
$(ev.target).find('.button-to').removeClass('btn-light');
|
||||
$(ev.target).find('.button-to').addClass('form-dropdown-state-item');
|
||||
$(ev.target).find('.button-to .view-switch-list-span').css('color', 'white');
|
||||
|
||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||
InfiniteScroll.loadMore(cardsWrapper);
|
||||
})
|
||||
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
});
|
||||
|
||||
// Active/Archived switch
|
||||
// We have different sorting, filters for active/archived views.
|
||||
// With turbolinks visit all those elements are updated.
|
||||
$(projectsPageSelector).on('click', '.archive-switch', function() {
|
||||
$(projectsPageSelector).find('.projects-container').remove();
|
||||
Turbolinks.visit($(this).data('url'));
|
||||
});
|
||||
}
|
||||
|
||||
function initSorting() {
|
||||
$('#sortMenuDropdown a').on('click', function() {
|
||||
if (projectsCurrentSort !== $(this).data('sort')) {
|
||||
$('#sortMenuDropdown a').removeClass('selected');
|
||||
projectsCurrentSort = $(this).data('sort');
|
||||
loadCardsView();
|
||||
$(this).addClass('selected');
|
||||
$('#sortMenu').dropdown('toggle');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectDate($field) {
|
||||
let datePicker = $field.data('dateTimePicker');
|
||||
if (datePicker && datePicker.date) {
|
||||
return datePicker.date.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initProjectsFilters() {
|
||||
var $filterDropdown = filterDropdown.init(filtersEnabled);
|
||||
let $projectsFilter = $('.projects-index .projects-filters');
|
||||
let $membersFilter = $('.members-filter', $projectsFilter);
|
||||
let $foldersCB = $('#folder_search', $projectsFilter);
|
||||
let $createdOnFromFilter = $('.created-on-filter .from-date', $projectsFilter);
|
||||
let $createdOnToFilter = $('.created-on-filter .to-date', $projectsFilter);
|
||||
let $archivedOnFromFilter = $('.archived-on-filter .from-date', $projectsFilter);
|
||||
let $archivedOnToFilter = $('.archived-on-filter .to-date', $projectsFilter);
|
||||
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
|
||||
|
||||
function getFilterValues() {
|
||||
createdOnFromFilter = selectDate($createdOnFromFilter);
|
||||
createdOnToFilter = selectDate($createdOnToFilter);
|
||||
membersFilter = dropdownSelector.getData($('.members-filter'));
|
||||
lookInsideFolders = $foldersCB.prop('checked') || '';
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter) || $archivedOnFromFilter.val();
|
||||
archivedOnToFilter = selectDate($archivedOnToFilter) || $archivedOnToFilter.val();
|
||||
projectsViewSearch = $textFilter.val();
|
||||
}
|
||||
|
||||
function filtersEnabled() {
|
||||
getFilterValues();
|
||||
|
||||
return projectsViewSearch
|
||||
|| createdOnFromFilter
|
||||
|| createdOnToFilter
|
||||
|| (membersFilter && membersFilter.length !== 0)
|
||||
|| lookInsideFolders
|
||||
|| archivedOnFromFilter
|
||||
|| archivedOnToFilter;
|
||||
}
|
||||
|
||||
function appliedFiltersMark() {
|
||||
filterDropdown.toggleFilterMark($filterDropdown, filtersEnabled());
|
||||
}
|
||||
|
||||
dropdownSelector.init($membersFilter, {
|
||||
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'
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '#folderSearchInfoBtn', function(e) {
|
||||
e.stopPropagation();
|
||||
$('#folderSearchInfo').toggle();
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '#folder_search', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$filterDropdown.on('filter:apply', function() {
|
||||
appliedFiltersMark();
|
||||
loadCardsView();
|
||||
});
|
||||
|
||||
// Clear filters
|
||||
$filterDropdown.on('filter:clear', function() {
|
||||
currentFilters = null;
|
||||
|
||||
dropdownSelector.clearData($membersFilter);
|
||||
|
||||
$createdOnFromFilter.data('dateTimePicker').clearDate();
|
||||
$createdOnToFilter.data('dateTimePicker').clearDate();
|
||||
$archivedOnFromFilter.data('dateTimePicker').clearDate();
|
||||
$archivedOnToFilter.data('dateTimePicker').clearDate();
|
||||
$foldersCB.prop('checked', false);
|
||||
$textFilter.val('');
|
||||
});
|
||||
|
||||
// Prevent filter window close
|
||||
$filterDropdown.on('filter:clickBody', function() {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
dropdownSelector.closeDropdown($membersFilter);
|
||||
$('#folderSearchInfo').hide();
|
||||
});
|
||||
|
||||
appliedFiltersMark();
|
||||
}
|
||||
|
||||
function updateSelectAllCheckbox() {
|
||||
const tableWrapper = $(cardsWrapper);
|
||||
const checkboxesCount = $(
|
||||
'.sci-checkbox.folder-card-selector, .sci-checkbox.project-card-selector',
|
||||
tableWrapper
|
||||
).length;
|
||||
const selectedCheckboxesCount = selectedProjects.length + selectedProjectFolders.length;
|
||||
const selectAllCheckbox = $('.sci-checkbox.select-all', tableWrapper);
|
||||
|
||||
selectAllCheckbox.prop('indeterminate', false);
|
||||
if (selectedCheckboxesCount === 0) {
|
||||
selectAllCheckbox.prop('checked', false);
|
||||
} else if (selectedCheckboxesCount === checkboxesCount) {
|
||||
selectAllCheckbox.prop('checked', true);
|
||||
} else {
|
||||
selectAllCheckbox.prop('indeterminate', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes cards view
|
||||
*/
|
||||
function init() {
|
||||
exportProjectsModal = $('#export-projects-modal');
|
||||
exportProjectsModalHeader = exportProjectsModal.find('.modal-title');
|
||||
exportProjectsModalBody = exportProjectsModal.find('.modal-body');
|
||||
|
||||
window.initActionToolbar();
|
||||
|
||||
updateSelectedCards();
|
||||
initNewProjectFolderModal();
|
||||
initNewProjectModal();
|
||||
initExportProjectsModal();
|
||||
initExportProjects();
|
||||
initDeleteFoldersToolbarButton();
|
||||
initArchiveRestoreToolbarButtons();
|
||||
initEditButton();
|
||||
initMoveButton();
|
||||
initSelectAllCheckbox();
|
||||
initArchiveRestoreButton();
|
||||
loadCardsView();
|
||||
initProjectsViewModeSwitch();
|
||||
initProjectsFilters();
|
||||
initSorting();
|
||||
AsyncDropdown.init($(projectsWrapper));
|
||||
|
||||
$(projectsWrapper).on('click', '.folder-card-selector', function() {
|
||||
let folderCard = $(this).closest('.folder-card');
|
||||
let folderId = folderCard.data('id');
|
||||
let index = $.inArray(folderId, selectedProjectFolders);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected folder IDs
|
||||
if (this.checked && index === -1) {
|
||||
selectedProjectFolders.push(folderId);
|
||||
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
|
||||
} else if (!this.checked && index !== -1) {
|
||||
selectedProjectFolders.splice(index, 1);
|
||||
}
|
||||
|
||||
updateSelectAllCheckbox();
|
||||
updateProjectsToolbar();
|
||||
});
|
||||
|
||||
$(projectsWrapper).on('click', '.project-card-selector', function() {
|
||||
let projectCard = $(this).closest('.project-card');
|
||||
let projectId = projectCard.data('id');
|
||||
// Determine whether ID is in the list of selected project IDs
|
||||
let index = $.inArray(projectId, selectedProjects);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected project IDs
|
||||
if (this.checked && index === -1) {
|
||||
$(this).closest('.project-card').addClass('selected');
|
||||
selectedProjects.push(projectId);
|
||||
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
|
||||
} else if (!this.checked && index !== -1) {
|
||||
$(this).closest('.project-card').removeClass('selected');
|
||||
selectedProjects.splice(index, 1);
|
||||
}
|
||||
|
||||
updateSelectAllCheckbox();
|
||||
updateProjectsToolbar();
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
loadCardsView: loadCardsView
|
||||
};
|
||||
}());
|
|
@ -1,20 +0,0 @@
|
|||
$("#content-wrapper")
|
||||
.on(
|
||||
"ajax:success",
|
||||
function(e, data) {
|
||||
if ($(e.target).is("[data-role='edit-step-form'], [data-role='new-step-form']")) {
|
||||
// Get the updated_at URL
|
||||
var url = $("[data-role='updated-at-label-url']").attr("data-url");
|
||||
|
||||
// Fetch new updated at label
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data2) {
|
||||
$("[data-role='updated-at-refresh']").html(data2.html);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
|
@ -1,46 +0,0 @@
|
|||
/* global dropdownSelector TinyMCE */
|
||||
var ProtocolRepositoryHeader = (function() {
|
||||
function initEditKeywords() {
|
||||
dropdownSelector.init('#keyword-input-field', {
|
||||
inputTagMode: true,
|
||||
onChange: function() {
|
||||
$.ajax({
|
||||
url: $('#keyword-input-field').data('update-url'),
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
data: { keywords: dropdownSelector.getValues('#keyword-input-field') },
|
||||
success: function() {
|
||||
dropdownSelector.highlightSuccess('#keyword-input-field');
|
||||
},
|
||||
error: function() {
|
||||
dropdownSelector.highlightError('#keyword-input-field');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initEditDescription() {
|
||||
var viewObject = $('#protocol_description_view');
|
||||
viewObject.on('click', function(e) {
|
||||
if ($(e.target).hasClass('record-info-link') || $(e.target).parent().hasClass('record-info-link')) return;
|
||||
TinyMCE.init('#protocol_description_textarea');
|
||||
}).on('click', 'a', function(e) {
|
||||
if ($(this).hasClass('record-info-link')) return;
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.protocol-repository-header').length > 0) {
|
||||
initEditKeywords();
|
||||
initEditDescription();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
ProtocolRepositoryHeader.init();
|
||||
});
|
|
@ -1,7 +1,8 @@
|
|||
//= require protocols/import_export/import
|
||||
/* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */
|
||||
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile _ PerfectSb protocolsIO
|
||||
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
||||
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile
|
||||
protocolFileImportModal PerfectSb protocolsIO
|
||||
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
||||
|
||||
// Global variables
|
||||
var ProtocolsIndex = (function() {
|
||||
|
@ -27,10 +28,10 @@ var ProtocolsIndex = (function() {
|
|||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
window.initActionToolbar();
|
||||
window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||
// window.initActionToolbar();
|
||||
// window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||
// make room for pagination
|
||||
window.actionToolbarComponent.setBottomOffset(68);
|
||||
// window.actionToolbarComponent.setBottomOffset(68);
|
||||
updateButtons();
|
||||
initProtocolsTable();
|
||||
initKeywordFiltering();
|
||||
|
@ -38,6 +39,7 @@ var ProtocolsIndex = (function() {
|
|||
initLinkedChildrenModal();
|
||||
initModals();
|
||||
initVersionsModal();
|
||||
initLocalFileImport();
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
|
@ -267,7 +269,6 @@ var ProtocolsIndex = (function() {
|
|||
let protocolFilters = $($('#protocolFilters').html());
|
||||
$(protocolFilters).appendTo('.protocols-container .protocol-filters');
|
||||
|
||||
initLocalFileImport();
|
||||
initProtocolsFilters();
|
||||
initRowSelection();
|
||||
},
|
||||
|
@ -649,8 +650,6 @@ var ProtocolsIndex = (function() {
|
|||
}
|
||||
|
||||
function updateButtons() {
|
||||
window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected });
|
||||
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelected.length > 0 ? 46 : 0}px`);
|
||||
}
|
||||
|
||||
function initLocalFileImport() {
|
||||
|
@ -673,7 +672,6 @@ var ProtocolsIndex = (function() {
|
|||
var importUrl = fileInput.attr('data-import-url');
|
||||
var teamId = fileInput.attr('data-team-id');
|
||||
var type = fileInput.attr('data-type');
|
||||
|
||||
if(ev.target.files[0].name.split('.').pop() === 'eln') {
|
||||
importProtocolFromFile(
|
||||
ev.target.files[0],
|
||||
|
@ -691,14 +689,14 @@ var ProtocolsIndex = (function() {
|
|||
|
||||
if (nrSuccessful) {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
|
||||
reloadTable();
|
||||
window.protocolsTable.$refs.table.updateTable();
|
||||
} else {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
protocolFileImportModal.init(ev.target.files, reloadTable);
|
||||
protocolFileImportModal.init(ev.target.files, window.protocolsTable.$refs.table.updateTable());
|
||||
}
|
||||
// $(this).val('');
|
||||
});
|
||||
|
|
|
@ -215,7 +215,7 @@ var protocolsIO = function() {
|
|||
animateSpinner(modal, false);
|
||||
modal.modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
ProtocolsIndex.reloadTable();
|
||||
window.protocolsTable.$refs.table.updateTable();
|
||||
},
|
||||
error: function(data) {
|
||||
showFormErrors(modal, data.responseJSON.validation_errors);
|
||||
|
|
|
@ -224,7 +224,7 @@
|
|||
},
|
||||
createdRow: addAttributesToRow,
|
||||
initComplete: function(settings) {
|
||||
initActionToolbar();
|
||||
window.initActionToolbar();
|
||||
actionToolbarComponent.setBottomOffset(68);
|
||||
|
||||
const { nTableWrapper: dataTableWrapper } = settings;
|
||||
|
|
|
@ -1,279 +0,0 @@
|
|||
/* globals I18n animateSpinner HelperModule */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var INVENTORY_PICKER;
|
||||
var COLUMN_PICKER;
|
||||
var ITEM_PICKER;
|
||||
var SELECTED_IDS = {
|
||||
repository_id: null,
|
||||
respository_column_id: null,
|
||||
repository_item_id: null
|
||||
};
|
||||
|
||||
function clearErrors() {
|
||||
var $columnsAlertSection = $('#save-PDF-to-inventory-column-warnings');
|
||||
var $itemsAlertSection = $('#save-PDF-to-inventory-warnings');
|
||||
$itemsAlertSection.empty();
|
||||
$columnsAlertSection.empty();
|
||||
}
|
||||
|
||||
function toggleHasFileErrorMessage(value) {
|
||||
var element = $('#selectInventoryItem [value="' + value + '"]');
|
||||
var $alertSection = $('#save-PDF-to-inventory-warnings');
|
||||
$alertSection.empty();
|
||||
if (element.data('hasfile')) {
|
||||
$alertSection.append(
|
||||
`<div class="alert alert-danger">
|
||||
${I18n.t('projects.reports.new.save_PDF_to_inventory_modal.asset_present_warning_html')}
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function appendSearchResults(data) {
|
||||
var items = [];
|
||||
if (data.hasOwnProperty('results')) {
|
||||
$.each(data.results, function(index, el) {
|
||||
items.push(
|
||||
{
|
||||
value: el.id,
|
||||
text: el.name,
|
||||
disabled: false
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function appendSearchResultsForItems(data) {
|
||||
var items = [];
|
||||
if (data.hasOwnProperty('results')) {
|
||||
$('#selectInventoryItem').parent().find('button').attr('disabled', false);
|
||||
$.each(data.results, function(index, el) {
|
||||
items.push(
|
||||
{
|
||||
value: el.id,
|
||||
text: el.name,
|
||||
disabled: false,
|
||||
data: {
|
||||
hasFile: el.hasOwnProperty('has_file_attached') ? el.has_file_attached : null
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
$('#selectInventoryItem').parent().find('button').attr('disabled', true);
|
||||
clearErrors();
|
||||
$('#save-PDF-to-inventory-warnings').append(
|
||||
`<strong class="danger">${data.no_items}</strong>`
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function appendSearchResultsForColumns(data) {
|
||||
var items = [];
|
||||
if (data.hasOwnProperty('results')) {
|
||||
$('#selectInventoryColumn').parent().find('button').attr('disabled', false);
|
||||
$.each(data.results, function(index, el) {
|
||||
items.push(
|
||||
{
|
||||
value: el.id,
|
||||
text: el.name,
|
||||
disabled: false
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
$('#selectInventoryColumn').parent().find('button').attr('disabled', true);
|
||||
clearErrors();
|
||||
$('#save-PDF-to-inventory-column-warnings').append(
|
||||
`<div class="save-PDF-to-inventory-alerts"><strong class="danger">${data.no_items}</strong></div>`
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function submitButtonEnableToggle(status) {
|
||||
var button = $('#savePDFtoInventorySubmit');
|
||||
if (status) {
|
||||
button.attr('disabled', false);
|
||||
} else {
|
||||
button.attr('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
function deselect(element) {
|
||||
if (element) {
|
||||
element.selectpicker('val', null);
|
||||
element.selectpicker('render');
|
||||
element.attr('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
// triggers first request and cleans the dropdown selectpicker
|
||||
function clearDropdownResultsCallback(element) {
|
||||
element.parent().find('button').on('click', function() {
|
||||
$(this).parent().find('input').trigger('keyup');
|
||||
});
|
||||
}
|
||||
|
||||
function initInventoryItemSelectPicker() {
|
||||
ITEM_PICKER = $('#selectInventoryItem')
|
||||
.attr('disabled', false)
|
||||
.selectpicker({ liveSearch: true })
|
||||
.ajaxSelectPicker({
|
||||
ajax: {
|
||||
url: $('#selectInventoryItem').data('target-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: function() {
|
||||
return {
|
||||
q: '{{{q}}}',
|
||||
repository_id: SELECTED_IDS.repository_id,
|
||||
repository_column_id: SELECTED_IDS.respository_column_id
|
||||
};
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
|
||||
},
|
||||
preprocessData: appendSearchResultsForItems,
|
||||
emptyRequest: true,
|
||||
clearOnEmpty: true,
|
||||
cache: false,
|
||||
preserveSelected: false
|
||||
})
|
||||
.on('change.bs.select', function(el) {
|
||||
var value = el.target.value;
|
||||
toggleHasFileErrorMessage(value);
|
||||
submitButtonEnableToggle(true);
|
||||
SELECTED_IDS.repository_item_id = value;
|
||||
});
|
||||
clearDropdownResultsCallback(ITEM_PICKER);
|
||||
}
|
||||
|
||||
function initInventoryColumnSelectPicker() {
|
||||
COLUMN_PICKER = $('#selectInventoryColumn')
|
||||
.attr('disabled', false)
|
||||
.selectpicker({ liveSearch: true })
|
||||
.ajaxSelectPicker({
|
||||
ajax: {
|
||||
url: $('#selectInventoryColumn').data('target-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: function() {
|
||||
return {
|
||||
q: '{{{q}}}',
|
||||
repository_id: SELECTED_IDS.repository_id
|
||||
};
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
|
||||
},
|
||||
preprocessData: appendSearchResultsForColumns,
|
||||
emptyRequest: true,
|
||||
clearOnEmpty: true,
|
||||
cache: false,
|
||||
preserveSelected: false
|
||||
})
|
||||
.on('change.bs.select', function(el) {
|
||||
SELECTED_IDS.respository_column_id = el.target.value;
|
||||
deselect(ITEM_PICKER);
|
||||
submitButtonEnableToggle(false);
|
||||
initInventoryItemSelectPicker();
|
||||
// refresh the dropdown state
|
||||
$('#selectInventoryItem').parent().find('input').trigger('keyup');
|
||||
clearErrors();
|
||||
});
|
||||
clearDropdownResultsCallback(COLUMN_PICKER);
|
||||
}
|
||||
|
||||
function initInventoriesSelectPicker() {
|
||||
INVENTORY_PICKER = $('#selectInventory')
|
||||
.selectpicker({ liveSearch: true })
|
||||
.ajaxSelectPicker({
|
||||
ajax: {
|
||||
url: $('#selectInventory').data('target-url'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: function() {
|
||||
return { q: '{{{q}}}' };
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: I18n.t('projects.reports.new.save_PDF_to_inventory_modal.nothing_selected')
|
||||
},
|
||||
preprocessData: appendSearchResults,
|
||||
emptyRequest: true,
|
||||
clearOnEmpty: false,
|
||||
cache: false,
|
||||
preserveSelected: false
|
||||
}).on('change.bs.select', function(el) {
|
||||
SELECTED_IDS.repository_id = el.target.value;
|
||||
deselect(COLUMN_PICKER);
|
||||
deselect(ITEM_PICKER);
|
||||
submitButtonEnableToggle(false);
|
||||
initInventoryColumnSelectPicker();
|
||||
clearErrors();
|
||||
// refresh the dropdown state
|
||||
$('#selectInventoryColumn').parent().find('input').trigger('keyup');
|
||||
});
|
||||
clearDropdownResultsCallback(INVENTORY_PICKER);
|
||||
}
|
||||
|
||||
$('#content-reports-index').on('click', '#savePDFtoInventorySubmit', function() {
|
||||
animateSpinner();
|
||||
$.ajax({
|
||||
url: $('#savePDFtoInventorySubmit').data('target-url'),
|
||||
data: SELECTED_IDS,
|
||||
type: 'POST',
|
||||
success: function(data) {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
$('#savePDFtoInventory').modal('hide');
|
||||
},
|
||||
error: function(xhr) {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(xhr.responseJSON.message, 'danger');
|
||||
$('#savePDFtoInventory').modal('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* INITIALIZERS
|
||||
*/
|
||||
|
||||
function initializeSavePDFtoInventoryModal() {
|
||||
$('#content-reports-index').on('shown.bs.modal', '#savePDFtoInventory', function() {
|
||||
initInventoriesSelectPicker();
|
||||
clearErrors();
|
||||
// refresh the dropdown state
|
||||
$('#selectInventory').parent().find('input').trigger('keyup');
|
||||
}).on('hidden.bs.modal', function() {
|
||||
// clear ids
|
||||
SELECTED_IDS = {
|
||||
repository_id: null,
|
||||
respository_column_id: null,
|
||||
repository_item_id: null
|
||||
};
|
||||
// clear select picker objects
|
||||
|
||||
if (COLUMN_PICKER) {
|
||||
deselect(COLUMN_PICKER);
|
||||
}
|
||||
|
||||
if (ITEM_PICKER) {
|
||||
deselect(ITEM_PICKER);
|
||||
}
|
||||
submitButtonEnableToggle(false);
|
||||
});
|
||||
}
|
||||
|
||||
initializeSavePDFtoInventoryModal();
|
||||
}());
|
|
@ -1,190 +0,0 @@
|
|||
/* global I18n animateSpinner HelperModule
|
||||
DataTableHelpers DataTableCheckboxes notTurbolinksPreview */
|
||||
/* eslint-disable no-param-reassign */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var REPOSITORIES_TABLE;
|
||||
var CHECKBOX_SELECTOR;
|
||||
|
||||
function updateActionButtons() {
|
||||
if (window.actionToolbarComponent) {
|
||||
window.actionToolbarComponent.fetchActions({ repository_ids: CHECKBOX_SELECTOR.selectedRows });
|
||||
$('.dataTables_scrollBody').css('padding-bottom', `${CHECKBOX_SELECTOR.selectedRows.length > 0 ? 68 : 0}px`);
|
||||
}
|
||||
|
||||
var rowsCount = CHECKBOX_SELECTOR.selectedRows.length;
|
||||
var row;
|
||||
$('#renameRepoBtn').attr('href', '#');
|
||||
$('#deleteRepoBtn').attr('href', '#');
|
||||
$('#copyRepoBtn').attr('href', '#');
|
||||
switch (rowsCount) {
|
||||
case 0:
|
||||
$('.main-actions [data-action-mode="single"]').addClass('disabled hidden');
|
||||
$('.main-actions [data-action-mode="multiple"]').addClass('disabled hidden');
|
||||
break;
|
||||
case 1:
|
||||
row = $('#repositoriesList').find('tr#' + CHECKBOX_SELECTOR.selectedRows[0]);
|
||||
$('.main-actions [data-action-mode="single"]').removeClass('disabled hidden');
|
||||
$('.main-actions [data-action-mode="multiple"]').removeClass('disabled hidden');
|
||||
$('#renameRepoBtn').attr('href', row.data('rename-modal-url'));
|
||||
$('#deleteRepoBtn').attr('href', row.data('delete-modal-url'));
|
||||
$('#copyRepoBtn').attr('href', row.data('copy-modal-url'));
|
||||
break;
|
||||
default:
|
||||
$('.main-actions [data-action-mode="single"]').removeClass('hidden').addClass('disabled');
|
||||
$('.main-actions [data-action-mode="multiple"]').removeClass('disabled hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function initRepositoriesDataTable(tableContainer, archived = false) {
|
||||
var tableTemplate = $('#RepositoriesListTable').html();
|
||||
$.get($(tableTemplate).data('source'), { archived: archived }, function(data) {
|
||||
if (REPOSITORIES_TABLE) REPOSITORIES_TABLE.destroy();
|
||||
CHECKBOX_SELECTOR = null;
|
||||
$('.content-body').html(tableTemplate);
|
||||
REPOSITORIES_TABLE = $(tableContainer).DataTable({
|
||||
aaData: data,
|
||||
dom: "R<'main-actions hidden'<'toolbar'><'filter-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
|
||||
processing: true,
|
||||
stateSave: true,
|
||||
pageLength: 25,
|
||||
colReorder: {
|
||||
enable: false
|
||||
},
|
||||
sScrollX: '100%',
|
||||
sScrollXInner: '100%',
|
||||
order: [[1, 'asc']],
|
||||
destroy: true,
|
||||
language: {
|
||||
emptyTable: archived ? I18n.t('repositories.index.no_archived_inventories') : I18n.t('repositories.index.no_inventories'),
|
||||
zeroRecords: archived ? I18n.t('repositories.index.no_archived_inventories_matched') : I18n.t('repositories.index.no_inventories_matched')
|
||||
},
|
||||
columnDefs: [{
|
||||
targets: 0,
|
||||
visible: !$('.repositories-index').data('readonly'),
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
render: function() {
|
||||
return `<div class="sci-checkbox-container">
|
||||
<input class='repository-row-selector sci-checkbox' type='checkbox' data-e2e="e2e-CB-inventories-all">
|
||||
<span class='sci-checkbox-label'></span>
|
||||
</div>`;
|
||||
}
|
||||
}, {
|
||||
targets: 1,
|
||||
className: 'item-name',
|
||||
render: function(value, type, row) {
|
||||
return `<a href="${row.repositoryUrl}" data-e2e="e2e-TL-inventories-Inventory-${row.DT_RowId}">${value}</a>`;
|
||||
}
|
||||
}, {
|
||||
targets: 5,
|
||||
render: {
|
||||
_: 'display',
|
||||
sort: 'sort'
|
||||
}
|
||||
}, {
|
||||
targets: 7,
|
||||
visible: archived,
|
||||
render: {
|
||||
_: 'display',
|
||||
sort: 'sort'
|
||||
}
|
||||
}, {
|
||||
targets: 8,
|
||||
visible: archived
|
||||
},
|
||||
{
|
||||
visible: true,
|
||||
searchable: false,
|
||||
data: 'stock',
|
||||
render: {
|
||||
_: 'display',
|
||||
sort: 'sort'
|
||||
}
|
||||
}],
|
||||
fnInitComplete: function(e) {
|
||||
initActionToolbar();
|
||||
|
||||
window.actionToolbarComponent.setReloadCallback(() =>
|
||||
initRepositoriesDataTable('#repositoriesList', archived));
|
||||
window.actionToolbarComponent.setBottomOffset(68);
|
||||
|
||||
var dataTableWrapper = $(e.nTableWrapper);
|
||||
CHECKBOX_SELECTOR = new DataTableCheckboxes(dataTableWrapper, {
|
||||
checkboxSelector: '.repository-row-selector',
|
||||
selectAllSelector: '.select-all-checkbox',
|
||||
onChanged: function() {
|
||||
updateActionButtons();
|
||||
}
|
||||
});
|
||||
|
||||
updateActionButtons();
|
||||
DataTableHelpers.initLengthAppearance(dataTableWrapper);
|
||||
DataTableHelpers.initSearchField(dataTableWrapper, I18n.t('repositories.index.filter_inventory'));
|
||||
$('.content-body .toolbar').html($('#repositoriesListButtons').html());
|
||||
dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden');
|
||||
|
||||
$('#createRepoBtn').initSubmitModal('#create-repo-modal', 'repository');
|
||||
$('#deleteRepoBtn').initSubmitModal('#delete-repo-modal', 'repository');
|
||||
$('#renameRepoBtn').initSubmitModal('#rename-repo-modal', 'repository');
|
||||
$('#copyRepoBtn').initSubmitModal('#copy-repo-modal', 'repository');
|
||||
},
|
||||
drawCallback: function() {
|
||||
if (CHECKBOX_SELECTOR) CHECKBOX_SELECTOR.checkSelectAllStatus();
|
||||
},
|
||||
rowCallback: function(row) {
|
||||
let $row = $(row);
|
||||
let checkbox = $row.find('.repository-row-selector');
|
||||
|
||||
if ($row.attr('data-shared') === 'true') {
|
||||
checkbox.attr('disabled', 'disabled');
|
||||
}
|
||||
|
||||
if (CHECKBOX_SELECTOR) CHECKBOX_SELECTOR.checkRowStatus(row);
|
||||
},
|
||||
stateLoadParams: function(_, state) {
|
||||
state.search.search = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#wrapper').on('sideBar::hidden sideBar::shown', function() {
|
||||
if (REPOSITORIES_TABLE) {
|
||||
REPOSITORIES_TABLE.columns.adjust();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('shown.bs.modal', function() {
|
||||
var inputField = $('#repository_name');
|
||||
var value = inputField.val();
|
||||
inputField.focus().val('').val(value);
|
||||
}).on('shown.bs.modal', '#export-repositories-modal', function() {
|
||||
if (!CHECKBOX_SELECTOR) return;
|
||||
|
||||
const exportButton = $(this).find('#export-repositories-modal-submit');
|
||||
const exportURL = exportButton.data('export-url');
|
||||
|
||||
exportButton.on('click', function() {
|
||||
$.ajax({
|
||||
url: exportURL,
|
||||
method: 'POST',
|
||||
data: { repository_ids: CHECKBOX_SELECTOR.selectedRows },
|
||||
success: function(data) {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
CHECKBOX_SELECTOR.clearSelection();
|
||||
$('#export-repositories-modal').modal('hide');
|
||||
},
|
||||
error: function(data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.create-new-repository').initSubmitModal('#create-repo-modal', 'repository');
|
||||
if (notTurbolinksPreview()) {
|
||||
initRepositoriesDataTable('#repositoriesList', $('.repositories-index').hasClass('archived'));
|
||||
}
|
||||
}());
|
|
@ -162,9 +162,9 @@ $.fn.dataTable.render.defaultRepositoryNumberValue = function() {
|
|||
return '';
|
||||
};
|
||||
|
||||
$.fn.dataTable.render.RepositoryNumberValue = function(data) {
|
||||
$.fn.dataTable.render.RepositoryNumberValue = function (data) {
|
||||
return `<span class="number-value" data-value="${data.value}">
|
||||
${data.value.toLocaleString('en-US', { timeZone: 'UTC' })}
|
||||
${data.value}
|
||||
</span>`;
|
||||
};
|
||||
|
||||
|
|
|
@ -406,35 +406,6 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
}
|
||||
|
||||
function initRepositoryViewSwitcher() {
|
||||
const viewSwitch = $('.view-switch');
|
||||
const repositoryShow = $('.repository-show');
|
||||
const stateViewSwitchBtnName = $('.state-view-switch-btn-name');
|
||||
const selectedSwitchOptionClass = 'form-dropdown-state-item prevent-shrink';
|
||||
|
||||
function switchView(event, activeClass, inactiveClass) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
repositoryShow.removeClass(inactiveClass).addClass(activeClass);
|
||||
|
||||
$(`.view-switch-${inactiveClass} a`).removeClass(selectedSwitchOptionClass);
|
||||
$(`.view-switch-${activeClass} a`).addClass(selectedSwitchOptionClass);
|
||||
|
||||
stateViewSwitchBtnName.text($(`.view-switch-${activeClass}`).text());
|
||||
viewSwitch.removeClass('open');
|
||||
RepositoryDatatable.reload();
|
||||
}
|
||||
|
||||
viewSwitch.on('click', '.view-switch-archived', function(event) {
|
||||
switchView(event, 'archived', 'active');
|
||||
});
|
||||
|
||||
viewSwitch.on('click', '.view-switch-active', function(event) {
|
||||
switchView(event, 'active', 'archived');
|
||||
});
|
||||
}
|
||||
|
||||
function initExportActions() {
|
||||
const exportModal = $('#exportRepositoryRowsModal');
|
||||
$(document).on('click', '#exportRepositoryRowsButton', (e) => {
|
||||
|
@ -825,7 +796,7 @@ var RepositoryDatatable = (function(global) {
|
|||
initSaveButton();
|
||||
initCancelButton();
|
||||
initBSTooltips();
|
||||
initRepositoryViewSwitcher();
|
||||
window.initRepositoryStateMenu();
|
||||
DataTableHelpers.initLengthAppearance($(TABLE_ID).closest('.dataTables_wrapper'));
|
||||
|
||||
$('.dataTables_wrapper').on('click', '.pagination', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global HelperModule PerfectScrollbar */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const ShareModal = (function() {
|
||||
// eslint-disable-next-line func-names
|
||||
window.ShareModal = (function() {
|
||||
function init() {
|
||||
var form = $('.share-repo-modal').find('form');
|
||||
var sharedCBs = form.find("input[name='share_team_ids[]']");
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
var formGroup = $('#form-records-file').find('.form-group');
|
||||
formGroup.addClass('has-error');
|
||||
formGroup.find('.help-block').remove();
|
||||
$('#form-records-file input[type="submit"]').removeAttr('disabled');
|
||||
$('#parse-sheet-loader').addClass('hidden');
|
||||
formGroup.append(
|
||||
'<span class="help-block">' + XHR.responseJSON.message + '</span>'
|
||||
);
|
||||
|
@ -24,6 +26,8 @@
|
|||
}
|
||||
|
||||
function handleSuccessfulSubmit(data) {
|
||||
$('#form-records-file input[type="submit"]').removeAttr('disabled');
|
||||
$('#parse-sheet-loader').addClass('hidden');
|
||||
$('#modal-import-records').modal('hide');
|
||||
$(data.html).appendTo('body').promise().done(function() {
|
||||
$('#parse-records-modal').modal('show');
|
||||
|
@ -54,6 +58,8 @@
|
|||
|
||||
submitBtn.on('click', function(event) {
|
||||
var data = new FormData();
|
||||
submitBtn.attr('disabled', true);
|
||||
$('#parse-sheet-loader').removeClass('hidden');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
data.append('file', document.getElementById('file').files[0]);
|
||||
|
|
|
@ -31,6 +31,7 @@ var CommentsSidebar = (function() {
|
|||
// Replace the number in comment element
|
||||
commentsCounter.text(commentsCounter.text().replace(/[\d\\+]+/g, commentsAmount));
|
||||
commentsCounter.removeClass('hidden');
|
||||
commentsCounter.css('display', 'flex');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ const GLOBAL_CONSTANTS = {
|
|||
NAME_MAX_LENGTH: <%= Constants::NAME_MAX_LENGTH %>,
|
||||
NAME_MIN_LENGTH: <%= Constants::NAME_MIN_LENGTH %>,
|
||||
TEXT_MAX_LENGTH: <%= Constants::TEXT_MAX_LENGTH %>,
|
||||
TABLE_CARD_MIN_WIDTH: 340, <%# pixels %>
|
||||
TABLE_CARD_GAP: 16, <%# pixels %>
|
||||
FILENAME_TRUNCATION_LENGTH: <%= Constants::FILENAME_TRUNCATION_LENGTH %>,
|
||||
FILE_MAX_SIZE_MB: parseInt($('meta[name="max-file-size"]').attr('content'), 10),
|
||||
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
|
||||
|
|
|
@ -716,15 +716,16 @@ var dropdownSelector = (function() {
|
|||
|
||||
// Add timeout for deleting animation
|
||||
setTimeout(() => {
|
||||
if (selector.data('combine-tags')) {
|
||||
const $selector = $(selector);
|
||||
if ($selector.data('combine-tags')) {
|
||||
// if we use combine-tags options we simply clear all values
|
||||
container.find('.data-field').val('[]');
|
||||
updateTags(selector, container);
|
||||
updateTags($selector, container);
|
||||
} else {
|
||||
// Or delete specific one
|
||||
deleteValue(selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group'));
|
||||
if (selector.data('config').tagClass) {
|
||||
removeOptionFromSelector(selector, tagLabel.data('ds-tag-id'));
|
||||
deleteValue($selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group'));
|
||||
if ($selector.data('config').tagClass) {
|
||||
removeOptionFromSelector($selector, tagLabel.data('ds-tag-id'));
|
||||
}
|
||||
}
|
||||
}, 350);
|
||||
|
|
|
@ -320,13 +320,14 @@ $(document).on('click', '.gene-sequence-edit-button', function() {
|
|||
});
|
||||
|
||||
function initMarvinJs() {
|
||||
if (typeof (ChemicalizeMarvinJs) === 'undefined') {
|
||||
setTimeout(initMarvinJs, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
MarvinJsEditor = MarvinJsEditorApi();
|
||||
|
||||
if (MarvinJsEditor.enabled()) {
|
||||
if (typeof (ChemicalizeMarvinJs) === 'undefined') {
|
||||
setTimeout(initMarvinJs, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote') {
|
||||
ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
|
||||
marvin.setDisplaySettings({ toolbars: 'reporting' });
|
||||
|
|
|
@ -135,3 +135,30 @@ $.fn.initSubmitModal = function(modalID, modelName) {
|
|||
})
|
||||
.animateSpinner();
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps tables in HTML with a specified wrapper.
|
||||
* @param {string || Element} htmlStringOrDomEl - HTML containing tables to be wrapped.
|
||||
* @returns {string} - HTML with tables wrapped.
|
||||
*/
|
||||
function wrapTables(htmlStringOrDomEl) {
|
||||
if (typeof htmlStringOrDomEl === 'string') {
|
||||
const container = $(`<span class="text-base">${htmlStringOrDomEl}</span>`);
|
||||
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: 100%"></div>
|
||||
`);
|
||||
});
|
||||
return container.prop('outerHTML');
|
||||
}
|
||||
// Check if the value is a DOM element
|
||||
if (htmlStringOrDomEl instanceof Element) {
|
||||
const tableElement = $(htmlStringOrDomEl).find('table');
|
||||
if (tableElement.length > 0) {
|
||||
tableElement.wrap('<div class="table-wrapper" style="overflow: auto; width: 100%"></div>');
|
||||
const updatedHtml = $(htmlStringOrDomEl).html();
|
||||
$(htmlStringOrDomEl).replaceWith(updatedHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
(function() {
|
||||
const formErrors = $('#form-error-data').data('form-errors');
|
||||
$('form').renderFormErrors('team', formErrors, false);
|
||||
}());
|
|
@ -81,6 +81,7 @@
|
|||
@import "shared/action_toolbar";
|
||||
@import "shared/assets";
|
||||
@import "shared/avatar";
|
||||
@import "shared/ag_table";
|
||||
@import "shared/cards";
|
||||
@import "shared/comments_sidebar";
|
||||
@import "shared/comments";
|
||||
|
@ -111,6 +112,7 @@
|
|||
@import "themes/repositories";
|
||||
@import "themes/scinote";
|
||||
|
||||
|
||||
@import "navigation/general";
|
||||
@import "navigation/breadcrumbs";
|
||||
@import "navigation/left_menu";
|
||||
|
|
|
@ -51,3 +51,12 @@ html {
|
|||
opacity: 1;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
/* Hide caret in Safari */
|
||||
#relationships-section summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ag-theme-alpine {
|
||||
--ag-font-family: "SN Inter", "Open Sans", Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,6 @@
|
|||
|
||||
#canvas-container,
|
||||
#module-archive {
|
||||
.experimnet-name {
|
||||
max-width: calc(100% - 2rem);
|
||||
|
||||
.fas {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
.left {
|
||||
align-items: center;
|
||||
|
|
|
@ -3,14 +3,6 @@
|
|||
#experimentTable,
|
||||
#experiment-canvas,
|
||||
#module-archive {
|
||||
.experimnet-name {
|
||||
max-width: calc(100% - 2rem);
|
||||
|
||||
.fas {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
align-items: center;
|
||||
column-gap: .5rem;
|
||||
|
|
|
@ -20,567 +20,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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);
|
||||
justify-content: space-between;
|
||||
|
||||
.toolbar-left-block {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
margin-right: .25em;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
&.experiment-header {
|
||||
column-gap: .25em;
|
||||
}
|
||||
|
||||
.sort-task-menu {
|
||||
&:not(.archived) {
|
||||
[data-view-mode="archived"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-switch-button {
|
||||
outline: 1px solid $color-alto;
|
||||
}
|
||||
|
||||
.view-switch,
|
||||
.filter-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
margin-left: auto;
|
||||
|
||||
.caret {
|
||||
margin: 8px 0 8px 8px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.caret {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
|
||||
.sn-icon-down {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: 100%;
|
||||
padding: 0;
|
||||
|
||||
.divider-label {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
padding: .25em 1em;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
white-space: nowrap;
|
||||
|
||||
.button-icon {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.cards-switch {
|
||||
&.active::after {
|
||||
@include font-awesome;
|
||||
content: "\f00c";
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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(100px, 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;
|
||||
|
||||
.table-body-cell {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table-body-cell:nth-child(1) {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
&::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%;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.assigned-users-container {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
align-items: center;
|
||||
border: 1px solid $color-white;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
height: 26px;
|
||||
justify-content: center;
|
||||
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: var(--sn-black);
|
||||
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;
|
||||
|
||||
&.status-light {
|
||||
@include not-started;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
&.no-data {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
align-items: center;
|
||||
background-color: var(--sn-science-blue);
|
||||
border: 1px solid $color-white;
|
||||
border-radius: 50%;
|
||||
color: var(--sn-white);
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
margin: -4px -14px 0 0;
|
||||
min-width: 16px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.datetime-container {
|
||||
width: 100%;
|
||||
|
||||
.clear-date {
|
||||
cursor: pointer;
|
||||
left: calc(100% - 16px);
|
||||
margin-left: auto;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
&:hover .clear-date {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
.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% - 24px);
|
||||
|
||||
.calendar-due-date {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.open-comments-sidebar {
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
.table-body-cell {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
|
||||
.archived-column {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.task_name-column {
|
||||
a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table-display-modal {
|
||||
.modal-description,
|
||||
.column-container {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.column-container {
|
||||
align-items: center;
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
padding: .5em 1.4em;
|
||||
|
||||
&:not(.visible) {
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.fas {
|
||||
cursor: pointer;
|
||||
margin-right: 1em;
|
||||
|
||||
&.disabled {
|
||||
color: $color-alto;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sn-icon {
|
||||
cursor: pointer;
|
||||
margin-right: .6em;
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.toolbar-row {
|
||||
.button-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,4 +102,10 @@
|
|||
.shareable-link-disclaimer {
|
||||
color: var(--sn-grey);
|
||||
}
|
||||
|
||||
.sci-input-container-v2 {
|
||||
textarea::placeholder {
|
||||
color: var(--sn-grey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,6 +197,7 @@
|
|||
|
||||
.repository-name-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.repository-title {
|
||||
|
|
|
@ -40,6 +40,10 @@ body.navigator-collapsed {
|
|||
|
||||
.sci--layout--navigator-open {
|
||||
display: inline-block;
|
||||
|
||||
+ h1 {
|
||||
max-width: calc(100% - 2.5rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.sci--layout-navigation-navigator {
|
||||
.handle-mr {
|
||||
cursor: url("/images/icon_small/resize_cursor.svg") 0 0, auto !important;
|
||||
cursor: col-resize;
|
||||
display: block !important;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
|
|
|
@ -4,14 +4,6 @@
|
|||
.sci--navigation--notificaitons-flyout-container {
|
||||
position: relative;
|
||||
|
||||
.sci--navigation--notificaitons-flyout-backdrop {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.has-unseen {
|
||||
&::after {
|
||||
align-items: center;
|
||||
|
@ -31,99 +23,94 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sci--navigation--notificaitons-flyout {
|
||||
background-color: $color-white;
|
||||
border-radius: 0 0 $border-radius-default $border-radius-default;
|
||||
box-shadow: $flyout-shadow;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--top-navigation-height) - 2em);
|
||||
height: calc(100vh - 8rem);
|
||||
padding: 1.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(var(--top-navigation-height) - 1.5rem);
|
||||
width: 400px;
|
||||
|
||||
.sci--navigation--notificaitons-flyout-title {
|
||||
@include font-h2;
|
||||
.sci--navigation--notificaitons-flyout-title {
|
||||
@include font-h2;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: .625rem;
|
||||
|
||||
.sn-icon {
|
||||
@include font-button;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: .625rem 0;
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-subtitle {
|
||||
@include font-main;
|
||||
line-height: 2.25rem;
|
||||
margin-bottom: .625rem;
|
||||
}
|
||||
|
||||
.sci--navigation--notificaitons-flyout-notifications {
|
||||
margin-left: -1.5rem;
|
||||
overscroll-behavior: contain;
|
||||
padding: 0 1.5rem;
|
||||
position: relative;
|
||||
width: calc(100% + 3rem);
|
||||
|
||||
.next-page-loader {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: .625rem;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sn-icon {
|
||||
@include font-button;
|
||||
.sci-navigation--notificaitons-flyout-notification {
|
||||
border-bottom: $border-tertiary;
|
||||
padding: 1rem 0;
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-icon {
|
||||
align-items: center;
|
||||
background-color: $brand-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-white;
|
||||
display: flex;
|
||||
grid-row: 1 / 5;
|
||||
height: 2rem;
|
||||
justify-content: center;
|
||||
margin-right: .75rem;
|
||||
width: 2rem;
|
||||
|
||||
&.deliver {
|
||||
background-color: $brand-warning;
|
||||
}
|
||||
|
||||
&.system {
|
||||
background-color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-date {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-title {
|
||||
margin: .25rem 0;
|
||||
|
||||
&:not([data-seen="true"]) {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-message {
|
||||
&[data-notification="system"] {
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: .625rem 0;
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-subtitle {
|
||||
@include font-main;
|
||||
line-height: 2.25rem;
|
||||
margin-bottom: .625rem;
|
||||
}
|
||||
|
||||
.sci--navigation--notificaitons-flyout-notifications {
|
||||
margin-left: -1.5rem;
|
||||
padding: 0 1.5rem;
|
||||
position: relative;
|
||||
width: calc(100% + 3rem);
|
||||
|
||||
.next-page-loader {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification {
|
||||
border-bottom: $border-tertiary;
|
||||
padding: 1rem 0;
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-icon {
|
||||
align-items: center;
|
||||
background-color: $brand-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-white;
|
||||
display: flex;
|
||||
grid-row: 1 / 5;
|
||||
height: 2rem;
|
||||
justify-content: center;
|
||||
margin-right: .75rem;
|
||||
width: 2rem;
|
||||
|
||||
&.deliver {
|
||||
background-color: $brand-warning;
|
||||
}
|
||||
|
||||
&.system {
|
||||
background-color: $brand-success;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-date {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-title {
|
||||
margin: .25rem 0;
|
||||
|
||||
&:not([data-seen="true"]) {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.sci-navigation--notificaitons-flyout-notification-message {
|
||||
&[data-notification="system"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
.view-mode {
|
||||
border: 1px solid transparent;
|
||||
cursor: url("/images/icon_small/edit.svg") 0 16, auto;
|
||||
cursor: text;
|
||||
height: 2rem;
|
||||
min-height: 1.5rem;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -568,10 +568,6 @@ li.module-hover {
|
|||
// New projects page
|
||||
|
||||
.projects-index {
|
||||
.content-header {
|
||||
height: var(--content-header-size);
|
||||
}
|
||||
|
||||
.project-users-list {
|
||||
hr {
|
||||
margin: .5em 0;
|
||||
|
|
|
@ -52,9 +52,15 @@
|
|||
min-height: 300px;
|
||||
padding: 0;
|
||||
|
||||
.dropdown-selector,
|
||||
.users-filter-dropdown {
|
||||
overflow: hidden;
|
||||
.dropdown-selector {
|
||||
.ps__rail-x,
|
||||
.ps__rail-x::after,
|
||||
.ps__rail-x::before,
|
||||
.ps__rail-y,
|
||||
.ps__rail-y::after,
|
||||
.ps__rail-y::before {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
|
@ -256,6 +256,12 @@
|
|||
.dropdown-selector-container {
|
||||
width: 150px;
|
||||
|
||||
.dropdown-container {
|
||||
left: 0 !important;
|
||||
margin: 0 !important;
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
.emoji-status {
|
||||
.emoji {
|
||||
height: 24px;
|
||||
|
|
41
app/assets/stylesheets/shared/ag_table.scss
Normal file
41
app/assets/stylesheets/shared/ag_table.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
.ag-root-wrapper {
|
||||
--agg-row-border-color: var(--sn-light-grey);
|
||||
--ag-odd-row-background-color: var(--sn-white);
|
||||
--ag-header-background-color: var(--sn-light-grey);
|
||||
--ag-selected-row-background-color: var(--sn-super-light-blue);
|
||||
--ag-range-selection-border: var(--sn-science-blue);
|
||||
--ag-grid-size: .5rem;
|
||||
--ag-row-hover-color: var(--sn-super-light-grey);
|
||||
--ag-header-column-resize-handle-height: 1rem;
|
||||
--ag-header-column-resize-handle-color: var(--sn-grey);
|
||||
--ag-header-column-resize-handle-width: 1px;
|
||||
--ag-row-border-width: 1px;
|
||||
--ag-icon-font-code-checkbox-unchecked: asset-url("checkbox/default.svg");
|
||||
--ag-icon-font-code-checkbox-checked: asset-url("checkbox/checked.svg");
|
||||
--ag-icon-font-code-checkbox-indeterminate: asset-url("checkbox/indeterminate.svg");
|
||||
--ag-input-focus-box-shadow: none;
|
||||
--ag-cell-horizontal-padding: .75rem;
|
||||
border: 0;
|
||||
|
||||
.ag-cell {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.ag-header {
|
||||
border-bottom: 0;
|
||||
border-radius: .25rem .25rem 0 0;
|
||||
}
|
||||
|
||||
.ag-input-field-input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ag-header-cell-resize {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.ag-input-field-input:focus {
|
||||
outline: none !important;
|
||||
outline-offset: 0 !important;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,10 @@
|
|||
width: var(--comments-sidebar-width);
|
||||
}
|
||||
|
||||
.atwho-user-container {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
background: $color-concrete;
|
||||
border-left: 1px solid $color-white;
|
||||
|
@ -168,6 +172,10 @@
|
|||
color: $color-volcano;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
.atwho-user-container {
|
||||
white-space: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-footer {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// scss-lint:disable NestingDepth QualifyingElement
|
||||
|
||||
.content-pane {
|
||||
--content-header-size: 9.5em;
|
||||
--content-header-size: 4em;
|
||||
background-color: var(--sn-white);
|
||||
margin: 20px 0;
|
||||
|
||||
|
@ -14,6 +14,11 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.fixed-content-body {
|
||||
height: calc(100vh - var(--content-header-size) - var(--navbar-height));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
&.sticky-header {
|
||||
background-color: inherit;
|
||||
|
@ -38,7 +43,7 @@
|
|||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
max-width: calc(100% - 2.5rem);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.name-readonly-placeholder {
|
||||
|
|
|
@ -105,16 +105,16 @@
|
|||
|
||||
.dp__input_wrap {
|
||||
.dp__input_icon {
|
||||
display: flex;
|
||||
height: 1.5rem;
|
||||
left: .75rem;
|
||||
}
|
||||
left: .5rem;
|
||||
|
||||
.dp__clear_icon {
|
||||
width: 1.375rem;
|
||||
img {
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dp__input {
|
||||
height: 2.25rem;
|
||||
line-height: unset;
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +129,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
.date-time-picker {
|
||||
&.size-mb {
|
||||
.dp__input {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
&.size-sm {
|
||||
.dp__input {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.size-xs {
|
||||
.dp__input {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dp__theme_light {
|
||||
--dp-background-color: var(--sn-white);
|
||||
--dp-text-color: var(--sn-black);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// scss-lint:disable NestingDepth
|
||||
|
||||
.sci-cursor-edit {
|
||||
cursor: url("/images/icon_small/edit.svg") 0 16, auto;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.inline-edit-placeholder:empty::before {
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
left: 0;
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
top: 2.5rem;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,32 @@ $sn-icon-check: "\e95f";
|
|||
}
|
||||
}
|
||||
|
||||
.sn-checkbox-icon {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
min-height: 1rem;
|
||||
min-width: 1rem;
|
||||
|
||||
&.unchecked {
|
||||
background-image: asset-url("checkbox/default.svg");
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background-image: asset-url("checkbox/checked.svg");
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-image: asset-url("checkbox/disabled.svg");
|
||||
}
|
||||
|
||||
&.indeterminate {
|
||||
background-image: asset-url("checkbox/indeterminate.svg");
|
||||
}
|
||||
}
|
||||
|
||||
@mixin font-h1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
$flyout-shadow: 0px 1px 4px rgba(35, 31, 32, 0.15);
|
||||
$modal-shadow: 0px 4px 16px rgba(35, 31, 32, 0.15);
|
||||
|
||||
.sn-shadow-flyout {
|
||||
box-shadow: $flyout-shadow;
|
||||
}
|
||||
|
||||
.sn-shadow-menu-sm {
|
||||
box-shadow: 0px 16px 32px 0px rgba(16, 24, 40, 0.07);
|
||||
|
|
|
@ -90,5 +90,13 @@ input[type="checkbox"].sci-checkbox {
|
|||
border: $border-tertiary;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + .sci-checkbox-label {
|
||||
&::before {
|
||||
background-color: var(--sn-sleepy-grey);
|
||||
border: 1px solid var(--sn-sleepy-grey);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
}
|
||||
|
||||
.btn {
|
||||
@apply relative inline-flex items-center text-sm shrink-0 gap-2 justify-center leading-[22px] py-2 px-4 rounded border border-solid appearance-none whitespace-nowrap cursor-pointer max-h-[40px] focus:outline-none;
|
||||
@apply relative inline-flex items-center text-sm shrink-0 gap-2 justify-center px-4 rounded border border-solid appearance-none whitespace-nowrap cursor-pointer h-[40px] focus:outline-none;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
|
||||
.btn.btn-lg {
|
||||
@apply py-2.5 px-[1.125rem] text-base leading-5 h-[44px];
|
||||
@apply px-[1.125rem] text-base h-[44px];
|
||||
}
|
||||
|
||||
.btn.btn-lg.icon-btn {
|
||||
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
.btn.btn-sm {
|
||||
@apply py-1.5 px-2.5 text-xs leading-5 h-[36px];
|
||||
@apply px-2.5 text-xs h-[36px];
|
||||
}
|
||||
|
||||
.btn.btn-sm.icon-btn {
|
||||
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
|
||||
.btn.btn-xs {
|
||||
@apply py-0.5 px-2.5 text-xs leading-5 h-[30px];
|
||||
@apply px-2.5 text-xs h-[30px];
|
||||
}
|
||||
|
||||
.btn.btn-xs.icon-btn {
|
||||
|
@ -59,7 +59,9 @@
|
|||
}
|
||||
|
||||
.btn.btn-primary:hover,
|
||||
.btn.btn-success:hover{
|
||||
.btn.btn-success:hover,
|
||||
.btn.btn-primary:focus,
|
||||
.btn.btn-success:focus{
|
||||
@apply bg-sn-blue-hover text-sn-white;
|
||||
}
|
||||
|
||||
|
@ -67,7 +69,7 @@
|
|||
.btn.btn-primary.disabled,
|
||||
.btn.btn-success:disabled,
|
||||
.btn.btn-success.disabled {
|
||||
@apply bg-sn-light-grey text-sn-sleepy-grey border-sn-light-grey;
|
||||
@apply bg-sn-super-light-grey text-sn-grey border-sn-grey;
|
||||
}
|
||||
|
||||
.btn.btn-secondary,
|
||||
|
@ -80,7 +82,8 @@
|
|||
}
|
||||
|
||||
.btn.btn-secondary:hover,
|
||||
.btn.btn-default:hover {
|
||||
.btn.btn-default:hover,
|
||||
.btn.btn-secondary:focus {
|
||||
@apply border-sn-blue-hover;
|
||||
}
|
||||
|
||||
|
@ -129,7 +132,8 @@
|
|||
@apply bg-sn-delete-red text-sn-white;
|
||||
}
|
||||
|
||||
.btn.btn-danger:hover {
|
||||
.btn.btn-danger:hover,
|
||||
.btn.btn-danger:focus {
|
||||
@apply bg-sn-delete-red-hover;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
@apply text-sm font-medium text-sn-dark-grey;
|
||||
}
|
||||
|
||||
.sci-label.error {
|
||||
@apply text-sn-coral;
|
||||
}
|
||||
|
||||
.sci-input-container-v2 {
|
||||
@apply relative h-[2.75rem] flex items-center;
|
||||
@apply relative h-[2.75rem] flex items-center transition-all;
|
||||
}
|
||||
|
||||
.sci-input-container-v2.input-sm {
|
||||
|
@ -24,6 +28,18 @@
|
|||
@apply !border-sn-coral;
|
||||
}
|
||||
|
||||
.sci-input-container-v2.error::before {
|
||||
@apply !text-sn-coral;
|
||||
@apply !text-xs;
|
||||
|
||||
bottom: -1rem;
|
||||
content: attr(data-error-text);
|
||||
left: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sci-input-container-v2 input:focus {
|
||||
@apply border-sn-science-blue shadow-none;
|
||||
}
|
||||
|
@ -77,4 +93,13 @@
|
|||
.sci-input-container-v2 .history-flyout li:hover {
|
||||
@apply bg-sn-super-light-grey;
|
||||
}
|
||||
|
||||
.sci-input-container-v2.error input {
|
||||
@apply border-sn-alert-passion;
|
||||
}
|
||||
|
||||
.sci-input-container-v2.error::after {
|
||||
@apply absolute -bottom-5 text-sn-alert-passion text-xs;
|
||||
content: attr(data-error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,15 @@ module AccessPermissions
|
|||
before_action :check_read_permissions, only: %i(show)
|
||||
before_action :check_manage_permissions, only: %i(edit update)
|
||||
|
||||
def show; end
|
||||
def show
|
||||
render json: @experiment.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||
each_serializer: UserAssignmentSerializer, user: current_user
|
||||
end
|
||||
|
||||
def new
|
||||
render json: @available_users, each_serializer: UserSerializer, user: current_user
|
||||
end
|
||||
|
||||
|
||||
def edit; end
|
||||
|
||||
|
@ -36,7 +44,7 @@ module AccessPermissions
|
|||
|
||||
log_change_activity
|
||||
|
||||
render :experiment_member
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,8 +8,14 @@ module AccessPermissions
|
|||
before_action :check_read_permissions, only: %i(show)
|
||||
before_action :check_manage_permissions, only: %i(edit update)
|
||||
|
||||
def show; end
|
||||
def show
|
||||
render json: @my_module.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||
each_serializer: UserAssignmentSerializer, user: current_user
|
||||
end
|
||||
|
||||
def new
|
||||
render json: @available_users, each_serializer: UserSerializer, user: current_user
|
||||
end
|
||||
def edit; end
|
||||
|
||||
def update
|
||||
|
|
|
@ -9,17 +9,59 @@ module AccessPermissions
|
|||
before_action :check_manage_permissions, except: %i(show)
|
||||
before_action :available_users, only: %i(new create)
|
||||
|
||||
def new
|
||||
@user_assignment = @project.user_assignments.new(
|
||||
assigned_by: current_user,
|
||||
team: current_team
|
||||
)
|
||||
def show
|
||||
render json: @project.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||
each_serializer: UserAssignmentSerializer, user: current_user
|
||||
end
|
||||
|
||||
def show; end
|
||||
def new
|
||||
render json: @available_users, each_serializer: UserSerializer, user: current_user
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
created_count = 0
|
||||
if permitted_create_params[:user_id] == 'all'
|
||||
@project.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
|
||||
log_activity(:project_grant_access_to_all_team_members,
|
||||
{ visibility: t('projects.activity.visibility_visible'),
|
||||
role: @project.default_public_user_role.name,
|
||||
team: @project.team.id })
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @project,
|
||||
user_id: permitted_create_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: permitted_create_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
log_activity(:assign_user_to_project, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
created_count += 1
|
||||
propagate_job(user_assignment)
|
||||
end
|
||||
|
||||
@message = if created_count.zero?
|
||||
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
|
||||
else
|
||||
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
|
||||
end
|
||||
render json: { message: @message }
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
errors = @project.errors.present? ? @project.errors&.map(&:message)&.join(',') : e.message
|
||||
render json: { flash: errors }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@user_assignment = @project.user_assignments.find_by(
|
||||
user_id: permitted_update_params[:user_id],
|
||||
|
@ -44,53 +86,6 @@ module AccessPermissions
|
|||
render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
created_count = 0
|
||||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
if user_assignment_params[:user_id] == 'all'
|
||||
@project.update!(visibility: :visible, default_public_user_role_id: user_assignment_params[:user_role_id])
|
||||
log_activity(:project_grant_access_to_all_team_members,
|
||||
{ visibility: t('projects.activity.visibility_visible'),
|
||||
role: @project.default_public_user_role.name,
|
||||
team: @project.team.id })
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @project,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
log_activity(:assign_user_to_project, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
|
||||
created_count += 1
|
||||
propagate_job(user_assignment)
|
||||
end
|
||||
end
|
||||
|
||||
@message = if created_count.zero?
|
||||
t('access_permissions.create.success', count: t('access_permissions.all_team'))
|
||||
else
|
||||
t('access_permissions.create.success', count: created_count)
|
||||
end
|
||||
render :edit
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
errors = @project.errors.present? ? @project.errors&.map(&:message)&.join(',') : e.message
|
||||
render json: { flash: errors }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
user = @project.assigned_users.find(params[:user_id])
|
||||
user_assignment = @project.user_assignments.find_by(user: user, team: current_team)
|
||||
|
@ -108,12 +103,14 @@ module AccessPermissions
|
|||
destroy: true
|
||||
)
|
||||
|
||||
user_assignment.destroy!
|
||||
|
||||
log_activity(:unassign_user_from_project, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
|
||||
render json: { flash: t('access_permissions.destroy.success', member_name: escape_input(user.full_name)) }
|
||||
render json: { message: t('access_permissions.destroy.success', member_name: escape_input(user.full_name)) }
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: { flash: t('access_permissions.destroy.failure') },
|
||||
render json: { message: t('access_permissions.destroy.failure') },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
|
@ -131,7 +128,7 @@ module AccessPermissions
|
|||
{ visibility: t('projects.activity.visibility_hidden'),
|
||||
role: previous_user_role_name,
|
||||
team: @project.team.id })
|
||||
render json: { flash: t('access_permissions.update.revoke_all_team_members') }
|
||||
render json: { message: t('access_permissions.update.revoke_all_team_members') }
|
||||
else
|
||||
# update all team members access
|
||||
@project.visibility = :visible
|
||||
|
@ -150,7 +147,7 @@ module AccessPermissions
|
|||
private
|
||||
|
||||
def permitted_default_public_user_role_params
|
||||
params.require(:project).permit(:default_public_user_role_id)
|
||||
params.require(:object).permit(:default_public_user_role_id)
|
||||
end
|
||||
|
||||
def permitted_update_params
|
||||
|
@ -159,8 +156,8 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def permitted_create_params
|
||||
params.require(:access_permissions_new_user_form)
|
||||
.permit(resource_members: %i(assign user_id user_role_id))
|
||||
params.require(:user_assignment)
|
||||
.permit(%i(user_id user_role_id))
|
||||
end
|
||||
|
||||
def set_project
|
||||
|
@ -184,7 +181,7 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def check_read_permissions
|
||||
render_403 unless can_read_project?(@project)
|
||||
render_403 unless can_read_project_users?(@project)
|
||||
end
|
||||
|
||||
def available_users
|
||||
|
@ -193,7 +190,7 @@ module AccessPermissions
|
|||
id: @project.user_assignments.automatically_assigned.select(:user_id)
|
||||
).or(
|
||||
current_team.users.where.not(id: @project.users.select(:id))
|
||||
)
|
||||
).order('users.full_name ASC')
|
||||
end
|
||||
|
||||
def log_activity(type_of, message_items = {})
|
||||
|
|
|
@ -2,22 +2,63 @@
|
|||
|
||||
module AccessPermissions
|
||||
class ProtocolsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :set_protocol
|
||||
before_action :check_read_permissions, only: %i(show)
|
||||
before_action :check_manage_permissions, except: %i(show)
|
||||
before_action :available_users, only: %i(new create)
|
||||
|
||||
def new
|
||||
@user_assignment = UserAssignment.new(
|
||||
assignable: @protocol,
|
||||
assigned_by: current_user,
|
||||
team: current_team
|
||||
)
|
||||
def show
|
||||
render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||
each_serializer: UserAssignmentSerializer, user: current_user
|
||||
end
|
||||
|
||||
def show; end
|
||||
def new
|
||||
render json: @available_users, each_serializer: UserSerializer, user: current_user
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
created_count = 0
|
||||
if permitted_create_params[:user_id] == 'all'
|
||||
@protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
|
||||
log_activity(:protocol_template_access_granted_all_team_members,
|
||||
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: permitted_create_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: permitted_create_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
created_count += 1
|
||||
end
|
||||
|
||||
@message = if created_count.zero?
|
||||
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
|
||||
else
|
||||
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
|
||||
end
|
||||
render json: { message: @message }
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
|
||||
render json: { flash: errors }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@user_assignment = @protocol.user_assignments.find_by(
|
||||
user_id: permitted_update_params[:user_id],
|
||||
|
@ -40,49 +81,6 @@ module AccessPermissions
|
|||
render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
created_count = 0
|
||||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
if user_assignment_params[:user_id] == 'all'
|
||||
@protocol.update!(default_public_user_role_id: user_assignment_params[:user_role_id])
|
||||
log_activity(:protocol_template_access_granted_all_team_members,
|
||||
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
created_count += 1
|
||||
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
end
|
||||
end
|
||||
|
||||
@message = if created_count.zero?
|
||||
t('access_permissions.create.success', count: t('access_permissions.all_team'))
|
||||
else
|
||||
t('access_permissions.create.success', count: created_count)
|
||||
end
|
||||
render :edit
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
|
||||
render json: { flash: errors }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
user = @protocol.assigned_users.find(params[:user_id])
|
||||
user_assignment = @protocol.user_assignments.find_by(user: user, team: current_team)
|
||||
|
@ -105,10 +103,10 @@ module AccessPermissions
|
|||
role: user_assignment.user_role.name })
|
||||
end
|
||||
|
||||
render json: { flash: t('access_permissions.destroy.success', member_name: user.full_name) }
|
||||
render json: { message: t('access_permissions.destroy.success', member_name: user.full_name) }
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
render json: { flash: t('access_permissions.destroy.failure') }, status: :unprocessable_entity
|
||||
render json: { message: t('access_permissions.destroy.failure') }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
|
@ -137,7 +135,7 @@ module AccessPermissions
|
|||
private
|
||||
|
||||
def permitted_default_public_user_role_params
|
||||
params.require(:protocol).permit(:default_public_user_role_id)
|
||||
params.require(:object).permit(:default_public_user_role_id)
|
||||
end
|
||||
|
||||
def permitted_update_params
|
||||
|
@ -146,8 +144,17 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def permitted_create_params
|
||||
params.require(:access_permissions_new_user_form)
|
||||
.permit(resource_members: %i(assign user_id user_role_id))
|
||||
params.require(:user_assignment)
|
||||
.permit(%i(user_id user_role_id))
|
||||
end
|
||||
|
||||
def available_users
|
||||
# automatically assigned or not assigned to project
|
||||
@available_users = current_team.users.where(
|
||||
id: @protocol.user_assignments.automatically_assigned.select(:user_id)
|
||||
).or(
|
||||
current_team.users.where.not(id: @protocol.users.select(:id))
|
||||
).order('users.full_name ASC')
|
||||
end
|
||||
|
||||
def set_protocol
|
||||
|
|
|
@ -136,10 +136,7 @@ module Api
|
|||
end
|
||||
|
||||
def check_read_permissions
|
||||
# team owners can always manage users, so they should also be able to read them
|
||||
unless can_read_project_users?(@project) || can_manage_project_users?(@project)
|
||||
raise PermissionError.new(Project, :read_users)
|
||||
end
|
||||
raise PermissionError.new(Project, :read_users) unless can_read_project_users?(@project)
|
||||
end
|
||||
|
||||
def load_user_assignment
|
||||
|
|
|
@ -113,4 +113,14 @@ class ApplicationController < ActionController::Base
|
|||
ensure
|
||||
I18n.backend.date_format = nil
|
||||
end
|
||||
|
||||
def pagination_dict(object)
|
||||
{
|
||||
current_page: object.current_page,
|
||||
next_page: object.next_page,
|
||||
prev_page: object.prev_page,
|
||||
total_pages: object.total_pages,
|
||||
total_count: object.total_count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AssetSyncController < ApplicationController
|
||||
include FileIconsHelper
|
||||
|
||||
skip_before_action :authenticate_user!, only: %i(update download)
|
||||
skip_before_action :verify_authenticity_token, only: %i(update download)
|
||||
before_action :authenticate_asset_sync_token!, only: %i(update download)
|
||||
|
@ -36,9 +38,19 @@ class AssetSyncController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
orig_file_size = @asset.file_size
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@asset.update(last_modified_by: current_user)
|
||||
@asset.file.attach(io: request.body, filename: @asset.file.filename)
|
||||
if wopi_file?(@asset)
|
||||
@asset.update_contents(request.body)
|
||||
else
|
||||
@asset.file.attach(io: request.body, filename: @asset.file.filename)
|
||||
@asset.touch
|
||||
end
|
||||
|
||||
@asset.team.release_space(orig_file_size)
|
||||
@asset.post_process_file
|
||||
|
||||
log_activity(:edit)
|
||||
end
|
||||
|
@ -75,11 +87,15 @@ class AssetSyncController < ApplicationController
|
|||
Asset.transaction do
|
||||
new_asset = @asset.dup
|
||||
new_asset.save
|
||||
new_asset.file.attach(
|
||||
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: request.body,
|
||||
filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}"
|
||||
filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}",
|
||||
metadata: @asset.blob.metadata
|
||||
)
|
||||
|
||||
new_asset.file.attach(blob)
|
||||
|
||||
case @asset.parent
|
||||
when Step
|
||||
StepAsset.create!(step: @asset.step, asset: new_asset)
|
||||
|
@ -89,6 +105,8 @@ class AssetSyncController < ApplicationController
|
|||
|
||||
@asset = new_asset.reload
|
||||
|
||||
new_asset.post_process_file
|
||||
|
||||
current_user.asset_sync_tokens.create!(asset_id: new_asset.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,7 @@ module Breadcrumbs
|
|||
@breadcrumbs_items.push(
|
||||
{
|
||||
label: project.name,
|
||||
url: project_path(project, view_mode: archived_branch ? :archived : :active),
|
||||
url: experiments_path(project_id: project, view_mode: archived_branch ? :archived : :active),
|
||||
archived: archived_branch
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
class DesignElementsController < ApplicationController
|
||||
def index
|
||||
end
|
||||
|
||||
def test_select
|
||||
render json: { data: [
|
||||
['1', 'One'],
|
||||
['2', 'Two'],
|
||||
['3', 'Three'],
|
||||
['4', 'Four'],
|
||||
['5', 'Five'],
|
||||
['6', 'Six'],
|
||||
['7', 'Seven'],
|
||||
['8', 'Eight'],
|
||||
['9', 'Nine'],
|
||||
['10', 'Ten']
|
||||
].select { |item| item[1].downcase.include?(params[:query].downcase) } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,34 +8,44 @@ class ExperimentsController < ApplicationController
|
|||
include Rails.application.routes.url_helpers
|
||||
include Breadcrumbs
|
||||
|
||||
before_action :load_project, only: %i(new create archive_group restore_group move)
|
||||
before_action :load_experiment, except: %i(new create archive_group restore_group
|
||||
inventory_assigning_experiment_filter actions_toolbar
|
||||
move move_modal)
|
||||
before_action :load_experiments, only: %i(move_modal move)
|
||||
before_action :check_move_permissions, only: %i(move_modal move)
|
||||
before_action :check_read_permissions, except: %i(edit archive clone move move_modal new
|
||||
before_action :load_project, only: %i(index create archive_group restore_group move)
|
||||
before_action :load_experiment, except: %i(create archive_group restore_group
|
||||
inventory_assigning_experiment_filter actions_toolbar index move)
|
||||
before_action :load_experiments, only: :move
|
||||
before_action :check_read_permissions, except: %i(index edit archive clone move new
|
||||
create archive_group restore_group
|
||||
inventory_assigning_experiment_filter actions_toolbar)
|
||||
before_action :check_canvas_read_permissions, only: %i(canvas)
|
||||
before_action :check_create_permissions, only: %i(new create move)
|
||||
before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules)
|
||||
before_action :check_update_permissions, only: %i(update)
|
||||
before_action :check_create_permissions, only: %i(create move)
|
||||
before_action :check_manage_permissions, only: :batch_clone_my_modules
|
||||
before_action :check_update_permissions, only: :update
|
||||
before_action :check_archive_permissions, only: :archive
|
||||
before_action :check_clone_permissions, only: %i(clone_modal clone)
|
||||
before_action :set_inline_name_editing, only: %i(canvas table module_archive)
|
||||
before_action :set_breadcrumbs_items, only: %i(canvas table module_archive)
|
||||
before_action :set_navigator, only: %i(canvas module_archive table)
|
||||
before_action :set_inline_name_editing, only: %i(index canvas module_archive)
|
||||
before_action :set_breadcrumbs_items, only: %i(index canvas module_archive)
|
||||
before_action :set_navigator, only: %i(index canvas module_archive)
|
||||
|
||||
layout 'fluid'
|
||||
|
||||
def new
|
||||
@experiment = Experiment.new
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'new_modal', formats: :html
|
||||
)
|
||||
}
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
experiments = Lists::ExperimentsService.new(@project.experiments,
|
||||
params.merge(project: @project),
|
||||
user: current_user).call
|
||||
render json: experiments, each_serializer: Lists::ExperimentSerializer, user: current_user,
|
||||
meta: pagination_dict(experiments)
|
||||
end
|
||||
format.html do
|
||||
render 'experiments/index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_users
|
||||
render json: User.where(id: @experiment.user_assignments.select(:user_id)),
|
||||
each_serializer: UserSerializer,
|
||||
user: current_user
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -46,7 +56,7 @@ class ExperimentsController < ApplicationController
|
|||
if @experiment.save
|
||||
experiment_annotation_notification
|
||||
log_activity(:create_experiment, @experiment)
|
||||
flash[:success] = t('experiments.create.success_flash',
|
||||
flash[:success] = t('.success_flash',
|
||||
experiment: @experiment.name)
|
||||
|
||||
render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
|
||||
|
@ -55,24 +65,6 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/details_modal', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
def permissions
|
||||
if stale?([@experiment, @experiment.project])
|
||||
render json: {
|
||||
editable: can_manage_experiment?(@experiment),
|
||||
moveable: can_move_experiment?(@experiment),
|
||||
archivable: can_archive_experiment?(@experiment),
|
||||
restorable: can_restore_experiment?(@experiment),
|
||||
duplicable: can_clone_experiment?(@experiment)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def canvas
|
||||
@project = @experiment.project
|
||||
@active_modules = unless @experiment.archived_branch?
|
||||
|
@ -84,40 +76,8 @@ class ExperimentsController < ApplicationController
|
|||
.select('COUNT(DISTINCT comments.id) as task_comments_count')
|
||||
.select('my_modules.*').group(:id)
|
||||
end
|
||||
end
|
||||
|
||||
def table
|
||||
@project = @experiment.project
|
||||
@experiment.current_view_state(current_user)
|
||||
@my_module_visible_table_columns = current_user.my_module_visible_table_columns
|
||||
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
@current_sort = view_state.state.dig('my_modules', my_modules_view_mode(@project), 'sort') || 'atoz'
|
||||
end
|
||||
|
||||
def my_modules_view_mode(my_module)
|
||||
return 'archived' if my_module.archived?
|
||||
|
||||
params[:view_mode] == 'archived' ? 'archived' : 'active'
|
||||
end
|
||||
|
||||
def load_table
|
||||
active_view_mode = params[:view_mode] != 'archived'
|
||||
my_modules = nil
|
||||
|
||||
unless @experiment.archived_branch? && active_view_mode
|
||||
my_modules = @experiment.my_modules.readable_by_user(current_user)
|
||||
|
||||
unless @experiment.archived_branch?
|
||||
my_modules = if active_view_mode
|
||||
my_modules.active
|
||||
else
|
||||
my_modules.archived
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
render json: Experiments::TableViewService.new(@experiment, my_modules, current_user, params).call
|
||||
save_view_type('canvas')
|
||||
end
|
||||
|
||||
def my_modules
|
||||
|
@ -135,12 +95,6 @@ class ExperimentsController < ApplicationController
|
|||
redirect_to view_mode_redirect_url(view_type_params)
|
||||
end
|
||||
|
||||
def edit
|
||||
render json: {
|
||||
html: render_to_string(partial: 'edit_modal', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
def update
|
||||
old_text = @experiment.description
|
||||
@experiment.assign_attributes(experiment_params)
|
||||
|
@ -160,25 +114,9 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
log_activity(activity_type, @experiment)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
format.html do
|
||||
flash[:success] = t('experiments.update.success_flash', experiment: @experiment.name)
|
||||
redirect_to project_path(@experiment.project)
|
||||
end
|
||||
end
|
||||
render json: { message: t('experiments.update.success_flash', experiment: @experiment.name) }, status: :ok
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: @experiment.errors, status: :unprocessable_entity
|
||||
end
|
||||
format.html do
|
||||
flash[:alert] = t('experiments.update.error_flash')
|
||||
redirect_back(fallback_location: root_path)
|
||||
end
|
||||
end
|
||||
render json: { message: @experiment.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -241,47 +179,50 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# GET: clone_modal_experiment_path(id)
|
||||
def clone_modal
|
||||
@projects = @experiment.project.team.projects.active
|
||||
.with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'clone_modal',
|
||||
locals: { view_mode: params[:view_mode] },
|
||||
formats: :html
|
||||
)
|
||||
}
|
||||
def projects_to_clone
|
||||
projects = @experiment.project.team.projects.active
|
||||
.with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
|
||||
.where('trim_html_tags(projects.name) ILIKE ?',
|
||||
"%#{ActiveRecord::Base.sanitize_sql_like(params['query'])}%")
|
||||
.map { |p| [p.id, p.name] }
|
||||
render json: { data: projects }, status: :ok
|
||||
end
|
||||
|
||||
def projects_to_move
|
||||
projects = @experiment.movable_projects(current_user)
|
||||
.where('trim_html_tags(projects.name) ILIKE ?',
|
||||
"%#{ActiveRecord::Base.sanitize_sql_like(params['query'])}%")
|
||||
.map { |p| [p.id, p.name] }
|
||||
render json: { data: projects }, status: :ok
|
||||
end
|
||||
|
||||
def experiments_to_move
|
||||
experiments = @experiment.project.experiments.active.where.not(id: @experiment)
|
||||
.managable_by_user(current_user).order(name: :asc).map { |e| [e.id, e.name] }
|
||||
render json: { data: experiments }, status: :ok
|
||||
end
|
||||
|
||||
# POST: clone_experiment(id)
|
||||
def clone
|
||||
@project = current_team.projects.find(move_experiment_param)
|
||||
return render_403 unless can_create_project_experiments?(project)
|
||||
return render_403 unless can_create_project_experiments?(@project)
|
||||
|
||||
service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment,
|
||||
project: project,
|
||||
project: @project,
|
||||
user: current_user)
|
||||
|
||||
if service.succeed?
|
||||
flash[:success] = t('experiments.clone.success_flash',
|
||||
experiment: @experiment.name)
|
||||
redirect_to canvas_experiment_path(service.cloned_experiment)
|
||||
render json: { url: canvas_experiment_path(service.cloned_experiment) }
|
||||
else
|
||||
flash[:error] = t('experiments.clone.error_flash',
|
||||
experiment: @experiment.name)
|
||||
redirect_to project_path(@experiment.project)
|
||||
render json: {
|
||||
message: t('experiments.clone.error_flash',
|
||||
experiment: @experiment.name)
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# GET: move_modal_experiment_path(id)
|
||||
def move_modal
|
||||
@projects = @experiments.first.movable_projects(current_user)
|
||||
render json: {
|
||||
html: render_to_string(partial: 'move_modal', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
def search_tags
|
||||
tags = @experiment.project.tags.where.not(id: JSON.parse(params[:selected_tags]))
|
||||
.where_attributes_like(:name, params[:query])
|
||||
|
@ -297,8 +238,10 @@ class ExperimentsController < ApplicationController
|
|||
render json: tags
|
||||
end
|
||||
|
||||
# POST: move_experiment(id)
|
||||
# POST: move_experiments(ids)
|
||||
def move
|
||||
return render_403 unless @experiments.all? { |e| can_move_experiment?(e) }
|
||||
|
||||
@project.transaction do
|
||||
@experiments.each do |experiment|
|
||||
service = Experiments::MoveToProjectService
|
||||
|
@ -386,35 +329,7 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
render json: {
|
||||
workflowimg: render_to_string(
|
||||
partial: 'projects/show/workflow_img',
|
||||
locals: { experiment: @experiment },
|
||||
formats: :html
|
||||
)
|
||||
}
|
||||
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)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: if params[:view_mode] == 'archived'
|
||||
'shared/sidebar/archived_my_modules'
|
||||
else
|
||||
'shared/sidebar/my_modules'
|
||||
end, locals: { experiment: @experiment, my_modules: my_modules }
|
||||
)
|
||||
workflowimg_url: rails_blob_path(@experiment.workflowimg, only_path: true),
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -435,6 +350,7 @@ class ExperimentsController < ApplicationController
|
|||
.joins(:my_modules)
|
||||
.where(experiments: { id: viewable_experiments })
|
||||
.where(my_modules: { id: assignable_my_modules })
|
||||
.order(:name)
|
||||
.distinct
|
||||
.pluck(:id, :name)
|
||||
|
||||
|
@ -443,17 +359,6 @@ class ExperimentsController < ApplicationController
|
|||
render json: experiments
|
||||
end
|
||||
|
||||
def actions_dropdown
|
||||
if stale?([@experiment, @experiment.project])
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'projects/show/experiment_actions_dropdown',
|
||||
locals: { experiment: @experiment }
|
||||
)
|
||||
}
|
||||
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|
|
||||
|
@ -526,7 +431,7 @@ class ExperimentsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ExperimentsService.new(
|
||||
current_user,
|
||||
experiment_ids: params[:experiment_ids].split(',')
|
||||
experiment_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -545,7 +450,9 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
def load_project
|
||||
@project = Project.find_by(id: params[:project_id])
|
||||
|
||||
render_404 unless @project
|
||||
render_403 unless can_read_project?(@project)
|
||||
end
|
||||
|
||||
def experiment_params
|
||||
|
@ -560,6 +467,12 @@ class ExperimentsController < ApplicationController
|
|||
params.require(:experiment).require(:view_type)
|
||||
end
|
||||
|
||||
def save_view_type(view_type)
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_state.state['my_modules']['view_type'] = view_type
|
||||
view_state.save!
|
||||
end
|
||||
|
||||
def check_read_permissions
|
||||
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
|
||||
render_403 unless can_read_experiment?(@experiment) ||
|
||||
|
@ -594,20 +507,28 @@ class ExperimentsController < ApplicationController
|
|||
render_403 unless can_clone_experiment?(@experiment)
|
||||
end
|
||||
|
||||
def check_move_permissions
|
||||
render_403 unless @experiments.all? { |e| can_move_experiment?(e) }
|
||||
end
|
||||
|
||||
def set_inline_name_editing
|
||||
return unless can_manage_experiment?(@experiment)
|
||||
if @experiment
|
||||
return unless can_manage_experiment?(@experiment)
|
||||
|
||||
@inline_editable_title_config = {
|
||||
name: 'title',
|
||||
params_group: 'experiment',
|
||||
item_id: @experiment.id,
|
||||
field_to_udpate: 'name',
|
||||
path_to_update: experiment_path(@experiment)
|
||||
}
|
||||
@inline_editable_title_config = {
|
||||
name: 'title',
|
||||
params_group: 'experiment',
|
||||
item_id: @experiment.id,
|
||||
field_to_udpate: 'name',
|
||||
path_to_update: experiment_path(@experiment)
|
||||
}
|
||||
else
|
||||
return unless can_manage_project?(@project)
|
||||
|
||||
@inline_editable_title_config = {
|
||||
name: 'title',
|
||||
params_group: 'project',
|
||||
item_id: @project.id,
|
||||
field_to_udpate: 'name',
|
||||
path_to_update: project_path(@project)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def experiment_annotation_notification(old_text = nil)
|
||||
|
@ -652,10 +573,10 @@ class ExperimentsController < ApplicationController
|
|||
when 'canvas'
|
||||
module_archive_experiment_path(@experiment)
|
||||
else
|
||||
table_experiment_path(@experiment, view_mode: :archived)
|
||||
my_modules_path(experiment_id: @experiment, view_mode: :archived)
|
||||
end
|
||||
else
|
||||
view_type == 'canvas' ? canvas_experiment_path(@experiment) : table_experiment_path(@experiment)
|
||||
view_type == 'canvas' ? canvas_experiment_path(@experiment) : my_modules_path(experiment_id: @experiment)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -689,10 +610,18 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
def set_navigator
|
||||
@navigator = {
|
||||
url: tree_navigator_experiment_path(@experiment),
|
||||
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
|
||||
id: @experiment.code
|
||||
}
|
||||
@navigator = if @experiment
|
||||
{
|
||||
url: tree_navigator_experiment_path(@experiment),
|
||||
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
|
||||
id: @experiment.code
|
||||
}
|
||||
else
|
||||
{
|
||||
url: tree_navigator_project_path(@project),
|
||||
archived: (action_name == 'index' && params[:view_mode] == 'archived'),
|
||||
id: @project.code
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,8 @@ class LabelTemplatesController < ApplicationController
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: @label_templates, each_serializer: LabelTemplateSerializer, user: current_user
|
||||
label_templates = Lists::LabelTemplatesService.new(@label_templates, params).call
|
||||
render json: label_templates, each_serializer: Lists::LabelTemplateSerializer, user: current_user, meta: pagination_dict(label_templates)
|
||||
end
|
||||
format.html do
|
||||
unless LabelTemplate.enabled?
|
||||
|
@ -28,13 +29,6 @@ class LabelTemplatesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def datatable
|
||||
render json: ::LabelTemplateDatatable.new(
|
||||
view_context,
|
||||
@label_templates
|
||||
)
|
||||
end
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json { render json: @label_template, serializer: LabelTemplateSerializer, user: current_user }
|
||||
|
@ -50,13 +44,11 @@ class LabelTemplatesController < ApplicationController
|
|||
label_template.last_modified_by = current_user
|
||||
label_template.save!
|
||||
log_activity(:label_template_created, label_template)
|
||||
redirect_to label_template_path(label_template, new_label: true)
|
||||
render json: { redirect_url: label_template_path(label_template, new_label: true) }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
flash[:error] = I18n.t('errors.general')
|
||||
redirect_to label_templates_path
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -154,7 +146,7 @@ class LabelTemplatesController < ApplicationController
|
|||
actions:
|
||||
Toolbars::LabelTemplatesService.new(
|
||||
current_user,
|
||||
label_template_ids: params[:label_template_ids].split(',')
|
||||
label_template_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
|
|
@ -27,6 +27,10 @@ class MyModuleTagsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def assigned_tags
|
||||
render json: @my_module.my_module_tags, each_serializer: MyModuleTagSerializer
|
||||
end
|
||||
|
||||
def canvas_index
|
||||
experiment = Experiment.find(params[:id])
|
||||
return render_403 unless can_read_experiment?(experiment)
|
||||
|
|
|
@ -8,9 +8,9 @@ class MyModulesController < ApplicationController
|
|||
include MyModulesHelper
|
||||
include Breadcrumbs
|
||||
|
||||
before_action :load_vars, except: %i(restore_group create new save_table_state
|
||||
before_action :load_vars, except: %i(index restore_group create new save_table_state
|
||||
inventory_assigning_my_module_filter actions_toolbar)
|
||||
before_action :load_experiment, only: %i(create new)
|
||||
before_action :load_experiment, only: %i(create new index)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_archive_permissions, only: %i(update)
|
||||
before_action :check_manage_permissions, only: %i(
|
||||
|
@ -19,15 +19,31 @@ class MyModulesController < ApplicationController
|
|||
before_action :check_read_permissions, except: %i(create new update update_description
|
||||
inventory_assigning_my_module_filter
|
||||
update_protocol_description restore_group
|
||||
save_table_state actions_toolbar)
|
||||
save_table_state actions_toolbar index)
|
||||
before_action :check_update_state_permissions, only: :update_state
|
||||
before_action :set_inline_name_editing, only: %i(protocols activities archive)
|
||||
before_action :set_inline_name_editing, only: %i(protocols activities archive index)
|
||||
before_action :load_experiment_my_modules, only: %i(protocols activities archive)
|
||||
before_action :set_breadcrumbs_items, only: %i(protocols activities archive)
|
||||
before_action :set_navigator, only: %i(protocols activities archive)
|
||||
before_action :set_breadcrumbs_items, only: %i(protocols activities archive index)
|
||||
before_action :set_navigator, only: %i(protocols activities archive index)
|
||||
|
||||
layout 'fluid'.freeze
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
my_modules = Lists::MyModulesService.new(@experiment.my_modules.readable_by_user(current_user),
|
||||
params.merge(experiment: @experiment),
|
||||
user: current_user).call
|
||||
render json: my_modules, each_serializer: Lists::MyModuleSerializer, user: current_user,
|
||||
meta: pagination_dict(my_modules)
|
||||
end
|
||||
format.html do
|
||||
save_view_type('table')
|
||||
render 'my_modules/index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@my_module = @experiment.my_modules.new
|
||||
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
|
||||
|
@ -51,7 +67,7 @@ class MyModulesController < ApplicationController
|
|||
)
|
||||
@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]))
|
||||
@my_module.tags << @experiment.project.tags.where(id: 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])
|
||||
|
@ -76,9 +92,16 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
render json: {
|
||||
html: render_to_string(partial: 'show')
|
||||
}
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render json: {
|
||||
html: render_to_string(partial: 'show')
|
||||
}
|
||||
end
|
||||
format.json do
|
||||
render json: @my_module, serializer: Lists::MyModuleSerializer, user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Description modal window in full-zoom canvas
|
||||
|
@ -328,16 +351,22 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
if counter == my_modules.size
|
||||
flash[:success] = t('my_modules.restore_group.success_flash_html', number: counter)
|
||||
message = t('my_modules.restore_group.success_flash_html', number: counter)
|
||||
elsif counter.positive?
|
||||
flash[:warning] = t('my_modules.restore_group.partial_success_flash_html', number: counter)
|
||||
message = t('my_modules.restore_group.partial_success_flash_html', number: counter)
|
||||
else
|
||||
flash[:error] = t('my_modules.restore_group.error_flash')
|
||||
error = t('my_modules.restore_group.error_flash')
|
||||
end
|
||||
|
||||
if params[:view] == 'table'
|
||||
redirect_to table_experiment_path(experiment, view_mode: :archived)
|
||||
if message
|
||||
render json: { message: message }
|
||||
else
|
||||
render json: { error: error }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
flash[:notice] = message if message
|
||||
flash[:error] = error if error
|
||||
redirect_to module_archive_experiment_path(experiment)
|
||||
end
|
||||
end
|
||||
|
@ -381,7 +410,7 @@ class MyModulesController < ApplicationController
|
|||
actions:
|
||||
Toolbars::MyModulesService.new(
|
||||
current_user,
|
||||
my_module_ids: params[:my_module_ids].split(',')
|
||||
my_module_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -404,6 +433,7 @@ class MyModulesController < ApplicationController
|
|||
|
||||
my_modules = experiment.my_modules
|
||||
.where(my_modules: { id: assignable_my_modules })
|
||||
.order(:name)
|
||||
.pluck(:id, :name)
|
||||
|
||||
return render plain: [].to_json if my_modules.blank?
|
||||
|
@ -424,8 +454,11 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def load_experiment
|
||||
@experiment = Experiment.preload(user_assignments: %i(user user_role)).find_by(id: params[:id])
|
||||
@experiment = Experiment.preload(user_assignments: %i(user user_role))
|
||||
.find_by(id: params[:id] || params[:experiment_id])
|
||||
|
||||
render_404 unless @experiment
|
||||
render_403 unless can_read_experiment?(@experiment)
|
||||
end
|
||||
|
||||
def load_experiment_my_modules
|
||||
|
@ -460,6 +493,19 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def set_inline_name_editing
|
||||
if action_name == 'index'
|
||||
return unless can_manage_experiment?(@experiment)
|
||||
|
||||
@inline_editable_title_config = {
|
||||
name: 'title',
|
||||
params_group: 'experiment',
|
||||
item_id: @experiment.id,
|
||||
field_to_udpate: 'name',
|
||||
path_to_update: experiment_path(@experiment)
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
return unless can_manage_my_module?(@my_module)
|
||||
|
||||
@inline_editable_title_config = {
|
||||
|
@ -487,7 +533,7 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def my_module_tags_params
|
||||
params.require(:my_module).permit(:tag_ids)
|
||||
params.require(:my_module).permit(tag_ids: [])
|
||||
end
|
||||
|
||||
def my_module_designated_users_params
|
||||
|
@ -538,6 +584,12 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def save_view_type(view_type)
|
||||
view_state = @experiment.current_view_state(current_user)
|
||||
view_state.state['my_modules']['view_type'] = view_type
|
||||
view_state.save!
|
||||
end
|
||||
|
||||
def log_activity(type_of, my_module = nil, message_items = {})
|
||||
my_module ||= @my_module
|
||||
message_items = { my_module: my_module.id }.merge(message_items)
|
||||
|
@ -588,6 +640,15 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def set_navigator
|
||||
if action_name == 'index'
|
||||
@navigator = {
|
||||
url: tree_navigator_experiment_path(@experiment),
|
||||
archived: params[:view_mode] == 'archived',
|
||||
id: @experiment.code
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
@navigator = {
|
||||
url: tree_navigator_my_module_path(@my_module),
|
||||
archived: params[:view_mode] == 'archived',
|
||||
|
|
|
@ -40,7 +40,8 @@ class NavigationsController < ApplicationController
|
|||
{
|
||||
name: current_user.full_name,
|
||||
avatar_url: avatar_path(current_user, :icon_small),
|
||||
sign_out_url: destroy_user_session_path
|
||||
sign_out_url: destroy_user_session_path,
|
||||
preferences_url: preferences_url
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ module Navigator
|
|||
{
|
||||
id: project.code,
|
||||
name: project.name,
|
||||
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
|
||||
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
|
||||
archived: project.archived,
|
||||
type: :project,
|
||||
has_children: project.has_children,
|
||||
|
@ -24,7 +24,7 @@ module Navigator
|
|||
url: project_folder_path(folder, view_mode: archived ? 'archived' : 'active'),
|
||||
archived: folder.archived,
|
||||
type: :folder,
|
||||
has_children: folder.has_children,
|
||||
has_children: folder.try(:has_children),
|
||||
children_url: navigator_project_folder_path(folder)
|
||||
}
|
||||
end
|
||||
|
@ -123,7 +123,7 @@ module Navigator
|
|||
'project_folders.archived',
|
||||
"#{has_children_sql} AS has_children"
|
||||
).group('project_folders.id')
|
||||
.having("project_folders.archived = :archived OR #{has_children_sql}", archived: archived)
|
||||
.having('project_folders.archived = :archived', archived: archived)
|
||||
end
|
||||
|
||||
def fetch_experiments(object, archived = false)
|
||||
|
@ -180,6 +180,8 @@ module Navigator
|
|||
archived = params[:archived] == 'true'
|
||||
tree = fetch_projects(folder.parent_folder, archived).map { |i| project_serializer(i, archived) } +
|
||||
fetch_project_folders(folder.parent_folder, archived).map { |i| folder_serializer(i, archived) }
|
||||
# Tree will not contain folder when folder archived state and params archived values are contradictory
|
||||
tree.find { |i| i[:id] == folder.code } || (tree << folder_serializer(folder, archived))
|
||||
tree.find { |i| i[:id] == folder.code }[:children] = children
|
||||
tree = build_folder_tree(folder.parent_folder, tree) if folder.parent_folder.present?
|
||||
tree
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Navigator
|
||||
class ProjectsController < BaseController
|
||||
before_action :load_project
|
||||
before_action :load_project, except: :index
|
||||
before_action :check_read_permissions, except: :index
|
||||
|
||||
def index
|
||||
|
@ -27,6 +27,8 @@ module Navigator
|
|||
|
||||
def load_project
|
||||
@project = current_team.projects.find_by(id: params[:id])
|
||||
|
||||
render_404 unless @project
|
||||
end
|
||||
|
||||
def check_read_permissions
|
||||
|
|
|
@ -13,6 +13,10 @@ class ProjectFoldersController < ApplicationController
|
|||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: %i(archive move_to)
|
||||
|
||||
def tree
|
||||
render json: folders_tree(current_team, current_user)
|
||||
end
|
||||
|
||||
def new
|
||||
@project_folder =
|
||||
current_team.project_folders.new(parent_folder: current_folder, archived: projects_view_mode_archived?)
|
||||
|
@ -48,11 +52,11 @@ class ProjectFoldersController < ApplicationController
|
|||
move_projects(destination_folder)
|
||||
move_folders(destination_folder)
|
||||
end
|
||||
render json: { flash: I18n.t('projects.move.success_flash') }
|
||||
render json: { message: I18n.t('projects.move.success_flash') }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
render json: { flash: I18n.t('projects.move.error_flash') }, status: :bad_request
|
||||
render json: { error: I18n.t('projects.move.error_flash') }, status: :bad_request
|
||||
end
|
||||
|
||||
def move_to_modal
|
||||
|
@ -109,7 +113,7 @@ class ProjectFoldersController < ApplicationController
|
|||
if counter.positive?
|
||||
render json: { message: t('projects.delete_folders.success_flash', number: counter) }
|
||||
else
|
||||
render json: { message: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity
|
||||
render json: { error: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -132,13 +136,9 @@ class ProjectFoldersController < ApplicationController
|
|||
end
|
||||
|
||||
def move_params
|
||||
parsed_params = ActionController::Parameters.new(
|
||||
movables: JSON.parse(params[:movables]),
|
||||
destination_folder_id: params[:destination_folder_id]
|
||||
)
|
||||
parsed_params.require(:destination_folder_id)
|
||||
parsed_params.require(:movables)
|
||||
parsed_params.permit(:destination_folder_id, movables: %i(id type))
|
||||
params.require(:destination_folder_id)
|
||||
params.require(:movables)
|
||||
params.permit(:destination_folder_id, movables: %i(id type))
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
|
@ -150,7 +150,7 @@ class ProjectFoldersController < ApplicationController
|
|||
end
|
||||
|
||||
def move_projects(destination_folder)
|
||||
project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project' }.compact
|
||||
project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'projects' }.compact
|
||||
return if project_ids.blank?
|
||||
|
||||
current_team.projects.where(id: project_ids).each do |project|
|
||||
|
@ -167,7 +167,7 @@ class ProjectFoldersController < ApplicationController
|
|||
end
|
||||
|
||||
def move_folders(destination_folder)
|
||||
folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folder' }.compact
|
||||
folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folders' }.compact
|
||||
return if folder_ids.blank?
|
||||
|
||||
current_team.project_folders.where(id: folder_ids).each do |folder|
|
||||
|
|
|
@ -8,98 +8,38 @@ class ProjectsController < ApplicationController
|
|||
include CardsViewHelper
|
||||
include ExperimentsHelper
|
||||
include Breadcrumbs
|
||||
include UserRolesHelper
|
||||
|
||||
attr_reader :current_folder
|
||||
|
||||
helper_method :current_folder
|
||||
|
||||
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 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 inventory_assigning_project_filter
|
||||
actions_toolbar)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: :edit
|
||||
before_action :load_exp_sort_var, only: :show
|
||||
before_action :reset_invalid_view_state, only: %i(index cards show)
|
||||
before_action :load_vars, only: %i(update notifications create_tag)
|
||||
before_action :load_current_folder, only: :index
|
||||
before_action :check_view_permissions, except: %i(index create update archive_group restore_group
|
||||
inventory_assigning_project_filter
|
||||
actions_toolbar user_roles users_filter)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: :update
|
||||
before_action :set_folder_inline_name_editing, only: %i(index cards)
|
||||
before_action :set_breadcrumbs_items, only: %i(index show)
|
||||
before_action :set_navigator, only: %i(index show)
|
||||
before_action :set_current_projects_view_type, only: %i(index cards)
|
||||
before_action :set_breadcrumbs_items, only: :index
|
||||
before_action :set_navigator, only: :index
|
||||
layout 'fluid'
|
||||
|
||||
def index; end
|
||||
|
||||
def cards
|
||||
overview_service = ProjectsOverviewService.new(current_team, current_user, current_folder, params)
|
||||
title = params[:view_mode] == 'archived' ? t('projects.index.head_title_archived') : t('projects.index.head_title')
|
||||
|
||||
if filters_included?
|
||||
render json: {
|
||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar'),
|
||||
filtered: true,
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/index/team_projects_grouped_by_folder',
|
||||
locals: { projects_by_folder: overview_service.grouped_by_folder_project_cards }
|
||||
)
|
||||
}
|
||||
else
|
||||
if current_folder
|
||||
projects_cards_url = project_folder_cards_url(current_folder)
|
||||
title_html = if @inline_editable_title_config.present?
|
||||
render_to_string(partial: 'shared/inline_editing',
|
||||
locals: {
|
||||
initial_value: current_folder&.name,
|
||||
config: @inline_editable_title_config
|
||||
})
|
||||
else
|
||||
escape_input(current_folder.name)
|
||||
end
|
||||
else
|
||||
projects_cards_url = cards_projects_url
|
||||
title_html = title
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
projects = Lists::ProjectsService.new(current_team, current_user, current_folder, params).call
|
||||
render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user,
|
||||
meta: pagination_dict(projects)
|
||||
end
|
||||
format.html do
|
||||
render 'projects/index'
|
||||
end
|
||||
|
||||
cards = Kaminari.paginate_array(overview_service.project_and_folder_cards)
|
||||
.page(params[:page] || 1).per(Constants::DEFAULT_ELEMENTS_PER_PAGE)
|
||||
|
||||
render json: {
|
||||
projects_cards_url: projects_cards_url,
|
||||
title_html: title_html,
|
||||
next_page: cards.next_page,
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/index/team_projects',
|
||||
locals: { cards: cards, view_mode: params[:view_mode] }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def permissions
|
||||
if stale?([@product, current_team])
|
||||
render json: {
|
||||
editable: can_manage_project?(@project),
|
||||
moveable: can_manage_team?(current_team),
|
||||
archivable: can_archive_project?(@project),
|
||||
restorable: can_restore_project?(@project)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def sidebar
|
||||
@current_sort = params[:sort] || @project.current_view_state(current_user)
|
||||
.state.dig('experiments', params[:view_mode], 'sort')
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/sidebar/experiments', locals: {
|
||||
project: @project,
|
||||
view_mode: experiments_view_mode(@project)
|
||||
}
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def inventory_assigning_project_filter
|
||||
viewable_experiments = Experiment.viewable_by_user(current_user, current_team)
|
||||
|
@ -110,6 +50,7 @@ class ProjectsController < ApplicationController
|
|||
.joins(experiments: :my_modules)
|
||||
.where(experiments: { id: viewable_experiments })
|
||||
.where(my_modules: { id: assignable_my_modules })
|
||||
.order(:name)
|
||||
.distinct
|
||||
.pluck(:id, :name)
|
||||
|
||||
|
@ -118,13 +59,6 @@ class ProjectsController < ApplicationController
|
|||
render json: projects
|
||||
end
|
||||
|
||||
def new
|
||||
@project = current_team.projects.new(project_folder: current_folder)
|
||||
render json: {
|
||||
html: render_to_string(partial: 'projects/index/modals/new_project')
|
||||
}
|
||||
end
|
||||
|
||||
def create
|
||||
@project = current_team.projects.new(project_params)
|
||||
@project.created_by = current_user
|
||||
|
@ -139,14 +73,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
render json: {
|
||||
html: render_to_string(partial: 'projects/index/modals/edit_project_contents',
|
||||
formats: :html,
|
||||
locals: { project: @project })
|
||||
}
|
||||
end
|
||||
|
||||
def update
|
||||
@project.assign_attributes(project_update_params)
|
||||
return_error = false
|
||||
|
@ -321,72 +247,25 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
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')
|
||||
@project_is_managable = can_manage_project?(@project)
|
||||
set_inline_name_editing if @project_is_managable
|
||||
end
|
||||
|
||||
def experiments_cards
|
||||
overview_service = ExperimentsOverviewService.new(@project, current_user, params)
|
||||
cards = overview_service.experiments
|
||||
.preload(my_modules: { my_module_status: :my_module_status_implications })
|
||||
.page(params[:page] || 1)
|
||||
.per(Constants::DEFAULT_ELEMENTS_PER_PAGE)
|
||||
render json: {
|
||||
next_page: cards.next_page,
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/show/experiments_list',
|
||||
locals: { cards: cards,
|
||||
view_mode: params[:view_mode],
|
||||
filters_included: filters_included? }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def notifications
|
||||
@modules = @project.assigned_modules(current_user).order(due_date: :desc)
|
||||
render json: {
|
||||
html: render_to_string(partial: 'notifications', formats: :html)
|
||||
}
|
||||
end
|
||||
|
||||
def users_filter
|
||||
users = current_team.users.search(false, params[:query]).map do |u|
|
||||
{ value: u.id, label: escape_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } }
|
||||
[u.id, u.name, { avatar_url: avatar_path(u, :icon_small) }]
|
||||
end
|
||||
|
||||
render json: users, status: :ok
|
||||
render json: { data: users }, status: :ok
|
||||
end
|
||||
|
||||
def view_type
|
||||
view_state = @project.current_view_state(current_user)
|
||||
view_state.state['experiments']['view_type'] = view_type_params
|
||||
view_state.save!
|
||||
|
||||
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
|
||||
def user_roles
|
||||
render json: { data: user_roles_collection(Project.new).map(&:reverse) }
|
||||
end
|
||||
|
||||
def actions_dropdown
|
||||
if stale?(@project)
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'projects/index/project_actions_dropdown',
|
||||
locals: { project: @project }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def actions_toolbar
|
||||
render json: {
|
||||
actions:
|
||||
Toolbars::ProjectsService.new(
|
||||
current_user,
|
||||
project_ids: params[:project_ids].split(','),
|
||||
project_folder_ids: params[:project_folder_ids].split(',')
|
||||
items: JSON.parse(params[:items])
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -464,30 +343,6 @@ class ProjectsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def load_exp_sort_var
|
||||
if params[:sort]
|
||||
@project.experiments_order = params[:sort].to_s
|
||||
@project.save
|
||||
end
|
||||
@current_sort = @project.experiments_order || 'new'
|
||||
end
|
||||
|
||||
def filters_included?
|
||||
%i(search created_on_from created_on_to updated_on_from updated_on_to members
|
||||
archived_on_from archived_on_to folders_search)
|
||||
.any? { |param_name| params.dig(param_name).present? }
|
||||
end
|
||||
|
||||
def reset_invalid_view_state
|
||||
view_state = if action_name == 'show'
|
||||
@project.current_view_state(current_user)
|
||||
else
|
||||
current_team.current_view_state(current_user)
|
||||
end
|
||||
|
||||
view_state.destroy unless view_state.valid?
|
||||
end
|
||||
|
||||
def log_activity(type_of, project = nil, message_items = {})
|
||||
project ||= @project
|
||||
message_items = { project: project.id }.merge(message_items)
|
||||
|
@ -521,12 +376,4 @@ class ProjectsController < ApplicationController
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
def set_current_projects_view_type
|
||||
if current_team
|
||||
view_state = current_team.current_view_state(current_user)
|
||||
@current_sort = view_state.state.dig('projects', projects_view_mode, 'sort') || 'atoz'
|
||||
@current_view_type = view_state.state.dig('projects', 'view_type')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class ProtocolsController < ApplicationController
|
|||
include ProtocolsIoHelper
|
||||
include TeamsHelper
|
||||
include ProtocolsExporterV2
|
||||
include UserRolesHelper
|
||||
|
||||
before_action :check_create_permissions, only: %i(
|
||||
create
|
||||
|
@ -19,6 +20,7 @@ class ProtocolsController < ApplicationController
|
|||
protocol_status_bar
|
||||
linked_children
|
||||
linked_children_datatable
|
||||
versions_list
|
||||
permissions
|
||||
)
|
||||
before_action :switch_team_with_param, only: %i(index protocolsio_index)
|
||||
|
@ -72,23 +74,36 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
layout 'fluid'
|
||||
|
||||
def index; end
|
||||
|
||||
def datatable
|
||||
render json: ::ProtocolsDatatable.new(
|
||||
view_context,
|
||||
@current_team,
|
||||
@type,
|
||||
current_user
|
||||
)
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
protocols = Lists::ProtocolsService.new(Protocol.viewable_by_user(current_user, @current_team), params).call
|
||||
render json: protocols,
|
||||
each_serializer: Lists::ProtocolSerializer,
|
||||
user: current_user,
|
||||
meta: pagination_dict(protocols)
|
||||
end
|
||||
format.html do
|
||||
render 'index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def versions_modal
|
||||
return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft?
|
||||
|
||||
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
|
||||
|
||||
if @protocol.draft.present? || @protocol.initial_draft?
|
||||
draft = @protocol.initial_draft? ? @protocol : @protocol.draft
|
||||
draft_hash = ProtocolDraftSerializer.new(draft, scope: current_user).as_json
|
||||
end
|
||||
|
||||
render json: {
|
||||
html: render_to_string(partial: 'protocols/index/protocol_versions_modal')
|
||||
draft: draft_hash,
|
||||
versions: @published_versions.map do |version|
|
||||
ProtocolVersionSerializer.new(version, scope: current_user).as_json
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -97,14 +112,44 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def linked_children
|
||||
if params[:version].present?
|
||||
records = @protocol.published_versions_with_original
|
||||
.find_by!(version_number: params[:version])
|
||||
.linked_children
|
||||
else
|
||||
records = Protocol.where(protocol_type: Protocol.protocol_types[:linked])
|
||||
records = records.where(parent_id: @protocol.published_versions)
|
||||
.or(records.where(parent_id: @protocol.id))
|
||||
end
|
||||
records = records.preload(my_module: { experiment: { project: :project_folder } })
|
||||
.distinct.order(updated_at: :desc).page(params[:page]).per(10)
|
||||
|
||||
render json: {
|
||||
title: I18n.t('protocols.index.linked_children.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string(partial: 'protocols/index/linked_children_modal_body',
|
||||
locals: { protocol: @protocol })
|
||||
data: records.map { |record|
|
||||
project_folder = record.my_module.experiment.project.project_folder
|
||||
|
||||
{
|
||||
my_module_name: record.my_module.name,
|
||||
experiment_name: record.my_module.experiment.name,
|
||||
project_name: record.my_module.experiment.project.name,
|
||||
my_module_url: protocols_my_module_path(record.my_module),
|
||||
experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id),
|
||||
project_url: experiments_path(project_id: record.my_module.experiment.project.id),
|
||||
project_folder_name: project_folder.present? ? project_folder.name : nil,
|
||||
project_folder_url: project_folder.present? ? project_folder_projects_url(project_folder) : nil
|
||||
}
|
||||
},
|
||||
next_page: records.next_page,
|
||||
total_pages: records.total_pages
|
||||
}
|
||||
end
|
||||
|
||||
def versions_list
|
||||
render json: { versions: (@protocol.parent || @protocol).published_versions_with_original
|
||||
.order(version_number: :desc)
|
||||
.map(&:version_number) }
|
||||
end
|
||||
|
||||
def linked_children_datatable
|
||||
render json: ::ProtocolLinkedChildrenDatatable.new(
|
||||
view_context,
|
||||
|
@ -152,8 +197,12 @@ class ProtocolsController < ApplicationController
|
|||
nil,
|
||||
protocol: @protocol.id)
|
||||
|
||||
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
||||
redirect_to protocols_path
|
||||
if params[:version_modal]
|
||||
render json: { message: I18n.t('protocols.delete_draft_modal.success') }
|
||||
else
|
||||
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
||||
redirect_to protocols_path
|
||||
end
|
||||
rescue ActiveRecord::RecordNotDestroyed => e
|
||||
Rails.logger.error e.message
|
||||
render json: { message: e.message }, status: :unprocessable_entity
|
||||
|
@ -325,17 +374,15 @@ class ProtocolsController < ApplicationController
|
|||
draft = @protocol.save_as_draft(current_user)
|
||||
|
||||
if draft.invalid?
|
||||
flash[:error] = draft.errors.full_messages.join(', ')
|
||||
redirect_to protocols_path
|
||||
render json: { error: draft.errors.messages.map { |_, value| value }.join(' ') }, status: :unprocessable_entity
|
||||
else
|
||||
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
|
||||
redirect_to protocol_path(draft)
|
||||
render json: { url: protocol_path(draft) }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
flash[:error] = I18n.t('errors.general')
|
||||
redirect_to protocols_path
|
||||
render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
@ -439,19 +486,16 @@ class ProtocolsController < ApplicationController
|
|||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
@protocol.load_from_repository(@source, current_user)
|
||||
rescue StandardError
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.load_from_repository_error')
|
||||
},
|
||||
status: :bad_request
|
||||
end
|
||||
render json: { message: t('my_modules.protocols.load_from_repository_error') }, status: :bad_request
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
log_activity(:load_protocol_to_task_from_repository,
|
||||
|
@ -460,7 +504,7 @@ class ProtocolsController < ApplicationController
|
|||
protocol_repository: @protocol.parent.id)
|
||||
flash[:success] = t('my_modules.protocols.load_from_repository_flash')
|
||||
flash.keep(:success)
|
||||
render json: {}, status: :ok
|
||||
render json: {}
|
||||
end
|
||||
else
|
||||
render json: {
|
||||
|
@ -785,7 +829,7 @@ class ProtocolsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ProtocolsService.new(
|
||||
current_user,
|
||||
protocol_ids: params[:protocol_ids].split(',')
|
||||
protocol_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -806,6 +850,10 @@ class ProtocolsController < ApplicationController
|
|||
render json: { job_id: @job.job_id }
|
||||
end
|
||||
|
||||
def user_roles
|
||||
render json: { data: user_roles_collection(Protocol.new).map(&:reverse) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_importer
|
||||
|
|
|
@ -12,7 +12,7 @@ class ReportsController < ApplicationController
|
|||
before_action :load_available_repositories, only: %i(index save_pdf_to_inventory_modal available_repositories)
|
||||
before_action :check_project_read_permissions, only: %i(create edit update generate_pdf
|
||||
generate_docx new_template_values project_contents)
|
||||
before_action :check_read_permissions, except: %i(index datatable new create edit update destroy actions_toolbar generate_pdf
|
||||
before_action :check_read_permissions, except: %i(index new create edit update destroy actions_toolbar generate_pdf
|
||||
generate_docx new_template_values project_contents
|
||||
available_repositories)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
|
@ -21,14 +21,17 @@ class ReportsController < ApplicationController
|
|||
after_action :generate_pdf_report, only: %i(create update generate_pdf)
|
||||
|
||||
# Index showing all reports of a single project
|
||||
def index; end
|
||||
|
||||
def datatable
|
||||
render json: ::ReportDatatable.new(
|
||||
view_context,
|
||||
current_user,
|
||||
Report.viewable_by_user(current_user, current_team)
|
||||
)
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
reports = Lists::ReportsService.new(Report.viewable_by_user(current_user, current_team), params).call
|
||||
render json: reports, each_serializer: Lists::ReportSerializer,
|
||||
user: current_user, meta: pagination_dict(reports)
|
||||
end
|
||||
format.html do
|
||||
render 'index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Report grouped by modules
|
||||
|
@ -137,7 +140,7 @@ class ReportsController < ApplicationController
|
|||
# Destroy multiple entries action
|
||||
def destroy
|
||||
begin
|
||||
report_ids = JSON.parse(params[:report_ids])
|
||||
report_ids = params[:report_ids]
|
||||
rescue
|
||||
render_404
|
||||
end
|
||||
|
@ -151,25 +154,11 @@ class ReportsController < ApplicationController
|
|||
report.destroy
|
||||
end
|
||||
|
||||
redirect_to reports_path
|
||||
render json: { message: I18n.t('projects.reports.index.modal_delete.success') }
|
||||
end
|
||||
|
||||
def status
|
||||
docx = @report.docx_preview_file.attached? ? document_preview_report_path(@report, report_type: :docx) : nil
|
||||
pdf = @report.pdf_file.attached? ? document_preview_report_path(@report, report_type: :pdf) : nil
|
||||
|
||||
render json: {
|
||||
docx: {
|
||||
processing: @report.docx_processing?,
|
||||
preview_url: docx,
|
||||
error: @report.docx_error?
|
||||
},
|
||||
pdf: {
|
||||
processing: @report.pdf_processing?,
|
||||
preview_url: pdf,
|
||||
error: @report.pdf_error?
|
||||
}
|
||||
}
|
||||
render json: @report, serializer: Lists::ReportSerializer
|
||||
end
|
||||
|
||||
# Generation actions
|
||||
|
@ -283,7 +272,7 @@ class ReportsController < ApplicationController
|
|||
end
|
||||
|
||||
def available_repositories
|
||||
render json: { results: @available_repositories }, status: :ok
|
||||
render json: { data: @available_repositories.map { |r| [r.id, r.name] } }
|
||||
end
|
||||
|
||||
def document_preview
|
||||
|
@ -302,7 +291,7 @@ class ReportsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ReportsService.new(
|
||||
current_user,
|
||||
report_ids: params[:report_ids].split(',')
|
||||
report_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -360,7 +349,7 @@ class ReportsController < ApplicationController
|
|||
@available_repositories = []
|
||||
repositories = Repository.active
|
||||
.accessible_by_teams(current_team)
|
||||
.name_like(search_params[:q])
|
||||
.name_like(search_params[:query])
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
repositories.each do |repository|
|
||||
next unless can_manage_repository_rows?(current_user, repository)
|
||||
|
@ -376,11 +365,11 @@ class ReportsController < ApplicationController
|
|||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:q)
|
||||
params.permit(:query)
|
||||
end
|
||||
|
||||
def save_pdf_params
|
||||
params.permit(:repository_id, :respository_column_id, :repository_item_id)
|
||||
params.permit(:repository_id, :repository_column_id, :repository_item_id)
|
||||
end
|
||||
|
||||
def log_activity(type_of, report = @report)
|
||||
|
@ -401,6 +390,8 @@ class ReportsController < ApplicationController
|
|||
|
||||
ensure_report_template!
|
||||
Reports::PdfJob.perform_later(@report.id, user_id: current_user.id)
|
||||
rescue ActiveRecord::ActiveRecordError => e
|
||||
Rails.logger.error e.message
|
||||
end
|
||||
|
||||
def ensure_report_template!
|
||||
|
|
|
@ -11,7 +11,7 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
|
||||
export_modal export_repositories)
|
||||
before_action :load_repositories, only: %i(index show sidebar)
|
||||
before_action :load_repositories, only: :index
|
||||
before_action :load_repositories_for_archiving, only: :archive
|
||||
before_action :load_repositories_for_restoring, only: :restore
|
||||
before_action :check_view_all_permissions, only: %i(index sidebar)
|
||||
|
@ -35,7 +35,9 @@ class RepositoriesController < ApplicationController
|
|||
render 'index'
|
||||
end
|
||||
format.json do
|
||||
render json: prepare_repositories_datatable(@repositories, current_team, params)
|
||||
repositories = Lists::RepositoriesService.new(@repositories, params).call
|
||||
render json: repositories, each_serializer: Lists::RepositorySerializer, user: current_user,
|
||||
meta: pagination_dict(repositories)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -93,6 +95,11 @@ class RepositoriesController < ApplicationController
|
|||
render json: { html: render_to_string(partial: 'share_repository_modal', formats: :html) }
|
||||
end
|
||||
|
||||
def shareable_teams
|
||||
teams = current_user.teams.order(:name) - [@repository.team]
|
||||
render json: teams, each_serializer: ShareableTeamSerializer, repository: @repository
|
||||
end
|
||||
|
||||
def hide_reminders
|
||||
# synchronously hide currently visible reminders
|
||||
if params[:visible_reminder_repository_row_ids].present?
|
||||
|
@ -122,12 +129,9 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
if @repository.save
|
||||
log_activity(:create_inventory)
|
||||
|
||||
flash[:success] = t('repositories.index.modal_create.success_flash_html', name: @repository.name)
|
||||
render json: { url: repository_path(@repository) }
|
||||
render json: { message: t('repositories.index.modal_create.success_flash_html', name: @repository.name) }
|
||||
else
|
||||
render json: @repository.errors,
|
||||
status: :unprocessable_entity
|
||||
render json: @repository.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -163,14 +167,14 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
flash[:success] = t('repositories.index.delete_flash',
|
||||
name: @repository.name)
|
||||
|
||||
log_activity(:delete_inventory) # Log before delete id
|
||||
|
||||
@repository.discard
|
||||
@repository.destroy_discarded(current_user.id)
|
||||
redirect_to team_repositories_path(archived: true)
|
||||
|
||||
render json: {
|
||||
message: t('repositories.index.delete_flash', name: @repository.name)
|
||||
}
|
||||
end
|
||||
|
||||
def rename_modal
|
||||
|
@ -236,17 +240,22 @@ class RepositoriesController < ApplicationController
|
|||
render json: @tmp_repository.errors, status: :unprocessable_entity
|
||||
else
|
||||
copied_repository = @repository.copy(current_user, @tmp_repository.name)
|
||||
old_repo_stock_column = @repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
|
||||
copied_repo_stock_column = copied_repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
|
||||
|
||||
if old_repo_stock_column && copied_repo_stock_column
|
||||
old_repo_stock_column.repository_stock_unit_items.each do |item|
|
||||
copied_item = item.dup
|
||||
copied_repo_stock_column.repository_stock_unit_items << copied_item
|
||||
end
|
||||
copied_repository.save!
|
||||
end
|
||||
|
||||
if !copied_repository
|
||||
render json: { name: ['Server error'] }, status: :unprocessable_entity
|
||||
else
|
||||
flash[:success] = t(
|
||||
'repositories.index.copy_flash',
|
||||
old: @repository.name,
|
||||
new: copied_repository.name
|
||||
)
|
||||
render json: {
|
||||
url: repository_path(copied_repository)
|
||||
message: t('repositories.index.copy_flash', old: @repository.name, new: copied_repository.name)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -426,7 +435,7 @@ class RepositoriesController < ApplicationController
|
|||
Toolbars::RepositoriesService.new(
|
||||
current_user,
|
||||
current_team,
|
||||
repository_ids: params[:repository_ids].split(',')
|
||||
repository_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
@ -450,12 +459,7 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def load_repositories
|
||||
@repositories = Repository.accessible_by_teams(current_team).order('repositories.created_at ASC')
|
||||
@repositories = if params[:archived] == 'true' || @repository&.archived?
|
||||
@repositories.archived
|
||||
else
|
||||
@repositories.active
|
||||
end
|
||||
@repositories = Repository.accessible_by_teams(current_team)
|
||||
end
|
||||
|
||||
def load_repositories_for_archiving
|
||||
|
|
|
@ -83,15 +83,7 @@ class RepositoryColumnsController < ApplicationController
|
|||
end
|
||||
|
||||
def available_asset_type_columns
|
||||
if @asset_columns.blank?
|
||||
render json: {
|
||||
no_items: t(
|
||||
'projects.reports.new.save_PDF_to_inventory_modal.no_columns'
|
||||
)
|
||||
}
|
||||
else
|
||||
render json: { results: @asset_columns }, status: :ok
|
||||
end
|
||||
render json: { data: @asset_columns.map { |c| [c.id, c.name] } }, status: :ok
|
||||
end
|
||||
|
||||
def available_columns
|
||||
|
|
|
@ -148,7 +148,7 @@ class RepositoryRowConnectionsController < ApplicationController
|
|||
name: repository_row.name_with_label,
|
||||
code: repository_row.code,
|
||||
path: repository_repository_row_path(repository_row.repository, repository_row),
|
||||
repository_name: repository_row.repository.name,
|
||||
repository_name: repository_row.repository.name_with_label,
|
||||
repository_path: repository_path(repository_row.repository),
|
||||
unlink_path: repository_repository_row_repository_row_connection_path(
|
||||
repository_row.repository,
|
||||
|
|
|
@ -272,11 +272,9 @@ class RepositoryRowsController < ApplicationController
|
|||
"#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'),
|
||||
repository_path(@repository),
|
||||
data: { 'no-turbolink' => true })}"
|
||||
render json: { no_items: no_items_string },
|
||||
status: :ok
|
||||
render json: { no_items: no_items_string }, status: :unprocessable_entity
|
||||
else
|
||||
render json: { results: load_available_rows },
|
||||
status: :ok
|
||||
render json: { results: load_available_rows }, status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class ResultsController < ApplicationController
|
||||
include Breadcrumbs
|
||||
include TeamsHelper
|
||||
skip_before_action :verify_authenticity_token, only: %i(create update destroy duplicate)
|
||||
before_action :load_my_module
|
||||
before_action :load_vars, only: %i(destroy elements assets upload_attachment archive restore destroy
|
||||
|
@ -148,17 +149,17 @@ class ResultsController < ApplicationController
|
|||
def apply_sort!
|
||||
case params[:sort]
|
||||
when 'updated_at_asc'
|
||||
@results = @results.order(updated_at: :asc)
|
||||
@results = @results.order('results.updated_at' => :asc)
|
||||
when 'updated_at_desc'
|
||||
@results = @results.order(updated_at: :desc)
|
||||
@results = @results.order('results.updated_at' => :desc)
|
||||
when 'created_at_asc'
|
||||
@results = @results.order(created_at: :asc)
|
||||
@results = @results.order('results.created_at' => :asc)
|
||||
when 'created_at_desc'
|
||||
@results = @results.order(created_at: :desc)
|
||||
@results = @results.order('results.created_at' => :desc)
|
||||
when 'name_asc'
|
||||
@results = @results.order(name: :asc)
|
||||
@results = @results.order('results.name' => :asc)
|
||||
when 'name_desc'
|
||||
@results = @results.order(name: :desc)
|
||||
@results = @results.order('results.name' => :desc)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -167,14 +168,15 @@ class ResultsController < ApplicationController
|
|||
@results = @results.search(current_user, params[:view_mode] == 'archived', params[:query], params[:page] || 1)
|
||||
end
|
||||
|
||||
@results = @results.where('created_at >= ?', params[:created_at_from]) if params[:created_at_from]
|
||||
@results = @results.where('created_at <= ?', params[:created_at_to]) if params[:created_at_to]
|
||||
@results = @results.where('updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
|
||||
@results = @results.where('updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
|
||||
@results = @results.where('results.created_at >= ?', params[:created_at_from]) if params[:created_at_from]
|
||||
@results = @results.where('results.created_at <= ?', params[:created_at_to]) if params[:created_at_to]
|
||||
@results = @results.where('results.updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
|
||||
@results = @results.where('results.updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
|
||||
end
|
||||
|
||||
def load_my_module
|
||||
@my_module = MyModule.readable_by_user(current_user).find(params[:my_module_id])
|
||||
current_team_switch(@my_module.team) if current_team != @my_module.team
|
||||
end
|
||||
|
||||
def load_vars
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
class TagsController < ApplicationController
|
||||
before_action :load_vars, only: [:create, :update, :destroy]
|
||||
before_action :load_vars, only: %i(index create update destroy)
|
||||
before_action :load_vars_nested, only: [:update, :destroy]
|
||||
before_action :check_manage_permissions, only: %i(create update destroy)
|
||||
|
||||
def index
|
||||
render json: @project.tags, each_serializer: TagSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@tag = Tag.new(tag_params)
|
||||
@tag.created_by = current_user
|
||||
@tag.last_modified_by = current_user
|
||||
@tag.project ||= @project
|
||||
|
||||
if @tag.name.blank?
|
||||
@tag.name = t("tags.create.new_name")
|
||||
|
|
|
@ -55,7 +55,7 @@ class TeamRepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def update_params
|
||||
params.permit(:permission_changes, share_team_ids: [], write_permissions: [])
|
||||
params.permit(permission_changes: {}, share_team_ids: [], write_permissions: [])
|
||||
end
|
||||
|
||||
def check_sharing_permissions
|
||||
|
@ -79,7 +79,7 @@ class TeamRepositoriesController < ApplicationController
|
|||
def teams_to_update
|
||||
return [] if update_params[:permission_changes].blank?
|
||||
|
||||
teams_to_update = JSON.parse(update_params[:permission_changes]).keys.map(&:to_i).to_a &
|
||||
teams_to_update = update_params[:permission_changes].keys.map(&:to_i).to_a &
|
||||
update_params[:share_team_ids]&.map(&:to_i).to_a
|
||||
wp = update_params[:write_permissions]&.map(&:to_i)
|
||||
|
||||
|
|
|
@ -91,12 +91,11 @@ class UserMyModulesController < ApplicationController
|
|||
users = @my_module.users
|
||||
.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)
|
||||
.where('trim_html_tags(users.full_name) ILIKE ?',
|
||||
"%#{ActiveRecord::Base.sanitize_sql_like(params[:query])}%")
|
||||
.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')
|
||||
.select('CASE WHEN user_my_modules.id IS NOT NULL THEN true ELSE false END as designated')
|
||||
.order('designated DESC', :full_name)
|
||||
|
||||
users = users.map do |user|
|
||||
next if params[:skip_assigned] && user.designated
|
||||
|
|
|
@ -34,7 +34,7 @@ module Users
|
|||
user = User.from_omniauth(auth)
|
||||
|
||||
# User found in database so just signing in
|
||||
return sign_in_and_redirect(user) if user.present?
|
||||
return sign_in_and_redirect(user, event: :authentication) if user.present?
|
||||
|
||||
if email.blank?
|
||||
# No email in the token so can not link or create user
|
||||
|
@ -47,12 +47,12 @@ module Users
|
|||
if user.blank?
|
||||
# Create new user and identity
|
||||
user = create_user_from_auth(email, auth)
|
||||
sign_in_and_redirect(user)
|
||||
sign_in_and_redirect(user, event: :authentication)
|
||||
elsif provider_conf['auto_link_on_sign_in']
|
||||
# Link to existing local account
|
||||
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
|
||||
user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank?
|
||||
sign_in_and_redirect(user)
|
||||
sign_in_and_redirect(user, event: :authentication)
|
||||
else
|
||||
# Cannot do anything with it, so just return an error
|
||||
error_message = I18n.t('devise.azure.errors.no_local_user_map')
|
||||
|
@ -74,14 +74,13 @@ module Users
|
|||
|
||||
def linkedin
|
||||
auth_hash = request.env['omniauth.auth']
|
||||
|
||||
@user = User.from_omniauth(auth_hash)
|
||||
if @user && @user.current_team_id?
|
||||
# User already exists and has been signed up with LinkedIn; just sign in
|
||||
set_flash_message(:notice,
|
||||
:success,
|
||||
kind: I18n.t('devise.linkedin.provider_name'))
|
||||
sign_in_and_redirect @user
|
||||
sign_in_and_redirect(@user, event: :authentication)
|
||||
elsif @user
|
||||
# User already exists and has started sign up with LinkedIn;
|
||||
# but doesn't have team (needs to complete sign up - agrees to TOS)
|
||||
|
@ -89,7 +88,8 @@ module Users
|
|||
:failure,
|
||||
kind: I18n.t('devise.linkedin.provider_name'),
|
||||
reason: I18n.t('devise.linkedin.complete_sign_up'))
|
||||
redirect_to users_sign_up_provider_path(user: @user)
|
||||
sign_in(@user, event: :authentication)
|
||||
redirect_to users_sign_up_provider_path
|
||||
elsif User.find_by_email(auth_hash['info']['email'])
|
||||
# email is already taken, so sign up with Linked in is not allowed
|
||||
set_flash_message(:alert,
|
||||
|
@ -123,7 +123,8 @@ module Users
|
|||
end
|
||||
# Confirm user
|
||||
@user.update!(confirmed_at: @user.created_at)
|
||||
redirect_to users_sign_up_provider_path(user: @user)
|
||||
sign_in(@user, event: :authentication)
|
||||
redirect_to users_sign_up_provider_path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -131,7 +132,7 @@ module Users
|
|||
auth = request.env['omniauth.auth']
|
||||
user = User.from_omniauth(auth)
|
||||
# User found in database so just signing in
|
||||
return sign_in_and_redirect(user) if user.present?
|
||||
return sign_in_and_redirect(user, event: :authentication) if user.present?
|
||||
|
||||
user = User.find_by(email: auth.info.email.downcase)
|
||||
|
||||
|
@ -142,7 +143,7 @@ module Users
|
|||
user.user_identities.create!(provider: auth.provider, uid: auth.uid)
|
||||
user.update!(confirmed_at: user.created_at) if user.confirmed_at.blank?
|
||||
end
|
||||
sign_in_and_redirect(user)
|
||||
sign_in_and_redirect(user, event: :authentication)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
|
|
|
@ -152,29 +152,28 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
end
|
||||
|
||||
def new_with_provider; end
|
||||
def new_with_provider
|
||||
return render_403 unless current_user
|
||||
return render_403 unless Rails.configuration.x.new_team_on_signup
|
||||
|
||||
@team = Team.new
|
||||
render layout: 'sign_in_halt'
|
||||
end
|
||||
|
||||
def create_with_provider
|
||||
@user = User.find_by_id(user_provider_params['user'])
|
||||
return render_403 unless current_user
|
||||
return render_403 unless Rails.configuration.x.new_team_on_signup
|
||||
|
||||
# Create new team for the new user
|
||||
@team = Team.new(team_provider_params)
|
||||
@team.created_by = current_user # set created_by for the team
|
||||
|
||||
if @team.valid? && @user && Rails.configuration.x.new_team_on_signup
|
||||
# Set the confirmed_at == created_at IF not using email confirmations
|
||||
unless Rails.configuration.x.enable_email_confirmations
|
||||
@user.update!(confirmed_at: @user.created_at)
|
||||
end
|
||||
|
||||
@team.created_by = @user # set created_by for team
|
||||
@team.save!
|
||||
|
||||
if @team.save
|
||||
# set current team to new user
|
||||
@user.current_team_id = @team.id
|
||||
@user.save!
|
||||
|
||||
sign_in_and_redirect @user
|
||||
current_user.update(current_team_id: @team.id)
|
||||
redirect_to root_path
|
||||
else
|
||||
render :new_with_provider
|
||||
render :new_with_provider, layout: 'sign_in_halt'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
29
app/controllers/users/settings/user_settings_controller.rb
Normal file
29
app/controllers/users/settings/user_settings_controller.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
module Settings
|
||||
class UserSettingsController < ApplicationController
|
||||
def show
|
||||
render json: { data: current_user.settings[params[:key]] }
|
||||
end
|
||||
|
||||
def update
|
||||
settings_params = params.require(:settings)
|
||||
|
||||
settings_params.each do |setting|
|
||||
key = setting[:key]
|
||||
data = setting[:data]
|
||||
|
||||
current_user.settings[key] = data if Extends::WHITELISTED_USER_SETTINGS.include?(key.to_s)
|
||||
end
|
||||
|
||||
if current_user.save
|
||||
head :ok
|
||||
else
|
||||
render json: { error: 'Failed to update settings', details: current_user.errors.full_messages },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,91 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class LabelTemplateDatatable < CustomDatatable
|
||||
include InputSanitizeHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
TABLE_COLUMNS = %w(
|
||||
label_templates.default
|
||||
label_templates.name
|
||||
label_templates.type
|
||||
label_templates.description
|
||||
label_templates.modified_by
|
||||
label_templates.updated_at
|
||||
label_templates.created_by_user
|
||||
label_templates.created_at
|
||||
).freeze
|
||||
|
||||
def initialize(view, label_templates)
|
||||
super(view)
|
||||
@label_templates = label_templates
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
@sortable_columns ||= TABLE_COLUMNS
|
||||
end
|
||||
|
||||
def searchable_columns
|
||||
@searchable_columns ||= TABLE_COLUMNS
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def data
|
||||
records.map do |record|
|
||||
{
|
||||
'0' => record.id,
|
||||
'1' => record.default,
|
||||
'2' => append_format_icon(record),
|
||||
'3' => escape_input(record.label_format),
|
||||
'4' => escape_input(record.description),
|
||||
'5' => escape_input(record.modified_by),
|
||||
'6' => I18n.l(record.updated_at, format: :full),
|
||||
'7' => escape_input(record.created_by_user),
|
||||
'8' => I18n.l(record.created_at, format: :full),
|
||||
'recordInfoUrl' => '',
|
||||
'DT_RowAttr': {
|
||||
'data-edit-url': label_template_path(record),
|
||||
'data-set-default-url': set_default_label_template_path(record),
|
||||
'data-default': record.default,
|
||||
'data-format': record.label_format
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def append_format_icon(record)
|
||||
{
|
||||
icon_image_tag:
|
||||
ActionController::Base.helpers.image_tag(
|
||||
"label_template_icons/#{record.icon}.svg",
|
||||
class: 'label-template-icon'
|
||||
),
|
||||
name: escape_input(record.name)
|
||||
}
|
||||
end
|
||||
|
||||
def get_raw_records
|
||||
res = @label_templates.joins(
|
||||
'LEFT OUTER JOIN users AS creators ' \
|
||||
'ON label_templates.created_by_id = creators.id'
|
||||
).joins(
|
||||
'LEFT OUTER JOIN users AS modifiers '\
|
||||
'ON label_templates.last_modified_by_id = modifiers.id'
|
||||
).select('label_templates.* AS label_templates')
|
||||
.select('creators.full_name AS created_by_user')
|
||||
.select('modifiers.full_name AS modified_by')
|
||||
.select(
|
||||
"('#{Extends::LABEL_TEMPLATE_FORMAT_MAP.to_json}'::jsonb -> label_templates.type)::text "\
|
||||
"AS label_format"
|
||||
)
|
||||
LabelTemplate.from(res, :label_templates)
|
||||
end
|
||||
|
||||
def filter_records(records)
|
||||
records.where_attributes_like(
|
||||
['label_templates.name', 'label_templates.label_format', 'label_templates.description',
|
||||
'label_templates.modified_by', 'label_templates.created_by_user'],
|
||||
dt_params.dig(:search, :value)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,282 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtocolsDatatable < CustomDatatable
|
||||
# Needed for sanitize_sql_like method
|
||||
include ActiveRecord::Sanitization::ClassMethods
|
||||
include InputSanitizeHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))"
|
||||
|
||||
def_delegator :@view, :linked_children_protocol_path
|
||||
def_delegator :@view, :protocol_path
|
||||
|
||||
def initialize(view, team, type, user)
|
||||
super(view)
|
||||
@team = team
|
||||
@type = type # :active or :archived
|
||||
@user = user
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
@sortable_columns ||= [
|
||||
'name',
|
||||
'adjusted_parent_id',
|
||||
'nr_of_versions',
|
||||
'protocol_keywords_str',
|
||||
'nr_of_linked_tasks',
|
||||
'nr_of_assigned_users',
|
||||
'full_username_str',
|
||||
'published_on',
|
||||
'updated_at',
|
||||
'archived_full_username_str',
|
||||
'archived_on'
|
||||
]
|
||||
end
|
||||
|
||||
def searchable_columns
|
||||
@searchable_columns ||= [
|
||||
'Protocol.name',
|
||||
'Protocol.archived_on',
|
||||
'Protocol.published_on',
|
||||
"Protocol.#{PREFIXED_ID_SQL}",
|
||||
'Protocol.updated_at',
|
||||
'ProtocolKeyword.name'
|
||||
]
|
||||
end
|
||||
|
||||
def as_json(_options = {})
|
||||
{
|
||||
draw: dt_params[:draw].to_i,
|
||||
recordsTotal: get_raw_records_base.distinct.count,
|
||||
recordsFiltered: records.present? ? records.first.filtered_count : 0,
|
||||
data: data
|
||||
}
|
||||
end
|
||||
|
||||
# A hack that overrides the new_search_contition method default behavior of the ajax-datatables-rails gem
|
||||
# now the method checks if the column is the created_at or updated_at and generate a custom SQL to parse
|
||||
# it back to the caller method
|
||||
def new_search_condition(column, value)
|
||||
model, column = column.split('.', 2)
|
||||
model = model.constantize
|
||||
case column
|
||||
when PREFIXED_ID_SQL
|
||||
casted_column = ::Arel::Nodes::SqlLiteral.new(PREFIXED_ID_SQL)
|
||||
when 'published_on'
|
||||
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
|
||||
when 'updated_at'
|
||||
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
[ Arel.sql("to_char( protocols.updated_at, '#{ formated_date }' ) AS VARCHAR") ] )
|
||||
else
|
||||
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
[model.arel_table[column.to_sym].as(typecast)])
|
||||
end
|
||||
casted_column.matches("%#{ActiveRecord::Base.sanitize_sql_like(value)}%")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns json of current protocols (already paginated)
|
||||
def data
|
||||
records.map do |record|
|
||||
parent = record.parent || record
|
||||
{
|
||||
DT_RowId: record.id,
|
||||
DT_RowAttr: {
|
||||
'data-permissions-url': permissions_protocol_path(parent),
|
||||
'data-versions-url': versions_modal_protocol_path(parent)
|
||||
},
|
||||
'1': name_html(parent),
|
||||
'2': parent.code,
|
||||
'3': versions_html(record),
|
||||
'4': keywords_html(record),
|
||||
'5': modules_html(record),
|
||||
'6': access_html(parent),
|
||||
'7': published_by(record),
|
||||
'8': published_timestamp(record),
|
||||
'9': modified_timestamp(record),
|
||||
'10': escape_input(record.archived_full_username_str),
|
||||
'11': (I18n.l(record.archived_on, format: :full) if record.archived_on)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_records
|
||||
super.select('COUNT("protocols"."id") OVER() AS filtered_count')
|
||||
end
|
||||
|
||||
def filter_protocols_records(records)
|
||||
if params[:name_and_keywords].present?
|
||||
records = records.where_attributes_like(['protocols.name', 'protocol_keywords.name'], params[:name_and_keywords])
|
||||
end
|
||||
|
||||
if params[:published_on_from].present?
|
||||
records = records.where('protocols.published_on > ?', params[:published_on_from])
|
||||
end
|
||||
records = records.where('protocols.published_on < ?', params[:published_on_to]) if params[:published_on_to].present?
|
||||
records = records.where('protocols.updated_at > ?', params[:modified_on_from]) if params[:modified_on_from].present?
|
||||
records = records.where('protocols.updated_at < ?', params[:modified_on_to]) if params[:modified_on_to].present?
|
||||
records = records.where(protocols: { published_by_id: params[:published_by] }) if params[:published_by].present?
|
||||
|
||||
if params[:members].present?
|
||||
records = records.where(all_user_assignments: { user_id: params[:members] })
|
||||
end
|
||||
|
||||
if params[:archived_on_from].present?
|
||||
records = records.where('protocols.archived_on > ?', params[:archived_on_from])
|
||||
end
|
||||
records = records.where('protocols.archived_on < ?', params[:archived_on_to]) if params[:archived_on_to].present?
|
||||
|
||||
records = records.where(protocols: { archived_by_id: params[:archived_by] }) if params[:archived_by].present?
|
||||
|
||||
if params[:has_draft].present?
|
||||
records =
|
||||
records
|
||||
.joins("LEFT OUTER JOIN protocols protocol_drafts " \
|
||||
"ON protocol_drafts.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||
"AND (protocol_drafts.parent_id = protocols.id OR protocol_drafts.parent_id = protocols.parent_id)")
|
||||
.where('protocols.protocol_type = ? OR protocol_drafts.id IS NOT NULL',
|
||||
Protocol.protocol_types[:in_repository_draft])
|
||||
end
|
||||
|
||||
records
|
||||
end
|
||||
|
||||
def get_raw_records_base
|
||||
team_protocols = Protocol.where(team: @team)
|
||||
original_without_versions = team_protocols
|
||||
.left_outer_joins(:published_versions)
|
||||
.in_repository_published_original
|
||||
.where(published_versions: { id: nil })
|
||||
.select(:id)
|
||||
published_versions = team_protocols
|
||||
.in_repository_published_version
|
||||
.order(:parent_id, version_number: :desc)
|
||||
.select('DISTINCT ON (parent_id) id')
|
||||
new_drafts = team_protocols
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
|
||||
.select(:id)
|
||||
|
||||
records = Protocol.where('protocols.id IN (?) OR protocols.id IN (?) OR protocols.id IN (?)',
|
||||
original_without_versions, published_versions, new_drafts)
|
||||
|
||||
records = @type == :archived ? records.archived : records.active
|
||||
|
||||
records.viewable_by_user(@user, @team)
|
||||
end
|
||||
|
||||
# Query database for records (this will be later paginated and filtered)
|
||||
# after that "data" function will return json
|
||||
def get_raw_records
|
||||
records =
|
||||
get_raw_records_base
|
||||
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \
|
||||
"AND protocol_versions.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \
|
||||
"ON self_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND self_linked_task_protocols.parent_id = protocols.id")
|
||||
.joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \
|
||||
"ON parent_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND parent_linked_task_protocols.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \
|
||||
"ON version_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND version_linked_task_protocols.parent_id = protocol_versions.id " \
|
||||
"AND version_linked_task_protocols.parent_id != protocols.id")
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.joins('LEFT OUTER JOIN "users" "archived_users" ON "archived_users"."id" = "protocols"."archived_by_id"')
|
||||
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" ' \
|
||||
'ON "all_user_assignments"."assignable_type" = \'Protocol\' ' \
|
||||
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
|
||||
.group('"protocols"."id"')
|
||||
|
||||
records = filter_protocols_records(records)
|
||||
records.select(
|
||||
'"protocols".*',
|
||||
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
|
||||
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
|
||||
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||
"END AS nr_of_versions",
|
||||
'(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks',
|
||||
'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"',
|
||||
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
|
||||
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
|
||||
)
|
||||
end
|
||||
|
||||
# Various helper methods
|
||||
|
||||
def name_html(record)
|
||||
path =
|
||||
if record.in_repository_published_original? && record.latest_published_version.present?
|
||||
protocol_path(record.latest_published_version)
|
||||
else
|
||||
protocol_path(record)
|
||||
end
|
||||
|
||||
if can_read_protocol_in_repository?(@user, record)
|
||||
"<a href='#{path}'>#{escape_input(record.name)}</a>"
|
||||
else
|
||||
# team admin can only see recod name
|
||||
"<span class='not-clickable-record'>#{escape_input(record.name)}</span>"
|
||||
end
|
||||
end
|
||||
|
||||
def keywords_html(record)
|
||||
if record.protocol_keywords.blank?
|
||||
I18n.t('protocols.no_keywords')
|
||||
else
|
||||
res = []
|
||||
record.protocol_keywords.sort_by { |kw| kw.name.downcase }.each do |kw|
|
||||
sanitized_kw = escape_input(kw.name)
|
||||
res << "<a href='#' data-action='filter' data-param='#{sanitized_kw}'>#{sanitized_kw}</a>"
|
||||
end
|
||||
res.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
def modules_html(record)
|
||||
"<a href='#' data-action='load-linked-children'" \
|
||||
"data-url='#{linked_children_protocol_path(record.parent || record)}'>" \
|
||||
"#{record.nr_of_linked_tasks}" \
|
||||
"</a>"
|
||||
end
|
||||
|
||||
def versions_html(record)
|
||||
@view.controller
|
||||
.render_to_string(partial: 'protocols/index/protocol_versions',
|
||||
formats: :html,
|
||||
locals: { protocol: record, readable: can_read_protocol_in_repository?(@user, record) })
|
||||
end
|
||||
|
||||
def access_html(record)
|
||||
@view.controller.render_to_string(partial: 'protocols/index/protocol_access', formats: :html, locals: { protocol: record })
|
||||
end
|
||||
|
||||
def published_by(record)
|
||||
return '' if record.published_by.blank?
|
||||
|
||||
escape_input(record.published_by.full_name)
|
||||
end
|
||||
|
||||
def published_timestamp(record)
|
||||
return '' if record.published_on.blank?
|
||||
|
||||
I18n.l(record.published_on, format: :full)
|
||||
end
|
||||
|
||||
def modified_timestamp(record)
|
||||
I18n.l(record.updated_at, format: :full)
|
||||
end
|
||||
end
|
|
@ -68,11 +68,15 @@ module GlobalActivitiesHelper
|
|||
|
||||
path = repository_path(obj.repository, team: obj.repository.team.id)
|
||||
when Project
|
||||
path = obj.archived? ? projects_path : project_path(obj)
|
||||
path = obj.archived? ? projects_path : experiments_path(project_id: obj)
|
||||
when Experiment
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : my_modules_experiment_path(obj)
|
||||
path = if obj.archived?
|
||||
experiments_path(project_id: obj.project, view_mode: :archived)
|
||||
else
|
||||
my_modules_experiment_path(obj)
|
||||
end
|
||||
when MyModule
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ module MyModulesHelper
|
|||
{
|
||||
type: project.class.name.underscore,
|
||||
value: project.name,
|
||||
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
|
||||
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
|
||||
archived: archived
|
||||
}
|
||||
end
|
||||
|
|
|
@ -30,34 +30,13 @@ module ProjectsHelper
|
|||
conns.to_s[1..-2]
|
||||
end
|
||||
|
||||
def sidebar_folders_tree(team, user, sort, folders_only: false)
|
||||
def folders_tree(team, user)
|
||||
sort ||= team.current_view_state(user).state.dig('projects', 'active', 'sort')
|
||||
if projects_view_mode_archived?
|
||||
records = ProjectFolder.archived.inner_folders(team)
|
||||
records += team.projects.archived.visible_to(user, team) unless folders_only
|
||||
records = ProjectFolder.archived.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id)
|
||||
else
|
||||
records = ProjectFolder.active.inner_folders(team)
|
||||
records += team.projects.active.visible_to(user, team) unless folders_only
|
||||
sort = 'new' if %w(archived_old archived_new).include?(sort)
|
||||
records = ProjectFolder.active.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id)
|
||||
end
|
||||
records = case sort
|
||||
when 'new'
|
||||
records.sort_by(&:created_at).reverse!
|
||||
when 'old'
|
||||
records.sort_by(&:created_at)
|
||||
when 'atoz'
|
||||
records.sort_by { |c| c.name.downcase }
|
||||
when 'ztoa'
|
||||
records.sort_by { |c| c.name.downcase }.reverse!
|
||||
when 'id_asc'
|
||||
records.sort_by(&:id)
|
||||
when 'id_desc'
|
||||
records.sort_by(&:id).reverse!
|
||||
when 'archived_old'
|
||||
records.sort_by(&:archived_on)
|
||||
when 'archived_new'
|
||||
records.sort_by(&:archived_on).reverse!
|
||||
end
|
||||
folders_recursive_builder(nil, records)
|
||||
end
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
@import "bootstrap-select/sass/bootstrap-select";
|
||||
@import "ag-grid-community/styles/ag-grid.css";
|
||||
@import "ag-grid-community/styles/ag-theme-alpine.css";
|
||||
|
|
45
app/javascript/packs/vue/design_system/select.js
Normal file
45
app/javascript/packs/vue/design_system/select.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import SelectDropdown from '../../../vue/shared/select_dropdown.vue';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import { mountWithTurbolinks } from '../helpers/turbolinks.js';
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
size: 'md',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
simpleOptions() {
|
||||
return [
|
||||
['1', 'One', { icon: 'sn-icon-edit' }],
|
||||
['2', 'Two', { icon: 'sn-icon-drag' }],
|
||||
['3', 'Three', { icon: 'sn-icon-delete' }],
|
||||
['4', 'Four', { icon: 'sn-icon-visibility-show' }],
|
||||
['5', 'Five', { icon: 'sn-icon-edit' }],
|
||||
['6', 'Six', { icon: 'sn-icon-locked-task' }],
|
||||
['7', 'Seven', { icon: 'sn-icon-drag' }],
|
||||
['8', 'Eight', { icon: 'sn-icon-delete' }],
|
||||
['9', 'Nine', { icon: 'sn-icon-edit' }],
|
||||
['10', 'Ten', { icon: 'sn-icon-close' }],
|
||||
];
|
||||
},
|
||||
longOptions() {
|
||||
return [
|
||||
['1', 'Very long long long option and label to test responsivness'],
|
||||
['2', 'Two'],
|
||||
['3', 'Three'],
|
||||
['4', 'Four'],
|
||||
]
|
||||
},
|
||||
renderer() {
|
||||
return (option) => {
|
||||
return `<span class="flex items-center gap-2"><i class="sn-icon ${option[2].icon}"></i> ${option[1]}</span>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
app.component('SelectDropdown', SelectDropdown);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#selects');
|
10
app/javascript/packs/vue/experiments_list.js
Normal file
10
app/javascript/packs/vue/experiments_list.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import ExperimentsList from '../../vue/experiments/list.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('ExperimentsList', ExperimentsList);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#ExperimentsList');
|
10
app/javascript/packs/vue/label_templates_table.js
Normal file
10
app/javascript/packs/vue/label_templates_table.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import LabelTemplatesTable from '../../vue/label_template/table.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('LabelTemplatesTable', LabelTemplatesTable);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#labelTemplatesTable');
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue