Merge branch 'develop' of https://github.com/biosistemika/scinote-web into features/new_permissions

This commit is contained in:
zmagoD 2021-05-08 10:33:43 +02:00
commit c0eb38b564
222 changed files with 2799 additions and 852 deletions

View file

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

View file

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

View file

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

View file

@ -1 +1 @@
1.21.1
1.21.10

View 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

View file

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

View file

@ -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
};
}());

View file

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

View file

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

View file

@ -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();
})
})
}

View file

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

View file

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

View file

@ -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();
});
});
}

View file

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

View file

@ -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', () => {

View 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();

View file

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

View file

@ -62,6 +62,7 @@
.filter-container {
height: 36px;
margin-right: 4px;
position: relative;
width: 36px;
.current-tasks-filters {

View file

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

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -123,6 +123,7 @@
margin-top: 1px;
max-width: 500px;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
width: auto;

View file

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

View file

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

View file

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

View file

@ -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;
}
}

View file

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

View 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;
}
}
}

View file

@ -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;
}
}
}
}
}
}

View file

@ -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;
}
}
}

View file

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

View file

@ -145,3 +145,7 @@
}
}
}
.modal-backdrop.in {
opacity: .4;
}

View file

@ -102,7 +102,7 @@
width: 460px;
}
.footer {
.footer:not(.center) {
.btn:last-child {
margin-left: auto;
}

View 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;
}
}
}

View file

@ -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;
}
}
}
}

View file

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

View file

@ -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",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
class SearchController < ApplicationController
include IconsHelper
include ProjectFoldersHelper
before_action :load_vars, only: :index
def index

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
module CardsViewHelper
def cards_view_type_class(view_type)
view_type == 'table' ? 'list' : ''
end
end

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

View file

@ -0,0 +1,4 @@
global.pdfjsLib = require('pdfjs-dist');
global.pdfjsLibUtils = require('pdfjs-dist/web/pdf_viewer.js');
PdfPreview.initCanvas();

View file

@ -0,0 +1 @@
@import "~pdfjs-dist/web/pdf_viewer";

View file

@ -0,0 +1 @@
require('pdfjs-dist/build/pdf.worker.js');

View 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

View 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

View file

@ -2,6 +2,7 @@
class Activity < ApplicationRecord
include ActivityValuesModel
include GenerateNotificationModel
enum type_of: Extends::ACTIVITY_TYPES

View file

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

View 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

View file

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

View file

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

View file

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

View file

@ -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? }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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