|
@ -1,4 +1,4 @@
|
|||
FROM ruby:2.6.4-buster
|
||||
FROM ruby:2.7.2-buster
|
||||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM ruby:2.6.4-buster
|
||||
FROM ruby:2.7.2-buster
|
||||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
ARG WKHTMLTOPDF_PACKAGE_URL=https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
|
||||
|
|
6
Gemfile
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
ruby '2.6.4'
|
||||
ruby '2.7.2'
|
||||
|
||||
gem 'bootsnap', require: false
|
||||
gem 'bootstrap-sass', '~> 3.4.1'
|
||||
|
@ -12,7 +12,7 @@ gem 'devise_invitable'
|
|||
gem 'figaro'
|
||||
gem 'pg', '~> 1.1'
|
||||
gem 'pg_search' # PostgreSQL full text search
|
||||
gem 'rails', '~> 6.0.0'
|
||||
gem 'rails', '~> 6.1.1'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize', '~> 5.2'
|
||||
gem 'sassc-rails'
|
||||
|
@ -66,7 +66,7 @@ gem 'deface', '~> 1.0'
|
|||
gem 'down', '~> 5.0'
|
||||
gem 'faker' # Generate fake data
|
||||
gem 'fastimage' # Light gem to get image resolution
|
||||
gem 'httparty', '~> 0.13.1'
|
||||
gem 'httparty', '~> 0.17.3'
|
||||
gem 'i18n-js', '~> 3.6' # Localization in javascript files
|
||||
gem 'jbuilder' # JSON structures via a Builder-style DSL
|
||||
gem 'logging', '~> 2.0.0'
|
||||
|
|
544
Gemfile.lock
|
@ -17,7 +17,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/biosistemika/yomu
|
||||
revision: 063b855d672e9dd9de1e6e585b349a9b63e120c3
|
||||
revision: 3baa161e76b433ede55142dd1bb7cc04159f93f0
|
||||
branch: master
|
||||
specs:
|
||||
yomu (0.2.4)
|
||||
|
@ -42,115 +42,134 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
actioncable (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
activejob (= 6.0.3)
|
||||
activerecord (= 6.0.3)
|
||||
activestorage (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
actionmailbox (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
activejob (= 6.1.1)
|
||||
activerecord (= 6.1.1)
|
||||
activestorage (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
actionview (= 6.0.3)
|
||||
activejob (= 6.0.3)
|
||||
actionmailer (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
actionview (= 6.1.1)
|
||||
activejob (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3)
|
||||
actionview (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
actionpack (6.1.1)
|
||||
actionview (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
activerecord (= 6.0.3)
|
||||
activestorage (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
actiontext (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
activerecord (= 6.1.1)
|
||||
activestorage (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
actionview (6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_model_serializers (0.10.10)
|
||||
actionpack (>= 4.1, < 6.1)
|
||||
activemodel (>= 4.1, < 6.1)
|
||||
active_model_serializers (0.10.12)
|
||||
actionpack (>= 4.1, < 6.2)
|
||||
activemodel (>= 4.1, < 6.2)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
activejob (6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
activerecord (6.0.3)
|
||||
activemodel (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
activerecord-import (1.0.4)
|
||||
activemodel (6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
activerecord (6.1.1)
|
||||
activemodel (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
activerecord-import (1.0.7)
|
||||
activerecord (>= 3.2)
|
||||
activestorage (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
activejob (= 6.0.3)
|
||||
activerecord (= 6.0.3)
|
||||
activestorage (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
activejob (= 6.1.1)
|
||||
activerecord (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
activesupport (6.1.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
aes_key_wrap (1.0.1)
|
||||
aes_key_wrap (1.1.0)
|
||||
ajax-datatables-rails (0.3.1)
|
||||
railties (>= 3.1)
|
||||
aspector (0.14.0)
|
||||
ast (2.4.1)
|
||||
auto_strip_attributes (2.5.0)
|
||||
ast (2.4.2)
|
||||
auto_strip_attributes (2.6.0)
|
||||
activerecord (>= 4.0)
|
||||
autoprefixer-rails (9.7.0)
|
||||
autoprefixer-rails (10.2.0.0)
|
||||
execjs
|
||||
autosize-rails (1.18.17)
|
||||
rails (>= 3.1)
|
||||
awesome_print (1.8.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.211.0)
|
||||
aws-sdk-core (3.67.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.420.0)
|
||||
aws-record (2.5.0)
|
||||
aws-sdk-dynamodb (~> 1.18)
|
||||
aws-sdk-core (3.111.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.24.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sdk-dynamodb (1.58.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-rails (2.1.0)
|
||||
aws-sdk-kms (1.41.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-rails (3.6.0)
|
||||
aws-record (~> 2)
|
||||
aws-sdk-ses (~> 1)
|
||||
railties (>= 3)
|
||||
aws-sdk-s3 (1.48.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sdk-sqs (~> 1)
|
||||
aws-sessionstore-dynamodb (~> 2)
|
||||
concurrent-ruby (~> 1)
|
||||
railties (>= 5.2.0)
|
||||
aws-sdk-s3 (1.87.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-ses (1.25.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sdk-ses (1.36.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
backports (3.15.0)
|
||||
aws-sdk-sqs (1.35.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sessionstore-dynamodb (2.0.1)
|
||||
aws-sdk-dynamodb (~> 1)
|
||||
rack (~> 2)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
backports (3.20.2)
|
||||
base62 (1.0.0)
|
||||
bcrypt (3.1.13)
|
||||
better_errors (2.5.1)
|
||||
bcrypt (3.1.16)
|
||||
better_errors (2.9.1)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
bindata (2.4.4)
|
||||
binding_of_caller (0.8.0)
|
||||
bindata (2.4.8)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.4.5)
|
||||
bootsnap (1.6.0)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap-sass (3.4.1)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
|
@ -160,34 +179,33 @@ GEM
|
|||
momentjs-rails (>= 2.8.1)
|
||||
bootstrap_form (2.7.0)
|
||||
builder (3.2.4)
|
||||
bullet (6.0.2)
|
||||
bullet (6.1.3)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.0.1)
|
||||
capybara (3.29.0)
|
||||
byebug (11.1.3)
|
||||
capybara (3.35.1)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
capybara-email (3.0.1)
|
||||
capybara-email (3.0.2)
|
||||
capybara (>= 2.4, < 4.0)
|
||||
mail
|
||||
caracal (1.4.1)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (~> 1.1)
|
||||
tilt (>= 1.4)
|
||||
caracal-rails (1.0.1)
|
||||
caracal-rails (1.0.2)
|
||||
caracal (~> 1.0)
|
||||
rails (>= 3.2)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
childprocess (1.0.1)
|
||||
rake (< 13.0)
|
||||
chunky_png (1.3.11)
|
||||
coderay (1.1.2)
|
||||
childprocess (3.0.0)
|
||||
chunky_png (1.4.0)
|
||||
coderay (1.1.3)
|
||||
coffee-rails (5.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 5.2.0)
|
||||
|
@ -195,11 +213,11 @@ GEM
|
|||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.6)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
concurrent-ruby (1.1.8)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
cucumber (3.1.2)
|
||||
cucumber (3.2.0)
|
||||
builder (>= 2.1.2)
|
||||
cucumber-core (~> 3.2.0)
|
||||
cucumber-expressions (~> 6.0.1)
|
||||
|
@ -221,76 +239,80 @@ GEM
|
|||
railties (>= 4.2, < 7)
|
||||
cucumber-tag_expressions (1.1.1)
|
||||
cucumber-wire (0.0.1)
|
||||
database_cleaner (1.7.0)
|
||||
debug_inspector (0.0.3)
|
||||
deface (1.5.1)
|
||||
database_cleaner (1.8.5)
|
||||
debug_inspector (1.0.0)
|
||||
deface (1.6.1)
|
||||
nokogiri (>= 1.6)
|
||||
polyglot
|
||||
rails (>= 4.1)
|
||||
rails (>= 5.2)
|
||||
rainbow (>= 2.1.0)
|
||||
delayed_job (4.1.8)
|
||||
activesupport (>= 3.0, < 6.1)
|
||||
delayed_job_active_record (4.1.4)
|
||||
activerecord (>= 3.0, < 6.1)
|
||||
delayed_job (4.1.9)
|
||||
activesupport (>= 3.0, < 6.2)
|
||||
delayed_job_active_record (4.1.5)
|
||||
activerecord (>= 3.0, < 6.2)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
devise (4.7.1)
|
||||
devise (4.7.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_invitable (2.0.1)
|
||||
devise_invitable (2.0.3)
|
||||
actionmailer (>= 5.0)
|
||||
devise (>= 4.6)
|
||||
diff-lcs (1.3)
|
||||
discard (1.1.0)
|
||||
diff-lcs (1.4.4)
|
||||
discard (1.2.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.3.2)
|
||||
doorkeeper (5.1.1)
|
||||
docile (1.3.5)
|
||||
doorkeeper (5.4.0)
|
||||
railties (>= 5)
|
||||
down (5.0.0)
|
||||
down (5.2.0)
|
||||
addressable (~> 2.5)
|
||||
erubi (1.9.0)
|
||||
et-orbi (1.2.2)
|
||||
erubi (1.10.0)
|
||||
et-orbi (1.2.4)
|
||||
tzinfo
|
||||
execjs (2.7.0)
|
||||
factory_bot (5.0.2)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.0.2)
|
||||
factory_bot (~> 5.0.2)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.2.2)
|
||||
i18n (~> 1.6.0)
|
||||
faraday (0.15.4)
|
||||
factory_bot (6.1.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.1.0)
|
||||
factory_bot (~> 6.1.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.15.1)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (1.3.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
fastimage (2.1.7)
|
||||
ffi (1.11.1)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
fugit (1.3.3)
|
||||
ruby2_keywords
|
||||
faraday-net_http (1.0.1)
|
||||
fastimage (2.2.1)
|
||||
ffi (1.14.2)
|
||||
figaro (1.2.0)
|
||||
thor (>= 0.14.0, < 2)
|
||||
fugit (1.4.2)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.1)
|
||||
raabro (~> 1.4)
|
||||
generator (0.0.1)
|
||||
gherkin (5.1.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
hammerjs-rails (2.0.8)
|
||||
hashdiff (1.0.0)
|
||||
hashie (3.6.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
httparty (0.17.3)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.6.0)
|
||||
i18n (1.8.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.6.0)
|
||||
i18n-js (3.8.0)
|
||||
i18n (>= 0.6.6)
|
||||
image_processing (1.9.3)
|
||||
image_processing (1.12.1)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.13, < 3)
|
||||
iniparse (1.4.4)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
iniparse (1.5.0)
|
||||
jbuilder (2.11.2)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.3.5)
|
||||
jquery-rails (4.4.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
|
@ -298,14 +320,14 @@ GEM
|
|||
railties (>= 3.2.16)
|
||||
js_cookie_rails (2.2.0)
|
||||
railties (>= 3.1)
|
||||
json (1.8.6)
|
||||
json-jwt (1.11.0)
|
||||
json (2.5.1)
|
||||
json-jwt (1.13.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
json_matchers (0.11.1)
|
||||
json_schema
|
||||
json_schema (0.20.7)
|
||||
json_schema (0.20.9)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (1.5.6)
|
||||
kaminari (1.2.1)
|
||||
|
@ -321,95 +343,94 @@ GEM
|
|||
kaminari-core (= 1.2.1)
|
||||
kaminari-core (1.2.1)
|
||||
lazy_priority_queue (0.1.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
listen (3.4.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
little-plugger (1.1.4)
|
||||
logging (2.0.0)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
loofah (2.5.0)
|
||||
loofah (2.9.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
method_source (0.9.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2020.0512)
|
||||
mime-types-data (3.2020.1104)
|
||||
mimemagic (0.3.5)
|
||||
mini_magick (4.9.5)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.5.0)
|
||||
minitest (5.14.1)
|
||||
minitest (5.14.3)
|
||||
momentjs-rails (2.17.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.3.1)
|
||||
multi_json (1.13.1)
|
||||
msgpack (1.4.2)
|
||||
multi_json (1.15.0)
|
||||
multi_test (0.1.2)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
nested_form_fields (0.8.2)
|
||||
nested_form_fields (0.8.4)
|
||||
coffee-rails (>= 3.2.1)
|
||||
jquery-rails
|
||||
rails (>= 3.2.0)
|
||||
newrelic_rpm (6.6.0.358)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.11.0)
|
||||
newrelic_rpm (6.15.0)
|
||||
nio4r (2.5.4)
|
||||
nokogiri (1.11.1)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
racc (~> 1.4)
|
||||
nokogumbo (2.0.2)
|
||||
nokogumbo (2.0.4)
|
||||
nokogiri (~> 1.8, >= 1.8.4)
|
||||
oauth2 (1.4.1)
|
||||
faraday (>= 0.8, < 0.16.0)
|
||||
oauth2 (1.4.4)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
omniauth (1.9.1)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-azure-activedirectory (1.0.0)
|
||||
jwt (~> 1.5)
|
||||
omniauth (~> 1.1)
|
||||
omniauth-linkedin-oauth2 (1.0.0)
|
||||
omniauth-oauth2
|
||||
omniauth-oauth2 (1.6.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-oauth2 (1.7.1)
|
||||
oauth2 (~> 1.4)
|
||||
omniauth (>= 1.9, < 3)
|
||||
omniauth-rails_csrf_protection (0.1.2)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (>= 1.3.1)
|
||||
orm_adapter (0.5.0)
|
||||
overcommit (0.49.1)
|
||||
childprocess (>= 0.6.3, < 2.0)
|
||||
overcommit (0.57.0)
|
||||
childprocess (>= 0.6.3, < 5)
|
||||
iniparse (~> 1.4)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
parallel (1.20.1)
|
||||
parser (3.0.0.0)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.1.4)
|
||||
pg_search (2.3.0)
|
||||
activerecord (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
pg (1.2.3)
|
||||
pg_search (2.3.5)
|
||||
activerecord (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
polyglot (0.3.5)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-byebug (3.7.0)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-byebug (3.9.0)
|
||||
byebug (~> 11.0)
|
||||
pry (~> 0.10)
|
||||
pry (~> 0.13.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.1)
|
||||
puma (4.3.5)
|
||||
public_suffix (4.0.6)
|
||||
puma (5.2.0)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.1.6)
|
||||
raabro (1.4.0)
|
||||
racc (1.5.2)
|
||||
rack (2.2.3)
|
||||
rack-attack (6.1.0)
|
||||
rack-attack (6.4.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
|
@ -417,25 +438,25 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.3)
|
||||
actioncable (= 6.0.3)
|
||||
actionmailbox (= 6.0.3)
|
||||
actionmailer (= 6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
actiontext (= 6.0.3)
|
||||
actionview (= 6.0.3)
|
||||
activejob (= 6.0.3)
|
||||
activemodel (= 6.0.3)
|
||||
activerecord (= 6.0.3)
|
||||
activestorage (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3)
|
||||
rails (6.1.1)
|
||||
actioncable (= 6.1.1)
|
||||
actionmailbox (= 6.1.1)
|
||||
actionmailer (= 6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
actiontext (= 6.1.1)
|
||||
actionview (= 6.1.1)
|
||||
activejob (= 6.1.1)
|
||||
activemodel (= 6.1.1)
|
||||
activerecord (= 6.1.1)
|
||||
activestorage (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
actionview (>= 5.0.1.x)
|
||||
activesupport (>= 5.0.1.x)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
|
@ -448,54 +469,53 @@ GEM
|
|||
rails (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (6.0.3)
|
||||
actionpack (= 6.0.3)
|
||||
activesupport (= 6.0.3)
|
||||
railties (6.1.1)
|
||||
actionpack (= 6.1.1)
|
||||
activesupport (= 6.1.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
thor (~> 1.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.3)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
rake (13.0.3)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rdoc (6.2.0)
|
||||
recaptcha (5.1.0)
|
||||
rdoc (6.3.0)
|
||||
recaptcha (5.6.0)
|
||||
json
|
||||
regexp_parser (1.6.0)
|
||||
responders (3.0.0)
|
||||
regexp_parser (2.0.3)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rexml (3.2.4)
|
||||
rgl (0.5.6)
|
||||
rgl (0.5.7)
|
||||
lazy_priority_queue (~> 0.1.0)
|
||||
stream (~> 0.5.2)
|
||||
roo (2.8.2)
|
||||
stream (~> 0.5.3)
|
||||
roo (2.8.3)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 1.2.1, < 2.0.0)
|
||||
rotp (6.0.0)
|
||||
addressable (~> 2.7)
|
||||
rqrcode (1.1.2)
|
||||
rubyzip (>= 1.3.0, < 3.0.0)
|
||||
rotp (6.2.0)
|
||||
rqrcode (1.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 0.1)
|
||||
rqrcode_core (0.1.2)
|
||||
rspec-core (3.8.2)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.4)
|
||||
rqrcode_core (~> 0.2)
|
||||
rqrcode_core (0.2.0)
|
||||
rspec-core (3.10.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-expectations (3.10.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-mocks (3.8.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-mocks (3.10.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-rails (4.0.0.beta2)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-rails (4.0.2)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.8)
|
||||
rspec-expectations (~> 3.8)
|
||||
rspec-mocks (~> 3.8)
|
||||
rspec-support (~> 3.8)
|
||||
rspec-support (3.8.2)
|
||||
rspec-core (~> 3.10)
|
||||
rspec-expectations (~> 3.10)
|
||||
rspec-mocks (~> 3.10)
|
||||
rspec-support (~> 3.10)
|
||||
rspec-support (3.10.1)
|
||||
rubocop (0.83.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
|
@ -503,25 +523,26 @@ GEM
|
|||
rexml
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-performance (1.5.1)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.4.0)
|
||||
rubocop-performance (1.7.1)
|
||||
rubocop (>= 0.82.0)
|
||||
rubocop-rails (2.6.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-graphviz (1.2.4)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-vips (2.0.15)
|
||||
rubocop (>= 0.82.0)
|
||||
ruby-graphviz (1.2.5)
|
||||
rexml
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-vips (2.0.17)
|
||||
ffi (~> 1.9)
|
||||
ruby_dep (1.5.0)
|
||||
ruby2_keywords (0.0.4)
|
||||
rubyzip (1.3.0)
|
||||
rufus-scheduler (3.6.0)
|
||||
rufus-scheduler (3.7.0)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
safe_yaml (1.0.5)
|
||||
sanitize (5.2.1)
|
||||
sanitize (5.2.3)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.8.0)
|
||||
nokogumbo (~> 2.0)
|
||||
sassc (2.2.1)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
|
@ -529,55 +550,56 @@ GEM
|
|||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
scenic (1.5.1)
|
||||
scenic (1.5.4)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
sdoc (1.0.0)
|
||||
sdoc (1.1.0)
|
||||
rdoc (>= 5.0)
|
||||
selenium-webdriver (3.142.4)
|
||||
childprocess (>= 0.5, < 3.0)
|
||||
rubyzip (~> 1.2, >= 1.2.2)
|
||||
shoulda-matchers (4.1.2)
|
||||
selenium-webdriver (3.142.7)
|
||||
childprocess (>= 0.5, < 4.0)
|
||||
rubyzip (>= 1.2.2)
|
||||
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.17.0)
|
||||
simplecov (0.21.2)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.2)
|
||||
spinjs-rails (1.4)
|
||||
rails (>= 3.1)
|
||||
sprockets (4.0.0)
|
||||
sprockets (4.0.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
stream (0.5.2)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
timecop (0.9.1)
|
||||
tinymce-rails (4.9.10)
|
||||
stream (0.5.3)
|
||||
generator
|
||||
thor (1.1.0)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.2)
|
||||
tinymce-rails (4.9.11)
|
||||
railties (>= 3.1.1)
|
||||
turbolinks (5.1.1)
|
||||
turbolinks-source (~> 5.1)
|
||||
turbolinks-source (5.2.0)
|
||||
tzinfo (1.2.7)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
underscore-rails (1.8.3)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webmock (3.7.2)
|
||||
uniform_notifier (1.13.2)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webmock (3.11.1)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
@ -585,7 +607,7 @@ GEM
|
|||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
whacamole (1.2.0)
|
||||
|
@ -594,7 +616,7 @@ GEM
|
|||
wkhtmltopdf-heroku (2.12.5.0)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.3.0)
|
||||
zeitwerk (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -639,7 +661,7 @@ DEPENDENCIES
|
|||
fastimage
|
||||
figaro
|
||||
hammerjs-rails
|
||||
httparty (~> 0.13.1)
|
||||
httparty (~> 0.17.3)
|
||||
i18n-js (~> 3.6)
|
||||
image_processing (~> 1.2)
|
||||
jbuilder
|
||||
|
@ -671,7 +693,7 @@ DEPENDENCIES
|
|||
puma
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (~> 6.0.0)
|
||||
rails (~> 6.1.1)
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
rails_autolink (~> 1.1, >= 1.1.6)
|
||||
|
@ -712,7 +734,7 @@ DEPENDENCIES
|
|||
yomu!
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.4p104
|
||||
ruby 2.7.2p137
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
2.1.4
|
||||
|
|
Before Width: | Height: | Size: 538 KiB |
Before Width: | Height: | Size: 531 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 183 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 530 KiB |
Before Width: | Height: | Size: 73 KiB |
|
@ -1,18 +0,0 @@
|
|||
# Content for tables used in demo project.
|
||||
|
||||
module1:
|
||||
experimental_design_table: '{"data":[["group/time","1 dpi","6 dpi","",""],["PVYNTN","1","1","",""],["mock","1","1","",""],["","","","",""]]}'
|
||||
|
||||
module2:
|
||||
samples_table: '{"data":[["Name","Sample type"],["Mock 1dpi","potatoe leaves"],["Mock 6dpi","potatoe leaves"],["PVY NTN 1dpi","potatoe leaves"],["PVY NTN 6dpi","potatoe leaves"]]}'
|
||||
module3:
|
||||
nanodrop: '{"data":[["Sample name","ng/ul ","260/280 ","260/230 "],["PVY NTN 6dpi","1.05","1.39","1.61"],["PVY NTN 1dpi","320.52","2.16","2.26"],["Mock 6dpi","906.19","2.15","2.26"],["Mock 1dpi","940.22","2.17","2.27"]]}'
|
||||
module4:
|
||||
volumes: '{"data":[["Amount of starting material (mg)","Volume of Buffer RLT (µl)"],["<20","350"],["20 - 30","600"]]}'
|
||||
dna_q: '{"data":[["Sample 1","Quantity "],["1","0,2"],["2","0,5"],["3","0,4"],["4","1,1"],["5","12,5"],["6","0,05"],["7","0"],["8","0"],["9","0"],["10","0"],["11","0"],["12","4,7"],["13","2,3"],["14","5,6"],["15","2,6"],["16","8,9"],["17","1,7"],["18","1"]]}'
|
||||
module6:
|
||||
mastermix: '{"data":[["Date:","","","","","","","","","","","",""],["Reaction volume:","20 ul","","","","","","","","","","",""],["UMM/reaction:","10 µl","","","","","","","","","","",""],["sample/reaction:","6 µl","","","","","","","","","","",""],["","","","","","","","","","","","",""],["Chemistry","TaqMan","","","SYBR Green I","","","TaqMan","","","SYBR Green I","",""],["Gene","cox","","","cab4","","","PVY-NTN","","","PR-1","",""],["","conc.*","final conc.**","µl/reaction","conc.*","final conc.**","µl/reaction","conc.*","final conc.**","µl/reaction","conc.*","final conc.**","µl/reaction"],["Probe","10 µM","150 nM","0.3","/","/","/","5 µM","150 nM","0.6","/","/","/"],["FP","30 µM","300 nM","0.2","30 µM","300 nM ","0.2","10 µM","250 nM","0.5","30 µM","300 nM ","0.2"],["RP","30 µM","300 nM","0.2","30 µM","300 nM ","0.2","10 µM","250 nM","0.5","30 µM","300 nM ","0.2"]]}'
|
||||
distribution: '{"data":[["%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t","%{sample0} (100x)\t",null,null,"Mix smpl (10x)\t","Mix smpl (10x)\t"],["%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t","%{sample0} (1000x)\t",null,null,"Mix smpl (100x)\t","Mix smpl (100x)\t"],["%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t","%{sample1} (100x)\t",null,null,"Mix smpl (1000x)\t","Mix smpl (1000x)\t"],["%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t","%{sample1} (1000x)\t",null,null,"Mix smpl (10000x)\t","Mix smpl (10000x)\t"],["%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t","%{sample2} (100x)\t",null,null,"NTC1","NTC2"],["%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t","%{sample2} (1000x)\t",null,null,"NTC1","NTC2"],["%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t","%{sample3} (100x)\t",null,null,"NTC1","NTC2"],["%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t","%{sample3} (1000x)\t",null,null,"NTC1","NTC2"],[null,null,null,null,null,null,null,null,null,null,null,null]]}'
|
||||
qpcr_raw_data: '{"data":[["Sample 1","Quantity ","Column 3","Column 4"],["2","0,2","0,4","0,08"],["2","0,5","1","0,5"],["3","0,4","1,2","0,48"],["4","1,1","4,4","4,84"],["5","12,5","62,5","781,25"],["6","0,05","0,3","0,015"],["7","0","4","0"],["8","0","0","0"],["9","0","0","0"],["10","0","0","0"],["11","0","0","0"],["12","4,7","56,4","265,08"],["13","2,3","30","69"],["14","5,6","78,4","439,04"],["15","2,6","39","101,4"],["16","8,9","142,4","1267,36"],["17","1,7","28,9","49,13"],["18","1","18","18"],["","","",""]]}'
|
||||
module7:
|
||||
qpcr_results: '{"data":[["Sample 1","Quantity ",null,null,null],["1","0,2",null,null,null],["2","0,5",null,null,null],["3","0,4",null,null,null],["4","1,1",null,null,null],["5","12,5",null,null,null],["6","0,05",null,null,null],["7","0",null,null,null],["8","0",null,null,null],["9","0",null,null,null],["10","0",null,null,null],["11","0",null,null,null],["12","4,7",null,null,null],["13","2,3",null,null,null],["14","5,6",null,null,null],["15","2,6",null,null,null],["16","8,9",null,null,null],["17","1,7",null,null,null],["18","1",null,null,null]]}'
|
Before Width: | Height: | Size: 16 KiB |
|
@ -1,54 +1,89 @@
|
|||
(function(){
|
||||
/* globals renderFormError animateSpinner */
|
||||
|
||||
(function() {
|
||||
function clearModal(id) {
|
||||
// Completely remove modal when it gets closed
|
||||
$(id).on('hidden.bs.modal', function() {
|
||||
$(id).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Reload after successfully updated experiment
|
||||
function validateExperimentForm(element) {
|
||||
if ($(element)) {
|
||||
let form = $(element).find('form');
|
||||
form.on('ajax:success', function(e, data) {
|
||||
animateSpinner(form, true);
|
||||
if (element.match(/#new-experiment-modal/)) {
|
||||
window.location.replace(data.path);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}).on('ajax:error', function(e, error) {
|
||||
let msg = JSON.parse(error.responseText);
|
||||
if ('name' in msg) {
|
||||
renderFormError(e, $(element).find('#experiment-name'), msg.name.toString(), true);
|
||||
} else if ('description' in msg) {
|
||||
renderFormError(e, $(element).find('#experiment-description'), msg.description.toString(), true);
|
||||
} else {
|
||||
renderFormError(e, $(element).find('#experiment-name'), error.statusText, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Validates move action
|
||||
function validateMoveModal(modal) {
|
||||
if (modal.match(/#move-experiment-modal-[0-9]*/)) {
|
||||
let form = $(modal).find('form');
|
||||
form.on('ajax:success', function(e, data) {
|
||||
animateSpinner(form, true);
|
||||
window.location.replace(data.path);
|
||||
}).on('ajax:error', function(e, error) {
|
||||
form.clearFormErrors();
|
||||
let msg = JSON.parse(error.responseText);
|
||||
renderFormError(e, form.find('#experiment_project_id'), msg.message.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create ajax hook on given 'element', which should return modal with 'id' =>
|
||||
// show that modal
|
||||
function initializeModal(element, id){
|
||||
|
||||
function initializeModal(element, id) {
|
||||
// Initialize new experiment modal listener
|
||||
$(element)
|
||||
.on("ajax:beforeSend", function(){
|
||||
$(element).on('ajax:beforeSend', function() {
|
||||
animateSpinner();
|
||||
})
|
||||
.on("ajax:success", function(e, data){
|
||||
}).on('ajax:success', function(e, data) {
|
||||
$('body').append($.parseHTML(data.html));
|
||||
$(id).modal('show',{
|
||||
$(id).modal('show', {
|
||||
backdrop: true,
|
||||
keyboard: false,
|
||||
keyboard: false
|
||||
});
|
||||
validateMoveModal(id);
|
||||
clearModal($(id));
|
||||
validateExperimentForm(id);
|
||||
})
|
||||
.on("ajax:error", function() {
|
||||
}).on('ajax:error', function() {
|
||||
animateSpinner(null, false);
|
||||
// TODO
|
||||
})
|
||||
.on("ajax:complete", function(){
|
||||
}).on('ajax:complete', function() {
|
||||
animateSpinner(null, false);
|
||||
$(id).find('.selectpicker').selectpicker();
|
||||
});
|
||||
}
|
||||
|
||||
function clearModal(id) {
|
||||
//Completely remove modal when it gets closed
|
||||
$(id).on('hidden.bs.modal', function() {
|
||||
$(id).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize dropdown actions on experiment:
|
||||
// - edit
|
||||
// - clone
|
||||
function initializeDropdownActions(){
|
||||
function initializeDropdownActions() {
|
||||
// { buttonClass: modalName } mappings
|
||||
// click on buttonClass summons modalName dialog
|
||||
modals = {
|
||||
let modals = {
|
||||
'.edit-experiment': '#edit-experiment-modal-',
|
||||
'.clone-experiment': '#clone-experiment-modal-',
|
||||
'.move-experiment': '#move-experiment-modal-'
|
||||
};
|
||||
|
||||
$.each($(".dropdown-experiment-actions"), function(){
|
||||
$.each($('.dropdown-experiment-actions'), function() {
|
||||
var $dropdown = $(this);
|
||||
$.each(modals, function(buttonClass, modalName) {
|
||||
var id = modalName + $dropdown.data('id');
|
||||
|
@ -57,64 +92,11 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Validates move action
|
||||
function validateMoveModal(modal){
|
||||
if ( modal.match(/#move-experiment-modal-[0-9]*/) ) {
|
||||
var form = $(modal).find("form");
|
||||
form
|
||||
.on('ajax:success', function(e, data) {
|
||||
animateSpinner(form, true);
|
||||
window.location.replace(data.path);
|
||||
})
|
||||
.on('ajax:error', function(e, error) {
|
||||
form.clearFormErrors();
|
||||
var msg = JSON.parse(error.responseText);
|
||||
renderFormError(e,
|
||||
form.find("#experiment_project_id"),
|
||||
msg.message.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reload after successfully updated experiment
|
||||
function validateExperimentForm(element){
|
||||
if ( $(element) ) {
|
||||
var form = $(element).find("form");
|
||||
form
|
||||
.on('ajax:success' , function(e, data){
|
||||
animateSpinner(form, true);
|
||||
if ( element.match(/#new-experiment-modal/) ) {
|
||||
window.location.replace(data.path);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
.on('ajax:error', function(e, error){
|
||||
var msg = JSON.parse(error.responseText);
|
||||
if ( 'name' in msg ) {
|
||||
renderFormError(e,
|
||||
$(element).find("#experiment-name"),
|
||||
msg.name.toString(),
|
||||
true);
|
||||
} else if ( 'description' in msg ) {
|
||||
renderFormError(e,
|
||||
$(element).find("#experiment-description"),
|
||||
msg.description.toString(),
|
||||
true);
|
||||
} else {
|
||||
renderFormError(e,
|
||||
$(element).find("#experiment-name"),
|
||||
error.statusText,
|
||||
true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Initialize no description edit link
|
||||
function initEditNoDescription(){
|
||||
function initEditNoDescription() {
|
||||
var modal = '#edit-experiment-modal-';
|
||||
$.each($('.experiment-no-description'), function() {
|
||||
var id = modal + $(this).data("id");
|
||||
var id = modal + $(this).data('id');
|
||||
initializeModal($(this), id);
|
||||
});
|
||||
}
|
||||
|
@ -140,4 +122,4 @@
|
|||
|
||||
// init
|
||||
initEditNoDescription();
|
||||
})();
|
||||
}());
|
||||
|
|
|
@ -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 */
|
||||
/* 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)
|
||||
|
@ -237,6 +273,19 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize view project users modal remote loading.
|
||||
function initViewProjectUsersLink() {
|
||||
$(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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize reloading manage user modal content after posting new
|
||||
// user.
|
||||
function initAddUserForm() {
|
||||
|
@ -312,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');
|
||||
|
@ -319,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)) {
|
||||
|
@ -485,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');
|
||||
|
@ -507,9 +562,9 @@
|
|||
let projectsPageSelector = '.projects-index';
|
||||
|
||||
// list/cards switch
|
||||
$(projectsPageSelector).on('click', '.projects-view-mode', function() {
|
||||
$(projectsPageSelector).on('click', '.cards-switch', function() {
|
||||
let $btn = $(this);
|
||||
$('.projects-view-mode').removeClass('active');
|
||||
$('.cards-switch').removeClass('active');
|
||||
if ($btn.hasClass('view-switch-cards')) {
|
||||
$(cardsWrapper).removeClass('list');
|
||||
} else if ($btn.hasClass('view-switch-list')) {
|
||||
|
@ -539,63 +594,36 @@
|
|||
});
|
||||
}
|
||||
|
||||
function appliedFiltersMark() {
|
||||
let filtersEnabled = projectsViewSearch
|
||||
|| createdOnFromFilter
|
||||
|| createdOnToFilter
|
||||
|| (membersFilter && membersFilter.length !== 0)
|
||||
|| lookInsideFolders
|
||||
|| archivedOnFromFilter
|
||||
|| archivedOnToFilter;
|
||||
if (filtersEnabled) {
|
||||
$('.filter-container').addClass('filters-applied');
|
||||
} else {
|
||||
$('.filter-container').removeClass('filters-applied');
|
||||
function selectDate($field) {
|
||||
var datePicker = $field.data('DateTimePicker');
|
||||
if (datePicker && datePicker.date()) {
|
||||
return datePicker.date()._d.toUTCString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function initProjectsFilters() {
|
||||
function applyFilters() {
|
||||
let teamId = $('.projects-filters').data('team-id');
|
||||
projectsViewSearch = $('#textSearchFilterInput').closest('.select-block').find('input[type=text]').val();
|
||||
try {
|
||||
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
if (!Array.isArray(recentSearchKeywords)) recentSearchKeywords = [];
|
||||
if (recentSearchKeywords.indexOf(projectsViewSearch) !== -1) {
|
||||
recentSearchKeywords.splice(recentSearchKeywords.indexOf(projectsViewSearch), 1);
|
||||
}
|
||||
if (recentSearchKeywords.length > 4) {
|
||||
recentSearchKeywords = recentSearchKeywords.slice(0, 4);
|
||||
}
|
||||
recentSearchKeywords.unshift(projectsViewSearch);
|
||||
localStorage.setItem(storagePath, JSON.stringify(recentSearchKeywords));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
$('#applyProjectFiltersButton').closest('.dropdown').removeClass('open');
|
||||
|
||||
createdOnFromFilter = $createdOnFromFilter.val();
|
||||
createdOnToFilter = $createdOnToFilter.val();
|
||||
membersFilter = dropdownSelector.getValues($('.members-filter'));
|
||||
lookInsideFolders = $foldersCB.prop('checked') ? 'true' : '';
|
||||
archivedOnFromFilter = $archivedOnFromFilter.val();
|
||||
archivedOnToFilter = $archivedOnToFilter.val();
|
||||
|
||||
appliedFiltersMark();
|
||||
refreshCurrentView();
|
||||
}
|
||||
|
||||
var $filterDropdown = filterDropdown.init();
|
||||
let $projectsFilter = $('.projects-index .projects-filters');
|
||||
let $membersFilter = $('.members-filter', $projectsFilter);
|
||||
let $foldersCB = $('#folder_search', $projectsFilter);
|
||||
let $createdOnFromFilter = $('#createdOnFromDate', $projectsFilter);
|
||||
let $createdOnToFilter = $('#createdOnToDate', $projectsFilter);
|
||||
let $archivedOnFromFilter = $('#archivedOnFromDate', $projectsFilter);
|
||||
let $archivedOnToFilter = $('#archivedOnToDate', $projectsFilter);
|
||||
let $createdOnFromFilter = $('.created-on-filter .from-date', $projectsFilter);
|
||||
let $createdOnToFilter = $('.created-on-filter .to-date', $projectsFilter);
|
||||
let $archivedOnFromFilter = $('.archived-on-filter .from-date', $projectsFilter);
|
||||
let $archivedOnToFilter = $('.archived-on-filter .to-date', $projectsFilter);
|
||||
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
|
||||
|
||||
function appliedFiltersMark() {
|
||||
let filtersEnabled = projectsViewSearch
|
||||
|| createdOnFromFilter
|
||||
|| createdOnToFilter
|
||||
|| (membersFilter && membersFilter.length !== 0)
|
||||
|| lookInsideFolders
|
||||
|| archivedOnFromFilter
|
||||
|| archivedOnToFilter;
|
||||
filterDropdown.toggleFilterMark($filterDropdown, filtersEnabled);
|
||||
}
|
||||
|
||||
dropdownSelector.init($membersFilter, {
|
||||
optionClass: 'checkbox-icon users-dropdown-list',
|
||||
optionLabel: (data) => {
|
||||
|
@ -608,66 +636,30 @@
|
|||
tagClass: 'users-dropdown-list'
|
||||
});
|
||||
|
||||
$textFilter.click((e) => {
|
||||
e.stopPropagation();
|
||||
$('#textSearchFilterHistory').toggle();
|
||||
$textFilter.closest('.dropdown').toggleClass('open');
|
||||
}).on('input', () => {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '.projects-search-keyword', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$textFilter.val($(this).data('keyword'));
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '#folderSearchInfoBtn', function(e) {
|
||||
e.stopPropagation();
|
||||
$('#folderSearchInfo').toggle();
|
||||
});
|
||||
|
||||
$('.project-filters-dropdown').on('show.bs.dropdown', function() {
|
||||
let teamId = $projectsFilter.data('team-id');
|
||||
$('#textSearchFilterHistory').find('li').remove();
|
||||
|
||||
try {
|
||||
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
$.each(recentSearchKeywords, function(i, keyword) {
|
||||
$('#textSearchFilterHistory').append($(
|
||||
`<li class="dropdown-item">
|
||||
<a class="projects-search-keyword" href="#" data-keyword="${keyword}">
|
||||
<i class="fas fa-history"></i>
|
||||
<span class="keyword-text">${keyword}</span>
|
||||
</a>
|
||||
</li>`
|
||||
));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}).on('hide.bs.dropdown', function() {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
$('#folderSearchInfo').hide();
|
||||
applyFilters();
|
||||
$projectsFilter.on('click', '#folder_search', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$('#applyProjectFiltersButton').click((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
applyFilters();
|
||||
$filterDropdown.on('filter:apply', function() {
|
||||
createdOnFromFilter = selectDate($createdOnFromFilter);
|
||||
createdOnToFilter = selectDate($createdOnToFilter);
|
||||
membersFilter = dropdownSelector.getValues($('.members-filter'));
|
||||
lookInsideFolders = $foldersCB.prop('checked') ? 'true' : '';
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter);
|
||||
archivedOnToFilter = selectDate($archivedOnToFilter);
|
||||
projectsViewSearch = $textFilter.val();
|
||||
|
||||
appliedFiltersMark();
|
||||
refreshCurrentView();
|
||||
});
|
||||
|
||||
// Clear filters
|
||||
$('.clear-button', $projectsFilter).click((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
$filterDropdown.on('filter:clear', function() {
|
||||
dropdownSelector.clearData($membersFilter);
|
||||
if ($createdOnFromFilter.data('DateTimePicker')) $createdOnFromFilter.data('DateTimePicker').clear();
|
||||
if ($createdOnToFilter.data('DateTimePicker')) $createdOnToFilter.data('DateTimePicker').clear();
|
||||
|
@ -678,15 +670,11 @@
|
|||
});
|
||||
|
||||
// Prevent filter window close
|
||||
$($projectsFilter).click((e) => {
|
||||
if (!$(e.target).is('input,a')) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
dropdownSelector.closeDropdown($membersFilter);
|
||||
$('#folderSearchInfo').hide();
|
||||
}
|
||||
$filterDropdown.on('filter:clickBody', function() {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
dropdownSelector.closeDropdown($membersFilter);
|
||||
$('#folderSearchInfo').hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -705,7 +693,9 @@
|
|||
initManageUsersModal();
|
||||
initExportProjectsModal();
|
||||
initExportProjects();
|
||||
initDeleteFoldersToolbarButton();
|
||||
initArchiveRestoreToolbarButtons();
|
||||
initViewProjectUsersLink();
|
||||
initManageProjectUsersLink();
|
||||
initAddUserForm();
|
||||
initRemoveUserLinks();
|
||||
|
@ -756,4 +746,8 @@
|
|||
}
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
loadCardsView: loadCardsView
|
||||
};
|
||||
}());
|
||||
|
|
|
@ -1,5 +1,276 @@
|
|||
/* global animateSpinner filterDropdown Sidebar Turbolinks HelperModule */
|
||||
(function() {
|
||||
function init() {
|
||||
const PERMISSIONS = ['editable', 'archivable', 'restorable', 'moveable', 'duplicable'];
|
||||
var cardsWrapper = '#cardsWrapper';
|
||||
var experimentsPage = '#projectShowWrapper';
|
||||
|
||||
var selectedExperiments = [];
|
||||
|
||||
let experimentsCurrentSort;
|
||||
let experimentsViewSearch;
|
||||
let startedOnFromFilter;
|
||||
let startedOnToFilter;
|
||||
let modifiedOnFromFilter;
|
||||
let modifiedOnToFilter;
|
||||
let archivedOnFromFilter;
|
||||
let archivedOnToFilter;
|
||||
|
||||
|
||||
function checkActionPermission(permission) {
|
||||
return selectedExperiments.every(function(experimentId) {
|
||||
return $(`.experiment-card[data-id="${experimentId}"]`).data(permission);
|
||||
});
|
||||
}
|
||||
|
||||
function updateExperimentsToolbar() {
|
||||
let experimentsToolbar = $('#projectShowToolbar');
|
||||
let toolbarVisible = false;
|
||||
|
||||
if (selectedExperiments.length === 0) {
|
||||
experimentsToolbar.find('.single-object-action, .multiple-object-action').addClass('hidden');
|
||||
}
|
||||
|
||||
if (selectedExperiments.length === 1) {
|
||||
experimentsToolbar.find('.single-object-action, .multiple-object-action').removeClass('hidden');
|
||||
} else if (selectedExperiments.length > 1) {
|
||||
experimentsToolbar.find('.single-object-action').addClass('hidden');
|
||||
experimentsToolbar.find('.multiple-object-action').removeClass('hidden');
|
||||
}
|
||||
PERMISSIONS.forEach((permission) => {
|
||||
if (!checkActionPermission(permission)) {
|
||||
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('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');
|
||||
});
|
||||
|
||||
$(experimentsPage).on('click', '.archive-switch', function() {
|
||||
Turbolinks.visit($(this).data('url'));
|
||||
});
|
||||
}
|
||||
|
||||
function loadCardsView() {
|
||||
var viewContainer = $(cardsWrapper);
|
||||
$.ajax({
|
||||
url: viewContainer.data('experiments-cards-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
view_mode: $(experimentsPage).data('view-mode'),
|
||||
sort: experimentsCurrentSort,
|
||||
search: experimentsViewSearch,
|
||||
created_on_from: startedOnFromFilter,
|
||||
created_on_to: startedOnToFilter,
|
||||
updated_on_from: modifiedOnFromFilter,
|
||||
updated_on_to: modifiedOnToFilter,
|
||||
archived_on_from: archivedOnFromFilter,
|
||||
archived_on_to: archivedOnToFilter
|
||||
},
|
||||
success: function(data) {
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCurrentView() {
|
||||
loadCardsView();
|
||||
Sidebar.reload({
|
||||
sort: experimentsCurrentSort,
|
||||
view_mode: $(experimentsPage).data('view-mode')
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
let $experimentsFilter = $('.experiments-filters');
|
||||
let $startedOnFromFilter = $('.started-on-filter .from-date', $experimentsFilter);
|
||||
let $startedOnToFilter = $('.started-on-filter .to-date', $experimentsFilter);
|
||||
let $modifiedOnFromFilter = $('.modified-on-filter .from-date', $experimentsFilter);
|
||||
let $modifiedOnToFilter = $('.modified-on-filter .to-date', $experimentsFilter);
|
||||
let $archivedOnFromFilter = $('.archived-on-filter .from-date', $experimentsFilter);
|
||||
let $archivedOnToFilter = $('.archived-on-filter .to-date', $experimentsFilter);
|
||||
let $textFilter = $('#textSearchFilterInput', $experimentsFilter);
|
||||
|
||||
function appliedFiltersMark() {
|
||||
let filtersEnabled = experimentsViewSearch
|
||||
|| startedOnFromFilter
|
||||
|| startedOnToFilter
|
||||
|| modifiedOnFromFilter
|
||||
|| modifiedOnToFilter
|
||||
|| archivedOnFromFilter
|
||||
|| archivedOnToFilter;
|
||||
filterDropdown.toggleFilterMark($filterDropdown, filtersEnabled);
|
||||
}
|
||||
|
||||
$filterDropdown.on('filter:apply', function() {
|
||||
startedOnFromFilter = selectDate($startedOnFromFilter);
|
||||
startedOnToFilter = selectDate($startedOnToFilter);
|
||||
modifiedOnFromFilter = selectDate($modifiedOnFromFilter);
|
||||
modifiedOnToFilter = selectDate($modifiedOnToFilter);
|
||||
archivedOnFromFilter = selectDate($archivedOnFromFilter);
|
||||
archivedOnToFilter = selectDate($archivedOnToFilter);
|
||||
experimentsViewSearch = $textFilter.val();
|
||||
appliedFiltersMark();
|
||||
refreshCurrentView();
|
||||
});
|
||||
|
||||
// Clear filters
|
||||
$filterDropdown.on('filter:clear', function() {
|
||||
if ($startedOnFromFilter.data('DateTimePicker')) $startedOnFromFilter.data('DateTimePicker').clear();
|
||||
if ($startedOnToFilter.data('DateTimePicker')) $startedOnToFilter.data('DateTimePicker').clear();
|
||||
if ($modifiedOnFromFilter.data('DateTimePicker')) $modifiedOnFromFilter.data('DateTimePicker').clear();
|
||||
if ($modifiedOnToFilter.data('DateTimePicker')) $modifiedOnToFilter.data('DateTimePicker').clear();
|
||||
if ($archivedOnFromFilter.data('DateTimePicker')) $archivedOnFromFilter.data('DateTimePicker').clear();
|
||||
if ($archivedOnToFilter.data('DateTimePicker')) $archivedOnToFilter.data('DateTimePicker').clear();
|
||||
$textFilter.val('');
|
||||
});
|
||||
}
|
||||
|
||||
function initSorting() {
|
||||
$('#sortMenuDropdown a').click(function() {
|
||||
if (experimentsCurrentSort !== $(this).data('sort')) {
|
||||
$('#sortMenuDropdown a').removeClass('selected');
|
||||
experimentsCurrentSort = $(this).data('sort');
|
||||
refreshCurrentView();
|
||||
$(this).addClass('selected');
|
||||
$('#sortMenu').dropdown('toggle');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initExperimentsSelector() {
|
||||
$(cardsWrapper).on('click', '.experiment-card-selector', function() {
|
||||
let card = $(this).closest('.experiment-card');
|
||||
let experimentId = card.data('id');
|
||||
// Determine whether ID is in the list of selected project IDs
|
||||
let index = $.inArray(experimentId, selectedExperiments);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected project IDs
|
||||
if (this.checked && index === -1) {
|
||||
$(this).closest('.experiment-card').addClass('selected');
|
||||
selectedExperiments.push(experimentId);
|
||||
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
|
||||
} else if (!this.checked && index !== -1) {
|
||||
$(this).closest('.experiment-card').removeClass('selected');
|
||||
selectedExperiments.splice(index, 1);
|
||||
}
|
||||
updateExperimentsToolbar();
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
let buttonForm = $(this);
|
||||
buttonForm.find('input[name="experiments_ids[]"]').remove();
|
||||
selectedExperiments.forEach(function(id) {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'experiments_ids[]',
|
||||
value: id
|
||||
}).appendTo(buttonForm);
|
||||
});
|
||||
})
|
||||
.on('ajax:success', '.archive-experiments-form, .restore-experiments-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
// Project saved, reload view
|
||||
refreshCurrentView();
|
||||
})
|
||||
.on('ajax:error', '.archive-experiments-form, .restore-experiments-form', function(ev, data) {
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
$('.workflowimg-container').each(function() {
|
||||
let container = $(this);
|
||||
if (container.data('workflowimg-present') === false) {
|
||||
|
@ -20,5 +291,35 @@
|
|||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
$('#content-wrapper').on('ajax:success', '.experiment-action-link', function(ev, data) {
|
||||
appendActionModal($(data.html));
|
||||
});
|
||||
|
||||
$('#content-wrapper')
|
||||
.on('ajax:beforeSend', '.experiment-action-form', function() {
|
||||
animateSpinner();
|
||||
})
|
||||
.on('ajax:success', '.experiment-action-form', function() {
|
||||
$(this).closest('.modal').modal('hide');
|
||||
refreshCurrentView();
|
||||
animateSpinner(null, false);
|
||||
})
|
||||
.on('ajax:error', '.experiment-action-form', function(ev, data) {
|
||||
animateSpinner(null, false);
|
||||
$(this).renderFormErrors('experiment', data.responseJSON);
|
||||
});
|
||||
|
||||
initExperimentsFilters();
|
||||
initSorting();
|
||||
loadCardsView();
|
||||
initProjectsViewModeSwitch();
|
||||
initExperimentsSelector();
|
||||
initArchiveRestoreToolbarButtons();
|
||||
initEditMoveDuplicateToolbarButton();
|
||||
initNewExperimentToolbarButton();
|
||||
initSelectAllCheckbox();
|
||||
}
|
||||
|
||||
init();
|
||||
}());
|
||||
|
|
116
app/assets/javascripts/sitewide/filter_dropdown.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
var filterDropdown = (function() {
|
||||
var $filterContainer = '';
|
||||
|
||||
function initClearButton() {
|
||||
$('.clear-button', $filterContainer).click(function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$filterContainer.trigger('filter:clear');
|
||||
});
|
||||
}
|
||||
|
||||
function preventDropdownClose() {
|
||||
$filterContainer.on('click', '.dropdown-menu', function(e) {
|
||||
if (!$(e.target).is('input,a')) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$('#textSearchFilterInput', $filterContainer).closest('.dropdown').removeClass('open');
|
||||
$filterContainer.trigger('filter:clickBody');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initSearchField() {
|
||||
var $textFilter = $('#textSearchFilterInput', $filterContainer);
|
||||
|
||||
$filterContainer.on('show.bs.dropdown', function() {
|
||||
let $filterDropdown = $filterContainer.find('.dropdown-menu');
|
||||
let teamId = $filterDropdown.data('team-id');
|
||||
$('#textSearchFilterHistory').find('li').remove();
|
||||
try {
|
||||
let storagePath = `${$filterDropdown.data('search-field-history-key')}/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
$.each(recentSearchKeywords, function(i, keyword) {
|
||||
$('#textSearchFilterHistory').append(
|
||||
$(`<li class="dropdown-item">
|
||||
<a class="projects-search-keyword" href="#" data-keyword="${keyword}">
|
||||
<i class="fas fa-history"></i>
|
||||
<span class="keyword-text">${keyword}</span>
|
||||
</a>
|
||||
</li>`)
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}).on('hide.bs.dropdown', function() {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$('.apply-filters', $filterContainer).click();
|
||||
});
|
||||
|
||||
$textFilter.click(function(e) {
|
||||
e.stopPropagation();
|
||||
$('#textSearchFilterHistory').toggle();
|
||||
$(this).closest('.dropdown').toggleClass('open');
|
||||
}).on('input', () => {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$(this).closest('.dropdown').removeClass('open');
|
||||
});
|
||||
|
||||
$filterContainer.on('click', '.projects-search-keyword', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$textFilter.val($(this).data('keyword'));
|
||||
$('#textSearchFilterHistory').hide();
|
||||
$textFilter.closest('.dropdown').removeClass('open');
|
||||
});
|
||||
}
|
||||
|
||||
function initApplyButton() {
|
||||
$('.apply-filters', $filterContainer).click(function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
let $filterDropdown = $filterContainer.find('.dropdown-menu');
|
||||
let teamId = $filterDropdown.data('team-id');
|
||||
let projectsViewSearch = $('#textSearchFilterInput').val();
|
||||
try {
|
||||
let storagePath = `${$filterDropdown.data('search-field-history-key')}/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
if (!Array.isArray(recentSearchKeywords)) recentSearchKeywords = [];
|
||||
if (recentSearchKeywords.indexOf(projectsViewSearch) !== -1) {
|
||||
recentSearchKeywords.splice(recentSearchKeywords.indexOf(projectsViewSearch), 1);
|
||||
}
|
||||
if (recentSearchKeywords.length > 4) {
|
||||
recentSearchKeywords = recentSearchKeywords.slice(0, 4);
|
||||
}
|
||||
recentSearchKeywords.unshift(projectsViewSearch);
|
||||
localStorage.setItem(storagePath, JSON.stringify(recentSearchKeywords));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
$filterContainer.trigger('filter:apply');
|
||||
$(this).closest('.dropdown').removeClass('open');
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
$filterContainer = $('.filter-container');
|
||||
initClearButton();
|
||||
preventDropdownClose();
|
||||
initApplyButton();
|
||||
initSearchField();
|
||||
return $filterContainer;
|
||||
},
|
||||
toggleFilterMark: function(filterContainer, filtersEnabled) {
|
||||
if (filtersEnabled) {
|
||||
filterContainer.addClass('filters-applied');
|
||||
} else {
|
||||
filterContainer.removeClass('filters-applied');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}());
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
.dashboard-container .calendar-widget {
|
||||
--calendar-day-size: 32px;
|
||||
min-height: 320px;
|
||||
|
||||
.dashboard-calendar {
|
||||
height: 100%;
|
||||
|
@ -40,9 +39,9 @@
|
|||
flex-basis: calc(100% - 42px);
|
||||
flex-grow: 1;
|
||||
grid-column-gap: 6px;
|
||||
grid-row-gap: 6px;
|
||||
grid-row-gap: 2px;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(auto-fit, minmax(1em, 1fr));
|
||||
justify-items: center;
|
||||
padding: 6px;
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
.filter-container {
|
||||
height: 36px;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
width: 36px;
|
||||
|
||||
.current-tasks-filters {
|
||||
|
|
|
@ -12,19 +12,20 @@
|
|||
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 30px;
|
||||
padding: calc(var(--dashboard-widgets-gap) / 2)
|
||||
calc(var(--dashboard-widgets-gap) / 2)
|
||||
padding: 15px
|
||||
var(--dashboard-widgets-gap)
|
||||
15px
|
||||
var(--dashboard-widgets-gap);
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
padding-bottom: calc(var(--dashboard-widgets-gap) / 2);
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
--widget-header-size: 44px;
|
||||
display: grid;
|
||||
grid-auto-rows: 30em;
|
||||
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));
|
||||
|
@ -99,20 +100,21 @@
|
|||
|
||||
@media (max-height: 800px) {
|
||||
.dashboard-container {
|
||||
grid-auto-rows: 24em;
|
||||
grid-auto-rows: 20em;
|
||||
}
|
||||
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 1300px) {
|
||||
@media (min-height: 1080px) {
|
||||
.dashboard-container {
|
||||
grid-auto-rows: 36em;
|
||||
grid-auto-rows: 32em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.dashboard-view {
|
||||
--dashboard-widgets-gap: 15px;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
--widget-header-size: 72px;
|
||||
|
|
|
@ -3,207 +3,356 @@
|
|||
// scss-lint:disable SelectorFormat
|
||||
// scss-lint:disable IdSelector
|
||||
|
||||
// EXPERIMENT PANEL
|
||||
.experiment-panel {
|
||||
@include box-shadow(0 4px 8px 0 $color-dove-gray);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 35px;
|
||||
margin-top: 15px;
|
||||
max-width: 700px;
|
||||
|
||||
.panel-title {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
// New experiments page
|
||||
|
||||
.panel-heading .clone-experiment,
|
||||
.panel-heading .dropdown {
|
||||
display: none;
|
||||
}
|
||||
.projects-show {
|
||||
.experiment-actions-menu {
|
||||
.dropdown-menu {
|
||||
a,
|
||||
button {
|
||||
@include font-button;
|
||||
padding: .5em 1em;
|
||||
|
||||
&:hover .clone-experiment,
|
||||
&:hover .dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.panel-date {
|
||||
color: $color-silver-chalice;
|
||||
|
||||
.fas {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
.fas {
|
||||
margin-right: .4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-heading > .clone-experiment {
|
||||
color: $color-silver-chalice;
|
||||
padding-left: 4px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
color: $color-silver-chalice;
|
||||
|
||||
button {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
a {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-description {
|
||||
margin-top: 10px;
|
||||
max-height: 86px;
|
||||
overflow-x: hidden;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-no-description {
|
||||
@include font-h3;
|
||||
color: $color-alto;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-workflowimg {
|
||||
color: $color-alto;
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
margin: 15px 0;
|
||||
max-height: 200px;
|
||||
padding-bottom: 70px;
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.workflowimg-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
max-height: 190px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// SHOW
|
||||
.center-btn {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.big-plus {
|
||||
color: $color-gainsboro;
|
||||
display: block;
|
||||
font-size: 180px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: $color-alto;
|
||||
}
|
||||
}
|
||||
|
||||
#project-show-archive {
|
||||
.experiment-panel {
|
||||
background: $color-concrete;
|
||||
|
||||
.panel-heading {
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.panel-date {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#project-show,
|
||||
#project-show-archive {
|
||||
.content-header {
|
||||
.project-name {
|
||||
max-width: calc(100% - 2em);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
max-width: calc(100% - 7em);
|
||||
|
||||
.fas {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-actions {
|
||||
margin-left: auto;
|
||||
|
||||
.experiment-sort-menu {
|
||||
.edit-experiments-form,
|
||||
.clone-experiments-form,
|
||||
.move-experiments-form,
|
||||
.archive-experiments-form,
|
||||
.new-experiment-form,
|
||||
.restore-experiments-form {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
#sortMenu {
|
||||
background: $color-white;
|
||||
.project-show-container {
|
||||
margin: 1.5em 0;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
.cards-wrapper {
|
||||
--card-min-width: 350px;
|
||||
--list-columns-number: 7;
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
.card {
|
||||
grid-row: span 6;
|
||||
|
||||
|
||||
&.experiment-card {
|
||||
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;
|
||||
display: -webkit-box;
|
||||
height: 3em;
|
||||
-webkit-line-clamp: 2;
|
||||
margin: .25em 1.75em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
right: .2em;
|
||||
top: .2em;
|
||||
}
|
||||
|
||||
.dates-and-img-container {
|
||||
display: flex;
|
||||
height: 7em;
|
||||
width: 100%;
|
||||
|
||||
.dates-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.data-row {
|
||||
display: flex;
|
||||
line-height: 34px;
|
||||
|
||||
.card-label {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 4px;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
.progress-bar {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.completed-task-cell {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
.description-text {
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
height: 3em;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
padding-top: .25em;
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(to right, transparent, $color-white 50%);
|
||||
bottom: .75em;
|
||||
content: "";
|
||||
height: 1.75em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.more-button {
|
||||
bottom: .8em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: 200px;
|
||||
padding: .5em 0;
|
||||
&.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;
|
||||
|
||||
a {
|
||||
border-radius: unset;
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
text-align: left;
|
||||
.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 {
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
.modified-date-cell {
|
||||
grid-column: 4;
|
||||
}
|
||||
|
||||
.completed-task-cell {
|
||||
grid-column: 5;
|
||||
}
|
||||
|
||||
.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 {
|
||||
background: $color-concrete;
|
||||
.description-text::after {
|
||||
background: linear-gradient(to right, transparent, $color-concrete 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
&.readonly {
|
||||
.experiment-name-cell {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-show-toolbar {
|
||||
margin-top: 1em;
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -8,20 +8,6 @@
|
|||
$color-group-hover: $color-alabaster;
|
||||
$color-module-hover: $brand-primary;
|
||||
|
||||
// Breadcrumbs
|
||||
.projects-breadcrumbs {
|
||||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
/* Secondary navigation */
|
||||
|
||||
.navbar-nav {
|
||||
.projects-view-mode-switch {
|
||||
margin: 8px 10px 12px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Canvas index page */
|
||||
|
||||
#canvas-container:not(.canvas-container-edit-mode) {
|
||||
|
@ -396,7 +382,6 @@ li.module-hover {
|
|||
|
||||
/* Edit module tags modal window */
|
||||
#manage-module-tags-modal {
|
||||
|
||||
.add-tag-form {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
|
@ -468,176 +453,36 @@ li.module-hover {
|
|||
}
|
||||
|
||||
|
||||
// New projects page
|
||||
|
||||
/// icon override
|
||||
#breadcrumbsWrapper {
|
||||
background: $color-white;
|
||||
height: 1em;
|
||||
margin-left: -2em;
|
||||
padding: 0 2em;
|
||||
width: calc(100% + 4em);
|
||||
|
||||
.panel-project {
|
||||
|
||||
.nav-tabs[data-hook="project-footer-icons"] {
|
||||
|
||||
.btn-link {
|
||||
display: inline-block;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.badge-indicator {
|
||||
background: transparent;
|
||||
color: $color-silver-chalice;
|
||||
font-size: 12px;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content[data-hook="project-dropdown-panel"] {
|
||||
|
||||
.over-due {
|
||||
|
||||
.title {
|
||||
border-top: 1px solid $color-concrete;
|
||||
font-size: 14px;
|
||||
line-height: 15px;
|
||||
padding: 10px 15px 0;
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding-left: 35px;
|
||||
|
||||
.date-time {
|
||||
position: relative;
|
||||
|
||||
.fas {
|
||||
font-size: 14px;
|
||||
left: -20px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.projects-breadcrumbs {
|
||||
@include font-small;
|
||||
padding: .75em 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// New projects page
|
||||
|
||||
.projects-index {
|
||||
.cards-wrapper {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-auto-rows: 2em;
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(291px, 1fr));
|
||||
width: 100%;
|
||||
|
||||
.table-header {
|
||||
display: none;
|
||||
.project-users-list {
|
||||
hr {
|
||||
margin: .5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-view-mode {
|
||||
&.active::after {
|
||||
@include font-awesome;
|
||||
content: "\f00c";
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.archive-projects-form {
|
||||
.delete-folders-form,
|
||||
.delete-folders-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.restore-projects-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
|
||||
.projects-sort-menu {
|
||||
display: inline-block;
|
||||
|
||||
#sortMenu {
|
||||
background: $color-white;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: 150px;
|
||||
padding: .5em 0;
|
||||
|
||||
a {
|
||||
border-radius: unset;
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
position: initial;
|
||||
|
||||
.projects-filters {
|
||||
padding: 0;
|
||||
width: 230px;
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
height: 2.75em;
|
||||
margin-bottom: 1em;
|
||||
padding: 0 .3em 0 1em;
|
||||
|
||||
.title {
|
||||
@include font-h2;
|
||||
flex-grow: 1;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.select-block {
|
||||
display: inline-block;
|
||||
padding: 0 1em 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
font-weight: bold;
|
||||
margin-bottom: .3em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&.folders {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
@ -661,94 +506,6 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-search-filter {
|
||||
&.open {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.recent-searches {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
left: auto;
|
||||
margin-top: -1px;
|
||||
top: auto;
|
||||
width: calc(100% - 2em);
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
font-weight: bold;
|
||||
margin-bottom: .3em;
|
||||
padding: 0 1.5em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.projects-search-keyword {
|
||||
@include font-button;
|
||||
padding: .5em 1.25em;
|
||||
|
||||
.keyword-text {
|
||||
margin-left: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
border-top: $border-default;
|
||||
display: flex;
|
||||
height: 4.25em;
|
||||
justify-content: center;
|
||||
padding: 0 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.filters-applied .filter-button::after {
|
||||
@include red-dot;
|
||||
right: .35em;
|
||||
top: .3em;
|
||||
}
|
||||
|
||||
.connect-line {
|
||||
background: $input-border;
|
||||
height: 10px;
|
||||
margin: 5px auto;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.projects-filters {
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
.btn:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
display: inline-block;
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.connect-line {
|
||||
display: inline-block;
|
||||
margin: 0 11px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.created-on-label {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.users-dropdown-list {
|
||||
|
@ -762,56 +519,25 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.projects-toolbar {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.form-group {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.archive-projects-form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.restore-projects-form {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-container {
|
||||
margin: 1.5em 20px;
|
||||
margin: 1.5em 0;
|
||||
|
||||
.cards-wrapper {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-auto-rows: 2em;
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(291px, 1fr));
|
||||
width: 100%;
|
||||
|
||||
&.no-results {
|
||||
|
||||
.no-results-container {
|
||||
grid-row: 8;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.no-results-img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-height: 230px;
|
||||
}
|
||||
|
||||
.no-results-title {
|
||||
@include font-h1;
|
||||
margin-bottom: .25em;
|
||||
margin-top: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-results-description {
|
||||
@include font-main;
|
||||
color: $color-silver-chalice;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: none;
|
||||
}
|
||||
--card-min-width: 291px;
|
||||
--list-columns-number: 6;
|
||||
|
||||
.projects-group {
|
||||
grid-column: 1/-1;
|
||||
|
@ -819,43 +545,9 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.card {
|
||||
background-color: $color-white;
|
||||
color: $color-volcano;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
grid-row: span 4;
|
||||
height: 100%;
|
||||
padding: .5em 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.checkbox-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
left: .5em;
|
||||
position: absolute;
|
||||
top: .5em;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
.global-avatar-container {
|
||||
height: 28px;
|
||||
margin-right: .25em;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.manage-users-link {
|
||||
.project-users-link {
|
||||
color: $color-silver-chalice;
|
||||
display: inline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
@ -971,48 +663,46 @@ li.module-hover {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.more-users {
|
||||
align-items: center;
|
||||
background: $color-volcano;
|
||||
border-radius: 50%;
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
margin-right: .25em;
|
||||
width: 2em;
|
||||
|
||||
&:hover {
|
||||
&.user-cell {
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.new-user {
|
||||
align-items: center;
|
||||
background: $color-concrete;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
width: 2em;
|
||||
.value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.global-avatar-container {
|
||||
height: 28px;
|
||||
margin-right: .25em;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.more-users {
|
||||
align-items: center;
|
||||
background: $color-volcano;
|
||||
border-radius: 50%;
|
||||
color: $color-white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
margin-right: .25em;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.new-user {
|
||||
background: $color-concrete;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.list {
|
||||
grid-auto-flow: dense;
|
||||
grid-auto-rows: 2em 1px;
|
||||
grid-row-gap: .5em;
|
||||
grid-template-columns: max-content repeat(3, minmax(100px, auto)) max-content max-content;
|
||||
margin: 40px 0 0 6px;
|
||||
|
||||
.no-results-container {
|
||||
grid-row: 12;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 3), minmax(100px, auto)) max-content max-content;
|
||||
|
||||
.projects-group {
|
||||
display: contents;
|
||||
|
@ -1031,17 +721,6 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.card {
|
||||
display: contents;
|
||||
|
||||
// Border element
|
||||
&:after {
|
||||
background: $color-concrete;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
grid-column: 1/-1;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
font-weight: normal;
|
||||
line-height: 1.25em;
|
||||
|
@ -1061,10 +740,6 @@ li.module-hover {
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
&.folder-card {
|
||||
text-align: left;
|
||||
|
||||
|
@ -1076,6 +751,7 @@ li.module-hover {
|
|||
|
||||
.name {
|
||||
grid-column: 5 span;
|
||||
line-height: 3em;
|
||||
|
||||
&:before {
|
||||
@include font-awesome;
|
||||
|
@ -1094,12 +770,9 @@ li.module-hover {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.project-name-cell {
|
||||
grid-column: 2;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
a:hover {
|
||||
|
@ -1120,39 +793,13 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.actions-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
grid-column: 6;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: contents;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.table-header-cell {
|
||||
align-items: center;
|
||||
border: 1px solid $color-white;
|
||||
display: flex;
|
||||
height: 3em;
|
||||
margin-left: -.5em;
|
||||
padding: 0 .5em;
|
||||
width: calc(100% + 1em);
|
||||
|
||||
&.select-all-checkboxes {
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
& > * {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1179,9 +826,6 @@ li.module-hover {
|
|||
grid-auto-rows: 2.5em;
|
||||
|
||||
.card.project-card {
|
||||
background: $color-concrete;
|
||||
box-shadow: none;
|
||||
|
||||
.data-row {
|
||||
color: $color-silver-chalice;
|
||||
|
||||
|
@ -1200,36 +844,15 @@ li.module-hover {
|
|||
}
|
||||
|
||||
&.list {
|
||||
grid-auto-rows: 2em 1px;
|
||||
grid-template-columns: max-content repeat(5, minmax(100px, auto)) max-content;
|
||||
--list-columns-number: 7;
|
||||
grid-auto-rows: 3em 1px;
|
||||
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content;
|
||||
|
||||
.card::after {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
.card.folder-card {
|
||||
.name {
|
||||
grid-column: 6 span;
|
||||
}
|
||||
}
|
||||
|
||||
.card.project-card {
|
||||
.checkbox-cell,
|
||||
.project-name-cell,
|
||||
.actions-cell,
|
||||
.data-row {
|
||||
align-items: center;
|
||||
background: $color-concrete;
|
||||
color: $color-volcano;
|
||||
display: flex;
|
||||
height: 3em;
|
||||
margin-left: -.5em;
|
||||
padding-left: .5em;
|
||||
width: calc(100% + 1em);
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
padding-left: 0;
|
||||
.card {
|
||||
&.folder-card {
|
||||
.name {
|
||||
grid-column: 6 span;
|
||||
}
|
||||
}
|
||||
|
||||
.archived-date-cell {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -97,6 +97,14 @@ div.print-report {
|
|||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.image-icon.preview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-icon.report {
|
||||
display: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
.report-step-table-element {
|
||||
|
@ -199,6 +207,14 @@ div.print-report {
|
|||
}
|
||||
}
|
||||
|
||||
.image-icon.preview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.image-icon.report {
|
||||
display: initial !important;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
|
@ -31,66 +31,70 @@
|
|||
|
||||
|
||||
#repository-toolbar {
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
border: 0;
|
||||
display: flex;
|
||||
left: var(--repository-sidebar-margin);
|
||||
padding: 0 2em;
|
||||
position: fixed;
|
||||
transition: .4s $timing-function-sharp;
|
||||
width: calc(100% - var(--repository-sidebar-margin));
|
||||
z-index: 100;
|
||||
|
||||
.toolbar-delimiter {
|
||||
border-bottom: $border-tertiary;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: calc(100% - 4em);
|
||||
}
|
||||
|
||||
.repository-share-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: -2px;
|
||||
|
||||
.fas-custom {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
}
|
||||
|
||||
.repository-title-name {
|
||||
@include font-h2;
|
||||
.title-row {
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
border: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
line-height: 32px;
|
||||
margin-right: 20px;
|
||||
overflow: hidden;
|
||||
padding-left: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
left: var(--repository-sidebar-margin);
|
||||
margin-left: 0em;
|
||||
padding: 0 2em;
|
||||
position: fixed;
|
||||
transition: .4s $timing-function-sharp;
|
||||
width: calc(100% - var(--repository-sidebar-margin));
|
||||
z-index: 100;
|
||||
|
||||
.name-container {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
.toolbar-delimiter {
|
||||
border-bottom: $border-tertiary;
|
||||
bottom: 0;
|
||||
margin-left: -2em;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.read-only-name {
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
max-width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.repository-share-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: -2px;
|
||||
|
||||
.fas-custom {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-editing-container {
|
||||
width: 100%;
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
}
|
||||
|
||||
.repository-title-name {
|
||||
@include font-h2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
line-height: 32px;
|
||||
margin-right: 20px;
|
||||
overflow: hidden;
|
||||
padding-left: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.name-container {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
|
||||
.read-only-name {
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
max-width: calc(100% - 30px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.inline-editing-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
178
app/assets/stylesheets/shared/cards.scss
Normal file
|
@ -0,0 +1,178 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
// scss-lint:disable SelectorFormat
|
||||
|
||||
.cards-wrapper {
|
||||
--card-min-width: 200px;
|
||||
--list-columns-number: 5;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-auto-rows: 2em;
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr));
|
||||
width: 100%;
|
||||
|
||||
.table-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.no-results {
|
||||
.no-results-container {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 8;
|
||||
}
|
||||
|
||||
.no-results-img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-height: 230px;
|
||||
}
|
||||
|
||||
.no-results-title {
|
||||
@include font-h1;
|
||||
margin-bottom: .25em;
|
||||
margin-top: 1.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-results-description {
|
||||
@include font-main;
|
||||
color: $color-silver-chalice;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: $color-white;
|
||||
color: $color-volcano;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
grid-row: span 4;
|
||||
height: 100%;
|
||||
padding: .5em 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.checkbox-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 2em;
|
||||
justify-content: center;
|
||||
left: .5em;
|
||||
position: absolute;
|
||||
top: .5em;
|
||||
width: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
&: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));
|
||||
|
||||
[cards-render="true"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.no-results-container {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 12;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: contents;
|
||||
|
||||
// Border element
|
||||
&::after {
|
||||
background: $color-concrete;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
grid-column: 1/-1;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.table-cell {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
height: 100%;
|
||||
padding: 0 .5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
grid-column: 1;
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: contents;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.table-header-cell {
|
||||
align-items: center;
|
||||
border: 1px solid $color-white;
|
||||
display: flex;
|
||||
height: 3em;
|
||||
padding: 0 .5em;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: calc(var(--content-header-size) + var(--navbar-height));
|
||||
z-index: 2;
|
||||
|
||||
&.select-all-checkboxes {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
& > * {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
// scss-lint:disable SelectorDepth SelectorFormat
|
||||
// scss-lint:disable NestingDepth QualifyingElement
|
||||
|
||||
.content-pane {
|
||||
--content-header-size: 9.5em;
|
||||
background-color: $color-white;
|
||||
margin: 20px 0;
|
||||
padding: 25px 20px;
|
||||
|
@ -9,50 +13,141 @@
|
|||
}
|
||||
|
||||
.content-header {
|
||||
align-items: center;
|
||||
border-bottom: $border-tertiary;
|
||||
display: flex;
|
||||
height: 5em;
|
||||
|
||||
h1 {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
&.sticky-header {
|
||||
background: $color-white;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: var(--navbar-height);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
margin-left: auto;
|
||||
.title-row {
|
||||
align-items: center;
|
||||
background: $color-white;
|
||||
border-bottom: $border-tertiary;
|
||||
display: flex;
|
||||
height: 4em;
|
||||
margin-left: -2em;
|
||||
padding: 0 2em;
|
||||
width: calc(100% + 4em);
|
||||
|
||||
.caret {
|
||||
margin: 8px 0 8px 8px;
|
||||
h1 {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
&.open {
|
||||
|
||||
.header-actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
margin-left: auto;
|
||||
|
||||
.caret {
|
||||
transform: rotateX(180deg)
|
||||
margin: 8px 0 8px 8px;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.caret {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: auto;
|
||||
|
||||
.divider-label {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
padding: .25em 1em;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 1em;
|
||||
white-space: nowrap;
|
||||
|
||||
.button-icon {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cards-switch {
|
||||
&.active::after {
|
||||
@include font-awesome;
|
||||
content: "\f00c";
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: auto;
|
||||
.sort-menu {
|
||||
display: inline-block;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 1em;
|
||||
white-space: nowrap;
|
||||
#sortMenu {
|
||||
background: $color-white;
|
||||
|
||||
.button-icon {
|
||||
margin-right: .5em;
|
||||
&:focus,
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
@include font-button;
|
||||
min-width: 150px;
|
||||
padding: .5em 0;
|
||||
|
||||
a {
|
||||
border-radius: unset;
|
||||
cursor: pointer;
|
||||
padding: .5em 1em;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
@include font-awesome;
|
||||
content: $font-fas-check;
|
||||
margin-left: auto;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-row {
|
||||
background: $color-white;
|
||||
border-bottom: $border-tertiary;
|
||||
margin-left: -2em;
|
||||
padding: 1em 0;
|
||||
padding-left: 2em;
|
||||
width: calc(100% + 4em);
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
&[data-toolbar-visible="false"] {
|
||||
--content-header-size: 5em;
|
||||
|
||||
.toolbar-row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
124
app/assets/stylesheets/shared/filter_dropdown.scss
Normal file
|
@ -0,0 +1,124 @@
|
|||
// scss-lint:disable NestingDepth SelectorDepth
|
||||
|
||||
.filter-container {
|
||||
position: initial;
|
||||
|
||||
.filter-dropdown {
|
||||
padding: 0;
|
||||
width: 230px;
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
height: 2.75em;
|
||||
margin-bottom: 1em;
|
||||
padding: 0 .3em 0 1em;
|
||||
|
||||
.title {
|
||||
@include font-h2;
|
||||
flex-grow: 1;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.select-block {
|
||||
display: inline-block;
|
||||
padding: 0 1em 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
font-weight: bold;
|
||||
margin-bottom: .3em;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.text-search-filter {
|
||||
&.open {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.recent-searches {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
left: auto;
|
||||
margin-top: -1px;
|
||||
top: auto;
|
||||
width: calc(100% - 2em);
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
font-weight: bold;
|
||||
margin-bottom: .3em;
|
||||
padding: 0 1.5em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.projects-search-keyword {
|
||||
@include font-button;
|
||||
padding: .5em 1.25em;
|
||||
|
||||
.keyword-text {
|
||||
margin-left: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
border-top: $border-default;
|
||||
display: flex;
|
||||
height: 4.25em;
|
||||
justify-content: center;
|
||||
padding: 0 1em;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.filters-applied .filter-button::after {
|
||||
@include red-dot;
|
||||
right: .35em;
|
||||
top: .3em;
|
||||
}
|
||||
|
||||
.connect-line {
|
||||
background: $input-border;
|
||||
height: 10px;
|
||||
margin: 5px auto;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.filter-dropdown {
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.footer:not(.center) {
|
||||
.btn:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.datetime-picker-container {
|
||||
display: inline-block;
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.connect-line {
|
||||
display: inline-block;
|
||||
margin: 0 11px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -632,7 +632,6 @@ ul.double-line > li {
|
|||
}
|
||||
|
||||
#project-show,
|
||||
#project-show-archive,
|
||||
#module-archive,
|
||||
#result-archive {
|
||||
.panel-default {
|
||||
|
@ -657,84 +656,6 @@ ul.double-line > li {
|
|||
}
|
||||
}
|
||||
|
||||
.panel-project {
|
||||
box-shadow: 0 3px 6px $color-alto;
|
||||
color: $color-silver-chalice;
|
||||
|
||||
&:not(.selected) .panel-heading .project-card-selector,
|
||||
&:not(.selected) .panel-heading .dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not(.selected):hover .project-card-selector,
|
||||
&:not(.selected):hover .dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav .btn-link {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: $brand-primary;
|
||||
box-shadow: 0 3px 10px $brand-primary;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
&,
|
||||
.panel-heading,
|
||||
.panel-body,
|
||||
.panel-footer-scinote {
|
||||
background-color: $color-concrete;
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
background-color: $color-white;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
color: $color-silver-chalice;
|
||||
|
||||
.fas {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
color: $brand-primary;
|
||||
|
||||
.fas {
|
||||
color: $color-silver-chalice;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 10px 15px;
|
||||
|
||||
.row {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
|
||||
.nav {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
color: $color-emperor;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-archive {
|
||||
.panel-heading {
|
||||
background-color: darken($color-gainsboro, 5%);
|
||||
|
|
Before Width: | Height: | Size: 474 KiB After Width: | Height: | Size: 474 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
2920
app/assets/templates/experiment_1/experiment.json
Normal 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,
|
||||
|
|
|
@ -700,7 +700,7 @@
|
|||
"step": {
|
||||
"id": 4414,
|
||||
"name": "Preparation",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Guidelines for samples preparation bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Guidelines for samples preparation below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
"step": {
|
||||
"id": 4533,
|
||||
"name": "Examine the plates",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -863,7 +863,7 @@
|
|||
"step": {
|
||||
"id": 5495,
|
||||
"name": "Slide test",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow. </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below. </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -1020,7 +1020,7 @@
|
|||
"step": {
|
||||
"id": 5492,
|
||||
"name": "Oxidase test",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines bellow: </p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow the guidelines below: </p></body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
@ -1604,7 +1604,7 @@
|
|||
"step": {
|
||||
"id": 5475,
|
||||
"name": "Reagents",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n<p>Reagents bellow can be made or purchased.<br><strong><br>Crystal Violet Staining Reagent:<br></strong><span style=\"text-decoration: underline;\"><br>Solution A for crystal violet staining reagent</span></p>\n<ul>\n<li>Crystal violet (certified 90% dye content), 2g </li>\n<li>Ethanol, 95% (vol/vol), 20 mL</li>\n</ul>\n<span style=\"text-decoration: underline;\">Solution B for crystal violet staining reagent<br></span>\n<ul>\n<li>Ammonium oxalate, 0.8 g</li>\n<li>Distilled water, 80 mL</li>\n</ul>\nMix A and B to obtain crystal violet staining reagent. Store for<strong> 24 h</strong> and filter through paper prior to use.<br><br><strong>Gram's Iodine:<br></strong><br>\n<ul>\n<li>Iodine, 1.0 g</li>\n<li>Potassium iodide, 2.0 g</li>\n<li>Distilled water, 300 mL</li>\n</ul>\nGrind the iodine and potassium iodide in a mortar and add water slowly with continuous grinding until the iodine is dissolved. Store in amber bottles.<br><br><strong>Safranin:<br><br></strong><span style=\"text-decoration-line: underline;\">Stock solution:</span><br>\n<ul>\n<li>2.5g Safranin O</li>\n<li>100 ml 95% Ethanol</li>\n</ul>\n<p><span style=\"text-decoration: underline;\">Working Solution:</span></p>\n<ul>\n<li>10 mL Stock Solution </li>\n<li>90 mL Distilled water</li>\n</ul>\n</body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body>\n<p>Reagents below can be made or purchased.<br><strong><br>Crystal Violet Staining Reagent:<br></strong><span style=\"text-decoration: underline;\"><br>Solution A for crystal violet staining reagent</span></p>\n<ul>\n<li>Crystal violet (certified 90% dye content), 2g </li>\n<li>Ethanol, 95% (vol/vol), 20 mL</li>\n</ul>\n<span style=\"text-decoration: underline;\">Solution B for crystal violet staining reagent<br></span>\n<ul>\n<li>Ammonium oxalate, 0.8 g</li>\n<li>Distilled water, 80 mL</li>\n</ul>\nMix A and B to obtain crystal violet staining reagent. Store for<strong> 24 h</strong> and filter through paper prior to use.<br><br><strong>Gram's Iodine:<br></strong><br>\n<ul>\n<li>Iodine, 1.0 g</li>\n<li>Potassium iodide, 2.0 g</li>\n<li>Distilled water, 300 mL</li>\n</ul>\nGrind the iodine and potassium iodide in a mortar and add water slowly with continuous grinding until the iodine is dissolved. Store in amber bottles.<br><br><strong>Safranin:<br><br></strong><span style=\"text-decoration-line: underline;\">Stock solution:</span><br>\n<ul>\n<li>2.5g Safranin O</li>\n<li>100 ml 95% Ethanol</li>\n</ul>\n<p><span style=\"text-decoration: underline;\">Working Solution:</span></p>\n<ul>\n<li>10 mL Stock Solution </li>\n<li>90 mL Distilled water</li>\n</ul>\n</body></html>",
|
||||
"position": 0,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
|
@ -1210,7 +1210,7 @@
|
|||
"step": {
|
||||
"id": 5154,
|
||||
"name": "Elution and collection of samples",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow elution and collection guidelines bellow.</p></body></html>",
|
||||
"description": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>Follow elution and collection guidelines below.</p></body></html>",
|
||||
"position": 1,
|
||||
"completed": false,
|
||||
"completed_on": null,
|
||||
|
|
15
app/controllers/active_storage/blobs/redirect_controller.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveStorage
|
||||
module Blobs
|
||||
class RedirectController < CustomBaseController
|
||||
include ActiveStorage::SetBlob
|
||||
include ActiveStorage::CheckBlobPermissions
|
||||
|
||||
def show
|
||||
expires_in ActiveStorage.service_urls_expire_in
|
||||
redirect_to @blob.url(disposition: params[:disposition])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveStorage
|
||||
class BlobsController < CustomBaseController
|
||||
include ActiveStorage::SetBlob
|
||||
include ActiveStorage::CheckBlobPermissions
|
||||
|
||||
def show
|
||||
expires_in ActiveStorage.service_urls_expire_in
|
||||
redirect_to @blob.service_url(disposition: params[:disposition])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,5 +8,11 @@ module ActiveStorage
|
|||
|
||||
prepend_before_action :authenticate_request!, if: -> { request.headers['Authorization'].present? }
|
||||
skip_before_action :authenticate_user!, if: -> { current_user.present? }
|
||||
|
||||
private
|
||||
|
||||
def stream(_blob)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module ActiveStorage
|
|||
before_action :check_file_size, only: :create
|
||||
|
||||
def create
|
||||
blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args)
|
||||
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
|
||||
render json: direct_upload_json(blob)
|
||||
end
|
||||
|
||||
|
@ -13,7 +13,7 @@ module ActiveStorage
|
|||
|
||||
def blob_args
|
||||
args = params.require(:blob)
|
||||
.permit(:filename, :byte_size, :checksum, :content_type, :metadata)
|
||||
.permit(:filename, :byte_size, :checksum, :content_type, metadata: {})
|
||||
.to_h
|
||||
.symbolize_keys
|
||||
args[:content_type] = 'application/octet-stream' if args[:content_type].blank?
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveStorage
|
||||
module Representations
|
||||
class RedirectController < CustomBaseController
|
||||
include ActiveStorage::SetBlob
|
||||
include ActiveStorage::CheckBlobPermissions
|
||||
|
||||
def show
|
||||
if @blob.attachments.take.record_type == 'Asset'
|
||||
return render plain: '', status: :accepted unless preview_ready?
|
||||
end
|
||||
|
||||
expires_in ActiveStorage.service_urls_expire_in
|
||||
redirect_to @blob.representation(params[:variation_key]).processed.url(disposition: params[:disposition])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def preview_ready?
|
||||
processing = @blob.attachments.take.record.file_processing
|
||||
return false if processing
|
||||
|
||||
preview_exists =
|
||||
if @blob.variable?
|
||||
@blob.service.exist?(@blob.representation(params['variation_key']).key)
|
||||
else
|
||||
@blob.preview(params['variation_key']).image.attached?
|
||||
end
|
||||
|
||||
return true if preview_exists
|
||||
|
||||
unless processing
|
||||
ActiveStorage::PreviewJob.perform_later(@blob.id)
|
||||
ActiveRecord::Base.no_touching do
|
||||
@blob.attachments.take.record.update(file_processing: true)
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveStorage
|
||||
class RepresentationsController < CustomBaseController
|
||||
include ActiveStorage::SetBlob
|
||||
include ActiveStorage::CheckBlobPermissions
|
||||
|
||||
def show
|
||||
if @blob.attachments.take.record_type == 'Asset'
|
||||
return render plain: '', status: :accepted unless preview_ready?
|
||||
end
|
||||
|
||||
expires_in ActiveStorage.service_urls_expire_in
|
||||
redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def preview_ready?
|
||||
processing = @blob.attachments.take.record.file_processing
|
||||
return false if processing
|
||||
|
||||
preview_exists =
|
||||
if @blob.variable?
|
||||
@blob.service.exist?(@blob.representation(params['variation_key']).key)
|
||||
else
|
||||
@blob.preview(params['variation_key']).image.attached?
|
||||
end
|
||||
|
||||
return true if preview_exists
|
||||
|
||||
unless processing
|
||||
ActiveStorage::PreviewJob.perform_later(@blob.id)
|
||||
ActiveRecord::Base.no_touching do
|
||||
@blob.attachments.take.record.update(file_processing: true)
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ module Api
|
|||
if @form_multipart_upload
|
||||
asset = @step.assets.new(asset_params.merge({ team_id: @team.id }))
|
||||
else
|
||||
blob = ActiveStorage::Blob.create_after_upload!(
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: StringIO.new(Base64.decode64(asset_params[:file_data])),
|
||||
filename: asset_params[:file_name],
|
||||
content_type: asset_params[:file_type]
|
||||
|
|
|
@ -129,7 +129,7 @@ module Api
|
|||
end
|
||||
|
||||
def create_blob_from_params
|
||||
blob = ActiveStorage::Blob.create_after_upload!(
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: StringIO.new(Base64.decode64(result_file_params[:file_data])),
|
||||
filename: result_file_params[:file_name],
|
||||
content_type: result_file_params[:file_type]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -7,9 +7,9 @@ class ExperimentsController < ApplicationController
|
|||
include ApplicationHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
before_action :load_project, only: %i(new create)
|
||||
before_action :load_experiment, except: %i(new create)
|
||||
before_action :check_view_permissions, except: %i(edit archive clone move new create)
|
||||
before_action :load_project, only: %i(new create archive_group restore_group)
|
||||
before_action :load_experiment, except: %i(new create archive_group restore_group)
|
||||
before_action :check_view_permissions, except: %i(edit archive clone move new create archive_group restore_group)
|
||||
before_action :check_create_permissions, only: %i(new create)
|
||||
before_action :check_manage_permissions, only: %i(edit)
|
||||
before_action :check_archive_permissions, only: :archive
|
||||
|
@ -39,7 +39,7 @@ class ExperimentsController < ApplicationController
|
|||
@experiment.project = @project
|
||||
if @experiment.save
|
||||
experiment_annotation_notification
|
||||
log_activity(:create_experiment)
|
||||
log_activity(:create_experiment, @experiment)
|
||||
flash[:success] = t('experiments.create.success_flash',
|
||||
experiment: @experiment.name)
|
||||
respond_to do |format|
|
||||
|
@ -56,10 +56,16 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def show
|
||||
render json: {
|
||||
html: render_to_string(partial: 'experiments/details_modal.html.erb')
|
||||
}
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
@ -98,15 +104,14 @@ class ExperimentsController < ApplicationController
|
|||
else
|
||||
:edit_experiment
|
||||
end
|
||||
log_activity(activity_type)
|
||||
log_activity(activity_type, @experiment)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
format.html do
|
||||
flash[:success] = t('experiments.update.success_flash',
|
||||
experiment: @experiment.name)
|
||||
flash[:success] = t('experiments.update.success_flash', experiment: @experiment.name)
|
||||
redirect_to project_path(@experiment.project)
|
||||
end
|
||||
end
|
||||
|
@ -124,11 +129,9 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
def archive
|
||||
@experiment.archived = true
|
||||
@experiment.archived_by = current_user
|
||||
@experiment.archived_on = DateTime.now
|
||||
@experiment.archive(current_user)
|
||||
if @experiment.save
|
||||
log_activity(:archive_experiment)
|
||||
log_activity(:archive_experiment, @experiment)
|
||||
flash[:success] = t('experiments.archive.success_flash',
|
||||
experiment: @experiment.name)
|
||||
|
||||
|
@ -139,6 +142,51 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def archive_group
|
||||
experiments = @project.experiments.active.where(id: params[:experiments_ids])
|
||||
counter = 0
|
||||
experiments.each do |experiment|
|
||||
next unless can_archive_experiment?(experiment)
|
||||
|
||||
experiment.transaction do
|
||||
experiment.archived_on = DateTime.now
|
||||
experiment.archive!(current_user)
|
||||
log_activity(:archive_experiment, experiment)
|
||||
counter += 1
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
if counter.positive?
|
||||
render json: { message: t('experiments.archive_group.success_flash', number: counter) }
|
||||
else
|
||||
render json: { message: t('experiments.archive_group.error_flash') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def restore_group
|
||||
experiments = @project.experiments.archived.where(id: params[:experiments_ids])
|
||||
counter = 0
|
||||
experiments.each do |experiment|
|
||||
next unless can_restore_experiment?(experiment)
|
||||
|
||||
experiment.transaction do
|
||||
experiment.restore!(current_user)
|
||||
log_activity(:restore_experiment, experiment)
|
||||
counter += 1
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
if counter.positive?
|
||||
render json: { message: t('experiments.restore_group.success_flash', number: counter) }
|
||||
else
|
||||
render json: { message: t('experiments.restore_group.error_flash') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# GET: clone_modal_experiment_path(id)
|
||||
def clone_modal
|
||||
@projects = @experiment.projects_with_role_above_user(current_user)
|
||||
|
@ -287,6 +335,7 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
def set_inline_name_editing
|
||||
return unless can_manage_experiment?(@experiment)
|
||||
|
||||
@inline_editable_title_config = {
|
||||
name: 'title',
|
||||
params_group: 'experiment',
|
||||
|
@ -311,13 +360,13 @@ class ExperimentsController < ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def log_activity(type_of)
|
||||
def log_activity(type_of, experiment)
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: type_of,
|
||||
owner: current_user,
|
||||
team: @experiment.project.team,
|
||||
project: @experiment.project,
|
||||
subject: @experiment,
|
||||
message_items: { experiment: @experiment.id })
|
||||
team: experiment.project.team,
|
||||
project: experiment.project,
|
||||
subject: experiment,
|
||||
message_items: { experiment: experiment.id })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,15 +30,15 @@ class ExternalProtocolsController < ApplicationController
|
|||
.split('/').map(&:to_sym))
|
||||
api_client = "ProtocolImporters::#{endpoint_name}::ApiClient".constantize.new
|
||||
|
||||
html_preview = api_client.protocol_html_preview(show_params[:protocol_id])
|
||||
base_uri = URI.parse(html_preview.request.last_uri.to_s)
|
||||
html_preview_request = api_client.protocol_html_preview(show_params[:protocol_id])
|
||||
base_uri = URI.parse(html_preview_request.request.last_uri.to_s)
|
||||
base_uri = "#{base_uri.scheme}://#{base_uri.host}"
|
||||
|
||||
render json: {
|
||||
protocol_source: show_params[:protocol_source],
|
||||
protocol_id: show_params[:protocol_id],
|
||||
base_uri: base_uri,
|
||||
html: html_preview
|
||||
html: html_preview_request.body
|
||||
}
|
||||
rescue StandardError => e
|
||||
render json: {
|
||||
|
|
|
@ -96,6 +96,35 @@ class ProjectFoldersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def destroy_modal
|
||||
render json: {
|
||||
html: render_to_string(partial: 'projects/index/modals/project_folder_delete.html.erb',
|
||||
locals: { project_folders_ids: params[:project_folders_ids] })
|
||||
}
|
||||
end
|
||||
|
||||
def destroy
|
||||
project_folders = current_team.project_folders.where(id: params[:project_folders_ids])
|
||||
counter = 0
|
||||
project_folders.each do |folder|
|
||||
next if folder.projects.exists? || folder.project_folders.exists? || !can_update_team?(current_team)
|
||||
|
||||
folder.transaction do
|
||||
log_activity(:delete_project_folder, folder, project_folder: folder.id)
|
||||
folder.destroy!
|
||||
counter += 1
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
if counter.positive?
|
||||
render json: { message: t('projects.delete_folders.success_flash', number: counter) }
|
||||
else
|
||||
render json: { message: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_project_folder
|
||||
|
|
|
@ -5,18 +5,19 @@ class ProjectsController < ApplicationController
|
|||
include TeamsHelper
|
||||
include InputSanitizeHelper
|
||||
include ProjectsHelper
|
||||
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)
|
||||
before_action :load_current_folder, only: %i(index cards new)
|
||||
before_action :check_view_permissions, only: %i(show notifications experiment_archive sidebar)
|
||||
before_action :load_vars, only: %i(show edit update notifications sidebar experiments_cards)
|
||||
before_action :load_current_folder, only: %i(index cards new show)
|
||||
before_action :check_view_permissions, only: %i(show notifications sidebar experiments_cards)
|
||||
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 :load_exp_sort_var, only: %i(show)
|
||||
before_action :reset_invalid_view_state, only: %i(index cards)
|
||||
|
||||
layout 'fluid'
|
||||
|
@ -30,6 +31,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
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: {
|
||||
|
@ -43,6 +45,7 @@ class ProjectsController < ApplicationController
|
|||
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') : '',
|
||||
title: current_folder ? current_folder&.name : title,
|
||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar.html.erb'),
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/index/team_projects.html.erb',
|
||||
|
@ -53,15 +56,15 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def sidebar
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/sidebar/experiments.html.erb', locals: { project: @project }
|
||||
)
|
||||
@current_sort = @project.current_view_state(current_user).state.dig('experiments', params[:view_mode], 'sort')
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/sidebar/experiments.html.erb', locals: {
|
||||
project: @project,
|
||||
view_mode: experiments_view_mode(@project)
|
||||
}
|
||||
end
|
||||
end
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -253,9 +256,22 @@ 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'
|
||||
end
|
||||
|
||||
def experiments_cards
|
||||
overview_service = ExperimentsOverviewService.new(@project, current_user, params)
|
||||
render json: {
|
||||
cards_html: render_to_string(
|
||||
partial: 'projects/show/experiments_list.html.erb',
|
||||
locals: { cards: overview_service.experiments,
|
||||
filters_included: filters_included? }
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def notifications
|
||||
|
@ -274,10 +290,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) } }
|
||||
|
@ -301,6 +313,8 @@ class ProjectsController < ApplicationController
|
|||
def load_current_folder
|
||||
if current_team && params[:project_folder_id].present?
|
||||
@current_folder = current_team.project_folders.find_by(id: params[:project_folder_id])
|
||||
elsif @project&.project_folder
|
||||
@current_folder = @project&.project_folder
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -334,11 +348,11 @@ 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
|
||||
|
||||
|
|
|
@ -26,7 +26,9 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do; end
|
||||
format.html do
|
||||
render 'empty_index' if Repository.accessible_by_teams(current_team).blank?
|
||||
end
|
||||
format.json do
|
||||
render json: prepare_repositories_datatable(@repositories, current_team, params)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class SearchController < ApplicationController
|
||||
include IconsHelper
|
||||
include ProjectFoldersHelper
|
||||
before_action :load_vars, only: :index
|
||||
|
||||
def index
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -10,16 +10,12 @@ class UserProjectsController < ApplicationController
|
|||
before_action :check_manage_permissions, only: %i(update destroy)
|
||||
|
||||
def index
|
||||
@users = @project.user_projects
|
||||
@user_projects = @project.user_projects
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'index.html.erb'
|
||||
),
|
||||
project_id: @project.id,
|
||||
counter: @project.users.count # Used for counter badge
|
||||
html: render_to_string(partial: 'index.html.erb')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -33,7 +29,6 @@ class UserProjectsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
project: @project,
|
||||
html_title: t('projects.index.modal_manage_users.modal_title', name: @project.name),
|
||||
html_body: render_to_string(partial: 'index_edit.html.erb'),
|
||||
html_footer: render_to_string(partial: 'index_edit_footer.html.erb')
|
||||
|
@ -51,7 +46,7 @@ class UserProjectsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to project_users_edit_path(format: :json), turbolinks: false
|
||||
redirect_to edit_project_users_path(format: :json), turbolinks: false
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -77,7 +72,7 @@ class UserProjectsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to project_users_edit_path(format: :json), turbolinks: false
|
||||
redirect_to edit_project_users_path(format: :json), turbolinks: false
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -97,7 +92,7 @@ class UserProjectsController < ApplicationController
|
|||
log_activity(:unassign_user_from_project)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to project_users_edit_path(format: :json),
|
||||
redirect_to edit_project_users_path(format: :json),
|
||||
turbolinks: false,
|
||||
status: :see_other
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
def create
|
||||
super
|
||||
|
||||
generate_demo_project
|
||||
generate_templates_project
|
||||
end
|
||||
|
||||
def two_factor_recovery
|
||||
|
@ -80,7 +80,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
session.delete(:otp_user_id)
|
||||
|
||||
sign_in(user)
|
||||
generate_demo_project
|
||||
generate_templates_project
|
||||
flash[:notice] = t('devise.sessions.signed_in')
|
||||
redirect_to stored_location_for(:user) || root_path
|
||||
else
|
||||
|
@ -100,7 +100,7 @@ class Users::SessionsController < Devise::SessionsController
|
|||
session.delete(:otp_user_id)
|
||||
if user.recover_2fa!(params[:recovery_code])
|
||||
sign_in(user)
|
||||
generate_demo_project
|
||||
generate_templates_project
|
||||
flash[:notice] = t('devise.sessions.signed_in')
|
||||
redirect_to root_path
|
||||
else
|
||||
|
@ -130,17 +130,9 @@ class Users::SessionsController < Devise::SessionsController
|
|||
end
|
||||
end
|
||||
|
||||
def generate_demo_project
|
||||
def generate_templates_project
|
||||
# Schedule templates creation for user
|
||||
TemplatesService.new.schedule_creation_for_user(current_user)
|
||||
|
||||
# Schedule demo project creation for user
|
||||
current_user.created_teams.each do |team|
|
||||
FirstTimeDataGenerator.delay(
|
||||
queue: :new_demo_project,
|
||||
priority: 10
|
||||
).seed_demo_data_with_id(current_user.id, team.id)
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.fatal("User ID #{current_user.id}: Error creating inital projects on sign_in: #{e.message}")
|
||||
end
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ExperimentsHelper
|
||||
def experiments_view_mode(project)
|
||||
return 'archived' if project.archived?
|
||||
|
||||
params[:view_mode] == 'archived' ? 'archived' : 'active'
|
||||
end
|
||||
|
||||
def grouped_by_prj(experiments)
|
||||
ungrouped_experiments = experiments.joins(:project)
|
||||
.select('projects.name as project_name,
|
||||
|
|
|
@ -30,7 +30,7 @@ module FileIconsHelper
|
|||
end
|
||||
|
||||
# For showing next to file
|
||||
def file_extension_icon(asset)
|
||||
def file_extension_icon(asset, report = false)
|
||||
file_ext = asset.file_name.split('.').last
|
||||
if Constants::FILE_TEXT_FORMATS.include?(file_ext)
|
||||
image_link = 'icon_small/docx_file.svg'
|
||||
|
@ -46,7 +46,14 @@ 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
|
||||
content_tag(:span) do
|
||||
concat ActionController::Base.helpers.image_tag(image_link, class: 'image-icon preview')
|
||||
concat wicked_pdf_image_tag(image_link, class: 'image-icon report')
|
||||
end
|
||||
else
|
||||
ActionController::Base.helpers.image_tag(image_link, class: 'image-icon')
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
|
@ -100,8 +107,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,
|
||||
|
|
|
@ -12,7 +12,7 @@ module GlobalActivitiesHelper
|
|||
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
|
||||
|
@ -63,7 +63,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?
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ class Asset < ApplicationRecord
|
|||
raise ArgumentError, 'Destination asset should be persisted first!' unless to_asset.persisted?
|
||||
|
||||
file.blob.open do |tmp_file|
|
||||
to_blob = ActiveStorage::Blob.create_after_upload!(io: tmp_file, filename: blob.filename, metadata: blob.metadata)
|
||||
to_blob = ActiveStorage::Blob.create_and_upload!(io: tmp_file, filename: blob.filename, metadata: blob.metadata)
|
||||
to_asset.file.attach(to_blob)
|
||||
end
|
||||
to_asset.post_process_file(to_asset.team)
|
||||
|
|
|
@ -33,10 +33,6 @@ class Experiment < ApplicationRecord
|
|||
validates :last_modified_by, presence: true
|
||||
validates :uuid, uniqueness: { scope: :project },
|
||||
unless: proc { |e| e.uuid.blank? }
|
||||
with_options if: :archived do |experiment|
|
||||
experiment.validates :archived_by, presence: true
|
||||
experiment.validates :archived_on, presence: true
|
||||
end
|
||||
|
||||
scope :is_archived, lambda { |is_archived|
|
||||
if is_archived
|
||||
|
@ -472,12 +468,17 @@ class Experiment < ApplicationRecord
|
|||
def normalize_module_positions
|
||||
# This method normalizes module positions so x-s and y-s
|
||||
# are all positive
|
||||
x_diff = my_modules.pluck(:x).min
|
||||
y_diff = my_modules.pluck(:y).min
|
||||
x_diff = my_modules.active.pluck(:x).min
|
||||
y_diff = my_modules.active.pluck(:y).min
|
||||
|
||||
my_modules.each do |m|
|
||||
m.update(x: m.x - x_diff, y: m.y - 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
|
||||
end
|
||||
|
||||
# Recalculate module groups in this project. Input is
|
||||
|
|
|
@ -2,6 +2,7 @@ class Project < ApplicationRecord
|
|||
include ArchivableModel
|
||||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
include ViewableModel
|
||||
|
||||
enum visibility: { hidden: 0, visible: 1 }
|
||||
|
||||
|
@ -150,6 +151,22 @@ class Project < ApplicationRecord
|
|||
.distinct
|
||||
end
|
||||
|
||||
def default_view_state
|
||||
{
|
||||
experiments: {
|
||||
active: { sort: 'new' },
|
||||
archived: { sort: 'new' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def validate_view_state(view_state)
|
||||
if %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
|
||||
end
|
||||
|
||||
def last_activities(count = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
|
||||
activities.order(created_at: :desc).first(count)
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ class ProjectFolder < ApplicationRecord
|
|||
validates :name,
|
||||
length: { minimum: Constants::NAME_MIN_LENGTH,
|
||||
maximum: Constants::NAME_MAX_LENGTH },
|
||||
uniqueness: { scope: %i(team_id parent_folder_id), case_sensitive: false }
|
||||
uniqueness: { scope: %i(team_id parent_folder_id archived), case_sensitive: false }
|
||||
validate :parent_folder_team, if: -> { parent_folder.present? }
|
||||
validate :parent_folder_validation, if: -> { parent_folder.present? }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -43,8 +43,6 @@ class Team < ApplicationRecord
|
|||
has_many :shared_repositories, through: :team_repositories, source: :repository
|
||||
|
||||
attr_accessor :without_templates
|
||||
attr_accessor :without_intro_demo
|
||||
after_create :generate_intro_demo
|
||||
|
||||
def default_view_state
|
||||
{
|
||||
|
@ -168,15 +166,4 @@ class Team < ApplicationRecord
|
|||
return if without_templates
|
||||
TemplatesService.new.delay(queue: :templates).update_team(self)
|
||||
end
|
||||
|
||||
include FirstTimeDataGenerator
|
||||
|
||||
def generate_intro_demo
|
||||
return if without_intro_demo
|
||||
|
||||
user = User.find(created_by_id)
|
||||
if user.created_teams.order(:created_at).first == self
|
||||
seed_demo_data(user, self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -194,7 +194,7 @@ class TinyMceAsset < ApplicationRecord
|
|||
raise ArgumentError, 'Destination TinyMce asset should be persisted first!' unless to_asset.persisted?
|
||||
|
||||
image.blob.open do |tmp_file|
|
||||
to_blob = ActiveStorage::Blob.create_after_upload!(io: tmp_file, filename: blob.filename, metadata: blob.metadata)
|
||||
to_blob = ActiveStorage::Blob.create_and_upload!(io: tmp_file, filename: blob.filename, metadata: blob.metadata)
|
||||
to_asset.image.attach(to_blob)
|
||||
end
|
||||
TinyMceAsset.update_estimated_size(to_asset.id)
|
||||
|
|
|
@ -62,8 +62,12 @@ Canaid::Permissions.register_for(Project) do
|
|||
end
|
||||
|
||||
# experiment: create
|
||||
can :create_experiments do |user, project|
|
||||
user.is_user_or_higher_of_project?(project)
|
||||
%i(create_experiments
|
||||
manage_experiments)
|
||||
.each do |perm|
|
||||
can perm do |user, project|
|
||||
user.is_user_or_higher_of_project?(project)
|
||||
end
|
||||
end
|
||||
|
||||
# project: create comment
|
||||
|
@ -93,3 +97,10 @@ Canaid::Permissions.register_for(ProjectComment) do
|
|||
user.is_owner_of_project?(project_comment.project))
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
@ -71,8 +71,8 @@ class ActivitiesService
|
|||
subjects_with_children = load_subjects_children('MyModule': [my_module.id])
|
||||
query = Activity.where(project: my_module.experiment.project)
|
||||
query.where(
|
||||
subjects_with_children.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR '),
|
||||
*subjects_with_children.flatten
|
||||
subjects_with_children.to_h.map { '(subject_type = ? AND subject_id IN(?))' }.join(' OR '),
|
||||
*subjects_with_children.to_h.flatten
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,17 +9,28 @@ module Experiments
|
|||
|
||||
def initialize(experiment:)
|
||||
@exp = experiment
|
||||
@graph = GraphViz.new(:G, type: :digraph, use: :neato)
|
||||
|
||||
@graph[:size] = '4,4'
|
||||
@graph.node[color: Constants::COLOR_ALTO,
|
||||
graph_params = {
|
||||
type: :digraph,
|
||||
use: :neato,
|
||||
inputscale: 3,
|
||||
size: '2,2',
|
||||
pad: '0.4',
|
||||
ratio: 'fill',
|
||||
splines: true,
|
||||
center: true,
|
||||
pack: true,
|
||||
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']
|
||||
|
||||
@graph.edge[color: Constants::COLOR_ALTO]
|
||||
@graph.edge[color: Constants::COLOR_VOLCANO, penwidth: '3.0']
|
||||
@errors = []
|
||||
end
|
||||
|
||||
|
@ -36,20 +47,11 @@ module Experiments
|
|||
private
|
||||
|
||||
def draw_diagram
|
||||
# Draw orphan nodes
|
||||
@exp.my_modules.without_group.each do |my_module|
|
||||
@graph.subgraph(rank: 'same').add_nodes(
|
||||
"Orphan-#{my_module.id}",
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
)
|
||||
end
|
||||
|
||||
# Draw grouped modules
|
||||
subg = {}
|
||||
@exp.my_module_groups.each_with_index do |group, gindex|
|
||||
subgraph_id = "cluster-#{gindex}"
|
||||
subg[subgraph_id] = @graph.subgraph(rank: 'same')
|
||||
subg[subgraph_id] = @graph.subgraph
|
||||
nodes = {}
|
||||
|
||||
group.my_modules.workflow_ordered.each_with_index do |my_module, index|
|
||||
|
@ -71,6 +73,15 @@ module Experiments
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Draw orphan nodes
|
||||
@exp.my_modules.without_group.each do |my_module|
|
||||
@graph.subgraph.add_nodes(
|
||||
"Orphan-#{my_module.id}",
|
||||
label: '',
|
||||
pos: "#{my_module.x / 10},-#{my_module.y / 10}!"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def save_file
|
||||
|
|
|
@ -20,15 +20,19 @@ 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!
|
||||
end
|
||||
|
||||
@errors.merge!(@exp.errors.to_hash) unless @exp.valid?
|
||||
|
|
92
app/services/experiments_overview_service.rb
Normal file
|
@ -0,0 +1,92 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExperimentsOverviewService
|
||||
def initialize(project, user, params)
|
||||
@project = project
|
||||
@user = user
|
||||
@params = params
|
||||
@view_state = @project.current_view_state(@user)
|
||||
@view_mode = if @project.archived?
|
||||
'archived'
|
||||
else
|
||||
params[:view_mode] || 'active'
|
||||
end
|
||||
|
||||
# Update sort if changed
|
||||
@sort = @view_state.state.dig('experiments', @view_mode, 'sort') || 'atoz'
|
||||
if @params[:sort] && @sort != @params[:sort] &&
|
||||
%w(new old atoz ztoa archived_old archived_new).include?(@params[:sort])
|
||||
@view_state.state['experiments'].merge!(Hash[@view_mode, { 'sort': @params[:sort] }.stringify_keys])
|
||||
@view_state.save!
|
||||
@sort = @view_state.state.dig('experiments', @view_mode, 'sort')
|
||||
end
|
||||
end
|
||||
|
||||
def experiments
|
||||
sort_records(
|
||||
filter_records(
|
||||
fetch_records
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_records
|
||||
@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)
|
||||
records = records.archived if @view_mode == 'archived' && @project.active?
|
||||
records = records.active if @view_mode == 'active'
|
||||
if @params[:search].present?
|
||||
records = records.where_attributes_like(%w(experiments.name experiments.description), @params[:search])
|
||||
end
|
||||
if @params[:created_on_from].present?
|
||||
records = records.where('experiments.created_at > ?', @params[:created_on_from])
|
||||
end
|
||||
records = records.where('experiments.created_at < ?', @params[:created_on_to]) if @params[:created_on_to].present?
|
||||
if @params[:updated_on_from].present?
|
||||
records = records.where('experiments.updated_at > ?', @params[:updated_on_from])
|
||||
end
|
||||
records = records.where('experiments.updated_at < ?', @params[:updated_on_to]) if @params[:updated_on_to].present?
|
||||
|
||||
if @params[:archived_on_from].present?
|
||||
records = records.where('COALESCE(experiments.archived_on, projects.archived_on) > ?', @params[:archived_on_from])
|
||||
end
|
||||
if @params[:archived_on_to].present?
|
||||
records = records.where('COALESCE(experiments.archived_on, projects.archived_on) < ?', @params[:archived_on_to])
|
||||
end
|
||||
records
|
||||
end
|
||||
|
||||
def sort_records(records)
|
||||
case @sort
|
||||
when 'new'
|
||||
records.order(created_at: :desc)
|
||||
when 'old'
|
||||
records.order(:created_at)
|
||||
when 'atoz'
|
||||
records.order(:name)
|
||||
when 'ztoa'
|
||||
records.order(name: :desc)
|
||||
when 'archived_old'
|
||||
records.group('projects.archived_on')
|
||||
.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) ASC'))
|
||||
when 'archived_new'
|
||||
records.group('projects.archived_on')
|
||||
.order(Arel.sql('COALESCE(experiments.archived_on, projects.archived_on) DESC'))
|
||||
else
|
||||
records
|
||||
end
|
||||
end
|
||||
end
|
|
@ -110,8 +110,9 @@ class ProjectsOverviewService
|
|||
records = records.archived if @view_mode == 'archived'
|
||||
records = records.active if @view_mode == 'active'
|
||||
records = records.where_attributes_like('projects.name', @params[:search]) if @params[:search].present?
|
||||
records = records.where_attributes_like('projects.name', @params[:search]) if @params[:search].present?
|
||||
records = records.joins(:user_projects).where('user_projects.user_id IN (?)', @params[:members]) if @params[:members]&.any?
|
||||
if @params[:members].present?
|
||||
records = records.joins(:user_projects).where('user_projects.user_id IN (?)', @params[:members])
|
||||
end
|
||||
records = records.where('projects.created_at > ?', @params[:created_on_from]) if @params[:created_on_from].present?
|
||||
records = records.where('projects.created_at < ?', @params[:created_on_to]) if @params[:created_on_to].present?
|
||||
records = records.where('projects.archived_on < ?', @params[:archived_on_to]) if @params[:archived_on_to].present?
|
||||
|
|
|
@ -11,7 +11,7 @@ module RepositoryActions
|
|||
log_activity(:archive_inventory_item, row) if @log_activities
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
@errors[:archiving_error] = I18n.t('repositories.archive_records.unsuccess_flash', @repository.name)
|
||||
@errors[:archiving_error] = I18n.t('repositories.archive_records.unsuccess_flash', repository: @repository.name)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ module RepositoryActions
|
|||
log_activity(:restore_inventory_item, row) if @log_activities
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
@errors[:restoring_error] = I18n.t('repositories.restore_records.unsuccess_flash', @repository.name)
|
||||
@errors[:restoring_error] = I18n.t('repositories.restore_records.unsuccess_flash', repository: @repository.name)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class RepositoryDatatableService
|
|||
@params = params
|
||||
@sortable_columns = build_sortable_columns
|
||||
create_columns_mappings
|
||||
process_query
|
||||
@repository_rows = process_query
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -57,7 +57,7 @@ class RepositoryDatatableService
|
|||
.select('COUNT(DISTINCT experiments.project_id) AS "assigned_projects_count"')
|
||||
repository_rows = repository_rows.preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
|
||||
|
||||
@repository_rows = sort_rows(order_obj, repository_rows)
|
||||
sort_rows(order_obj, repository_rows)
|
||||
end
|
||||
|
||||
def fetch_rows(search_value)
|
||||
|
|
|
@ -57,7 +57,6 @@ class TeamImporter
|
|||
team_last_modified_by = team.last_modified_by_id
|
||||
team.last_modified_by_id = nil
|
||||
team.without_templates = true
|
||||
team.without_intro_demo = true
|
||||
team.save!
|
||||
|
||||
create_users(team_json['users'], team)
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DelayedUploaderDemo
|
||||
# Get asset from demo_files folder
|
||||
def self.get_asset(user, team, file_name)
|
||||
asset = Asset.create(
|
||||
created_by: user,
|
||||
team: team,
|
||||
last_modified_by: user
|
||||
)
|
||||
asset.file.attach(io: File.open("#{Rails.root}/app/assets/demo_files/#{file_name}", 'r'), filename: file_name)
|
||||
asset
|
||||
end
|
||||
|
||||
# Generates results asset for given module, file_name assumes file is located
|
||||
# in demo_files.
|
||||
def self.generate_result_asset(
|
||||
my_module:,
|
||||
current_user:,
|
||||
current_team:,
|
||||
result_name:,
|
||||
created_at: Time.now,
|
||||
file_name:,
|
||||
comment: nil
|
||||
)
|
||||
temp_asset = get_asset(current_user, current_team, file_name)
|
||||
temp_result = Result.new(
|
||||
created_at: created_at,
|
||||
user: current_user,
|
||||
my_module: my_module,
|
||||
name: result_name,
|
||||
asset: temp_asset
|
||||
)
|
||||
|
||||
temp_result.save
|
||||
|
||||
# Generate comment if it exists
|
||||
generate_result_comment(temp_result, current_user, comment) if comment
|
||||
|
||||
temp_asset.post_process_file(my_module.experiment.project.team)
|
||||
end
|
||||
|
||||
# Adds asset to existing step
|
||||
def self.add_step_asset(step:, current_user:, current_team:, file_name:)
|
||||
temp_asset =
|
||||
DelayedUploaderDemo.get_asset(current_user, current_team, file_name)
|
||||
step.assets << temp_asset
|
||||
temp_asset.post_process_file(step.my_module.experiment.project.team)
|
||||
end
|
||||
|
||||
def self.generate_result_comment(result, user, message, created_at = nil)
|
||||
created_at ||= result.created_at
|
||||
ResultComment.create(
|
||||
user: user,
|
||||
message: message,
|
||||
created_at: created_at,
|
||||
result: result
|
||||
)
|
||||
end
|
||||
end
|
|
@ -157,7 +157,7 @@ module ProtocolsImporter
|
|||
|
||||
# Decode the file bytes
|
||||
file = StringIO.new(Base64.decode64(tiny_mce_img_json['bytes']))
|
||||
to_blob = ActiveStorage::Blob.create_after_upload!(io: file,
|
||||
to_blob = ActiveStorage::Blob.create_and_upload!(io: file,
|
||||
filename: tiny_mce_img_json['fileName'],
|
||||
content_type: tiny_mce_img_json['fileType'],
|
||||
metadata: JSON.parse(tiny_mce_img_json['fileMetadata'] || '{}'))
|
||||
|
|
|
@ -30,12 +30,14 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div class="task-selector-container pull-left" data-view-mode="archived" data-task-id="<%= my_module.id %>">
|
||||
<div class="sci-checkbox-container">
|
||||
<input value="1" type="checkbox" class="sci-checkbox task-selector">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
<% unless my_module.experiment.archived_branch? %>
|
||||
<div class="task-selector-container pull-left " data-view-mode="archived" data-task-id="<%= my_module.id %>">
|
||||
<div class="sci-checkbox-container">
|
||||
<input value="1" type="checkbox" class="sci-checkbox task-selector">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
data-disable-placeholder="<%= t("dashboard.current_tasks.filter.select_experiment") %>"
|
||||
data-placeholder="<%= t("dashboard.current_tasks.filter.select_experiment") %>"></select>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="footer center">
|
||||
<div class="btn btn-primary apply-filters"><%= t("dashboard.current_tasks.filter.apply") %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
<!-- Clone experiment modal -->
|
||||
<div class="modal"
|
||||
id="clone-experiment-modal-<%= @experiment.id %>"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="clone-experiment-modal-label">
|
||||
<%= bootstrap_form_for @experiment, url: clone_experiment_path(),
|
||||
method: :post do |f| %>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal" id="clone-experiment-modal-<%= @experiment.id %>" tabindex="-1" role="dialog" aria-labelledby="clone-experiment-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<%= bootstrap_form_for @experiment, url: clone_experiment_path(@experiment), method: :post do |f| %>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="clone-experiment-modal-label"><%= t("experiments.clone.modal_title", experiment: @experiment.name ) %></h5>
|
||||
|
@ -29,7 +23,7 @@
|
|||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<%= f.submit t("experiments.clone.modal_submit"), class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
20
app/views/experiments/_details_modal.html.erb
Normal file
|
@ -0,0 +1,20 @@
|
|||
<div class="modal" id="experimentDetailsModal" tabindex="-1" role="dialog" aria-labelledby="experimentDetailsModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="experimentDetailsModalLabel">
|
||||
<%= t('experiments.index.details_modal.description', name: @experiment.name) %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<%= @experiment.description %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= t('general.close') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -29,6 +29,7 @@
|
|||
<li><%= link_to t('experiments.archive.label_title'),
|
||||
archive_experiment_url(experiment),
|
||||
type: 'button',
|
||||
method: :post,
|
||||
data: { confirm: t('experiments.canvas.archive_confirm') } %></li>
|
||||
<% end %>
|
||||
<% if can_restore_experiment?(experiment) %>
|
||||
|
|
|
@ -1,24 +1,21 @@
|
|||
<!-- Edit experiment modal -->
|
||||
<div class="modal"
|
||||
id="edit-experiment-modal-<%= @experiment.id %>"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="edit-experiment-modal-label">
|
||||
<%= bootstrap_form_for [@project, @experiment], remote: true do |f| %>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal" id="edit-experiment-modal-<%= @experiment.id %>" tabindex="-1" role="dialog" aria-labelledby="edit-experiment-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<%= bootstrap_form_for [@project, @experiment], remote: true, html: { class: 'experiment-action-form' } do |f| %>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="edit-eperiment-modal-label"><%= t("experiments.edit.modal_title", experiment: @experiment.name ) %></h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="<%= t('general.close') %>"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="edit-eperiment-modal-label">
|
||||
<%= t('experiments.edit.modal_title', experiment: @experiment.name ) %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<%= render partial: "form.html.erb", locals: { form: f } %>
|
||||
<%= render partial: 'form.html.erb', locals: { form: f } %>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<%= f.submit t("experiments.edit.modal_create"), class: "btn btn-primary" %>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t('general.cancel') %></button>
|
||||
<%= f.submit t('experiments.edit.modal_create'), class: 'btn btn-primary' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="form-group">
|
||||
<%= form.text_field :name,
|
||||
label: t("experiments.new.name"),
|
||||
label: t('experiments.new.name'),
|
||||
autofocus: true,
|
||||
placeholder: t("experiments.new.name_placeholder"),
|
||||
id: 'experiment-name'%>
|
||||
placeholder: t('experiments.new.name_placeholder'),
|
||||
id: 'experiment-name' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|