mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 06:35:56 +08:00
Merge branch 'master' of https://github.com/biosistemika/scinote-web into office_integration
This commit is contained in:
commit
a434649f42
34
.eslintrc.json
Normal file
34
.eslintrc.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"extends": "google",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jquery": true
|
||||
},
|
||||
"rules": {
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"markers": [
|
||||
"="
|
||||
]
|
||||
}
|
||||
],
|
||||
"lines-around-comment": [
|
||||
"warn",
|
||||
{
|
||||
"beforeLineComment": false
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"_": true
|
||||
}
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -49,3 +49,6 @@ tags
|
|||
|
||||
# Ignore rubocop cache files
|
||||
rubocop_cache
|
||||
|
||||
# Ignore ESLint local instalation
|
||||
node_modules
|
||||
|
|
|
@ -1,2 +1,9 @@
|
|||
ruby:
|
||||
config_file: .rubocop.yml
|
||||
|
||||
eslint:
|
||||
enabled: true
|
||||
config_file: .eslintrc.json
|
||||
|
||||
scss:
|
||||
config_file: .scss-lint.yml
|
||||
|
|
6
.jsbeautifyrc
Normal file
6
.jsbeautifyrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"erb": {
|
||||
"indent_size": 2,
|
||||
"wrap_line_length": 80
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ AllCops:
|
|||
- "vendor/**/*"
|
||||
- "db/schema.rb"
|
||||
UseCache: false
|
||||
TargetRubyVersion: 2.2
|
||||
|
||||
##################### Style ####################################
|
||||
|
||||
|
|
258
.scss-lint.yml
Normal file
258
.scss-lint.yml
Normal file
|
@ -0,0 +1,258 @@
|
|||
# Default application configuration that all configurations inherit from.
|
||||
|
||||
scss_files: "app/assets/javascripts/**/*.scss"
|
||||
plugin_directories: ['.scss-linters']
|
||||
|
||||
# List of gem names to load custom linters from (make sure they are already
|
||||
# installed)
|
||||
plugin_gems: []
|
||||
|
||||
# Default severity of all linters.
|
||||
severity: warning
|
||||
|
||||
linters:
|
||||
BangFormat:
|
||||
enabled: true
|
||||
space_before_bang: true
|
||||
space_after_bang: false
|
||||
|
||||
BemDepth:
|
||||
enabled: false
|
||||
max_elements: 1
|
||||
|
||||
BorderZero:
|
||||
enabled: true
|
||||
convention: zero # or `none`
|
||||
|
||||
ChainedClasses:
|
||||
enabled: false
|
||||
|
||||
ColorKeyword:
|
||||
enabled: true
|
||||
|
||||
ColorVariable:
|
||||
enabled: true
|
||||
|
||||
Comment:
|
||||
enabled: true
|
||||
style: silent
|
||||
|
||||
DebugStatement:
|
||||
enabled: true
|
||||
|
||||
DeclarationOrder:
|
||||
enabled: true
|
||||
|
||||
DisableLinterReason:
|
||||
enabled: false
|
||||
|
||||
DuplicateProperty:
|
||||
enabled: true
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
ignore_single_line_blocks: true
|
||||
|
||||
EmptyRule:
|
||||
enabled: true
|
||||
|
||||
ExtendDirective:
|
||||
enabled: false
|
||||
|
||||
FinalNewline:
|
||||
enabled: true
|
||||
present: true
|
||||
|
||||
HexLength:
|
||||
enabled: true
|
||||
style: short # or 'long'
|
||||
|
||||
HexNotation:
|
||||
enabled: true
|
||||
style: lowercase # or 'uppercase'
|
||||
|
||||
HexValidation:
|
||||
enabled: true
|
||||
|
||||
IdSelector:
|
||||
enabled: true
|
||||
|
||||
ImportantRule:
|
||||
enabled: true
|
||||
|
||||
ImportPath:
|
||||
enabled: true
|
||||
leading_underscore: false
|
||||
filename_extension: false
|
||||
|
||||
Indentation:
|
||||
enabled: true
|
||||
allow_non_nested_indentation: false
|
||||
character: space # or 'tab'
|
||||
width: 2
|
||||
|
||||
LeadingZero:
|
||||
enabled: true
|
||||
style: exclude_zero # or 'include_zero'
|
||||
|
||||
MergeableSelector:
|
||||
enabled: true
|
||||
force_nesting: true
|
||||
|
||||
NameFormat:
|
||||
enabled: true
|
||||
allow_leading_underscore: true
|
||||
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
|
||||
|
||||
NestingDepth:
|
||||
enabled: true
|
||||
max_depth: 3
|
||||
ignore_parent_selectors: false
|
||||
|
||||
PlaceholderInExtend:
|
||||
enabled: true
|
||||
|
||||
PrivateNamingConvention:
|
||||
enabled: false
|
||||
prefix: _
|
||||
|
||||
PropertyCount:
|
||||
enabled: false
|
||||
include_nested: false
|
||||
max_properties: 10
|
||||
|
||||
PropertySortOrder:
|
||||
enabled: true
|
||||
ignore_unspecified: false
|
||||
min_properties: 2
|
||||
separate_groups: false
|
||||
|
||||
PropertySpelling:
|
||||
enabled: true
|
||||
extra_properties: []
|
||||
disabled_properties: []
|
||||
|
||||
PropertyUnits:
|
||||
enabled: true
|
||||
global: [
|
||||
'ch', 'em', 'ex', 'rem', # Font-relative lengths
|
||||
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
|
||||
'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
|
||||
'deg', 'grad', 'rad', 'turn', # Angle
|
||||
'ms', 's', # Duration
|
||||
'Hz', 'kHz', # Frequency
|
||||
'dpi', 'dpcm', 'dppx', # Resolution
|
||||
'%'] # Other
|
||||
properties: {}
|
||||
|
||||
PseudoElement:
|
||||
enabled: true
|
||||
|
||||
QualifyingElement:
|
||||
enabled: true
|
||||
allow_element_with_attribute: false
|
||||
allow_element_with_class: false
|
||||
allow_element_with_id: false
|
||||
|
||||
SelectorDepth:
|
||||
enabled: true
|
||||
max_depth: 3
|
||||
|
||||
SelectorFormat:
|
||||
enabled: true
|
||||
convention: hyphenated_lowercase # or 'strict_BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
|
||||
|
||||
Shorthand:
|
||||
enabled: true
|
||||
allowed_shorthands: [1, 2, 3, 4]
|
||||
|
||||
SingleLinePerProperty:
|
||||
enabled: true
|
||||
allow_single_line_rule_sets: true
|
||||
|
||||
SingleLinePerSelector:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterComma:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space', or 'at_least_one_space'
|
||||
|
||||
SpaceAfterComment:
|
||||
enabled: false
|
||||
style: one_space # or 'no_space', or 'at_least_one_space'
|
||||
allow_empty_comments: true
|
||||
|
||||
SpaceAfterPropertyColon:
|
||||
enabled: true
|
||||
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
|
||||
|
||||
SpaceAfterPropertyName:
|
||||
enabled: true
|
||||
|
||||
SpaceAfterVariableColon:
|
||||
enabled: false
|
||||
style: one_space # or 'no_space', 'at_least_one_space' or 'one_space_or_newline'
|
||||
|
||||
SpaceAfterVariableName:
|
||||
enabled: true
|
||||
|
||||
SpaceAroundOperator:
|
||||
enabled: true
|
||||
style: one_space # or 'at_least_one_space', or 'no_space'
|
||||
|
||||
SpaceBeforeBrace:
|
||||
enabled: true
|
||||
style: space # or 'new_line'
|
||||
allow_single_line_padding: false
|
||||
|
||||
SpaceBetweenParens:
|
||||
enabled: true
|
||||
spaces: 0
|
||||
|
||||
StringQuotes:
|
||||
enabled: true
|
||||
style: single_quotes # or double_quotes
|
||||
|
||||
TrailingSemicolon:
|
||||
enabled: true
|
||||
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
TrailingZero:
|
||||
enabled: false
|
||||
|
||||
TransitionAll:
|
||||
enabled: false
|
||||
|
||||
UnnecessaryMantissa:
|
||||
enabled: true
|
||||
|
||||
UnnecessaryParentReference:
|
||||
enabled: true
|
||||
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
|
||||
UrlQuotes:
|
||||
enabled: true
|
||||
|
||||
VariableForProperty:
|
||||
enabled: false
|
||||
properties: []
|
||||
|
||||
VendorPrefix:
|
||||
enabled: true
|
||||
identifier_list: base
|
||||
additional_identifiers: []
|
||||
excluded_identifiers: []
|
||||
|
||||
ZeroUnit:
|
||||
enabled: true
|
||||
|
||||
Compass::*:
|
||||
enabled: false
|
|
@ -1,39 +0,0 @@
|
|||
# Contributing to *sciNote*
|
||||
|
||||
We'd love to hear your feedback. *sciNote* was developed with the aim to be a collaborative open-source project, so please open new issues describing any bugs, feature requests or suggestions that you have.
|
||||
|
||||
**DISCLAIMER: We firmly believe that our vision for the future of sciNote is the right one, therefore we reserve the right to accept OR reject any reported bugs and opened pull requests.**
|
||||
|
||||
### Non code-related feedback
|
||||
|
||||
This GitHub repository is a place to discuss *sciNote* source code. If you have any questions regarding usage of *sciNote*, tutorials, etc., please contact our support by sending an email to [info@scinote.net](mailto:info@scinote.net).
|
||||
|
||||
If we receive a lot of similar feedback, we will try to provide better instructions / readme-s / tutorials.
|
||||
|
||||
## Issue Reporting
|
||||
|
||||
For issue reporting, please visit our [Jira page](https://scinote.atlassian.net). It is an open source Jira that requires no sign-up (you can report issues as anonymous user). Detailed instructions about issue reporting are documented there.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We will consider high quality pull requests.
|
||||
|
||||
### Things to Keep in Mind
|
||||
|
||||
1. Whenever pull request requires additional work to be done on the client side (e.g. including a new Gem will requires calling of `make build`, adding database changes requires `rake db:migrate` task, ...), please specify that in the pull request description.
|
||||
|
||||
2. Whenever implementing changes to the database layer, make sure to apply changes on the following locations:
|
||||
* Code change/s inside `ActiveRecord`-s as needed,
|
||||
* Code database migrations that need to be:
|
||||
* **reversible** - this can be done by using `up` and `down` methods.
|
||||
* Fix model tests so they aren't potentially broken and (optionally) add new tests that will test the new functionality. Calling `rake test:models` **must result in 0 errors & 0 failures**.
|
||||
* *(related to model tests)* Fix the fixtures so they are in lieu with the database changes.
|
||||
* Fix the fake seeding rake task - [db_fake_data.rake](lib/tasks/db_fake_data.rake), so it will auto-generate potential new changes.
|
||||
* Fix the demo tutorial seeding method - [first_time_data_generator.rb](app/utilities/first_time_data_generator.rb), so it will auto-generate potential new changes.
|
||||
* When merging a database-related pull request, always make sure that [schema.rb](db/schema.rb) gets updated. This often means editing `schema.rb` by hand. Make sure all changes are persisted into this document, and that the schema version (`ActiveRecord::Schema.define(version: <version>)`) equals to the last migration in the application.
|
||||
|
||||
### Contributor License Agreement (CLA)
|
||||
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username.
|
||||
|
||||
[Complete your CLA here](https://www.clahub.com/agreements/biosistemika/scinote-web)
|
12
Gemfile
12
Gemfile
|
@ -7,11 +7,13 @@ gem 'figaro'
|
|||
gem 'pg'
|
||||
gem 'devise', '3.5.6'
|
||||
gem 'devise_invitable'
|
||||
gem 'simple_token_authentication', '~> 1.0' # Token authentication for Devise
|
||||
gem 'bootstrap-sass', '~> 3.3.5'
|
||||
gem 'sass-rails', '~> 5.0'
|
||||
gem 'bootstrap_form'
|
||||
gem 'yomu'
|
||||
gem 'font-awesome-rails', '~> 4.6'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
|
||||
# JS datetime library, requirement of datetime picker
|
||||
gem 'momentjs-rails', '>= 2.9.0'
|
||||
|
@ -29,6 +31,7 @@ gem 'hammerjs-rails'
|
|||
gem 'introjs-rails' # Create quick tutorials
|
||||
gem 'js_cookie_rails' # Simple JS API for cookies
|
||||
gem 'spinjs-rails'
|
||||
gem 'autosize-rails' # jQuery autosize plugin
|
||||
|
||||
gem 'underscore-rails'
|
||||
gem 'turbolinks'
|
||||
|
@ -46,9 +49,12 @@ gem 'roo', '~> 2.1.0' # Spreadsheet parser
|
|||
gem 'wicked_pdf'
|
||||
gem 'wkhtmltopdf-heroku'
|
||||
gem 'remotipart', '~> 1.2' # Async file uploads
|
||||
gem 'redcarpet' # Markdown parser
|
||||
gem 'faker' # Generate fake data
|
||||
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces from ActiveRecord or ActiveModel attributes
|
||||
gem 'deface', '~> 1.0'
|
||||
gem 'nokogiri' # HTML/XML parser
|
||||
gem 'sneaky-save', git: 'git://github.com/einzige/sneaky-save.git'
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
|
||||
gem 'paperclip', '~> 4.3' # File attachment, image attachment library
|
||||
gem 'aws-sdk', '~> 2.2.8'
|
||||
|
@ -56,6 +62,9 @@ gem 'aws-sdk-v1'
|
|||
gem 'delayed_job_active_record'
|
||||
gem 'devise-async'
|
||||
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
|
||||
gem 'quill-rails', # Rich text editor
|
||||
git: 'https://github.com/biosistemika/quill-rails.git',
|
||||
ref: 'e765c04'
|
||||
|
||||
gem 'nokogiri' # XML parser
|
||||
|
||||
|
@ -65,6 +74,7 @@ group :development, :test do
|
|||
gem 'binding_of_caller'
|
||||
gem 'awesome_print'
|
||||
gem 'rubocop', require: false
|
||||
gem 'scss_lint', require: false
|
||||
gem 'starscope', require: false
|
||||
end
|
||||
|
||||
|
|
42
Gemfile.lock
42
Gemfile.lock
|
@ -1,3 +1,18 @@
|
|||
GIT
|
||||
remote: git://github.com/einzige/sneaky-save.git
|
||||
revision: 03866e838f62a4b13e15784974fcc13e14cd9502
|
||||
specs:
|
||||
sneaky-save (0.1.1)
|
||||
activerecord (>= 3.2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/biosistemika/quill-rails.git
|
||||
revision: e765c042d9ff7ec22f8c51a0e08e39e783530f8b
|
||||
ref: e765c04
|
||||
specs:
|
||||
quill-rails (0.1.4)
|
||||
railties (>= 3.1.0, < 5.0)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
|
@ -48,6 +63,8 @@ GEM
|
|||
autoprefixer-rails (6.1.2)
|
||||
execjs
|
||||
json
|
||||
autosize-rails (1.18.17)
|
||||
rails (>= 3.1)
|
||||
awesome_print (1.7.0)
|
||||
aws-sdk (2.2.8)
|
||||
aws-sdk-resources (= 2.2.8)
|
||||
|
@ -90,9 +107,15 @@ GEM
|
|||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
colorize (0.8.1)
|
||||
commit_param_routing (0.0.1)
|
||||
concurrent-ruby (1.0.0)
|
||||
debug_inspector (0.0.2)
|
||||
deface (1.0.2)
|
||||
colorize (>= 0.5.8)
|
||||
nokogiri (~> 1.6.0)
|
||||
polyglot
|
||||
rails (>= 3.1)
|
||||
delayed_job (4.1.1)
|
||||
activesupport (>= 3.0, < 5.0)
|
||||
delayed_job_active_record (4.1.0)
|
||||
|
@ -184,6 +207,7 @@ GEM
|
|||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pkg-config (1.1.7)
|
||||
polyglot (0.3.5)
|
||||
powerpack (0.1.1)
|
||||
puma (2.15.3)
|
||||
rack (1.6.4)
|
||||
|
@ -211,6 +235,8 @@ GEM
|
|||
rails_12factor (0.0.3)
|
||||
rails_serve_static_assets
|
||||
rails_stdout_logging
|
||||
rails_autolink (1.1.6)
|
||||
rails (> 3.1)
|
||||
rails_serve_static_assets (0.0.4)
|
||||
rails_stdout_logging (0.0.4)
|
||||
railties (4.2.5)
|
||||
|
@ -221,7 +247,6 @@ GEM
|
|||
rainbow (2.1.0)
|
||||
rake (11.2.2)
|
||||
rdoc (4.2.0)
|
||||
redcarpet (3.3.3)
|
||||
remotipart (1.2.1)
|
||||
responders (2.2.0)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
|
@ -248,12 +273,19 @@ GEM
|
|||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
scss_lint (0.50.2)
|
||||
rake (>= 0.9, < 12)
|
||||
sass (~> 3.4.20)
|
||||
sdoc (0.4.1)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
rdoc (~> 4.0)
|
||||
shoulda-context (1.2.1)
|
||||
shoulda-matchers (3.1.1)
|
||||
activesupport (>= 4.0.0)
|
||||
simple_token_authentication (1.14.0)
|
||||
actionmailer (>= 3.2.6, < 6)
|
||||
actionpack (>= 3.2.6, < 6)
|
||||
devise (>= 3.2, < 6)
|
||||
skylight (0.10.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sourcemap (0.1.1)
|
||||
|
@ -301,6 +333,7 @@ DEPENDENCIES
|
|||
ajax-datatables-rails (~> 0.3.1)
|
||||
aspector
|
||||
auto_strip_attributes (~> 2.1)
|
||||
autosize-rails
|
||||
awesome_print
|
||||
aws-sdk (~> 2.2.8)
|
||||
aws-sdk-v1
|
||||
|
@ -313,6 +346,7 @@ DEPENDENCIES
|
|||
bootstrap_form
|
||||
byebug
|
||||
commit_param_routing
|
||||
deface (~> 1.0)
|
||||
delayed_job_active_record
|
||||
devise (= 3.5.6)
|
||||
devise-async
|
||||
|
@ -337,19 +371,23 @@ DEPENDENCIES
|
|||
paperclip (~> 4.3)
|
||||
pg
|
||||
puma
|
||||
quill-rails!
|
||||
rails (= 4.2.5)
|
||||
rails_12factor
|
||||
redcarpet
|
||||
rails_autolink (~> 1.1, >= 1.1.6)
|
||||
remotipart (~> 1.2)
|
||||
rgl
|
||||
roo (~> 2.1.0)
|
||||
rubocop
|
||||
ruby-graphviz (~> 1.2)
|
||||
sass-rails (~> 5.0)
|
||||
scss_lint
|
||||
sdoc (~> 0.4.0)
|
||||
shoulda-context
|
||||
shoulda-matchers (>= 3.0.1)
|
||||
simple_token_authentication (~> 1.0)
|
||||
skylight
|
||||
sneaky-save!
|
||||
spinjs-rails
|
||||
starscope
|
||||
turbolinks
|
||||
|
|
|
@ -1516,47 +1516,39 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
RedCarpet Gem
|
||||
Quill
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2009, Natacha Porté
|
||||
Copyright (c) 2015, Vicent Marti
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Markdown
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Copyright © 2004, John Gruber
|
||||
http://daringfireball.net/
|
||||
Copyright (c) 2014, Jason Chen
|
||||
Copyright (c) 2013, salesforce.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Faker Gem
|
||||
|
|
3
Makefile
3
Makefile
|
@ -34,6 +34,9 @@ start:
|
|||
stop:
|
||||
@docker-compose stop
|
||||
|
||||
worker:
|
||||
@$(MAKE) rails cmd="rake jobs:work"
|
||||
|
||||
cli:
|
||||
@$(MAKE) rails cmd="/bin/bash"
|
||||
|
||||
|
|
184
README.md
184
README.md
|
@ -4,195 +4,19 @@
|
|||
|
||||
## About
|
||||
|
||||
sciNote is an open source electronic lab notebook ([ELN](https://en.wikipedia.org/wiki/Electronic_lab_notebook)) that helps you manage your laboratory work and stores all your experimental data in one place. sciNote is specifically designed for life science students, researchers, lab technicians and group leaders.
|
||||
[sciNote is an open source electronic lab notebook](http://www.scinote.net) ([ELN](https://en.wikipedia.org/wiki/Electronic_lab_notebook)) that helps you manage your laboratory work and stores all your experimental data in one place. sciNote is specifically designed for life science students, researchers, lab technicians and group leaders.
|
||||
|
||||
## Build & run
|
||||
|
||||
sciNote is developed in [Ruby on Rails](http://rubyonrails.org/). It also makes use of [Docker](https://www.docker.com/) technology, so the easiest way to run it is inside Docker containers.
|
||||
|
||||
### Quick start
|
||||
|
||||
The following are minimal steps needed to start sciNote in development environment:
|
||||
|
||||
1. Clone this Git repository onto your development machine.
|
||||
2. Create a file `config/application.yml`. Populate it with mandatory environmental variables (see [environmental variables](#user-content-environmental-variables)).
|
||||
3. In sciNote folder, run the following command: `make docker`. This can take a while, since Docker must first pull an image from the Internet, and then also install all neccesary Gems required by sciNote.
|
||||
4. Once the Docker image is created, run `make cli` command. Once inside the running Docker container, run the following command: `rake db:reset`. This should initialize the database and fill it with (very minimal) seed data.
|
||||
5. Exit the Docker container by typing `exit`.
|
||||
6. To start the server, run command `make run`. Wait until the server starts listening on port `3000`.
|
||||
7. Open your favourite browser and navigate to [http://localhost:3000](http://localhost:3000/). Use the seeded administrator account from [seeds.rb](db/seeds.rb) to login, or sign up for a new account.
|
||||
|
||||
### OS-specific Install Instructions
|
||||
|
||||
**Debian**
|
||||
|
||||
1. Install Docker and add user `1000` to the docker group as described [here](https://docs.docker.com/engine/installation/linux/debian/).
|
||||
2. Install Docker Compose as described [here](https://docs.docker.com/compose/install/).
|
||||
3. Follow [Quick Start Guide](#user-content-quick-start) above as user `1000`.
|
||||
|
||||
**Mac OS X**
|
||||
|
||||
1. Install command line developer tools (there are many resources online, like [this](http://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/)).
|
||||
2. Install Docker Toolbox as described [here](https://docs.docker.com/mac/step_one/).
|
||||
3. Inside CLI, run `git clone https://github.com/biosistemika/scinote-web.git`.
|
||||
4. Run Docker Quickstart Terminal (also described [here](https://docs.docker.com/mac/step_one/)).
|
||||
5. Inside this terminal, navigate to cloned Git folder.
|
||||
6. Follow the [Quick Start Guide](#user-content-quick-start) above.
|
||||
7. When opening sciNote in browser, instead of navigating to `localhost:3000`, navigate to `<docker-machine-ip>:3000` (you can get the docker machine IP by running command `docker-machine ip default`).
|
||||
|
||||
### Docker structure
|
||||
|
||||
The main sciNote application runs in a Docker container called `web`. The database runs in a separate container, called `db`. This database container makes use of a special, persistent container called `dbdata`.
|
||||
|
||||
### Commands
|
||||
|
||||
Call `make` commands to build Docker images and build Rails environment, including database.
|
||||
|
||||
Following commands are available:
|
||||
|
||||
| Command | Description |
|
||||
|----------------|-------------------------------------------------------------------------------------------------|
|
||||
| `make docker` | Downloads the Docker image and build Gems. This should be called whenever `Gemfile` is changed. |
|
||||
| `make db-cli` | Runs a `/bin/bash` inside the `db` container. |
|
||||
| `make run` | Runs the `db` container & starts the Rails server in `web` container. |
|
||||
| `make start` | Runs the `db` container & starts the Rails server in `web` container in background. |
|
||||
| `make stop` | Stops the `db` & `web` containers. |
|
||||
| `make cli` | Runs a `/bin/bash` inside the `web` container. |
|
||||
| `make tests` | Execute all Rails tests. |
|
||||
| `make console` | Enters the Rails console in `web` container. |
|
||||
| `make export` | Zips the head of this Git repository into a `.tar.gz` file. |
|
||||
|
||||
## Environmental variables
|
||||
|
||||
sciNote reads configuration parameters from system environment parameters. On production servers, this can be simply be system environmental variables, while for development, a file `config/application.yml` can be created to specify those variables.
|
||||
|
||||
The following table describes all available environmental variables for sciNote server.
|
||||
|
||||
| Variable | Mandatory | Description |
|
||||
|-------------------------|-----------|-------------|
|
||||
| SECRET_KEY_BASE | Yes | Random hash for Rails encryption. Can be generated by running `rake secret`. |
|
||||
| PAPERCLIP_STORAGE | Yes | Set to `'s3'` to store files on Amazon S3, or `'filesystem'` to store files on local server. If storing on S3, additional parameters need to be specified. |
|
||||
| AWS_SECRET_ACCESS_KEY | No* | If storing files on Amazon S3, this must contain access key for accessing AWS S3 API. |
|
||||
| AWS_ACCESS_KEY_ID | No* | If storing files on Amazon S3, this must contain access key ID for AWS S3. |
|
||||
| S3_BUCKET | No* | If storing files on Amazon S3, this must contain S3 bucket on which files are stored. |
|
||||
| AWS_REGION | No* | If storing files on Amazon S3, this must contain the AWS region. |
|
||||
| PAPERCLIP_DIRECT_UPLOAD | No* | If storing files on Amazon S3, this must be set either to `1` (to upload files directly from client-side to S3, without passing through sciNote server) or to `0` (to upload files to S3 through sciNote server). |
|
||||
| MAIL_FROM | Yes | The **from** address for emails sent from sciNote. |
|
||||
| MAIL_REPLYTO | Yes | The **reply to** address for emails sent from sciNote. |
|
||||
| SMTP_ADDRESS | Yes | The server address of the SMTP mailer used for delivering emails generated in sciNote. |
|
||||
| SMTP_PORT | Yes | The port of the SMTP server. Defaults to `587`. |
|
||||
| SMTP_DOMAIN | Yes | The server domain of the SMTP mailer used for delivering emails generated in sciNote. |
|
||||
| SMTP_USERNAME | Yes | The username for SMTP mailer used for delivering emails generated in sciNote. |
|
||||
| SMTP_PASSWORD | Yes | The password for SMTP mailer used for delivering emails generated in sciNote. |
|
||||
| MAIL_SERVER_URL | Yes | The root URL address of the actual sciNote server. This is used in sent emails to redirect user to the correct sciNote server URL. Defaults to `localhost`. |
|
||||
| PAPERCLIP_HASH_SECRET | Yes | Random key for generating Paperclip hash key for URLs. Can be generated via following Ruby function: `SecureRandom.base64(128)`. |
|
||||
| ENABLE_TUTORIAL | Yes | Whether to display tutorial (and auto-generate demo project) to first-time users. Defaults to `false` on development, and to `true` on production. |
|
||||
|
||||
## Rake tasks
|
||||
|
||||
### Delayed jobs
|
||||
|
||||
sciNote uses [delayed jobs](https://github.com/tobi/delayed_job) library to do background processing, mostly for the following tasks:
|
||||
* Sending emails,
|
||||
* Extracting text from uploaded files (*full-text* search).
|
||||
|
||||
Best option to run delayed jobs is inside a worker process. To start a background worker process that will execute delayed jobs, run the following command:
|
||||
```
|
||||
rake jobs:work
|
||||
```
|
||||
To clear all currently queued jobs, you can use the following command:
|
||||
```
|
||||
rake jobs:clear
|
||||
```
|
||||
**Warning!** This is not advised to do on production environments.
|
||||
|
||||
### Adding users
|
||||
|
||||
To simplify adding of new users to the system, couple of special `rake` tasks have been created.
|
||||
|
||||
The first, `rake db:add_user` simply queries all the information for a specific user via STDIN, and then proceeds to create the user.
|
||||
|
||||
The second task, `rake db:load_users[file_path,create_orgs]` takes 2 parameters as an input:
|
||||
* Path to `.yml` file containing list of users & organizations to be added. The YAML file needs to be structured properly - field names must match those in the database, users need to have a name `user_<id>`, and organizations name `org_<id>`. For an example load users file, see [db/load_users_template.yml](db/load_users_template.yml) file.
|
||||
* A boolean ('true' or 'false') whether to create individual organizations for each user or not.
|
||||
|
||||
Both of those rake actions include all database operations inside a transaction, so as long as any error happens during the process, database will be unaffected.
|
||||
|
||||
### Generating fake data
|
||||
|
||||
For testing purposes, two special tasks that will populate the database with randomized, fake data, have been implemented.
|
||||
|
||||
The first, `rake db:fake:generate`, will add fake data to an existing database. Since the algorithm that generates randomized data relies heavily on querying existing entries in database, use of this task is **not advisable**.
|
||||
|
||||
It is **much better** to use `rake db:fake` task, that will drop the database first, recreate it, and populate it with fake data afterwards.
|
||||
|
||||
### Web statistics
|
||||
|
||||
To check current login statistics of registered users, use `rake web_stats:login` task.
|
||||
|
||||
### Clearing data
|
||||
|
||||
Execute `rake data:clean_temp_files` to remove all temporary files. Temporary files are used when importing samples.
|
||||
Execute `rake data:clean_unconfirmed_users` to remove all users that registered, but never confirmed their email.
|
||||
Calling `rake data:clean` will execute both above tasks.
|
||||
|
||||
## Mailer
|
||||
|
||||
sciNote needs a configured SMTP mail server to work properly. See [environmental variables](#user-content-environmental-variables) for configuration of the mailer.
|
||||
|
||||
## Deploy onto Heroku
|
||||
|
||||
Before deploying to Heroku, install heroku client as describe on offical website. To use existing heroku application, add new git remote repository.
|
||||
|
||||
```
|
||||
git remote add heroku git@heroku.com:my-random-app-name.git
|
||||
```
|
||||
|
||||
Or create new heroku application by executing following command.
|
||||
|
||||
```
|
||||
heroku create
|
||||
```
|
||||
|
||||
Add additional heroku buildpacks in the same order as specified in [.buildpacks](.buildpacks):
|
||||
|
||||
```
|
||||
heroku buildpacks:add --index <i> <buildpack>
|
||||
```
|
||||
|
||||
e.g. for adding graphviz write:
|
||||
|
||||
```
|
||||
heroku buildpacks:add --index 2 https://github.com/weibeld/heroku-buildpack-graphviz.git
|
||||
```
|
||||
|
||||
Before pushing to heroku master branch, some environmental variables should be set.
|
||||
|
||||
### Heroku environmental variables
|
||||
|
||||
For deployment of sciNote onto Heroku, additional environmental variables need to be specified.
|
||||
|
||||
| Variable | Mandatory | Description |
|
||||
|--------------------------|-----------|--------------------------------------------------------------------------------------------|
|
||||
| SKYLIGHT_AUTHENTICATION | No | The API key for [Skylight](https://www.skylight.io/) code profiler, if choosing to use it. |
|
||||
| LANG | Yes | The default localization language (e.g. `en_US.UTF-8`). |
|
||||
| RAILS_ENV | Yes | Rails environment: `production`, `test` or `development`. |
|
||||
| RACK_ENV | Yes | Rack environment: `production`, `test` or `development`. |
|
||||
| RAILS_SERVE_STATIC_FILES | Yes | Whether to serve static files. Must be set to `enabled`. |
|
||||
| WEB_CONCURRENCY | Yes | The concurrency of the server. See Heroku specifications for details. |
|
||||
| MAX_THREADS | Yes | The max. number of threads. See Heroku specifications for details. |
|
||||
| PORT | Yes | The port on which the application should run. See Heroku specifications for details. |
|
||||
| S3_HOST_NAME | No* | If storing files on Amazon S3, this must contain the S3 service host name. |
|
||||
| RAILS_FORCE_SSL | Yes | If set to `1`, enforce SSL communication on all levels of application. |
|
||||
| DATABASE_URL | Yes | Full URL for connecting to PostgreSQL database. |
|
||||
See [Wiki/Build & run](https://github.com/biosistemika/scinote-web/wiki/setup-guide#build-&-run).
|
||||
|
||||
## Testing
|
||||
|
||||
In current version, only *model* tests are implemented for sciNote. To execute them, call `rake test:models`.
|
||||
See [Wiki/Testing](https://github.com/biosistemika/scinote-web/wiki/setup-guide#testing).
|
||||
|
||||
## Contributing
|
||||
|
||||
For contributing, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
See [Wiki/Contributing & Collaboration](https://github.com/biosistemika/scinote-web/wiki/contributing-&-collaboration).
|
||||
|
||||
## License
|
||||
|
||||
|
|
0
addons/.keep
Normal file
0
addons/.keep
Normal file
BIN
app/assets/images/custom/grippy.png
Normal file
BIN
app/assets/images/custom/grippy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 207 B |
|
@ -6,6 +6,7 @@
|
|||
//= require jquery.remotipart
|
||||
//= require jquery.mousewheel.min
|
||||
//= require jquery.scrollTo
|
||||
//= require jquery.autosize
|
||||
//= require jquery-ui/widget
|
||||
//= require jquery-ui/mouse
|
||||
//= require jquery-ui/draggable
|
||||
|
@ -21,6 +22,7 @@
|
|||
//= require bootstrap-datetimepicker
|
||||
//= require bootstrap-colorselector
|
||||
//= require bootstrap-tagsinput.min
|
||||
//= require bootstrap-checkbox.min
|
||||
//= require typeahead.bundle.min
|
||||
//= require nested_form_fields
|
||||
//= require_directory ./sitewide
|
||||
|
@ -30,6 +32,7 @@
|
|||
//= require i18n.js
|
||||
//= require i18n/translations
|
||||
//= require turbolinks
|
||||
//= require quill
|
||||
|
||||
|
||||
// Initialize links for submitting forms. This is useful for submitting
|
||||
|
@ -190,12 +193,35 @@ function notificationAlertClose(){
|
|||
.removeClass("alert-shown");
|
||||
});
|
||||
}
|
||||
var HelperModule = (function(){
|
||||
|
||||
$(document).ready(function(){
|
||||
$('.tree-link a').each( function(){
|
||||
truncateLongString( $(this), 30);
|
||||
var helpers = {};
|
||||
|
||||
helpers.treeLinkTruncation = function() {
|
||||
$('.tree-link a').each( function(){
|
||||
truncateLongString( $(this), <%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
});
|
||||
$(".tree-link span").each( function(){
|
||||
truncateLongString( $(this), <%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
});
|
||||
}
|
||||
|
||||
helpers.hideFlashMsg = function() {
|
||||
var flash = $('.alert');
|
||||
if (flash.length > 0) {
|
||||
window.setTimeout(function () {
|
||||
flash.fadeTo(500, 0).slideUp(500, function () {
|
||||
$(this).remove();
|
||||
$('#content-wrapper').removeClass('alert-shown');
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
helpers.treeLinkTruncation();
|
||||
helpers.hideFlashMsg();
|
||||
});
|
||||
$(".tree-link span").each( function(){
|
||||
truncateLongString( $(this), 30);
|
||||
});
|
||||
});
|
||||
|
||||
return helpers;
|
||||
})();
|
|
@ -1,151 +0,0 @@
|
|||
function initCommentOptions(scrollableContainer, useParentOffset) {
|
||||
useParentOffset = (typeof useParentOffset !== 'undefined') ? useParentOffset : true;
|
||||
scrollCommentOptions($(".dropdown-comment"), useParentOffset);
|
||||
|
||||
// Reposition dropdown to the left
|
||||
// (only do this when using parent offset)
|
||||
if (useParentOffset) {
|
||||
$(document).on("shown.bs.dropdown", ".dropdown-comment", function() {
|
||||
var $el = $(this);
|
||||
var menu = $el.find(".dropdown-menu");
|
||||
var leftPos = $el.offset().left;
|
||||
if (leftPos + menu.width() > $(window).width()) {
|
||||
menu.offset({ left: leftPos - menu.width() });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reposition dropdowns vertically on scroll events
|
||||
document.addEventListener('scroll', function (event) {
|
||||
var $target = $(event.target);
|
||||
var parent = $(scrollableContainer);
|
||||
|
||||
if ($target.length) {
|
||||
scrollCommentOptions(parent.find(".dropdown-comment"), useParentOffset);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function scrollCommentOptions(selector, useParentOffset) {
|
||||
useParentOffset = (typeof useParentOffset !== 'undefined') ? useParentOffset : true;
|
||||
_.each(selector, function(el) {
|
||||
var $el = $(el);
|
||||
var offset = useParentOffset ? $el.offset().top : $el.position().top;
|
||||
$el.find(".dropdown-menu-fixed")
|
||||
.offset({ top: (offset + 20) });
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteComments(parent) {
|
||||
$(parent).on("click", "[data-action=delete-comment]", function(e) {
|
||||
var $this = $(this);
|
||||
if (confirm($this.attr("data-confirm-message"))) {
|
||||
$.ajax({
|
||||
url: $this.attr("data-url"),
|
||||
type: "DELETE",
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
// There are 3 possible actions:
|
||||
// - (A) comment is the last comment in project
|
||||
// - (B) comment is the last comment inside specific date (remove the date separator)
|
||||
// - (C) comment is a usual comment
|
||||
|
||||
var commentEl = $this.closest(".comment");
|
||||
|
||||
// Case A
|
||||
if (commentEl.prevAll(".comment").length == 0 && commentEl.next().length == 0) {
|
||||
commentEl.after("<li class='no-comments'><em>" + I18n.t("projects.index.no_comments") + "</em></li>");
|
||||
}
|
||||
|
||||
// Case B
|
||||
if (commentEl.prev(".comment-date-separator").length > 0 && commentEl.next(".comment").length == 0) {
|
||||
commentEl.prev(".comment-date-separator").remove();
|
||||
}
|
||||
commentEl.remove();
|
||||
|
||||
scrollCommentOptions($(parent).find(".dropdown-comment"));
|
||||
},
|
||||
error: function(data) {
|
||||
// Display alert
|
||||
alert(data.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initEditComments(parent) {
|
||||
$(parent).on("click", "[data-action=edit-comment]", function(e) {
|
||||
var $this = $(this);
|
||||
$.ajax({
|
||||
url: $this.attr("data-url"),
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
var commentEl = $this.closest(".comment");
|
||||
var container = commentEl.find("[data-role=comment-message-container]");
|
||||
var oldMessage = container.find("[data-role=comment-message]");
|
||||
var optionsBtn = commentEl.find("[data-role=comment-options]");
|
||||
|
||||
// Hide old message, append new HTML
|
||||
oldMessage.hide();
|
||||
optionsBtn.hide();
|
||||
container.append(data.html);
|
||||
|
||||
var form = container.find("[data-role=edit-comment-message-form]");
|
||||
var input = form.find("input[data-role=message-input]");
|
||||
var submitBtn = form.find("[data-action=save]");
|
||||
var cancelBtn = form.find("[data-action=cancel]");
|
||||
|
||||
input.focus();
|
||||
|
||||
form
|
||||
.on("ajax:send", function() {
|
||||
input.attr("readonly", true);
|
||||
})
|
||||
.on("ajax:success", function(e, data) {
|
||||
var newMessage = input.val();
|
||||
oldMessage.html(newMessage);
|
||||
|
||||
form.off("ajax:send ajax:success ajax:error ajax:complete");
|
||||
submitBtn.off("click");
|
||||
cancelBtn.off("click");
|
||||
form.remove();
|
||||
oldMessage.show();
|
||||
optionsBtn.show();
|
||||
})
|
||||
.on("ajax:error", function(ev, xhr) {
|
||||
if (xhr.status === 422) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
if (messageError) {
|
||||
$(".form-group", form)
|
||||
.addClass("has-error");
|
||||
$(".help-block", form)
|
||||
.html(messageError[0] + " |")
|
||||
.removeClass("hide");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("ajax:complete", function() {
|
||||
input.attr("readonly", false).focus();
|
||||
});
|
||||
|
||||
submitBtn.on("click", function() {
|
||||
form.submit();
|
||||
});
|
||||
|
||||
cancelBtn.on("click", function() {
|
||||
form.off("ajax:send ajax:success ajax:error ajax:complete");
|
||||
submitBtn.off("click");
|
||||
cancelBtn.off("click");
|
||||
form.remove();
|
||||
oldMessage.show();
|
||||
optionsBtn.show();
|
||||
});
|
||||
},
|
||||
error: function(data) {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
371
app/assets/javascripts/comments.js.erb
Normal file
371
app/assets/javascripts/comments.js.erb
Normal file
|
@ -0,0 +1,371 @@
|
|||
var Comments = (function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Initializes the comments
|
||||
*
|
||||
*/
|
||||
function initializeComments(){
|
||||
var comments;
|
||||
if ( $('.step-comment') && $('.step-comment').length > 0 ) {
|
||||
comments = $('.step-comment');
|
||||
} else if ( $('.result-comment') && $('.result-comment').length > 0 ) {
|
||||
comments = $('.result-comment');
|
||||
}
|
||||
if(!_.isUndefined(comments)) {
|
||||
$.each(comments, function(){
|
||||
var that = $(this);
|
||||
var link = that.attr('data-href');
|
||||
$.ajax({ method: 'GET',
|
||||
url: link,
|
||||
beforeSend: animateSpinner(that, true) })
|
||||
.done(function(data) {
|
||||
that.html(data.html);
|
||||
initCommentForm(that);
|
||||
initCommentsLink(that);
|
||||
scrollBottom(that.find('.content-comments'));
|
||||
})
|
||||
.always(function() {
|
||||
animateSpinner(that, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// scroll to the botttom
|
||||
function scrollBottom(id) {
|
||||
var list;
|
||||
if ( id.hasClass('content-comments') ) {
|
||||
list = id;
|
||||
} else {
|
||||
list = id.find('.content-comments');
|
||||
}
|
||||
if ( list && list.length > 0) {
|
||||
list.scrollTop($(list)[0].scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize show more comments link.
|
||||
function initCommentsLink($el) {
|
||||
|
||||
$el.find('.btn-more-comments')
|
||||
.on('ajax:success', function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $(this).parents('ul');
|
||||
var moreBtn = list.find('.btn-more-comments');
|
||||
var listItem = moreBtn.parents('li');
|
||||
|
||||
$(data.html).insertAfter(listItem);
|
||||
if (data.resultsNumber < data.perPage) {
|
||||
moreBtn.remove();
|
||||
} else {
|
||||
moreBtn.attr('href', data.moreUrl);
|
||||
moreBtn.trigger('blur');
|
||||
}
|
||||
|
||||
var date;
|
||||
$.each(list.find('.comment-date-separator'), function() {
|
||||
if ( $(this).find('p').html() === date ) {
|
||||
$(this).remove();
|
||||
} else {
|
||||
date = $(this).find('p').html();
|
||||
}
|
||||
});
|
||||
|
||||
// Reposition dropdown comment options
|
||||
scrollCommentOptions(listItem
|
||||
.closest('.content-comments')
|
||||
.find('.dropdown-comment'));
|
||||
} else {
|
||||
$('.btn-more-comments').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize comment form.
|
||||
function initCommentForm($el) {
|
||||
|
||||
var $form = $el.find('ul form');
|
||||
|
||||
$('.help-block', $form).addClass('hide');
|
||||
|
||||
$form.on('ajax:send', function () {
|
||||
$('#comment_message', $form).attr('readonly', true);
|
||||
})
|
||||
.on('ajax:success', function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $form.parents('ul');
|
||||
|
||||
// Remove potential 'no comments' element
|
||||
list.parent().find('.content-comments')
|
||||
.find('li.no-comments').remove();
|
||||
|
||||
// Find previous date separator
|
||||
var dateSeparator = list.parent().find('.comment-date-separator:last');
|
||||
if (dateSeparator.length > 0) {
|
||||
// Parse string with creation date
|
||||
var pr = dateSeparator.text().split('.');
|
||||
var comm = data.date.split('.');
|
||||
// Build Date objects and compare
|
||||
var sepDate = new Date(pr[2], pr[1] - 1, pr[0]);
|
||||
var commDate = new Date(comm[2], comm[1] - 1, comm[0]);
|
||||
if (commDate > sepDate) {
|
||||
// Add date separator
|
||||
list.parent().find('.content-comments')
|
||||
.append('<li class="comment-date-separator">\
|
||||
<p class="text-center">' + data.date + '</p>\
|
||||
</li>');
|
||||
}
|
||||
} else {
|
||||
// Comment is the first one so add date separator
|
||||
list.parent().find('.content-comments')
|
||||
.append('<li class="comment-date-separator">\
|
||||
<p class="text-center">' + data.date + '</p>\
|
||||
</li>');
|
||||
}
|
||||
|
||||
list.parent().find('.content-comments')
|
||||
.append('<li class="comment">' + data.html + '</li>')
|
||||
.scrollTop(0);
|
||||
list.parents('ul').find('> li.comment:gt(8)').remove();
|
||||
$('#comment_message', $form).val('');
|
||||
$('.form-group', $form)
|
||||
.removeClass('has-error');
|
||||
$('.help-block', $form)
|
||||
.html('')
|
||||
.addClass('hide');
|
||||
scrollBottom($el);
|
||||
}
|
||||
})
|
||||
.on('ajax:error', function (ev, xhr) {
|
||||
if (xhr.status === 400) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
|
||||
if (messageError) {
|
||||
$('.form-group', $form)
|
||||
.addClass('has-error');
|
||||
$('.help-block', $form)
|
||||
.html(messageError[0])
|
||||
.removeClass('hide');
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('ajax:complete', function () {
|
||||
scrollBottom($('#comment_message', $form));
|
||||
$('#comment_message', $form)
|
||||
.attr('readonly', false)
|
||||
.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// restore comments after update or when new element is created
|
||||
function bindCommentInitializerToNewElement() {
|
||||
$(document)
|
||||
.ready(function() {
|
||||
if( document.getElementById('steps') !== null ) {
|
||||
$('#steps')
|
||||
.change(function() {
|
||||
$('.step-save')
|
||||
.on('click', function() {
|
||||
$(document)
|
||||
.on('ajax:success', function(){
|
||||
initializeComments();
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if ( document.getElementById('results') !== null ) {
|
||||
$('#results')
|
||||
.change(function() {
|
||||
$('.save-result')
|
||||
.on('click', function() {
|
||||
$(document)
|
||||
.on('ajax:success', function(){
|
||||
initializeComments();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCommentOptions(scrollableContainer, useParentOffset) {
|
||||
if ( ! _.isUndefined(useParentOffset) ) {
|
||||
useParentOffset = useParentOffset;
|
||||
} else {
|
||||
useParentOffset = true;
|
||||
}
|
||||
scrollCommentOptions($('.dropdown-comment'), useParentOffset);
|
||||
|
||||
// Reposition dropdown to the left
|
||||
// (only do this when using parent offset)
|
||||
if (useParentOffset) {
|
||||
$(document).on('shown.bs.dropdown', '.dropdown-comment', function() {
|
||||
var $el = $(this);
|
||||
var menu = $el.find('.dropdown-menu');
|
||||
var leftPos = $el.offset().left;
|
||||
var parentTopPos = $el.offset().top;
|
||||
if (leftPos + menu.width() > $(window).width()) {
|
||||
menu.offset({ left: leftPos - menu.width(),
|
||||
top: (parentTopPos +
|
||||
<%= Constants::DROPDOWN_TOP_OFFSET_PX %>)});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reposition dropdowns vertically on scroll events
|
||||
document.addEventListener('scroll', function (event) {
|
||||
var $target = $(event.target);
|
||||
var parent = $(scrollableContainer);
|
||||
|
||||
if ($target.length) {
|
||||
scrollCommentOptions(parent.find('.dropdown-comment'), useParentOffset);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function scrollCommentOptions(selector, useParentOffset) {
|
||||
if ( ! _.isUndefined(useParentOffset) ) {
|
||||
useParentOffset = useParentOffset;
|
||||
} else {
|
||||
useParentOffset = true;
|
||||
}
|
||||
_.each(selector, function(el) {
|
||||
var $el = $(el);
|
||||
var offset = useParentOffset ? $el.offset().top : $el.position().top;
|
||||
$el.find('.dropdown-menu-fixed')
|
||||
.offset({ top: (offset + <%= Constants::DROPDOWN_TOP_OFFSET_PX %>) });
|
||||
});
|
||||
}
|
||||
|
||||
function initDeleteComments(parent) {
|
||||
$(parent).on('click', '[data-action=delete-comment]', function() {
|
||||
var $this = $(this);
|
||||
if (confirm($this.attr('data-confirm-message'))) {
|
||||
$.ajax({
|
||||
url: $this.attr('data-url'),
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
success: function() {
|
||||
// There are 3 possible actions:
|
||||
// - (A) comment is the last comment in project
|
||||
// - (B) comment is the last comment inside specific date
|
||||
// (remove the date separator)
|
||||
// - (C) comment is a usual comment
|
||||
|
||||
var commentEl = $this.closest('.comment');
|
||||
|
||||
// Case A
|
||||
if (commentEl.prevAll('.comment').length === 0 &&
|
||||
commentEl.next().length === 0) {
|
||||
commentEl.after('<li class="no-comments"><em>' +
|
||||
I18n.t('projects.index.no_comments') + '</em></li>');
|
||||
}
|
||||
|
||||
// Case B
|
||||
if (commentEl.prev('.comment-date-separator').length > 0 &&
|
||||
commentEl.next('.comment').length === 0) {
|
||||
commentEl.prev('.comment-date-separator').remove();
|
||||
}
|
||||
commentEl.remove();
|
||||
|
||||
scrollCommentOptions($(parent).find('.dropdown-comment'));
|
||||
},
|
||||
error: function(data) {
|
||||
// Display alert
|
||||
alert(data.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initEditComments(parent) {
|
||||
$(parent).unbind('click').on('click', '[data-action=edit-comment]', function() {
|
||||
|
||||
var $this = $(this);
|
||||
$.ajax({
|
||||
url: $this.attr('data-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var commentEl = $this.closest('.comment');
|
||||
var container = commentEl
|
||||
.find('[data-role=comment-message-container]');
|
||||
var oldMessage = container.find('[data-role=comment-message]');
|
||||
var optionsBtn = commentEl.find('[data-role=comment-options]');
|
||||
|
||||
// Hide old message, append new HTML
|
||||
oldMessage.hide();
|
||||
optionsBtn.hide();
|
||||
container.append(data.html);
|
||||
|
||||
var form = container.find('[data-role=edit-comment-message-form]');
|
||||
var input = form.find('[data-role=message-input]');
|
||||
var submitBtn = form.find('[data-action=save]');
|
||||
var cancelBtn = form.find('[data-action=cancel]');
|
||||
|
||||
input.focus();
|
||||
|
||||
form
|
||||
.on('ajax:send', function() {
|
||||
input.attr('readonly', true);
|
||||
})
|
||||
.on('ajax:success', function() {
|
||||
var newMessage = input.val();
|
||||
oldMessage.html(newMessage);
|
||||
|
||||
form.off('ajax:send ajax:success ajax:error ajax:complete');
|
||||
submitBtn.off('click');
|
||||
cancelBtn.off('click');
|
||||
form.remove();
|
||||
oldMessage.show();
|
||||
optionsBtn.show();
|
||||
})
|
||||
.on('ajax:error', function(ev, xhr) {
|
||||
if (xhr.status === 422) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
if (messageError) {
|
||||
$('.form-group', form)
|
||||
.addClass('has-error');
|
||||
$('.help-block', form)
|
||||
.html(messageError[0] + ' |')
|
||||
.removeClass('hide');
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('ajax:complete', function() {
|
||||
input.attr('readonly', false).focus();
|
||||
});
|
||||
|
||||
submitBtn.on('click', function() {
|
||||
form.submit();
|
||||
});
|
||||
|
||||
cancelBtn.on('click', function() {
|
||||
form.off('ajax:send ajax:success ajax:error ajax:complete');
|
||||
submitBtn.off('click');
|
||||
cancelBtn.off('click');
|
||||
form.remove();
|
||||
oldMessage.show();
|
||||
optionsBtn.show();
|
||||
});
|
||||
},
|
||||
error: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: initializeComments,
|
||||
scrollBottom: scrollBottom,
|
||||
moreComments: initCommentsLink,
|
||||
form: initCommentForm,
|
||||
bindNewElement: bindCommentInitializerToNewElement,
|
||||
initCommentOptions: initCommentOptions,
|
||||
initDeleteComments: initDeleteComments,
|
||||
initEditComments: initEditComments
|
||||
};
|
||||
|
||||
})();
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -66,12 +66,12 @@
|
|||
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());
|
||||
})
|
||||
.clearFormErrors();
|
||||
});
|
||||
}
|
||||
}
|
||||
// Reload after successfully updated experiment
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
//= require protocols/import_export/import
|
||||
//= require protocols/import_export/export
|
||||
//= require comments
|
||||
//= require datatables
|
||||
|
||||
// Currently selected row in "load from protocol" modal
|
||||
var selectedRow = null;
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
bindEditDueDateAjax();
|
||||
initEditDescription();
|
||||
initCopyToRepository();
|
||||
initLinkUpdate();
|
||||
initLoadFromRepository();
|
||||
initRefreshStatusBar();
|
||||
initImport();
|
||||
initExport();
|
||||
Comments.bindNewElement();
|
||||
Comments.initialize();
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
// Initialize edit description modal window
|
||||
function initEditDescription() {
|
||||
var editDescriptionModal = $("#manage-module-description-modal");
|
||||
|
@ -97,87 +115,34 @@ function bindEditDueDateAjax() {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
if (showTutorial() && (currentStep > 8 && currentStep < 12)) {
|
||||
var resultsTab = $("#results-nav-tab");
|
||||
var moduleProtocolsTutorial = $("[data-role='tutorial-data']").attr("data-module-protocols-step-text");
|
||||
var moduleProtocolsClickResultsTutorial = $("[data-role='tutorial-data']").attr("data-module-protocols-click-results-step-text");
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 12 && stepNum <= 14) {
|
||||
var resultsTab = $('#results-nav-tab');
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
intro: moduleProtocolsTutorial
|
||||
},
|
||||
{
|
||||
intro: moduleProtocolsClickResultsTutorial,
|
||||
element: resultsTab[0],
|
||||
tooltipClass: 'custom next-page-link'
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.1',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
nextLabel: 'Next',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom'
|
||||
})
|
||||
.onafterchange(function(tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 10);
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', resultsTab.find("a").attr('href'));
|
||||
positionTutorialTooltip();
|
||||
}, 500);
|
||||
} else {
|
||||
positionTutorialTooltip();
|
||||
}
|
||||
})
|
||||
.goToStep(currentStep == 11 ? 2 : 1)
|
||||
.start();
|
||||
|
||||
window.onresize = positionTutorialTooltip;
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
});
|
||||
});
|
||||
var nextPage = resultsTab.find('a').attr('href');
|
||||
var steps = [{
|
||||
intro: $("[data-role='tutorial-data']")
|
||||
.attr('data-module-protocols-step-text')
|
||||
}, {
|
||||
element: $('#protocol-copy-to-repository')[0],
|
||||
intro: $("[data-role='tutorial-data']")
|
||||
.attr('data-module-protocols-save-step-text'),
|
||||
position: 'right'
|
||||
}, {
|
||||
element: resultsTab[0],
|
||||
intro: $("[data-role='tutorial-data']")
|
||||
.attr('data-module-protocols-click-results-step-text'),
|
||||
position: 'right'
|
||||
}];
|
||||
initPageTutorialSteps(12, 14, nextPage,
|
||||
function() {}, function() {}, steps);
|
||||
}
|
||||
}
|
||||
|
||||
function positionTutorialTooltip() {
|
||||
if (Cookies.get('current_tutorial_step') == 11) {
|
||||
if ($("#results-nav-tab").offset().left == 0) {
|
||||
$(".introjs-tooltip").css("left", (window.innerWidth / 2 - 50) + "px");
|
||||
$(".introjs-tooltip").addClass("repositioned");
|
||||
} else if ($(".introjs-tooltip").hasClass("repositioned")) {
|
||||
$(".introjs-tooltip").css("left", "");
|
||||
$(".introjs-tooltip").removeClass("repositioned");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var tutorialModuleId = tutorialData[0].qpcr_module;
|
||||
var currentModuleId = $("[data-role='tutorial-data']").attr("data-module-id");
|
||||
return tutorialModuleId == currentModuleId;
|
||||
}
|
||||
|
||||
function initCopyToRepository() {
|
||||
var link = $("[data-action='copy-to-repository']");
|
||||
var modal = $("#copy-to-repository-modal");
|
||||
|
@ -531,7 +496,13 @@ function initImport() {
|
|||
} else if (data.status === 'locked') {
|
||||
alert(I18n.t("my_modules.protocols.load_from_file_error_locked"));
|
||||
} else {
|
||||
alert(I18n.t("my_modules.protocols.load_from_file_error"));
|
||||
if (data.status === 'size_too_large') {
|
||||
alert('<%= I18n.t('my_modules.protocols.load_from_file_size_error',
|
||||
size: Constants::FILE_MAX_SIZE_MB ) %>');
|
||||
} else {
|
||||
alert(I18n.t("my_modules.protocols.load_from_file_error"));
|
||||
}
|
||||
animateSpinner(null, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -547,13 +518,4 @@ function initExport() {
|
|||
});
|
||||
}
|
||||
|
||||
// On init
|
||||
bindEditDueDateAjax();
|
||||
initEditDescription();
|
||||
initCopyToRepository();
|
||||
initLinkUpdate();
|
||||
initLoadFromRepository();
|
||||
initRefreshStatusBar();
|
||||
initImport();
|
||||
initExport();
|
||||
initTutorial();
|
||||
init();
|
|
@ -1,293 +0,0 @@
|
|||
//= require comments
|
||||
function initHandsOnTables(root) {
|
||||
root.find("div.hot-table").each(function() {
|
||||
var $container = $(this).find(".step-result-hot-table");
|
||||
var contents = $(this).find('.hot-contents');
|
||||
|
||||
$container.handsontable({
|
||||
width: '100%',
|
||||
startRows: 5,
|
||||
startCols: 5,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
fillHandle: false,
|
||||
formulas: true,
|
||||
cells: function (row, col, prop) {
|
||||
var cellProperties = {};
|
||||
|
||||
if (col >= 0)
|
||||
cellProperties.readOnly = true;
|
||||
else
|
||||
cellProperties.readOnly = false;
|
||||
|
||||
return cellProperties;
|
||||
}
|
||||
});
|
||||
var hot = $container.handsontable('getInstance');
|
||||
var data = JSON.parse(contents.attr("value"));
|
||||
hot.loadData(data.data);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function initResultCommentTabAjax() {
|
||||
$(".comment-tab-link")
|
||||
.on("ajax:before", function (e) {
|
||||
var $this = $(this);
|
||||
var parentNode = $this.parents("li");
|
||||
var targetId = $this.attr("aria-controls");
|
||||
|
||||
if (parentNode.hasClass("active")) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var $this = $(this);
|
||||
var targetId = $this.attr("aria-controls");
|
||||
var target = $("#" + targetId);
|
||||
var parentNode = $this.parents("ul").parent();
|
||||
|
||||
target.html(data.html);
|
||||
initCommentForm(parentNode);
|
||||
initCommentsLink(parentNode);
|
||||
|
||||
parentNode.find(".active").removeClass("active");
|
||||
$this.parents("li").addClass("active");
|
||||
target.addClass("active");
|
||||
}
|
||||
})
|
||||
.on("ajax:error", function(e, xhr, status, error) {
|
||||
// TODO
|
||||
});
|
||||
}
|
||||
|
||||
function applyCollapseLinkCallBack() {
|
||||
$(".result-panel-collapse-link")
|
||||
.on("ajax:success", function() {
|
||||
var collapseIcon = $(this).find(".collapse-result-icon");
|
||||
var collapsed = $(this).hasClass("collapsed");
|
||||
// Toggle collapse button
|
||||
collapseIcon.toggleClass("glyphicon-collapse-up", !collapsed);
|
||||
collapseIcon.toggleClass("glyphicon-collapse-down", collapsed);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle editing buttons
|
||||
function toggleResultEditButtons(show) {
|
||||
if (show) {
|
||||
$("#results-toolbar").show();
|
||||
$(".edit-result-button").show();
|
||||
} else {
|
||||
$(".edit-result-button").hide();
|
||||
$("#results-toolbar").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Expand all results
|
||||
function expandAllResults() {
|
||||
$('.result .panel-collapse').collapse('show');
|
||||
$(document).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-up");
|
||||
$(this).removeClass("glyphicon-collapse-down");
|
||||
});
|
||||
$(document).find("div.step-result-hot-table").each(function() {
|
||||
renderTable(this);
|
||||
});
|
||||
}
|
||||
|
||||
function expandResult(result) {
|
||||
$('.panel-collapse', result).collapse('show');
|
||||
$(result).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-up");
|
||||
$(this).removeClass("glyphicon-collapse-down");
|
||||
});
|
||||
renderTable($(result).find("div.step-result-hot-table"));
|
||||
}
|
||||
|
||||
function renderTable(table) {
|
||||
$(table).handsontable("render");
|
||||
// Yet another dirty hack to solve HandsOnTable problems
|
||||
if (parseInt($(table).css("height"), 10) < parseInt($(table).css("max-height"), 10) - 30) {
|
||||
$(table).find(".ht_master .wtHolder").css({ 'height': '100%',
|
||||
'width': '100%' });
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize first-time tutorial
|
||||
function initTutorial() {
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
if (showTutorial() && (currentStep > 10 && currentStep < 14)) {
|
||||
var moduleResultsTutorial = $("#results").attr("data-module-protocols-step-text");
|
||||
var moduleResultsClickSamplesTutorial = $("#results").attr("data-module-protocols-click-samples-step-text");
|
||||
var samplesTab = $("#module-samples-nav-tab");
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
element: document.getElementById("results-toolbar"),
|
||||
intro: moduleResultsTutorial,
|
||||
disableInteraction: true
|
||||
},
|
||||
{
|
||||
element: samplesTab[0],
|
||||
intro: moduleResultsClickSamplesTutorial,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.1',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
nextLabel: 'Next',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom',
|
||||
disableInteraction: true
|
||||
})
|
||||
.onafterchange(function (tarEl){
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 12);
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', samplesTab.find("a").attr('href'));
|
||||
|
||||
$(".introjs-disableInteraction").remove();
|
||||
positionTutorialTooltip();
|
||||
}, 500);
|
||||
} else {
|
||||
positionTutorialTooltip();
|
||||
}
|
||||
})
|
||||
.goToStep(currentStep == 13 ? 2 : 1)
|
||||
.start();
|
||||
|
||||
window.onresize = positionTutorialTooltip;
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function positionTutorialTooltip() {
|
||||
if (Cookies.get('current_tutorial_step') == 13) {
|
||||
if ($("#module-samples-nav-tab").offset().left == 0) {
|
||||
$(".introjs-tooltip").css("left", (window.innerWidth / 2 - 50) + "px");
|
||||
$(".introjs-tooltip").addClass("repositioned");
|
||||
} else if ($(".introjs-tooltip").hasClass("repositioned")) {
|
||||
$(".introjs-tooltip").css("left", "");
|
||||
$(".introjs-tooltip").removeClass("repositioned");
|
||||
}
|
||||
} else {
|
||||
if ($(".introjs-tooltip").offset().left > window.innerWidth) {
|
||||
$(".introjs-tooltip").css("left", (window.innerWidth / 2 - 50) + "px");
|
||||
$(".introjs-tooltip").addClass("repositioned");
|
||||
} else if ($(".introjs-tooltip").hasClass("repositioned")) {
|
||||
$(".introjs-tooltip").css("left", "");
|
||||
$(".introjs-tooltip").removeClass("repositioned");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var tutorialModuleId = tutorialData[0].qpcr_module;
|
||||
var currentModuleId = $("#results").attr("data-module-id");
|
||||
return tutorialModuleId == currentModuleId;
|
||||
}
|
||||
|
||||
var ResultTypeEnum = Object.freeze({
|
||||
FILE: 0,
|
||||
TABLE: 1,
|
||||
TEXT: 2,
|
||||
COMMENT: 3
|
||||
});
|
||||
|
||||
function processResult(ev, resultTypeEnum, editMode, forS3) {
|
||||
forS3 = (typeof forS3 !== 'undefined') ? forS3 : false;
|
||||
var $form = $(ev.target.form);
|
||||
$form.clearFormErrors();
|
||||
|
||||
switch(resultTypeEnum) {
|
||||
case ResultTypeEnum.FILE:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, TextLimitEnum.OPTIONAL,
|
||||
TextLimitEnum.NAME_MAX_LENGTH);
|
||||
var $fileInput = $form.find("#result_asset_attributes_file");
|
||||
var filesValid = filesValidator(ev, $fileInput, FileTypeSizeEnum.FILE, editMode);
|
||||
|
||||
if(nameValid && filesValid) {
|
||||
if(forS3) {
|
||||
// Redirects file uploading to S3
|
||||
var url = '/asset_signature.json';
|
||||
directUpload(ev, url);
|
||||
} else {
|
||||
// Local file uploading
|
||||
animateSpinner();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ResultTypeEnum.TABLE:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, TextLimitEnum.OPTIONAL,
|
||||
TextLimitEnum.NAME_MAX_LENGTH);
|
||||
break;
|
||||
case ResultTypeEnum.TEXT:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, TextLimitEnum.OPTIONAL,
|
||||
TextLimitEnum.NAME_MAX_LENGTH);
|
||||
var $textInput = $form.find("#result_result_text_attributes_text");
|
||||
textValidator(ev, $textInput, TextLimitEnum.REQUIRED,
|
||||
TextLimitEnum.TEXT_MAX_LENGTH);
|
||||
break;
|
||||
case ResultTypeEnum.COMMENT:
|
||||
var $commentInput = $form.find("#comment_message");
|
||||
var commentValid = textValidator(ev, $commentInput, TextLimitEnum.REQUIRED,
|
||||
TextLimitEnum.TEXT_MAX_LENGTH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This checks if the ctarget param exist in the
|
||||
// rendered url and opens the comment tab
|
||||
$(document).ready(function(){
|
||||
initHandsOnTables($(document));
|
||||
initResultCommentTabAjax();
|
||||
expandAllResults();
|
||||
initTutorial();
|
||||
applyCollapseLinkCallBack();
|
||||
|
||||
initCommentOptions("ul.content-comments");
|
||||
initEditComments("#results");
|
||||
initDeleteComments("#results");
|
||||
|
||||
$(function () {
|
||||
$("#results-collapse-btn").click(function () {
|
||||
$('.result .panel-collapse').collapse('hide');
|
||||
$(document).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-down");
|
||||
$(this).removeClass("glyphicon-collapse-up");
|
||||
});
|
||||
});
|
||||
|
||||
$("#results-expand-btn").click(expandAllResults);
|
||||
});
|
||||
|
||||
if( getParam('ctarget') ){
|
||||
var target = "#"+ getParam('ctarget');
|
||||
$(target).find('a.comment-tab-link').click();
|
||||
}
|
||||
});
|
199
app/assets/javascripts/my_modules/results.js.erb
Normal file
199
app/assets/javascripts/my_modules/results.js.erb
Normal file
|
@ -0,0 +1,199 @@
|
|||
//= require comments
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
initHandsOnTables($(document));
|
||||
expandAllResults();
|
||||
initTutorial();
|
||||
applyCollapseLinkCallBack();
|
||||
|
||||
Comments.bindNewElement();
|
||||
Comments.initialize();
|
||||
|
||||
Comments.initCommentOptions("ul.content-comments");
|
||||
Comments.initEditComments("#results");
|
||||
Comments.initDeleteComments("#results");
|
||||
|
||||
$(function () {
|
||||
$("#results-collapse-btn").click(function () {
|
||||
$('.result .panel-collapse').collapse('hide');
|
||||
$(document).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-down");
|
||||
$(this).removeClass("glyphicon-collapse-up");
|
||||
});
|
||||
});
|
||||
|
||||
$("#results-expand-btn").click(expandAllResults);
|
||||
});
|
||||
|
||||
// This checks if the ctarget param exist in the rendered url and opens the
|
||||
// comment tab
|
||||
if( getParam('ctarget') ){
|
||||
var target = "#"+ getParam('ctarget');
|
||||
$(target).find('a.comment-tab-link').click();
|
||||
}
|
||||
}
|
||||
|
||||
function initHandsOnTables(root) {
|
||||
root.find("div.hot-table").each(function() {
|
||||
var $container = $(this).find(".step-result-hot-table");
|
||||
var contents = $(this).find('.hot-contents');
|
||||
|
||||
$container.handsontable({
|
||||
width: '100%',
|
||||
startRows: 5,
|
||||
startCols: 5,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
fillHandle: false,
|
||||
formulas: true,
|
||||
cells: function (row, col, prop) {
|
||||
var cellProperties = {};
|
||||
|
||||
if (col >= 0)
|
||||
cellProperties.readOnly = true;
|
||||
else
|
||||
cellProperties.readOnly = false;
|
||||
|
||||
return cellProperties;
|
||||
}
|
||||
});
|
||||
var hot = $container.handsontable('getInstance');
|
||||
var data = JSON.parse(contents.attr("value"));
|
||||
hot.loadData(data.data);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function applyCollapseLinkCallBack() {
|
||||
$(".result-panel-collapse-link")
|
||||
.on("ajax:success", function() {
|
||||
var collapseIcon = $(this).find(".collapse-result-icon");
|
||||
var collapsed = $(this).hasClass("collapsed");
|
||||
// Toggle collapse button
|
||||
collapseIcon.toggleClass("glyphicon-collapse-up", !collapsed);
|
||||
collapseIcon.toggleClass("glyphicon-collapse-down", collapsed);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle editing buttons
|
||||
function toggleResultEditButtons(show) {
|
||||
if (show) {
|
||||
$("#results-toolbar").show();
|
||||
$(".edit-result-button").show();
|
||||
} else {
|
||||
$(".edit-result-button").hide();
|
||||
$("#results-toolbar").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Expand all results
|
||||
function expandAllResults() {
|
||||
$('.result .panel-collapse').collapse('show');
|
||||
$(document).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-up");
|
||||
$(this).removeClass("glyphicon-collapse-down");
|
||||
});
|
||||
$(document).find("div.step-result-hot-table").each(function() {
|
||||
renderTable(this);
|
||||
});
|
||||
}
|
||||
|
||||
function expandResult(result) {
|
||||
$('.panel-collapse', result).collapse('show');
|
||||
$(result).find("span.collapse-result-icon").each(function() {
|
||||
$(this).addClass("glyphicon-collapse-up");
|
||||
$(this).removeClass("glyphicon-collapse-down");
|
||||
});
|
||||
renderTable($(result).find("div.step-result-hot-table"));
|
||||
}
|
||||
|
||||
function renderTable(table) {
|
||||
$(table).handsontable("render");
|
||||
// Yet another dirty hack to solve HandsOnTable problems
|
||||
if (parseInt($(table).css("height"), 10) < parseInt($(table).css("max-height"), 10) - 30) {
|
||||
$(table).find(".ht_master .wtHolder").css({ 'height': '100%',
|
||||
'width': '100%' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 15 && stepNum <= 16) {
|
||||
var samplesTab = $('#module-samples-nav-tab');
|
||||
|
||||
var nextPage = samplesTab.find('a').attr('href');
|
||||
var steps = [{
|
||||
element: $('#results-toolbar')[0],
|
||||
intro: $('#results').attr('data-module-protocols-step-text')
|
||||
}, {
|
||||
element: samplesTab[0],
|
||||
intro: $('#results')
|
||||
.attr('data-module-protocols-click-samples-step-text'),
|
||||
position: 'left'
|
||||
}];
|
||||
initPageTutorialSteps(15, 16, nextPage, function() {},
|
||||
function() {}, steps);
|
||||
}
|
||||
}
|
||||
|
||||
var ResultTypeEnum = Object.freeze({
|
||||
FILE: 0,
|
||||
TABLE: 1,
|
||||
TEXT: 2,
|
||||
COMMENT: 3
|
||||
});
|
||||
|
||||
function processResult(ev, resultTypeEnum, editMode, forS3) {
|
||||
forS3 = (typeof forS3 !== 'undefined') ? forS3 : false;
|
||||
var $form = $(ev.target.form);
|
||||
$form.clearFormErrors();
|
||||
|
||||
switch(resultTypeEnum) {
|
||||
case ResultTypeEnum.FILE:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, 0,
|
||||
<%= Constants::NAME_MAX_LENGTH %>);
|
||||
var $fileInput = $form.find("#result_asset_attributes_file");
|
||||
var filesValid = filesValidator(ev, $fileInput, FileTypeEnum.FILE,
|
||||
editMode);
|
||||
|
||||
if(nameValid && filesValid) {
|
||||
if(forS3) {
|
||||
// Redirects file uploading to S3
|
||||
var url = '/asset_signature.json';
|
||||
directUpload(ev, url);
|
||||
} else {
|
||||
// Local file uploading
|
||||
animateSpinner();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ResultTypeEnum.TABLE:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, 0,
|
||||
<%= Constants::NAME_MAX_LENGTH %>);
|
||||
break;
|
||||
case ResultTypeEnum.TEXT:
|
||||
var $nameInput = $form.find("#result_name");
|
||||
var nameValid = textValidator(ev, $nameInput, 0,
|
||||
<%= Constants::NAME_MAX_LENGTH %>);
|
||||
var $textInput = $form.find("#result_result_text_attributes_text");
|
||||
textValidator(ev, $textInput, 1, <%= Constants::TEXT_MAX_LENGTH %>);
|
||||
break;
|
||||
case ResultTypeEnum.COMMENT:
|
||||
var $commentInput = $form.find("#comment_message");
|
||||
var commentValid = textValidator(ev, $commentInput, 1,
|
||||
<%= Constants::TEXT_MAX_LENGTH %>);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
init();
|
||||
});
|
|
@ -1,12 +1,113 @@
|
|||
/* Loading overlay for search */
|
||||
$("#search-bar").submit(function (){
|
||||
if( $("#update-canvas") ){
|
||||
$(document.body).spin(true);
|
||||
setTimeout(function(){
|
||||
$(".spinner").remove();
|
||||
}, 1000);
|
||||
} else {
|
||||
animateSpinner();
|
||||
}
|
||||
});
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
/* Loading overlay for search */
|
||||
$("#search-bar").submit(function (){
|
||||
if( $("#update-canvas") ){
|
||||
$(document.body).spin(true);
|
||||
setTimeout(function(){
|
||||
$(".spinner").remove();
|
||||
}, 1000);
|
||||
} else {
|
||||
animateSpinner();
|
||||
}
|
||||
});
|
||||
|
||||
function loadDropdownNotifications() {
|
||||
var button = $('#notifications-dropdown');
|
||||
var noRecentText =
|
||||
$('.dropdown-notifications .notifications-no-recent');
|
||||
button
|
||||
.on('click', function() {
|
||||
noRecentText.hide();
|
||||
$.ajax({
|
||||
url: button.attr('data-href'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
beforeSend: animateSpinner($('.notifications-dropdown-header'), true),
|
||||
success: function(data) {
|
||||
$('.notifications-dropdown-header')
|
||||
.nextAll('li.notification')
|
||||
.remove();
|
||||
$('.notifications-dropdown-header')
|
||||
.after(data.html);
|
||||
animateSpinner($('.notifications-dropdown-header'), false);
|
||||
|
||||
var ul = $('.dropdown-menu.dropdown-notifications');
|
||||
if (ul.children('.notification').length === 0) {
|
||||
noRecentText.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#count-notifications').hide();
|
||||
toggleNotificationBellPosition();
|
||||
});
|
||||
}
|
||||
|
||||
function loadUnseenNotificationsNumber() {
|
||||
var notificationCount = $('#count-notifications');
|
||||
$.ajax({
|
||||
url: notificationCount.attr('data-href'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
notificationCount.html('');
|
||||
if ( data.notificationNmber > 0 ) {
|
||||
notificationCount.html(data.notificationNmber);
|
||||
notificationCount.show();
|
||||
toggleNotificationBellPosition();
|
||||
} else {
|
||||
notificationCount.hide();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleNotificationBellPosition() {
|
||||
var notificationCount = $('#count-notifications');
|
||||
var button = $('#notifications-dropdown');
|
||||
|
||||
if ( notificationCount.is(":hidden") ) {
|
||||
button
|
||||
.find('.fa-bell')
|
||||
.css('position', 'relative');
|
||||
} else {
|
||||
button
|
||||
.find('.fa-bell')
|
||||
.css('position', 'absolute');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initGlobalSwitchForm() {
|
||||
var teamSwitch = $('#team-switch');
|
||||
teamSwitch
|
||||
.find('.dropdown-menu .change-team')
|
||||
.on('click', function(){
|
||||
$('#user_current_organization_id')
|
||||
.val($(this).attr('data-id'));
|
||||
|
||||
teamSwitch
|
||||
.find('form')
|
||||
.submit();
|
||||
});
|
||||
}
|
||||
|
||||
function focusSearchInput() {
|
||||
var searchIco = $('#search-ico');
|
||||
searchIco
|
||||
.on('shown.bs.dropdown', function() {
|
||||
searchIco
|
||||
.find('input.form-control')
|
||||
.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// init
|
||||
loadDropdownNotifications();
|
||||
loadUnseenNotificationsNumber();
|
||||
toggleNotificationBellPosition();
|
||||
focusSearchInput();
|
||||
initGlobalSwitchForm();
|
||||
})();
|
||||
|
|
19
app/assets/javascripts/notifications.js
Normal file
19
app/assets/javascripts/notifications.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
$(document.body).ready(function () {
|
||||
$('.btn-more-notifications')
|
||||
.on("ajax:success", function (e, data) {
|
||||
var list = $('.notifications-list');
|
||||
var moreBtn = $('.btn-more-notifications');
|
||||
if (data.html) {
|
||||
// Remove button if all notifications are shown
|
||||
if (data.results_number < data.per_page) {
|
||||
moreBtn.remove();
|
||||
// Otherwise update reference
|
||||
} else {
|
||||
moreBtn.attr('href', data.more_notifications_url);
|
||||
}
|
||||
$(list).append(data.html);
|
||||
} else if (data.results_number < 1) {
|
||||
moreBtn.remove();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -4,11 +4,6 @@
|
|||
// CONSTANTS
|
||||
//************************************
|
||||
|
||||
var NAME_VALID = 0;
|
||||
var NAME_LENGTH_ERROR = -1;
|
||||
var NAME_INVALID_CHARACTERS_ERROR = -2;
|
||||
var NAME_WHITESPACES_ERROR = -3;
|
||||
|
||||
var DRAG_INVALID = 0;
|
||||
var DRAG_MOUSE = 1;
|
||||
var DRAG_TOUCH = 2;
|
||||
|
@ -41,7 +36,7 @@ var DEFAULT_CONNECTOR_STYLE =
|
|||
} ];
|
||||
var DEFAULT_CONNECTOR_STYLE_2 =
|
||||
{
|
||||
strokeStyle: "#FFFFFF",
|
||||
strokeStyle: '<%= Constants::COLOR_WHITE %>',
|
||||
lineWidth: 1.5,
|
||||
outlineColor: "transparent",
|
||||
outlineWidth: 0
|
||||
|
@ -62,7 +57,7 @@ var EDIT_ENDPOINT_STYLE =
|
|||
} ];
|
||||
var EDIT_CONNECTOR_STYLE_2 =
|
||||
{
|
||||
strokeStyle: "#FFFFFF",
|
||||
strokeStyle: '<%= Constants::COLOR_WHITE %>',
|
||||
lineWidth: 3,
|
||||
outlineColor: "transparent",
|
||||
outlineWidth: 0
|
||||
|
@ -141,13 +136,21 @@ var tutorialData;
|
|||
//************************************
|
||||
// DEFAULT INITIALIZATION CODE
|
||||
//************************************
|
||||
jsPlumb.ready(function () {
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
bindModeChange();
|
||||
bindAjax();
|
||||
bindWindowResizeEvent();
|
||||
initializeGraph(".diagram .module-large");
|
||||
initializeFullZoom();
|
||||
initializeTutorial(false);
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
jsPlumb.ready(function () {
|
||||
init();
|
||||
});
|
||||
|
||||
//************************************
|
||||
|
@ -250,8 +253,6 @@ function initializeEdit() {
|
|||
{ color: 'white', shadow: true }
|
||||
);
|
||||
});
|
||||
|
||||
initializeTutorial(true);
|
||||
}
|
||||
|
||||
function destroyEdit() {
|
||||
|
@ -324,9 +325,9 @@ function initializeFullZoom() {
|
|||
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
|
||||
|
||||
// Initialize comments
|
||||
initCommentOptions("ul.content-comments", false);
|
||||
initEditComments(".panel.module-large .tab-content");
|
||||
initDeleteComments(".panel.module-large .tab-content");
|
||||
Comments.initCommentOptions("ul.content-comments", false);
|
||||
Comments.initEditComments(".panel.module-large .tab-content");
|
||||
Comments.initDeleteComments(".panel.module-large .tab-content");
|
||||
}
|
||||
|
||||
function destroyFullZoom() {
|
||||
|
@ -656,85 +657,6 @@ function bindFullZoomAjaxTabs() {
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize comment form.
|
||||
function initCommentForm($el) {
|
||||
|
||||
var $form = $el.find("ul form");
|
||||
|
||||
$(".help-block", $form).addClass("hide");
|
||||
|
||||
$form.on("ajax:send", function (data) {
|
||||
$("#comment_message", $form).attr("readonly", true);
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $form.parents("ul");
|
||||
|
||||
// Remove potential "no comments" element
|
||||
list.parent().find(".content-comments")
|
||||
.find("li.no-comments").remove();
|
||||
|
||||
list.parent().find(".content-comments")
|
||||
.prepend("<li class='comment'>" + data.html + "</li>")
|
||||
.scrollTop(0);
|
||||
list.parents("ul").find("> li.comment:gt(8)").remove();
|
||||
$("#comment_message", $form).val("");
|
||||
$(".form-group", $form)
|
||||
.removeClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html("")
|
||||
.addClass("hide");
|
||||
scrollCommentOptions(
|
||||
list.parent().find(".content-comments .dropdown-comment"),
|
||||
false
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("ajax:error", function (ev, xhr) {
|
||||
if (xhr.status === 400) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
|
||||
if (messageError) {
|
||||
$(".form-group", $form)
|
||||
.addClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html(messageError[0])
|
||||
.removeClass("hide");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("ajax:complete", function () {
|
||||
$("#comment_message", $form)
|
||||
.attr("readonly", false)
|
||||
.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize show more comments link.
|
||||
function initCommentsLink($el) {
|
||||
|
||||
$el.find(".btn-more-comments")
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $(this).parents("ul");
|
||||
var moreBtn = list.find(".btn-more-comments");
|
||||
var listItem = moreBtn.parents('li');
|
||||
$(data.html).insertBefore(listItem);
|
||||
if (data.results_number < data.per_page) {
|
||||
moreBtn.remove();
|
||||
} else {
|
||||
moreBtn.attr("href", data.more_url);
|
||||
}
|
||||
|
||||
// Reposition dropdown comment options
|
||||
scrollCommentOptions(
|
||||
listItem.closest(".content-comments").find(".dropdown-comment"),
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize reloading manage user modal content after posting new
|
||||
// user.
|
||||
function initAddUserForm() {
|
||||
|
@ -838,14 +760,20 @@ function bindFullZoomAjaxTabs() {
|
|||
} else if (targetContents === "users") {
|
||||
initUsersEditLink(parentNode);
|
||||
} else if (targetContents === "comments") {
|
||||
initCommentForm(parentNode);
|
||||
initCommentsLink(parentNode);
|
||||
Comments.form(parentNode);
|
||||
Comments.moreComments(parentNode);
|
||||
}
|
||||
|
||||
$this.parents("ul").parent().find(".active").removeClass("active");
|
||||
$this.parents("li").addClass("active");
|
||||
target.addClass("active");
|
||||
$this.parents(".module-large").addClass("expanded");
|
||||
|
||||
// Call scrollBotton after the comments are displayed
|
||||
// so that the scrollHight can be calculated
|
||||
if ( targetContents === 'comments' ) {
|
||||
Comments.scrollBottom(parentNode);
|
||||
}
|
||||
})
|
||||
.on("ajax:error", function (e, xhr, status, error) {
|
||||
// TODO
|
||||
|
@ -1512,7 +1440,8 @@ function bindNewModuleAction(gridDistX, gridDistY) {
|
|||
var input = $("#new-module-name-input");
|
||||
// Validate module name
|
||||
var moduleNameValid = textValidator(ev, input,
|
||||
TextLimitEnum.NAME_MIN_LENGTH, TextLimitEnum.NAME_MAX_LENGTH, true);
|
||||
<%= Constants::NAME_MIN_LENGTH %>, <%= Constants::NAME_MAX_LENGTH %>,
|
||||
true);
|
||||
if (moduleNameValid) {
|
||||
// Set the "clicked" property to true
|
||||
modal.data("submit", "true");
|
||||
|
@ -1602,7 +1531,8 @@ function initEditModules() {
|
|||
var input = modal.find("#edit-module-name-input");
|
||||
// Validate module name
|
||||
var moduleNameValid = textValidator(ev, input,
|
||||
TextLimitEnum.NAME_MIN_LENGTH, TextLimitEnum.NAME_MAX_LENGTH, true);
|
||||
<%= Constants::NAME_MIN_LENGTH %>, <%= Constants::NAME_MAX_LENGTH %>,
|
||||
true);
|
||||
if (moduleNameValid) {
|
||||
var newName = input.val();
|
||||
var moduleId = modal.attr("data-module-id");
|
||||
|
@ -1709,8 +1639,8 @@ function initEditModuleGroups() {
|
|||
function handleRenameConfirm(modal, ev) {
|
||||
var input = modal.find("#edit-module-group-name-input");
|
||||
// Validate module group name
|
||||
var moduleNameValid = textValidator(ev, input, TextLimitEnum.REQUIRED,
|
||||
TextLimitEnum.NAME_MAX_LENGTH, true);
|
||||
var moduleNameValid = textValidator(ev, input, 1,
|
||||
<%= Constants::NAME_MAX_LENGTH %>, true);
|
||||
if (moduleNameValid) {
|
||||
var newModuleGroupName = input.val();
|
||||
var moduleId = modal.attr("data-module-id");
|
||||
|
@ -3196,202 +3126,97 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
|
|||
}
|
||||
})();
|
||||
|
||||
// Initialize first-time tutorial
|
||||
function initializeTutorial(isEditMode) {
|
||||
if (showTutorial()) {
|
||||
canvas_tutorial_helper();
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
// Add edit canvas tutorial step and show it
|
||||
if (!isEditMode && currentStep > 2 && currentStep < 6) {
|
||||
var $introJs = introJs();
|
||||
$introJs
|
||||
.setOptions({
|
||||
overlayOpacity: '0.2',
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnEsc: false,
|
||||
exitOnOverlayClick: false,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 4);
|
||||
if (this._currentStep == 1) {
|
||||
$introJs.setOption("disableInteraction", true);
|
||||
// Go to project canvas
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.on('click', function() {
|
||||
$('#edit-canvas-button').click();
|
||||
});
|
||||
}, 500);
|
||||
} else {
|
||||
$introJs.setOption("disableInteraction", false);
|
||||
var top = $('#canvas-container').position().top + $('#canvas-container').height()/3;
|
||||
$(".introjs-tooltipReferenceLayer").css({
|
||||
top: top + 'px'
|
||||
});
|
||||
}
|
||||
})
|
||||
.goToStep(currentStep == "5" ? 2 : 1)
|
||||
.start();
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var tutorialData = Cookies.get('tutorial_data');
|
||||
if (tutorialData) {
|
||||
tutorialData = JSON.parse(tutorialData);
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
|
||||
window.onresize = function() {
|
||||
$(".introjs-tooltip").css("right", ($("#canvas-container").width() + 20) + "px");
|
||||
};
|
||||
} else if (isEditMode && currentStep > 4 && currentStep < 7) {
|
||||
canvas_tutorial_helper();
|
||||
var editWorkflowTutorial = $("#canvas-container").attr("data-edit-workflow-step-text");
|
||||
var editWorkflowClickSaveTutorial = $("#canvas-container").attr("data-edit-workflow-click-save-step-text");
|
||||
$(".introjs-overlay").remove();
|
||||
$(".introjs-helperLayer").remove();
|
||||
$(".introjs-tooltipReferenceLayer").remove();
|
||||
if (stepNum >= 6 && stepNum <= 7) {
|
||||
var nextPage = window.location.pathname;
|
||||
initPageTutorialSteps(6, 7, nextPage, tutorialBeforeCb, tutorialAfterCb);
|
||||
} else if (stepNum >= 8 && stepNum <= 9) {
|
||||
// Go to edit canvas mode
|
||||
$('.introjs-overlay').remove();
|
||||
$('.introjs-helperLayer').remove();
|
||||
$('.introjs-tooltipReferenceLayer').remove();
|
||||
$('#edit-canvas-button').click();
|
||||
|
||||
setTimeout(function() {
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
intro: editWorkflowTutorial,
|
||||
element: document.querySelector('#canvas-new-module'),
|
||||
disableInteraction: true
|
||||
},
|
||||
{
|
||||
intro: editWorkflowClickSaveTutorial,
|
||||
element: document.querySelector('#canvas-save')
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.1',
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
// Go to edit workflow mode
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 6);
|
||||
if (this._currentStep == 0) {
|
||||
$(".introjs-tooltipReferenceLayer").addClass("max");
|
||||
$(".introjs-tooltip").css("left", "0");
|
||||
} else if (this._currentStep == 1) {
|
||||
setTimeout(function () {
|
||||
$(".introjs-tooltipReferenceLayer").removeClass("max");
|
||||
$(".introjs-tooltip").css("left", "0");
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.on('click', function() { $("#" + tarEl.id).click(); });
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}, 500);
|
||||
var nextPage = window.location.pathname;
|
||||
var steps = [{
|
||||
intro: $('#canvas-container').attr('data-edit-workflow-step-text'),
|
||||
element: $('#canvas-new-module')[0],
|
||||
position: 'right'
|
||||
}, {
|
||||
intro: $('#canvas-container')
|
||||
.attr('data-edit-workflow-click-save-step-text'),
|
||||
element: $('#canvas-save')[0],
|
||||
position: 'right'
|
||||
}];
|
||||
initPageTutorialSteps(8, 9, nextPage, tutorialBeforeCb, function() {
|
||||
// Go out of edit canvas mode
|
||||
$('.cancel-edit-canvas').click();
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
setTimeout( function(){
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
});
|
||||
});
|
||||
}, 600);
|
||||
} else if (!isEditMode && currentStep > 5 || currentStep < 10) {
|
||||
canvas_tutorial_helper();
|
||||
var sidebarTutorial = $("#canvas-container").attr("data-sidebar-step-text");
|
||||
var sidebarClickModuleTutorial = $("#canvas-container").attr("data-sidebar-click-module-step-text");
|
||||
Cookies.set('current_tutorial_step', '8');
|
||||
var qpcrModuleLeaf = $("li.leaf[data-module-id='" + tutorialData[0].qpcr_module + "']");
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
element: document.querySelector("#slide-panel .tree"),
|
||||
intro: sidebarTutorial,
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
element: qpcrModuleLeaf[0],
|
||||
intro: sidebarClickModuleTutorial,
|
||||
position: 'right'
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.2',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
nextLabel: 'Next',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnEsc: false,
|
||||
exitOnOverlayClick: false,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
})
|
||||
.onafterchange(function(tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 8);
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', qpcrModuleLeaf.find("a.module-link").attr('href'));
|
||||
}, 500);
|
||||
} else {
|
||||
$(".introjs-tooltipReferenceLayer").css("top", $("#slide-panel .tree").height()/3 + "px");
|
||||
}
|
||||
})
|
||||
.goToStep(currentStep == "9" ? 2 : 1)
|
||||
.start();
|
||||
tutorialAfterCb();
|
||||
}, steps);
|
||||
}, 1000);
|
||||
} else if (stepNum >= 10 && stepNum <= 11) {
|
||||
var qpcrModuleLeaf =
|
||||
$("li.leaf[data-module-id='" + tutorialData[0].qpcr_module + "']");
|
||||
|
||||
var nextPage = qpcrModuleLeaf.find('a.module-link').attr('href');
|
||||
var steps = [{
|
||||
element: $('#slide-panel .tree')[0],
|
||||
intro: $('#canvas-container').attr('data-sidebar-step-text'),
|
||||
position: 'right'
|
||||
}, {
|
||||
element: qpcrModuleLeaf[0],
|
||||
intro: $('#canvas-container')
|
||||
.attr('data-sidebar-click-module-step-text'),
|
||||
position: 'right'
|
||||
}];
|
||||
initPageTutorialSteps(10, 11, nextPage, tutorialBeforeCb,
|
||||
tutorialAfterCb, steps);
|
||||
}
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function canvas_tutorial_helper(){
|
||||
if( $('div').hasClass('introjs-overlay') ){
|
||||
$('#slide-panel')
|
||||
.css({'pointer-events': 'none'});
|
||||
$('#canvas-new-module')
|
||||
.css({'pointer-events': 'none'});
|
||||
/**
|
||||
* Callback to be executed before tutorial starts
|
||||
*/
|
||||
function tutorialBeforeCb() {
|
||||
$('#slide-panel')
|
||||
.css({'pointer-events': 'none'});
|
||||
$('#canvas-new-module')
|
||||
.css({'pointer-events': 'none'});
|
||||
|
||||
$.each( $('.panel-default'), function(i, el){
|
||||
$(el)
|
||||
.find('.tab-pane')
|
||||
.css({'pointer-events': 'none'});
|
||||
$(el)
|
||||
.find('.edit-tags-link')
|
||||
.css({'pointer-events': 'none'});
|
||||
$(el)
|
||||
.find('.panel-heading')
|
||||
.css({'pointer-events': 'none'});
|
||||
});
|
||||
}
|
||||
$.each($('.panel-default'), function(i, el) {
|
||||
$(el)
|
||||
.find('.tab-pane')
|
||||
.css({'pointer-events': 'none'});
|
||||
$(el)
|
||||
.find('.edit-tags-link')
|
||||
.css({'pointer-events': 'none'});
|
||||
$(el)
|
||||
.find('.panel-heading')
|
||||
.css({'pointer-events': 'none'});
|
||||
});
|
||||
}
|
||||
|
||||
function restore_after_tutorial(){
|
||||
$('#canvas-new-module')
|
||||
.css({'pointer-events': 'auto'});
|
||||
/**
|
||||
* Callback to be executed after tutorial exits
|
||||
*/
|
||||
function tutorialAfterCb() {
|
||||
$('#slide-panel')
|
||||
.css({'pointer-events': 'auto'});
|
||||
$('#canvas-new-module')
|
||||
.css({'pointer-events': 'auto'});
|
||||
|
||||
$.each( $('.panel-default'), function(i, el){
|
||||
$.each($('.panel-default'), function(i, el) {
|
||||
$(el)
|
||||
.find('.tab-pane')
|
||||
.css({'pointer-events': 'auto'});
|
||||
|
@ -3403,13 +3228,3 @@ function restore_after_tutorial(){
|
|||
.css({'pointer-events': 'auto'});
|
||||
});
|
||||
}
|
||||
|
||||
function showTutorial() {
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var tutorialProjectId = tutorialData[0].project;
|
||||
var currentProjectId = $("#canvas-container").attr("data-project-id");
|
||||
return tutorialProjectId == currentProjectId;
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
var editProjectModal = null;
|
||||
var editProjectModalTitle = null;
|
||||
var editProjectModalForm = null;
|
||||
var editProjectModalBody = null;
|
||||
var editProjectBtn = null;
|
||||
|
||||
var manageUsersModal = null;
|
||||
|
@ -39,6 +39,73 @@
|
|||
.prop("checked", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
newProjectModal = $('#new-project-modal');
|
||||
newProjectModalForm = newProjectModal.find('form');
|
||||
newProjectModalBody = newProjectModal.find('.modal-body');
|
||||
newProjectBtn = $('#new-project-btn');
|
||||
|
||||
editProjectModal = $('#edit-project-modal');
|
||||
editProjectModalTitle = editProjectModal.find('#edit-project-modal-label');
|
||||
editProjectModalBody = editProjectModal.find('.modal-body');
|
||||
editProjectBtn = editProjectModal.find(".btn[data-action='submit']");
|
||||
|
||||
manageUsersModal = $('#manage-users-modal');
|
||||
manageUsersModalBody = manageUsersModal.find('.modal-body');
|
||||
manageUsersModalFooter = manageUsersModal.find('.modal-footer');
|
||||
|
||||
initNewProjectModal();
|
||||
initEditProjectModal();
|
||||
initManageUsersModal();
|
||||
Comments.initCommentOptions('ul.content-comments', true);
|
||||
Comments.initEditComments('.panel-project .tab-content');
|
||||
Comments.initDeleteComments('.panel-project .tab-content');
|
||||
|
||||
// initialize project tab remote loading
|
||||
$('.panel-project .panel-footer [role=tab]')
|
||||
|
||||
.on('ajax:before', function(e) {
|
||||
var $this = $(this);
|
||||
var parentNode = $this.parents('li');
|
||||
var targetId = $this.attr('aria-controls');
|
||||
|
||||
if (parentNode.hasClass('active')) {
|
||||
// TODO move to fn
|
||||
parentNode.removeClass('active');
|
||||
$('#' + targetId).removeClass('active');
|
||||
return false;
|
||||
}
|
||||
})
|
||||
|
||||
.on('ajax:success', function(e, data, status, xhr) {
|
||||
var $this = $(this);
|
||||
var targetId = $this.attr('aria-controls');
|
||||
var target = $('#' + targetId);
|
||||
var parentNode = $this.parents('ul').parent();
|
||||
|
||||
target.html(data.html);
|
||||
initUsersEditLink(parentNode);
|
||||
Comments.form(parentNode);
|
||||
Comments.moreComments(parentNode);
|
||||
|
||||
// TODO move to fn
|
||||
parentNode.find('.active').removeClass('active');
|
||||
$this.parents('li').addClass('active');
|
||||
target.addClass('active');
|
||||
|
||||
Comments.scrollBottom(parentNode);
|
||||
})
|
||||
|
||||
.on('ajax:error', function() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the JS for new project modal to work.
|
||||
*/
|
||||
|
@ -203,83 +270,9 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize comment form.
|
||||
function initCommentForm($el) {
|
||||
|
||||
var $form = $el.find("ul form");
|
||||
|
||||
$(".help-block", $form).addClass("hide");
|
||||
|
||||
$form.on("ajax:send", function (data) {
|
||||
$("#comment_message", $form).attr("readonly", true);
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $form.parents("ul");
|
||||
|
||||
// Remove potential "no comments" element
|
||||
list.parent().find(".content-comments")
|
||||
.find("li.no-comments").remove();
|
||||
|
||||
list.parent().find(".content-comments")
|
||||
.prepend("<li class='comment'>" + data.html + "</li>")
|
||||
.scrollTop(0);
|
||||
list.parents("ul").find("> li.comment:gt(8)").remove();
|
||||
$("#comment_message", $form).val("");
|
||||
$(".form-group", $form)
|
||||
.removeClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html("")
|
||||
.addClass("hide");
|
||||
scrollCommentOptions(
|
||||
list.parent().find(".content-comments .dropdown-comment")
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("ajax:error", function (ev, xhr) {
|
||||
if (xhr.status === 400) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
|
||||
if (messageError) {
|
||||
$(".form-group", $form)
|
||||
.addClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html(messageError[0])
|
||||
.removeClass("hide");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("ajax:complete", function () {
|
||||
$("#comment_message", $form)
|
||||
.attr("readonly", false)
|
||||
.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize show more comments link.
|
||||
function initCommentsLink($el) {
|
||||
|
||||
$el.find(".btn-more-comments")
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $(this).parents("ul");
|
||||
var moreBtn = list.find(".btn-more-comments");
|
||||
var listItem = moreBtn.parents('li');
|
||||
$(data.html).insertBefore(listItem);
|
||||
if (data.results_number < data.per_page) {
|
||||
moreBtn.remove();
|
||||
} else {
|
||||
moreBtn.attr("href", data.more_url);
|
||||
}
|
||||
|
||||
// Reposition dropdown comment options
|
||||
scrollCommentOptions(listItem.closest(".content-comments").find(".dropdown-comment"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize reloading manage user modal content after posting new
|
||||
// user.
|
||||
|
||||
function initAddUserForm() {
|
||||
|
||||
manageUsersModalBody.find(".add-user-form")
|
||||
|
@ -339,217 +332,112 @@
|
|||
initUserRoleForms();
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
newProjectModal = $("#new-project-modal");
|
||||
newProjectModalForm = newProjectModal.find("form");
|
||||
newProjectModalBody = newProjectModal.find(".modal-body");
|
||||
newProjectBtn = $("#new-project-btn");
|
||||
|
||||
editProjectModal = $("#edit-project-modal");
|
||||
editProjectModalTitle = editProjectModal.find("#edit-project-modal-label");
|
||||
editProjectModalBody = editProjectModal.find(".modal-body");
|
||||
editProjectBtn = editProjectModal.find(".btn[data-action='submit']");
|
||||
|
||||
manageUsersModal = $("#manage-users-modal");
|
||||
manageUsersModalBody = manageUsersModal.find(".modal-body");
|
||||
manageUsersModalFooter = manageUsersModal.find(".modal-footer");
|
||||
|
||||
initNewProjectModal();
|
||||
initEditProjectModal();
|
||||
initManageUsersModal();
|
||||
initCommentOptions("ul.content-comments");
|
||||
initEditComments(".panel-project .tab-content");
|
||||
initDeleteComments(".panel-project .tab-content");
|
||||
|
||||
// initialize project tab remote loading
|
||||
$(".panel-project .panel-footer [role=tab]")
|
||||
|
||||
.on("ajax:before", function (e) {
|
||||
var $this = $(this);
|
||||
var parentNode = $this.parents("li");
|
||||
var targetId = $this.attr("aria-controls");
|
||||
|
||||
if (parentNode.hasClass("active")) {
|
||||
// TODO move to fn
|
||||
parentNode.removeClass("active");
|
||||
$("#" + targetId).removeClass("active");
|
||||
return false;
|
||||
}
|
||||
})
|
||||
|
||||
.on("ajax:success", function (e, data, status, xhr) {
|
||||
|
||||
var $this = $(this);
|
||||
var targetId = $this.attr("aria-controls");
|
||||
var target = $("#" + targetId);
|
||||
var parentNode = $this.parents("ul").parent();
|
||||
|
||||
target.html(data.html);
|
||||
initUsersEditLink(parentNode);
|
||||
initCommentForm(parentNode);
|
||||
initCommentsLink(parentNode);
|
||||
|
||||
// TODO move to fn
|
||||
parentNode.find(".active").removeClass("active");
|
||||
$this.parents("li").addClass("active");
|
||||
target.addClass("active");
|
||||
})
|
||||
|
||||
.on("ajax:error", function (e, xhr, status, error) {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Initialize first-time tutorial
|
||||
if (Cookies.get('tutorial_data')) {
|
||||
var tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
var goToStep = 1;
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var tutorialData = Cookies.get('tutorial_data');
|
||||
if (tutorialData) {
|
||||
tutorialData = JSON.parse(tutorialData);
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (isNaN(stepNum)) {
|
||||
// Cookies data initialization
|
||||
stepNum = 1;
|
||||
Cookies.set('current_tutorial_step', stepNum);
|
||||
tutorialData[0].backPagesPaths = [];
|
||||
Cookies.set('tutorial_data', tutorialData);
|
||||
}
|
||||
var demoProjectId = tutorialData[0].project;
|
||||
if (Cookies.get('current_tutorial_step')) {
|
||||
goToStep = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
}
|
||||
var demoProject = $("#" + demoProjectId);
|
||||
if (goToStep < 4) {
|
||||
var projectOptionsTutorial = $("#projects-toolbar").attr("data-project-options-step-text");
|
||||
demoProject.attr('data-step', '3');
|
||||
demoProject.attr('data-intro', projectOptionsTutorial);
|
||||
demoProject.attr('data-tooltipClass', 'custom next-page-link');
|
||||
var demoProject = $('#' + demoProjectId);
|
||||
|
||||
if (demoProject.offset().top > window.innerHeight / 2) {
|
||||
demoProject.attr('data-position', 'top');
|
||||
} // Otherwise show bottom
|
||||
if (stepNum >= 1 && stepNum <= 3) {
|
||||
var thirdStepPos = (demoProject.offset().top > window.innerHeight / 2) ?
|
||||
'top' : 'bottom';
|
||||
|
||||
var $introjs = introJs();
|
||||
$introjs
|
||||
.setOptions({
|
||||
overlayOpacity: '0.2',
|
||||
hidePrev: true,
|
||||
nextLabel: 'Next',
|
||||
prevLabel: 'Back',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
disableInteraction: true,
|
||||
tooltipClass: 'custom',
|
||||
tooltipPosition: 'right'
|
||||
})
|
||||
.goToStep(goToStep)
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep+1);
|
||||
if (this._currentStep == 2) {
|
||||
// Go to project canvas
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', $('#' + demoProjectId + '-project-canvas-link').attr('href'));
|
||||
var nextPage = $('#' + demoProjectId + '-project-canvas-link')
|
||||
.attr('href');
|
||||
var steps = [{
|
||||
element: $('#projects-toolbar')[0],
|
||||
intro: I18n.t('tutorial.tutorial_welcome_title_html'),
|
||||
position: 'bottom'
|
||||
}, {
|
||||
element: $('#new-project-btn')[0],
|
||||
intro: I18n.t('tutorial.create_project_html'),
|
||||
position: 'left'
|
||||
}, {
|
||||
element: demoProject[0],
|
||||
intro: I18n.t('tutorial.project_options_html'),
|
||||
position: thirdStepPos
|
||||
}];
|
||||
initPageTutorialSteps(1, 3, nextPage, tutorialBeforeCb,
|
||||
tutorialAfterCb, steps);
|
||||
} else if (stepNum === 22) {
|
||||
var protocolLink = $('#protocol-link');
|
||||
|
||||
// Disabling interactions for individual steps is
|
||||
// not (yet) possible in intro.js
|
||||
$(".introjs-disableInteraction").remove();
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$('.introjs-skipbutton').each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
});
|
||||
});
|
||||
|
||||
window.onresize = function() {
|
||||
if (goToStep == 3) {
|
||||
introJs().refresh();
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (goToStep > 18) {
|
||||
var archiveProjectTutorial = $("#projects-toolbar").attr("data-archive-project-step-text");
|
||||
var goodbye_message = $("#projects-toolbar").attr("data-goodbye-tutorial");
|
||||
Cookies.set('current_tutorial_step', '20');
|
||||
var position = "right";
|
||||
if (demoProject.offset().left > window.innerWidth / 2 || window.innerWidth < demoProject.width() + 100) {
|
||||
if (demoProject.offset().top > 500 && demoProject.offset().top > window.innerHeight / 2) {
|
||||
position = "top";
|
||||
var nextPage = protocolLink.attr('href');
|
||||
var steps = [{
|
||||
element: protocolLink[0],
|
||||
intro: I18n.t('tutorial.protocols_link_html'),
|
||||
position: 'left'
|
||||
}];
|
||||
initPageTutorialSteps(22, 22, nextPage, function() {}, function() {},
|
||||
steps);
|
||||
} else if (stepNum >= 25 && stepNum <= 26) {
|
||||
var firstStepPos = 'right';
|
||||
if (demoProject.offset().left > window.innerWidth / 2 ||
|
||||
window.innerWidth < demoProject.width() + 100) {
|
||||
if (demoProject.offset().top > 500 && demoProject.offset().top >
|
||||
window.innerHeight / 2) {
|
||||
firstStepPos = 'top';
|
||||
} else {
|
||||
position = "bottom";
|
||||
firstStepPos = 'bottom';
|
||||
}
|
||||
}
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [{
|
||||
element: document.getElementById(demoProjectId),
|
||||
intro: archiveProjectTutorial,
|
||||
position: position
|
||||
},{
|
||||
element: document.getElementsByClassName("avatar")[0],
|
||||
intro: goodbye_message
|
||||
}],
|
||||
overlayOpacity: '0.2',
|
||||
doneLabel: 'Start using sciNote',
|
||||
nextLabel: 'Next',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
disableInteraction: true,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
})
|
||||
.oncomplete(function () {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
})
|
||||
.start();
|
||||
|
||||
window.onresize = function() {
|
||||
switch (position) {
|
||||
case "right":
|
||||
$(".introjs-tooltip").css("left", (demoProject.width() + 20) + "px");
|
||||
break;
|
||||
default:
|
||||
$(".introjs-tooltip").css("left", (demoProject.width()/2 - $(".introjs-tooltip").width()/2) + "px");
|
||||
}
|
||||
};
|
||||
var nextPage = $('#new-report-btn').attr('href');
|
||||
var steps = [{
|
||||
element: demoProject[0],
|
||||
intro: I18n.t('tutorial.archive_project_html'),
|
||||
position: firstStepPos
|
||||
}, {
|
||||
element: $('.avatar')[0],
|
||||
intro: I18n.t('tutorial.goodbye_message'),
|
||||
position: 'left'
|
||||
}];
|
||||
initPageTutorialSteps(25, 26, nextPage, function() {}, function() {},
|
||||
steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function project_tutorial_helper(){
|
||||
$(document).ready(function(){
|
||||
if( $('div').hasClass('introjs-overlay')){
|
||||
$.each($('.panel'), function(i, el){
|
||||
$(el)
|
||||
.find('.panel-title')
|
||||
.css({ 'pointer-events': 'none' });
|
||||
$(el)
|
||||
.find('.tab-content')
|
||||
.css({ 'pointer-events': 'none' });
|
||||
$(el)
|
||||
.find('.form-submit-link')
|
||||
.css({
|
||||
'pointer-events': 'none',
|
||||
'color': '#d2d2d2'});
|
||||
$(el)
|
||||
.find('[data-action="edit"]')
|
||||
.css({
|
||||
'pointer-events': 'none',
|
||||
'color': '#d2d2d2'});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Callback to be executed before tutorial starts
|
||||
*/
|
||||
function tutorialBeforeCb() {
|
||||
$.each($('.panel'), function(i, el){
|
||||
$(el)
|
||||
.find('.panel-title')
|
||||
.css({ 'pointer-events': 'none' });
|
||||
$(el)
|
||||
.find('.tab-content')
|
||||
.css({ 'pointer-events': 'none' });
|
||||
$(el)
|
||||
.find('.form-submit-link')
|
||||
.css({
|
||||
'pointer-events': 'none',
|
||||
'color': '<%= Constants::COLOR_ALTO %>'});
|
||||
$(el)
|
||||
.find("[data-action='edit']")
|
||||
.css({
|
||||
'pointer-events': 'none',
|
||||
'color': '<%= Constants::COLOR_ALTO %>'});
|
||||
});
|
||||
}
|
||||
|
||||
function restore_after_tutorial(){
|
||||
/**
|
||||
* Callback to be executed after tutorial exits
|
||||
*/
|
||||
function tutorialAfterCb() {
|
||||
$.each($('.panel'), function(i, el){
|
||||
$(el)
|
||||
.find('.tab-content')
|
||||
|
@ -561,16 +449,14 @@
|
|||
.find('.form-submit-link')
|
||||
.css({
|
||||
'pointer-events': 'auto',
|
||||
'color': '#262626'});
|
||||
'color': '<%= Constants::COLOR_NERO %>'});
|
||||
$(el)
|
||||
.find('[data-action="edit"]')
|
||||
.find("[data-action='edit']")
|
||||
.css({
|
||||
'pointer-events': 'auto',
|
||||
'color': '#262626'});
|
||||
'color': '<%= Constants::COLOR_NERO %>'});
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
project_tutorial_helper();
|
||||
|
||||
}());
|
|
@ -1,10 +0,0 @@
|
|||
(function(){
|
||||
// display introductory modal
|
||||
if( Cookies.get('popup-already-shown') !== 'yes'){
|
||||
$("#introductory-popup-modal").modal('show',{
|
||||
backdrop: true,
|
||||
keyboard: false,
|
||||
});
|
||||
Cookies.set('popup-already-shown', 'yes', { expires: 7300 });
|
||||
}
|
||||
})();
|
|
@ -7,6 +7,8 @@
|
|||
that.find(".workflowimg-container").hide();
|
||||
initProjectExperiment(that);
|
||||
});
|
||||
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
function initProjectExperiment(element){
|
||||
|
@ -60,97 +62,38 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
// init
|
||||
init();
|
||||
})();
|
||||
|
||||
|
||||
/* Initialize the first-time demo tutorial if needed. */
|
||||
(function(){
|
||||
function initializeTutorial() {
|
||||
if (showTutorial()) {
|
||||
introJs()
|
||||
.setOptions({
|
||||
overlayOpacity: '0.2',
|
||||
hidePrev: true,
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom next-page-link',
|
||||
disableInteraction: true
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 2);
|
||||
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', $('[data-canvas-link]').data('canvasLink'));
|
||||
$('.introjs-disableInteraction').remove();
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
}
|
||||
})
|
||||
.start();
|
||||
|
||||
window.onresize = function() {
|
||||
if (Cookies.get('current_tutorial_step') == 4 ) {
|
||||
$(".introjs-tooltip").css("right", ($(".new-element.initial").width() + 60) + "px");
|
||||
}
|
||||
};
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
});
|
||||
});
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 4 && stepNum <= 5) {
|
||||
var nextPage = $('[data-canvas-link]').data('canvasLink');
|
||||
initPageTutorialSteps(4, 5, nextPage, tutorialBeforeCb, tutorialAfterCb);
|
||||
}
|
||||
}
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
if (currentStep < 3 || currentStep > 5)
|
||||
return false;
|
||||
var tutorialProjectId = tutorialData[0].project;
|
||||
var currentProjectId = $("#data-holder").attr("data-project-id");
|
||||
|
||||
return tutorialProjectId == currentProjectId;
|
||||
}
|
||||
|
||||
function project_tutorial_helper(){
|
||||
$(document).ready(function(){
|
||||
if( $('div').hasClass('introjs-overlay')){
|
||||
$.each( $(".panel-title"), function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
$.each( $(".workflowimg-container"), function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
$.each( $(".dropdown-experiment-actions").find("li"),
|
||||
function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Callback to be executed before tutorial starts
|
||||
*/
|
||||
function tutorialBeforeCb() {
|
||||
$.each( $(".panel-title"), function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
$.each( $(".workflowimg-container"), function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
$.each( $(".dropdown-experiment-actions").find("li"),
|
||||
function(){
|
||||
$(this).css({ "pointer-events": "none" });
|
||||
});
|
||||
}
|
||||
|
||||
function restore_after_tutorial(){
|
||||
/**
|
||||
* Callback to be executed after tutorial exits
|
||||
*/
|
||||
function tutorialAfterCb() {
|
||||
$.each( $(".panel-title"), function(){
|
||||
$(this).css({ "pointer-events": "auto" });
|
||||
});
|
||||
|
@ -163,9 +106,5 @@
|
|||
});
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initializeTutorial();
|
||||
project_tutorial_helper();
|
||||
});
|
||||
|
||||
init();
|
||||
})();
|
||||
|
|
|
@ -88,11 +88,11 @@ function initEditKeywords() {
|
|||
|
||||
// Init tagsinput & typeahead
|
||||
input.tagsinput({
|
||||
maxChars: 50,
|
||||
maxChars: <%= Constants::NAME_MAX_LENGTH %>,
|
||||
trimValue: true,
|
||||
typeaheadjs: {
|
||||
highlight: true,
|
||||
minLength: 3,
|
||||
minLength: <%= Constants::NAME_MIN_LENGTH %>,
|
||||
name: "keywords",
|
||||
source: keywordsEngine
|
||||
}
|
||||
|
@ -193,4 +193,4 @@ initEditName();
|
|||
initEditKeywords();
|
||||
initEditAuthors();
|
||||
initEditDescription();
|
||||
initModalSubmitAndHide();
|
||||
initModalSubmitAndHide();
|
|
@ -60,7 +60,7 @@ function importProtocolFromFile(
|
|||
// Populate values in the template
|
||||
if (values !== null) {
|
||||
_.each(values, function(val, key) {
|
||||
template.find("[data-val='" + key + "']").text(val);
|
||||
template.find("[data-val='" + key + "']").append(val);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ function importProtocolFromFile(
|
|||
var stepGuid = node.attr("guid");
|
||||
var stepPosition = String(Number.parseInt(node.attr("position")) + 1);
|
||||
var stepName = node.children("name").text();
|
||||
var stepDescription = node.children("description").text();
|
||||
var stepDescription = $(node.children("description")).html();
|
||||
|
||||
// Generate step element
|
||||
var stepEl = newPreviewElement(
|
||||
|
@ -431,7 +431,7 @@ function importProtocolFromFile(
|
|||
stepJson.id = stepId;
|
||||
stepJson.position = $(this).attr("position");
|
||||
stepJson.name = $(this).children("name").text();
|
||||
stepJson.description = $(this).children("description").text();
|
||||
stepJson.description = $(this).children("description").html();
|
||||
|
||||
// Iterate through assets
|
||||
var stepAssetsJson = [];
|
||||
|
@ -497,7 +497,7 @@ function importProtocolFromFile(
|
|||
$.extend(data_json, params);
|
||||
|
||||
var rough_size = roughSizeOfObject(data_json);
|
||||
if (rough_size > <%= FILE_MAX_SIZE.megabytes %>) {
|
||||
if (rough_size > <%= Constants::FILE_MAX_SIZE_MB.megabytes %>) {
|
||||
// Call the callback function
|
||||
resultCallback({ name: protocolJson["name"], new_name: null, status: "size_too_large" });
|
||||
return;
|
||||
|
|
|
@ -8,6 +8,21 @@ var protocolsTableEl = null;
|
|||
var protocolsDatatable = null;
|
||||
var repositoryType;
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
updateButtons();
|
||||
initProtocolsTable();
|
||||
initRowSelection();
|
||||
initKeywordFiltering();
|
||||
initLinkedChildrenModal();
|
||||
initCreateNewModal();
|
||||
initModals();
|
||||
initImport();
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
// Initialize protocols DataTable
|
||||
function initProtocolsTable() {
|
||||
protocolsTableEl = $("#protocols-table");
|
||||
|
@ -642,6 +657,7 @@ function initImport() {
|
|||
}
|
||||
);
|
||||
modalBody.append(failedMessageEl);
|
||||
animateSpinner(null, false);
|
||||
}
|
||||
if (nrSuccessful > 0) {
|
||||
var successMessageEl = newElement(
|
||||
|
@ -698,12 +714,15 @@ function initImport() {
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize everything
|
||||
updateButtons();
|
||||
initProtocolsTable();
|
||||
initRowSelection();
|
||||
initKeywordFiltering();
|
||||
initLinkedChildrenModal();
|
||||
initCreateNewModal();
|
||||
initModals();
|
||||
initImport();
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 23 && stepNum <= 24) {
|
||||
var nextPage = $('.navbar-brand').attr('href');
|
||||
initPageTutorialSteps(23, 24, nextPage, function() {}, function() {});
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
|
|
@ -77,6 +77,12 @@ function applyCancelCallBack() {
|
|||
initCallBacks();
|
||||
initHandsOnTable($new_step);
|
||||
toggleButtons(true);
|
||||
|
||||
setTimeout(function() {
|
||||
initStepsComments();
|
||||
openLinksInNewTab();
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
.on("ajax:error", function(e, xhr, status, error) {
|
||||
// TODO: error handling
|
||||
|
@ -106,6 +112,7 @@ function applyEditCallBack() {
|
|||
$("#new-step-main-tab a").on("shown.bs.tab", function() {
|
||||
$("#step_name").focus();
|
||||
});
|
||||
openLinksInNewTab();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -173,6 +180,11 @@ function formCallback($form) {
|
|||
contents.attr("value", data);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
initStepsComments();
|
||||
openLinksInNewTab();
|
||||
}, 1000);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -280,8 +292,8 @@ function initHandsOnTable(root) {
|
|||
var contents = $(this).find('.hot-contents');
|
||||
|
||||
$container.handsontable({
|
||||
startRows: 5,
|
||||
startCols: 5,
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
fillHandle: false,
|
||||
|
@ -326,8 +338,8 @@ function initEditableHandsOnTable(root) {
|
|||
|
||||
$container.handsontable({
|
||||
data: data,
|
||||
startRows: 5,
|
||||
startCols: 5,
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
minRows: 1,
|
||||
minCols: 1,
|
||||
rowHeaders: true,
|
||||
|
@ -373,6 +385,7 @@ function initCallBacks() {
|
|||
applyMoveStepCallBack();
|
||||
applyCollapseLinkCallBack();
|
||||
initDeleteStep();
|
||||
initHighlightjs();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -493,15 +506,15 @@ function processStep(ev, editMode, forS3) {
|
|||
$form.removeBlankFileForms();
|
||||
|
||||
var $fileInputs = $form.find("input[type=file]");
|
||||
var filesValid = filesValidator(ev, $fileInputs, FileTypeSizeEnum.FILE);
|
||||
var filesValid = filesValidator(ev, $fileInputs, FileTypeEnum.FILE);
|
||||
var $checklists = $form.find(".nested_step_checklists");
|
||||
var checklistsValid = checklistsValidator(ev, $checklists, editMode);
|
||||
var $nameInput = $form.find("#step_name");
|
||||
var nameValid = textValidator(ev, $nameInput, TextLimitEnum.REQUIRED,
|
||||
TextLimitEnum.NAME_MAX_LENGTH);
|
||||
var nameValid = textValidator(ev, $nameInput, 1,
|
||||
<%= Constants::NAME_MAX_LENGTH %>);
|
||||
var $descrTextarea = $form.find("#step_description");
|
||||
var descriptionValid = textValidator(ev, $descrTextarea,
|
||||
TextLimitEnum.OPTIONAL, TextLimitEnum.TEXT_MAX_LENGTH);
|
||||
var descriptionValid = textValidator(ev, $descrTextarea, 0,
|
||||
<%= Constants::TEXT_MAX_LENGTH %>);
|
||||
|
||||
if (filesValid && checklistsValid && nameValid && descriptionValid) {
|
||||
if (forS3) {
|
||||
|
@ -545,17 +558,29 @@ function renderTable(table) {
|
|||
}
|
||||
}
|
||||
|
||||
function initHighlightjs() {
|
||||
if(hljs){
|
||||
$('.ql-editor pre').each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initStepsComments() {
|
||||
Comments.initialize();
|
||||
Comments.initCommentOptions("ul.content-comments");
|
||||
Comments.initEditComments("#steps");
|
||||
Comments.initDeleteComments("#steps");
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// On init
|
||||
initCallBacks();
|
||||
initHandsOnTable($(document));
|
||||
expandAllSteps();
|
||||
setupAssetsLoading();
|
||||
|
||||
// Init comments edit/delete
|
||||
initCommentOptions("ul.content-comments");
|
||||
initEditComments("#steps");
|
||||
initDeleteComments("#steps");
|
||||
initStepsComments();
|
||||
initHighlightjs();
|
||||
|
||||
$(function () {
|
||||
|
|
@ -16,6 +16,31 @@
|
|||
|
||||
var checkedReports = [];
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
// Initialize selectors
|
||||
newReportModal = $('#new-report-modal');
|
||||
newReportModalBody = newReportModal.find('.modal-body');
|
||||
newReportCreateButton = $('#create-new-report-btn');
|
||||
deleteReportsModal = $('#delete-reports-modal');
|
||||
deleteReportsInput = $('#report-ids');
|
||||
newReportButton = $('#new-report-btn');
|
||||
editReportButton = $('#edit-report-btn');
|
||||
deleteReportsButton = $('#delete-reports-btn');
|
||||
checkAll = $('.check-all-reports');
|
||||
allChecks = $('.check-report');
|
||||
allRows = $('.report-row');
|
||||
|
||||
initNewReportModal();
|
||||
initCheckboxesAndEditing();
|
||||
updateButtons();
|
||||
initEditReport();
|
||||
initDeleteReports();
|
||||
initTutorial();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the new report modal.
|
||||
*/
|
||||
|
@ -138,86 +163,22 @@
|
|||
});
|
||||
}
|
||||
|
||||
/* Initilize first-time tutorial if needed */
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
if (showTutorial() && (currentStep > 14 && currentStep < 18)) {
|
||||
var reportsClickNewReportTutorial = $("#content").attr("data-reports-click-new-report-step-text");
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [{},
|
||||
{
|
||||
element: document.getElementById("new-report-btn"),
|
||||
intro: reportsClickNewReportTutorial,
|
||||
tooltipClass: "custom next-page-link"
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.1',
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom'
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 16);
|
||||
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', tarEl.href);
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
.goToStep(2)
|
||||
.start();
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
});
|
||||
});
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum === 19) {
|
||||
var nextPage = $('#new-report-btn').attr('href');
|
||||
var steps = [{
|
||||
element: $('#new-report-btn')[0],
|
||||
intro: $('#content').attr('data-reports-click-new-report-step-text'),
|
||||
position: 'right'
|
||||
}];
|
||||
initPageTutorialSteps(19, 19, nextPage, function() {}, function() {},
|
||||
steps);
|
||||
}
|
||||
}
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var tutorialProjectId = tutorialData[0].project;
|
||||
var currentProjectId = $(".report-table").attr("data-project-id");
|
||||
return tutorialProjectId == currentProjectId;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Initialize selectors
|
||||
newReportModal = $("#new-report-modal");
|
||||
newReportModalBody = newReportModal.find(".modal-body");
|
||||
newReportCreateButton = $("#create-new-report-btn");
|
||||
deleteReportsModal = $("#delete-reports-modal");
|
||||
deleteReportsInput = $("#report-ids");
|
||||
newReportButton = $("#new-report-btn");
|
||||
editReportButton = $("#edit-report-btn");
|
||||
deleteReportsButton = $("#delete-reports-btn");
|
||||
checkAll = $(".check-all-reports");
|
||||
allChecks = $(".check-report");
|
||||
allRows = $(".report-row");
|
||||
|
||||
initNewReportModal();
|
||||
initCheckboxesAndEditing();
|
||||
updateButtons();
|
||||
initEditReport();
|
||||
initDeleteReports();
|
||||
initTutorial();
|
||||
});
|
||||
|
||||
$(document).ready(init);
|
||||
}());
|
||||
|
|
|
@ -21,6 +21,25 @@ var ignoreUnsavedWorkAlert;
|
|||
* INITIALIZATION FUNCTIONS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
initializeReportElements($(REPORT_CONTENT));
|
||||
initializeGlobalReportSort();
|
||||
initializePrintPopup();
|
||||
initializeSaveToPdf();
|
||||
initializeSaveReport();
|
||||
initializeAddContentsModal();
|
||||
initializeSidebarNavigation();
|
||||
initializeUnsavedWorkDialog();
|
||||
initTutorial();
|
||||
|
||||
$('.report-nav-link').each(function() {
|
||||
truncateLongString($(this), <%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the hands on table on the given
|
||||
* element with the specified data.
|
||||
|
@ -317,7 +336,12 @@ function initializeSaveReport() {
|
|||
.on("ajax:success", function(e, xhr, opts, data) {
|
||||
if (data.status == 200) {
|
||||
// Redirect back to index
|
||||
|
||||
// Turn off all hooks related to alert window
|
||||
ignoreUnsavedWorkAlert = true;
|
||||
$(window).off('beforeunload');
|
||||
$(document).off('page:before-change');
|
||||
|
||||
$(location).attr("href", xhr.url);
|
||||
}
|
||||
})
|
||||
|
@ -435,40 +459,30 @@ function initializeSaveToPdf() {
|
|||
}
|
||||
|
||||
function initializeUnsavedWorkDialog() {
|
||||
var dh = $("#data-holder");
|
||||
var alertText = dh.attr("data-unsaved-work-text");
|
||||
var dh = $('#data-holder');
|
||||
var alertText = dh.attr('data-unsaved-work-text');
|
||||
|
||||
ignoreUnsavedWorkAlert = false;
|
||||
|
||||
$(window)
|
||||
.on("beforeunload", function(ev) {
|
||||
if (ignoreUnsavedWorkAlert) {
|
||||
// Remove unload listeners
|
||||
$(window).off("beforeunload");
|
||||
$(document).off("page:before-change");
|
||||
|
||||
ev.returnValue = undefined;
|
||||
return undefined;
|
||||
} else {
|
||||
return alertText;
|
||||
}
|
||||
});
|
||||
$(document).on("page:before-change", function(ev) {
|
||||
/**
|
||||
* Before unload event logic
|
||||
*/
|
||||
function beforeUnload() {
|
||||
var exit;
|
||||
if (ignoreUnsavedWorkAlert) {
|
||||
exit = true;
|
||||
} else {
|
||||
exit = confirm(alertText);
|
||||
}
|
||||
|
||||
if (exit) {
|
||||
// Remove unload listeners
|
||||
$(window).off("beforeunload");
|
||||
$(document).off("page:before-change");
|
||||
// We leave the page so remove all listeners
|
||||
$(window).off('beforeunload');
|
||||
$(document).off('page:before-change');
|
||||
}
|
||||
}
|
||||
|
||||
return exit;
|
||||
});
|
||||
$(window).on('beforeunload', beforeUnload);
|
||||
$(document).on('page:before-change', beforeUnload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1119,7 +1133,8 @@ function initializeReportSidebartruncation() {
|
|||
function() {
|
||||
$.each($("a.report-nav-link"),
|
||||
function(){
|
||||
truncateLongString($(this), 30);
|
||||
truncateLongString($(this),
|
||||
<%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -1128,94 +1143,31 @@ function initializeReportSidebartruncation() {
|
|||
observer.observe(target, config);
|
||||
}
|
||||
|
||||
/* Initialize the first-time demo tutorial if needed. */
|
||||
function initializeTutorial() {
|
||||
if (showTutorial()) {
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 20 && stepNum <= 21) {
|
||||
ignoreUnsavedWorkAlert = true;
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
overlayOpacity: '0.1',
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
tooltipClass: 'custom next-page-link',
|
||||
disableInteraction: true
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 18);
|
||||
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', tarEl.href);
|
||||
$('.introjs-disableInteraction').remove();
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
}
|
||||
})
|
||||
.start();
|
||||
|
||||
window.onresize = function() {
|
||||
if (Cookies.get('current_tutorial_step') == 18) {
|
||||
$(".introjs-tooltip").css("right", ($(".new-element.initial").width() + 60) + "px");
|
||||
}
|
||||
};
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
});
|
||||
var nextPage = $('.navbar-brand').attr('href');
|
||||
initPageTutorialSteps(20, 21, nextPage, function() {}, function() {
|
||||
ignoreUnsavedWorkAlert = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var currentStep = Cookies.get('current_tutorial_step');
|
||||
if (currentStep < 16 || currentStep > 18)
|
||||
return false;
|
||||
var tutorialProjectId = tutorialData[0].project;
|
||||
var currentProjectId = $("#data-holder").attr("data-project-id");
|
||||
return tutorialProjectId == currentProjectId;
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
/**
|
||||
* ACTUAL CODE
|
||||
*/
|
||||
initializeReportElements($(REPORT_CONTENT));
|
||||
initializeGlobalReportSort();
|
||||
initializePrintPopup();
|
||||
initializeSaveToPdf();
|
||||
initializeSaveReport();
|
||||
initializeAddContentsModal();
|
||||
initializeSidebarNavigation();
|
||||
initializeUnsavedWorkDialog();
|
||||
initializeTutorial();
|
||||
|
||||
$(".report-nav-link").each( function(){
|
||||
truncateLongString( $(this), 30);
|
||||
});
|
||||
// Check if we are actually at new report page
|
||||
if ($(REPORT_CONTENT).length) {
|
||||
init();
|
||||
}
|
||||
})
|
||||
|
||||
$(document).change(function(){
|
||||
setTimeout(function(){
|
||||
$(".report-nav-link").each( function(){
|
||||
truncateLongString( $(this), 30);
|
||||
truncateLongString( $(this), <%= Constants::NAME_TRUNCATION_LENGTH %>);
|
||||
});
|
||||
}, 1000);
|
||||
});
|
|
@ -61,7 +61,6 @@ function formAjaxResultAsset($form) {
|
|||
applyCollapseLinkCallBack();
|
||||
|
||||
toggleResultEditButtons(true);
|
||||
initResultCommentTabAjax();
|
||||
expandResult($newResult);
|
||||
$imgs = $newResult.find("img");
|
||||
reloadImages($imgs);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -10,8 +10,8 @@ function initEditableHandsOnTable(root) {
|
|||
|
||||
$container.handsontable({
|
||||
data: data,
|
||||
startRows: 5,
|
||||
startCols: 5,
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
minRows: 1,
|
||||
minCols: 1,
|
||||
rowHeaders: true,
|
||||
|
@ -99,7 +99,6 @@ function formAjaxResultTable($form) {
|
|||
applyCollapseLinkCallBack();
|
||||
initHandsOnTables($result);
|
||||
toggleResultEditButtons(true);
|
||||
initResultCommentTabAjax();
|
||||
expandResult($result);
|
||||
});
|
||||
$form.on("ajax:error", function(e, xhr, status, error) {
|
|
@ -60,13 +60,13 @@ function formAjaxResultText($form) {
|
|||
applyEditResultTextCallback();
|
||||
applyCollapseLinkCallBack();
|
||||
toggleResultEditButtons(true);
|
||||
initResultCommentTabAjax();
|
||||
expandResult(newResult);
|
||||
initHighlightjs();
|
||||
});
|
||||
$form.on("ajax:error", function(e, xhr, status, error) {
|
||||
var data = xhr.responseJSON;
|
||||
$form.renderFormErrors("result", data);
|
||||
|
||||
initHighlightjs();
|
||||
if (data["result_text.text"]) {
|
||||
var $el = $form.find("textarea[name=result\\[result_text_attributes\\]\\[text\\]]");
|
||||
|
||||
|
@ -77,4 +77,14 @@ function formAjaxResultText($form) {
|
|||
}
|
||||
|
||||
|
||||
function initHighlightjs() {
|
||||
if(hljs) {
|
||||
$('.ql-editor pre').each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
}
|
||||
$(document).ready(function() {
|
||||
initHighlightjs();
|
||||
});
|
||||
applyEditResultTextCallback();
|
||||
|
|
|
@ -1,709 +0,0 @@
|
|||
var rowsSelected = [];
|
||||
|
||||
// Tells whether we're currently viewing or editing table
|
||||
var currentMode = "viewMode";
|
||||
|
||||
// Tells what action will execute by pressing on save button (update/create)
|
||||
var saveAction = "update";
|
||||
var selectedSample;
|
||||
|
||||
table = $("#samples").DataTable({
|
||||
order: [[2, "desc"]],
|
||||
dom: "RB<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>>tpi",
|
||||
stateSave: true,
|
||||
buttons: [{
|
||||
extend: "colvis",
|
||||
text: function () {
|
||||
return '<span class="glyphicon glyphicon-option-horizontal"></span> ' +
|
||||
"<span class='hidden-xs-custom'>" +
|
||||
I18n.t('samples.column_visibility') +
|
||||
'</span>';
|
||||
},
|
||||
columns: ":gt(2)"
|
||||
}],
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: $("#samples").data("source"),
|
||||
global: false,
|
||||
type: "POST"
|
||||
},
|
||||
colReorder: {
|
||||
fixedColumnsLeft: 1000000 // Disable reordering
|
||||
},
|
||||
columnDefs: [{
|
||||
targets: 0,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: "dt-body-center",
|
||||
sWidth: "1%",
|
||||
render: function (data, type, full, meta){
|
||||
return "<input type='checkbox'>";
|
||||
}
|
||||
}, {
|
||||
targets: 1,
|
||||
searchable: false,
|
||||
orderable: true,
|
||||
sWidth: "1%"
|
||||
}],
|
||||
rowCallback: function(row, data, dataIndex){
|
||||
// Get row ID
|
||||
var rowId = data["DT_RowId"];
|
||||
|
||||
// If row ID is in the list of selected row IDs
|
||||
if($.inArray(rowId, rowsSelected) !== -1){
|
||||
$(row).find('input[type="checkbox"]').prop('checked', true);
|
||||
|
||||
$(row).addClass('selected');
|
||||
}
|
||||
},
|
||||
columns: (function() {
|
||||
var numOfColumns = $("#samples").data("num-columns");
|
||||
var columns = [];
|
||||
|
||||
for (var i = 0; i < numOfColumns; i++) {
|
||||
var visible = (i <= 6);
|
||||
columns.push({
|
||||
data: i + "",
|
||||
defaultContent: "",
|
||||
visible: visible
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
})(),
|
||||
fnDrawCallback: function(settings, json) {
|
||||
animateSpinner(this, false);
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
},
|
||||
stateLoadParams: function(settings, data) {
|
||||
// Check if URL parameters contain the column to show, if so, display it
|
||||
// no matter what
|
||||
if (getParam("new_col") !== null &&
|
||||
data.columns.length === $("#samples").data("num-columns") - 1) {
|
||||
// # of columns grew to +1, we need to add new column to data!
|
||||
var i = data.columns.length + "";
|
||||
data.columns.push({
|
||||
data: i,
|
||||
defaultContent: "",
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
},
|
||||
stateSaveCallback: function (settings, data) {
|
||||
// Set a cookie with the table state using the organization id
|
||||
localStorage.setItem('datatables_state/' + $("#samples").attr("data-organization-id"), JSON.stringify(data));
|
||||
},
|
||||
stateLoadCallback: function (settings) {
|
||||
// Load the table state for the current organization
|
||||
var state = localStorage.getItem('datatables_state/' + $("#samples").attr("data-organization-id"));
|
||||
if (state !== null) {
|
||||
return JSON.parse(state);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
preDrawCallback: function(settings) {
|
||||
animateSpinner(this);
|
||||
}
|
||||
});
|
||||
|
||||
table.buttons().container().appendTo('#datatables-buttons');
|
||||
|
||||
// Enables noSearchHidden plugin
|
||||
$.fn.dataTable.defaults.noSearchHidden = true
|
||||
|
||||
// Append button to inner toolbar in table
|
||||
$("div.toolbarButtons").appendTo("div.toolbar");
|
||||
$("div.toolbarButtons").show();
|
||||
|
||||
$(".delete_samples_submit").click(function () {
|
||||
animateLoading();
|
||||
});
|
||||
|
||||
$("#assignSamples, #unassignSamples").click(function () {
|
||||
animateLoading();
|
||||
});
|
||||
|
||||
// Updates "Select all" control in a data table
|
||||
function updateDataTableSelectAllCtrl(table){
|
||||
var $table = table.table().node();
|
||||
var $chkbox_all = $('tbody input[type="checkbox"]', $table);
|
||||
var $chkbox_checked = $('tbody input[type="checkbox"]:checked', $table);
|
||||
var chkbox_select_all = $('thead input[name="select_all"]', $table).get(0);
|
||||
|
||||
// If none of the checkboxes are checked
|
||||
if($chkbox_checked.length === 0){
|
||||
chkbox_select_all.checked = false;
|
||||
if('indeterminate' in chkbox_select_all){
|
||||
chkbox_select_all.indeterminate = false;
|
||||
}
|
||||
|
||||
// If all of the checkboxes are checked
|
||||
} else if ($chkbox_checked.length === $chkbox_all.length){
|
||||
chkbox_select_all.checked = true;
|
||||
if('indeterminate' in chkbox_select_all){
|
||||
chkbox_select_all.indeterminate = false;
|
||||
}
|
||||
|
||||
// If some of the checkboxes are checked
|
||||
} else {
|
||||
chkbox_select_all.checked = true;
|
||||
if('indeterminate' in chkbox_select_all){
|
||||
chkbox_select_all.indeterminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle click on table cells with checkboxes
|
||||
$('#samples').on('click', 'tbody td, thead th:first-child', function(e){
|
||||
$(this).parent().find('input[type="checkbox"]').trigger('click');
|
||||
});
|
||||
|
||||
// Handle clicks on checkbox
|
||||
$("#samples tbody").on("click", "input[type='checkbox']", function(e){
|
||||
if (currentMode != "viewMode")
|
||||
return false;
|
||||
|
||||
// Get row ID
|
||||
var $row = $(this).closest("tr");
|
||||
var data = table.row($row).data();
|
||||
var rowId = data["DT_RowId"];
|
||||
|
||||
// Determine whether row ID is in the list of selected row IDs
|
||||
var index = $.inArray(rowId, rowsSelected);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected row IDs
|
||||
if(this.checked && index === -1){
|
||||
rowsSelected.push(rowId);
|
||||
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
|
||||
} else if (!this.checked && index !== -1){
|
||||
rowsSelected.splice(index, 1);
|
||||
}
|
||||
|
||||
if(this.checked){
|
||||
$row.addClass('selected');
|
||||
} else {
|
||||
$row.removeClass('selected');
|
||||
}
|
||||
|
||||
updateDataTableSelectAllCtrl(table);
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
// Handle click on "Select all" control
|
||||
$('#samples thead input[name="select_all"]').on('click', function(e){
|
||||
if(this.checked){
|
||||
$('#samples tbody input[type="checkbox"]:not(:checked)').trigger('click');
|
||||
} else {
|
||||
$('#samples tbody input[type="checkbox"]:checked').trigger('click');
|
||||
}
|
||||
|
||||
// Prevent click event from propagating to parent
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Append selected samples to form
|
||||
$("form#form-samples").submit(function(e){
|
||||
var form = this;
|
||||
|
||||
if (currentMode == "viewMode")
|
||||
appendSamplesIdToForm(form);
|
||||
});
|
||||
|
||||
// Append selected samples and headers form
|
||||
$("form#form-export").submit(function(e){
|
||||
var form = this;
|
||||
|
||||
if (currentMode == "viewMode") {
|
||||
// Remove all hidden fields
|
||||
$("#form-export").find("input[name=sample_ids\\[\\]]").remove();
|
||||
$("#form-export").find("input[name=header_ids\\[\\]]").remove();
|
||||
|
||||
// Append samples
|
||||
appendSamplesIdToForm(form);
|
||||
|
||||
// Append visible column information
|
||||
$("table#samples thead tr").children("th").each(function(i) {
|
||||
var th = $(this);
|
||||
var val;
|
||||
|
||||
if ($(th).attr("id") == "sample-name")
|
||||
val = -1;
|
||||
else if ($(th).attr("id") == "sample-type")
|
||||
val = -2;
|
||||
else if ($(th).attr("id") == "sample-group")
|
||||
val = -3;
|
||||
else if ($(th).attr("id") == "added-by")
|
||||
val = -4;
|
||||
else if ($(th).attr("id") == "added-on")
|
||||
val = -5;
|
||||
else if ($(th).hasClass("custom-field"))
|
||||
val = th.attr("id");
|
||||
|
||||
if (val)
|
||||
$(form).append(
|
||||
$('<input>')
|
||||
.attr('type', 'hidden')
|
||||
.attr('name', 'header_ids[]')
|
||||
.val(val)
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function appendSamplesIdToForm(form) {
|
||||
$.each(rowsSelected, function(index, rowId){
|
||||
$(form).append(
|
||||
$('<input>')
|
||||
.attr('type', 'hidden')
|
||||
.attr('name', 'sample_ids[]')
|
||||
.val(rowId)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle table draw event
|
||||
table.on('draw', function(){
|
||||
updateDataTableSelectAllCtrl(table);
|
||||
});
|
||||
|
||||
// Edit sample
|
||||
function onClickEdit() {
|
||||
if (rowsSelected.length != 1) return;
|
||||
|
||||
var row = table.row("#" + rowsSelected[0]);
|
||||
var node = row.node();
|
||||
var rowData = row.data();
|
||||
|
||||
$(node).find("td input").trigger("click");
|
||||
selectedSample = node;
|
||||
|
||||
clearAllErrors();
|
||||
changeToEditMode();
|
||||
updateButtons();
|
||||
saveAction = "update";
|
||||
|
||||
$.ajax({
|
||||
url: rowData["sampleInfoUrl"],
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
// Show save and cancel buttons in first two columns
|
||||
$(node).children("td").eq(0).html($("#saveSample").clone());
|
||||
$(node).children("td").eq(1).html($("#cancelSave").clone());
|
||||
|
||||
// Sample name column
|
||||
var colIndex = getColumnIndex("#sample-name");
|
||||
if (colIndex) {
|
||||
$(node).children("td").eq(colIndex).html(changeToInputField("sample", "name", data["sample"]["name"]));
|
||||
}
|
||||
|
||||
// Sample type column
|
||||
var colIndex = getColumnIndex("#sample-type");
|
||||
if (colIndex) {
|
||||
var selectType = createSampleTypeSelect(data["sample_types"], data["sample"]["sample_type"]);
|
||||
$(node).children("td").eq(colIndex).html(selectType);
|
||||
$("select[name=sample_type_id]").selectpicker();
|
||||
}
|
||||
|
||||
// Sample group column
|
||||
var colIndex = getColumnIndex("#sample-group");
|
||||
if (colIndex) {
|
||||
var selectGroup = createSampleGroupSelect(data["sample_groups"], data["sample"]["sample_group"]);
|
||||
$(node).children("td").eq(colIndex).html(selectGroup);
|
||||
$("select[name=sample_group_id]").selectpicker();
|
||||
}
|
||||
|
||||
// Take care of custom fields
|
||||
var cfields = data["sample"]["custom_fields"];
|
||||
$(node).children("td").each(function(i) {
|
||||
var td = $(this);
|
||||
var rawIndex = table.column.index("fromVisible", i);
|
||||
var colHeader = table.column(rawIndex).header();
|
||||
if ($(colHeader).hasClass("custom-field")) {
|
||||
// Check if custom field on this sample exists
|
||||
var cf = cfields[$(colHeader).attr("id")];
|
||||
if (cf)
|
||||
td.html(changeToInputField("sample_custom_fields", cf["sample_custom_field_id"], cf["value"]));
|
||||
else
|
||||
td.html(changeToInputField("custom_fields", $(colHeader).attr("id"), ""));
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function (e, data, status, xhr) {
|
||||
if (e.status == 403) {
|
||||
sampleAlertMsg(I18n.t("samples.js.permission_error"), "danger");
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save sample
|
||||
function onClickSave() {
|
||||
if (saveAction == "update") {
|
||||
var row = table.row(selectedSample);
|
||||
var node = row.node();
|
||||
var rowData = row.data();
|
||||
} else if (saveAction == "create")
|
||||
var node = selectedSample;
|
||||
|
||||
// First fetch all the data in input fields
|
||||
data = {
|
||||
sample_id: $(selectedSample).attr("id"),
|
||||
sample: {},
|
||||
custom_fields: {}, // These fields are not currently bound to this sample
|
||||
sample_custom_fields: {} // These fields are already in database (linked to this sample)
|
||||
};
|
||||
|
||||
// Direct sample attributes
|
||||
// Sample name
|
||||
$(node).find("td input[data-object = sample]").each(function() {
|
||||
data["sample"][$(this).attr("name")] = $(this).val();
|
||||
});
|
||||
|
||||
// Sample type
|
||||
$(node).find("td select[name = sample_type_id]").each(function() {
|
||||
data["sample"]["sample_type_id"] = $(this).val();
|
||||
});
|
||||
|
||||
// Sample group
|
||||
$(node).find("td select[name = sample_group_id]").each(function() {
|
||||
data["sample"]["sample_group_id"] = $(this).val();
|
||||
});
|
||||
|
||||
// Custom fields (new fields)
|
||||
$(node).find("td input[data-object = custom_fields]").each(function () {
|
||||
// Send data only and only if string is not empty
|
||||
if ($(this).val().trim()) {
|
||||
data["custom_fields"][$(this).attr("name")] = $(this).val();
|
||||
}
|
||||
});
|
||||
|
||||
// Sample custom fields (existent fields)
|
||||
$(node).find("td input[data-object = sample_custom_fields]").each(function () {
|
||||
data["sample_custom_fields"][$(this).attr("name")] = $(this).val();
|
||||
});
|
||||
|
||||
var url = (saveAction == "update" ? rowData["sampleUpdateUrl"] : $("table#samples").data("create-sample"))
|
||||
var type = (saveAction == "update" ? "PUT" : "POST")
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: type,
|
||||
dataType: "json",
|
||||
data: data,
|
||||
success: function (data) {
|
||||
sampleAlertMsg(data.flash, "success");
|
||||
onClickCancel();
|
||||
},
|
||||
error: function (e, eData, status, xhr) {
|
||||
var data = e.responseJSON;
|
||||
clearAllErrors();
|
||||
sampleAlertMsgHide();
|
||||
|
||||
if (e.status == 404) {
|
||||
sampleAlertMsg(I18n.t("samples.js.not_found_error"), "danger");
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
}
|
||||
else if (e.status == 403) {
|
||||
sampleAlertMsg(I18n.t("samples.js.permission_error"), "danger");
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
}
|
||||
else if (e.status == 400) {
|
||||
if (data["init_fields"]) {
|
||||
var init_fields = data["init_fields"];
|
||||
|
||||
// Validate sample name
|
||||
if (init_fields["name"]) {
|
||||
var input = $(selectedSample).find("input[name=name]");
|
||||
|
||||
if (input) {
|
||||
input.closest(".form-group").addClass("has-error");
|
||||
input.parent().append("<span class='help-block'>" + init_fields["name"] + "<br /></span>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Validate custom fields
|
||||
$.each(data["custom_fields"] || [], function(key, val) {
|
||||
$.each(val, function(key, val) {
|
||||
var input = $(selectedSample).find("input[name=" + key + "]");
|
||||
|
||||
if (input) {
|
||||
input.closest(".form-group").addClass("has-error");
|
||||
input.parent().append("<span class='help-block'>" + val["value"][0] + "<br /></span>");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Validate sample custom fields
|
||||
$.each(data["sample_custom_fields"] || [], function(key, val) {
|
||||
$.each(val, function(key, val) {
|
||||
var input = $(selectedSample).find("input[name=" + key + "]");
|
||||
|
||||
if (input) {
|
||||
input.closest(".form-group").addClass("has-error");
|
||||
input.parent().append("<span class='help-block'>" + val["value"][0] + "<br /></span>");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enable/disable edit button
|
||||
function updateButtons() {
|
||||
if (currentMode=="viewMode") {
|
||||
$("#importSamplesButton").removeClass("disabled");
|
||||
$("#importSamplesButton").prop("disabled",false);
|
||||
$("#addSample").removeClass("disabled");
|
||||
$("#addSample").prop("disabled",false);
|
||||
$("#addNewColumn").removeClass("disabled");
|
||||
$("#addNewColumn").prop("disabled",false);
|
||||
|
||||
if (rowsSelected.length == 1) {
|
||||
$("#editSample").prop("disabled", false);
|
||||
$("#editSample").removeClass("disabled");
|
||||
$("#deleteSamplesButton").prop("disabled", false);
|
||||
$("#deleteSamplesButton").removeClass("disabled");
|
||||
$("#exportSamplesButton").removeClass("disabled");
|
||||
$("#exportSamplesButton").prop("disabled",false);
|
||||
$("#exportSamplesButton").on("click", function() { $('#form-export').submit(); });
|
||||
$("#assignSamples").removeClass("disabled");
|
||||
$("#assignSamples").prop("disabled", false);
|
||||
$("#unassignSamples").removeClass("disabled");
|
||||
$("#unassignSamples").prop("disabled", false);
|
||||
}
|
||||
else if (rowsSelected.length == 0) {
|
||||
$("#editSample").prop("disabled", true);
|
||||
$("#editSample").addClass("disabled");
|
||||
$("#deleteSamplesButton").prop("disabled", true);
|
||||
$("#deleteSamplesButton").addClass("disabled");
|
||||
$("#exportSamplesButton").addClass("disabled");
|
||||
$("#exportSamplesButton").prop("disabled",true);
|
||||
$("#exportSamplesButton").off("click");
|
||||
$("#assignSamples").addClass("disabled");
|
||||
$("#assignSamples").prop("disabled", true);
|
||||
$("#unassignSamples").addClass("disabled");
|
||||
$("#unassignSamples").prop("disabled", true);
|
||||
}
|
||||
else {
|
||||
$("#editSample").prop("disabled", true);
|
||||
$("#editSample").addClass("disabled");
|
||||
$("#deleteSamplesButton").prop("disabled", false);
|
||||
$("#deleteSamplesButton").removeClass("disabled");
|
||||
$("#exportSamplesButton").removeClass("disabled");
|
||||
$("#exportSamplesButton").prop("disabled",false);
|
||||
$("#exportSamplesButton").on("click", function() { $('#form-export').submit(); });
|
||||
$("#assignSamples").removeClass("disabled");
|
||||
$("#assignSamples").prop("disabled", false);
|
||||
$("#unassignSamples").removeClass("disabled");
|
||||
$("#unassignSamples").prop("disabled", false);
|
||||
}
|
||||
}
|
||||
else if (currentMode=="editMode") {
|
||||
$("#importSamplesButton").addClass("disabled");
|
||||
$("#importSamplesButton").prop("disabled",true);
|
||||
$("#addSample").addClass("disabled");
|
||||
$("#addSample").prop("disabled",true);
|
||||
$("#editSample").addClass("disabled");
|
||||
$("#editSample").prop("disabled",true);
|
||||
$("#addNewColumn").addClass("disabled");
|
||||
$("#addNewColumn").prop("disabled", true);
|
||||
$("#exportSamplesButton").addClass("disabled");
|
||||
$("#exportSamplesButton").off("click");
|
||||
$("#deleteSamplesButton").addClass("disabled");
|
||||
$("#deleteSamplesButton").prop("disabled",true);
|
||||
$("#assignSamples").addClass("disabled");
|
||||
$("#assignSamples").prop("disabled", true);
|
||||
$("#unassignSamples").addClass("disabled");
|
||||
$("#unassignSamples").prop("disabled", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all has-error tags
|
||||
function clearAllErrors() {
|
||||
// Remove any validation errors
|
||||
$(selectedSample).find(".has-error").each(function() {
|
||||
$(this).removeClass("has-error");
|
||||
$(this).find("span").remove();
|
||||
});
|
||||
|
||||
// Remove any alerts
|
||||
$("#alert-container").find("div").remove();
|
||||
}
|
||||
|
||||
// Restore previous table
|
||||
function onClickCancel() {
|
||||
table.ajax.reload();
|
||||
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function onClickAddSample() {
|
||||
changeToEditMode();
|
||||
updateButtons();
|
||||
|
||||
saveAction = "create";
|
||||
$.ajax({
|
||||
url: $("table#samples").data("new-sample"),
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var tr = document.createElement("tr")
|
||||
$("table#samples thead tr").children("th").each(function(i) {
|
||||
var th = $(this);
|
||||
if ($(th).attr("id") == "checkbox") {
|
||||
var td = createTdElement("");
|
||||
$(td).html($("#saveSample").clone());
|
||||
tr.appendChild(td);
|
||||
}
|
||||
else if ($(th).attr("id") == "assigned") {
|
||||
var td = createTdElement("");
|
||||
$(td).html($("#cancelSave").clone());
|
||||
tr.appendChild(td);
|
||||
}
|
||||
else if ($(th).attr("id") == "sample-name") {
|
||||
var input = changeToInputField("sample", "name", "");
|
||||
tr.appendChild(createTdElement(input));
|
||||
}
|
||||
else if ($(th).attr("id") == "sample-type") {
|
||||
var colIndex = getColumnIndex("#sample-type")
|
||||
if (colIndex) {
|
||||
var selectType = createSampleTypeSelect(data["sample_types"], -1);
|
||||
var td = createTdElement("");
|
||||
td.appendChild(selectType[0]);
|
||||
tr.appendChild(td);
|
||||
}
|
||||
}
|
||||
else if ($(th).attr("id") == "sample-group") {
|
||||
var colIndex = getColumnIndex("#sample-group")
|
||||
if (colIndex) {
|
||||
var selectGroup = createSampleGroupSelect(data["sample_groups"], -1);
|
||||
var td = createTdElement("");
|
||||
td.appendChild(selectGroup[0]);
|
||||
tr.appendChild(td);
|
||||
}
|
||||
}
|
||||
else if ($(th).hasClass("custom-field")) {
|
||||
var input = changeToInputField("custom_fields", th.attr("id"), "");
|
||||
tr.appendChild(createTdElement(input));
|
||||
}
|
||||
else {
|
||||
// Column we don't care for, just add empty td
|
||||
tr.appendChild(createTdElement(""));
|
||||
}
|
||||
});
|
||||
$("table#samples").prepend(tr);
|
||||
selectedSample = tr;
|
||||
|
||||
// Init dropdown with icons
|
||||
$("select[name=sample_group_id]").selectpicker();
|
||||
$("select[name=sample_type_id]").selectpicker();
|
||||
},
|
||||
error: function (e, eData, status, xhr) {
|
||||
if (e.status == 403)
|
||||
sampleAlertMsg(I18n.t("samples.js.permission_error"), "danger");
|
||||
changeToViewMode();
|
||||
updateButtons();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Handle enter key
|
||||
$(document).off("keypress").keypress(function(event) {
|
||||
var keycode = (event.keyCode ? event.keyCode : event.which);
|
||||
if(currentMode == "editMode" && keycode == '13'){
|
||||
$("#saveSample").click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
function getColumnIndex(id) {
|
||||
if (id < 0) return false;
|
||||
return table.column(id).index("visible");
|
||||
}
|
||||
|
||||
// Takes object and surrounds it with input
|
||||
function changeToInputField(object, name, value) {
|
||||
return "<div class='form-group'><input class='form-control' data-object='" + object + "' name='" + name + "' value='" + value + "'></input></div>";
|
||||
}
|
||||
|
||||
// Return td element with content
|
||||
function createTdElement(content) {
|
||||
var td = document.createElement("td");
|
||||
td.innerHTML = content;
|
||||
return td;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates select dropdown for sample type
|
||||
* @param data List of sample types
|
||||
* @param selected Selected sample type id
|
||||
*/
|
||||
function createSampleTypeSelect(data, selected) {
|
||||
var $selectType = $("<select></select>").attr("name", "sample_type_id").addClass("show-tick");
|
||||
|
||||
var $option = $("<option></option>").attr("value", -1).text(I18n.t("samples.table.no_type"))
|
||||
$selectType.append($option);
|
||||
|
||||
$.each(data, function(i, val) {
|
||||
var $option = $("<option></option>").attr("value", val["id"]).text(val["name"]);
|
||||
$selectType.append($option);
|
||||
});
|
||||
$selectType.val(selected);
|
||||
return $selectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates select dropdown for sample group
|
||||
* @param data List of sample groups
|
||||
* @param selected Selected sample group id
|
||||
*/
|
||||
function createSampleGroupSelect(data, selected) {
|
||||
var $selectGroup = $("<select></select>").attr("name", "sample_group_id").addClass("show-tick");
|
||||
|
||||
var $span = $("<span></span>").addClass("glyphicon glyphicon-asterisk");
|
||||
var $option = $("<option></option>").attr("value", -1).text(I18n.t("samples.table.no_group"))
|
||||
.attr("data-icon", "glyphicon glyphicon-asterisk");
|
||||
$selectGroup.append($option);
|
||||
|
||||
$.each(data, function(i, val) {
|
||||
var $span = $("<span></span>").addClass("glyphicon glyphicon-asterisk").css("color", val["color"]);
|
||||
var $option = $("<option></option>").attr("value", val["id"]).text(val["name"])
|
||||
.attr("data-content", $span.prop("outerHTML") + " " + val["name"]);
|
||||
|
||||
$selectGroup.append($option);
|
||||
});
|
||||
$selectGroup.val(selected);
|
||||
return $selectGroup;
|
||||
}
|
||||
|
||||
function changeToViewMode() {
|
||||
currentMode = "viewMode";
|
||||
|
||||
// $("#saveCancel").hide();
|
||||
|
||||
// Table specific stuff
|
||||
table.button(0).enable(true);
|
||||
}
|
||||
|
||||
function changeToEditMode() {
|
||||
currentMode = "editMode";
|
||||
|
||||
// $("#saveCancel").show();
|
||||
|
||||
// Table specific stuff
|
||||
table.button(0).enable(false);
|
||||
}
|
1310
app/assets/javascripts/samples/sample_datatable.js.erb
Normal file
1310
app/assets/javascripts/samples/sample_datatable.js.erb
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
265
app/assets/javascripts/samples/sample_types_groups.js
Normal file
265
app/assets/javascripts/samples/sample_types_groups.js
Normal file
|
@ -0,0 +1,265 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
function showNewSampleTypeGroupForm() {
|
||||
$('#create-resource').off();
|
||||
$('#create-resource').on('click', function() {
|
||||
$('.new-resource-form').slideDown();
|
||||
$('#name-input').focus();
|
||||
});
|
||||
}
|
||||
|
||||
function newSampleTypeFormCancel() {
|
||||
$('#remove').off();
|
||||
$('#remove').on('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
$('#name-input').val('');
|
||||
$('.new-resource-form').slideUp();
|
||||
$('#new_sample_type').clearFormErrors();
|
||||
$('#new_sample_group').clearFormErrors();
|
||||
});
|
||||
}
|
||||
|
||||
function submitEditSampleTypeGroupForm() {
|
||||
$('.edit-confirm').off();
|
||||
$('.edit-confirm').on('click', function() {
|
||||
var form = $(this).closest('form');
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
function abortEditSampleTypeGroupAction() {
|
||||
$('.abort').off();
|
||||
$('.abort').on('click', function() {
|
||||
var li = $(this).closest('li');
|
||||
var href = $(this).attr('data-element');
|
||||
var id = $(li).attr('data-id');
|
||||
$(li).clearFormErrors();
|
||||
$.ajax({
|
||||
url: href,
|
||||
data: { id: id },
|
||||
success: function(data) {
|
||||
$(li).replaceWith($.parseHTML(data.html));
|
||||
editSampleTypeForm();
|
||||
destroySampleTypeGroup();
|
||||
initSampleColorPicker(li)
|
||||
appendCarretToColorPickerDropdown();
|
||||
editSampleGroupColor();
|
||||
editSampleGroupForm();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function destroySampleTypeGroup() {
|
||||
$('.delete').off();
|
||||
$('.delete').on('click', function() {
|
||||
var li = $(this).closest('li');
|
||||
var href = li.attr('data-delete');
|
||||
var id = $(li).attr('data-id');
|
||||
|
||||
$.ajax({
|
||||
url: href,
|
||||
data: { id: id },
|
||||
success: function(data) {
|
||||
$('body').append($.parseHTML(data.html));
|
||||
$('#modal-delete').modal('show',{
|
||||
backdrop: true,
|
||||
keyboard: false,
|
||||
});
|
||||
|
||||
clearModal('#modal-delete');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearModal(id) {
|
||||
$(id).on('hidden.bs.modal', function() {
|
||||
$(id).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function bindNewSampleTypeAction() {
|
||||
$('#new_sample_type').off();
|
||||
$('#new_sample_type').bind('ajax:success', function(ev, data) {
|
||||
var li = $.parseHTML(data.html);
|
||||
$('#name-input').val('');
|
||||
$('.new-resource-form').slideUp();
|
||||
$(li).insertAfter('.new-resource-form');
|
||||
editSampleTypeForm();
|
||||
destroySampleTypeGroup();
|
||||
$('#new_sample_type').clearFormErrors();
|
||||
}).bind('ajax:error', function(ev, error) {
|
||||
$(this).clearFormErrors();
|
||||
var msg = $.parseJSON(error.responseText);
|
||||
renderFormError(ev,
|
||||
$(this).find('#name-input'),
|
||||
Object.keys(msg)[0] + ' '+ msg.name.toString());
|
||||
});
|
||||
}
|
||||
|
||||
function appendCarretToColorPickerDropdown() {
|
||||
$(document).ready(function() {
|
||||
_.each($('.btn-colorselector'), function(el){
|
||||
if(!$(el).next().is('span.caret')) {
|
||||
$(el).after($.parseHTML('<span class="caret"></span>'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editSampleGroupColor() {
|
||||
$(document).ready(function() {
|
||||
$('.edit_sample_group a.color-btn').off();
|
||||
$('.edit_sample_group a.color-btn').on('click', function() {
|
||||
var color = $(this).attr('data-value');
|
||||
var form = $(this).closest('form');
|
||||
$('select[name="sample_group[color]"]')
|
||||
.val(color);
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindNewSampleGroupAction() {
|
||||
$('#new_sample_group').off();
|
||||
$('#new_sample_group').bind('ajax:success', function(ev, data) {
|
||||
var li = $.parseHTML(data.html);
|
||||
$('#name-input').val('');
|
||||
$('.new-resource-form').slideUp();
|
||||
$(li).insertAfter('.new-resource-form');
|
||||
initSampleColorPicker(li);
|
||||
appendCarretToColorPickerDropdown();
|
||||
editSampleGroupColor();
|
||||
editSampleGroupForm();
|
||||
destroySampleTypeGroup();
|
||||
$('#new_sample_group').clearFormErrors();
|
||||
}).bind('ajax:error', function(ev, error) {
|
||||
$(this).clearFormErrors();
|
||||
var msg = $.parseJSON(error.responseText);
|
||||
renderFormError(ev,
|
||||
$(this).find('#name-input'),
|
||||
Object.keys(msg)[0] + ' '+ msg.name.toString());
|
||||
});
|
||||
}
|
||||
|
||||
function editSampleTypeForm() {
|
||||
$('.edit-sample-type').off();
|
||||
$('.edit-sample-type').on('click', function() {
|
||||
var li = $(this).closest('li');
|
||||
$.ajax({
|
||||
url: li.attr('data-edit'),
|
||||
success: function(data) {
|
||||
$(li).replaceWith($.parseHTML(data.html));
|
||||
|
||||
submitEditSampleTypeGroupForm();
|
||||
abortEditSampleTypeGroupAction();
|
||||
destroySampleTypeGroup();
|
||||
$('#edit_sample_type_' + data.id)
|
||||
.find('[name="sample_type[name]"]')
|
||||
.focus();
|
||||
|
||||
|
||||
$('#edit_sample_type_' + data.id).off();
|
||||
$('#edit_sample_type_' + data.id)
|
||||
.bind('ajax:success', function(ev, data) {
|
||||
$(this).closest('li').replaceWith($.parseHTML(data.html));
|
||||
editSampleTypeForm();
|
||||
destroySampleTypeGroup();
|
||||
}).bind('ajax:error', function(ev, error){
|
||||
$(this).clearFormErrors();
|
||||
var msg = $.parseJSON(error.responseText);
|
||||
renderFormError(ev,
|
||||
$(this).find('#sample_type_name'),
|
||||
Object.keys(msg)[0] + ' '+ msg.name.toString());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editSampleGroupForm() {
|
||||
$('.edit-sample-group').off();
|
||||
$('.edit-sample-group').on('click', function() {
|
||||
var li = $(this).closest('li');
|
||||
$.ajax({
|
||||
url: li.attr('data-edit'),
|
||||
success: function(data) {
|
||||
$(li).replaceWith($.parseHTML(data.html));
|
||||
|
||||
submitEditSampleTypeGroupForm();
|
||||
abortEditSampleTypeGroupAction();
|
||||
destroySampleTypeGroup();
|
||||
initSampleColorPicker(li);
|
||||
appendCarretToColorPickerDropdown();
|
||||
editSampleGroupColor();
|
||||
|
||||
$('#edit_sample_group_' + data.id)
|
||||
.find('[name="sample_group[name]"]')
|
||||
.focus();
|
||||
|
||||
$('#edit_sample_group_' + data.id).off();
|
||||
$('#edit_sample_group_' + data.id)
|
||||
.bind('ajax:success', function(ev, data) {
|
||||
$(this).closest('li').replaceWith($.parseHTML(data.html));
|
||||
editSampleGroupForm();
|
||||
destroySampleTypeGroup();
|
||||
initSampleColorPicker($(this).closest('li'));
|
||||
appendCarretToColorPickerDropdown();
|
||||
editSampleGroupColor();
|
||||
}).bind('ajax:error', function(ev, error){
|
||||
$(this).clearFormErrors();
|
||||
var msg = $.parseJSON(error.responseText);
|
||||
renderFormError(ev,
|
||||
$(this).find('#sample_group_name'),
|
||||
Object.keys(msg)[0] + ' '+ msg.name.toString());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initSampleGroupColor() {
|
||||
var elements = $('.edit-sample-group-color');
|
||||
_.each(elements, function(el) {
|
||||
var color = $(el).closest('[data-color]')
|
||||
.attr('data-color');
|
||||
$(el).colorselector('setColor', color);
|
||||
});
|
||||
}
|
||||
|
||||
function initSampleColorPicker(el) {
|
||||
var element = $(el).find('.edit-sample-group-color');
|
||||
var color = $(element).closest('[data-color]').attr('data-color');
|
||||
$(element).colorselector('setColor', color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens adding mode when redirected from samples page, when clicking link for
|
||||
* adding sample type or group link
|
||||
*/
|
||||
function sampleTypeGroupEditMode() {
|
||||
if (getParam('add-mode')) {
|
||||
$('#create-resource').click();
|
||||
}
|
||||
}
|
||||
|
||||
function initSampleTypesGroups() {
|
||||
showNewSampleTypeGroupForm();
|
||||
newSampleTypeFormCancel();
|
||||
bindNewSampleTypeAction();
|
||||
editSampleTypeForm();
|
||||
destroySampleTypeGroup();
|
||||
editSampleGroupForm();
|
||||
editSampleGroupColor();
|
||||
initSampleGroupColor();
|
||||
bindNewSampleGroupAction();
|
||||
appendCarretToColorPickerDropdown();
|
||||
sampleTypeGroupEditMode();
|
||||
}
|
||||
|
||||
// initialize sample types/groups actions
|
||||
initSampleTypesGroups();
|
||||
})();
|
|
@ -1,79 +1,5 @@
|
|||
//= require datatables
|
||||
|
||||
// Create custom field ajax
|
||||
$("#modal-create-custom-field").on("show.bs.modal", function(event) {
|
||||
// Clear input when modal is opened
|
||||
input = $(this).find("input#name-input");
|
||||
input.val("");
|
||||
input.closest(".form-group").removeClass("has-error");
|
||||
input.closest(".form-group").find(".help-block").remove();
|
||||
});
|
||||
$("#modal-create-custom-field").on("shown.bs.modal", function(event) {
|
||||
$(this).find("input#name-input").focus();
|
||||
});
|
||||
|
||||
$("form#new_custom_field").on("ajax:success", function(ev, data, status) {
|
||||
$("#modal-create-custom-field").modal("hide");
|
||||
|
||||
// Reload page with URL parameter of newly created field
|
||||
window.location.href = addParam(window.location.href, "new_col");
|
||||
});
|
||||
|
||||
$("form#new_custom_field").on("ajax:error", function(e, data, status, xhr) {
|
||||
$('form').renderFormErrors('custom_field', data.responseJSON, true, e);
|
||||
});
|
||||
|
||||
// Create sample type ajax
|
||||
$("#modal-create-sample-type").on("show.bs.modal", function(event) {
|
||||
// Clear input when modal is opened
|
||||
input = $(this).find("input#name-input");
|
||||
input.val("");
|
||||
input.closest(".form-group").removeClass("has-error");
|
||||
input.closest(".form-group").find(".help-block").remove();
|
||||
});
|
||||
|
||||
$("#modal-create-sample-type").on("shown.bs.modal", function(event) {
|
||||
$(this).find("input#name-input").focus();
|
||||
});
|
||||
|
||||
$("form#new_sample_type").on("ajax:success", function(ev, data, status) {
|
||||
$("#modal-create-sample-type").modal("hide");
|
||||
updateSamplesTypesandGroups();
|
||||
sampleAlertMsg(data.flash, "success");
|
||||
currentMode = "viewMode";
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
$("form#new_sample_type").on("ajax:error", function(e, data, status, xhr) {
|
||||
$('form').renderFormErrors('sample_type', data.responseJSON, true, e);
|
||||
});
|
||||
|
||||
// Create sample group ajax
|
||||
$("#modal-create-sample-group").on("show.bs.modal", function(event) {
|
||||
// Clear input when modal is opened
|
||||
input = $(this).find("input#name-input");
|
||||
input.val("");
|
||||
input.closest(".form-group").removeClass("has-error");
|
||||
input.closest(".form-group").find(".help-block").remove();
|
||||
});
|
||||
|
||||
$("#modal-create-sample-group").on("shown.bs.modal", function(event) {
|
||||
$(this).find("input#name-input").focus();
|
||||
});
|
||||
|
||||
$("form#new_sample_group").on("ajax:success", function(ev, data, status) {
|
||||
$("#modal-create-sample-group").modal("hide");
|
||||
updateSamplesTypesandGroups();
|
||||
sampleAlertMsg(data.flash, "success");
|
||||
currentMode = "viewMode";
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
$("form#new_sample_group").on("ajax:error", function(e, data, status, xhr) {
|
||||
$('form').renderFormErrors('sample_group', data.responseJSON, true, e);
|
||||
});
|
||||
|
||||
|
||||
// Create import samples ajax
|
||||
$("#modal-import-samples").on("show.bs.modal", function(event) {
|
||||
formGroup = $(this).find(".form-group");
|
||||
|
@ -154,112 +80,24 @@ function sampleAlertMsgHide() {
|
|||
$('#content-wrapper').removeClass('alert-shown');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes tutorial
|
||||
*/
|
||||
function initTutorial() {
|
||||
var currentStep = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (currentStep == 8)
|
||||
currentStep++;
|
||||
if (showTutorial() && (currentStep > 12 && currentStep < 16)) {
|
||||
var samplesTutorial =$("#samples-toolbar").attr("data-samples-step-text");
|
||||
var breadcrumbsTutorial = $("#samples-toolbar").attr("data-breadcrumbs-step-text");
|
||||
|
||||
introJs()
|
||||
.setOptions({
|
||||
steps: [
|
||||
{
|
||||
element: document.getElementById("importSamplesButton"),
|
||||
intro: samplesTutorial
|
||||
},
|
||||
{
|
||||
element: document.getElementById("secondary-menu"),
|
||||
intro: breadcrumbsTutorial,
|
||||
tooltipClass: 'custom next-page-link',
|
||||
}
|
||||
],
|
||||
overlayOpacity: '0.1',
|
||||
nextLabel: 'Next',
|
||||
doneLabel: 'End tutorial',
|
||||
skipLabel: 'End tutorial',
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
disableInteraction: true,
|
||||
tooltipClass: "custom"
|
||||
})
|
||||
.onafterchange(function (tarEl) {
|
||||
Cookies.set('current_tutorial_step', this._currentStep + 14);
|
||||
|
||||
if (this._currentStep == 1) {
|
||||
setTimeout(function() {
|
||||
$('.next-page-link a.introjs-nextbutton')
|
||||
.removeClass('introjs-disabled')
|
||||
.attr('href', $("#reports-nav-tab a").attr('href'));
|
||||
$('.introjs-disableInteraction').remove();
|
||||
positionTutorialTooltip();
|
||||
}, 500);
|
||||
} else {
|
||||
positionTutorialTooltip();
|
||||
}
|
||||
})
|
||||
.goToStep(currentStep == 15 ? 2 : 1)
|
||||
.start();
|
||||
|
||||
// Destroy first-time tutorial cookies when skip tutorial
|
||||
// or end tutorial is clicked
|
||||
$(".introjs-skipbutton").each(function (){
|
||||
$(this).click(function (){
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
restore_after_tutorial();
|
||||
});
|
||||
});
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (stepNum >= 17 && stepNum <= 18) {
|
||||
var nextPage = $('#reports-nav-tab a').attr('href');
|
||||
var steps = [{
|
||||
element: $('#importSamplesButton')[0],
|
||||
intro: $('#samples-toolbar').attr('data-samples-step-text'),
|
||||
position: 'right'
|
||||
}, {
|
||||
element: $('#secondary-menu')[0],
|
||||
intro: $('#samples-toolbar').attr('data-breadcrumbs-step-text')
|
||||
}];
|
||||
initPageTutorialSteps(17, 18, nextPage,
|
||||
function() {}, function() {}, steps);
|
||||
}
|
||||
}
|
||||
|
||||
function positionTutorialTooltip() {
|
||||
if (Cookies.get('current_tutorial_step') == 13) {
|
||||
if ($("#reports-nav-tab").offset().left == 0) {
|
||||
$(".introjs-tooltip").css("left", (window.innerWidth / 2 - 50) + "px");
|
||||
$(".introjs-tooltip").addClass("repositioned");
|
||||
} else if ($(".introjs-tooltip").hasClass("repositioned")) {
|
||||
$(".introjs-tooltip").css("left", "");
|
||||
$(".introjs-tooltip").removeClass("repositioned");
|
||||
}
|
||||
} else {
|
||||
if ($(".introjs-tooltip").offset().left > window.innerWidth) {
|
||||
$(".introjs-tooltip").css("left", (window.innerWidth / 2 - 50) + "px");
|
||||
$(".introjs-tooltip").addClass("repositioned");
|
||||
} else if ($(".introjs-tooltip").hasClass("repositioned")) {
|
||||
$(".introjs-tooltip").css("left", "");
|
||||
$(".introjs-tooltip").removeClass("repositioned");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function showTutorial() {
|
||||
var tutorialData;
|
||||
if (Cookies.get('tutorial_data'))
|
||||
tutorialData = JSON.parse(Cookies.get('tutorial_data'));
|
||||
else
|
||||
return false;
|
||||
var tutorialModuleId = tutorialData[0].qpcr_module;
|
||||
var currentModuleId = $("#samples-toolbar").attr("data-module-id");
|
||||
return tutorialModuleId == currentModuleId;
|
||||
}
|
||||
|
||||
function samples_tutorial_helper(){
|
||||
if( $('div').hasClass('introjs-overlay') ){
|
||||
$.each( $('#secondary-menu').find('a'), function(){
|
||||
$(this).css({ 'pointer-events': 'none' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function restore_after_tutorial(){
|
||||
$.each( $('#secondary-menu').find('a'), function(){
|
||||
$(this).css({ 'pointer-events': 'auto' });
|
||||
});
|
||||
}
|
||||
// Initialize first-time tutorial
|
||||
initTutorial();
|
||||
samples_tutorial_helper();
|
||||
|
|
|
@ -38,4 +38,4 @@ $("form#form-import")
|
|||
// Populate the errors container
|
||||
$("#import-errors-container").html(data.responseJSON.html);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
var STORAGE_TREE_KEY = "scinote-sidebar-tree-collapsed-ids";
|
||||
var STORAGE_TOGGLE_KEY = "scinote-sidebar-toggled";
|
||||
var SCREEN_SIZE_LARGE = 928;
|
||||
|
||||
/**
|
||||
* Get all collapsed sidebar elements.
|
||||
|
@ -251,7 +250,8 @@ function initializeSidebarToggle() {
|
|||
var wrapper = $("#wrapper");
|
||||
var toggled = sessionIsSidebarToggled();
|
||||
|
||||
if (toggled || toggled === null && $(window).width() < SCREEN_SIZE_LARGE) {
|
||||
if (toggled || toggled === null && $(window).width() <
|
||||
<%= Constants::SCREEN_WIDTH_LARGE %>) {
|
||||
wrapper.addClass("no-animation");
|
||||
wrapper.addClass("toggled");
|
||||
// Cause reflow of the wrapper element
|
||||
|
@ -283,7 +283,7 @@ function resizeSidebarContents() {
|
|||
}
|
||||
// Automatic toggling of sidebar for smaller devices
|
||||
if (toggled === null) {
|
||||
if ($(window).width() < SCREEN_SIZE_LARGE) {
|
||||
if ($(window).width() < <%= Constants::SCREEN_WIDTH_LARGE %>) {
|
||||
wrapper.addClass("toggled");
|
||||
navbar.addClass("navbar-without-sidebar");
|
||||
} else {
|
|
@ -18,35 +18,30 @@ $.fn.onSubmitValidator = function(validatorCb) {
|
|||
}
|
||||
};
|
||||
|
||||
var TextLimitEnum = Object.freeze({
|
||||
OPTIONAL: 0,
|
||||
REQUIRED: 1,
|
||||
NAME_MIN_LENGTH: '<%= NAME_MIN_LENGTH %>',
|
||||
NAME_MAX_LENGTH: '<%= NAME_MAX_LENGTH %>',
|
||||
TEXT_MAX_LENGTH: '<%= TEXT_MAX_LENGTH %>'
|
||||
});
|
||||
|
||||
/*
|
||||
* @param {boolean} clearErr Set clearErr to true if this is the only
|
||||
* error that can happen/show.
|
||||
*/
|
||||
function textValidator(ev, textInput, textLimitEnumMin, textLimitEnumMax, clearErr) {
|
||||
function textValidator(ev, textInput, textLimitMin, textLimitMax, clearErr) {
|
||||
clearErr = _.isUndefined(clearErr) ? false : clearErr;
|
||||
|
||||
var text = $(textInput).val().trim();
|
||||
$(textInput).val(text);
|
||||
var nameTooShort = text.length < textLimitEnumMin;
|
||||
var nameTooLong = text.length > textLimitEnumMax;
|
||||
var text_from_html = $("<div/>").html(text).text();
|
||||
if (text_from_html.length < text.length) text = text_from_html;
|
||||
|
||||
var nameTooShort = text.length < textLimitMin;
|
||||
var nameTooLong = text.length > textLimitMax;
|
||||
|
||||
var errMsg;
|
||||
if (nameTooShort) {
|
||||
if (textLimitEnumMin === TextLimitEnum.REQUIRED) {
|
||||
if (textLimitMin === 1) {
|
||||
errMsg = I18n.t("general.text.not_blank");
|
||||
} else {
|
||||
errMsg = I18n.t("general.text.length_too_short", { min_length: textLimitEnumMin });
|
||||
errMsg = I18n.t("general.text.length_too_short", { min_length: textLimitMin });
|
||||
}
|
||||
} else if (nameTooLong) {
|
||||
errMsg = I18n.t("general.text.length_too_long", { max_length: textLimitEnumMax });
|
||||
errMsg = I18n.t("general.text.length_too_long", { max_length: textLimitMax });
|
||||
}
|
||||
|
||||
var noErrors = _.isUndefined(errMsg);
|
||||
|
@ -71,7 +66,8 @@ function checklistsValidator(ev, checklists, editMode) {
|
|||
if ($item.css('display') != 'none') {
|
||||
|
||||
if ($itemInput.val()) {
|
||||
var itemNameValid = textValidator(ev, $itemInput, TextLimitEnum.REQUIRED, TextLimitEnum.TEXT_MAX_LENGTH);
|
||||
var itemNameValid = textValidator(ev, $itemInput, 1,
|
||||
<%= Constants::TEXT_MAX_LENGTH %>);
|
||||
if (!itemNameValid) {
|
||||
noErrors = false;
|
||||
}
|
||||
|
@ -86,9 +82,9 @@ function checklistsValidator(ev, checklists, editMode) {
|
|||
var $checklistInput = $checklist.find(".checklist_name");
|
||||
// In edit mode, checklist's name can't be blank if any items present
|
||||
var allowBlankChklstName = !(anyChecklistItemFilled || editMode);
|
||||
var textLimitEnumMin = allowBlankChklstName ? TextLimitEnum.OPTIONAL : TextLimitEnum.REQUIRED;
|
||||
var textLimitMin = allowBlankChklstName ? 0 : 1;
|
||||
var checklistNameValid = textValidator(ev, $checklistInput,
|
||||
textLimitEnumMin, TextLimitEnum.TEXT_MAX_LENGTH);
|
||||
textLimitMin, <%= Constants::TEXT_MAX_LENGTH %>);
|
||||
if (!checklistNameValid) {
|
||||
noErrors = false;
|
||||
} else if (allowBlankChklstName) {
|
||||
|
@ -101,9 +97,9 @@ function checklistsValidator(ev, checklists, editMode) {
|
|||
return noErrors;
|
||||
}
|
||||
|
||||
var FileTypeSizeEnum = Object.freeze({
|
||||
FILE: '<%= FILE_MAX_SIZE.megabytes %>',
|
||||
AVATAR: '<%= AVATAR_MAX_SIZE.megabytes %>'
|
||||
var FileTypeEnum = Object.freeze({
|
||||
FILE: <%= Constants::FILE_MAX_SIZE_MB.megabytes %>,
|
||||
AVATAR: <%= Constants::AVATAR_MAX_SIZE_MB.megabytes %>
|
||||
});
|
||||
|
||||
function filesValidator(ev, fileInputs, fileTypeEnum, canBeEmpty) {
|
||||
|
@ -112,6 +108,7 @@ function filesValidator(ev, fileInputs, fileTypeEnum, canBeEmpty) {
|
|||
if (fileInputs.length) {
|
||||
var filesPresentValid = canBeEmpty || filesPresentValidator(ev, fileInputs);
|
||||
var filesSizeValid = filesSizeValidator(ev, fileInputs, fileTypeEnum);
|
||||
|
||||
// File spoof check is done on server-side only
|
||||
filesValid = filesPresentValid && filesSizeValid;
|
||||
}
|
||||
|
@ -138,19 +135,40 @@ function filesSizeValidator(ev, fileInputs, fileTypeEnum) {
|
|||
}
|
||||
if (file.size > fileTypeEnum) {
|
||||
switch (fileTypeEnum) {
|
||||
case FileTypeSizeEnum.FILE:
|
||||
return "<%= I18n.t 'general.file.size_exceeded', file_size: FILE_MAX_SIZE %>".strToErrorFormat();
|
||||
case FileTypeSizeEnum.AVATAR:
|
||||
return "<%= I18n.t 'general.file.size_exceeded', file_size: AVATAR_MAX_SIZE %>".strToErrorFormat();
|
||||
case FileTypeEnum.FILE:
|
||||
return "<%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %>".strToErrorFormat();
|
||||
case FileTypeEnum.AVATAR:
|
||||
return "<%= I18n.t 'general.file.size_exceeded', file_size: Constants::AVATAR_MAX_SIZE_MB %>".strToErrorFormat();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function checkFilesTotalSize(fileInputs) {
|
||||
if (!fileInputs || fileInputs < 2) {
|
||||
return ;
|
||||
}
|
||||
|
||||
var size = 0;
|
||||
_.each(fileInputs, function(fileInput) {
|
||||
var file = fileInput.files[0]
|
||||
size += file.size;
|
||||
})
|
||||
|
||||
if (size > fileTypeEnum) {
|
||||
return "<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>".strToErrorFormat();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any file exceeds allowed size limit
|
||||
var filesSizeValid = true;
|
||||
|
||||
// Check total size of uploaded files
|
||||
var totalSizeOK = checkFilesTotalSize(fileInputs);
|
||||
|
||||
_.each(fileInputs, function(fileInput) {
|
||||
var file = fileInput.files[0];
|
||||
var assetError = getFileTooBigError(file);
|
||||
var assetError = totalSizeOK;
|
||||
if (assetError) {
|
||||
renderFormError(ev, fileInput, assetError, false, "data-error='file-size'");
|
||||
filesSizeValid = false;
|
||||
|
|
190
app/assets/javascripts/sitewide/invite_users_modal.js.erb
Normal file
190
app/assets/javascripts/sitewide/invite_users_modal.js.erb
Normal file
|
@ -0,0 +1,190 @@
|
|||
(function() {
|
||||
'use strict';
|
||||
|
||||
function initializeModal(modal) {
|
||||
var modalDialog = modal.find('.modal-dialog');
|
||||
var type = modal.attr('data-type');
|
||||
var stepForm = modal.find('[data-role=step-form]');
|
||||
var stepResults = modal.find('[data-role=step-results]');
|
||||
var stepResultsDiv = modal.find('[data-role=step-results][data-clear]');
|
||||
var inviteBtn = modal.find('[data-role=invite-btn]');
|
||||
var inviteWithRoleDiv =
|
||||
modal.find('[data-role=invite-with-role-div]');
|
||||
var inviteWithRoleBtn =
|
||||
modal.find('[data-role=invite-with-role-btn]');
|
||||
var orgSelectorCheckbox =
|
||||
modal.find('[data-role=org-selector-checkbox]');
|
||||
var orgSelectorDropdown =
|
||||
modal.find('[data-role=org-selector-dropdown]');
|
||||
var orgSelectorDropdown2 = $();
|
||||
var tagsInput = modal.find('[data-role=tags-input]');
|
||||
|
||||
// Set max tags
|
||||
tagsInput.tagsinput({
|
||||
maxTags: <%= Constants::INVITE_USERS_LIMIT %>
|
||||
});
|
||||
|
||||
modal
|
||||
.on('show.bs.modal', function() {
|
||||
// This cannot be scoped outside this function
|
||||
// because it is generated via JS
|
||||
orgSelectorDropdown2 =
|
||||
orgSelectorDropdown
|
||||
.next('.btn-group.bootstrap-select.form-control')
|
||||
.find('button.dropdown-toggle, li');
|
||||
|
||||
// Show/hide correct step
|
||||
stepForm.show();
|
||||
stepResults.hide();
|
||||
|
||||
// Show/hide buttons & other elements
|
||||
switch (type) {
|
||||
case 'invite_to_org_with_role':
|
||||
case 'invite':
|
||||
case 'invite_with_org_selector':
|
||||
case 'invite_with_org_selector_and_role':
|
||||
inviteBtn.show();
|
||||
inviteWithRoleDiv.hide();
|
||||
break;
|
||||
case 'invite_to_org':
|
||||
inviteBtn.hide();
|
||||
inviteWithRoleDiv.show();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Checkbox toggle event
|
||||
if (
|
||||
type === 'invite_with_org_selector' ||
|
||||
type === 'invite_with_org_selector_and_role'
|
||||
) {
|
||||
orgSelectorCheckbox.on('change', function() {
|
||||
if ($(this).is(':checked')) {
|
||||
orgSelectorDropdown.removeAttr('disabled');
|
||||
orgSelectorDropdown2.removeClass('disabled');
|
||||
if (type === 'invite_with_org_selector') {
|
||||
inviteBtn.hide();
|
||||
inviteWithRoleDiv.show();
|
||||
}
|
||||
} else {
|
||||
orgSelectorDropdown.attr('disabled', 'disabled');
|
||||
orgSelectorDropdown2.addClass('disabled');
|
||||
if (type === 'invite_with_org_selector') {
|
||||
inviteBtn.show();
|
||||
inviteWithRoleDiv.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle depending on input tags
|
||||
tagsInput
|
||||
.on('itemAdded', function(event) {
|
||||
inviteBtn.removeAttr('disabled');
|
||||
inviteWithRoleBtn.removeAttr('disabled');
|
||||
})
|
||||
.on('itemRemoved', function(event) {
|
||||
if ($(this).val() === null) {
|
||||
inviteBtn.attr('disabled', 'disabled');
|
||||
inviteWithRoleBtn.attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// Click action
|
||||
modal.find('[data-action=invite]').on('click', function() {
|
||||
animateSpinner(modalDialog);
|
||||
|
||||
var data = {
|
||||
emails: tagsInput.val()
|
||||
};
|
||||
switch (type) {
|
||||
case 'invite_to_org':
|
||||
data.organizationId = modal.attr('data-organization-id');
|
||||
data.role = $(this).attr('data-organization-role');
|
||||
break;
|
||||
case 'invite_to_org_with_role':
|
||||
data.organizationId = modal.attr('data-organization-id');
|
||||
data.role = modal.attr('data-organization-role');
|
||||
break;
|
||||
case 'invite':
|
||||
break;
|
||||
case 'invite_with_org_selector':
|
||||
if (orgSelectorCheckbox.is(':checked')) {
|
||||
data.organizationId = orgSelectorDropdown.val();
|
||||
data.role = $(this).attr('data-organization-role');
|
||||
}
|
||||
break;
|
||||
case 'invite_with_org_selector_and_role':
|
||||
if (orgSelectorCheckbox.is(':checked')) {
|
||||
data.organizationId = orgSelectorDropdown.val();
|
||||
data.role = modal.attr('data-organization-role');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: modal.attr('data-url'),
|
||||
dataType: 'json',
|
||||
data: data,
|
||||
success: function(data) {
|
||||
animateSpinner(modalDialog, false);
|
||||
stepForm.hide();
|
||||
stepResultsDiv.html(data.html);
|
||||
stepResults.show();
|
||||
// Add 'data-invited="true"' status to modal element
|
||||
modal.attr('data-invited', 'true');
|
||||
},
|
||||
error: function() {
|
||||
animateSpinner(modalDialog, false);
|
||||
modal.modal('hide');
|
||||
alert('Error inviting users.');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.on('shown.bs.modal', function() {
|
||||
tagsInput.tagsinput('focus');
|
||||
|
||||
// Remove 'data-invited="true"' status
|
||||
modal.removeAttr('data-invited');
|
||||
})
|
||||
.on('hide.bs.modal', function() {
|
||||
// 'Reset' modal state
|
||||
tagsInput.tagsinput('removeAll');
|
||||
orgSelectorCheckbox.prop('checked', false);
|
||||
inviteBtn.attr('disabled', 'disabled');
|
||||
inviteWithRoleBtn.attr('disabled', 'disabled');
|
||||
orgSelectorDropdown2.addClass('disabled');
|
||||
animateSpinner(modalDialog, false);
|
||||
|
||||
// Unbind event listeners
|
||||
orgSelectorCheckbox.off('change');
|
||||
tagsInput.off('itemAdded itemRemoved');
|
||||
modal.find('[data-action=invite]').off('click');
|
||||
|
||||
// Hide contents of the results <div>
|
||||
stepResultsDiv.html('');
|
||||
stepResults.hide();
|
||||
stepForm.show();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeModalsToggle() {
|
||||
$("[data-trigger='invite-users']").on('click', function() {
|
||||
var id = $(this).attr('data-modal-id');
|
||||
$('[data-role=invite-users-modal][data-id=' + id + ']')
|
||||
.modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-role=invite-users-modal]').each(function() {
|
||||
initializeModal($(this));
|
||||
});
|
||||
initializeModalsToggle();
|
||||
});
|
||||
})();
|
23
app/assets/javascripts/sitewide/quill_links.js
Normal file
23
app/assets/javascripts/sitewide/quill_links.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
//= require quill
|
||||
|
||||
|
||||
// Globally overwrite links handling in Quill rich text editor
|
||||
var Link = Quill.import('formats/link');
|
||||
Link.sanitize = function(url) {
|
||||
if (url.includes('http:') || url.includes('https:')) {
|
||||
return url;
|
||||
}
|
||||
return 'http://' + url;
|
||||
};
|
||||
|
||||
function openLinksInNewTab() {
|
||||
_.each($('.ql-editor a'), function(el) {
|
||||
if ($(el).attr('target') !== '_blank') {
|
||||
$(el).attr('target', '_blank');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
openLinksInNewTab();
|
||||
});
|
|
@ -2,15 +2,24 @@
|
|||
* Truncate long strings where is necessary.
|
||||
*/
|
||||
function truncateLongString( el, chars ) {
|
||||
var input = $.trim(el.text());
|
||||
if($.type(el) !== 'string'){
|
||||
var input = $.trim(el.text());
|
||||
} else {
|
||||
var input = $.trim(el);
|
||||
}
|
||||
|
||||
var html = "";
|
||||
if( el.children().hasClass("glyphicon") ){
|
||||
if( $.type(el) !== 'string' &&
|
||||
el.children().hasClass("glyphicon")) {
|
||||
html = el.children()[0];
|
||||
}
|
||||
|
||||
if( input.length >= chars){
|
||||
var newText = el.text().slice(0, chars);
|
||||
if( input.length >= chars ){
|
||||
if($.type(el) != 'string') {
|
||||
var newText = el.text().slice(0, chars);
|
||||
}else {
|
||||
var newText = el.slice(0, chars);
|
||||
}
|
||||
for( var i = newText.length; i > 0; i--){
|
||||
if(newText[i] === ' ' && i > 10){
|
||||
newText = newText.slice(0, i);
|
||||
|
@ -21,19 +30,25 @@ function truncateLongString( el, chars ) {
|
|||
if ( html ) {
|
||||
el.html(html.outerHTML + newText + '...' );
|
||||
} else {
|
||||
if($.type(el) === 'string'){
|
||||
return newText + '...';
|
||||
} else {
|
||||
el.html(newText + '...' );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Usefull for converting locals messages to error format
|
||||
* (i.e. lower cased capital and no dot at the end).
|
||||
* (i.e. no dot at the end).
|
||||
*/
|
||||
String.prototype.strToErrorFormat = function() {
|
||||
var length = this.length;
|
||||
if (this[length - 1] === ".") {
|
||||
length -= 1;
|
||||
}
|
||||
return this.charAt(0).toLowerCase() + this.slice(1, length);
|
||||
return this.slice(0, length);
|
||||
}
|
||||
|
|
30
app/assets/javascripts/sitewide/textarea_autosize.js
Normal file
30
app/assets/javascripts/sitewide/textarea_autosize.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
$(document).on(
|
||||
'focus',
|
||||
'textarea.smart-text-area:not([readonly]):not([disabled])',
|
||||
function() {
|
||||
var $this = $(this);
|
||||
var height = $this.css('height');
|
||||
|
||||
if ($this.hasClass('textarea-sm-present')) {
|
||||
$this
|
||||
.removeClass('textarea-sm-present')
|
||||
.addClass('textarea-sm');
|
||||
$this.attr('rows', '1');
|
||||
} else if ($this.hasClass('textarea-sm')) {
|
||||
// Set the nr. of rows to 1 if small textarea
|
||||
$this.attr('rows', '1');
|
||||
} else {
|
||||
$this.removeAttr('rows');
|
||||
}
|
||||
|
||||
// Initialize autosize plugin if it's not initialized yet
|
||||
if (_.isUndefined($this.data('autosize'))) {
|
||||
$this.autosize({append: ''});
|
||||
|
||||
// Restore previous height!
|
||||
$this.css('height', height);
|
||||
}
|
||||
|
||||
$this.trigger('autosize.resize');
|
||||
}
|
||||
);
|
|
@ -25,3 +25,193 @@ $.fn.onAjaxComplete = function (cb) {
|
|||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
// Number of all tutorial steps
|
||||
var TUTORIAL_STEPS_CNT = 26;
|
||||
|
||||
/**
|
||||
* Initializes tutorial steps for the current page.
|
||||
* NOTE: You can specify steps manually in JS with steps parameter (preferred
|
||||
* way), or hardcode them in HTML
|
||||
* NOTE: If some steps edit page, then this function needs to be called several
|
||||
* times for the same page, but for different steps. The same goes if the page
|
||||
* has discontinuous tutorial steps. In such cases, use steps branching, e.g.:
|
||||
* @example
|
||||
* var tutorialData = Cookies.get('tutorial_data');
|
||||
* if (tutorialData) {
|
||||
* tutorialData = JSON.parse(tutorialData);
|
||||
* var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
*
|
||||
* if (stepNum >= 6 && stepNum <= 7) {
|
||||
* ...
|
||||
* } else if ...
|
||||
* NOTE: If an element the popup is pointing at is of lesser horizontal length
|
||||
* than the popup itself, then it will not be positioned correctly if it's
|
||||
* position is top or bottom, so set/change the step's position to either left
|
||||
* or right (and don't use any custom styling!), e.g.:
|
||||
* @example
|
||||
* var steps = [
|
||||
* {
|
||||
* ...
|
||||
* position: 'right'
|
||||
* },
|
||||
* {
|
||||
* ...
|
||||
* ];
|
||||
* NOTE: If only one page step is needed, then make pageFirstStepN ==
|
||||
* pageLastStepN (both represent the one and only step number)
|
||||
*
|
||||
* @param {number} pageFirstStepN Page's first step number
|
||||
* @param {number} pageLastStepN Page's last step number
|
||||
* @param {string} nextPagePath Next page absolute path
|
||||
* @param {function} beforeCb Callback called before the tutorial starts. Mainly
|
||||
* used for setting 'pointer-events: none' on the elements the page's steps
|
||||
* highlight.
|
||||
* @param {function} endCb Callback called after the tutorial ends. Mainly used
|
||||
* for setting 'pointer-events: auto' on the elements the page's steps
|
||||
* highlight.
|
||||
* @param {object} steps Optional JSON containing introJs steps. They can be
|
||||
* specified here, or hardcoded in HTML.
|
||||
*/
|
||||
function initPageTutorialSteps(pageFirstStepN, pageLastStepN, nextPagePath,
|
||||
beforeCb, endCb, steps) {
|
||||
var tutorialData = Cookies.get('tutorial_data');
|
||||
if (tutorialData) {
|
||||
tutorialData = JSON.parse(tutorialData);
|
||||
var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
|
||||
if (isNaN(stepNum)) {
|
||||
// Cookies data initialization
|
||||
stepNum = 1;
|
||||
Cookies.set('current_tutorial_step', stepNum);
|
||||
tutorialData[0].backPagesPaths = [];
|
||||
Cookies.set('tutorial_data', tutorialData);
|
||||
}
|
||||
var thisPagePath = window.location.pathname;
|
||||
beforeCb();
|
||||
|
||||
// Initialize tutorial for the current page's steps
|
||||
var doneLabel = (pageLastStepN === TUTORIAL_STEPS_CNT) ?
|
||||
'Start using sciNote' : 'End tutorial';
|
||||
if (_.isUndefined(steps)) {
|
||||
introJs()
|
||||
.setOptions({
|
||||
overlayOpacity: '0.2',
|
||||
prevLabel: 'Back',
|
||||
nextLabel: 'Next',
|
||||
skipLabel: 'End tutorial',
|
||||
doneLabel: doneLabel,
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
disableInteraction: true,
|
||||
keyboardNavigation: false,
|
||||
tooltipClass: 'custom next-page-link'
|
||||
})
|
||||
.goToStep(stepNum - (pageFirstStepN - 1))
|
||||
.onexit(function() {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
location.reload();
|
||||
})
|
||||
.oncomplete(function() {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
location.reload();
|
||||
})
|
||||
.start();
|
||||
} else {
|
||||
if (pageFirstStepN === pageLastStepN) {
|
||||
// Only one page step, so add another fake one, so the back and next
|
||||
// buttons are added to the popup
|
||||
steps.push({});
|
||||
}
|
||||
introJs()
|
||||
.setOptions({
|
||||
overlayOpacity: '0.2',
|
||||
prevLabel: 'Back',
|
||||
nextLabel: 'Next',
|
||||
skipLabel: 'End tutorial',
|
||||
doneLabel: doneLabel,
|
||||
showBullets: false,
|
||||
showStepNumbers: false,
|
||||
exitOnOverlayClick: false,
|
||||
exitOnEsc: false,
|
||||
disableInteraction: true,
|
||||
keyboardNavigation: false,
|
||||
tooltipClass: 'custom next-page-link',
|
||||
steps: steps
|
||||
})
|
||||
.onexit(function() {
|
||||
location.reload();
|
||||
})
|
||||
.oncomplete(function() {
|
||||
location.reload();
|
||||
})
|
||||
.goToStep(stepNum - (pageFirstStepN - 1))
|
||||
.onexit(function() {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
})
|
||||
.oncomplete(function() {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
// Page navigation when coming to this page from previous/next page
|
||||
$(function() {
|
||||
if (stepNum === pageFirstStepN && stepNum > 1) {
|
||||
$('.introjs-prevbutton').removeClass('introjs-disabled');
|
||||
} else if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
|
||||
$('.introjs-nextbutton').removeClass('introjs-disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// Page navigation when already on this page
|
||||
|
||||
$('.introjs-skipbutton').click(function() {
|
||||
Cookies.remove('tutorial_data');
|
||||
Cookies.remove('current_tutorial_step');
|
||||
|
||||
endCb();
|
||||
});
|
||||
|
||||
$('.introjs-prevbutton').click(function() {
|
||||
if (stepNum > 1) {
|
||||
Cookies.set('current_tutorial_step', --stepNum);
|
||||
|
||||
if (stepNum === pageFirstStepN && stepNum > 1) {
|
||||
$('.introjs-prevbutton').removeClass('introjs-disabled');
|
||||
} else if (stepNum < pageFirstStepN) {
|
||||
// Go to previous page;
|
||||
|
||||
var prevPagePath = tutorialData[0].backPagesPaths.pop();
|
||||
Cookies.set('tutorial_data', tutorialData);
|
||||
$('.introjs-prevbutton').attr('href', prevPagePath);
|
||||
introJs().exit();
|
||||
endCb();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.introjs-nextbutton').click(function() {
|
||||
if (stepNum < TUTORIAL_STEPS_CNT) {
|
||||
Cookies.set('current_tutorial_step', ++stepNum);
|
||||
|
||||
if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
|
||||
$('.introjs-nextbutton').removeClass('introjs-disabled');
|
||||
} else if (stepNum > pageLastStepN) {
|
||||
// Go to next page
|
||||
|
||||
tutorialData[0].backPagesPaths.push(thisPagePath);
|
||||
Cookies.set('tutorial_data', tutorialData);
|
||||
$('.introjs-nextbutton').attr('href', nextPagePath);
|
||||
introJs().exit();
|
||||
endCb();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
(function(){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Initializes the comments
|
||||
*
|
||||
*/
|
||||
function initializeComments(){
|
||||
var comments;
|
||||
if ( $(".step-comment") && $(".step-comment").length > 0 ) {
|
||||
comments = $(".step-comment");
|
||||
} else if ( $(".result-comment") && $(".result-comment").length > 0 ) {
|
||||
comments = $(".result-comment");
|
||||
}
|
||||
if(!_.isUndefined(comments)) {
|
||||
$.each(comments, function(){
|
||||
var that = $(this);
|
||||
var link = that.attr('data-href');
|
||||
$.ajax({ method: 'GET',
|
||||
url: link,
|
||||
beforeSend: animateSpinner(that, true) })
|
||||
.done(function(data) {
|
||||
that.html(data.html);
|
||||
initCommentForm(that);
|
||||
initCommentsLink(that);
|
||||
scrollBottom(that.find('.content-comments'));
|
||||
})
|
||||
.always(function() {
|
||||
animateSpinner(that, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// scroll to the botttom
|
||||
function scrollBottom(id) {
|
||||
var list;
|
||||
if ( id.hasClass("content-comments")) {
|
||||
list = id;
|
||||
} else {
|
||||
list = id.find(".content-comments");
|
||||
}
|
||||
if ( list && list.length > 0) {
|
||||
list.scrollTop($(list)[0].scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize show more comments link.
|
||||
function initCommentsLink($el) {
|
||||
|
||||
$el.find(".btn-more-comments")
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $(this).parents("ul");
|
||||
var moreBtn = list.find(".btn-more-comments");
|
||||
var listItem = moreBtn.parents("li");
|
||||
$(data.html).insertAfter(listItem);
|
||||
if (data.results_number < data.per_page) {
|
||||
moreBtn.remove();
|
||||
} else {
|
||||
moreBtn.attr("href", data.more_url);
|
||||
moreBtn.trigger("blur");
|
||||
}
|
||||
|
||||
// Reposition dropdown comment options
|
||||
scrollCommentOptions(listItem
|
||||
.closest(".content-comments")
|
||||
.find(".dropdown-comment"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize comment form.
|
||||
function initCommentForm($el) {
|
||||
|
||||
var $form = $el.find("ul form");
|
||||
|
||||
$(".help-block", $form).addClass("hide");
|
||||
|
||||
$form.on("ajax:send", function () {
|
||||
$("#comment_message", $form).attr("readonly", true);
|
||||
})
|
||||
.on("ajax:success", function (e, data) {
|
||||
if (data.html) {
|
||||
var list = $form.parents("ul");
|
||||
|
||||
// Remove potential "no comments" element
|
||||
list.parent().find(".content-comments")
|
||||
.find("li.no-comments").remove();
|
||||
|
||||
list.parent().find(".content-comments")
|
||||
.append("<li class='comment'>" + data.html + "</li>")
|
||||
.scrollTop(0);
|
||||
list.parents("ul").find("> li.comment:gt(8)").remove();
|
||||
$("#comment_message", $form).val("");
|
||||
$(".form-group", $form)
|
||||
.removeClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html("")
|
||||
.addClass("hide");
|
||||
scrollBottom($el);
|
||||
}
|
||||
})
|
||||
.on("ajax:error", function (ev, xhr) {
|
||||
if (xhr.status === 400) {
|
||||
var messageError = xhr.responseJSON.errors.message;
|
||||
|
||||
if (messageError) {
|
||||
$(".form-group", $form)
|
||||
.addClass("has-error");
|
||||
$(".help-block", $form)
|
||||
.html(messageError[0])
|
||||
.removeClass("hide");
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("ajax:complete", function () {
|
||||
scrollBottom($("#comment_message", $form));
|
||||
$("#comment_message", $form)
|
||||
.attr("readonly", false)
|
||||
.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// restore comments after update or when new element is created
|
||||
function bindCommentInitializerToNewElement() {
|
||||
$(document)
|
||||
.ready(function() {
|
||||
if( document.getElementById("steps") !== null ) {
|
||||
$("#steps")
|
||||
.change(function() {
|
||||
$(".step-save")
|
||||
.on("click", function() {
|
||||
$(document)
|
||||
.on('ajax:success', function(){
|
||||
initializeComments();
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if ( document.getElementById("results") !== null ) {
|
||||
$("#results")
|
||||
.change(function() {
|
||||
$(".save-result")
|
||||
.on("click", function() {
|
||||
$(document)
|
||||
.on('ajax:success', function(){
|
||||
initializeComments();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindCommentInitializerToNewElement();
|
||||
initializeComments();
|
||||
|
||||
})();
|
|
@ -1,2 +0,0 @@
|
|||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
|
@ -68,7 +68,7 @@ function processFile(ev, forS3) {
|
|||
$form.clearFormErrors();
|
||||
|
||||
var $fileInput = $form.find("input[type=file]");
|
||||
if(filesValidator(ev, $fileInput, FileTypeSizeEnum.AVATAR)) {
|
||||
if(filesValidator(ev, $fileInput, FileTypeEnum.AVATAR)) {
|
||||
if(forS3) {
|
||||
// Redirects file uploading to S3
|
||||
var url = "/avatar_signature.json";
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//= require datatables
|
||||
//= require users/settings/organizations/add_user_modal
|
||||
|
||||
var usersDatatable = null;
|
||||
|
||||
|
@ -236,8 +235,19 @@ function initRemoveUsers() {
|
|||
);
|
||||
}
|
||||
|
||||
function initReloadPageAfterInviteUsers() {
|
||||
$('[data-id=org-invite-users-modal]')
|
||||
.on('hidden.bs.modal', function() {
|
||||
if (!_.isUndefined($(this).attr('data-invited'))) {
|
||||
// Reload page
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initEditName();
|
||||
initEditDescription();
|
||||
initUsersTable();
|
||||
initUpdateRoles();
|
||||
initRemoveUsers();
|
||||
initRemoveUsers();
|
||||
initReloadPageAfterInviteUsers();
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/* Global selectors */
|
||||
var modal = $("#add-user-modal");
|
||||
var modalContent = modal.find(".modal-content");
|
||||
var invitingExisting = true;
|
||||
var inviteButton = $("[data-id='invite-btn']");
|
||||
var inviteLinks = $("[data-action='invite']");
|
||||
var inviteExistingCollapsible = $("#invite-existing");
|
||||
var inviteExistingForm = $("[data-id='invite-existing-form']");
|
||||
var inviteExistingQuery = $("#existing_query");
|
||||
var inviteExistingResults = $("#invite-existing-results");
|
||||
var inviteNewCollapsible = $("#invite-new");
|
||||
var inviteNewForm = $("[data-id='invite-new-form']");
|
||||
var inviteNewRoleInput = $("[data-id='new-user-role-input']");
|
||||
var inviteNewNameInput = $("[data-id='invite-new-name-input']");
|
||||
var inviteNewEmailInput = $("[data-id='invite-new-email-input']");
|
||||
|
||||
function disableInviteBtn() {
|
||||
inviteButton.attr("disabled", "disabled");
|
||||
}
|
||||
function enableInviteBtn() {
|
||||
inviteButton.removeAttr("disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* General modal configuration & toggling.
|
||||
*/
|
||||
modal
|
||||
.on("shown.bs.modal", function() {
|
||||
// Focus the invite existing input
|
||||
inviteExistingQuery.focus();
|
||||
invitingExisting = true;
|
||||
})
|
||||
.on("hidden.bs.modal", function() {
|
||||
// Disable invite button,
|
||||
// reset forms, reset rendered content
|
||||
disableInviteBtn();
|
||||
inviteExistingForm.clearFormFields();
|
||||
inviteExistingForm.clearFormErrors();
|
||||
inviteExistingResults.html("");
|
||||
inviteNewForm.clearFormFields();
|
||||
inviteNewForm.clearFormErrors();
|
||||
});
|
||||
|
||||
inviteExistingCollapsible
|
||||
.on("hidden.bs.collapse", function() {
|
||||
// Reset form & rendered content
|
||||
inviteExistingForm.clearFormFields();
|
||||
inviteExistingForm.clearFormErrors();
|
||||
inviteExistingResults.html("");
|
||||
})
|
||||
.on("hide.bs.collapse", function() {
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("shown.bs.collapse", function() {
|
||||
// Focus input when collapsible is shown
|
||||
inviteExistingQuery.focus();
|
||||
invitingExisting = true;
|
||||
});
|
||||
|
||||
inviteNewCollapsible
|
||||
.on("hidden.bs.collapse", function() {
|
||||
// Reset form
|
||||
inviteNewForm.clearFormFields();
|
||||
inviteNewForm.clearFormErrors();
|
||||
})
|
||||
.on("hide.bs.collapse", function() {
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("shown.bs.collapse", function() {
|
||||
// Focus input when collapsible is shown
|
||||
inviteNewNameInput.focus();
|
||||
invitingExisting = false;
|
||||
});
|
||||
|
||||
// Invite links simply submit either of the forms
|
||||
inviteLinks.on("click", function() {
|
||||
var $this = $(this);
|
||||
|
||||
if (invitingExisting) {
|
||||
var form =
|
||||
inviteExistingResults
|
||||
.find("form[data-id='create-user-organization-form']");
|
||||
|
||||
// Set the role value in the form
|
||||
form
|
||||
.find("[data-id='existing-user-role-input']")
|
||||
.attr("value", $this.attr("data-value"));
|
||||
|
||||
// Submit the form inside "invite existing"
|
||||
animateSpinner(modalContent);
|
||||
form.submit();
|
||||
} else {
|
||||
// Set the role value in the form
|
||||
inviteNewRoleInput
|
||||
.attr("value", $this.attr("data-value"));
|
||||
|
||||
// Submit the form inside "invite new"
|
||||
animateSpinner(modalContent);
|
||||
inviteNewForm.submit();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Invite existing user functionality.
|
||||
*/
|
||||
|
||||
// Invite existing form submission
|
||||
modal
|
||||
.on("ajax:success", inviteExistingForm.selector, function(ev, data, status) {
|
||||
// Clear form errors
|
||||
inviteExistingForm.clearFormErrors();
|
||||
|
||||
// Alright, render the html
|
||||
inviteExistingResults.html(data.html);
|
||||
|
||||
// Disable invite button
|
||||
disableInviteBtn();
|
||||
})
|
||||
.on("ajax:error", inviteExistingForm.selector, function(ev, data, status) {
|
||||
// Display form errors
|
||||
inviteExistingForm.renderFormErrors("", data.responseJSON);
|
||||
});
|
||||
|
||||
// Update values & enable "invite" button
|
||||
// when user clicks on existing user
|
||||
inviteExistingResults
|
||||
.on("change", "[data-action='select-existing-user']", function() {
|
||||
var $this = $(this);
|
||||
// Set the hidden input user ID
|
||||
$("[data-id='existing-user-id-input']")
|
||||
.attr("value", $this.attr("data-user-id"));
|
||||
|
||||
// Enable button
|
||||
enableInviteBtn();
|
||||
});
|
||||
|
||||
/**
|
||||
* Invite new user functionality.
|
||||
*/
|
||||
|
||||
inviteNewForm
|
||||
.on("ajax:success", function(ev, data, status) {
|
||||
// Reload the page
|
||||
location.reload();
|
||||
})
|
||||
.on("ajax:error", function(ev, data, status) {
|
||||
// Render form errors
|
||||
animateSpinner(modalContent, false);
|
||||
$(this).renderFormErrors("user", data.responseJSON);
|
||||
});
|
||||
|
||||
|
||||
// Enable/disable invite button depending whether
|
||||
// any of the new user inputs are empty
|
||||
inviteNewForm
|
||||
.on("input", "input[data-role='input']", function() {
|
||||
if (
|
||||
_.isEmpty(inviteNewNameInput.val()) ||
|
||||
_.isEmpty(inviteNewEmailInput.val())
|
||||
) {
|
||||
disableInviteBtn();
|
||||
} else {
|
||||
enableInviteBtn();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,90 +1,177 @@
|
|||
/**
|
||||
* Toggle the view/edit form visibility.
|
||||
* @param form - The jQuery form selector.
|
||||
* @param edit - True to set form to edit mode;
|
||||
* false to set form to view mode.
|
||||
*/
|
||||
function toggleFormVisibility(form, edit) {
|
||||
if (edit) {
|
||||
form.find("[data-part='view']").hide();
|
||||
form.find("[data-part='edit']").show();
|
||||
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
|
||||
} else {
|
||||
form.find("[data-part='view']").show();
|
||||
form.find("[data-part='edit'] input").blur();
|
||||
form.find("[data-part='edit']").hide();
|
||||
(function(){
|
||||
'use strict';
|
||||
/**
|
||||
* Toggle the view/edit form visibility.
|
||||
* @param form - The jQuery form selector.
|
||||
* @param edit - True to set form to edit mode;
|
||||
* false to set form to view mode.
|
||||
*/
|
||||
function toggleFormVisibility(form, edit) {
|
||||
if (edit) {
|
||||
form.find("[data-part='view']").hide();
|
||||
form.find("[data-part='edit']").show();
|
||||
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
|
||||
} else {
|
||||
form.find("[data-part='view']").show();
|
||||
form.find("[data-part='edit'] input").blur();
|
||||
form.find("[data-part='edit']").hide();
|
||||
|
||||
// Clear all errors on the parent form
|
||||
form.clearFormErrors();
|
||||
// Clear all errors on the parent form
|
||||
form.clearFormErrors();
|
||||
|
||||
// Clear any neccesary fields
|
||||
form.find("input[data-role='clear']").val("");
|
||||
// Clear any neccesary fields
|
||||
form.find("input[data-role='clear']").val("");
|
||||
|
||||
// Copy field data
|
||||
var val = form.find("input[data-role='src']").val();
|
||||
form.find("input[data-role='edit']").val(val);
|
||||
// Copy field data
|
||||
var val = form.find("input[data-role='src']").val();
|
||||
form.find("input[data-role='edit']").val(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var forms = $("form[data-for]");
|
||||
var forms = $("form[data-for]");
|
||||
|
||||
// Add "edit form" listeners
|
||||
forms
|
||||
.find("[data-action='edit']").click(function() {
|
||||
var form = $(this).closest("form");
|
||||
// Add "edit form" listeners
|
||||
forms
|
||||
.find("[data-action='edit']").click(function() {
|
||||
var form = $(this).closest("form");
|
||||
|
||||
// First, hide all form edits
|
||||
_.each(forms, function(form) {
|
||||
toggleFormVisibility($(form), false);
|
||||
// First, hide all form edits
|
||||
_.each(forms, function(form) {
|
||||
toggleFormVisibility($(form), false);
|
||||
});
|
||||
|
||||
// Then, edit the current form
|
||||
toggleFormVisibility(form, true);
|
||||
});
|
||||
|
||||
// Then, edit the current form
|
||||
toggleFormVisibility(form, true);
|
||||
});
|
||||
// Add "cancel form" listeners
|
||||
forms
|
||||
.find("[data-action='cancel']").click(function() {
|
||||
var form = $(this).closest("form");
|
||||
|
||||
// Add "cancel form" listeners
|
||||
forms
|
||||
.find("[data-action='cancel']").click(function() {
|
||||
var form = $(this).closest("form");
|
||||
// Hide the edit portion of the form
|
||||
toggleFormVisibility(form, false);
|
||||
});
|
||||
|
||||
// Hide the edit portion of the form
|
||||
toggleFormVisibility(form, false);
|
||||
});
|
||||
|
||||
// Add form submit listeners
|
||||
forms
|
||||
.on("ajax:success", function(ev, data, status) {
|
||||
// Simply reload the page
|
||||
location.reload();
|
||||
})
|
||||
.on("ajax:error", function(ev, data, status) {
|
||||
// Render form errors
|
||||
$(this).renderFormErrors("user", data.responseJSON);
|
||||
});
|
||||
|
||||
var repeatTutorialModal = $("#repeat-tutorial-modal");
|
||||
var repeatTutorialModalBody = repeatTutorialModal.find(".modal-body");
|
||||
initRepeatTutorialModal();
|
||||
|
||||
$("#reset-tutorial-btn")
|
||||
.on("ajax:before", function () {
|
||||
repeatTutorialModal.modal('show');
|
||||
// Add form submit listeners
|
||||
forms
|
||||
.on("ajax:success", function(ev, data, status) {
|
||||
// Simply reload the page
|
||||
location.reload();
|
||||
})
|
||||
|
||||
.on("ajax:success", function (e, data) {
|
||||
initRepeatTutorialModalBody(data);
|
||||
.on("ajax:error", function(ev, data, status) {
|
||||
// Render form errors
|
||||
$(this).renderFormErrors("user", data.responseJSON);
|
||||
});
|
||||
|
||||
function initRepeatTutorialModal() {
|
||||
// Remove modal content when modal window is closed.
|
||||
repeatTutorialModal.on("hidden.bs.modal", function () {
|
||||
repeatTutorialModalBody.html("");
|
||||
});
|
||||
}
|
||||
var repeatTutorialModal = $("#repeat-tutorial-modal");
|
||||
var repeatTutorialModalBody = repeatTutorialModal.find(".modal-body");
|
||||
initRepeatTutorialModal();
|
||||
notificationsSettings();
|
||||
initNotificationSettingsForm();
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body. This
|
||||
// function must be called when modal body is changed.
|
||||
function initRepeatTutorialModalBody(data) {
|
||||
repeatTutorialModalBody.html(data.html);
|
||||
repeatTutorialModalBody.find(".selectpicker").selectpicker();
|
||||
}
|
||||
$("#reset-tutorial-btn")
|
||||
.on("ajax:before", function () {
|
||||
repeatTutorialModal.modal('show');
|
||||
})
|
||||
|
||||
.on("ajax:success", function (e, data) {
|
||||
initRepeatTutorialModalBody(data);
|
||||
});
|
||||
|
||||
function initRepeatTutorialModal() {
|
||||
// Remove modal content when modal window is closed.
|
||||
repeatTutorialModal.on("hidden.bs.modal", function () {
|
||||
repeatTutorialModalBody.html("");
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize ajax listeners and elements style on modal body. This
|
||||
// function must be called when modal body is changed.
|
||||
function initRepeatTutorialModalBody(data) {
|
||||
repeatTutorialModalBody.html(data.html);
|
||||
repeatTutorialModalBody.find(".selectpicker").selectpicker();
|
||||
}
|
||||
|
||||
// Setup notification checkbox buttons
|
||||
function notificationsSettings() {
|
||||
var notification_settings = [ "recent_notification",
|
||||
"assignments_notification" ]
|
||||
|
||||
for (var i = 0; i < notification_settings.length; i++ ) {
|
||||
var setting = $('[name="' + notification_settings[i] + '"]');
|
||||
var dependant = $('[name="' + notification_settings[i] + '_email"]');
|
||||
dependant.checkboxpicker({ onActiveCls: 'btn-primary' });
|
||||
setting
|
||||
.checkboxpicker({
|
||||
onActiveCls: 'btn-primary'
|
||||
}).change(function() {
|
||||
if ( $(this).prop('checked') ) {
|
||||
enableDependant($('[name="' + $(this).attr('name') + '_email"]'));
|
||||
} else {
|
||||
disableDependant($('[name="' + $(this).attr('name') + '_email"]'));
|
||||
}
|
||||
});
|
||||
|
||||
if ( setting.attr('value') === 'true' ) {
|
||||
setting.prop('checked', true);
|
||||
} else {
|
||||
setting.prop('checked', false);
|
||||
disableDependant(dependant);
|
||||
}
|
||||
|
||||
setEmailSwitch(dependant);
|
||||
}
|
||||
|
||||
function setEmailSwitch(setting) {
|
||||
setting
|
||||
.checkboxpicker({
|
||||
onActiveCls: 'btn-primary'
|
||||
});
|
||||
if ( setting.attr('value') === 'true' ) {
|
||||
setting.prop('checked', true);
|
||||
enableDependant(setting);
|
||||
} else {
|
||||
setting.prop('checked', false);
|
||||
}
|
||||
}
|
||||
|
||||
function disableDependant(dependant) {
|
||||
dependant.checkboxpicker().prop('disabled', true);
|
||||
dependant.checkboxpicker().prop('checked', false);
|
||||
}
|
||||
|
||||
function enableDependant(dependant) {
|
||||
dependant.checkboxpicker().prop('disabled', false);
|
||||
}
|
||||
|
||||
// Initialize system messages
|
||||
var system_message_notification = $('[name="system_message_notification"]');
|
||||
system_message_notification
|
||||
.checkboxpicker({
|
||||
onActiveCls: 'btn-primary'
|
||||
});
|
||||
system_message_notification.prop('checked', true);
|
||||
system_message_notification.prop('disabled', true);
|
||||
|
||||
// Initialize system messages email
|
||||
var system_message_notification_mail = $('[name="system_message_notification_email"]');
|
||||
system_message_notification_mail
|
||||
.checkboxpicker({
|
||||
onActiveCls: 'btn-primary'
|
||||
});
|
||||
system_message_notification_mail.prop(
|
||||
'checked',
|
||||
system_message_notification_mail.attr('value') === 'true'
|
||||
);
|
||||
}
|
||||
|
||||
// triggers submit action when the user clicks
|
||||
function initNotificationSettingsForm() {
|
||||
$('#notifications-settings-panel')
|
||||
.find('.btn-group')
|
||||
.on('click', function() {
|
||||
$(this).submit();
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
*= require jquery-ui/draggable
|
||||
*= require rails_bootstrap_forms
|
||||
*= require bootstrap-select
|
||||
*= require colors
|
||||
*= require constants
|
||||
*= require introjs
|
||||
*= stub reports_pdf
|
||||
*= require quill.snow
|
||||
*/
|
||||
@import "bootstrap-sprockets";
|
||||
@import "bootstrap";
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
// Theme colors
|
||||
$color-theme-primary: #37a0d9;
|
||||
$color-theme-secondary: #8fd13f;
|
||||
$color-theme-dark: #6d6e71;
|
||||
|
||||
// Grayscales
|
||||
$color-white: #fff;
|
||||
$color-alabaster: #fcfcfc;
|
||||
$color-wild-sand: #f5f5f5;
|
||||
$color-concrete: #f2f2f2;
|
||||
$color-gallery: #EEE;
|
||||
$color-alto: #d2d2d2;
|
||||
$color-silver: #c5c5c5;
|
||||
$color-silver-chalice: #a0a0a0;
|
||||
$color-gray: #909088;
|
||||
$color-dove-gray: #666666;
|
||||
$color-emperor: #555;
|
||||
$color-mine-shaft: #333;
|
||||
$color-black: #000;
|
||||
|
||||
// Misc.
|
||||
$color-mystic: #eaeff2;
|
||||
$color-candlelight: #ffda23;
|
||||
|
||||
// Reds
|
||||
$color-mojo: #cf4b48;
|
||||
$color-apple-blossom: #a94442;
|
||||
$color-milano-red: #a70b05;
|
46
app/assets/stylesheets/constants.scss
Normal file
46
app/assets/stylesheets/constants.scss
Normal file
|
@ -0,0 +1,46 @@
|
|||
//==============================================================================
|
||||
// Colors
|
||||
//==============================================================================
|
||||
|
||||
// Theme colors
|
||||
$color-theme-primary: #37a0d9;
|
||||
$color-theme-secondary: #8fd13f;
|
||||
$color-theme-dark: #6d6e71;
|
||||
|
||||
// Grayscale colors
|
||||
$color-white: #fff;
|
||||
$color-alabaster: #fcfcfc;
|
||||
$color-snow: #f9f9f9;
|
||||
$color-wild-sand: #f5f5f5;
|
||||
$color-concrete: #f2f2f2;
|
||||
$color-gallery: #eee;
|
||||
$color-gainsboro: #e3e3e3;
|
||||
$color-alto: #d2d2d2;
|
||||
$color-silver: #c5c5c5;
|
||||
$color-dark-gray: #adadad;
|
||||
$color-silver-chalice: #a0a0a0;
|
||||
$color-gray: #909088;
|
||||
$color-dove-gray: #666;
|
||||
$color-emperor: #555;
|
||||
$color-mine-shaft: #333;
|
||||
$color-nero: #262626;
|
||||
$color-black: #000;
|
||||
|
||||
// Miscelaneous colors
|
||||
$color-mystic: #eaeff2;
|
||||
$color-candlelight: #ffda23;
|
||||
|
||||
// Red colors
|
||||
$color-mojo: #cf4b48;
|
||||
$color-apple-blossom: #a94442;
|
||||
$color-milano-red: #a70b05;
|
||||
|
||||
// Colors for specific intents
|
||||
$color-visited-link: #23527c;
|
||||
|
||||
//==============================================================================
|
||||
// Other
|
||||
//==============================================================================
|
||||
|
||||
// Some big value which is still supported by all browsers
|
||||
$infinity: 9999999;
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the CustomFields controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
63
app/assets/stylesheets/extend/bootstrap.scss
vendored
63
app/assets/stylesheets/extend/bootstrap.scss
vendored
|
@ -1,6 +1,6 @@
|
|||
/* Extending Bootstrap */
|
||||
|
||||
@import "colors";
|
||||
@import 'constants';
|
||||
|
||||
/* navbar avatar image */
|
||||
.navbar-nav .avatar {
|
||||
|
@ -49,3 +49,64 @@
|
|||
padding-right: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-comment .col-xs-12 {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.result-comment .col-xs-12 {
|
||||
float: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.navbar-header {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.navbar-left,
|
||||
.navbar-right {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
border-width: 0 0 1px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.navbar-collapse.collapse {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
float: none !important;
|
||||
margin-top: 7.5px;
|
||||
}
|
||||
|
||||
.navbar-nav>li {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.navbar-nav>li>a {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.collapse.in {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,22 @@
|
|||
}
|
||||
|
||||
.btn-colorselector{
|
||||
background-color: transparent !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.step-container .row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.module-large {
|
||||
.description-label {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.comment {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
|
91
app/assets/stylesheets/notifications.scss
Normal file
91
app/assets/stylesheets/notifications.scss
Normal file
|
@ -0,0 +1,91 @@
|
|||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
.notifications-container {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.notifications-header {
|
||||
background-color: $color-concrete;
|
||||
border-left: 1px solid $color-alto;
|
||||
border-right: 1px solid $color-alto;
|
||||
border-top: 1px solid $color-alto;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
color: $color-dove-gray;
|
||||
font-weight: bold;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.notifications-list {
|
||||
background-color: $color-white;
|
||||
border: 1px solid $color-alto;
|
||||
list-style: none;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
|
||||
.notification {
|
||||
border-bottom: 1px solid $color-alto;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-mystic;
|
||||
}
|
||||
|
||||
&.no-notifications {
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.unseen {
|
||||
border-left: 4px solid $color-theme-primary;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
margin-left: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.assignment {
|
||||
background-color: $color-theme-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
height: 30px;
|
||||
padding-top: 5px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: $color-theme-secondary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
height: 30px;
|
||||
padding-top: 5px;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.notifications-footer {
|
||||
|
||||
background-color: $color-mystic;
|
||||
|
||||
.btn-more-notifications {
|
||||
border-bottom: 1px solid $color-alto;
|
||||
border-left: 1px solid $color-alto;
|
||||
border-right: 1px solid $color-alto;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-mystic;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the Organizations controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -4,7 +4,7 @@
|
|||
* For details, see http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*/
|
||||
|
||||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
$wrapper-width: 280px;
|
||||
|
@ -83,6 +83,7 @@ $toggle-btn-size: 50px;
|
|||
}
|
||||
|
||||
.tree {
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0;
|
||||
padding-top: 15px;
|
||||
opacity: 1;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
.tree {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
// Some color definitions
|
||||
|
@ -212,10 +212,10 @@ path, ._jsPlumb_endpoint {
|
|||
}
|
||||
|
||||
&.group-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-group-hover);
|
||||
@include box-shadow(0 0 0 5px $color-group-hover);
|
||||
}
|
||||
&.module-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-module-hover);
|
||||
@include box-shadow(0 0 0 5px $color-module-hover);
|
||||
}
|
||||
&.alert-yellow .panel-body {
|
||||
color: $color-candlelight;
|
||||
|
@ -244,10 +244,10 @@ path, ._jsPlumb_endpoint {
|
|||
z-index: 5;
|
||||
|
||||
&.group-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-group-hover);
|
||||
@include box-shadow(0 0 0 5px $color-group-hover);
|
||||
}
|
||||
&.module-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-module-hover);
|
||||
@include box-shadow(0 0 0 5px $color-module-hover);
|
||||
}
|
||||
&.alert-yellow {
|
||||
border-color: $color-candlelight;
|
||||
|
@ -316,10 +316,10 @@ path, ._jsPlumb_endpoint {
|
|||
}
|
||||
|
||||
&.group-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-group-hover);
|
||||
@include box-shadow(0 0 0 5px $color-group-hover);
|
||||
}
|
||||
&.module-hover {
|
||||
@include box-shadow(0px 0px 13px 7px $color-module-hover);
|
||||
@include box-shadow(0 0 0 5px $color-module-hover);
|
||||
}
|
||||
&.alert-yellow {
|
||||
border-color: $color-candlelight;
|
||||
|
@ -425,13 +425,12 @@ li.module-hover {
|
|||
}
|
||||
|
||||
.experiment-description {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
border: 1px solid $color-alto;
|
||||
border-radius: 4px;
|
||||
height: 100px;
|
||||
margin-top: 10px;
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
overflow-y: overlay;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.experiment-no-description {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
/* Index page */
|
||||
|
@ -309,17 +309,33 @@ ul {
|
|||
border-bottom: none;
|
||||
height: 0;
|
||||
|
||||
.result-name-container {
|
||||
max-width: 570px;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.user-time {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > .report-element-header {
|
||||
color: $color-theme-primary;
|
||||
}
|
||||
|
||||
.report-element-body {
|
||||
clear: left;
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Result asset element style */
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
/** Custom CSS for report print (& PDF) */
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the ResultAssets controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the ResultComments controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the ResultTables controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the ResultTexts controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the SampleGroups controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the SampleTypes controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
97
app/assets/stylesheets/sample_types_groups.scss
Normal file
97
app/assets/stylesheets/sample_types_groups.scss
Normal file
|
@ -0,0 +1,97 @@
|
|||
@import 'constants';
|
||||
|
||||
.sample_types_groups_list {
|
||||
margin-top: 50px;
|
||||
|
||||
.sample-type-group-name {
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid $color-alto;
|
||||
padding: 15px 5px;
|
||||
|
||||
&:nth-child(2) {
|
||||
border-top: 1px solid $color-alto;
|
||||
}
|
||||
|
||||
.edit {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.abort {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#remove {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-inline .form-group,
|
||||
.form-inline .form-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#name-input {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.new-resource-form {
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid $color-alto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.glyphicon-ok {
|
||||
color: $color-theme-secondary;
|
||||
}
|
||||
|
||||
.glyphicon {
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sample-group-colorselector {
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
|
||||
.btn-colorselector {
|
||||
border: 1px solid $color-black;
|
||||
}
|
||||
|
||||
.dropdown-colorselector > .dropdown-menu > li {
|
||||
border-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dropdown-colorselector > .dropdown-menu {
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.dropdown-menu.dropdown-caret:before {
|
||||
left: 80px;
|
||||
}
|
||||
|
||||
.dropdown-menu.dropdown-caret:after {
|
||||
left: 81px;
|
||||
}
|
||||
|
||||
.caret {
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#new_sample_group .color-picker {
|
||||
margin-top: 7px;
|
||||
}
|
|
@ -2,8 +2,10 @@
|
|||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
|
||||
@import 'constants';
|
||||
|
||||
#search-content {
|
||||
background-color: #fff;
|
||||
background-color: $color-white;
|
||||
padding-top: 20px;
|
||||
}
|
||||
.search-asset-text-data{
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the StepComments controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -1,4 +1,4 @@
|
|||
@import "colors";
|
||||
@import 'constants';
|
||||
@import "mixins";
|
||||
|
||||
/** Layout **/
|
||||
|
@ -72,6 +72,118 @@ table {
|
|||
max-width: 400px;
|
||||
}
|
||||
|
||||
#notifications-dropdown {
|
||||
.fa-bell {
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#count-notifications {
|
||||
background-color: $color-theme-primary;
|
||||
border-radius: 5px;
|
||||
color: $color-wild-sand;
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 12px;
|
||||
padding: 1px 6px;
|
||||
position: relative;
|
||||
z-index: $infinity;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width:450px) {
|
||||
.dropdown-notifications {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-notifications {
|
||||
max-height: 500px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
width: 450px;
|
||||
word-wrap: break-word;
|
||||
|
||||
.notifications-no-recent {
|
||||
padding-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-bottom: 1px solid $color-alto;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.unseen {
|
||||
border-left: 4px solid $color-theme-primary;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
top: 0px;
|
||||
margin-top: 5px;
|
||||
height: 45px;
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.assignment {
|
||||
background-color: $color-theme-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
display: block;
|
||||
font-size: 23px;
|
||||
height: 45px;
|
||||
padding-top: 5px;
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: $color-theme-secondary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
display: block;
|
||||
font-size: 23px;
|
||||
height: 45px;
|
||||
padding-top: 8px;
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
.notifications-dropdown-header {
|
||||
background-color: $color-theme-primary;
|
||||
color: $color-wild-sand;
|
||||
font-weight: bold;
|
||||
padding: 8px;
|
||||
|
||||
a {
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.notifications-dropdown-footer {
|
||||
background-color: $color-mystic;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding-right: 8px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#search-menu {
|
||||
padding-right: 0;
|
||||
|
||||
|
@ -316,6 +428,13 @@ a[data-toggle="tooltip"] {
|
|||
}
|
||||
}
|
||||
|
||||
.has-error {
|
||||
.ql-container.ql-snow,
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 1px solid $color-apple-blossom;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs-less {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -363,6 +482,17 @@ a[data-toggle="tooltip"] {
|
|||
overflow: hidden
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
.ql-editor {
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
|
||||
blockquote {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-secondary {
|
||||
background: $color-concrete !important;
|
||||
margin-left: -280px;
|
||||
|
@ -442,6 +572,60 @@ a[data-toggle="tooltip"] {
|
|||
}
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
padding-right: 25px;
|
||||
width: 250px;
|
||||
|
||||
input {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width:1000px) {
|
||||
.search-dropdown {
|
||||
width: 270px;
|
||||
|
||||
.form-group {
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global team switch
|
||||
#team-switch {
|
||||
border-left: 1px solid $color-alto;
|
||||
border-right: 1px solid $color-alto;
|
||||
word-wrap: break-word;
|
||||
|
||||
.dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-concrete;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-emperor;
|
||||
display: block;
|
||||
line-height: 1.6em;
|
||||
padding: 3px 20px;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Settings */
|
||||
.nav-settings {
|
||||
margin-top: 15px;
|
||||
|
@ -462,14 +646,64 @@ a[data-toggle="tooltip"] {
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.notification-settings-labels {
|
||||
line-height: 3em;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.notification-settings-container {
|
||||
margin-bottom: 50px;
|
||||
margin-top: 50px;
|
||||
|
||||
.col-md-4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.assignment {
|
||||
background-color: $color-theme-primary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
font-size: 15px;
|
||||
margin-left: 20px;
|
||||
margin-right: 15px;
|
||||
padding: 7px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: $color-theme-secondary;
|
||||
border-radius: 50%;
|
||||
color: $color-wild-sand;
|
||||
font-size: 15px;
|
||||
margin-left: 20px;
|
||||
margin-right: 15px;
|
||||
padding: 8px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.img-circle {
|
||||
margin-left: 20px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#reset-tutorial-btn {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
// Help link
|
||||
#help-link {
|
||||
padding: 13px;
|
||||
|
||||
.glyphicon.glyphicon-info-sign {
|
||||
font-size: 16px;
|
||||
margin-top: 4px;
|
||||
|
||||
#help-link {
|
||||
padding: 13px;
|
||||
.glyphicon.glyphicon-info-sign {
|
||||
font-size: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,7 +722,7 @@ a[data-toggle="tooltip"] {
|
|||
&:focus, &:active, &:hover {
|
||||
box-shadow: none;
|
||||
background-color: $color-theme-primary;
|
||||
border-color: #adadad;
|
||||
border-color: $color-dark-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -528,6 +762,25 @@ a[data-toggle="tooltip"] {
|
|||
}
|
||||
}
|
||||
|
||||
.user-statistics {
|
||||
.list-inline {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
li {
|
||||
height: 100px;
|
||||
margin: 15px;
|
||||
padding-bottom: 15px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
/** Protocols management */
|
||||
.breadcrumb-protocols-manager {
|
||||
overflow: visible;
|
||||
|
@ -902,7 +1155,7 @@ ul.content-module-activities {
|
|||
}
|
||||
|
||||
.badge-preview {
|
||||
background-color:#c5c5c5;
|
||||
background-color: $color-silver;
|
||||
border-radius: 2em;
|
||||
float: left;
|
||||
font-size: 20px;
|
||||
|
@ -1085,12 +1338,12 @@ table.dataTable {
|
|||
top: 50%;
|
||||
margin-top: -19px;
|
||||
border-top: 19px solid transparent;
|
||||
border-left: 13px solid #37A0D9;
|
||||
border-left: 13px solid $color-theme-primary;
|
||||
border-bottom: 19px solid transparent;
|
||||
}
|
||||
|
||||
.nav-stacked-arrow > li.active > a:hover:after {
|
||||
border-left: 13px solid #337ab7;
|
||||
border-left: 13px solid darken($color-theme-primary, 15%);
|
||||
}
|
||||
|
||||
/* Overlay to disable interaction while loading ajax */
|
||||
|
@ -1108,6 +1361,7 @@ table.dataTable {
|
|||
position: fixed;
|
||||
left:50%;
|
||||
top:50%;
|
||||
z-index: 1000000000;
|
||||
}
|
||||
|
||||
html.turbolinks-progress-bar::before {
|
||||
|
@ -1147,10 +1401,6 @@ html.turbolinks-progress-bar::before {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.custom .introjs-prevbutton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom .introjs-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1168,31 +1418,32 @@ html.turbolinks-progress-bar::before {
|
|||
}
|
||||
|
||||
.introjs-tooltipReferenceLayer {
|
||||
z-index: 999999 !important;
|
||||
z-index: $infinity !important;
|
||||
|
||||
&.max {
|
||||
z-index: 99999999 !important;
|
||||
z-index: calc($infinity + 1) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-tooltip {
|
||||
cursor: help;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-bottom: 1px dotted $color-alto;
|
||||
}
|
||||
cursor: help;
|
||||
display: inline-block;
|
||||
|
||||
.modal-tooltip .modal-tooltiptext {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
width: auto;
|
||||
background-color: $color-alto;
|
||||
color: $color-mine-shaft;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
z-index: 999999;
|
||||
.modal-tooltiptext {
|
||||
background-color: $color-alto;
|
||||
border-radius: 6px;
|
||||
color: $color-mine-shaft;
|
||||
display: block;
|
||||
margin-left: 100px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
word-wrap: break-word;
|
||||
z-index: $infinity;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-tooltip:hover .modal-tooltiptext {
|
||||
|
@ -1236,3 +1487,220 @@ html.turbolinks-progress-bar::before {
|
|||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
//edit avatar
|
||||
.avatar-container {
|
||||
background-color: lighten($color-concrete, 2%);
|
||||
height: 100px;
|
||||
position: relative;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.avatar-container:hover .avatar-edit {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.avatar-edit {
|
||||
display: none;
|
||||
margin-top: 70%;
|
||||
opacity: .8;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
||||
.btn-greyed {
|
||||
border-radius: 0;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-invite-users {
|
||||
.bootstrap-tagsinput {
|
||||
min-width: 450px;
|
||||
}
|
||||
|
||||
.results-container .alert {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.results-container .results-wrap {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.org-selector .heading {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
input[type=checkbox] {
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable textarea resizing throughout application
|
||||
// (will be done via autosize JS plugin)
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea.textarea-sm {
|
||||
height: 34px;
|
||||
min-height: 34px;
|
||||
|
||||
&.form-control {
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-form .input-group {
|
||||
height: 100%;
|
||||
|
||||
textarea {
|
||||
-webkit-margin-before: 1px;
|
||||
-webkit-margin-after: 1px;
|
||||
}
|
||||
|
||||
.input-group-btn {
|
||||
height: 100%;
|
||||
|
||||
.btn.btn-default {
|
||||
height: 100%;
|
||||
min-height: 34px;
|
||||
-webkit-padding-before: 0;
|
||||
-webkit-padding-after: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// new smart dropdown styles
|
||||
|
||||
.smart-dropdown {
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
width: 300px;
|
||||
|
||||
.modal-tooltiptext {
|
||||
margin-left: 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
|
||||
li {
|
||||
background: $color-white;
|
||||
border: 1px solid $color-alto;
|
||||
padding: 10px 10px 10px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[data-position] {
|
||||
cursor: -webkit-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.ui-sortable-helper {
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.grippy {
|
||||
background-repeat: none;
|
||||
display: inline-block;
|
||||
height: 19px;
|
||||
margin-top: 5px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
li:not(.editing) .grippy-img {
|
||||
background: url(asset-path('custom/grippy.png'));
|
||||
}
|
||||
|
||||
.text {
|
||||
display: inline-block;
|
||||
padding-left: 15px;
|
||||
padding-top: 5px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.text-edit {
|
||||
display: inline-block;
|
||||
margin-left: 2px;
|
||||
margin-top: -2.5px;
|
||||
position: absolute;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.col-invisible {
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $color-emperor;
|
||||
}
|
||||
}
|
||||
|
||||
.ok {
|
||||
color: $color-theme-secondary;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
margin-left: 5px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.vis {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.edit {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.del {
|
||||
}
|
||||
}
|
||||
|
||||
.help-block {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-column-form .help-block {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
th.custom-field .modal-tooltiptext {
|
||||
margin-left: 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
|
||||
.disable-click {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// Place all the styles related to the UserMyModules controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -2,7 +2,7 @@ class ActivitiesController < ApplicationController
|
|||
before_filter :load_vars
|
||||
|
||||
def index
|
||||
@per_page = 10
|
||||
@per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT
|
||||
@activities = current_user.last_activities(@last_activity_id,
|
||||
@per_page + 1)
|
||||
|
||||
|
|
|
@ -2,13 +2,16 @@ class ApplicationController < ActionController::Base
|
|||
include PermissionHelper
|
||||
include FirstTimeDataGenerator
|
||||
|
||||
acts_as_token_authentication_handler_for User
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :null_session instead.
|
||||
protect_from_forgery with: :exception
|
||||
before_action :authenticate_user!
|
||||
helper_method :current_organization
|
||||
before_action :generate_intro_tutorial, if: :is_current_page_root?
|
||||
before_action :update_current_organization, if: :user_signed_in?
|
||||
around_action :set_time_zone, if: :current_user
|
||||
layout "main"
|
||||
layout 'main'
|
||||
|
||||
def forbidden
|
||||
render_403
|
||||
|
@ -19,7 +22,12 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def is_current_page_root?
|
||||
controller_name == "projects" && action_name == "index"
|
||||
controller_name == 'projects' && action_name == 'index'
|
||||
end
|
||||
|
||||
# Sets current organization for all controllers
|
||||
def current_organization
|
||||
Organization.find_by_id(current_user.current_organization_id)
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -75,6 +83,15 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def update_current_organization
|
||||
if current_user.current_organization_id.blank? &&
|
||||
current_user.organizations.count > 0
|
||||
current_user.update(
|
||||
current_organization_id: current_user.organizations.first.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# With this Devise callback user is redirected directly to sign in page instead
|
||||
# of to root path. Therefore notification for sign out is displayed.
|
||||
def after_sign_out_path_for(resource_or_scope)
|
||||
|
|
|
@ -140,7 +140,7 @@ class AssetsController < ApplicationController
|
|||
success_action_status: '201',
|
||||
acl: 'private',
|
||||
storage_class: "STANDARD",
|
||||
content_length_range: 1..FILE_MAX_SIZE.megabytes,
|
||||
content_length_range: 1..Constants::FILE_MAX_SIZE_MB.megabytes,
|
||||
content_type: asset.file_content_type
|
||||
)
|
||||
posts.push({
|
||||
|
@ -148,14 +148,16 @@ class AssetsController < ApplicationController
|
|||
fields: s3_post.fields
|
||||
})
|
||||
|
||||
if (asset.file_content_type =~ /^image\//) == 0
|
||||
condition = %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
|
||||
|
||||
if condition === asset.file_content_type
|
||||
asset.file.options[:styles].each do |style, option|
|
||||
s3_post = S3_BUCKET.presigned_post(
|
||||
key: asset.file.path(style)[1..-1],
|
||||
success_action_status: '201',
|
||||
acl: 'public-read',
|
||||
storage_class: "REDUCED_REDUNDANCY",
|
||||
content_length_range: 1..FILE_MAX_SIZE.megabytes,
|
||||
content_length_range: 1..Constants::FILE_MAX_SIZE_MB.megabytes,
|
||||
content_type: asset.file_content_type
|
||||
)
|
||||
posts.push({
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
class CustomFieldsController < ApplicationController
|
||||
before_action :load_vars_nested, only: [:create]
|
||||
before_action :check_create_permissions, only: [:create]
|
||||
before_action :load_vars, only: [:update, :destroy, :destroy_html]
|
||||
before_action :load_vars_nested, only: [:create, :destroy_html]
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_update_permissions, only: :update
|
||||
before_action :check_destroy_permissions, only: [:destroy, :destroy_html]
|
||||
|
||||
def create
|
||||
@custom_field = CustomField.new(custom_field_params)
|
||||
|
@ -9,16 +12,19 @@ class CustomFieldsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
if @custom_field.save
|
||||
flash[:success] = t(
|
||||
"custom_fields.create.success_flash",
|
||||
custom_field: @custom_field.name,
|
||||
organization: @organization.name
|
||||
)
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
id: @custom_field.id
|
||||
id: @custom_field.id,
|
||||
name: @custom_field.name,
|
||||
edit_url:
|
||||
organization_custom_field_path(@organization, @custom_field),
|
||||
destroy_html_url:
|
||||
organization_custom_field_destroy_html_path(
|
||||
@organization, @custom_field
|
||||
)
|
||||
},
|
||||
status: :ok }
|
||||
status: :ok
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: @custom_field.errors.to_json,
|
||||
|
@ -28,22 +34,77 @@ class CustomFieldsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars_nested
|
||||
@organization = Organization.find_by_id(params[:organization_id])
|
||||
|
||||
unless @organization
|
||||
render_404
|
||||
def update
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
@custom_field.update_attributes(custom_field_params)
|
||||
if @custom_field.save
|
||||
render json: { status: :ok }
|
||||
else
|
||||
render json: @custom_field.errors.to_json,
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
unless can_create_custom_field_in_organization(@organization)
|
||||
render_403
|
||||
def destroy_html
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'samples/delete_custom_field_modal_body.html.erb',
|
||||
locals: { column_index: params[:column_index] }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@del_custom_field = @custom_field.dup
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @custom_field.destroy
|
||||
SamplesTable.update_samples_table_state(
|
||||
@del_custom_field,
|
||||
params[:custom_field][:column_index]
|
||||
)
|
||||
render json: { status: :ok }
|
||||
else
|
||||
render json: { status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@custom_field = CustomField.find_by_id(params[:id])
|
||||
@custom_field = CustomField.find_by_id(
|
||||
params[:custom_field_id]
|
||||
) unless @custom_field
|
||||
render_404 unless @custom_field
|
||||
end
|
||||
|
||||
def load_vars_nested
|
||||
@organization = Organization.find_by_id(params[:organization_id])
|
||||
render_404 unless @organization
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_create_custom_field_in_organization(@organization)
|
||||
end
|
||||
|
||||
def check_update_permissions
|
||||
render_403 unless can_edit_custom_field(@custom_field)
|
||||
end
|
||||
|
||||
def check_destroy_permissions
|
||||
render_403 unless can_delete_custom_field(@custom_field)
|
||||
end
|
||||
|
||||
def custom_field_params
|
||||
params.require(:custom_field).permit(:name)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
class ExperimentsController < ApplicationController
|
||||
include PermissionHelper
|
||||
include OrganizationsHelper
|
||||
|
||||
before_action :set_experiment,
|
||||
except: [:new, :create]
|
||||
before_action :set_project,
|
||||
|
@ -37,6 +39,16 @@ class ExperimentsController < ApplicationController
|
|||
@experiment.last_modified_by = current_user
|
||||
@experiment.project = @project
|
||||
if @experiment.save
|
||||
Activity.create(
|
||||
type_of: :create_experiment,
|
||||
project: @experiment.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.create_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment: @experiment.name
|
||||
)
|
||||
)
|
||||
flash[:success] = t('experiments.create.success_flash',
|
||||
experiment: @experiment.name)
|
||||
respond_to do |format|
|
||||
|
@ -55,6 +67,7 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
def canvas
|
||||
@project = @experiment.project
|
||||
current_organization_switch(@project.organization)
|
||||
end
|
||||
|
||||
def edit
|
||||
|
@ -73,6 +86,16 @@ class ExperimentsController < ApplicationController
|
|||
@experiment.update_attributes(experiment_params)
|
||||
@experiment.last_modified_by = current_user
|
||||
if @experiment.save
|
||||
Activity.create(
|
||||
type_of: :edit_experiment,
|
||||
project: @experiment.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.edit_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment: @experiment.name
|
||||
)
|
||||
)
|
||||
@experiment.touch(:workflowimg_updated_at)
|
||||
flash[:success] = t('experiments.update.success_flash',
|
||||
experiment: @experiment.name)
|
||||
|
@ -103,6 +126,16 @@ class ExperimentsController < ApplicationController
|
|||
@experiment.archived_by = current_user
|
||||
@experiment.archived_on = DateTime.now
|
||||
if @experiment.save
|
||||
Activity.create(
|
||||
type_of: :archive_experiment,
|
||||
project: @experiment.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.archive_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment: @experiment.name
|
||||
)
|
||||
)
|
||||
flash[:success] = t('experiments.archive.success_flash',
|
||||
experiment: @experiment.name)
|
||||
|
||||
|
@ -242,7 +275,8 @@ class ExperimentsController < ApplicationController
|
|||
@organization,
|
||||
nil,
|
||||
nil,
|
||||
@experiment)
|
||||
@experiment,
|
||||
current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class MyModuleCommentsController < ApplicationController
|
||||
before_action :load_vars
|
||||
before_action :check_view_permissions, only: [ :index ]
|
||||
before_action :check_add_permissions, only: [ :new, :create ]
|
||||
before_action :check_view_permissions, only: :index
|
||||
before_action :check_add_permissions, only: [:create]
|
||||
before_action :check_edit_permissions, only: [:edit, :update]
|
||||
before_action :check_destroy_permissions, only: [:destroy]
|
||||
|
||||
|
@ -9,40 +9,35 @@ class MyModuleCommentsController < ApplicationController
|
|||
@comments = @my_module.last_comments(@last_comment_id, @per_page)
|
||||
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
# 'index' partial includes header and form for adding new
|
||||
# messages. 'list' partial is used for showing more
|
||||
# comments.
|
||||
partial = "index.html.erb"
|
||||
partial = "list.html.erb" if @last_comment_id > 0
|
||||
more_url = ""
|
||||
partial = 'index.html.erb'
|
||||
partial = 'list.html.erb' if @last_comment_id > 0
|
||||
more_url = ''
|
||||
if @comments.count > 0
|
||||
more_url = url_for(my_module_my_module_comments_url(@my_module,
|
||||
format: :json,
|
||||
from: @comments.last.id))
|
||||
format: :json,
|
||||
from: @comments
|
||||
.first.id))
|
||||
end
|
||||
render :json => {
|
||||
per_page: @per_page,
|
||||
results_number: @comments.length,
|
||||
more_url: more_url,
|
||||
html: render_to_string({
|
||||
render json: {
|
||||
perPage: @per_page,
|
||||
resultsNumber: @comments.length,
|
||||
moreUrl: more_url,
|
||||
html: render_to_string(
|
||||
partial: partial,
|
||||
locals: {
|
||||
comments: @comments,
|
||||
more_comments_url: more_url
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@comment = Comment.new(
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
|
||||
def create
|
||||
@comment = Comment.new(
|
||||
message: comment_params[:message],
|
||||
|
@ -63,31 +58,25 @@ class MyModuleCommentsController < ApplicationController
|
|||
)
|
||||
)
|
||||
|
||||
format.html {
|
||||
flash[:success] = t(
|
||||
"my_module_comments.create.success_flash",
|
||||
module: @my_module.name)
|
||||
redirect_to session.delete(:return_to)
|
||||
}
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "comment.html.erb",
|
||||
html: render_to_string(
|
||||
partial: 'comment.html.erb',
|
||||
locals: {
|
||||
comment: @comment
|
||||
}
|
||||
})
|
||||
),
|
||||
date: @comment.created_at.strftime('%d.%m.%Y')
|
||||
},
|
||||
status: :created
|
||||
}
|
||||
end
|
||||
else
|
||||
response.status = 400
|
||||
format.html { render :new }
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
errors: @comment.errors.to_hash(true)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -161,7 +150,7 @@ class MyModuleCommentsController < ApplicationController
|
|||
|
||||
def load_vars
|
||||
@last_comment_id = params[:from].to_i
|
||||
@per_page = 10
|
||||
@per_page = Constants::COMMENTS_SEARCH_LIMIT
|
||||
@my_module = MyModule.find_by_id(params[:my_module_id])
|
||||
|
||||
unless @my_module
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class MyModuleTagsController < ApplicationController
|
||||
before_action :load_vars
|
||||
before_action :check_view_permissions, only: [:index_edit, :index]
|
||||
before_action :check_create_permissions, only: [:new, :create]
|
||||
before_action :check_view_permissions, only: [:index_edit]
|
||||
before_action :check_create_permissions, only: [:create]
|
||||
before_action :check_destroy_permissions, only: [:destroy]
|
||||
|
||||
def index_edit
|
||||
|
@ -22,101 +22,27 @@ class MyModuleTagsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: {
|
||||
html_canvas: render_to_string(
|
||||
partial: "canvas/tags.html.erb",
|
||||
locals: { my_module: @my_module }
|
||||
),
|
||||
html_module_header: render_to_string(
|
||||
partial: "my_modules/tags.html.erb",
|
||||
locals: { my_module: @my_module }
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
session[:return_to] ||= request.referer
|
||||
@mt = MyModuleTag.new(my_module: @my_module)
|
||||
init_gui
|
||||
end
|
||||
|
||||
def create
|
||||
@mt = MyModuleTag.new(mt_params.merge(my_module: @my_module))
|
||||
@mt.created_by = current_user
|
||||
@mt.save
|
||||
|
||||
if @mt.save
|
||||
flash_success = t(
|
||||
"my_module_tags.create.success_flash",
|
||||
tag: @mt.tag.name,
|
||||
module: @mt.my_module.name)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = flash_success
|
||||
redirect_to session.delete(:return_to)
|
||||
}
|
||||
format.json {
|
||||
redirect_to my_module_tags_edit_path(format: :json), :status => 303
|
||||
}
|
||||
end
|
||||
else
|
||||
flash_error = t(
|
||||
"my_module_tags.create.error_flash",
|
||||
module: @mt.my_module.name)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:error] = flash_error
|
||||
init_gui
|
||||
render :new
|
||||
}
|
||||
format.json {
|
||||
# TODO
|
||||
redirect_to my_module_tags_edit_path(format: :json), :status => 303
|
||||
}
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to my_module_tags_edit_path(format: :json),
|
||||
status: 303
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
session[:return_to] ||= request.referer
|
||||
@mt = MyModuleTag.find_by_id(params[:id])
|
||||
@mt.destroy
|
||||
|
||||
if @mt.present? and @mt.destroy
|
||||
flash_success = t(
|
||||
"my_module_tags.destroy.success_flash",
|
||||
tag: @mt.tag.name,
|
||||
module: @mt.my_module.name)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = flash_success
|
||||
redirect_to session.delete(:return_to)
|
||||
}
|
||||
format.json {
|
||||
redirect_to my_module_tags_edit_path(format: :json), :status => 303
|
||||
}
|
||||
end
|
||||
else
|
||||
flash_success = t(
|
||||
"my_module_tags.destroy.error_flash",
|
||||
tag: @mt.tag.name,
|
||||
module: @mt.my_module.name)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:error] = flash_error
|
||||
redirect_to session.delete(:return_to)
|
||||
}
|
||||
format.json {
|
||||
# TODO
|
||||
redirect_to my_module_tags_edit_path(format: :json), :status => 303
|
||||
}
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
redirect_to my_module_tags_edit_path(format: :json),
|
||||
status: 303
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
class MyModulesController < ApplicationController
|
||||
include SampleActions
|
||||
include OrganizationsHelper
|
||||
|
||||
before_action :load_vars, only: [
|
||||
:show, :edit, :update, :destroy,
|
||||
:show, :update, :destroy,
|
||||
:description, :due_date, :protocols, :results,
|
||||
:samples, :activities, :activities_tab,
|
||||
:assign_samples, :unassign_samples,
|
||||
:delete_samples,
|
||||
:samples_index, :archive]
|
||||
before_action :load_markdown, only: [ :results ]
|
||||
before_action :load_vars_nested, only: [:new, :create]
|
||||
before_action :check_edit_permissions, only: [
|
||||
:edit, :update, :description, :due_date
|
||||
:update, :description, :due_date
|
||||
]
|
||||
before_action :check_destroy_permissions, only: [:destroy]
|
||||
before_action :check_view_info_permissions, only: [:show]
|
||||
|
@ -34,7 +34,6 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json {
|
||||
render :json => {
|
||||
:html => render_to_string({
|
||||
|
@ -131,10 +130,6 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
session[:return_to] ||= request.referer
|
||||
end
|
||||
|
||||
def update
|
||||
@my_module.assign_attributes(my_module_params)
|
||||
@my_module.last_modified_by = current_user
|
||||
|
@ -192,11 +187,6 @@ class MyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
if saved
|
||||
format.html {
|
||||
flash[:success] = t("my_modules.update.success_flash",
|
||||
module: @my_module.name)
|
||||
redirect_to(:back)
|
||||
}
|
||||
format.json {
|
||||
alerts = []
|
||||
alerts << "alert-red" if @my_module.is_overdue?
|
||||
|
@ -219,9 +209,6 @@ class MyModulesController < ApplicationController
|
|||
}
|
||||
}
|
||||
else
|
||||
format.html {
|
||||
render :edit
|
||||
}
|
||||
format.json {
|
||||
render json: @my_module.errors,
|
||||
status: :unprocessable_entity
|
||||
|
@ -232,10 +219,14 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def protocols
|
||||
@protocol = @my_module.protocol
|
||||
current_organization_switch(@protocol.organization)
|
||||
end
|
||||
|
||||
def results
|
||||
|
||||
current_organization_switch(@my_module
|
||||
.experiment
|
||||
.project
|
||||
.organization)
|
||||
end
|
||||
|
||||
def samples
|
||||
|
@ -245,6 +236,10 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def archive
|
||||
@archived_results = @my_module.archived_results
|
||||
current_organization_switch(@my_module
|
||||
.experiment
|
||||
.project
|
||||
.organization)
|
||||
end
|
||||
|
||||
# Submit actions
|
||||
|
@ -262,9 +257,26 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
task_names = []
|
||||
new_samples = []
|
||||
@my_module.get_downstream_modules.each do |my_module|
|
||||
new_samples = samples.select { |el| my_module.samples.exclude?(el) }
|
||||
my_module.samples.push(*new_samples)
|
||||
task_names << my_module.name
|
||||
end
|
||||
if new_samples.any?
|
||||
Activity.create(
|
||||
type_of: :assign_sample,
|
||||
project: @my_module.experiment.project,
|
||||
my_module: @my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.assign_sample',
|
||||
user: current_user.full_name,
|
||||
tasks: task_names.join(', '),
|
||||
samples: new_samples.map(&:name).join(', ')
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
redirect_to samples_my_module_path(@my_module)
|
||||
|
@ -279,14 +291,30 @@ class MyModulesController < ApplicationController
|
|||
sample.last_modified_by = current_user
|
||||
sample.save
|
||||
|
||||
if sample
|
||||
if sample && @my_module.samples.include?(sample)
|
||||
samples << sample
|
||||
end
|
||||
end
|
||||
|
||||
task_names = []
|
||||
@my_module.get_downstream_modules.each do |my_module|
|
||||
task_names << my_module.name
|
||||
my_module.samples.destroy(samples & my_module.samples)
|
||||
end
|
||||
if samples.any?
|
||||
Activity.create(
|
||||
type_of: :unassign_sample,
|
||||
project: @my_module.experiment.project,
|
||||
my_module: @my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.unassign_sample',
|
||||
user: current_user.full_name,
|
||||
tasks: task_names.join(', '),
|
||||
samples: samples.map(&:name).join(', ')
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
redirect_to samples_my_module_path(@my_module)
|
||||
end
|
||||
|
@ -297,9 +325,14 @@ class MyModulesController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json {
|
||||
render json: ::SampleDatatable.new(view_context, @organization, nil, @my_module)
|
||||
}
|
||||
format.json do
|
||||
render json: ::SampleDatatable.new(view_context,
|
||||
@organization,
|
||||
nil,
|
||||
@my_module,
|
||||
nil,
|
||||
current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -316,16 +349,6 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# Initialize markdown parser
|
||||
def load_markdown
|
||||
@markdown = Redcarpet::Markdown.new(
|
||||
Redcarpet::Render::HTML.new(
|
||||
filter_html: true,
|
||||
no_images: true
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def check_edit_permissions
|
||||
unless can_edit_module(@my_module)
|
||||
render_403
|
||||
|
|
|
@ -11,8 +11,10 @@ class OrganizationsController < ApplicationController
|
|||
if params[:file]
|
||||
begin
|
||||
|
||||
if params[:file].size > FILE_MAX_SIZE.megabytes
|
||||
error = t("organizations.parse_sheet.errors.file_size_exceeded")
|
||||
if params[:file].size > Constants::FILE_MAX_SIZE_MB.megabytes
|
||||
error = t 'general.file.size_exceeded',
|
||||
file_size: Constants::FILE_MAX_SIZE_MB
|
||||
|
||||
format.html {
|
||||
flash[:alert] = error
|
||||
redirect_to session.delete(:return_to)
|
||||
|
@ -39,6 +41,10 @@ class OrganizationsController < ApplicationController
|
|||
|
||||
# Fill in fields for dropdown
|
||||
@available_fields = @organization.get_available_sample_fields
|
||||
# Truncate long fields
|
||||
@available_fields.update(@available_fields) do |_k, v|
|
||||
v.truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN)
|
||||
end
|
||||
|
||||
# Save file for next step (importing)
|
||||
@temp_file = TempFile.new(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class ProjectCommentsController < ApplicationController
|
||||
before_action :load_vars
|
||||
before_action :check_view_permissions, only: [ :index ]
|
||||
before_action :check_add_permissions, only: [ :new, :create ]
|
||||
before_action :check_view_permissions, only: :index
|
||||
before_action :check_add_permissions, only: [:create]
|
||||
before_action :check_edit_permissions, only: [:edit, :update]
|
||||
before_action :check_destroy_permissions, only: [:destroy]
|
||||
|
||||
|
@ -9,39 +9,34 @@ class ProjectCommentsController < ApplicationController
|
|||
@comments = @project.last_comments(@last_comment_id, @per_page)
|
||||
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
# 'index' partial includes header and form for adding new
|
||||
# messages. 'list' partial is used for showing more
|
||||
# comments.
|
||||
partial = "index.html.erb"
|
||||
partial = "list.html.erb" if @last_comment_id > 0
|
||||
more_url = ""
|
||||
partial = 'index.html.erb'
|
||||
partial = 'list.html.erb' if @last_comment_id > 0
|
||||
more_url = ''
|
||||
if @comments.count > 0
|
||||
more_url = url_for(project_project_comments_url(format: :json,
|
||||
from: @comments.last.id))
|
||||
from: @comments
|
||||
.first.id))
|
||||
end
|
||||
render :json => {
|
||||
:per_page => @per_page,
|
||||
:results_number => @comments.length,
|
||||
:more_url => more_url,
|
||||
:html => render_to_string({
|
||||
:partial => partial,
|
||||
:locals => {
|
||||
:comments => @comments,
|
||||
:more_comments_url => more_url
|
||||
render json: {
|
||||
perPage: @per_page,
|
||||
resultsNumber: @comments.length,
|
||||
moreUrl: more_url,
|
||||
html: render_to_string(
|
||||
partial: partial,
|
||||
locals: {
|
||||
comments: @comments,
|
||||
more_comments_url: more_url
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@comment = Comment.new(
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
|
||||
def create
|
||||
@comment = Comment.new(
|
||||
message: comment_params[:message],
|
||||
|
@ -62,25 +57,19 @@ class ProjectCommentsController < ApplicationController
|
|||
)
|
||||
)
|
||||
|
||||
format.html {
|
||||
flash[:success] = t(
|
||||
"project_comments.create.success_flash",
|
||||
project: @project.name)
|
||||
redirect_to projects_path
|
||||
}
|
||||
format.json {
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
html: render_to_string(
|
||||
partial: 'comment.html.erb',
|
||||
locals: {
|
||||
comment: @comment
|
||||
}
|
||||
})
|
||||
),
|
||||
date: @comment.created_at.strftime('%d.%m.%Y')
|
||||
}, status: :created
|
||||
}
|
||||
else
|
||||
response.status = 400
|
||||
format.html { render :new }
|
||||
format.json {
|
||||
render json: {
|
||||
errors: @comment.errors.to_hash(true)
|
||||
|
@ -157,7 +146,7 @@ class ProjectCommentsController < ApplicationController
|
|||
|
||||
def load_vars
|
||||
@last_comment_id = params[:from].to_i
|
||||
@per_page = 10
|
||||
@per_page = Constants::COMMENTS_SEARCH_LIMIT
|
||||
@project = Project.find_by_id(params[:project_id])
|
||||
|
||||
unless @project
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class ProjectsController < ApplicationController
|
||||
include SampleActions
|
||||
include RenamingUtil
|
||||
include OrganizationsHelper
|
||||
|
||||
before_action :load_vars, only: [:show, :edit, :update,
|
||||
:notifications, :reports,
|
||||
|
@ -14,7 +15,7 @@ class ProjectsController < ApplicationController
|
|||
before_action :check_experiment_archive_permissions,
|
||||
only: [:experiment_archive]
|
||||
|
||||
filter_by_archived = false
|
||||
@filter_by_archived = false
|
||||
|
||||
# except parameter could be used but it is not working.
|
||||
layout :choose_layout
|
||||
|
@ -23,10 +24,22 @@ class ProjectsController < ApplicationController
|
|||
DELETE_SAMPLES = I18n.t("samples.delete_samples")
|
||||
|
||||
def index
|
||||
@current_organization_id = params[:organization].to_i
|
||||
@current_sort = params[:sort].to_s
|
||||
@projects_by_orgs = current_user.projects_by_orgs(
|
||||
@current_organization_id, @current_sort, @filter_by_archived)
|
||||
if params[:organization]
|
||||
current_organization_switch(Organization
|
||||
.find_by_id(params[:organization]))
|
||||
end
|
||||
|
||||
if current_user.organizations.any?
|
||||
@current_organization_id = current_organization.id if current_organization
|
||||
|
||||
@current_organization_id ||= current_user.organizations.first.id
|
||||
@current_sort = params[:sort].to_s
|
||||
@projects_by_orgs = current_user
|
||||
.projects_by_orgs(@current_organization_id,
|
||||
@current_sort,
|
||||
@filter_by_archived)
|
||||
end
|
||||
|
||||
@organizations = current_user.organizations
|
||||
|
||||
# New project for create new project modal
|
||||
|
@ -40,14 +53,14 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def new
|
||||
@project = Project.new
|
||||
@organizations = current_user.organizations
|
||||
end
|
||||
|
||||
def create
|
||||
@project = Project.new(project_params)
|
||||
@project.created_by = current_user
|
||||
@project.created_by = current_user
|
||||
@project.last_modified_by = current_user
|
||||
if @project.save
|
||||
if current_organization.id == project_params[:organization_id].to_i &&
|
||||
@project.save
|
||||
# Create user-project association
|
||||
up = UserProject.new(
|
||||
role: :owner,
|
||||
|
@ -237,6 +250,8 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def show
|
||||
# This is the "info" view
|
||||
current_organization_switch(@project.organization)
|
||||
@current_sort = params[:sort].to_s
|
||||
end
|
||||
|
||||
def notifications
|
||||
|
@ -261,16 +276,22 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def experiment_archive
|
||||
current_organization_switch(@project.organization)
|
||||
end
|
||||
|
||||
def samples_index
|
||||
@organization = @project.organization
|
||||
|
||||
@user = current_user
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json {
|
||||
render json: ::SampleDatatable.new(view_context, @organization, @project, nil)
|
||||
}
|
||||
format.json do
|
||||
render json: ::SampleDatatable.new(view_context,
|
||||
@organization,
|
||||
@project,
|
||||
nil,
|
||||
nil,
|
||||
@user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -159,6 +159,10 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def update_keywords
|
||||
respond_to do |format|
|
||||
# sanitize user input
|
||||
params[:keywords].collect! do |keyword|
|
||||
ActionController::Base.helpers.sanitize(keyword)
|
||||
end
|
||||
if @protocol.update_keywords(params[:keywords])
|
||||
format.json {
|
||||
render json: {
|
||||
|
@ -373,7 +377,18 @@ class ProtocolsController < ApplicationController
|
|||
status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :revert_protocol,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.revert_protocol',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol.name
|
||||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_parent_flash",
|
||||
)
|
||||
|
@ -451,7 +466,18 @@ class ProtocolsController < ApplicationController
|
|||
status: :bad_request
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_repository,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_repository',
|
||||
user: current_user.full_name,
|
||||
protocol: @source.name
|
||||
)
|
||||
)
|
||||
flash[:success] = t('my_modules.protocols.load_from_repository_flash')
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
|
@ -485,9 +511,20 @@ class ProtocolsController < ApplicationController
|
|||
render json: { status: :error }, status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_file,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_file',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol_json[:name]
|
||||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.load_from_file_flash",
|
||||
'my_modules.protocols.load_from_file_flash',
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json {
|
||||
|
@ -777,9 +814,7 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def load_organization_and_type
|
||||
@organizations = current_user.organizations.order(name: :asc)
|
||||
@current_organization = @organizations.select{ |org| org.id == params[:organization].to_i }.first
|
||||
@current_organization ||= @organizations.first
|
||||
@current_organization = current_organization
|
||||
# :public, :private or :archive
|
||||
@type = (params[:type] || "public").to_sym
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class ReportsController < ApplicationController
|
||||
include OrganizationsHelper
|
||||
# Ignore CSRF protection just for PDF generation (because it's
|
||||
# used via target='_blank')
|
||||
protect_from_forgery with: :exception, :except => :generate
|
||||
|
@ -49,16 +50,6 @@ class ReportsController < ApplicationController
|
|||
|
||||
layout "fluid"
|
||||
|
||||
# Initialize markdown parser
|
||||
def load_markdown
|
||||
@markdown = Redcarpet::Markdown.new(
|
||||
Redcarpet::Render::HTML.new(
|
||||
filter_html: true,
|
||||
no_images: true
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
# Index showing all reports of a single project
|
||||
def index
|
||||
end
|
||||
|
@ -83,6 +74,17 @@ class ReportsController < ApplicationController
|
|||
@report.last_modified_by = current_user
|
||||
|
||||
if continue and @report.save_with_contents(report_contents)
|
||||
# record an activity
|
||||
Activity.create(
|
||||
type_of: :create_report,
|
||||
project: @report.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.create_report',
|
||||
user: current_user.full_name,
|
||||
report: @report.name
|
||||
)
|
||||
)
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: { url: project_reports_path(@project) }, status: :ok
|
||||
|
@ -99,9 +101,9 @@ class ReportsController < ApplicationController
|
|||
|
||||
def edit
|
||||
# cleans all the deleted report
|
||||
current_organization_switch(@report.project.organization)
|
||||
@report.cleanup_report
|
||||
load_markdown
|
||||
render "reports/new.html.erb"
|
||||
render 'reports/new.html.erb'
|
||||
end
|
||||
|
||||
# Updating existing report from the _save modal of the new page
|
||||
|
@ -117,6 +119,17 @@ class ReportsController < ApplicationController
|
|||
@report.assign_attributes(report_params)
|
||||
|
||||
if continue and @report.save_with_contents(report_contents)
|
||||
# record an activity
|
||||
Activity.create(
|
||||
type_of: :edit_report,
|
||||
project: @report.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.edit_report',
|
||||
user: current_user.full_name,
|
||||
report: @report.name
|
||||
)
|
||||
)
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: { url: project_reports_path(@project) }, status: :ok
|
||||
|
@ -147,6 +160,17 @@ class ReportsController < ApplicationController
|
|||
report = Report.find_by_id(report_id)
|
||||
|
||||
if report.present?
|
||||
# record an activity
|
||||
Activity.create(
|
||||
type_of: :delete_report,
|
||||
project: report.project,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.delete_report',
|
||||
user: current_user.full_name,
|
||||
report: report.name
|
||||
)
|
||||
)
|
||||
report.destroy
|
||||
end
|
||||
end
|
||||
|
@ -520,12 +544,11 @@ class ReportsController < ApplicationController
|
|||
end
|
||||
end
|
||||
if in_params? :module_result_texts then
|
||||
load_markdown
|
||||
(my_module.results.select { |r| r.is_text && r.active? }).each do |result_text|
|
||||
res << generate_new_el(false)
|
||||
el = generate_el(
|
||||
"reports/elements/result_text_element.html.erb",
|
||||
{ result: result_text, markdown: @markdown }
|
||||
result: result_text
|
||||
)
|
||||
el[:children] = generate_result_contents_json(result_text)
|
||||
res << el
|
||||
|
|
|
@ -2,7 +2,7 @@ class ResultCommentsController < ApplicationController
|
|||
before_action :load_vars
|
||||
|
||||
before_action :check_view_permissions, only: [:index]
|
||||
before_action :check_add_permissions, only: [:new, :create]
|
||||
before_action :check_add_permissions, only: [:create]
|
||||
before_action :check_edit_permissions, only: [:edit, :update]
|
||||
before_action :check_destroy_permissions, only: [:destroy]
|
||||
|
||||
|
@ -24,9 +24,9 @@ class ResultCommentsController < ApplicationController
|
|||
.first.id))
|
||||
end
|
||||
render json: {
|
||||
per_page: @per_page,
|
||||
results_number: @comments.length,
|
||||
more_url: more_url,
|
||||
perPage: @per_page,
|
||||
resultsNumber: @comments.length,
|
||||
moreUrl: more_url,
|
||||
html: render_to_string(partial: partial,
|
||||
locals: { comments: @comments,
|
||||
more_comments_url: more_url })
|
||||
|
@ -35,12 +35,6 @@ class ResultCommentsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@comment = Comment.new(
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
|
||||
def create
|
||||
@comment = Comment.new(
|
||||
message: comment_params[:message],
|
||||
|
@ -62,25 +56,20 @@ class ResultCommentsController < ApplicationController
|
|||
)
|
||||
)
|
||||
|
||||
format.html {
|
||||
flash[:success] = t(
|
||||
"result_comments.create.success_flash")
|
||||
redirect_to session.delete(:return_to)
|
||||
}
|
||||
format.json {
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
html: render_to_string(
|
||||
partial: "comment.html.erb",
|
||||
locals: {
|
||||
comment: @comment
|
||||
}
|
||||
})
|
||||
),
|
||||
date: @comment.created_at.strftime('%d.%m.%Y')
|
||||
},
|
||||
status: :created
|
||||
}
|
||||
else
|
||||
response.status = 400
|
||||
format.html { render :new }
|
||||
format.json {
|
||||
render json: {
|
||||
errors: @comment.errors.to_hash(true)
|
||||
|
@ -159,7 +148,7 @@ class ResultCommentsController < ApplicationController
|
|||
|
||||
def load_vars
|
||||
@last_comment_id = params[:from].to_i
|
||||
@per_page = 10
|
||||
@per_page = Constants::COMMENTS_SEARCH_LIMIT
|
||||
@result = Result.find_by_id(params[:result_id])
|
||||
@my_module = @result.my_module
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ class ResultTextsController < ApplicationController
|
|||
|
||||
before_action :load_vars, only: [:edit, :update, :download]
|
||||
before_action :load_vars_nested, only: [:new, :create]
|
||||
before_action :load_markdown, only: [ :create, :update ]
|
||||
|
||||
before_action :check_create_permissions, only: [:new, :create]
|
||||
before_action :check_edit_permissions, only: [:edit, :update]
|
||||
|
@ -63,8 +62,7 @@ class ResultTextsController < ApplicationController
|
|||
html: render_to_string({
|
||||
partial: "my_modules/result.html.erb",
|
||||
locals: {
|
||||
result: @result,
|
||||
markdown: @markdown
|
||||
result: @result
|
||||
}
|
||||
})
|
||||
}, status: :ok
|
||||
|
@ -142,8 +140,7 @@ class ResultTextsController < ApplicationController
|
|||
html: render_to_string({
|
||||
partial: "my_modules/result.html.erb",
|
||||
locals: {
|
||||
result: @result,
|
||||
markdown: @markdown
|
||||
result: @result
|
||||
}
|
||||
})
|
||||
}, status: :ok
|
||||
|
@ -182,16 +179,6 @@ class ResultTextsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# Initialize markdown parser
|
||||
def load_markdown
|
||||
@markdown = Redcarpet::Markdown.new(
|
||||
Redcarpet::Render::HTML.new(
|
||||
filter_html: true,
|
||||
no_images: true
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
unless can_create_result_text_in_module(@my_module)
|
||||
render_403
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
class SampleGroupsController < ApplicationController
|
||||
before_action :load_vars, only: [:edit, :update]
|
||||
before_action :load_vars_nested, only: [:new, :create]
|
||||
before_action :check_create_permissions, only: [:new, :create]
|
||||
before_action :check_edit_permissions, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
@sample_group = SampleGroup.new
|
||||
session[:return_to] ||= request.referer
|
||||
end
|
||||
before_action :load_vars_nested
|
||||
before_action :check_create_permissions
|
||||
before_action :set_sample_group, except: [:create, :index]
|
||||
before_action :set_project_my_module, only: :index
|
||||
layout 'fluid'
|
||||
|
||||
def create
|
||||
@sample_group = SampleGroup.new(sample_group_params)
|
||||
|
@ -17,75 +13,128 @@ class SampleGroupsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
if @sample_group.save
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
id: @sample_group.id,
|
||||
flash: t(
|
||||
'sample_groups.create.success_flash',
|
||||
sample_group: @sample_group.name,
|
||||
organization: @organization.name
|
||||
html: render_to_string(
|
||||
partial: 'sample_group.html.erb',
|
||||
locals: { sample_group: @sample_group,
|
||||
organization: @organization }
|
||||
)
|
||||
},
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json {
|
||||
format.json do
|
||||
render json: @sample_group.errors,
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
render_404 unless current_organization
|
||||
@sample_groups = current_organization.sample_groups
|
||||
end
|
||||
|
||||
def update
|
||||
@sample_group.update_attributes(sample_group_params)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @sample_group.save
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'sample_group.html.erb',
|
||||
locals: { sample_group: @sample_group,
|
||||
organization: @organization }
|
||||
)
|
||||
}
|
||||
else
|
||||
render json: @sample_group.errors,
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html:
|
||||
render_to_string(
|
||||
partial: 'edit.html.erb',
|
||||
locals: { sample_group: @sample_group,
|
||||
organization: @organization }
|
||||
),
|
||||
id: @sample_group.id
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@sample_group.last_modified_by = current_user
|
||||
if @sample_group.update_attributes(sample_group_params)
|
||||
flash[:success] = t(
|
||||
"sample_groups.update.success_flash",
|
||||
sample_group: @sample_group.name,
|
||||
organization: @organization.name)
|
||||
redirect_to (session.delete(:return_to) || root_path)
|
||||
else
|
||||
render :edit
|
||||
def sample_group_element
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'sample_group.html.erb',
|
||||
locals: { sample_group: @sample_group,
|
||||
organization: @organization }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_confirmation
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'delete_sample_group_modal.html.erb',
|
||||
locals: { sample_group: @sample_group,
|
||||
organization: @organization }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
flash[:success] = t 'sample_groups.index.destroy_flash',
|
||||
name: @sample_group.name
|
||||
Sample.where(sample_group: @sample_group).find_each do |sample|
|
||||
sample.update(sample_group_id: nil)
|
||||
end
|
||||
@sample_group.destroy
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@sample_group = SampleGroup.find_by_id(params[:id])
|
||||
@organization = @sample_group.organization
|
||||
def set_project_my_module
|
||||
@project = Project.find_by_id(params[:project_id]) if params[:project_id]
|
||||
@experiment = Experiment
|
||||
.find_by_id(params[:experiment_id]) if params[:experiment_id]
|
||||
@my_module = MyModule
|
||||
.find_by_id(params[:my_module_id]) if params[:my_module_id]
|
||||
render_403 unless @project || @my_module
|
||||
end
|
||||
|
||||
unless @sample_group
|
||||
render_404
|
||||
end
|
||||
def set_sample_group
|
||||
@sample_group = SampleGroup.find_by_id(params[:id])
|
||||
end
|
||||
|
||||
def load_vars_nested
|
||||
@organization = Organization.find_by_id(params[:organization_id])
|
||||
|
||||
unless @organization
|
||||
render_404
|
||||
end
|
||||
render_404 unless @organization
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
unless can_create_sample_type_in_organization(@organization)
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
||||
def check_edit_permissions
|
||||
unless can_edit_sample_type_in_organization(@organization)
|
||||
render_403
|
||||
end
|
||||
render_403 unless can_create_sample_type_in_organization(@organization)
|
||||
end
|
||||
|
||||
def sample_group_params
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue