mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-11 15:45:34 +08:00
Merge branch 'develop' of https://github.com/biosistemika/scinote-web into features/new_permissions
This commit is contained in:
commit
c0eb38b564
222 changed files with 2799 additions and 852 deletions
3
Gemfile
3
Gemfile
|
@ -16,7 +16,6 @@ gem 'rails', '~> 6.1.1'
|
|||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize', '~> 5.2'
|
||||
gem 'sassc-rails'
|
||||
gem 'simple_token_authentication', '~> 1.16.0' # Token authentication for Devise
|
||||
gem 'webpacker', '~> 4.0.0'
|
||||
gem 'yomu', git: 'https://github.com/biosistemika/yomu', branch: 'master'
|
||||
|
||||
|
@ -98,7 +97,7 @@ gem 'rufus-scheduler', '~> 3.5'
|
|||
|
||||
gem 'discard', '~> 1.0'
|
||||
|
||||
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
|
||||
gem 'graphviz'
|
||||
gem 'tinymce-rails', '~> 4.9.10' # Rich text editor - SEE BELOW
|
||||
# Any time you update tinymce-rails Gem, also update the cache_suffix parameter
|
||||
# in sitewide/tiny_mce.js - to prevent browsers from loading old, cached .js
|
||||
|
|
22
Gemfile.lock
22
Gemfile.lock
|
@ -295,6 +295,8 @@ GEM
|
|||
gherkin (5.1.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
graphviz (1.2.1)
|
||||
process-pipeline
|
||||
hammerjs-rails (2.0.8)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
|
@ -361,7 +363,8 @@ GEM
|
|||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2020.1104)
|
||||
mimemagic (0.3.5)
|
||||
mimemagic (0.3.8)
|
||||
nokogiri (~> 1)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.5.0)
|
||||
|
@ -416,6 +419,12 @@ GEM
|
|||
activerecord (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
polyglot (0.3.5)
|
||||
process-group (1.2.3)
|
||||
process-terminal (~> 0.2.0)
|
||||
process-pipeline (1.0.2)
|
||||
process-group
|
||||
process-terminal (0.2.0)
|
||||
ffi
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
|
@ -487,7 +496,7 @@ GEM
|
|||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rexml (3.2.4)
|
||||
rexml (3.2.5)
|
||||
rgl (0.5.7)
|
||||
lazy_priority_queue (~> 0.1.0)
|
||||
stream (~> 0.5.3)
|
||||
|
@ -529,8 +538,6 @@ GEM
|
|||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.82.0)
|
||||
ruby-graphviz (1.2.5)
|
||||
rexml
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-vips (2.0.17)
|
||||
ffi (~> 1.9)
|
||||
|
@ -561,10 +568,6 @@ GEM
|
|||
shoulda-matchers (4.5.1)
|
||||
activesupport (>= 4.2.0)
|
||||
silencer (1.0.1)
|
||||
simple_token_authentication (1.16.0)
|
||||
actionmailer (>= 3.2.6, < 7)
|
||||
actionpack (>= 3.2.6, < 7)
|
||||
devise (>= 3.2, < 6)
|
||||
simplecov (0.21.2)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
|
@ -660,6 +663,7 @@ DEPENDENCIES
|
|||
faker
|
||||
fastimage
|
||||
figaro
|
||||
graphviz
|
||||
hammerjs-rails
|
||||
httparty (~> 0.17.3)
|
||||
i18n-js (~> 3.6)
|
||||
|
@ -706,7 +710,6 @@ DEPENDENCIES
|
|||
rubocop (= 0.83.0)
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
ruby-graphviz (~> 1.2)
|
||||
rubyzip
|
||||
rufus-scheduler (~> 3.5)
|
||||
sanitize (~> 5.2)
|
||||
|
@ -716,7 +719,6 @@ DEPENDENCIES
|
|||
selenium-webdriver
|
||||
shoulda-matchers
|
||||
silencer
|
||||
simple_token_authentication (~> 1.16.0)
|
||||
simplecov
|
||||
sneaky-save!
|
||||
spinjs-rails
|
||||
|
|
4
Makefile
4
Makefile
|
@ -5,7 +5,7 @@ PAPERCLIP_HASH_SECRET=$(shell openssl rand -base64 128 | tr -d '\n')
|
|||
define PRODUCTION_CONFIG_BODY
|
||||
SECRET_KEY_BASE=$(shell openssl rand -hex 64)
|
||||
PAPERCLIP_HASH_SECRET=$(shell openssl rand -base64 128 | tr -d '\n')
|
||||
DATABASE_URL=postgresql://postgres@db/scinote_production
|
||||
DATABASE_URL=postgresql://postgres:mysecretpassword@db/scinote_production
|
||||
PAPERCLIP_STORAGE=filesystem
|
||||
ENABLE_RECAPTCHA=false
|
||||
ENABLE_USER_CONFIRMATION=false
|
||||
|
@ -85,7 +85,7 @@ tests-ci:
|
|||
@docker-compose run --rm web bash -c "bundle install && yarn install"
|
||||
@docker-compose up -d webpack
|
||||
@docker-compose ps
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAIL_FROM=MAIL_FROM -e MAIL_REPLYTO=MAIL_REPLYTO -e RAILS_ENV=test -e PAPERCLIP_HASH_SECRET=PAPERCLIP_HASH_SECRET -e MAIL_SERVER_URL=localhost:3000 -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create && rake db:migrate && yarn install && bundle exec rspec && bundle exec cucumber"
|
||||
@docker-compose run -e ENABLE_EMAIL_CONFIRMATIONS=false -e MAIL_FROM=MAIL_FROM -e MAIL_REPLYTO=MAIL_REPLYTO -e RAILS_ENV=test -e PAPERCLIP_HASH_SECRET=PAPERCLIP_HASH_SECRET -e MAIL_SERVER_URL=localhost:3000 -e ENABLE_RECAPTCHA=false -e ENABLE_USER_CONFIRMATION=false -e ENABLE_USER_REGISTRATION=true -e CORE_API_RATE_LIMIT=1000000 --rm web bash -c "rake db:create && rake db:migrate && yarn install && bundle exec rspec"
|
||||
|
||||
console:
|
||||
@$(MAKE) rails cmd="rails console"
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.21.1
|
||||
1.21.10
|
||||
|
|
78
app/assets/images/pdf_js/blocked.svg
Normal file
78
app/assets/images/pdf_js/blocked.svg
Normal file
|
@ -0,0 +1,78 @@
|
|||
<svg width="183" height="163" viewBox="0 0 183 163" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<ellipse cx="90" cy="148.5" rx="89.5" ry="14.5" fill="#404048"/>
|
||||
<path d="M164.098 87.8924C164.068 90.2505 163.96 92.8925 163.717 95.676L170.514 87.2064L170.561 87.3389C170.005 85.4274 169.182 83.6038 168.117 81.9219L168.17 82.0465L164.098 87.8924Z" fill="#0ECDC0"/>
|
||||
<path d="M164.103 87.4789L168.048 81.8162C166.914 80.1085 165.555 78.7863 164.004 77.9797C163.923 77.9378 163.842 77.8987 163.761 77.8599C163.822 78.4982 164.151 82.2472 164.103 87.4789Z" fill="#0ECDC0"/>
|
||||
<path d="M171.364 103.917C171.531 102.93 171.661 101.944 171.753 100.961L161.504 109.006L161.49 108.935C161.336 109.516 161.175 110.086 161.007 110.646L171.34 103.761L171.364 103.917Z" fill="#0ECDC0"/>
|
||||
<path d="M162.673 103.589L171.846 95.0926L171.882 95.2396C171.764 92.6125 171.335 90.0085 170.604 87.4822L163.678 96.1131C163.439 98.7209 163.104 101.213 162.673 103.589Z" fill="#0ECDC0"/>
|
||||
<path d="M163.503 95.9663L151.233 83.78L151.287 83.6311C150.291 85.2184 149.459 86.9021 148.803 88.6565L162.766 101.891C163.098 99.8229 163.336 97.8248 163.503 95.9663Z" fill="#0ECDC0"/>
|
||||
<path d="M162.445 103.743C162.539 103.238 162.627 102.736 162.71 102.237L148.695 88.9532L148.747 88.8032C148.515 89.4213 148.298 90.0593 148.094 90.7172L148.145 90.5668L162.445 103.743Z" fill="#0ECDC0"/>
|
||||
<path d="M161.577 108.598L171.74 100.62L171.77 100.772C171.931 98.9902 171.971 97.1995 171.89 95.4121L162.593 104.024C162.296 105.606 161.958 107.131 161.577 108.598Z" fill="#0ECDC0"/>
|
||||
<path d="M147.12 103.202L158.613 116.517C159.392 114.77 160.067 112.978 160.632 111.151L146.801 98.2183C146.771 99.9368 146.856 101.655 147.056 103.363L147.12 103.202Z" fill="#0ECDC0"/>
|
||||
<path d="M163.565 77.8593L163.658 77.811C161.246 76.6967 158.72 76.9774 156.365 78.3846L163.914 87.4801C163.964 81.8198 163.571 77.9153 163.565 77.8593Z" fill="#0ECDC0"/>
|
||||
<path d="M160.715 111.599C160.195 113.244 159.585 114.859 158.889 116.438L169.316 110.258L169.336 110.412C170.066 108.998 170.615 107.497 170.968 105.945C171.104 105.335 171.225 104.724 171.331 104.112L160.875 111.079C160.822 111.253 160.769 111.427 160.715 111.599Z" fill="#0ECDC0"/>
|
||||
<path d="M163.909 87.9018L156.188 78.5976L156.247 78.4553C154.48 79.5422 152.813 81.2649 151.366 83.5083L163.536 95.5954C163.774 92.8433 163.88 90.2325 163.909 87.9018Z" fill="#0ECDC0"/>
|
||||
<path d="M160.731 110.831C161.402 108.612 161.953 106.358 162.382 104.08L148.046 90.8716C147.775 91.7641 147.532 92.6902 147.317 93.6498C147.022 95.0251 146.852 96.4244 146.811 97.8304L160.721 110.837L160.731 110.831Z" fill="#0ECDC0"/>
|
||||
<path d="M175.497 125.121C173.969 126.918 172.2 128.884 170.237 130.874L180.876 128.683L180.829 128.815C181.62 126.989 182.15 125.059 182.403 123.085L182.365 123.215L175.497 125.121Z" fill="#0ECDC0"/>
|
||||
<path d="M181.751 117.427C181.716 117.343 181.678 117.261 181.641 117.18C181.28 117.71 179.141 120.807 175.765 124.806L182.417 122.959C182.634 120.921 182.431 119.037 181.751 117.427Z" fill="#0ECDC0"/>
|
||||
<path d="M160.059 139.666C159.57 140.014 159.082 140.351 158.595 140.676L170.948 141.959L170.866 142.095C171.625 141.441 172.354 140.765 173.052 140.066L160.024 139.729L160.059 139.666Z" fill="#0ECDC0"/>
|
||||
<path d="M164.382 136.302L176.87 135.606L176.803 135.742C178.389 133.643 179.721 131.364 180.77 128.952L169.928 131.185C168.079 133.041 166.231 134.747 164.382 136.302Z" fill="#0ECDC0"/>
|
||||
<path d="M160.341 139.461L173.261 139.796L173.186 139.932C174.447 138.662 175.621 137.308 176.699 135.88L164.043 136.586C162.805 137.616 161.571 138.575 160.341 139.461Z" fill="#0ECDC0"/>
|
||||
<path d="M144.963 146.731C145.049 146.633 145.136 146.534 145.226 146.43C147.824 145.86 150.345 144.978 152.732 143.805L152.546 134.543C152.636 134.301 152.723 134.058 152.807 133.812L153.005 143.671C154.72 142.823 156.383 141.873 157.985 140.826L155.794 123.679C155.883 123.626 155.973 123.572 156.066 123.516L158.254 140.64L158.265 140.642C160.198 139.361 162.061 137.976 163.845 136.495L161.564 119.631C161.652 119.558 161.74 119.485 161.829 119.411L164.109 136.276C164.504 135.947 164.892 135.616 165.274 135.284L163.209 118.198C163.296 118.118 163.384 118.036 163.471 117.955L165.538 135.054C167.114 133.673 168.572 132.285 169.887 130.961L168.214 113.754L168.35 113.673C167.879 113.825 167.402 113.998 166.921 114.188C167.782 113.081 168.546 111.903 169.205 110.665L158.678 116.904C158.012 118.368 157.25 119.787 156.397 121.151C156.21 121.349 156.024 121.55 155.841 121.756L155.868 121.965C155.786 122.087 155.704 122.207 155.621 122.327L155.586 122.048C155.481 122.169 155.377 122.292 155.274 122.415C156.519 120.646 157.595 118.764 158.488 116.794L147.079 103.576C147.223 104.804 147.414 106.037 147.64 107.259C146.563 105.72 145.284 104.332 143.836 103.135L143.926 103.237L141.861 110.054C142.561 112.306 143.276 114.852 143.906 117.574L147.697 107.56C147.726 107.712 147.755 107.863 147.785 108.014L144.004 118.002C144.583 120.556 145.035 123.03 145.36 125.423L150.102 116.933C150.138 117.049 150.175 117.164 150.212 117.279L145.418 125.86C145.626 127.457 145.775 129.012 145.867 130.525L151.756 121.748C151.796 121.857 151.836 121.963 151.875 122.067L145.923 130.936L145.888 130.873C145.921 131.472 145.945 132.064 145.959 132.649L152.682 124.14C152.727 124.25 152.769 124.352 152.807 124.445L145.967 133.102C145.97 133.283 145.973 133.465 145.975 133.645C145.989 135.37 145.909 137.095 145.735 138.811L146.388 138.069C146.273 138.349 146.16 138.625 146.051 138.897L145.679 139.319C145.65 139.575 145.617 139.828 145.583 140.08C145.457 140.407 145.335 140.725 145.218 141.036C145.313 140.452 145.395 139.865 145.464 139.274L130.526 130.227C134.829 139.594 142.414 148.301 142.9 148.854C143.226 148.15 143.52 147.43 143.783 146.694C143.872 146.68 143.961 146.665 144.05 146.651C144.019 146.739 143.989 146.828 143.958 146.916C143.911 146.923 143.864 146.932 143.818 146.939C143.856 146.948 143.897 146.956 143.939 146.966C143.752 147.481 143.553 147.985 143.341 148.477C143.64 148.175 144.087 147.709 144.63 147.106C148.061 147.773 157.266 149.088 164.898 145.916L152.809 144.011C150.317 145.24 147.682 146.154 144.963 146.731ZM152.477 131.148L152.747 130.842L152.756 131.277L152.486 131.583L152.477 131.148Z" fill="#0ECDC0"/>
|
||||
<path d="M181.49 117.054L181.593 117.077C180.446 114.68 178.321 113.287 175.609 112.869L175.619 124.686C179.27 120.359 181.459 117.101 181.49 117.054Z" fill="#0ECDC0"/>
|
||||
<path d="M157.763 141.223C156.313 142.158 154.813 143.014 153.269 143.786L165.243 145.673L165.16 145.804C166.626 145.181 168.006 144.375 169.268 143.404C169.762 143.021 170.246 142.627 170.718 142.224L158.218 140.925C158.066 141.025 157.915 141.126 157.763 141.223Z" fill="#0ECDC0"/>
|
||||
<path d="M170.149 130.696C172.088 128.728 173.836 126.784 175.346 125.008L175.337 112.92L175.473 112.848C173.419 112.559 171.035 112.823 168.489 113.629L170.149 130.696Z" fill="#0ECDC0"/>
|
||||
<path d="M143.738 103.056C142.131 101.782 140.43 100.944 138.705 100.656C138.616 100.641 138.527 100.629 138.438 100.617C138.692 101.206 140.165 104.67 141.737 109.66L143.738 103.056Z" fill="#0ECDC0"/>
|
||||
<path d="M143.793 117.917L128.355 110.117L128.359 109.959C127.904 111.776 127.633 113.635 127.551 115.506L144.924 123.779C144.6 121.709 144.208 119.736 143.793 117.917Z" fill="#0ECDC0"/>
|
||||
<path d="M144.977 124.125L127.541 115.821L127.543 115.663C127.514 116.322 127.505 116.996 127.515 117.685L127.516 117.526L145.192 125.639C145.125 125.13 145.053 124.625 144.977 124.125Z" fill="#0ECDC0"/>
|
||||
<path d="M145.758 133.245L128.604 125.218C129.107 126.862 129.72 128.47 130.438 130.032L130.45 129.859L145.497 138.972C145.699 137.07 145.786 135.158 145.758 133.245Z" fill="#0ECDC0"/>
|
||||
<path d="M138.251 100.677L138.324 100.603C135.685 100.288 133.37 101.335 131.566 103.401L141.558 109.719C139.855 104.321 138.273 100.729 138.251 100.677Z" fill="#0ECDC0"/>
|
||||
<path d="M141.684 110.122L131.463 103.658L131.475 103.505C130.131 105.084 129.078 107.238 128.396 109.818L143.708 117.554C143.083 114.863 142.376 112.347 141.684 110.122Z" fill="#0ECDC0"/>
|
||||
<path d="M145.753 132.91C145.705 130.592 145.533 128.279 145.236 125.98L127.517 117.846C127.535 118.779 127.59 119.735 127.683 120.714C127.827 122.113 128.099 123.496 128.494 124.846L145.746 132.919L145.753 132.91Z" fill="#0ECDC0"/>
|
||||
<path d="M131.314 91.8542L79.8523 85.371C77.5199 65.9803 81.0437 47.0499 87.06 28.2241L138.522 34.7073C130.756 53.1762 128.477 72.2385 131.314 91.8542Z" fill="white"/>
|
||||
<path d="M125.699 39.0293L94.2505 35.0674L93.8246 38.4442L125.273 42.4062L125.699 39.0293Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M129.605 47.8338L86.9805 42.4639L86.7675 44.1523L129.392 49.5222L129.605 47.8338Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M128.917 53.2886L86.2925 47.9187L86.0795 49.6071L128.704 54.977L128.917 53.2886Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M128.229 58.7437L85.6045 53.3738L85.3915 55.0622L128.017 60.4321L128.229 58.7437Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M127.541 64.1985L84.9165 58.8286L84.7035 60.517L127.329 65.887L127.541 64.1985Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M126.853 69.6536L84.2285 64.2837L84.0156 65.9721L126.641 71.342L126.853 69.6536Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M126.166 75.1084L83.5405 69.7385L83.3276 71.427L125.953 76.7969L126.166 75.1084Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M125.478 80.5633L82.8525 75.1934L82.6396 76.8818L125.265 82.2517L125.478 80.5633Z" fill="#104DA9"/>
|
||||
<path d="M106.945 72.7508L58.7988 53.4644C61.4517 34.1149 69.6537 16.6916 80.2406 0L128.386 19.2864C116.198 35.1905 109.166 53.056 106.945 72.7508Z" fill="#F0F0F6"/>
|
||||
<path d="M114.886 20.2251L85.4639 8.43896L84.1969 11.5982L113.619 23.3844L114.886 20.2251Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M116.436 29.7307L76.5581 13.7561L75.9246 15.3357L115.803 31.3104L116.436 29.7307Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M114.389 34.8343L74.5112 18.8596L73.8777 20.4392L113.756 36.4139L114.389 34.8343Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M112.343 39.9378L72.4648 23.9631L71.8313 25.5428L111.71 41.5174L112.343 39.9378Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M110.296 45.0413L70.418 29.0667L69.7845 30.6463L109.663 46.6209L110.296 45.0413Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M108.249 50.1445L68.3711 34.1699L67.7376 35.7495L107.616 51.7242L108.249 50.1445Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M106.203 55.2478L66.3247 39.2732L65.6912 40.8528L105.569 56.8275L106.203 55.2478Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M104.157 60.3514L64.2783 44.3767L63.6448 45.9563L103.523 61.931L104.157 60.3514Z" fill="#104DA9"/>
|
||||
<path d="M95.0379 77.396H43.1688C38.4282 58.4491 39.5555 39.2268 43.1688 19.7969H95.0379C89.6445 39.0914 89.7684 58.2889 95.0379 77.396Z" fill="white"/>
|
||||
<path d="M82.8565 25.6875H51.1587V29.0911H82.8565V25.6875Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 33.9346H44.8716V35.6364H87.8338V33.9346Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 39.4326H44.8716V41.1344H87.8338V39.4326Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 44.9309H44.8716V46.6327H87.8338V44.9309Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 50.429H44.8716V52.1308H87.8338V50.429Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 55.927H44.8716V57.6288H87.8338V55.927Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 61.4253H44.8716V63.1271H87.8338V61.4253Z" fill="#104DA9"/>
|
||||
<path opacity="0.3" d="M87.8338 66.9231H44.8716V68.6249H87.8338V66.9231Z" fill="#104DA9"/>
|
||||
<path d="M60.0757 65.897L42.2062 52.0127C42.0274 51.7019 41.7698 51.4438 41.4593 51.2643C41.1488 51.0848 40.7965 50.9902 40.4378 50.9902H30.7933C30.2523 50.9902 29.7334 51.205 29.3509 51.5873C28.9683 51.9697 28.7534 52.4882 28.7534 53.0289V138.104C28.7534 139.858 29.4505 141.54 30.6913 142.78C31.9322 144.02 33.6151 144.717 35.3699 144.717H134.375C135.244 144.717 136.104 144.546 136.907 144.213C137.71 143.881 138.439 143.394 139.054 142.78C139.668 142.166 140.155 141.437 140.488 140.634C140.82 139.832 140.992 138.972 140.992 138.104V71.9074C140.992 70.3134 140.358 68.7846 139.23 67.6574C138.102 66.5303 136.573 65.897 134.978 65.897H60.0757Z" fill="#7094CB"/>
|
||||
<path d="M131.472 160.158C121.96 160.793 114.105 159.146 113.927 156.479C113.748 153.812 121.315 151.135 130.827 150.5C140.338 149.865 148.194 151.512 148.372 154.179C148.55 156.846 140.984 159.523 131.472 160.158Z" fill="#231F20"/>
|
||||
<path d="M138.824 150.472C138.824 150.472 141.18 155.094 139.609 155.269C138.039 155.443 137.428 155.443 136.73 154.745C136.348 154.364 135.575 153.852 134.953 153.466C134.584 153.242 134.286 152.918 134.093 152.531C133.9 152.145 133.82 151.712 133.862 151.283C133.908 150.856 134.068 150.504 134.461 150.472C135.508 150.384 136.73 149.425 136.73 149.425L138.824 150.472Z" fill="white"/>
|
||||
<path d="M125.821 153.873C125.821 153.873 128.177 158.496 126.607 158.67C125.036 158.844 124.425 158.844 123.727 158.147C123.345 157.765 122.572 157.253 121.95 156.868C121.581 156.643 121.283 156.319 121.09 155.933C120.897 155.546 120.817 155.114 120.859 154.684C120.905 154.257 121.065 153.906 121.458 153.873C122.505 153.786 123.727 152.826 123.727 152.826L125.821 153.873Z" fill="white"/>
|
||||
<path d="M137.714 106.01C137.714 106.01 140.716 114.941 139.382 118.114C138.048 121.287 135.935 124.343 135.935 124.343C135.935 124.343 136.714 106.598 137.714 106.01Z" fill="#F0F0F6"/>
|
||||
<path d="M136.468 124.916C136.468 124.916 136.642 126.574 136.729 126.661C136.817 126.748 136.642 126.922 136.729 127.184C136.817 127.446 136.904 127.795 136.729 127.882C136.555 127.969 137.689 135.644 137.689 135.644C137.689 135.644 140.482 139.307 139.348 145.064L138.998 150.907C138.998 150.907 136.293 151.082 136.293 150.122C136.293 150.122 136.468 148.989 136.468 148.465C136.468 147.942 136.031 147.942 136.293 147.68C136.555 147.419 136.555 147.244 136.555 147.244C136.555 147.244 136.119 146.895 136.206 146.808C136.293 146.721 135.42 140.528 135.42 140.528C135.42 140.528 134.46 139.569 134.46 139.046V138.522C134.46 138.522 134.024 137.389 134.024 137.301C134.024 137.214 131.668 131.894 131.668 131.894L130.708 135.731L129.661 141.226C129.661 141.226 129.137 146.198 128.09 148.116C128.09 148.116 126.257 154.396 126.257 154.222C126.257 154.047 123.203 153.611 123.29 152.826C123.377 152.041 125.123 141.052 125.123 141.052L124.686 124.742L136.468 124.916Z" fill="#104DA9"/>
|
||||
<path d="M129.884 101.012C128.312 99.7357 128.072 97.4267 129.35 95.855C130.627 94.2832 132.937 94.044 134.51 95.3206C136.083 96.5972 136.322 98.9062 135.045 100.478C133.767 102.05 131.457 102.289 129.884 101.012Z" fill="#F8B68E"/>
|
||||
<path d="M133.952 100.334C133.952 100.334 134.302 105.807 134.492 105.895C134.683 105.984 129.552 105.339 129.552 105.339C129.552 105.339 130.255 100.585 130.241 100.117L133.952 100.334Z" fill="#F8B68E"/>
|
||||
<path d="M126.65 104.812C126.65 104.812 131.886 101.934 132.41 102.021C132.933 102.109 136.433 103.718 138.128 106.373C138.626 108.7 138.626 111.888 138.427 113.682C138.228 115.475 137.032 122.848 136.86 123.652C136.543 125.139 139.426 138.953 139.252 138.953C139.61 140.925 122.48 148.715 122.392 148.453C122.392 147.481 126.65 104.812 126.65 104.812Z" fill="white"/>
|
||||
<path d="M123.726 125.527C123.726 125.527 122.068 130.586 123.465 130.411C124.861 130.237 125.472 126.05 125.472 126.05L123.726 125.527Z" fill="#F8B68E"/>
|
||||
<path opacity="0.1" d="M130.141 118.157L126.127 125.309L129.509 117.773L130.141 118.157Z" fill="black"/>
|
||||
<path d="M130.043 95.0273L130.21 94.8022L129.69 94.5585C129.751 94.4925 129.826 94.4405 129.91 94.4063C129.993 94.372 130.083 94.3562 130.173 94.3601L129.733 93.96C130.567 93.7246 131.439 93.6605 132.298 93.7715C133.157 93.8825 133.985 94.1663 134.731 94.6059C135.846 95.271 136.786 96.3816 136.869 97.6765C136.909 98.3046 136.747 98.9301 136.517 99.516C136.093 100.595 135.318 101.662 134.182 101.897C133.028 102.135 131.904 101.454 130.922 100.802C130.577 100.573 130.217 100.329 130.021 99.9634C130.265 99.5961 130.588 99.288 130.966 99.0616C131.096 99.0017 131.208 98.9106 131.294 98.7964C131.474 98.4928 131.168 98.1312 130.866 97.9474C130.565 97.7635 130.193 97.5545 130.184 97.2019C130.177 96.959 130.354 96.7565 130.49 96.5553C130.862 96.0078 129.961 95.1384 130.043 95.0273Z" fill="#512B1F"/>
|
||||
<path d="M128.151 104.225C128.151 104.225 131.423 104.934 130.892 109.365C130.362 113.796 129.389 118.226 129.389 118.226L126.116 125.404L125.762 126.733L123.374 126.113L125.143 115.834C125.143 115.834 125.762 104.846 126.558 104.491C127.06 104.277 127.606 104.186 128.151 104.225Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="182" height="163" fill="white" transform="translate(0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
|
@ -10,7 +10,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
function appendTasksList(json, container) {
|
||||
$.each(json.data, (i, task) => {
|
||||
var currentTaskItem = task;
|
||||
$(container).append(currentTaskItem);
|
||||
$(container).find('.current-tasks-list').append(currentTaskItem);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
}
|
||||
|
||||
function initInfiniteScroll() {
|
||||
InfiniteScroll.init('.current-tasks-list', {
|
||||
InfiniteScroll.init('.current-tasks-list-wrapper', {
|
||||
url: $('.current-tasks-list').data('tasksListUrl'),
|
||||
customResponse: (json, container) => {
|
||||
appendTasksList(json, container);
|
||||
|
@ -134,9 +134,9 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
$currentTasksList.find('.widget-placeholder').addClass($('.current-tasks-navbar .active').data('mode'));
|
||||
}
|
||||
}
|
||||
appendTasksList(result, $currentTasksList);
|
||||
appendTasksList(result, '.current-tasks-list-wrapper');
|
||||
PerfectSb().update_all();
|
||||
if (newList) InfiniteScroll.resetScroll('.current-tasks-list');
|
||||
if (newList) InfiniteScroll.resetScroll('.current-tasks-list-wrapper');
|
||||
animateSpinner($currentTasksList, false);
|
||||
}).error(function(error) {
|
||||
// If error is 403, it is possible that the user was removed from project/experiment,
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
// - refresh project users tab after manage user modal is closed
|
||||
// - refactor view handling using library, ex. backbone.js
|
||||
|
||||
/* global animateSpinner HelperModule dropdownSelector Sidebar Turbolinks filterDropdown */
|
||||
/* global HelperModule dropdownSelector Sidebar Turbolinks filterDropdown */
|
||||
|
||||
(function() {
|
||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable'];
|
||||
var ProjectsIndex = (function() {
|
||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'deletable'];
|
||||
var projectsWrapper = '#projectsWrapper';
|
||||
var toolbarWrapper = '#toolbarWrapper';
|
||||
var cardsWrapper = '#cardsWrapper';
|
||||
|
@ -97,6 +97,42 @@
|
|||
});
|
||||
}
|
||||
|
||||
// init delete project folders
|
||||
function initDeleteFoldersToolbarButton() {
|
||||
$(projectsWrapper)
|
||||
.on('ajax:before', '.delete-folders-btn', function() {
|
||||
let buttonForm = $(this);
|
||||
buttonForm.find('input[name="project_folders_ids[]"]').remove();
|
||||
selectedProjectFolders.forEach(function(id) {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'project_folders_ids[]',
|
||||
value: id
|
||||
}).appendTo(buttonForm);
|
||||
});
|
||||
})
|
||||
.on('ajax:success', '.delete-folders-btn', function(ev, data) {
|
||||
// Add and show modal
|
||||
let deleteModal = $(data.html);
|
||||
$(projectsWrapper).append(deleteModal);
|
||||
deleteModal.modal('show');
|
||||
// Remove modal when it gets closed
|
||||
deleteModal.on('hidden.bs.modal', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
|
||||
$(projectsWrapper)
|
||||
.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)
|
||||
|
@ -239,11 +275,14 @@
|
|||
|
||||
// Initialize view project users modal remote loading.
|
||||
function initViewProjectUsersLink() {
|
||||
$('#cardsWrapper').on('ajax:success', '.view-project-users-link', function(e, data) {
|
||||
let viewProjectUsersModal = $('#viewProjectUsersModal');
|
||||
viewProjectUsersModal.find('.modal-title').html(data.html_title);
|
||||
viewProjectUsersModal.find('.modal-body').html(data.html_body);
|
||||
$(projectsWrapper).on('ajax:success', '.view-project-users-link', function(e, data) {
|
||||
let viewProjectUsersModal = $(data.html);
|
||||
$(projectsWrapper).append(viewProjectUsersModal);
|
||||
viewProjectUsersModal.modal('show');
|
||||
// Remove modal when it gets closed
|
||||
viewProjectUsersModal.on('hidden.bs.modal', function() {
|
||||
viewProjectUsersModal.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -322,6 +361,8 @@
|
|||
projectsToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
|
||||
if (selectedProjectFolders.length === 1) {
|
||||
projectsToolbar.find('.project-only-action').addClass('hidden');
|
||||
} else {
|
||||
projectsToolbar.find('.folders-only-action').addClass('hidden');
|
||||
}
|
||||
} else {
|
||||
projectsToolbar.find('.single-object-action').addClass('hidden');
|
||||
|
@ -329,6 +370,9 @@
|
|||
if (selectedProjectFolders.length > 0) {
|
||||
projectsToolbar.find('.project-only-action').addClass('hidden');
|
||||
}
|
||||
if (selectedProjects.length > 0) {
|
||||
projectsToolbar.find('.folder-only-action').addClass('hidden');
|
||||
}
|
||||
}
|
||||
PERMISSIONS.forEach((permission) => {
|
||||
if (!checkActionPermission(permission)) {
|
||||
|
@ -495,6 +539,7 @@
|
|||
},
|
||||
success: function(data) {
|
||||
$('#breadcrumbsWrapper').html(data.breadcrumbs_html);
|
||||
$(projectsWrapper).find('.projects-title').html(data.title);
|
||||
$(toolbarWrapper).html(data.toolbar_html);
|
||||
viewContainer.data('projects-cards-url', data.projects_cards_url);
|
||||
viewContainer.removeClass('no-results');
|
||||
|
@ -516,17 +561,16 @@
|
|||
function initProjectsViewModeSwitch() {
|
||||
let projectsPageSelector = '.projects-index';
|
||||
|
||||
// list/cards switch
|
||||
$(projectsPageSelector).on('click', '.cards-switch', function() {
|
||||
let $btn = $(this);
|
||||
$('.cards-switch').removeClass('active');
|
||||
if ($btn.hasClass('view-switch-cards')) {
|
||||
$(cardsWrapper).removeClass('list');
|
||||
} else if ($btn.hasClass('view-switch-list')) {
|
||||
$(cardsWrapper).addClass('list');
|
||||
}
|
||||
$btn.addClass('active');
|
||||
});
|
||||
$(projectsPageSelector)
|
||||
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
|
||||
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
|
||||
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
|
||||
$(ev.target).find('.button-to').addClass('selected');
|
||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||
})
|
||||
.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.
|
||||
|
@ -549,6 +593,14 @@
|
|||
});
|
||||
}
|
||||
|
||||
function selectDate($field) {
|
||||
var datePicker = $field.data('DateTimePicker');
|
||||
if (datePicker && datePicker.date()) {
|
||||
return datePicker.date()._d.toUTCString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initProjectsFilters() {
|
||||
var $filterDropdown = filterDropdown.init();
|
||||
let $projectsFilter = $('.projects-index .projects-filters');
|
||||
|
@ -588,13 +640,17 @@
|
|||
$('#folderSearchInfo').toggle();
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '#folder_search', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$filterDropdown.on('filter:apply', function() {
|
||||
createdOnFromFilter = $createdOnFromFilter.val();
|
||||
createdOnToFilter = $createdOnToFilter.val();
|
||||
createdOnFromFilter = selectDate($createdOnFromFilter);
|
||||
createdOnToFilter = selectDate($createdOnToFilter);
|
||||
membersFilter = dropdownSelector.getValues($('.members-filter'));
|
||||
lookInsideFolders = $foldersCB.prop('checked') ? 'true' : '';
|
||||
archivedOnFromFilter = $archivedOnFromFilter.val();
|
||||
archivedOnToFilter = $archivedOnToFilter.val();
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter);
|
||||
archivedOnToFilter = selectDate($archivedOnToFilter);
|
||||
projectsViewSearch = $textFilter.val();
|
||||
|
||||
appliedFiltersMark();
|
||||
|
@ -636,6 +692,7 @@
|
|||
initManageUsersModal();
|
||||
initExportProjectsModal();
|
||||
initExportProjects();
|
||||
initDeleteFoldersToolbarButton();
|
||||
initArchiveRestoreToolbarButtons();
|
||||
initViewProjectUsersLink();
|
||||
initManageProjectUsersLink();
|
||||
|
@ -688,4 +745,8 @@
|
|||
}
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
loadCardsView: loadCardsView
|
||||
};
|
||||
}());
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* global animateSpinner filterDropdown Sidebar Turbolinks HelperModule */
|
||||
(function() {
|
||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable'];
|
||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'duplicable'];
|
||||
var cardsWrapper = '#cardsWrapper';
|
||||
var experimentsPage = '#projectShowWrapper';
|
||||
|
||||
|
@ -24,15 +24,15 @@
|
|||
|
||||
function updateExperimentsToolbar() {
|
||||
let experimentsToolbar = $('#projectShowToolbar');
|
||||
let toolbarVisible = false;
|
||||
|
||||
if (selectedExperiments.length === 0) {
|
||||
experimentsToolbar.find('.single-object-action, .multiple-object-action').addClass('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedExperiments.length === 1) {
|
||||
experimentsToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
|
||||
} else {
|
||||
} else if (selectedExperiments.length > 1) {
|
||||
experimentsToolbar.find('.single-object-action').addClass('hidden');
|
||||
experimentsToolbar.find('.multiple-object-action').removeClass('hidden');
|
||||
}
|
||||
|
@ -41,9 +41,27 @@
|
|||
experimentsToolbar.find(`.btn[data-for="${permission}"]`).addClass('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
$.each($('#projectShowToolbar').find('.btn'), (i, btn) => {
|
||||
if (window.getComputedStyle(btn).display !== 'none') {
|
||||
toolbarVisible = true;
|
||||
}
|
||||
});
|
||||
$(experimentsPage).attr('data-toolbar-visible', toolbarVisible);
|
||||
}
|
||||
|
||||
function initProjectsViewModeSwitch() {
|
||||
$(experimentsPage)
|
||||
.on('ajax:success', '.change-experiments-view-type-form', function(ev, data) {
|
||||
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
|
||||
$(experimentsPage).find('.cards-switch .button-to').removeClass('selected');
|
||||
$(ev.target).find('.button-to').addClass('selected');
|
||||
$(ev.target).parents('.dropdown.view-switch').removeClass('open');
|
||||
})
|
||||
.on('ajax:error', '.change-projects-view-type-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
|
||||
});
|
||||
|
||||
$(experimentsPage).on('click', '.archive-switch', function() {
|
||||
Turbolinks.visit($(this).data('url'));
|
||||
});
|
||||
|
@ -67,10 +85,15 @@
|
|||
archived_on_to: archivedOnToFilter
|
||||
},
|
||||
success: function(data) {
|
||||
viewContainer.find('.card').remove();
|
||||
viewContainer.find('.card, .no-results-container').remove();
|
||||
viewContainer.removeClass('no-results');
|
||||
viewContainer.append(data.cards_html);
|
||||
if (viewContainer.find('.no-results-container').length) {
|
||||
viewContainer.addClass('no-results');
|
||||
}
|
||||
selectedExperiments.length = 0;
|
||||
updateExperimentsToolbar();
|
||||
loadExperimentWorkflowImages();
|
||||
},
|
||||
error: function() {
|
||||
viewContainer.html('Error loading project list');
|
||||
|
@ -86,6 +109,14 @@
|
|||
});
|
||||
}
|
||||
|
||||
function selectDate($field) {
|
||||
var datePicker = $field.data('DateTimePicker');
|
||||
if (datePicker && datePicker.date()) {
|
||||
return datePicker.date()._d.toUTCString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initExperimentsFilters() {
|
||||
var $filterDropdown = filterDropdown.init();
|
||||
|
||||
|
@ -110,12 +141,12 @@
|
|||
}
|
||||
|
||||
$filterDropdown.on('filter:apply', function() {
|
||||
startedOnFromFilter = $startedOnFromFilter.val();
|
||||
startedOnToFilter = $startedOnToFilter.val();
|
||||
modifiedOnFromFilter = $modifiedOnFromFilter.val();
|
||||
modifiedOnToFilter = $modifiedOnToFilter.val();
|
||||
archivedOnFromFilter = $archivedOnFromFilter.val();
|
||||
archivedOnToFilter = $archivedOnToFilter.val();
|
||||
startedOnFromFilter = selectDate($startedOnFromFilter);
|
||||
startedOnToFilter = selectDate($startedOnToFilter);
|
||||
modifiedOnFromFilter = selectDate($modifiedOnFromFilter);
|
||||
modifiedOnToFilter = selectDate($modifiedOnToFilter);
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter);
|
||||
archivedOnToFilter = selectDate($archivedOnToFilter);
|
||||
experimentsViewSearch = $textFilter.val();
|
||||
appliedFiltersMark();
|
||||
refreshCurrentView();
|
||||
|
@ -165,6 +196,15 @@
|
|||
});
|
||||
}
|
||||
|
||||
function initSelectAllCheckbox() {
|
||||
$(experimentsPage).on('click', '.sci-checkbox.select-all', function() {
|
||||
var selectAll = this.checked;
|
||||
$.each($('.experiment-card-selector'), function() {
|
||||
if (this.checked !== selectAll) this.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initArchiveRestoreToolbarButtons() {
|
||||
$(experimentsPage)
|
||||
.on('ajax:before', '.archive-experiments-form, .restore-experiments-form', function() {
|
||||
|
@ -188,36 +228,73 @@
|
|||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
$('.workflowimg-container').each(function() {
|
||||
let container = $(this);
|
||||
function appendActionModal(modal) {
|
||||
$('#content-wrapper').append(modal);
|
||||
modal.modal('show');
|
||||
modal.find('.selectpicker').selectpicker();
|
||||
// Remove modal when it gets closed
|
||||
modal.on('hidden.bs.modal', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initEditMoveDuplicateToolbarButton() {
|
||||
let forms = '.edit-experiments-form, .move-experiments-form, .clone-experiments-form';
|
||||
$(experimentsPage)
|
||||
.on('ajax:before', forms, function() {
|
||||
let buttonForm = $(this);
|
||||
buttonForm.find('input[name="id"]').remove();
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'id',
|
||||
value: selectedExperiments[0]
|
||||
}).appendTo(buttonForm);
|
||||
})
|
||||
.on('ajax:success', forms, function(ev, data) {
|
||||
appendActionModal($(data.html));
|
||||
})
|
||||
.on('ajax:error', forms, function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function initNewExperimentToolbarButton() {
|
||||
let forms = '.new-experiment-form';
|
||||
$(experimentsPage)
|
||||
.on('ajax:success', forms, function(ev, data) {
|
||||
appendActionModal($(data.html));
|
||||
})
|
||||
.on('ajax:error', forms, function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function loadExperimentWorkflowImages() {
|
||||
$('.experiment-card').each(function() {
|
||||
let card = $(this);
|
||||
let container = $(this).find('.workflowimg-container').first();
|
||||
if (container.data('workflowimg-present') === false) {
|
||||
let imgUrl = container.data('workflowimg-url');
|
||||
container.find('.workflowimg-spinner').removeClass('hidden');
|
||||
card.find('.workflowimg-spinner').removeClass('hidden');
|
||||
$.ajax({
|
||||
url: imgUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
container.html(data.workflowimg);
|
||||
card.find('.workflowimg-container').html(data.workflowimg);
|
||||
},
|
||||
error: function() {
|
||||
container.find('.workflowimg-spinner').addClass('hidden');
|
||||
card.find('.workflowimg-spinner').addClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
$('#content-wrapper').on('ajax:success', '.experiment-action-link', function(ev, data) {
|
||||
// Add and show modal
|
||||
let modal = $(data.html);
|
||||
$('#content-wrapper').append(modal);
|
||||
modal.modal('show');
|
||||
modal.find('.selectpicker').selectpicker();
|
||||
// Remove modal when it gets closed
|
||||
modal.on('hidden.bs.modal', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
appendActionModal($(data.html));
|
||||
});
|
||||
|
||||
$('#content-wrapper')
|
||||
|
@ -225,7 +302,9 @@
|
|||
animateSpinner();
|
||||
})
|
||||
.on('ajax:success', '.experiment-action-form', function() {
|
||||
location.reload();
|
||||
$(this).closest('.modal').modal('hide');
|
||||
refreshCurrentView();
|
||||
animateSpinner(null, false);
|
||||
})
|
||||
.on('ajax:error', '.experiment-action-form', function(ev, data) {
|
||||
animateSpinner(null, false);
|
||||
|
@ -238,6 +317,9 @@
|
|||
initProjectsViewModeSwitch();
|
||||
initExperimentsSelector();
|
||||
initArchiveRestoreToolbarButtons();
|
||||
initEditMoveDuplicateToolbarButton();
|
||||
initNewExperimentToolbarButton();
|
||||
initSelectAllCheckbox();
|
||||
}
|
||||
|
||||
init();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//= require protocols/import_export/import
|
||||
/* global ProtocolRepositoryHeader */
|
||||
/* global ProtocolRepositoryHeader PdfPreview */
|
||||
|
||||
// Global variables
|
||||
var rowsSelected = [];
|
||||
|
@ -223,6 +223,7 @@ function initProtocolPreviewModal() {
|
|||
modal.modal("show");
|
||||
ProtocolRepositoryHeader.init();
|
||||
initHandsOnTable(modalBody);
|
||||
PdfPreview.initCanvas();
|
||||
},
|
||||
error: function (error) {
|
||||
// TODO
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//= require Sortable.min
|
||||
//= require canvas-to-blob.min
|
||||
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
@ -552,6 +551,7 @@
|
|||
$new_step.find('.attachments').trigger('reorder');
|
||||
tinyMCE.editors.step_description_textarea.remove();
|
||||
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
|
||||
PdfPreview.initCanvas();
|
||||
|
||||
//Rerender tables
|
||||
$new_step.find("div.step-result-hot-table").each(function() {
|
||||
|
@ -684,7 +684,8 @@
|
|||
}, function(result) {
|
||||
viewModeBtn.closest('.dropdown-menu').find('.attachments-view-mode').removeClass('selected');
|
||||
viewModeBtn.addClass('selected');
|
||||
viewModeBtn.closest('.step').find('.attachments').html(result.html)
|
||||
viewModeBtn.closest('.step').find('.attachments').html(result.html);
|
||||
PdfPreview.initCanvas();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global windowScrollEvents HelperModule I18n */
|
||||
/* global windowScrollEvents HelperModule I18n PdfPreview */
|
||||
$(document).on('click', '.asset-context-menu .change-preview-type', function(e) {
|
||||
var viewModeBtn = $(this);
|
||||
var viewMode = viewModeBtn.data('preview-type');
|
||||
|
@ -15,6 +15,7 @@ $(document).on('click', '.asset-context-menu .change-preview-type', function(e)
|
|||
viewModeBtn.closest('.dropdown-menu').find('.change-preview-type').removeClass('selected');
|
||||
viewModeBtn.addClass('selected');
|
||||
$(`.asset[data-asset-id=${assetId}]`).replaceWith(data.html);
|
||||
PdfPreview.initCanvas();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -96,10 +97,19 @@ var InlineAttachments = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
function initReloadButtons() {
|
||||
$(document).on('ajax:success', '.asset .reload-asset', function(e, data) {
|
||||
$(this).closest('.asset').replaceWith(data.html);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
windowScrollEvents.InlineAttachments = InlineAttachments.scrollEvent;
|
||||
},
|
||||
initReloadButtons: () => {
|
||||
initReloadButtons();
|
||||
},
|
||||
scrollEvent: () => {
|
||||
checkForAttachmentsState();
|
||||
}
|
||||
|
@ -110,3 +120,4 @@ $(document).on('turbolinks:load', function() {
|
|||
InlineAttachments.init();
|
||||
InlineAttachments.scrollEvent();
|
||||
});
|
||||
InlineAttachments.initReloadButtons();
|
||||
|
|
|
@ -874,6 +874,7 @@ var dropdownSelector = (function() {
|
|||
|
||||
valuesArray.forEach(function(value) {
|
||||
option = $selector.find(`option[value="${value}"]`)[0];
|
||||
option.selected = 'selected';
|
||||
options.push(convertOptionToJson(option));
|
||||
});
|
||||
setData($selector, options);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint no-underscore-dangle: ["error", { "allowAfterThis": true }]*/
|
||||
/* eslint no-use-before-define: ["error", { "functions": false }]*/
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* global PdfPreview */
|
||||
var FilePreviewModal = (function() {
|
||||
'use strict';
|
||||
|
||||
|
@ -18,6 +19,7 @@ var FilePreviewModal = (function() {
|
|||
$('#filePreviewModal .modal-content').html(result.html);
|
||||
$('#filePreviewModal').modal('show');
|
||||
$('.modal-backdrop').last().css('z-index', $('#filePreviewModal').css('z-index') - 1);
|
||||
PdfPreview.initCanvas();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -26,6 +28,7 @@ var FilePreviewModal = (function() {
|
|||
e.stopPropagation();
|
||||
$.get($(this).attr('href'), { gallery: $(this).data('gallery-elements') }, function(result) {
|
||||
$('#filePreviewModal .modal-content').html(result.html);
|
||||
PdfPreview.initCanvas();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ var filterDropdown = (function() {
|
|||
}
|
||||
|
||||
function preventDropdownClose() {
|
||||
$('.dropdown-menu', $filterContainer).click((e) => {
|
||||
$filterContainer.on('click', '.dropdown-menu', function(e) {
|
||||
if (!$(e.target).is('input,a')) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
|
|
@ -47,8 +47,6 @@ var InfiniteScroll = (function() {
|
|||
|
||||
if (config.loadFirstPage) {
|
||||
loadData($container, 1);
|
||||
} else if (scrollNotVisible($container)) {
|
||||
loadData($container, $container.data('next-page'));
|
||||
}
|
||||
|
||||
$container.on('scroll', () => {
|
||||
|
|
207
app/assets/javascripts/sitewide/pdf_preview.js
Normal file
207
app/assets/javascripts/sitewide/pdf_preview.js
Normal file
|
@ -0,0 +1,207 @@
|
|||
/* global pdfjsLib animateSpinner dropdownSelector pdfjsLibUtils PerfectScrollbar */
|
||||
/* eslint-disable no-param-reassign, no-use-before-define */
|
||||
|
||||
var PdfPreview = (function() {
|
||||
const MIN_ZOOM = 0.25;
|
||||
const MAX_ZOOM = 3;
|
||||
const DEFAULT_ZOOM = 1;
|
||||
const ZOOM_STEP = 0.25;
|
||||
const MAX_LOAD_ATTEMPTS = 5;
|
||||
|
||||
var pageRendering = false;
|
||||
|
||||
function initActionButtons() {
|
||||
$(document)
|
||||
// Next page
|
||||
.on('click', '.pdf-viewer .next-page', function() {
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
renderPdfPage($canvas[0], $canvas.data('current-page') + 1);
|
||||
})
|
||||
// Previous field
|
||||
.on('click', '.pdf-viewer .prev-page', function() {
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
renderPdfPage($canvas[0], $canvas.data('current-page') - 1);
|
||||
})
|
||||
// Page change field
|
||||
.on('change', '.pdf-viewer .current-page', function() {
|
||||
var page = parseInt($(this).val(), 10) || 1;
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
var totalPage = $canvas.data('total-page');
|
||||
if (page < 1) page = 1;
|
||||
if (page > totalPage) page = totalPage;
|
||||
renderPdfPage($canvas[0], page);
|
||||
})
|
||||
// Zoom out button
|
||||
.on('click', '.pdf-viewer .zoom-out', function() {
|
||||
var zoomSelector = $(this).closest('.pdf-viewer').find('.zoom-page-selector');
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
if (zoomSelector.val() === 'auto') {
|
||||
dropdownSelector.selectValues(zoomSelector, DEFAULT_ZOOM);
|
||||
} else {
|
||||
dropdownSelector.selectValues(zoomSelector, parseFloat(zoomSelector.val()) - ZOOM_STEP);
|
||||
}
|
||||
renderPdfPage($canvas[0], $canvas.data('current-page'));
|
||||
})
|
||||
// Zoom in button
|
||||
.on('click', '.pdf-viewer .zoom-in', function() {
|
||||
var zoomSelector = $(this).closest('.pdf-viewer').find('.zoom-page-selector');
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
if (zoomSelector.val() === 'auto') {
|
||||
dropdownSelector.selectValues(zoomSelector, DEFAULT_ZOOM);
|
||||
} else {
|
||||
dropdownSelector.selectValues(zoomSelector, parseFloat(zoomSelector.val()) + ZOOM_STEP);
|
||||
}
|
||||
renderPdfPage($canvas[0], $canvas.data('current-page'));
|
||||
})
|
||||
// Zoom dropdown
|
||||
.on('change', '.pdf-viewer .zoom-page-selector', function() {
|
||||
var $canvas = $(this).closest('.pdf-viewer').find('.pdf-canvas');
|
||||
renderPdfPage($canvas[0], $canvas.data('current-page'));
|
||||
})
|
||||
// Load big pdf
|
||||
.on('click', '.pdf-viewer .load-blocked-pdf', function() {
|
||||
var $viewer = $(this).closest('.pdf-viewer');
|
||||
$viewer.removeClass('blocked');
|
||||
$viewer.find('.pdf-canvas').addClass('ready');
|
||||
PdfPreview.initCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
function initZoomDropdown($canvas) {
|
||||
var zoomSelector = $canvas.closest('.pdf-viewer').find('.zoom-page-selector');
|
||||
dropdownSelector.init(zoomSelector, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
disableSearch: true
|
||||
});
|
||||
}
|
||||
|
||||
function refreshPageCounter(canvas) {
|
||||
var $canvas = $(canvas);
|
||||
var currentPage = $canvas.data('current-page');
|
||||
var totalPage = $canvas.data('total-page');
|
||||
var counterContainer = $canvas.closest('.pdf-viewer').find('.page-counter');
|
||||
counterContainer.find('.current-page').val(currentPage);
|
||||
counterContainer.find('.total-page').text(totalPage);
|
||||
$canvas.closest('.pdf-viewer').find('.prev-page')
|
||||
.attr('disabled', currentPage === 1);
|
||||
$canvas.closest('.pdf-viewer').find('.next-page')
|
||||
.attr('disabled', currentPage === totalPage);
|
||||
}
|
||||
|
||||
function refreshZoomButtons(canvas) {
|
||||
var $viewer = $(canvas).closest('.pdf-viewer');
|
||||
var zoomSelector = $viewer.find('.zoom-page-selector');
|
||||
$viewer.find('.zoom-out').attr('disabled', parseFloat(zoomSelector.val()) === MIN_ZOOM);
|
||||
$viewer.find('.zoom-in').attr('disabled', parseFloat(zoomSelector.val()) === MAX_ZOOM);
|
||||
}
|
||||
|
||||
function renderPdfPreview(canvas) {
|
||||
$(canvas).removeClass('ready');
|
||||
initZoomDropdown($(canvas));
|
||||
animateSpinner($(canvas).closest('.pdf-viewer'), true);
|
||||
$(canvas).data(
|
||||
'custom-scrollbar',
|
||||
new PerfectScrollbar($(canvas).closest('.page-container')[0])
|
||||
).data('load-attempts', 0);
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = canvas.dataset.pdfWorkerUrl;
|
||||
loadPdfDocument(canvas);
|
||||
}
|
||||
|
||||
|
||||
function loadPdfDocument(canvas, page = 1) {
|
||||
var loadingPdf = pdfjsLib.getDocument(canvas.dataset.pdfUrl);
|
||||
$(canvas).data('load-attempts', $(canvas).data('load-attempts') + 1);
|
||||
loadingPdf.promise
|
||||
.then(function(pdfDocument) {
|
||||
$(canvas).data('pdfDocument', pdfDocument);
|
||||
return renderPdfPage(canvas, page);
|
||||
})
|
||||
.catch(function(reason) {
|
||||
pageRendering = false;
|
||||
if (reason.status === 202) {
|
||||
setTimeout(function() {
|
||||
loadPdfDocument(canvas, page);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderPdfPage(canvas, page = 1) {
|
||||
var pdfDocument = $(canvas).data('pdfDocument');
|
||||
if (pageRendering) return false;
|
||||
pageRendering = true;
|
||||
pdfDocument.getPage(page).then(function(pdfPage) {
|
||||
var ctx;
|
||||
var renderTask;
|
||||
var userScale = $(canvas).closest('.pdf-viewer').find('.zoom-page-selector').val();
|
||||
var $layersContainer = $(canvas).closest('.pdf-viewer').find('.layers-container');
|
||||
var scale = userScale === 'auto' ? DEFAULT_ZOOM : userScale;
|
||||
var viewport = pdfPage.getViewport({ scale: scale });
|
||||
var previewContainer = $(canvas).closest('.page-container')[0];
|
||||
var $textLayer = $(canvas).closest('.pdf-viewer').find('.textLayer');
|
||||
if (previewContainer.clientHeight < viewport.height && userScale === 'auto') {
|
||||
scale = previewContainer.clientHeight / viewport.height;
|
||||
}
|
||||
viewport = pdfPage.getViewport({ scale: scale });
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
ctx = canvas.getContext('2d');
|
||||
renderTask = pdfPage.render({
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
});
|
||||
|
||||
// Text layer draw
|
||||
$layersContainer.css({
|
||||
height: viewport.height + 'px',
|
||||
width: viewport.width + 'px'
|
||||
});
|
||||
|
||||
pdfPage.getTextContent().then(function(textContent) {
|
||||
var textLayer = new pdfjsLibUtils.TextLayerBuilder({
|
||||
textLayerDiv: $textLayer[0],
|
||||
pageIndex: page - 1,
|
||||
viewport: viewport
|
||||
});
|
||||
textLayer.eventBus = new pdfjsLibUtils.EventBus();
|
||||
textLayer.setTextContent(textContent);
|
||||
textLayer.render();
|
||||
});
|
||||
|
||||
$(canvas)
|
||||
.data('current-page', page)
|
||||
.data('total-page', pdfDocument.numPages);
|
||||
$(canvas).data('custom-scrollbar').update();
|
||||
animateSpinner($(canvas).closest('.pdf-viewer'), false);
|
||||
refreshPageCounter(canvas);
|
||||
refreshZoomButtons(canvas);
|
||||
pageRendering = false;
|
||||
return renderTask.promise;
|
||||
}).catch(function() {
|
||||
pageRendering = false;
|
||||
if ($(canvas).data('load-attempts') <= MAX_LOAD_ATTEMPTS) {
|
||||
setTimeout(function() {
|
||||
loadPdfDocument(canvas, page);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
initCanvas: function() {
|
||||
$.each($('.pdf-canvas.ready'), function(i, canvas) {
|
||||
renderPdfPreview(canvas);
|
||||
});
|
||||
},
|
||||
init: function() {
|
||||
initActionButtons();
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
PdfPreview.init();
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
.dashboard-container .calendar-widget {
|
||||
--calendar-day-size: 32px;
|
||||
min-height: 320px;
|
||||
|
||||
.dashboard-calendar {
|
||||
height: 100%;
|
||||
|
@ -40,9 +39,9 @@
|
|||
flex-basis: calc(100% - 42px);
|
||||
flex-grow: 1;
|
||||
grid-column-gap: 6px;
|
||||
grid-row-gap: 6px;
|
||||
grid-row-gap: 2px;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(auto-fit, minmax(1em, 1fr));
|
||||
justify-items: center;
|
||||
padding: 6px;
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
.filter-container {
|
||||
height: 36px;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
width: 36px;
|
||||
|
||||
.current-tasks-filters {
|
||||
|
|
|
@ -12,19 +12,20 @@
|
|||
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 30px;
|
||||
padding: calc(var(--dashboard-widgets-gap) / 2)
|
||||
calc(var(--dashboard-widgets-gap) / 2)
|
||||
padding: 15px
|
||||
var(--dashboard-widgets-gap)
|
||||
15px
|
||||
var(--dashboard-widgets-gap);
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
padding-bottom: calc(var(--dashboard-widgets-gap) / 2);
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
--widget-header-size: 44px;
|
||||
display: grid;
|
||||
grid-auto-rows: 28em;
|
||||
grid-auto-rows: 26em;
|
||||
grid-column-gap: var(--dashboard-widgets-gap);
|
||||
grid-row-gap: var(--dashboard-widgets-gap);
|
||||
grid-template-columns: repeat(auto-fit, minmax(7em, 1fr));
|
||||
|
@ -101,6 +102,10 @@
|
|||
.dashboard-container {
|
||||
grid-auto-rows: 20em;
|
||||
}
|
||||
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 1080px) {
|
||||
|
@ -110,9 +115,6 @@
|
|||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 15px;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
--widget-header-size: 72px;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
// New experiments page
|
||||
|
||||
.projects-show {
|
||||
|
||||
&.active {
|
||||
[data-view-mode="archived"] {
|
||||
display: none !important;
|
||||
|
@ -48,7 +47,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.edit-experiments-form,
|
||||
.clone-experiments-form,
|
||||
.move-experiments-form,
|
||||
.archive-experiments-form,
|
||||
.new-experiment-form,
|
||||
.restore-experiments-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -68,6 +71,29 @@
|
|||
border-radius: 4px;
|
||||
box-shadow: $flyout-shadow;
|
||||
|
||||
.workflow-img-wrapper {
|
||||
background-color: $color-concrete;
|
||||
border-radius: 4px;
|
||||
height: 76px;
|
||||
width: 76px;
|
||||
|
||||
.archived-icon-plceholder {
|
||||
color: $color-silver-chalice;
|
||||
font-size: 3.5em;
|
||||
line-height: 76px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workflowimg-container {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
border-radius: 4px;
|
||||
max-height: 76px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-name-cell {
|
||||
@include font-h3;
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -90,7 +116,7 @@
|
|||
top: .2em;
|
||||
}
|
||||
|
||||
.dates-and-img-container{
|
||||
.dates-and-img-container {
|
||||
display: flex;
|
||||
height: 6em;
|
||||
width: 100%;
|
||||
|
@ -130,6 +156,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.completed-task-cell {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
.description-text {
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -161,12 +191,60 @@
|
|||
}
|
||||
|
||||
&.list {
|
||||
grid-auto-rows: 1px 5em;
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content;
|
||||
grid-template-rows: 3em;
|
||||
|
||||
.card {
|
||||
&.experiment-card {
|
||||
.card-value {
|
||||
font-weight: normal;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.workflow-img-wrapper {
|
||||
flex-shrink: 0;
|
||||
height: 3.5em;
|
||||
margin: .25em 1em .25em .5em;
|
||||
width: 3.5em;
|
||||
|
||||
.archived-icon-plceholder {
|
||||
font-size: 2em;
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
.workflowimg-container {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-height: 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dates-and-img-container,
|
||||
.dates-container {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
align-items: normal;
|
||||
padding-top: .5em;
|
||||
}
|
||||
|
||||
.experiment-name-cell {
|
||||
@include font-button;
|
||||
color: $brand-primary;
|
||||
display: flex;
|
||||
font-weight: normal;
|
||||
grid-column: 2;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: .25em 0;
|
||||
|
||||
a {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.start-date-cell {
|
||||
|
@ -183,15 +261,119 @@
|
|||
|
||||
.description-cell {
|
||||
grid-column: 6;
|
||||
position: relative;
|
||||
|
||||
.description-text {
|
||||
height: 4.5em;
|
||||
-webkit-line-clamp: 3;
|
||||
|
||||
&::after {
|
||||
bottom: .5em;
|
||||
right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.more-button {
|
||||
bottom: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
grid-column: 7;
|
||||
padding-top: 3px;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.description-text::after {
|
||||
background: linear-gradient(to right, transparent, $color-concrete 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.readonly {
|
||||
.experiment-name-cell {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
[data-view-mode="archived"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.archived {
|
||||
[data-view-mode="active"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.project-show-container {
|
||||
.experiment-actions-menu {
|
||||
.btn-light:hover {
|
||||
background: $color-alto;
|
||||
}
|
||||
}
|
||||
|
||||
.cards-wrapper {
|
||||
.card.experiment-card {
|
||||
.workflow-img-wrapper {
|
||||
background-color: $color-alto;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: $color-silver-chalice;
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
width: 100%;
|
||||
|
||||
.description-text::before {
|
||||
background: $color-alto;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.description-text::after {
|
||||
background: linear-gradient(to right, transparent, $color-concrete 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
.card.experiment-card {
|
||||
.archived-date-cell {
|
||||
grid-column: 5;
|
||||
}
|
||||
.description-cell {
|
||||
.description-text::before {
|
||||
content: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.description-text::after {
|
||||
background: linear-gradient(to right, transparent, $color-alto 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
.projects-show {
|
||||
.experiments-filters {
|
||||
max-height: calc(100vh - var(--navbar-height) - var(--content-header-size));
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
* Scrollbar thumb styles
|
||||
*/
|
||||
.ps__thumb-x {
|
||||
background-color: $color-silver-chalice;
|
||||
background-color: $color-black;
|
||||
border-radius: 4px;
|
||||
transition: background-color .2s linear, height .2s ease-in-out;
|
||||
-webkit-transition: background-color .2s linear, height .2s ease-in-out;
|
||||
|
@ -75,11 +75,12 @@
|
|||
/* there must be 'bottom' for ps__thumb-x */
|
||||
bottom: 2px;
|
||||
/* please don't change 'position' */
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ps__thumb-y {
|
||||
background-color: $color-silver-chalice;
|
||||
background-color: $color-black;
|
||||
border-radius: 3px;
|
||||
transition: background-color .2s linear, width .2s ease-in-out;
|
||||
-webkit-transition: background-color .2s linear, width .2s ease-in-out;
|
||||
|
@ -87,6 +88,7 @@
|
|||
/* there must be 'right' for ps__thumb-y */
|
||||
right: 2px;
|
||||
/* please don't change 'position' */
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
margin-top: 1px;
|
||||
max-width: 500px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
|
|
|
@ -13,14 +13,6 @@
|
|||
font-size: $font-size-base;
|
||||
padding: 0 !important;
|
||||
|
||||
.preview-close {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
@ -32,14 +24,12 @@
|
|||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
color: $color-white;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: $color-black;
|
||||
border: 0;
|
||||
background: $color-white;
|
||||
display: flex;
|
||||
height: 60px;
|
||||
line-height: 40px;
|
||||
|
@ -123,14 +113,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-save-link {
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#new-step-sketch {
|
||||
|
|
|
@ -455,8 +455,23 @@ li.module-hover {
|
|||
|
||||
// New projects page
|
||||
|
||||
.projects-breadcrumbs {
|
||||
padding: .5em 1em;
|
||||
#breadcrumbsWrapper {
|
||||
background: $color-white;
|
||||
height: 1em;
|
||||
margin-left: -2em;
|
||||
padding: 0 2em;
|
||||
width: calc(100% + 4em);
|
||||
|
||||
.projects-breadcrumbs {
|
||||
padding: .75em 0;
|
||||
}
|
||||
|
||||
&.breadcrumbs-in-secondary-navigation {
|
||||
.projects-breadcrumbs {
|
||||
margin-left: 3em;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.projects-index {
|
||||
|
@ -466,6 +481,11 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
|
||||
.delete-folders-form,
|
||||
.delete-folders-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
.projects-filters {
|
||||
.select-block {
|
||||
|
@ -532,8 +552,9 @@ li.module-hover {
|
|||
|
||||
.card {
|
||||
.project-users-link {
|
||||
align-items: center;
|
||||
color: $color-silver-chalice;
|
||||
display: inline;
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
@ -660,21 +681,21 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.global-avatar-container {
|
||||
height: 28px;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
margin-right: .25em;
|
||||
width: 28px;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.more-users {
|
||||
align-items: center;
|
||||
background: $color-volcano;
|
||||
border-radius: 50%;
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
line-height: 2em;
|
||||
margin-right: .25em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
|
@ -812,9 +833,6 @@ li.module-hover {
|
|||
grid-auto-rows: 2.5em;
|
||||
|
||||
.card.project-card {
|
||||
background: $color-concrete;
|
||||
box-shadow: none;
|
||||
|
||||
.data-row {
|
||||
color: $color-silver-chalice;
|
||||
|
||||
|
@ -838,23 +856,6 @@ li.module-hover {
|
|||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content;
|
||||
|
||||
.card {
|
||||
&::after {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
&.project-card {
|
||||
.table-cell {
|
||||
background: $color-concrete;
|
||||
color: $color-volcano;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.table-cell {
|
||||
background: $color-alto;
|
||||
}
|
||||
}
|
||||
|
||||
&.folder-card {
|
||||
.name {
|
||||
grid-column: 6 span;
|
||||
|
|
|
@ -368,6 +368,10 @@ label {
|
|||
.file-name {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.image-icon.report {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
|
@ -464,6 +468,10 @@ label {
|
|||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.image-icon.report {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .report-element-header .file-name {
|
||||
|
|
|
@ -133,12 +133,12 @@
|
|||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: nowrap;
|
||||
height: 5em;
|
||||
height: 6em;
|
||||
left: var(--repository-sidebar-margin);
|
||||
overflow: hidden;
|
||||
padding: 0 2em;
|
||||
padding: 1em 2em 0;
|
||||
position: fixed;
|
||||
top: calc(5em + var(--navbar-height));
|
||||
top: calc(4em + var(--navbar-height));
|
||||
transition: .4s $timing-function-sharp;
|
||||
width: calc(100% - var(--repository-sidebar-margin));
|
||||
z-index: 90;
|
||||
|
@ -519,3 +519,63 @@
|
|||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-sidebar-container {
|
||||
padding: 1em 2.5em 2em 2em;
|
||||
|
||||
.repo-template {
|
||||
border-radius: $border-radius-modal;
|
||||
box-shadow: $modal-shadow;
|
||||
font-weight: bold;
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0;
|
||||
|
||||
li {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.fas-custom {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.instructions {
|
||||
@include font-main;
|
||||
padding: .75em .25em;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-repositories {
|
||||
.content-header {
|
||||
align-items: center;
|
||||
border-bottom: $border-tertiary;
|
||||
display: flex;
|
||||
height: 4em;
|
||||
margin-left: -2em;
|
||||
padding-left: 2em;
|
||||
width: calc(100% + 4em);
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
text-align: center;
|
||||
|
||||
.description {
|
||||
@include font-main;
|
||||
color: $color-silver-chalice;
|
||||
margin: auto;
|
||||
margin-bottom: 2em;
|
||||
max-width: 570px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-inventory-img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-top: 60px;
|
||||
max-height: 290px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,6 +186,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.pdf-viewer {
|
||||
align-items: center;
|
||||
background: $color-silver-chalice;
|
||||
display: flex;
|
||||
height: calc(100% - 4em);
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -214,6 +223,11 @@
|
|||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-office-file {
|
||||
padding: 5em 1em 1em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.list-attachment-container {
|
||||
|
|
42
app/assets/stylesheets/shared/breadcrumbs.scss
Normal file
42
app/assets/stylesheets/shared/breadcrumbs.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
.breadcrumbs-container {
|
||||
@include font-small;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.delimiter {
|
||||
@include font-button;
|
||||
color: $color-silver-chalice;
|
||||
font-weight: bold;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
.breadcrumbs-link {
|
||||
display: inline-block;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs-collapsed-container {
|
||||
color: $brand-primary;
|
||||
position: relative;
|
||||
|
||||
.show-breadcrumbs {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
@include font-button;
|
||||
color: $brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
|
||||
.cards-wrapper {
|
||||
--card-min-width: 200px;
|
||||
--list-columns-number: 5;
|
||||
|
@ -15,8 +19,8 @@
|
|||
|
||||
&.no-results {
|
||||
.no-results-container {
|
||||
grid-row: 8;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 8;
|
||||
}
|
||||
|
||||
.no-results-img {
|
||||
|
@ -62,24 +66,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(.list) {
|
||||
[list-render="true"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
grid-auto-flow: dense;
|
||||
grid-auto-rows: 3em 1px;
|
||||
grid-column-gap: 0;
|
||||
grid-row-gap: 0;
|
||||
grid-template-columns: repeat(var(--list-columns-number), minmax(100px, auto));
|
||||
margin: 40px 0 0 6px;
|
||||
|
||||
[cards-render="true"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-results-container {
|
||||
grid-row: 12;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 12;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: contents;
|
||||
|
||||
// Border element
|
||||
&:after {
|
||||
&::after {
|
||||
background: $color-concrete;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
|
@ -121,8 +134,7 @@
|
|||
padding: 0 .5em;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 13em;
|
||||
width: calc(100% + 1em);
|
||||
top: calc(var(--content-header-size) + var(--navbar-height));
|
||||
z-index: 2;
|
||||
|
||||
&.select-all-checkboxes {
|
||||
|
@ -136,3 +148,31 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-pane.archived {
|
||||
.cards-wrapper {
|
||||
.card {
|
||||
background: $color-concrete;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.list {
|
||||
.card {
|
||||
&::after {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
background: $color-concrete;
|
||||
color: $color-volcano;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.table-cell {
|
||||
background: $color-alto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// scss-lint:disable NestingDepth QualifyingElement
|
||||
|
||||
.content-pane {
|
||||
--content-header-size: 9.5em;
|
||||
background-color: $color-white;
|
||||
margin: 20px 0;
|
||||
padding: 25px 20px;
|
||||
|
@ -25,7 +26,7 @@
|
|||
background: $color-white;
|
||||
border-bottom: $border-tertiary;
|
||||
display: flex;
|
||||
height: 5em;
|
||||
height: 4em;
|
||||
margin-left: -2em;
|
||||
padding: 0 2em;
|
||||
width: calc(100% + 4em);
|
||||
|
@ -35,9 +36,16 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.name-readonly-placeholder {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
|
@ -141,4 +149,12 @@
|
|||
width: calc(100% + 4em);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-toolbar-visible="false"] {
|
||||
--content-header-size: 5em;
|
||||
|
||||
.toolbar-row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
margin-top: 1px;
|
||||
max-width: 240px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
|
@ -277,6 +278,7 @@
|
|||
.ds-simple {
|
||||
.tag-label {
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
|
|
|
@ -145,3 +145,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop.in {
|
||||
opacity: .4;
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
width: 460px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
.footer:not(.center) {
|
||||
.btn:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
96
app/assets/stylesheets/shared/pdf_preview.scss
Normal file
96
app/assets/stylesheets/shared/pdf_preview.scss
Normal file
|
@ -0,0 +1,96 @@
|
|||
.pdf-viewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.layers-container {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-toolbar {
|
||||
align-items: center;
|
||||
background: $color-concrete;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: .5em;
|
||||
width: 100%;
|
||||
|
||||
.page-counter {
|
||||
padding: 0 .25em;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
margin-right: .25em;
|
||||
max-width: 3em;
|
||||
}
|
||||
|
||||
.total-page {
|
||||
margin-left: .25em;
|
||||
}
|
||||
|
||||
.divider {
|
||||
background: $color-alto;
|
||||
height: 2em;
|
||||
margin: 0 1em;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
.zoom-page {
|
||||
margin-right: .5em;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.blocked-screen {
|
||||
align-items: center;
|
||||
color: $color-white;
|
||||
display: none;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
@include font-h1;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include font-main;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.image {
|
||||
background: unset;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
&.blocked {
|
||||
.pdf-toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocked-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,15 +41,40 @@
|
|||
|
||||
.dropdown-menu {
|
||||
.form-dropdown-item {
|
||||
padding: 0 !important;
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
color: $color-black !important;
|
||||
padding-left: .9em;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-dd-hover !important;
|
||||
.project-archive-restore-form {
|
||||
.button-to {
|
||||
&:hover {
|
||||
background-color: $color-dd-hover !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.change-projects-view-type-form,
|
||||
.change-experiments-view-type-form {
|
||||
.button-to {
|
||||
float: unset !important;
|
||||
height: 48px;
|
||||
margin: 0;
|
||||
|
||||
&:active {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -320,11 +320,14 @@ a[data-toggle="tooltip"] {
|
|||
.open > a:focus {
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
&.with-breadcrumbs {
|
||||
padding-top: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-name {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 54px;
|
||||
line-height: 44px;
|
||||
margin: 0;
|
||||
|
@ -632,7 +635,6 @@ ul.double-line > li {
|
|||
}
|
||||
|
||||
#project-show,
|
||||
#project-show-archive,
|
||||
#module-archive,
|
||||
#result-archive {
|
||||
.panel-default {
|
||||
|
|
|
@ -229,7 +229,7 @@
|
|||
"step": {
|
||||
"id": 2,
|
||||
"name": "Inoculation of potatoes",
|
||||
"description": "Collect samples in <strong>2ml tubes</strong> and put them in <strong>liquid nitrogen</strong> or store at <strong>-80°C</strong>.",
|
||||
"description": "50% of samples should be mock inoculated and the other 50% should be inoculated with the PVY NTN virus.",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -313,7 +313,7 @@
|
|||
"step": {
|
||||
"id": 4,
|
||||
"name": "Collection of potatoes",
|
||||
"description": null,
|
||||
"description": "50% of PVY NTN inoculated potatoes and 50% of mock inoculated potatoes collect 1 day post inoculation, while the other half of the samples collect 6 days post inoculation.",
|
||||
"position": 2,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -347,7 +347,7 @@
|
|||
"step": {
|
||||
"id": 3,
|
||||
"name": "Store samples",
|
||||
"description": "50% of all samples are PVYNTN inoculated potatoes and the other 50% are mock-inoculated potatoes. Collect the samples twice: 1 day post-inoculation and 6 days post-inoculation.",
|
||||
"description": "Collect samples in <strong>2ml tubes</strong> and put them in <strong>liquid nitrogen</strong> or store at <strong>-80°C</strong>.",
|
||||
"position": 1,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -2390,7 +2390,7 @@
|
|||
"checklist_items": [
|
||||
{
|
||||
"id": 26,
|
||||
"text": "Check buffer stock & prepare new stock if needed",
|
||||
"text": "Check buffer stock & prepare new stock if needed.",
|
||||
"checked": false,
|
||||
"checklist_id": 6,
|
||||
"created_at": "2020-12-21T15:54:04.966Z",
|
||||
|
@ -2401,7 +2401,7 @@
|
|||
},
|
||||
{
|
||||
"id": 27,
|
||||
"text": "Check stock of reagents & order new stock if needed",
|
||||
"text": "Check stock of reagents & order new stock if needed.",
|
||||
"checked": false,
|
||||
"checklist_id": 6,
|
||||
"created_at": "2020-12-21T15:54:04.967Z",
|
||||
|
@ -2412,7 +2412,7 @@
|
|||
},
|
||||
{
|
||||
"id": 28,
|
||||
"text": "Use gloves at all times",
|
||||
"text": "Use gloves at all times.",
|
||||
"checked": false,
|
||||
"checklist_id": 6,
|
||||
"created_at": "2020-12-21T15:54:04.968Z",
|
||||
|
|
|
@ -595,7 +595,7 @@
|
|||
"step": {
|
||||
"id": 4382,
|
||||
"name": "Prepare antibiotic dilutions",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Prepare antibiotic dilutions following steps bellow.</p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Prepare antibiotic dilutions following steps below.</p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -808,7 +808,7 @@
|
|||
"step": {
|
||||
"id": 4370,
|
||||
"name": "Prepare agar plates",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Prepare agar plates following guidelines bellow.</p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Prepare agar plates following guidelines below.</p></body></html>",
|
||||
"position": 3,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -1401,7 +1401,7 @@
|
|||
"step": {
|
||||
"id": 4362,
|
||||
"name": "Isolate preparation",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow steps bellow for isolate preparation.</p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow steps below for isolate preparation.</p></body></html>",
|
||||
"position": 1,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -700,7 +700,7 @@
|
|||
"step": {
|
||||
"id": 4414,
|
||||
"name": "Preparation",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Guidelines for samples preparation bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Guidelines for samples preparation below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
"step": {
|
||||
"id": 4533,
|
||||
"name": "Examine the plates",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -863,7 +863,7 @@
|
|||
"step": {
|
||||
"id": 5495,
|
||||
"name": "Slide test",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -1020,7 +1020,7 @@
|
|||
"step": {
|
||||
"id": 5492,
|
||||
"name": "Oxidase test",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow: </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below: </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -1604,7 +1604,7 @@
|
|||
"step": {
|
||||
"id": 5475,
|
||||
"name": "Reagents",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n<p>Reagents bellow can be made or purchased.<br><strong><br>Crystal Violet Staining Reagent:<br></strong><span style=\"text-decoration: underline;\"><br>Solution A for crystal violet staining reagent</span></p>\n<ul>\n<li>Crystal violet (certified 90% dye content), 2g </li>\n<li>Ethanol, 95% (vol/vol), 20 mL</li>\n</ul>\n<span style=\"text-decoration: underline;\">Solution B for crystal violet staining reagent<br></span>\n<ul>\n<li>Ammonium oxalate, 0.8 g</li>\n<li>Distilled water, 80 mL</li>\n</ul>\nMix A and B to obtain crystal violet staining reagent. Store for<strong> 24 h</strong> and filter through paper prior to use.<br><br><strong>Gram's Iodine:<br></strong><br>\n<ul>\n<li>Iodine, 1.0 g</li>\n<li>Potassium iodide, 2.0 g</li>\n<li>Distilled water, 300 mL</li>\n</ul>\nGrind the iodine and potassium iodide in a mortar and add water slowly with continuous grinding until the iodine is dissolved. Store in amber bottles.<br><br><strong>Safranin:<br><br></strong><span style=\"text-decoration-line: underline;\">Stock solution:</span><br>\n<ul>\n<li>2.5g Safranin O</li>\n<li>100 ml 95% Ethanol</li>\n</ul>\n<p><span style=\"text-decoration: underline;\">Working Solution:</span></p>\n<ul>\n<li>10 mL Stock Solution </li>\n<li>90 mL Distilled water</li>\n</ul>\n</body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n<p>Reagents below can be made or purchased.<br><strong><br>Crystal Violet Staining Reagent:<br></strong><span style=\"text-decoration: underline;\"><br>Solution A for crystal violet staining reagent</span></p>\n<ul>\n<li>Crystal violet (certified 90% dye content), 2g </li>\n<li>Ethanol, 95% (vol/vol), 20 mL</li>\n</ul>\n<span style=\"text-decoration: underline;\">Solution B for crystal violet staining reagent<br></span>\n<ul>\n<li>Ammonium oxalate, 0.8 g</li>\n<li>Distilled water, 80 mL</li>\n</ul>\nMix A and B to obtain crystal violet staining reagent. Store for<strong> 24 h</strong> and filter through paper prior to use.<br><br><strong>Gram's Iodine:<br></strong><br>\n<ul>\n<li>Iodine, 1.0 g</li>\n<li>Potassium iodide, 2.0 g</li>\n<li>Distilled water, 300 mL</li>\n</ul>\nGrind the iodine and potassium iodide in a mortar and add water slowly with continuous grinding until the iodine is dissolved. Store in amber bottles.<br><br><strong>Safranin:<br><br></strong><span style=\"text-decoration-line: underline;\">Stock solution:</span><br>\n<ul>\n<li>2.5g Safranin O</li>\n<li>100 ml 95% Ethanol</li>\n</ul>\n<p><span style=\"text-decoration: underline;\">Working Solution:</span></p>\n<ul>\n<li>10 mL Stock Solution </li>\n<li>90 mL Distilled water</li>\n</ul>\n</body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -1210,7 +1210,7 @@
|
|||
"step": {
|
||||
"id": 5154,
|
||||
"name": "Elution and collection of samples",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow elution and collection guidelines bellow.</p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow elution and collection guidelines below.</p></body></html>",
|
||||
"position": 1,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -36,11 +36,6 @@ module Api
|
|||
def health
|
||||
User.new && Team.new && Project.new
|
||||
User.first if params[:db]
|
||||
if Rails.application.secrets.system_notifications_uri.present? &&
|
||||
Rails.application.secrets.system_notifications_channel.present? &&
|
||||
!Notifications::SyncSystemNotificationsService.available?
|
||||
return render plain: 'SYSTEM NOTIFICATIONS SERVICE CHECK FAILED', status: :error
|
||||
end
|
||||
render plain: 'RUNNING'
|
||||
end
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ module Api
|
|||
.preload(repository_cells: @inventory.cell_preload_includes)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
.order(:id)
|
||||
render jsonapi: items, each_serializer: InventoryItemSerializer, include: include_params
|
||||
end
|
||||
|
||||
|
|
|
@ -32,13 +32,16 @@ module Api
|
|||
|
||||
step = @protocol.steps.create!(step_params.merge!(completed: false,
|
||||
user: current_user,
|
||||
position: @protocol.number_of_steps))
|
||||
position: @protocol.number_of_steps,
|
||||
last_modified_by_id: current_user.id))
|
||||
|
||||
render jsonapi: step, serializer: StepSerializer, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
@step.assign_attributes(step_params)
|
||||
@step.assign_attributes(
|
||||
step_params.merge!(last_modified_by_id: current_user.id)
|
||||
)
|
||||
|
||||
if @step.changed? && @step.save!
|
||||
if @step.saved_change_to_attribute?(:completed)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
acts_as_token_authentication_handler_for User, unless: -> { current_user.present? }
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :null_session instead.
|
||||
protect_from_forgery with: :exception, prepend: true
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
class AssetsController < ApplicationController
|
||||
include WopiUtil
|
||||
include AssetsActions
|
||||
# include ActionView::Helpers
|
||||
include ActiveStorage::SetCurrent
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Context
|
||||
include ActiveStorageFileUtil
|
||||
include ApplicationHelper
|
||||
include InputSanitizeHelper
|
||||
include FileIconsHelper
|
||||
|
@ -52,6 +52,19 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def load_asset
|
||||
gallery_view_id = if @assoc.is_a?(Step)
|
||||
@assoc.id
|
||||
elsif @assoc.is_a?(Result)
|
||||
@assoc.my_module.id
|
||||
end
|
||||
render json: { html: render_to_string(partial: 'assets/asset.html.erb',
|
||||
locals: {
|
||||
asset: @asset,
|
||||
gallery_view_id: gallery_view_id
|
||||
}) }
|
||||
end
|
||||
|
||||
def file_url
|
||||
return render_404 unless @asset.file.attached?
|
||||
|
||||
|
@ -86,6 +99,13 @@ class AssetsController < ApplicationController
|
|||
render layout: false
|
||||
end
|
||||
|
||||
def pdf_preview
|
||||
return render plain: '', status: :not_acceptable unless previewable_document?(@asset.blob)
|
||||
return render plain: '', status: :accepted unless @asset.pdf_preview_ready?
|
||||
|
||||
redirect_to @asset.file_pdf_preview.service_url
|
||||
end
|
||||
|
||||
def create_start_edit_image_activity
|
||||
create_edit_image_activity(@asset, current_user, :start_editing)
|
||||
end
|
||||
|
|
|
@ -11,7 +11,10 @@ module ActiveStorage
|
|||
private
|
||||
|
||||
def check_read_permissions
|
||||
case @blob.attachments.first.record_type
|
||||
attachment = @blob.attachments.take
|
||||
return render_404 if attachment.blank?
|
||||
|
||||
case attachment.record_type
|
||||
when 'Asset'
|
||||
check_asset_read_permissions
|
||||
when 'TinyMceAsset'
|
||||
|
|
|
@ -62,20 +62,19 @@ module StepsActions
|
|||
new_checklists,
|
||||
old_checklists)
|
||||
step_description_annotation(step, old_description)
|
||||
new_checklists.each do |e|
|
||||
new_checklists.each do |new_checklist|
|
||||
# generates smart annotaion if the checklist is new
|
||||
add_new_checklist(step, e) if e.id.zero?
|
||||
checklist_name_annotation(step, e) unless e.id
|
||||
add_new_checklist(step, new_checklist) and next if new_checklist.id.zero?
|
||||
# else check if checklist is not deleted and generates
|
||||
# new notifications
|
||||
next unless old_checklists.map(&:id).include?(e.id)
|
||||
old_checklist = old_checklists.select { |i| i.id == e.id }.first
|
||||
checklist_name_annotation(step, e, old_checklist.name)
|
||||
e.items.each do |ci|
|
||||
old_list = old_checklists.select { |i| i.id == e.id }.first
|
||||
old_item = old_list.items.select { |i| i.id == ci.id }.first if old_list
|
||||
text = old_item ? old_item.text : ''
|
||||
checklist_item_annotation(step, ci, text)
|
||||
next unless old_checklists.map(&:id).include?(new_checklist.id)
|
||||
|
||||
old_checklist = old_checklists.find { |i| i.id == new_checklist.id }
|
||||
checklist_name_annotation(step, new_checklist, old_checklist.name)
|
||||
new_checklist.items.each do |new_checklist_item|
|
||||
old_checklist_item = old_checklist.items.find { |i| i.id == new_checklist_item.id } if old_checklist
|
||||
text = old_checklist_item ? old_checklist_item.text : ''
|
||||
checklist_item_annotation(step, new_checklist_item, text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module Dashboard
|
|||
tasks = tasks.left_outer_joins(:user_my_modules).where(user_my_modules: { user_id: current_user.id })
|
||||
end
|
||||
|
||||
tasks = tasks.where(my_module_status_id: task_filters[:statuses])
|
||||
tasks = tasks.where(my_module_status_id: task_filters[:statuses]) if task_filters[:statuses].present?
|
||||
|
||||
case task_filters[:sort]
|
||||
when 'start_date'
|
||||
|
|
|
@ -65,7 +65,7 @@ class ExperimentsController < ApplicationController
|
|||
def canvas
|
||||
redirect_to module_archive_experiment_path(@experiment) if @experiment.archived_branch?
|
||||
@project = @experiment.project
|
||||
@active_modules = @experiment.my_modules.active.includes(:tags, :inputs, :outputs)
|
||||
@active_modules = @experiment.my_modules.active.order(:name).includes(:tags, :inputs, :outputs)
|
||||
current_team_switch(@project.team)
|
||||
end
|
||||
|
||||
|
|
|
@ -30,15 +30,15 @@ class ExternalProtocolsController < ApplicationController
|
|||
.split('/').map(&:to_sym))
|
||||
api_client = "ProtocolImporters::#{endpoint_name}::ApiClient".constantize.new
|
||||
|
||||
html_preview = api_client.protocol_html_preview(show_params[:protocol_id])
|
||||
base_uri = URI.parse(html_preview.request.last_uri.to_s)
|
||||
html_preview_request = api_client.protocol_html_preview(show_params[:protocol_id])
|
||||
base_uri = URI.parse(html_preview_request.request.last_uri.to_s)
|
||||
base_uri = "#{base_uri.scheme}://#{base_uri.host}"
|
||||
|
||||
render json: {
|
||||
protocol_source: show_params[:protocol_source],
|
||||
protocol_id: show_params[:protocol_id],
|
||||
base_uri: base_uri,
|
||||
html: html_preview
|
||||
html: html_preview_request.body
|
||||
}
|
||||
rescue StandardError => e
|
||||
render json: {
|
||||
|
|
|
@ -184,11 +184,13 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
|
||||
def update_description
|
||||
old_description = @my_module.description
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @my_module.update(description: params.require(:my_module)[:description])
|
||||
log_activity(:change_module_description)
|
||||
TinyMceAsset.update_images(@my_module, params[:tiny_mce_images], current_user)
|
||||
my_module_annotation_notification(old_description)
|
||||
render json: {
|
||||
html: custom_auto_link(
|
||||
@my_module.tinymce_render(:description),
|
||||
|
@ -206,12 +208,15 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def update_protocol_description
|
||||
protocol = @my_module.protocol
|
||||
old_description = protocol.description
|
||||
return render_404 unless protocol
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if protocol.update(description: params.require(:protocol)[:description])
|
||||
log_activity(:protocol_description_in_task_edited)
|
||||
TinyMceAsset.update_images(protocol, params[:tiny_mce_images], current_user)
|
||||
protocol_annotation_notification(old_description)
|
||||
render json: {
|
||||
html: custom_auto_link(
|
||||
protocol.tinymce_render(:description),
|
||||
|
@ -402,4 +407,32 @@ class MyModulesController < ApplicationController
|
|||
:page, :starting_timestamp, :from_date, :to_date, types: [], users: [], subjects: {}
|
||||
)
|
||||
end
|
||||
|
||||
def my_module_annotation_notification(old_text = nil)
|
||||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
new_text: @my_module.description,
|
||||
title: t('notifications.my_module_description_annotation_title',
|
||||
my_module: @my_module.name,
|
||||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_description_annotation_message_html',
|
||||
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
|
||||
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
|
||||
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
|
||||
)
|
||||
end
|
||||
|
||||
def protocol_annotation_notification(old_text = nil)
|
||||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
new_text: @my_module.protocol.description,
|
||||
title: t('notifications.my_module_protocol_annotation_title',
|
||||
my_module: @my_module.name,
|
||||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_protocol_annotation_message_html',
|
||||
project: link_to(@my_module.experiment.project.name, project_url(@my_module.experiment.project)),
|
||||
experiment: link_to(@my_module.experiment.name, canvas_experiment_url(@my_module.experiment)),
|
||||
my_module: link_to(@my_module.name, protocols_my_module_url(@my_module)))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -96,6 +96,35 @@ class ProjectFoldersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def destroy_modal
|
||||
render json: {
|
||||
html: render_to_string(partial: 'projects/index/modals/project_folder_delete.html.erb',
|
||||
locals: { project_folders_ids: params[:project_folders_ids] })
|
||||
}
|
||||
end
|
||||
|
||||
def destroy
|
||||
project_folders = current_team.project_folders.where(id: params[:project_folders_ids])
|
||||
counter = 0
|
||||
project_folders.each do |folder|
|
||||
next if folder.projects.exists? || folder.project_folders.exists? || !can_update_team?(current_team)
|
||||
|
||||
folder.transaction do
|
||||
log_activity(:delete_project_folder, folder, project_folder: folder.id)
|
||||
folder.destroy!
|
||||
counter += 1
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_project_folder
|
||||
|
|
|
@ -5,20 +5,21 @@ class ProjectsController < ApplicationController
|
|||
include TeamsHelper
|
||||
include InputSanitizeHelper
|
||||
include ProjectsHelper
|
||||
include CardsViewHelper
|
||||
include ExperimentsHelper
|
||||
|
||||
attr_reader :current_folder
|
||||
helper_method :current_folder
|
||||
|
||||
before_action :switch_team_with_param, only: :index
|
||||
before_action :load_vars, only: %i(show edit update notifications experiment_archive sidebar experiments_cards)
|
||||
before_action :load_current_folder, only: %i(index cards new show experiment_archive)
|
||||
before_action :check_view_permissions, only: %i(show notifications experiment_archive sidebar experiments_cards)
|
||||
before_action :load_vars, only: %i(show edit update notifications sidebar experiments_cards view_type)
|
||||
before_action :load_current_folder, only: %i(index cards new show)
|
||||
before_action :check_view_permissions, only: %i(show notifications sidebar experiments_cards view_type)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: :edit
|
||||
before_action :set_inline_name_editing, only: %i(show)
|
||||
before_action :load_exp_sort_var, only: %i(show experiment_archive)
|
||||
before_action :reset_invalid_view_state, only: %i(index cards)
|
||||
before_action :load_exp_sort_var, only: :show
|
||||
before_action :reset_invalid_view_state, only: %i(index cards show)
|
||||
|
||||
layout 'fluid'
|
||||
|
||||
|
@ -26,11 +27,13 @@ class ProjectsController < ApplicationController
|
|||
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
|
||||
|
||||
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: {
|
||||
|
@ -41,9 +44,20 @@ class ProjectsController < ApplicationController
|
|||
)
|
||||
}
|
||||
else
|
||||
if current_folder
|
||||
breadcrumbs_html = render_to_string(partial: 'projects/index/breadcrumbs.html.erb',
|
||||
locals: { target_folder: current_folder, folder_page: true })
|
||||
projects_cards_url = project_folder_cards_url(current_folder)
|
||||
title = current_folder.name
|
||||
else
|
||||
breadcrumbs_html = ''
|
||||
projects_cards_url = cards_projects_url
|
||||
end
|
||||
|
||||
render json: {
|
||||
projects_cards_url: current_folder ? project_folder_cards_url(current_folder) : cards_projects_url,
|
||||
breadcrumbs_html: current_folder ? render_to_string(partial: 'projects/index/breadcrumbs.html.erb') : '',
|
||||
projects_cards_url: projects_cards_url,
|
||||
breadcrumbs_html: breadcrumbs_html,
|
||||
title: title,
|
||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/index/team_projects.html.erb',
|
||||
|
@ -254,9 +268,12 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
redirect_to action: :experiment_archive if @project.archived?
|
||||
# This is the "info" view
|
||||
current_team_switch(@project.team)
|
||||
|
||||
view_state = @project.current_view_state(current_user)
|
||||
@current_sort = view_state.state.dig('experiments', experiments_view_mode(@project), 'sort') || 'atoz'
|
||||
@current_view_type = view_state.state.dig('experiments', 'view_type')
|
||||
end
|
||||
|
||||
def experiments_cards
|
||||
|
@ -264,10 +281,10 @@ class ProjectsController < ApplicationController
|
|||
render json: {
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/show/experiments_list.html.erb',
|
||||
locals: { cards: overview_service.experiments }
|
||||
locals: { cards: overview_service.experiments,
|
||||
filters_included: filters_included? }
|
||||
)
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
def notifications
|
||||
|
@ -286,10 +303,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def experiment_archive
|
||||
current_team_switch(@project.team)
|
||||
end
|
||||
|
||||
def users_filter
|
||||
users = current_team.users.search(false, params[:query]).map do |u|
|
||||
{ value: u.id, label: sanitize_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } }
|
||||
|
@ -298,12 +311,24 @@ class ProjectsController < ApplicationController
|
|||
render json: 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
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_params
|
||||
params.require(:project).permit(:name, :team_id, :visibility, :archived, :project_folder_id)
|
||||
end
|
||||
|
||||
def view_type_params
|
||||
params.require(:project).require(:view_type)
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@project = Project.find_by(id: params[:id])
|
||||
|
||||
|
@ -348,16 +373,21 @@ class ProjectsController < ApplicationController
|
|||
@project.save
|
||||
end
|
||||
@current_sort = @project.experiments_order || 'new'
|
||||
@current_sort = 'new' if @current_sort.include?('arch') && action_name != 'experiment_archive'
|
||||
end
|
||||
|
||||
def filters_included?
|
||||
%i(search created_on_from created_on_to members archived_on_from archived_on_to folders_search)
|
||||
%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 = current_team.current_view_state(current_user)
|
||||
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
|
||||
|
||||
|
|
|
@ -192,11 +192,13 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def update_description
|
||||
old_description = @protocol.description
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @protocol.update(description: params.require(:protocol)[:description])
|
||||
log_activity(:edit_description_in_protocol_repository, nil, protocol: @protocol.id)
|
||||
TinyMceAsset.update_images(@protocol, params[:tiny_mce_images], current_user)
|
||||
protocol_annotation_notification(old_description)
|
||||
render json: {
|
||||
html: custom_auto_link(
|
||||
@protocol.tinymce_render(:description),
|
||||
|
@ -1200,4 +1202,16 @@ class ProtocolsController < ApplicationController
|
|||
project: project,
|
||||
message_items: message_items)
|
||||
end
|
||||
|
||||
def protocol_annotation_notification(old_text)
|
||||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
new_text: @protocol.description,
|
||||
title: t('notifications.protocol_description_annotation_title',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol.name),
|
||||
message: t('notifications.protocol_description_annotation_message_html',
|
||||
protocol: link_to(@protocol.name, edit_protocol_url(@protocol)))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ class RepositoriesController < ApplicationController
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render 'empty_index' if @repositories.blank?
|
||||
render 'empty_index' if Repository.accessible_by_teams(current_team).blank?
|
||||
end
|
||||
format.json do
|
||||
render json: prepare_repositories_datatable(@repositories, current_team, params)
|
||||
|
|
|
@ -29,7 +29,6 @@ class ResultAssetsController < ApplicationController
|
|||
if obj.fetch(:status)
|
||||
flash[:success] = t('result_assets.create.success_flash',
|
||||
module: @my_module.name)
|
||||
p params.as_json
|
||||
redirect_to results_my_module_path(@my_module, page: params[:page], order: params[:order])
|
||||
else
|
||||
flash[:error] = t('result_assets.error_flash')
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class SearchController < ApplicationController
|
||||
include IconsHelper
|
||||
include ProjectFoldersHelper
|
||||
before_action :load_vars, only: :index
|
||||
|
||||
def index
|
||||
|
|
|
@ -330,6 +330,7 @@ class StepsController < ApplicationController
|
|||
completed = params[:completed] == 'true'
|
||||
changed = @step.completed != completed
|
||||
@step.completed = completed
|
||||
@step.last_modified_by = current_user
|
||||
|
||||
if @step.save
|
||||
# Create activity
|
||||
|
|
|
@ -73,6 +73,8 @@ class TeamRepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
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 &
|
||||
update_params[:share_team_ids]&.map(&:to_i).to_a
|
||||
wp = update_params[:write_permissions]&.map(&:to_i)
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
class TeamsController < ApplicationController
|
||||
include ProjectsHelper
|
||||
|
||||
include CardsViewHelper
|
||||
attr_reader :current_folder
|
||||
helper_method :current_folder
|
||||
|
||||
before_action :load_vars, only: %i(sidebar export_projects export_projects_modal)
|
||||
before_action :load_current_folder, only: :sidebar
|
||||
before_action :check_read_permissions
|
||||
before_action :check_read_permissions, except: :view_type
|
||||
before_action :check_export_projects_permissions, only: %i(export_projects_modal export_projects)
|
||||
|
||||
def sidebar
|
||||
|
@ -77,6 +77,14 @@ class TeamsController < ApplicationController
|
|||
redirect_to root_path
|
||||
end
|
||||
|
||||
def view_type
|
||||
view_state = current_team.current_view_state(current_user)
|
||||
view_state.state['projects']['view_type'] = view_type_params
|
||||
view_state.save!
|
||||
|
||||
render json: { cards_view_type_class: cards_view_type_class(view_type_params) }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
|
@ -88,6 +96,10 @@ class TeamsController < ApplicationController
|
|||
params.permit(:id, project_ids: [], project_folder_ids: [])
|
||||
end
|
||||
|
||||
def view_type_params
|
||||
params.require(:projects).require(:view_type)
|
||||
end
|
||||
|
||||
def check_read_permissions
|
||||
render_403 unless can_read_team?(@team)
|
||||
end
|
||||
|
|
|
@ -15,8 +15,7 @@ class UserProjectsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html_title: t('projects.index.modal_view_users.modal_title', name: @project.name),
|
||||
html_body: render_to_string(partial: 'index.html.erb')
|
||||
html: render_to_string(partial: 'index.html.erb')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
|
||||
class Users::SessionsController < Devise::SessionsController
|
||||
layout :session_layout
|
||||
|
||||
after_action :after_sign_in, only: %i(create authenticate_with_two_factor)
|
||||
before_action :remove_authenticate_mesasge_if_root_path, only: :new
|
||||
prepend_before_action :redirect_2fa, only: :create
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken do
|
||||
redirect_to new_user_session_path
|
||||
|
@ -23,8 +21,15 @@ class Users::SessionsController < Devise::SessionsController
|
|||
|
||||
# POST /resource/sign_in
|
||||
def create
|
||||
super
|
||||
|
||||
super do |user|
|
||||
if user.two_factor_auth_enabled? && !bypass_two_factor_auth?
|
||||
sign_out
|
||||
session[:otp_user_id] = user.id
|
||||
store_location_for(:user, request.original_fullpath) if request.get?
|
||||
redirect_to users_two_factor_auth_path
|
||||
return
|
||||
end
|
||||
end
|
||||
generate_templates_project
|
||||
end
|
||||
|
||||
|
@ -34,34 +39,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
end
|
||||
end
|
||||
|
||||
# DELETE /resource/sign_out
|
||||
# def destroy
|
||||
# super
|
||||
# end
|
||||
|
||||
# Singing in with authentication token (needed when signing in automatically
|
||||
# from another website). NOTE: For some reason URL needs to end with '/'.
|
||||
def auth_token_create
|
||||
user = User.find_by_email(params[:user_email])
|
||||
user_token = params[:user_token]
|
||||
# Remove trailing slash if present
|
||||
user_token.chop! if !user_token.nil? && user_token.end_with?('/')
|
||||
|
||||
if user && user.authentication_token == user_token
|
||||
sign_in(:user, user)
|
||||
# This will cause new token to be generated
|
||||
user.update(authentication_token: nil)
|
||||
redirect_url = root_path
|
||||
else
|
||||
flash[:error] = t('devise.sessions.auth_token_create.wrong_credentials')
|
||||
redirect_url = new_user_session_path
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to redirect_url
|
||||
end
|
||||
end
|
||||
def two_factor_auth
|
||||
end
|
||||
|
||||
def after_sign_in
|
||||
|
@ -118,18 +96,6 @@ class Users::SessionsController < Devise::SessionsController
|
|||
end
|
||||
end
|
||||
|
||||
def redirect_2fa
|
||||
user = User.find_by(email: params[:user][:email])
|
||||
|
||||
return unless user&.valid_password?(params[:user][:password])
|
||||
|
||||
if user&.two_factor_auth_enabled?
|
||||
session[:otp_user_id] = user.id
|
||||
store_location_for(:user, request.original_fullpath) if request.get?
|
||||
render :two_factor_auth
|
||||
end
|
||||
end
|
||||
|
||||
def generate_templates_project
|
||||
# Schedule templates creation for user
|
||||
TemplatesService.new.schedule_creation_for_user(current_user)
|
||||
|
@ -144,4 +110,8 @@ class Users::SessionsController < Devise::SessionsController
|
|||
'layouts/main'
|
||||
end
|
||||
end
|
||||
|
||||
def bypass_two_factor_auth?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,9 +16,9 @@ class TeamUsersDatatable < CustomDatatable
|
|||
@sortable_columns ||= [
|
||||
'User.full_name',
|
||||
'User.email',
|
||||
'UserTeam.role',
|
||||
'UserTeam.created_at',
|
||||
'User.confirmed_at',
|
||||
'UserTeam.role'
|
||||
'User.status'
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -82,6 +82,16 @@ class TeamUsersDatatable < CustomDatatable
|
|||
end
|
||||
end
|
||||
|
||||
# Overwrite default pagination method as here
|
||||
# we need to be able work also with arrays
|
||||
def paginate_records(records)
|
||||
records.to_a.drop(offset).first(per_page)
|
||||
end
|
||||
|
||||
def load_paginator
|
||||
self
|
||||
end
|
||||
|
||||
# Query database for records (this will be later paginated and filtered)
|
||||
# after that "data" function will return json
|
||||
def get_raw_records
|
||||
|
@ -91,4 +101,16 @@ class TeamUsersDatatable < CustomDatatable
|
|||
.where(team: @team)
|
||||
.distinct
|
||||
end
|
||||
|
||||
def sort_records(records)
|
||||
if sort_column(order_params) == 'users.status'
|
||||
records = records.sort_by { |record| record.user.active_status_str }
|
||||
order_params['dir'] == 'asc' ? records : records.reverse
|
||||
elsif sort_column(order_params) == 'user_teams.role'
|
||||
records = records.sort_by(&:role_str)
|
||||
order_params['dir'] == 'asc' ? records : records.reverse
|
||||
else
|
||||
super(records)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,6 +63,15 @@ class TeamsDatatable < CustomDatatable
|
|||
elsif order_params['dir'] == 'desc'
|
||||
return records.reverse
|
||||
end
|
||||
elsif sort_column(order_params) == 'user_teams.role'
|
||||
records_with_role = records.where(user: @user).order(role: :asc)
|
||||
records_with_no_role = records.where.not(user: @user)
|
||||
records = records_with_no_role + records_with_role
|
||||
if order_params['dir'] == 'asc'
|
||||
records
|
||||
else
|
||||
records.reverse
|
||||
end
|
||||
else
|
||||
super(records)
|
||||
end
|
||||
|
|
|
@ -202,6 +202,13 @@ module ApplicationHelper
|
|||
return user.convert_variant_to_base64(avatar_link) if base64_encoded_imgs
|
||||
|
||||
avatar_link.processed.service_url(expires_in: Constants::URL_LONG_EXPIRE_TIME)
|
||||
elsif base64_encoded_imgs
|
||||
file_path = Rails.root.join('app', 'assets', *avatar_link.split('/'))
|
||||
encoded_data =
|
||||
File.open(file_path) do |file|
|
||||
Base64.strict_encode64(file.read)
|
||||
end
|
||||
"data:#{avatar_link.split('.').last};base64,#{encoded_data}"
|
||||
else
|
||||
avatar_link
|
||||
end
|
||||
|
|
7
app/helpers/cards_view_helper.rb
Normal file
7
app/helpers/cards_view_helper.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CardsViewHelper
|
||||
def cards_view_type_class(view_type)
|
||||
view_type == 'table' ? 'list' : ''
|
||||
end
|
||||
end
|
|
@ -204,7 +204,7 @@ module CommentHelper
|
|||
title: t('notifications.my_module_comment_annotation_title',
|
||||
my_module: my_module.name,
|
||||
user: current_user.full_name),
|
||||
message: t('notifications.my_module_annotation_message_html',
|
||||
message: t('notifications.my_module_comment_annotation_message_html',
|
||||
project: link_to(my_module.experiment.project.name,
|
||||
project_url(my_module
|
||||
.experiment
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
module FileIconsHelper
|
||||
def wopi_file?(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
file_ext = asset.file_name.split('.').last&.downcase
|
||||
%w(csv ods xls xlsb xlsm xlsx odp pot potm potx pps ppsm
|
||||
ppsx ppt pptm pptx doc docm docx dot dotm dotx odt rtf).include?(file_ext)
|
||||
end
|
||||
|
||||
def file_fa_icon_class(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
file_ext = asset.file_name.split('.').last&.downcase
|
||||
|
||||
if Extends::FILE_FA_ICON_MAPPINGS[file_ext] # Check for custom mappings or possible overrides
|
||||
Extends::FILE_FA_ICON_MAPPINGS[file_ext]
|
||||
|
@ -30,8 +30,8 @@ module FileIconsHelper
|
|||
end
|
||||
|
||||
# For showing next to file
|
||||
def file_extension_icon(asset)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
def file_extension_icon(asset, report = false)
|
||||
file_ext = asset.file_name.split('.').last&.downcase
|
||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
image_link = 'icon_small/docx_file.svg'
|
||||
elsif Constants::FILE_TABLE_FORMATS.include?(file_ext)
|
||||
|
@ -46,7 +46,13 @@ module FileIconsHelper
|
|||
image_link = Extends::FILE_ICON_MAPPINGS[file_ext] if Extends::FILE_ICON_MAPPINGS[file_ext]
|
||||
|
||||
if image_link
|
||||
ActionController::Base.helpers.image_tag(image_link, class: 'image-icon')
|
||||
if report
|
||||
image_tag("data:image/svg+xml;base64,#{
|
||||
Base64.encode64(File.read(Rails.root.join('app/assets/images/', image_link)))
|
||||
}", class: 'image-icon')
|
||||
else
|
||||
ActionController::Base.helpers.image_tag(image_link, class: 'image-icon')
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
|
@ -100,8 +106,8 @@ module FileIconsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def file_extension_icon_html(asset)
|
||||
html = file_extension_icon(asset)
|
||||
def file_extension_icon_html(asset, report = false)
|
||||
html = file_extension_icon(asset, report)
|
||||
if html.blank?
|
||||
html = ActionController::Base.helpers.content_tag(
|
||||
:i,
|
||||
|
|
|
@ -5,22 +5,27 @@ module GlobalActivitiesHelper
|
|||
include ActionView::Helpers::UrlHelper
|
||||
include InputSanitizeHelper
|
||||
|
||||
def generate_activity_content(activity, no_links = false)
|
||||
def generate_activity_content(activity, no_links: false, no_custom_links: false)
|
||||
parameters = {}
|
||||
activity.message_items.each do |key, value|
|
||||
parameters[key] =
|
||||
if value.is_a? String
|
||||
value
|
||||
elsif value['type'] == 'Time' # use saved date for printing
|
||||
l(Time.at(value['value']), format: :full)
|
||||
I18n.l(Time.zone.at(value['value']), format: :full)
|
||||
else
|
||||
no_links ? generate_name(value) : generate_link(value, activity)
|
||||
end
|
||||
end
|
||||
custom_auto_link(
|
||||
I18n.t("global_activities.content.#{activity.type_of}_html", parameters.symbolize_keys),
|
||||
team: activity.team
|
||||
)
|
||||
|
||||
if no_custom_links
|
||||
I18n.t("global_activities.content.#{activity.type_of}_html", parameters.symbolize_keys)
|
||||
else
|
||||
custom_auto_link(
|
||||
I18n.t("global_activities.content.#{activity.type_of}_html", parameters.symbolize_keys),
|
||||
team: activity.team
|
||||
)
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
|
@ -63,7 +68,7 @@ module GlobalActivitiesHelper
|
|||
when Experiment
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
path = obj.archived? ? experiment_archive_project_path(obj.project) : canvas_experiment_path(obj)
|
||||
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : canvas_experiment_path(obj)
|
||||
when MyModule
|
||||
return current_value unless obj.navigable?
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProjectsHelper
|
||||
def projects_view_mode
|
||||
def projects_view_mode(project: nil)
|
||||
return (project.archived? ? 'archived' : 'active') if project
|
||||
|
||||
return 'archived' if current_folder&.archived?
|
||||
|
||||
params[:view_mode] == 'archived' ? 'archived' : 'active'
|
||||
|
|
BIN
app/javascript/packs/pdfjs/images/loading-icon.gif
Normal file
BIN
app/javascript/packs/pdfjs/images/loading-icon.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
app/javascript/packs/pdfjs/images/shadow.png
Normal file
BIN
app/javascript/packs/pdfjs/images/shadow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 B |
4
app/javascript/packs/pdfjs/pdf_js.js
Normal file
4
app/javascript/packs/pdfjs/pdf_js.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
global.pdfjsLib = require('pdfjs-dist');
|
||||
global.pdfjsLibUtils = require('pdfjs-dist/web/pdf_viewer.js');
|
||||
|
||||
PdfPreview.initCanvas();
|
1
app/javascript/packs/pdfjs/pdf_js_styles.scss
Normal file
1
app/javascript/packs/pdfjs/pdf_js_styles.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "~pdfjs-dist/web/pdf_viewer";
|
1
app/javascript/packs/pdfjs/pdf_js_worker.js
Normal file
1
app/javascript/packs/pdfjs/pdf_js_worker.js
Normal file
|
@ -0,0 +1 @@
|
|||
require('pdfjs-dist/build/pdf.worker.js');
|
9
app/jobs/create_notification_from_activity_job.rb
Normal file
9
app/jobs/create_notification_from_activity_job.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateNotificationFromActivityJob < ApplicationJob
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(activity)
|
||||
activity.generate_notification_from_activity
|
||||
end
|
||||
end
|
54
app/jobs/pdf_preview_job.rb
Normal file
54
app/jobs/pdf_preview_job.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Provides asynchronous generation of image previews for ActiveStorage::Blob records.
|
||||
class PdfPreviewJob < ApplicationJob
|
||||
queue_as :assets
|
||||
|
||||
discard_on StandardError do |job, error|
|
||||
asset = Asset.find_by(id: job.arguments.first)
|
||||
ActiveRecord::Base.no_touching do
|
||||
asset&.update(pdf_preview_processing: false)
|
||||
end
|
||||
Rails.logger.error("Couldn't generate PDF preview for Asset with id: #{job.arguments.first}. Error:\n #{error}")
|
||||
end
|
||||
|
||||
discard_on ActiveRecord::RecordNotFound
|
||||
|
||||
def perform(asset_id)
|
||||
asset = Asset.find(asset_id)
|
||||
blob = asset.file.blob
|
||||
blob.open(tmpdir: tempdir) do |input|
|
||||
work_dir = File.dirname(input.path)
|
||||
preview_filename = "#{File.basename(input.path, '.*')}.pdf"
|
||||
preview_file = File.join(work_dir, preview_filename)
|
||||
Rails.logger.info "Starting preparing document preview for file #{blob.filename.sanitized}..."
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
success = system(
|
||||
libreoffice_path, '--headless', '--invisible', '--convert-to', 'pdf', '--outdir', work_dir, input.path
|
||||
)
|
||||
unless success && File.file?(preview_file)
|
||||
raise StandardError, "There was an error generating PDF preview, blob id: #{blob.id}"
|
||||
end
|
||||
|
||||
ActiveRecord::Base.no_touching do
|
||||
asset.file_pdf_preview.attach(io: File.open(preview_file), filename: "#{blob.filename.base}.pdf")
|
||||
asset.update(pdf_preview_processing: false)
|
||||
end
|
||||
Rails.logger.info("Finished preparing PDF preview for file #{blob.filename.sanitized}.")
|
||||
ensure
|
||||
File.delete(preview_file) if File.file?(preview_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tempdir
|
||||
Rails.root.join('tmp')
|
||||
end
|
||||
|
||||
def libreoffice_path
|
||||
ENV['LIBREOFFICE_PATH'] || 'soffice'
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class Activity < ApplicationRecord
|
||||
include ActivityValuesModel
|
||||
include GenerateNotificationModel
|
||||
|
||||
enum type_of: Extends::ACTIVITY_TYPES
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ class Asset < ApplicationRecord
|
|||
|
||||
# ActiveStorage configuration
|
||||
has_one_attached :file
|
||||
has_one_attached :file_pdf_preview
|
||||
|
||||
# Asset validation
|
||||
# This could cause some problems if you create empty asset and want to
|
||||
|
@ -235,6 +236,24 @@ class Asset < ApplicationRecord
|
|||
file.metadata[:asset_type] == 'marvinjs'
|
||||
end
|
||||
|
||||
def pdf_preview_ready?
|
||||
return false if pdf_preview_processing
|
||||
|
||||
return true if file_pdf_preview.attached?
|
||||
|
||||
PdfPreviewJob.perform_later(id)
|
||||
ActiveRecord::Base.no_touching { update(pdf_preview_processing: true) }
|
||||
false
|
||||
end
|
||||
|
||||
def pdf?
|
||||
content_type == 'application/pdf'
|
||||
end
|
||||
|
||||
def pdf_previewable?
|
||||
pdf? || (previewable_document?(blob) && Rails.application.config.x.enable_pdf_previews)
|
||||
end
|
||||
|
||||
def post_process_file(team = nil)
|
||||
# Extract asset text if it's of correct type
|
||||
if text?
|
||||
|
@ -248,6 +267,11 @@ class Asset < ApplicationRecord
|
|||
# Update asset's estimated size immediately
|
||||
update_estimated_size(team)
|
||||
end
|
||||
|
||||
if Rails.application.config.x.enable_pdf_previews && previewable_document?(blob)
|
||||
PdfPreviewJob.perform_later(id)
|
||||
ActiveRecord::Base.no_touching { update(pdf_preview_processing: true) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.extract_asset_text_delayed(asset_id, in_template = false)
|
||||
|
|
112
app/models/concerns/generate_notification_model.rb
Normal file
112
app/models/concerns/generate_notification_model.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module GenerateNotificationModel
|
||||
extend ActiveSupport::Concern
|
||||
include GlobalActivitiesHelper
|
||||
|
||||
included do
|
||||
after_create :generate_notification
|
||||
end
|
||||
|
||||
def generate_notification_from_activity
|
||||
return if notification_recipients.none?
|
||||
|
||||
message = generate_activity_content(self, no_links: true, no_custom_links: true)
|
||||
description = generate_notification_description_elements(subject).reverse.join(' | ')
|
||||
|
||||
notification = Notification.create(
|
||||
type_of: :recent_changes,
|
||||
title: sanitize_input(message, %w(strong a)),
|
||||
message: sanitize_input(description, %w(strong a)),
|
||||
generator_user_id: owner.id
|
||||
)
|
||||
|
||||
notification_recipients.each do |user|
|
||||
notification.create_user_notification(user)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def notification_recipients
|
||||
users = []
|
||||
|
||||
case subject
|
||||
when Project
|
||||
users = subject.users
|
||||
when Experiment
|
||||
users = subject.project.users
|
||||
when MyModule
|
||||
users = subject.users
|
||||
when Protocol
|
||||
users = subject.in_repository? ? [] : subject.my_module.users
|
||||
when Result
|
||||
users = subject.my_module.users
|
||||
when Repository
|
||||
users = subject.team.users
|
||||
when Team
|
||||
users = subject.users
|
||||
when Report
|
||||
users = subject.team.users
|
||||
when ProjectFolder
|
||||
users = subject.team.users
|
||||
end
|
||||
users - [owner]
|
||||
end
|
||||
|
||||
# This method returns unsanitized elements. They must be sanitized before saving to DB
|
||||
def generate_notification_description_elements(object, elements = [])
|
||||
case object
|
||||
when Project
|
||||
path = Rails.application.routes.url_helpers.project_path(object)
|
||||
elements << "#{I18n.t('search.index.project')} <a href='#{path}'>#{object.name}</a>"
|
||||
when Experiment
|
||||
path = Rails.application.routes.url_helpers.canvas_experiment_path(object)
|
||||
elements << "#{I18n.t('search.index.experiment')} <a href='#{path}'>#{object.name}</a>"
|
||||
generate_notification_description_elements(object.project, elements)
|
||||
when MyModule
|
||||
path = if object.archived?
|
||||
Rails.application.routes.url_helpers.module_archive_experiment_path(object.experiment)
|
||||
else
|
||||
Rails.application.routes.url_helpers.protocols_my_module_path(object)
|
||||
end
|
||||
elements << "#{I18n.t('search.index.module')} <a href='#{path}'>#{object.name}</a>"
|
||||
generate_notification_description_elements(object.experiment, elements)
|
||||
when Protocol
|
||||
if object.in_repository?
|
||||
path = Rails.application.routes.url_helpers.protocols_path(team: object.team.id)
|
||||
elements << "#{I18n.t('search.index.protocol')} <a href='#{path}'>#{object.name}</a>"
|
||||
generate_notification_description_elements(object.team, elements)
|
||||
else
|
||||
generate_notification_description_elements(object.my_module, elements)
|
||||
end
|
||||
when Result
|
||||
generate_notification_description_elements(object.my_module, elements)
|
||||
when Repository
|
||||
path = Rails.application.routes.url_helpers.repository_path(object, team: object.team.id)
|
||||
elements << "#{I18n.t('search.index.repository')} <a href='#{path}'>#{object.name}</a>"
|
||||
generate_notification_description_elements(object.team, elements)
|
||||
when Team
|
||||
path = Rails.application.routes.url_helpers.projects_path(team: object.id)
|
||||
elements << "#{I18n.t('search.index.team')} <a href='#{path}'>#{object.name}</a>"
|
||||
when Report
|
||||
path = Rails.application.routes.url_helpers.reports_path(team: object.team.id)
|
||||
elements << "#{I18n.t('search.index.report')} <a href='#{path}'>#{object.name}</a>"
|
||||
generate_notification_description_elements(object.team, elements)
|
||||
when ProjectFolder
|
||||
generate_notification_description_elements(object.team, elements)
|
||||
end
|
||||
|
||||
elements
|
||||
end
|
||||
|
||||
def notifiable?
|
||||
type_of.in? ::Extends::NOTIFIABLE_ACTIVITIES
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_notification
|
||||
CreateNotificationFromActivityJob.perform_later(self) if notifiable?
|
||||
end
|
||||
end
|
|
@ -477,7 +477,13 @@ class Experiment < ApplicationRecord
|
|||
x_diff = my_modules.active.pluck(:x).min
|
||||
y_diff = my_modules.active.pluck(:y).min
|
||||
|
||||
my_modules.active.each do |m|
|
||||
return unless x_diff && y_diff
|
||||
|
||||
moving_direction = {
|
||||
x: x_diff.positive? ? :asc : :desc,
|
||||
y: y_diff.positive? ? :asc : :desc
|
||||
}
|
||||
my_modules.active.order(moving_direction).each do |m|
|
||||
m.update!(x: m.x - x_diff, y: m.y - y_diff)
|
||||
end
|
||||
my_modules.reload
|
||||
|
|
|
@ -10,4 +10,10 @@ class Notification < ApplicationRecord
|
|||
.pluck(:checked)
|
||||
.first
|
||||
end
|
||||
|
||||
def create_user_notification(user)
|
||||
return if user == generator_user
|
||||
|
||||
user_notifications.create!(user: user) if user.enabled_notifications_for?(type_of.to_sym, :web)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -163,13 +163,15 @@ class Project < ApplicationRecord
|
|||
{
|
||||
experiments: {
|
||||
active: { sort: 'new' },
|
||||
archived: { sort: 'new' }
|
||||
archived: { sort: 'new' },
|
||||
view_type: 'cards'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def validate_view_state(view_state)
|
||||
if %w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
|
||||
if %w(cards table).exclude?(view_state.state.dig('experiments', 'view_type')) ||
|
||||
%w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
|
||||
view_state.errors.add(:state, :wrong_state)
|
||||
end
|
||||
|
@ -358,6 +360,6 @@ class Project < ApplicationRecord
|
|||
end
|
||||
|
||||
def remove_project_folder
|
||||
self.project_folder = nil if archived?
|
||||
self.project_folder = nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ class ProjectFolder < ApplicationRecord
|
|||
validates :name,
|
||||
length: { minimum: Constants::NAME_MIN_LENGTH,
|
||||
maximum: Constants::NAME_MAX_LENGTH },
|
||||
uniqueness: { scope: %i(team_id parent_folder_id), case_sensitive: false }
|
||||
uniqueness: { scope: %i(team_id parent_folder_id archived), case_sensitive: false }
|
||||
validate :parent_folder_team, if: -> { parent_folder.present? }
|
||||
validate :parent_folder_validation, if: -> { parent_folder.present? }
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ class RepositoryNumberValue < ApplicationRecord
|
|||
end
|
||||
|
||||
def data_changed?(new_data)
|
||||
BigDecimal(new_data) != data
|
||||
BigDecimal(new_data.to_s) != data
|
||||
end
|
||||
|
||||
def update_data!(new_data, user)
|
||||
self.data = BigDecimal(new_data)
|
||||
self.data = BigDecimal(new_data.to_s)
|
||||
self.last_modified_by = user
|
||||
save!
|
||||
end
|
||||
|
@ -40,12 +40,12 @@ class RepositoryNumberValue < ApplicationRecord
|
|||
|
||||
def self.new_with_payload(payload, attributes)
|
||||
value = new(attributes)
|
||||
value.data = BigDecimal(payload)
|
||||
value.data = BigDecimal(payload.to_s)
|
||||
value
|
||||
end
|
||||
|
||||
def self.import_from_text(text, attributes, _options = {})
|
||||
new(attributes.merge(data: BigDecimal(text)))
|
||||
new(attributes.merge(data: BigDecimal(text.to_s)))
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -107,11 +107,6 @@ class Step < ApplicationRecord
|
|||
StepComment.from(comments, :comments).order(created_at: :asc)
|
||||
end
|
||||
|
||||
def save(current_user=nil)
|
||||
@current_user = current_user
|
||||
super()
|
||||
end
|
||||
|
||||
def space_taken
|
||||
st = 0
|
||||
assets.each do |asset|
|
||||
|
@ -202,23 +197,6 @@ class Step < ApplicationRecord
|
|||
end
|
||||
|
||||
def set_last_modified_by
|
||||
if @current_user&.is_a?(User)
|
||||
self.tables.each do |t|
|
||||
t.created_by ||= @current_user
|
||||
t.last_modified_by = @current_user if t.changed?
|
||||
end
|
||||
self.assets.each do |a|
|
||||
a.created_by ||= @current_user
|
||||
a.last_modified_by = @current_user if a.changed?
|
||||
end
|
||||
self.checklists.each do |checklist|
|
||||
checklist.created_by ||= @current_user
|
||||
checklist.last_modified_by = @current_user if checklist.changed?
|
||||
checklist.checklist_items.each do |checklist_item|
|
||||
checklist_item.created_by ||= @current_user
|
||||
checklist_item.last_modified_by = @current_user if checklist_item.changed?
|
||||
end
|
||||
end
|
||||
end
|
||||
self.last_modified_by_id ||= user_id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ class SystemNotification < ApplicationRecord
|
|||
}
|
||||
# ignoring: :accents
|
||||
|
||||
has_many :user_system_notifications
|
||||
has_many :user_system_notifications, dependent: :destroy
|
||||
has_many :users, through: :user_system_notifications
|
||||
|
||||
validates :title, :modal_title, :modal_body, :description, :source_created_at, :source_id, :last_time_changed_at,
|
||||
|
|
|
@ -43,17 +43,4 @@ class Tag < ApplicationRecord
|
|||
.offset((page - 1) * Constants::SEARCH_LIMIT)
|
||||
end
|
||||
end
|
||||
|
||||
def clone_to_project_or_return_existing(project)
|
||||
tag = Tag.find_by(project: project, name: name, color: color)
|
||||
return tag if tag
|
||||
|
||||
Tag.create(
|
||||
name: name,
|
||||
color: color,
|
||||
created_by: created_by,
|
||||
last_modified_by: last_modified_by,
|
||||
project: project
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,14 +48,16 @@ class Team < ApplicationRecord
|
|||
{
|
||||
projects: {
|
||||
active: { sort: 'new' },
|
||||
archived: { sort: 'new' }
|
||||
archived: { sort: 'new' },
|
||||
view_type: 'cards'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def validate_view_state(view_state)
|
||||
if %w(new old atoz ztoa).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
|
||||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort'))
|
||||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
|
||||
%w(cards table).exclude?(view_state.state.dig('projects', 'view_type'))
|
||||
view_state.errors.add(:state, :wrong_state)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,6 @@ class User < ApplicationRecord
|
|||
include InputSanitizeHelper
|
||||
include ActiveStorageConcerns
|
||||
|
||||
acts_as_token_authenticatable
|
||||
devise :invitable, :confirmable, :database_authenticatable, :registerable,
|
||||
:async, :recoverable, :rememberable, :trackable, :validatable,
|
||||
:timeoutable, :omniauthable, :lockable,
|
||||
|
@ -556,6 +555,19 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def enabled_notifications_for?(notification_type, channel)
|
||||
return true if notification_type == :deliver
|
||||
|
||||
case channel
|
||||
when :web
|
||||
notification_type == :recent_changes && recent_notification ||
|
||||
notification_type == :assignment && assignments_notification
|
||||
when :email
|
||||
notification_type == :recent_changes && recent_email_notification ||
|
||||
notification_type == :assignment && assignments_email_notification
|
||||
end
|
||||
end
|
||||
|
||||
def increase_daily_exports_counter!
|
||||
range = Time.now.utc.beginning_of_day.to_i..Time.now.utc.end_of_day.to_i
|
||||
last_export = export_vars[:last_export_timestamp] || 0
|
||||
|
|
|
@ -6,7 +6,7 @@ class UserNotification < ApplicationRecord
|
|||
belongs_to :user, optional: true
|
||||
belongs_to :notification, optional: true
|
||||
|
||||
after_save :send_email
|
||||
after_create :send_email
|
||||
|
||||
def self.last_notifications(
|
||||
user,
|
||||
|
@ -37,27 +37,6 @@ class UserNotification < ApplicationRecord
|
|||
end
|
||||
|
||||
def send_email
|
||||
case notification.type_of
|
||||
when 'system_message'
|
||||
send_email_notification(
|
||||
user,
|
||||
notification
|
||||
) if user.system_message_email_notification
|
||||
when 'assignment'
|
||||
send_email_notification(
|
||||
user,
|
||||
notification
|
||||
) if user.assignments_email_notification
|
||||
when 'recent_changes'
|
||||
send_email_notification(
|
||||
user,
|
||||
notification
|
||||
) if user.recent_email_notification
|
||||
when 'deliver'
|
||||
send_email_notification(
|
||||
user,
|
||||
notification
|
||||
)
|
||||
end
|
||||
send_email_notification(user, notification) if user.enabled_notifications_for?(notification.type_of.to_sym, :email)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,10 +64,15 @@ Canaid::Permissions.register_for(Project) do
|
|||
end
|
||||
|
||||
# experiment: create
|
||||
|
||||
can :create_experiments do |user, project|
|
||||
project.permission_granted?(user, ProjectPermissions::CREATE_EXPERIMENTS)
|
||||
end
|
||||
|
||||
can :manage_experiments do |user, project|
|
||||
project.permission_granted?(user, ProjectPermissions::CREATE_EXPERIMENTS)
|
||||
end
|
||||
|
||||
# project: create comment
|
||||
can :create_comments_in_project do |user, project|
|
||||
project.permission_granted?(user, ProjectPermissions::CREATE_COMMENTS)
|
||||
|
@ -95,3 +100,10 @@ Canaid::Permissions.register_for(ProjectComment) do
|
|||
project.permission_granted?(user, ProjectPermissions::MANAGE_COMMENTS))
|
||||
end
|
||||
end
|
||||
|
||||
Canaid::Permissions.register_for(ProjectFolder) do
|
||||
# ProjectFolder: delete
|
||||
can :delete_project_folder do |_, project_folder|
|
||||
!project_folder.projects.exists? && !project_folder.project_folders.exists?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ module Api
|
|||
if object.old_activity?
|
||||
object.message
|
||||
else
|
||||
generate_activity_content(object, true)
|
||||
generate_activity_content(object, no_links: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,10 +16,10 @@ class ActivitiesService
|
|||
subjects_with_children = load_subjects_children(filters[:subjects])
|
||||
if subjects_with_children['Project']
|
||||
query = query.where('project_id IN (?)', subjects_with_children['Project'])
|
||||
subjects_with_children.except!('Project')
|
||||
subjects_with_children = subjects_with_children.except('Project')
|
||||
end
|
||||
where_condition = subjects_with_children.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR ')
|
||||
where_arguments = subjects_with_children.flatten
|
||||
where_condition = subjects_with_children.to_h.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR ')
|
||||
where_arguments = subjects_with_children.to_h.flatten
|
||||
if subjects_with_children[:my_module]
|
||||
where_condition = where_condition.concat(' OR (my_module_id IN(?))')
|
||||
where_arguments << subjects_with_children[:my_module]
|
||||
|
@ -28,7 +28,8 @@ class ActivitiesService
|
|||
end
|
||||
|
||||
query = query.where(owner_id: filters[:users]) if filters[:users]
|
||||
query = query.where(type_of: filters[:types]) if filters[:types]
|
||||
query = query.where(type_of: filters[:types].map(&:to_i)) if filters[:types]
|
||||
|
||||
query = query.where('created_at <= ?', Time.at(filters[:starting_timestamp].to_i)) if filters[:starting_timestamp]
|
||||
|
||||
activities =
|
||||
|
@ -71,8 +72,8 @@ class ActivitiesService
|
|||
subjects_with_children = load_subjects_children('MyModule': [my_module.id])
|
||||
query = Activity.where(project: my_module.experiment.project)
|
||||
query.where(
|
||||
subjects_with_children.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR '),
|
||||
*subjects_with_children.flatten
|
||||
subjects_with_children.to_h.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR '),
|
||||
*subjects_with_children.to_h.flatten
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,9 +9,7 @@ module Experiments
|
|||
|
||||
def initialize(experiment:)
|
||||
@exp = experiment
|
||||
graph_params = {
|
||||
type: :digraph,
|
||||
use: :neato,
|
||||
@graph_params = {
|
||||
inputscale: 3,
|
||||
size: '2,2',
|
||||
pad: '0.4',
|
||||
|
@ -22,15 +20,20 @@ module Experiments
|
|||
bgcolor: Constants::COLOR_CONCRETE,
|
||||
mode: 'ipsep'
|
||||
}
|
||||
@graph = GraphViz.new(:G, graph_params)
|
||||
@graph.node[color: Constants::COLOR_VOLCANO,
|
||||
style: :filled,
|
||||
fontcolor: Constants::COLOR_VOLCANO,
|
||||
shape: 'circle',
|
||||
fontname: 'Arial',
|
||||
fontsize: '16.0']
|
||||
@node_params = {
|
||||
color: Constants::COLOR_VOLCANO,
|
||||
style: :filled,
|
||||
fontcolor: Constants::COLOR_VOLCANO,
|
||||
shape: 'circle',
|
||||
fontname: 'Arial',
|
||||
fontsize: '16.0'
|
||||
}
|
||||
@edge_params = {
|
||||
color: Constants::COLOR_VOLCANO,
|
||||
penwidth: '3.0'
|
||||
}
|
||||
|
||||
@graph.edge[color: Constants::COLOR_VOLCANO, penwidth: '3.0']
|
||||
@graph = Graphviz::Graph.new('G', @graph_params)
|
||||
@errors = []
|
||||
end
|
||||
|
||||
|
@ -48,18 +51,18 @@ module Experiments
|
|||
|
||||
def draw_diagram
|
||||
# Draw grouped modules
|
||||
subg = {}
|
||||
@exp.my_module_groups.each_with_index do |group, gindex|
|
||||
subgraph_id = "cluster-#{gindex}"
|
||||
subg[subgraph_id] = @graph.subgraph
|
||||
nodes = {}
|
||||
|
||||
group.my_modules.workflow_ordered.each_with_index do |my_module, index|
|
||||
# draw nodes
|
||||
node = subg[subgraph_id].add_nodes(
|
||||
node = @graph.add_node(
|
||||
"#{subgraph_id}-#{index}",
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
@node_params.merge(
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
)
|
||||
)
|
||||
nodes[my_module.id] = node
|
||||
end
|
||||
|
@ -69,17 +72,19 @@ module Experiments
|
|||
m.outputs.each do |output|
|
||||
parent_node = nodes[m.id]
|
||||
child_node = nodes[output.input_id]
|
||||
subg[subgraph_id].add_edges(parent_node, child_node)
|
||||
parent_node.connect(child_node, @edge_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Draw orphan nodes
|
||||
@exp.my_modules.without_group.each do |my_module|
|
||||
@graph.subgraph.add_nodes(
|
||||
@graph.add_node(
|
||||
"Orphan-#{my_module.id}",
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
@node_params.merge(
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -87,7 +92,7 @@ module Experiments
|
|||
def save_file
|
||||
file = Tempfile.open(%w(wimg .png), Rails.root.join('tmp'))
|
||||
begin
|
||||
@graph.output(png: file.path)
|
||||
Graphviz.output(@graph, path: file.path, format: 'png', dot: 'neato')
|
||||
file.rewind
|
||||
@exp.workflowimg.attach(io: file, filename: File.basename(file.path))
|
||||
ensure
|
||||
|
|
|
@ -20,19 +20,24 @@ module Experiments
|
|||
ActiveRecord::Base.transaction do
|
||||
@exp.project = @project
|
||||
|
||||
@exp.my_modules.each do |m|
|
||||
new_tags = m.tags.map do |t|
|
||||
t.clone_to_project_or_return_existing(@project)
|
||||
@exp.my_modules.each do |my_module|
|
||||
new_tags = []
|
||||
my_module.tags.each do |tag|
|
||||
new_tag = @project.tags.where.not(id: new_tags).find_by(name: tag.name, color: tag.color)
|
||||
new_tag ||=
|
||||
@project.tags.create!(name: tag.name, color: tag.color, created_by: @user, last_modified_by: @user)
|
||||
new_tags << new_tag
|
||||
end
|
||||
m.my_module_tags.delete_all
|
||||
m.tags = new_tags
|
||||
my_module.tags.destroy_all
|
||||
my_module.tags = new_tags
|
||||
end
|
||||
|
||||
raise ActiveRecord::Rollback unless @exp.save
|
||||
@exp.save!
|
||||
rescue
|
||||
@errors.merge!(@exp.errors.to_hash) unless @exp.valid?
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
@errors.merge!(@exp.errors.to_hash) unless @exp.valid?
|
||||
|
||||
track_activity if succeed?
|
||||
self
|
||||
end
|
||||
|
|
|
@ -33,7 +33,16 @@ class ExperimentsOverviewService
|
|||
private
|
||||
|
||||
def fetch_records
|
||||
@project.experiments.joins(:project)
|
||||
@project.experiments
|
||||
.joins(:project)
|
||||
.joins('LEFT OUTER JOIN my_modules AS active_tasks ON active_tasks.experiment_id = experiments.id ' \
|
||||
'AND active_tasks.archived = FALSE')
|
||||
.joins('LEFT OUTER JOIN my_modules AS active_completed_tasks ON active_completed_tasks.experiment_id '\
|
||||
'= experiments.id AND active_completed_tasks.archived = FALSE AND active_completed_tasks.state = 1')
|
||||
.select('experiments.*')
|
||||
.select('COUNT(DISTINCT active_tasks.id) AS task_count')
|
||||
.select('COUNT(DISTINCT active_completed_tasks.id) AS completed_task_count')
|
||||
.group('experiments.id')
|
||||
end
|
||||
|
||||
def filter_records(records)
|
||||
|
@ -71,9 +80,11 @@ class ExperimentsOverviewService
|
|||
when 'ztoa'
|
||||
records.order(name: :desc)
|
||||
when 'archived_old'
|
||||
records.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) ASC'))
|
||||
records.group('projects.archived_on')
|
||||
.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) ASC'))
|
||||
when 'archived_new'
|
||||
records.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) DESC'))
|
||||
records.group('projects.archived_on')
|
||||
.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) DESC'))
|
||||
else
|
||||
records
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ module Notifications
|
|||
include HTTParty
|
||||
base_uri Rails.application.secrets.system_notifications_uri
|
||||
|
||||
SYNC_TIMESTAMP_CACHE_KEY = 'system_notifications_last_sync_timestamp'
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize
|
||||
|
@ -35,7 +37,10 @@ module Notifications
|
|||
private
|
||||
|
||||
def call_api
|
||||
last_sync = SystemNotification.last_sync_timestamp
|
||||
last_sync =
|
||||
Rails.cache.fetch(SYNC_TIMESTAMP_CACHE_KEY, expires_in: 24.hours, skip_nil: true) do
|
||||
SystemNotification.last_sync_timestamp
|
||||
end
|
||||
channel = Rails.application.secrets.system_notifications_channel
|
||||
|
||||
unless last_sync
|
||||
|
@ -70,30 +75,27 @@ module Notifications
|
|||
end
|
||||
|
||||
def save_new_notifications
|
||||
@api_call.parsed_response['notifications'].each do |sn|
|
||||
received_notifications = @api_call.parsed_response['notifications']
|
||||
return if received_notifications.blank?
|
||||
|
||||
received_notifications.each do |received_notification|
|
||||
# Save new notification if not exists or override old 1
|
||||
attrs =
|
||||
sn.slice('title',
|
||||
'description',
|
||||
'modal_title',
|
||||
'modal_body',
|
||||
'show_on_login',
|
||||
'source_id')
|
||||
.merge('source_created_at':
|
||||
Time.parse(sn['source_created_at']),
|
||||
'last_time_changed_at':
|
||||
Time.parse(sn['last_time_changed_at']))
|
||||
.symbolize_keys
|
||||
attrs = received_notification
|
||||
.slice('title', 'description', 'modal_title', 'modal_body', 'show_on_login', 'source_id')
|
||||
.merge('source_created_at': Time.zone.parse(received_notification['source_created_at']),
|
||||
'last_time_changed_at': Time.zone.parse(received_notification['last_time_changed_at']))
|
||||
.symbolize_keys
|
||||
|
||||
n = SystemNotification
|
||||
.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
|
||||
notification = SystemNotification.where(source_id: attrs[:source_id]).first_or_initialize(attrs)
|
||||
|
||||
if n.new_record?
|
||||
save_notification n
|
||||
elsif n.last_time_changed_at < attrs[:last_time_changed_at]
|
||||
n.update!(attrs)
|
||||
if notification.new_record?
|
||||
save_notification(notification)
|
||||
elsif notification.last_time_changed_at < attrs[:last_time_changed_at]
|
||||
notification.update!(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
Rails.cache.delete(SYNC_TIMESTAMP_CACHE_KEY)
|
||||
end
|
||||
|
||||
def save_notification(notification)
|
||||
|
|
|
@ -24,7 +24,8 @@ module ProtocolImporters
|
|||
s = Step.new(step_params
|
||||
.symbolize_keys
|
||||
.slice(:name, :position, :description, :tables_attributes)
|
||||
.merge(user: @user, completed: false))
|
||||
.merge(user: @user, completed: false)
|
||||
.merge(last_modified_by_id: @user.id))
|
||||
|
||||
# 'Manually' create assets here. "Accept nasted attributes" won't work for assets
|
||||
s.assets << AttachmentsBuilder.generate(step_params.deep_symbolize_keys, user: @user, team: @team)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue