Merge branch 'master' of https://github.com/biosistemika/scinote-web into office_integration

This commit is contained in:
zmagod 2017-01-03 16:35:25 +01:00
commit a434649f42
369 changed files with 9522 additions and 5877 deletions

34
.eslintrc.json Normal file
View 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
View file

@ -49,3 +49,6 @@ tags
# Ignore rubocop cache files
rubocop_cache
# Ignore ESLint local instalation
node_modules

View file

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

@ -0,0 +1,6 @@
{
"erb": {
"indent_size": 2,
"wrap_line_length": 80
}
}

View file

@ -3,6 +3,7 @@ AllCops:
- "vendor/**/*"
- "db/schema.rb"
UseCache: false
TargetRubyVersion: 2.2
##################### Style ####################################

258
.scss-lint.yml Normal file
View 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

View file

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

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

View file

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

View file

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

View file

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -61,7 +61,6 @@ function formAjaxResultAsset($form) {
applyCollapseLinkCallBack();
toggleResultEditButtons(true);
initResultCommentTabAjax();
expandResult($newResult);
$imgs = $newResult.find("img");
reloadImages($imgs);

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

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

View file

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

View file

@ -38,4 +38,4 @@ $("form#form-import")
// Populate the errors container
$("#import-errors-container").html(data.responseJSON.html);
}
});
});

View file

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

View file

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

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
@import "colors";
@import 'constants';
@import "mixins";
.tree {

View file

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

View file

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

View file

@ -1,4 +1,4 @@
@import "colors";
@import 'constants';
@import "mixins";
/** Custom CSS for report print (& PDF) */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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