├── .rspec ├── OSSMETADATA ├── app ├── assets │ ├── images │ │ └── workflowable │ │ │ ├── .keep │ │ │ ├── select2.png │ │ │ ├── select2x2.png │ │ │ └── select2-spinner.gif │ ├── stylesheets │ │ └── workflowable │ │ │ ├── workflow.css │ │ │ ├── dagre-d3.css │ │ │ ├── application.css │ │ │ ├── foundation │ │ │ └── normalize.css │ │ │ └── select2.css │ └── javascripts │ │ └── workflowable │ │ ├── workflow.js │ │ ├── foundation │ │ ├── placeholder.js │ │ ├── fastclick.js │ │ └── modernizr.js │ │ ├── application.js │ │ └── dagre-min.js ├── views │ ├── workflowable │ │ ├── workflows │ │ │ ├── edit.html.erb │ │ │ ├── new.html.erb │ │ │ ├── stages.json.jbuilder │ │ │ ├── index.html.erb │ │ │ ├── configure_stages.html.erb │ │ │ ├── show.html.erb │ │ │ └── _form.html.erb │ │ └── actions │ │ │ ├── options.js.erb │ │ │ └── _options.html.erb │ ├── layouts │ │ └── workflowable │ │ │ └── application.html.erb │ └── shared │ │ └── actions │ │ ├── _details.html.erb │ │ └── _fields.html.erb ├── helpers │ └── workflowable │ │ ├── workflow_helper.rb │ │ └── application_helper.rb ├── controllers │ └── workflowable │ │ ├── application_controller.rb │ │ ├── actions_controller.rb │ │ └── workflows_controller.rb └── models │ └── workflowable │ ├── workflow_action.rb │ ├── stage_action.rb │ ├── stage_next_step.rb │ ├── workflow.rb │ ├── action.rb │ └── stage.rb ├── AUTHORS ├── lib ├── tasks │ └── workflowable_tasks.rake ├── workflowable │ ├── version.rb │ ├── railtie.rb │ ├── engine.rb │ ├── actions │ │ └── action.rb │ └── model_additions.rb └── workflowable.rb ├── .gitignore ├── bin └── rails ├── Gemfile ├── db └── migrate │ ├── 20140708173311_add_order_to_action.rb │ ├── 20140818062426_add_position_to_stage_action.rb │ ├── 20140407184821_rename_workflow_initial_stage_to_initial_stage_id.rb │ ├── 20140406195941_create_workflowable_workflows.rb │ ├── 20140407170846_create_workflowable_stages.rb │ ├── 20140407194247_create_workflowable_actions.rb │ ├── 20140407191620_create_workflowable_stage_next_steps.rb │ ├── 20140408164900_create_workflowable_workflow_actions.rb │ └── 20140407193057_create_workflowable_stage_actions.rb ├── Rakefile ├── config └── routes.rb ├── README.md ├── workflowable.gemspec ├── Gemfile.lock └── LICENSE /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /app/assets/images/workflowable/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | - Andy Hoernecke -------------------------------------------------------------------------------- /app/views/workflowable/workflows/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit workflow

2 | <%= render 'form' %> 3 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/new.html.erb: -------------------------------------------------------------------------------- 1 |

New workflow

2 | <%= render 'form' %> 3 | -------------------------------------------------------------------------------- /app/assets/images/workflowable/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/Workflowable/HEAD/app/assets/images/workflowable/select2.png -------------------------------------------------------------------------------- /lib/tasks/workflowable_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :workflowable do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /app/assets/images/workflowable/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/Workflowable/HEAD/app/assets/images/workflowable/select2x2.png -------------------------------------------------------------------------------- /app/assets/images/workflowable/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/Workflowable/HEAD/app/assets/images/workflowable/select2-spinner.gif -------------------------------------------------------------------------------- /app/assets/stylesheets/workflowable/workflow.css: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | */ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/db/*.sqlite3-journal 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | test/dummy/.sass-cache 9 | spec/ 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/workflow.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/stages.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.nodes @workflow.stages, :id, :name 2 | 3 | 4 | 5 | next_steps=[] 6 | @workflow.stages.each{|s| next_steps += s.stage_next_steps} 7 | 8 | json.edges next_steps, :current_stage_id, :next_stage_id 9 | -------------------------------------------------------------------------------- /app/views/workflowable/actions/options.js.erb: -------------------------------------------------------------------------------- 1 | $('.action_options_fields.active').html("<%= escape_javascript(render :partial=>"options", :locals => {:options=>@options, context: params[:context]}) %>"); 2 | $('.action_options_fields.active').removeClass("active"); -------------------------------------------------------------------------------- /app/assets/stylesheets/workflowable/dagre-d3.css: -------------------------------------------------------------------------------- 1 | svg { 2 | overflow: hidden; 3 | } 4 | 5 | .node rect { 6 | stroke: #333; 7 | stroke-width: 1.5px; 8 | fill: #fff; 9 | } 10 | 11 | .edgeLabel rect { 12 | fill: #fff; 13 | } 14 | 15 | .edgePath { 16 | stroke: #333; 17 | stroke-width: 1.5px; 18 | fill: none; 19 | } -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/workflowable/engine', __FILE__) 6 | 7 | # Set up gems listed in the Gemfile. 8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 10 | 11 | require 'rails/all' 12 | require 'rails/engine/commands' 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in workflowable.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% @workflows.each do |workflow| %> 7 | 8 | 9 | 12 | 13 | <% end %> 14 |
Name
<%= link_to workflow.name, workflow %> 10 | <%= link_to 'Edit', edit_workflow_path(workflow), class: "button tiny" %> 11 | <%= link_to 'Destroy', workflow, method: :delete, data: { confirm: 'Are you sure?' }, class: "button tiny alert" %>
15 |
16 | <%= link_to 'New Workflow', new_workflow_path, class: "button" %> 17 | -------------------------------------------------------------------------------- /lib/workflowable/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | VERSION = "1.0.1" 18 | end 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/workflowable/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | 14 | 15 | */ 16 | 17 | 18 | div.field select 19 | { 20 | margin-bottom: 16px; 21 | } 22 | 23 | nav.top-bar 24 | { 25 | margin-bottom: 10px; 26 | } -------------------------------------------------------------------------------- /app/helpers/workflowable/workflow_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | module WorkflowHelper 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/helpers/workflowable/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | module ApplicationHelper 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/layouts/workflowable/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Workflowable 6 | <%= stylesheet_link_tag "workflowable/application", media: "all" %> 7 | <%= javascript_include_tag "workflowable/application" %> 8 | <%= csrf_meta_tags %> 9 | 10 | 11 | 18 |
19 |
20 | <%= yield %> 21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /app/controllers/workflowable/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | module Workflowable 16 | class ApplicationController < ActionController::Base 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20140708173311_add_order_to_action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class AddOrderToAction < ActiveRecord::Migration 17 | def change 18 | add_column :workflowable_actions, :position, :integer 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/workflowable/workflow_action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class WorkflowAction < ActiveRecord::Base 18 | belongs_to :workflow 19 | belongs_to :action 20 | 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20140818062426_add_position_to_stage_action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class AddPositionToStageAction < ActiveRecord::Migration 17 | def change 18 | add_column :workflowable_stage_actions, :position, :integer 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/workflowable/stage_action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class StageAction < ActiveRecord::Base 18 | belongs_to :stage 19 | belongs_to :action 20 | 21 | delegate :position, to: :action 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/workflowable/stage_next_step.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class StageNextStep < ActiveRecord::Base 18 | belongs_to :current_stage, :class_name=>'Stage' 19 | belongs_to :next_stage, :class_name=>'Stage' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/shared/actions/_details.html.erb: -------------------------------------------------------------------------------- 1 | <% if actions.count == 0 %> 2 | No actions defined 3 | <% else %> 4 | <% actions.each do |action| %> 5 |
<%= action.name %> 6 | (<%= action.action_plugin %>)
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% if action.options.present? %> 17 | <% action.options.each do |k,v| %> 18 | 19 | 20 | 21 | 22 | 23 | <% end %> 24 | <% else %> 25 | 26 | <% end %> 27 | 28 |
OptionValue SpecifiedDefault Value
<%= k %><%= v.try(:[],:value) %><%= v.try(:[],:default) %>
No options defined
29 | <% end %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /lib/workflowable.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | #require 'workflow/workflowable' 17 | require 'rails' 18 | require "workflowable/engine" 19 | require "workflowable/model_additions" 20 | require "workflowable/railtie" if defined? Rails 21 | 22 | module Workflowable 23 | 24 | 25 | 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20140407184821_rename_workflow_initial_stage_to_initial_stage_id.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class RenameWorkflowInitialStageToInitialStageId < ActiveRecord::Migration 17 | def change 18 | rename_column :workflowable_workflows, :initial_stage, :initial_stage_id 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/workflowable/railtie.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require 'workflowable/model_additions' 17 | 18 | module Workflowable 19 | class Railtie < Rails::Railtie 20 | if defined?(ActiveRecord::Base) 21 | ActiveRecord::Base.send :include, Workflowable::ModelAdditions 22 | end 23 | 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140406195941_create_workflowable_workflows.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableWorkflows < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_workflows do |t| 19 | t.string :name 20 | t.integer :initial_stage 21 | 22 | t.timestamps 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140407170846_create_workflowable_stages.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableStages < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_stages do |t| 19 | t.string :name 20 | t.references :workflow, index: true 21 | 22 | t.timestamps 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140407194247_create_workflowable_actions.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableActions < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_actions do |t| 19 | t.string :name 20 | t.text :options 21 | t.string :action_plugin 22 | 23 | t.timestamps 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20140407191620_create_workflowable_stage_next_steps.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableStageNextSteps < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_stage_next_steps do |t| 19 | t.integer :current_stage_id 20 | t.integer :next_stage_id 21 | 22 | t.timestamps 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140408164900_create_workflowable_workflow_actions.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableWorkflowActions < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_workflow_actions do |t| 19 | t.references :workflow, index: true 20 | t.references :action, index: true 21 | 22 | t.timestamps 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'Workflowable' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.rdoc') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 18 | load 'rails/tasks/engine.rake' 19 | 20 | 21 | 22 | Bundler::GemHelper.install_tasks 23 | 24 | #require 'rake/testtask' 25 | require 'rspec/core' 26 | require 'rspec/core/rake_task' 27 | 28 | # Rake::TestTask.new(:test) do |t| 29 | # t.libs << 'lib' 30 | # t.libs << 'test' 31 | # t.pattern = 'test/**/*_test.rb' 32 | # t.verbose = false 33 | # end 34 | 35 | desc "Run all specs in spec directory (excluding plugin specs)" 36 | RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare') 37 | 38 | 39 | task default: :spec 40 | -------------------------------------------------------------------------------- /db/migrate/20140407193057_create_workflowable_stage_actions.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | class CreateWorkflowableStageActions < ActiveRecord::Migration 17 | def change 18 | create_table :workflowable_stage_actions do |t| 19 | t.references :stage, index: true 20 | t.references :action, index: true 21 | t.string :event 22 | 23 | t.timestamps 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | Workflowable::Engine.routes.draw do 17 | resources :workflows do 18 | member do 19 | get 'stages' 20 | get 'configure_stages' 21 | post 'update_stages' 22 | end 23 | end 24 | 25 | resources :actions, only: [] do 26 | collection do 27 | get 'options' 28 | end 29 | end 30 | 31 | root to: "workflows#index" 32 | 33 | 34 | end 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Workflowable 3 | 4 | ## What is Workflowable? 5 | 6 | Workflowable is a Ruby gem that allows adding flexible workflow functionality to Ruby on Rails Applications. Workflowable provides an admin interface for configuring workflows, adding stages, triggering automatic actions, etc. 7 | 8 | An example of an application using Workflowable is [Scumblr](https://www.github.com/Netflix/Scumblr) 9 | 10 | ## How do I use Workflowable? 11 | 12 | Workflowable is installed as a gem as part of a Ruby on Rails web application. In order to use workflowable you'll need to: 13 | 14 | 1. Install/setup the gem 15 | 2. Run the database migrations 16 | 3. Add "acts_as_workflowable" to one more model you want associated with workflow 17 | 4. Setup your workflow 18 | 5. Optionally create automated actions that occur during certain states (notifications, ticket creation, external API calls, etc.) 19 | 20 | # Sounds great! How do I get started? 21 | 22 | Take a look at the [wiki](https://www.github.com/Netflix/Workflowable/wiki) for detailed instructions on setup, configuration, and use! 23 | 24 | -------------------------------------------------------------------------------- /app/controllers/workflowable/actions_controller.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require_dependency "workflowable/application_controller" 17 | 18 | module Workflowable 19 | class ActionsController < ApplicationController 20 | 21 | def options 22 | 23 | action = Action.new(action_plugin: params[:action_plugin]) 24 | @options = action.available_options 25 | 26 | respond_to do |format| 27 | format.json { render json: @options } 28 | format.js 29 | 30 | end 31 | end 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /app/views/shared/actions/_fields.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.fields_for association do |action_form| %> 2 |
3 | <%= label.humanize %> 4 | details - 5 | <%= action_form.link_to_remove "Remove" %> 6 |
7 | <%= action_form.label :action_name %> 8 | <%= action_form.text_field :name %> 9 | <%= action_form.label :position %> 10 | <%= action_form.text_field :position %> 11 |
12 |
13 | <%= action_form.label :action %> 14 | <%= action_form.collection_select :action_plugin, Workflowable::Actions.constants, :to_s, :to_s, {include_blank: true}, {class: "action_plugin_select"} %> 15 |
16 |
17 | <% available_options = action_form.object.available_options(action_form.object.options) %> 18 | <% if available_options %> 19 | <%= render :partial=>"workflowable/actions/options", :locals => {:options=>available_options , context: action_form.object_name } %> 20 | <% end %> 21 |
22 |
23 |
24 |
25 | <% end %> 26 |

<%= f.link_to_add "Add #{label}", association %>

27 | -------------------------------------------------------------------------------- /workflowable.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "workflowable/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "workflowable" 9 | s.version = Workflowable::VERSION 10 | s.authors = ["Andy Hoernecke"] 11 | s.email = ["ahoernecke@netflix.com"] 12 | s.homepage = "https://github.com/netflix/workflowable" 13 | s.summary = "Add worklow to your Rails application." 14 | s.description = "Flexible workflow gem." 15 | 16 | s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "AUTHOR", "Rakefile", "README.md"] 17 | s.add_dependency "rails", ">= 4.0" 18 | s.add_dependency "jquery-rails" 19 | s.add_dependency "jbuilder" 20 | s.add_dependency "nested_form" 21 | 22 | 23 | 24 | s.add_development_dependency "thor" 25 | s.add_development_dependency "rspec" 26 | s.add_development_dependency 'rspec-rails' 27 | s.add_development_dependency 'factory_girl_rails' 28 | s.add_development_dependency 'database_cleaner' 29 | s.add_development_dependency "guard-rspec", "~> 4.2.8" 30 | s.add_development_dependency 'shoulda' 31 | s.add_development_dependency 'shoulda-matchers' 32 | s.add_development_dependency "sqlite3" 33 | s.add_development_dependency "simplecov" 34 | 35 | 36 | end 37 | -------------------------------------------------------------------------------- /lib/workflowable/engine.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require 'rubygems' 17 | require 'rails' 18 | require 'jquery-rails' 19 | require 'jbuilder' 20 | require 'nested_form' 21 | 22 | 23 | module Workflowable 24 | class Engine < ::Rails::Engine 25 | isolate_namespace Workflowable 26 | 27 | 28 | config.to_prepare do 29 | Dir[File.dirname(__FILE__)+ "/actions/*.rb"].each {|file| require file } 30 | Dir[Rails.root + "lib/workflowable/actions/*.rb"].each {|file| require file } if Rails.root 31 | end 32 | 33 | 34 | config.generators do |g| 35 | g.test_framework :rspec 36 | g.fixture_replacement :factory_girl, :dir => 'spec/support/factories' 37 | end 38 | 39 | end 40 | end 41 | 42 | 43 | 44 | 45 | 46 | 47 | #ActiveRecord::Base.send :include, Workflowable::Workflow 48 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/configure_stages.html.erb: -------------------------------------------------------------------------------- 1 | <%= nested_form_for(@workflow) do |f| %> 2 | <% if @workflow.errors.any? %> 3 |
4 |

<%= pluralize(@workflow.errors.count, "error") %> 5 | prohibited this workflow from being saved:

6 | 11 |
12 | <% end %> 13 |

Workflow Options

14 |
15 | <%= f.label :initial_stage %> 16 | <%= f.collection_select :initial_stage_id, @workflow.stages, :id, :name, include_blank: true %> 17 |
18 |

Stage Transitions

19 | <%= f.fields_for :stages do |stage_form| %> 20 |
21 |

<%= stage_form.object.name %>

22 | <%= stage_form.fields_for :stage_next_steps do |next_steps_form| %> 23 |
24 | <%= next_steps_form.label :next_step %> 25 | <%= next_steps_form.collection_select :next_stage_id, @workflow.stages, :id, :name, include_blank: true %> 26 |
27 | <%= next_steps_form.link_to_remove "Remove this step" %> 28 | <% end %> 29 |

<%= stage_form.link_to_add "Add a next step", :stage_next_steps %>

30 |
31 | <% end %> 32 | <%= f.submit class: "button" %> 33 | <%= link_to 'Back', workflows_path, class: "button secondary" %> 34 | <% end %> 35 | -------------------------------------------------------------------------------- /app/views/workflowable/actions/_options.html.erb: -------------------------------------------------------------------------------- 1 | <%= fields_for "#{context}[options]", OpenStruct.new({}) do |options_form| %> 2 | <% options.each do |key, value| %> 3 |
4 |
5 |
6 | <%= label_tag do %> 7 | Specify Value: 8 | <%= value.try(:[],:name) || key.to_s %> 9 | <%= "(required)" if value[:required] == true %> 10 | <% end %> 11 | <% if value[:type] == :choice %> 12 | <%= options_form.select "#{key}][value", value[:choices].collect{|v| [v[:name],v.to_json]},{ include_blank: true, selected: value[:value].to_json} %> 13 | <% elsif value[:type] == :boolean %> 14 | <%= options_form.check_box "#{key}][value", :checked=>value[:value] == "1" %> 15 | <% else %> 16 | <%= options_form.text_field "#{key}][value", :value=>value[:value] %> 17 | <% end %> 18 |
19 |
20 |
21 | OR 22 |
23 |
24 | <%= label_tag do %> 25 | Default Value: 26 | <%= value.try(:[],:name) || key.to_s %> 27 | <% end %> 28 | <% if value[:type] == :choice %> 29 | <%= options_form.select "#{key}][default", value[:choices].collect{|v| [v[:name],v.to_json]},{ include_blank: true, selected: value[:default].to_json} %> 30 | <% elsif value[:type] == :boolean %> 31 | <%= options_form.check_box "#{key}][default", :checked=>value[:default] == "1" %> 32 | <% else %> 33 | <%= options_form.text_field "#{key}][default", :value=>value[:default] %> 34 | <% end %> 35 |
36 |
37 |
38 | <% end %> 39 | <% end %> 40 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/foundation/placeholder.js: -------------------------------------------------------------------------------- 1 | /*! http://mths.be/placeholder v2.0.7 by @mathias */ 2 | !function(a,b,c){function d(a){var b={},d=/^jQuery\d+$/;return c.each(a.attributes,function(a,c){c.specified&&!d.test(c.name)&&(b[c.name]=c.value)}),b}function e(a,d){var e=this,f=c(e);if(e.value==f.attr("placeholder")&&f.hasClass("placeholder"))if(f.data("placeholder-password")){if(f=f.hide().next().show().attr("id",f.removeAttr("id").data("placeholder-id")),a===!0)return f[0].value=d;f.focus()}else e.value="",f.removeClass("placeholder"),e==b.activeElement&&e.select()}function f(){var a,b=this,f=c(b),g=this.id;if(""==b.value){if("password"==b.type){if(!f.data("placeholder-textinput")){try{a=f.clone().attr({type:"text"})}catch(h){a=c("").attr(c.extend(d(this),{type:"text"}))}a.removeAttr("name").data({"placeholder-password":!0,"placeholder-id":g}).bind("focus.placeholder",e),f.data({"placeholder-textinput":a,"placeholder-id":g}).before(a)}f=f.removeAttr("id").hide().prev().attr("id",g).show()}f.addClass("placeholder"),f[0].value=f.attr("placeholder")}else f.removeClass("placeholder")}var g,h,i="placeholder"in b.createElement("input"),j="placeholder"in b.createElement("textarea"),k=c.fn,l=c.valHooks;i&&j?(h=k.placeholder=function(){return this},h.input=h.textarea=!0):(h=k.placeholder=function(){var a=this;return a.filter((i?"textarea":":input")+"[placeholder]").not(".placeholder").bind({"focus.placeholder":e,"blur.placeholder":f}).data("placeholder-enabled",!0).trigger("blur.placeholder"),a},h.input=i,h.textarea=j,g={get:function(a){var b=c(a);return b.data("placeholder-enabled")&&b.hasClass("placeholder")?"":a.value},set:function(a,d){var g=c(a);return g.data("placeholder-enabled")?(""==d?(a.value=d,a!=b.activeElement&&f.call(a)):g.hasClass("placeholder")?e.call(a,!0,d)||(a.value=d):a.value=d,g):a.value=d}},i||(l.input=g),j||(l.textarea=g),c(function(){c(b).delegate("form","submit.placeholder",function(){var a=c(".placeholder",this).each(e);setTimeout(function(){a.each(f)},10)})}),c(a).bind("beforeunload.placeholder",function(){c(".placeholder").each(function(){this.value=""})}))}(this,document,jQuery); 3 | -------------------------------------------------------------------------------- /app/models/workflowable/workflow.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class Workflow < ActiveRecord::Base 18 | has_many :stages, :inverse_of=>:workflow 19 | belongs_to :initial_stage, :class_name=>Stage 20 | has_many :workflow_actions, -> { order("position ASC") } 21 | has_many :actions, :through=>:workflow_actions 22 | accepts_nested_attributes_for :stages, :allow_destroy => true 23 | accepts_nested_attributes_for :actions, :allow_destroy => true 24 | 25 | 26 | 27 | validates :name, :presence=>true 28 | validates :name, :uniqueness=>true 29 | 30 | 31 | 32 | def run_workflow_actions(options={}, object, current_stage, next_stage, user) 33 | actions.each do |action| 34 | action.run(options.try(:[],action.name), self, object, current_stage, next_stage, user) 35 | end 36 | end 37 | 38 | def workflow_action_options(options={}, object, current_stage, next_stage, user) 39 | options ||= {} 40 | workflow_action_options = {} 41 | actions.each do |action| 42 | workflow_action_options[action.name] = action.available_options(options.try(:[],action.name) || {}, self, object, current_stage, next_stage, user) 43 | end 44 | workflow_action_options 45 | end 46 | 47 | def validate_action_options(options={}, object, current_stage, next_stage, user) 48 | options ||= {} 49 | action_errors = {} 50 | actions.each do |action| 51 | errors = action.validate_options(options[action.name] || {}, self, object, current_stage, next_stage, user) 52 | action_errors[action.name] = errors if errors.present? 53 | end 54 | action_errors 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/show.html.erb: -------------------------------------------------------------------------------- 1 |

Workflow: 2 | <%= @workflow.name %>

3 |
Global Actions
4 |
5 |
6 | ">Actions (<%= @workflow.actions.count %>) 7 |
" class="content"> 8 | <%= render partial: 'shared/actions/details', locals: {actions: @workflow.actions, uuid: "workflow_#{@workflow.id}_actions"} %> 9 |
10 |
11 |
12 | <% @workflow.stages.each do |stage| %> 13 |
14 |
15 | "><%= stage.name %> 16 |
" class="content"> 17 |
18 |
">Before Actions (<%= stage.before_actions.count %>)
19 |
">After Actions (<%= stage.after_actions.count %>)
20 |
">Next Steps (<%= stage.next_steps.count %>)
21 |
22 |
23 |
" class="content active"> 24 | <%= render partial: 'shared/actions/details', locals: {actions: stage.before_actions, uuid: "stage_#{stage.id}_before"} %> 25 |
26 |
" class="content"> 27 | <%= render partial: 'shared/actions/details', locals: {actions: stage.after_actions, uuid: "stage_#{stage.id}_after"} %> 28 |
29 |
" class="content"> 30 | <% if stage.next_steps.count == 0 %> 31 | No next steps defined 32 | <% else %> 33 | <% stage.next_steps.each do |step| %> 34 | <%= step.name %>
35 | <% end %> 36 | <% end %> 37 |
38 |
39 |
40 |
41 |
42 | <% end %> 43 |
44 | <%= link_to "Edit", edit_workflow_path(@workflow), class:"button" %> 45 | <%= link_to "Configure Stages", configure_stages_workflow_path(@workflow), class:"button" %> 46 | <%= link_to "Back", workflows_path , class:"button secondary"%> 47 | <%= content_tag :svg, class: "workflow_diagram", data: {url: stages_workflow_url(@workflow, format: :json)} do %> 48 | 49 | <% end %> 50 | -------------------------------------------------------------------------------- /app/views/workflowable/workflows/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= nested_form_for(@workflow) do |f| %> 2 | <% if @workflow.errors.any? %> 3 |
4 |

<%= pluralize(@workflow.errors.count, "error") %> 5 | prohibited this workflow from being saved:

6 |
    7 | <% @workflow.errors.full_messages.each do |msg| %> 8 |
  • <%= msg %>
  • 9 | <% end %> 10 |
11 |
12 | <% end %> 13 |
14 | 19 |
20 | 21 |
22 | Basic Details 23 |
24 | <%= f.label :name %> 25 | <%= f.text_field :name %> 26 |
27 |
28 |
29 | 30 |

Global Actions
These actions will be run every time the stage changes

31 | <%= render partial: 'shared/actions/fields', locals: {f: f, label: 'global action', association: :actions} %> 32 |
33 | 34 |

Stages
The available states for the workflow

35 | <%= f.fields_for :stages do |stage_form| %> 36 |
37 | Stage Details - 38 | <%= stage_form.link_to_remove "Remove this stage" %> 39 |
40 | <%= stage_form.label :name %> 41 | <%= stage_form.text_field :name %> 42 |
43 |
44 |
Before Actions
These actions will run before entering this stage
45 | <%= render partial: 'shared/actions/fields', locals: {f: stage_form, association: :before_actions, label: 'before action'} %> 46 |
47 |
After Actions
These actions will be run when leaving this stage
48 | <%= render partial: 'shared/actions/fields', locals: {f: stage_form, association: :after_actions, label: 'after action'} %> 49 |
50 |
51 |
52 | <% end %> 53 |
54 |

<%= f.link_to_add "Add a stage", :stages %>

55 | <%= f.submit class: "button" %> 56 | <%= link_to 'Back', workflows_path, class: "button secondary" %> 57 | <% end %> 58 | -------------------------------------------------------------------------------- /lib/workflowable/actions/action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | module Actions 18 | class Action 19 | NAME = "" 20 | OPTIONS = {} 21 | 22 | def initialize(options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 23 | options ||={} 24 | @options = options.with_indifferent_access 25 | @workflow = workflow 26 | @object = object 27 | @current_stage = current_stage 28 | @next_stage = next_stage 29 | @user = user 30 | 31 | errors = validate_options 32 | 33 | if(errors.present?) 34 | return errors 35 | end 36 | 37 | end 38 | 39 | def run 40 | 41 | end 42 | 43 | def self.autocomplete(field_type=nil, value=nil, options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 44 | end 45 | 46 | def self.name 47 | return self::NAME 48 | end 49 | 50 | def self.options(options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 51 | 52 | return self::OPTIONS.with_indifferent_access.deep_merge(options) 53 | end 54 | 55 | def validate_options 56 | errors = [] 57 | current_options = self.class.options(@options, @workflow, @object, @current_stage, @next_stage, @user) 58 | begin 59 | 60 | @options.assert_valid_keys(current_options.keys) 61 | rescue ArgumentError=>e 62 | errors << "#{e.message} for action: #{self.class.name}\n" 63 | end 64 | 65 | missing_keys = current_options.reject{|k,v| v[:required] != true }.keys - @options.reject{|k,v| v[:value].blank? }.keys 66 | 67 | missing_keys.each do |k| 68 | errors << "#{k} is required\n" 69 | end 70 | 71 | return errors.blank? ? nil : errors 72 | end 73 | 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require jquery_nested_form 16 | //= require_tree . 17 | 18 | 19 | 20 | 21 | $(function() { 22 | 23 | $(document).foundation(); 24 | 25 | $(".select2").select2(); 26 | 27 | $(document).on('nested:fieldAdded', function(event){ 28 | 29 | $(document).foundation(); 30 | $(".select2").select2(); 31 | 32 | // this field was just inserted into your form 33 | var field = event.field; 34 | 35 | 36 | $(".action_plugin_select").on("change", 37 | function(e) 38 | { 39 | options_field = $(this).closest("fieldset").find(".action_options_fields") 40 | options_field.addClass("active"); 41 | $.getScript(options_field.data('url') +'?action_plugin=' + this.value + "&context=" + options_field.data('context')) 42 | 43 | 44 | }); 45 | }); 46 | 47 | 48 | $(".action_plugin_select").on("change", 49 | function(e) 50 | { 51 | options_field = $(this).closest("fieldset").find(".action_options_fields") 52 | options_field.addClass("active"); 53 | $.getScript(options_field.data('url') +'?action_plugin=' + this.value + "&context=" + options_field.data('context')) 54 | 55 | 56 | 57 | } 58 | ); 59 | 60 | 61 | $(".workflow_diagram").each(function(obj) { 62 | $.getJSON( $(this).data('url'), function( data ) { 63 | 64 | 65 | // Create a new directed graph 66 | var g = new dagreD3.Digraph(); 67 | 68 | // Add nodes to the graph. The first argument is the node id. The second is 69 | // metadata about the node. In this case we're going to add labels to each of 70 | // our nodes. 71 | 72 | 73 | nodes = data.nodes 74 | 75 | for (var x in nodes) 76 | { 77 | 78 | g.addNode(nodes[x].id, { label: nodes[x].name }); 79 | } 80 | 81 | 82 | edges= data.edges 83 | 84 | for (var x in edges) 85 | { 86 | if(edges[x].current_stage_id != undefined && edges[x].next_stage_id != undefined ) 87 | { 88 | g.addEdge(null, edges[x].current_stage_id, edges[x].next_stage_id); 89 | } 90 | } 91 | 92 | 93 | 94 | 95 | var renderer = new dagreD3.Renderer(); 96 | 97 | var layout = dagreD3.layout() 98 | .nodeSep(20) 99 | .rankDir("LR"); 100 | 101 | 102 | layout = renderer.layout(layout).run(g, d3.select("svg")); 103 | 104 | var svg = d3.select("svg") 105 | .attr("width", layout.graph().width + 40) 106 | .attr("height", layout.graph().height + 40) 107 | .call(d3.behavior.zoom().on("zoom", function() { 108 | var ev = d3.event; 109 | svg.select("g") 110 | .attr("transform", "translate(" + ev.translate + ") scale(" + ev.scale + ")"); 111 | })); 112 | }); 113 | }); 114 | 115 | }); 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /app/models/workflowable/action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class Action < ActiveRecord::Base 18 | has_many :workflow_actions 19 | has_many :workflows, :through=>:workflow_actions 20 | has_many :stage_actions 21 | has_many :stages, :through=>:stage_actions 22 | validate :validate_action_plugin 23 | validates :name, :uniqueness=>true 24 | 25 | before_save :reject_blank_values 26 | 27 | 28 | serialize :options, Hash 29 | 30 | def reject_blank_values 31 | options.each{|k,v| v.reject!{ |k,v| v.blank? }} 32 | options.reject!{|k,v| v.blank?} 33 | end 34 | 35 | def validate_action_plugin 36 | begin 37 | if(Workflowable::Actions.constants.include?(action_plugin.to_sym)) 38 | #if(Workflowable::Actions::Action.subclasses.include?(("Workflowable::Actions::"+action_plugin).constantize)) 39 | return true 40 | end 41 | rescue 42 | end 43 | errors.add :action_plugin, "is invalid" 44 | end 45 | 46 | def autocomplete(field_type, value, options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 47 | ("Workflowable::Actions::"+action_plugin).constantize.autocomplete(field_type, value, options, workflow, object, current_stage, next_stage, user) 48 | end 49 | 50 | 51 | def run(options={}, workflow, object, current_stage, next_stage, user) 52 | options ||={} 53 | plugin = ("Workflowable::Actions::"+action_plugin).constantize#("Workflowable::Actions::" + self.action_plugin.to_s).constantize 54 | plugin.new(self.available_options(options), workflow, object, current_stage, next_stage, user).run 55 | 56 | end 57 | 58 | 59 | def validate_options(options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 60 | plugin = ("Workflowable::Actions::"+action_plugin).constantize#("Workflowable::Actions::" + self.action_plugin.to_s).constantize 61 | results = plugin.new(self.available_options(options), workflow, object, current_stage, next_stage, user).validate_options 62 | 63 | return results 64 | end 65 | 66 | 67 | def available_options(options={}, workflow=nil, object=nil, current_stage=nil, next_stage=nil, user=nil) 68 | options ||={} 69 | 70 | # value_options = options.with_indifferent_access.deep_dup.each{|k1,v1| 71 | # v1.reject!{|k2,v2| k2!= "value" || v2.blank?}; 72 | # v1[:user_specified]=true; 73 | # } 74 | # default_options = options.with_indifferent_access.deep_dup.each{ |k1,v1| 75 | # v1.reject!{|k2,v2| k2!= "default" || v2.blank? } 76 | # } 77 | 78 | 79 | 80 | value_options = options.with_indifferent_access.each{|k1,v1| 81 | v1.reject{|k2,v2| k2 != "value" || v2.blank?}; 82 | v1[:user_specified]=true; 83 | } 84 | 85 | 86 | 87 | default_options = options.with_indifferent_access.each{ |k1,v1| 88 | v1.reject{|k2,v2| k2 != "default" || v2.blank? } 89 | } 90 | 91 | options = self.options.with_indifferent_access.deep_merge(default_options) 92 | 93 | options = value_options.with_indifferent_access.deep_merge(options) 94 | if(action_plugin) 95 | all_options = ("Workflowable::Actions::"+action_plugin).constantize.options(options, workflow, object, current_stage, next_stage, user) 96 | else 97 | all_options = {} 98 | end 99 | 100 | 101 | end 102 | 103 | def action_plugin_options 104 | ("Workflowable::Actions::"+action_plugin).constantize.options 105 | end 106 | 107 | 108 | def action_plugin_name 109 | ("Workflowable::Actions::"+action_plugin).constantize.name 110 | end 111 | 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /app/controllers/workflowable/workflows_controller.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require_dependency "workflowable/application_controller" 17 | 18 | module Workflowable 19 | class WorkflowsController < ApplicationController 20 | before_filter :load_workflow, only: [:show, :edit, :update, :destroy, :stages, :configure_stages] 21 | def index 22 | @workflows = Workflowable::Workflow.order(:name) 23 | end 24 | 25 | def new 26 | @workflow = Workflow.new 27 | 28 | respond_to do |format| 29 | format.html # new.html.erb 30 | format.json { render json: @workflow } 31 | end 32 | end 33 | 34 | # GET /workflows/1/edit 35 | def edit 36 | 37 | end 38 | 39 | # POST /workflows 40 | # POST /workflows.json 41 | def create 42 | @workflow = Workflow.new(workflow_params) 43 | 44 | 45 | respond_to do |format| 46 | if @workflow.save 47 | format.html { 48 | if(@workflow.stages.count > 1) 49 | redirect_to configure_stages_workflow_path(@workflow), notice: 'Workflow was successfully created. Please configure transitions.' 50 | else 51 | redirect_to @workflow, notice: 'Workflow was successfully created.' 52 | end 53 | 54 | } 55 | format.json { render json: @workflow, status: :created, location: @workflow } 56 | else 57 | format.html { render action: "new" } 58 | format.json { render json: @workflow.errors, status: :unprocessable_entity } 59 | end 60 | end 61 | end 62 | 63 | # PUT /workflows/1 64 | # PUT /workflows/1.json 65 | def update 66 | 67 | 68 | respond_to do |format| 69 | if @workflow.update_attributes(workflow_params) 70 | format.html { redirect_to @workflow, notice: 'Workflow was successfully updated.' } 71 | format.json { head :no_content } 72 | else 73 | format.html { render action: "edit" } 74 | format.json { render json: @workflow.errors, status: :unprocessable_entity } 75 | end 76 | end 77 | end 78 | 79 | def show 80 | 81 | 82 | respond_to do |format| 83 | format.html # new.html.erb 84 | format.json { render json: @workflow } 85 | end 86 | 87 | end 88 | 89 | def stages 90 | 91 | end 92 | 93 | def configure_stages 94 | 95 | end 96 | 97 | 98 | # DELETE /workflows/1 99 | # DELETE /workflows/1.json 100 | def destroy 101 | @workflow.destroy 102 | 103 | respond_to do |format| 104 | format.html { redirect_to workflows_url } 105 | format.json { head :no_content } 106 | end 107 | end 108 | 109 | private 110 | 111 | 112 | def workflow_params 113 | 114 | #params.require(:search).permit(:name, :description, :provider, :query, :tag_list).merge(:options =>all_options) 115 | 116 | params.require(:workflow).permit! 117 | 118 | # params.require(:workflow).permit(:name, :workflow_id, 119 | # :actions_attributes=>[:id, :name, :action_plugin, :_destroy], 120 | # :stages_attributes=>[:id, :name, :_destroy, 121 | # :before_actions_attributes=>[:id, :name, :action_plugin, :_destroy], 122 | # :after_actions_attributes=>[:id, :name, :action_plugin, :_destroy], 123 | # :stage_next_steps_attributes=>[:next_stage_id, :id, :_destroy] 124 | 125 | # ] 126 | # ).tap do |whitelisted| 127 | # whitelisted[:actions_attributes] = params[:workflow].try(:[],:actions_attributes) 128 | # whitelisted[:stages_attributes] = params[:workflow].try(:[], :stages_attributes) 129 | # end 130 | end 131 | 132 | def load_workflow 133 | @workflow = Workflow.find(params[:id]) 134 | end 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /app/models/workflowable/stage.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module Workflowable 17 | class Stage < ActiveRecord::Base 18 | belongs_to :workflow, :inverse_of=>:stages 19 | has_many :stage_next_steps, :foreign_key=>:current_stage_id 20 | has_many :next_steps, 21 | :through=>:stage_next_steps, 22 | :class_name=>"Stage", 23 | :source=>:next_stage 24 | 25 | 26 | 27 | 28 | 29 | 30 | has_many :before_stage_actions, -> { where(event: 'before').order("position ASC") }, class_name: 'StageAction' 31 | has_many :after_stage_actions, -> { where(event: 'after').order("position ASC") }, class_name: 'StageAction' 32 | 33 | has_many :before_actions, :through=>:before_stage_actions, :class_name=>'Action', :source=>:action 34 | has_many :after_actions, :through=>:after_stage_actions, :class_name=>'Action', :source=>:action 35 | 36 | validates :name, :presence=>true 37 | validates :name, :uniqueness=>{:scope=> :workflow_id} 38 | 39 | #validates :workflow_id, :presence=>true 40 | validates_presence_of :workflow_id, :unless => lambda {|stage| stage.workflow.try(:valid?)} 41 | validates_associated :workflow 42 | 43 | accepts_nested_attributes_for :before_actions, :allow_destroy => true 44 | accepts_nested_attributes_for :after_actions, :allow_destroy => true 45 | accepts_nested_attributes_for :stage_next_steps, :allow_destroy => true 46 | 47 | 48 | 49 | 50 | 51 | def run_before_actions(options={}, object, current_stage, user) 52 | options ||= {} 53 | self.before_actions.each do |action| 54 | action.run(options[action.name], self.workflow, object, current_stage, self, user) 55 | end 56 | end 57 | 58 | def run_after_actions(options={}, object, next_stage, user) 59 | options ||= {} 60 | self.after_actions.each do |action| 61 | action.run(options[action.name], self.workflow, object, self, next_stage, user) 62 | end 63 | end 64 | 65 | def validate_after_actions(options={}, object, next_stage, user) 66 | options ||= {} 67 | after_action_errors = {} 68 | after_actions.each do |action| 69 | errors = action.validate_options(options[action.name] || {}, self.workflow, object, self, next_stage, user) 70 | after_action_errors[action.name] = errors if errors.present? 71 | end 72 | after_action_errors 73 | end 74 | 75 | def validate_before_actions(options={}, object, current_stage, user) 76 | options ||= {} 77 | before_action_errors = {} 78 | before_actions.each do |action| 79 | errors = action.validate_options(options[action.name] || {}, self.workflow, object, current_stage, self, user) 80 | before_action_errors[action.name] = errors if errors.present? 81 | end 82 | before_action_errors 83 | end 84 | 85 | def run_after_options(options={}, object, next_stage, user) 86 | options ||= {} 87 | after_action_options = {} 88 | after_actions.each do |action| 89 | after_action_options[action.name] = action.available_options(options[action.name] || {}, self.workflow, object, self, next_stage, user) 90 | end 91 | after_action_options 92 | end 93 | 94 | def run_before_options(options={}, object, current_stage, user) 95 | options ||= {} 96 | before_actions_options = {} 97 | before_actions.each do |action| 98 | before_actions_options [action.name] = action.available_options(options[action.name] || {}, self.workflow, object, current_stage, self, user) 99 | end 100 | before_actions_options 101 | end 102 | 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | workflowable (0.0.1) 5 | jbuilder 6 | jquery-rails 7 | nested_form 8 | rails (~> 4.0.4) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionmailer (4.0.4) 14 | actionpack (= 4.0.4) 15 | mail (~> 2.5.4) 16 | actionpack (4.0.4) 17 | activesupport (= 4.0.4) 18 | builder (~> 3.1.0) 19 | erubis (~> 2.7.0) 20 | rack (~> 1.5.2) 21 | rack-test (~> 0.6.2) 22 | activemodel (4.0.4) 23 | activesupport (= 4.0.4) 24 | builder (~> 3.1.0) 25 | activerecord (4.0.4) 26 | activemodel (= 4.0.4) 27 | activerecord-deprecated_finders (~> 1.0.2) 28 | activesupport (= 4.0.4) 29 | arel (~> 4.0.0) 30 | activerecord-deprecated_finders (1.0.3) 31 | activesupport (4.0.4) 32 | i18n (~> 0.6, >= 0.6.9) 33 | minitest (~> 4.2) 34 | multi_json (~> 1.3) 35 | thread_safe (~> 0.1) 36 | tzinfo (~> 0.3.37) 37 | arel (4.0.2) 38 | builder (3.1.4) 39 | celluloid (0.15.2) 40 | timers (~> 1.1.0) 41 | celluloid-io (0.15.0) 42 | celluloid (>= 0.15.0) 43 | nio4r (>= 0.5.0) 44 | coderay (1.1.0) 45 | database_cleaner (1.2.0) 46 | diff-lcs (1.2.5) 47 | docile (1.1.3) 48 | erubis (2.7.0) 49 | factory_girl (4.4.0) 50 | activesupport (>= 3.0.0) 51 | factory_girl_rails (4.4.1) 52 | factory_girl (~> 4.4.0) 53 | railties (>= 3.0.0) 54 | ffi (1.9.3) 55 | formatador (0.2.4) 56 | guard (2.6.0) 57 | formatador (>= 0.2.4) 58 | listen (~> 2.7) 59 | lumberjack (~> 1.0) 60 | pry (>= 0.9.12) 61 | thor (>= 0.18.1) 62 | guard-rspec (4.2.8) 63 | guard (~> 2.1) 64 | rspec (>= 2.14, < 4.0) 65 | hike (1.2.3) 66 | i18n (0.6.9) 67 | jbuilder (2.1.2) 68 | activesupport (>= 3.0.0, < 5) 69 | multi_json (~> 1.2) 70 | jquery-rails (3.1.1) 71 | railties (>= 3.0, < 5.0) 72 | thor (>= 0.14, < 2.0) 73 | listen (2.7.1) 74 | celluloid (>= 0.15.2) 75 | celluloid-io (>= 0.15.0) 76 | rb-fsevent (>= 0.9.3) 77 | rb-inotify (>= 0.9) 78 | lumberjack (1.0.5) 79 | mail (2.5.4) 80 | mime-types (~> 1.16) 81 | treetop (~> 1.4.8) 82 | method_source (0.8.2) 83 | mime-types (1.25.1) 84 | minitest (4.7.5) 85 | multi_json (1.9.2) 86 | nested_form (0.3.2) 87 | nio4r (1.0.0) 88 | polyglot (0.3.5) 89 | pry (0.9.12.6) 90 | coderay (~> 1.0) 91 | method_source (~> 0.8) 92 | slop (~> 3.4) 93 | rack (1.5.2) 94 | rack-test (0.6.2) 95 | rack (>= 1.0) 96 | rails (4.0.4) 97 | actionmailer (= 4.0.4) 98 | actionpack (= 4.0.4) 99 | activerecord (= 4.0.4) 100 | activesupport (= 4.0.4) 101 | bundler (>= 1.3.0, < 2.0) 102 | railties (= 4.0.4) 103 | sprockets-rails (~> 2.0.0) 104 | railties (4.0.4) 105 | actionpack (= 4.0.4) 106 | activesupport (= 4.0.4) 107 | rake (>= 0.8.7) 108 | thor (>= 0.18.1, < 2.0) 109 | rake (10.2.2) 110 | rb-fsevent (0.9.4) 111 | rb-inotify (0.9.3) 112 | ffi (>= 0.5.0) 113 | rspec (2.14.1) 114 | rspec-core (~> 2.14.0) 115 | rspec-expectations (~> 2.14.0) 116 | rspec-mocks (~> 2.14.0) 117 | rspec-core (2.14.8) 118 | rspec-expectations (2.14.5) 119 | diff-lcs (>= 1.1.3, < 2.0) 120 | rspec-mocks (2.14.6) 121 | rspec-rails (2.14.2) 122 | actionpack (>= 3.0) 123 | activemodel (>= 3.0) 124 | activesupport (>= 3.0) 125 | railties (>= 3.0) 126 | rspec-core (~> 2.14.0) 127 | rspec-expectations (~> 2.14.0) 128 | rspec-mocks (~> 2.14.0) 129 | shoulda (3.5.0) 130 | shoulda-context (~> 1.0, >= 1.0.1) 131 | shoulda-matchers (>= 1.4.1, < 3.0) 132 | shoulda-context (1.2.1) 133 | shoulda-matchers (2.5.0) 134 | activesupport (>= 3.0.0) 135 | simplecov (0.8.2) 136 | docile (~> 1.1.0) 137 | multi_json 138 | simplecov-html (~> 0.8.0) 139 | simplecov-html (0.8.0) 140 | slop (3.5.0) 141 | sprockets (2.12.0) 142 | hike (~> 1.2) 143 | multi_json (~> 1.0) 144 | rack (~> 1.0) 145 | tilt (~> 1.1, != 1.3.0) 146 | sprockets-rails (2.0.1) 147 | actionpack (>= 3.0) 148 | activesupport (>= 3.0) 149 | sprockets (~> 2.8) 150 | sqlite3 (1.3.9) 151 | thor (0.19.1) 152 | thread_safe (0.3.2) 153 | tilt (1.4.1) 154 | timers (1.1.0) 155 | treetop (1.4.15) 156 | polyglot 157 | polyglot (>= 0.3.1) 158 | tzinfo (0.3.39) 159 | 160 | PLATFORMS 161 | ruby 162 | 163 | DEPENDENCIES 164 | database_cleaner 165 | factory_girl_rails 166 | guard-rspec (~> 4.2.8) 167 | rspec 168 | rspec-rails 169 | shoulda 170 | shoulda-matchers 171 | simplecov 172 | sqlite3 173 | thor 174 | workflowable! 175 | -------------------------------------------------------------------------------- /lib/workflowable/model_additions.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Netflix, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require 'active_record/railtie' 17 | require 'active_support/core_ext' 18 | 19 | module Workflowable 20 | module ModelAdditions 21 | #extend ActiveSupport::Concern 22 | 23 | def self.included(base) 24 | base.extend ClassMethods 25 | end 26 | 27 | module ClassMethods 28 | def acts_as_workflowable 29 | include Workflowable::ModelAdditions::LocalInstanceMethods 30 | class_eval do 31 | attr_accessor :workflow_options 32 | attr_accessor :current_user 33 | 34 | belongs_to :workflow, :class_name => "Workflowable::Workflow" 35 | belongs_to :stage, :class_name => "Workflowable::Stage" 36 | before_save :set_default_stage 37 | 38 | end 39 | end 40 | end 41 | 42 | module LocalInstanceMethods 43 | 44 | 45 | 46 | 47 | def next_step_options(next_step, options={}, user=nil, unspecified_only=false) 48 | options ||= {} 49 | 50 | if(user.nil? && defined? current_user) 51 | user = current_user 52 | end 53 | 54 | 55 | 56 | if(stage) 57 | next_stage = stage.next_steps.find_by_id(next_step) 58 | else 59 | next_stage = workflow.stages.find_by_id(next_step) 60 | end 61 | 62 | if(next_stage) 63 | next_stage_options = {} 64 | next_stage_options[:global] = workflow.workflow_action_options(options[:global], self, self.stage, next_stage, user) 65 | next_stage_options[:before] = next_stage.run_before_options(options[:before], self, self.stage, user) 66 | next_stage_options[:after] = stage.run_after_options(options[:after], self, next_stage, user) if stage 67 | return next_stage_options 68 | 69 | else 70 | return nil 71 | end 72 | 73 | end 74 | 75 | 76 | 77 | def validate_actions(stage_id, options={}, user=nil) 78 | options ||= {} 79 | errors=[] 80 | 81 | if(user.nil? && defined? current_user) 82 | user = current_user 83 | end 84 | 85 | if(stage) 86 | new_stage = stage.next_steps.find_by_id(stage_id) 87 | else 88 | new_stage = workflow.stages.find_by_id(stage_id) 89 | end 90 | 91 | if(new_stage) 92 | workflow_errors = workflow.validate_action_options(options[:global], self, self.stage, new_stage, user) 93 | after_errors = stage.present? ? stage.validate_after_actions(options[:after], self, new_stage, user) : nil 94 | before_errors = new_stage.validate_before_actions(options[:before], self, self.stage, user) 95 | errors << workflow_errors if workflow_errors.present? 96 | errors << after_errors if after_errors.present? 97 | errors << before_errors if before_errors.present? 98 | 99 | if(errors.present?) 100 | return errors 101 | end 102 | return nil 103 | else 104 | return errors + ["Could not identify next stage"] 105 | end 106 | 107 | end 108 | 109 | def set_stage(stage_id, options={}, user=nil) 110 | options ||= {} 111 | errors=[] 112 | if(user.nil? && defined? current_user) 113 | user = current_user 114 | end 115 | 116 | errors = validate_actions(stage_id, options, user) 117 | 118 | if(errors.present?) 119 | return errors 120 | end 121 | 122 | if(stage) 123 | new_stage = stage.next_steps.find_by_id(stage_id) 124 | else 125 | new_stage = workflow.stages.find_by_id(stage_id) 126 | end 127 | 128 | if(new_stage) 129 | 130 | workflow.run_workflow_actions(options[:global], self, self.stage, new_stage, user) 131 | stage.run_after_actions(options[:after], self, new_stage, user) if stage 132 | new_stage.run_before_actions(options[:before], self, self.stage, user) 133 | update_attributes(stage_id: new_stage.id) 134 | return true 135 | end 136 | end 137 | 138 | 139 | 140 | private 141 | 142 | def set_default_stage 143 | self.set_stage(workflow.initial_stage_id, self.workflow_options) if (workflow && workflow.initial_stage_id && self.stage.blank?) 144 | #self.stage_id = workflow.initial_stage_id 145 | 146 | end 147 | 148 | 149 | 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/foundation/fastclick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. 3 | * 4 | * @version 1.0.0 5 | * @codingstandard ftlabs-jsv2 6 | * @copyright The Financial Times Limited [All Rights Reserved] 7 | * @license MIT License (see LICENSE.txt) 8 | */ 9 | function FastClick(a){"use strict";function b(a,b){return function(){return a.apply(b,arguments)}}var c;this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.touchBoundary=10,this.layer=a,FastClick.notNeeded(a)||(deviceIsAndroid&&(a.addEventListener("mouseover",b(this.onMouse,this),!0),a.addEventListener("mousedown",b(this.onMouse,this),!0),a.addEventListener("mouseup",b(this.onMouse,this),!0)),a.addEventListener("click",b(this.onClick,this),!0),a.addEventListener("touchstart",b(this.onTouchStart,this),!1),a.addEventListener("touchmove",b(this.onTouchMove,this),!1),a.addEventListener("touchend",b(this.onTouchEnd,this),!1),a.addEventListener("touchcancel",b(this.onTouchCancel,this),!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(c=a.onclick,a.addEventListener("click",function(a){c(a)},!1),a.onclick=null))}var deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),deviceIsIOS4=deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),deviceIsIOSWithBadTarget=deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent);FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"select":case"textarea":if(a.disabled)return!0;break;case"input":if(deviceIsIOS&&"file"===a.type||a.disabled)return!0;break;case"label":case"video":return!0}return/\bneedsclick\b/.test(a.className)},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":return!0;case"select":return!deviceIsAndroid;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent(this.determineEventType(a),!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.determineEventType=function(a){"use strict";return deviceIsAndroid&&"select"===a.tagName.toLowerCase()?"mousedown":"click"},FastClick.prototype.focus=function(a){"use strict";var b;deviceIsIOS&&a.setSelectionRange&&0!==a.type.indexOf("date")&&"time"!==a.type?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(a.targetTouches.length>1)return!0;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0],c=this.touchBoundary;return Math.abs(b.pageX-this.touchStartX)>c||Math.abs(b.pageY-this.touchStartY)>c?!0:!1},FastClick.prototype.onTouchMove=function(a){"use strict";return this.trackingClick?((this.targetElement!==this.getTargetElementFromEventTarget(a.target)||this.touchHasMoved(a))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.cancelNextClick=!1,this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,deviceIsIOSWithBadTarget&&(f=a.changedTouches[0],g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)||g,g.fastClickScrollParent=this.targetElement.fastClickScrollParent),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.sendClick(g,a),deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return deviceIsIOS&&!deviceIsIOS4&&(e=g.fastClickScrollParent,e&&e.fastClickLastScrollTop!==e.scrollTop)?!0:(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchmove",this.onTouchMove,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.notNeeded=function(a){"use strict";var b,c;if("undefined"==typeof window.ontouchstart)return!0;if(c=+(/Chrome\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1]){if(!deviceIsAndroid)return!0;if(b=document.querySelector("meta[name=viewport]")){if(-1!==b.content.indexOf("user-scalable=no"))return!0;if(c>31&&window.innerWidth<=window.screen.width)return!0}}return"none"===a.style.msTouchAction?!0:!1},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd?define(function(){"use strict";return FastClick}):"undefined"!=typeof module&&module.exports?(module.exports=FastClick.attach,module.exports.FastClick=FastClick):window.FastClick=FastClick; 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/workflowable/foundation/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.0 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined in IE 8/9. 28 | */ 29 | 30 | article, 31 | aside, 32 | details, 33 | figcaption, 34 | figure, 35 | footer, 36 | header, 37 | hgroup, 38 | main, 39 | nav, 40 | section, 41 | summary { 42 | display: block; 43 | } 44 | 45 | /** 46 | * 1. Correct `inline-block` display not defined in IE 8/9. 47 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; /* 1 */ 55 | vertical-align: baseline; /* 2 */ 56 | } 57 | 58 | /** 59 | * Prevent modern browsers from displaying `audio` without controls. 60 | * Remove excess height in iOS 5 devices. 61 | */ 62 | 63 | audio:not([controls]) { 64 | display: none; 65 | height: 0; 66 | } 67 | 68 | /** 69 | * Address `[hidden]` styling not present in IE 8/9. 70 | * Hide the `template` element in IE, Safari, and Firefox < 22. 71 | */ 72 | 73 | [hidden], 74 | template { 75 | display: none; 76 | } 77 | 78 | /* Links 79 | ========================================================================== */ 80 | 81 | /** 82 | * Remove the gray background color from active links in IE 10. 83 | */ 84 | 85 | a { 86 | background: transparent; 87 | } 88 | 89 | /** 90 | * Improve readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* Text-level semantics 99 | ========================================================================== */ 100 | 101 | /** 102 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 103 | */ 104 | 105 | abbr[title] { 106 | border-bottom: 1px dotted; 107 | } 108 | 109 | /** 110 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 111 | */ 112 | 113 | b, 114 | strong { 115 | font-weight: bold; 116 | } 117 | 118 | /** 119 | * Address styling not present in Safari 5 and Chrome. 120 | */ 121 | 122 | dfn { 123 | font-style: italic; 124 | } 125 | 126 | /** 127 | * Address variable `h1` font-size and margin within `section` and `article` 128 | * contexts in Firefox 4+, Safari 5, and Chrome. 129 | */ 130 | 131 | h1 { 132 | font-size: 2em; 133 | margin: 0.67em 0; 134 | } 135 | 136 | /** 137 | * Address styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | /** 146 | * Address inconsistent and variable font size in all browsers. 147 | */ 148 | 149 | small { 150 | font-size: 80%; 151 | } 152 | 153 | /** 154 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 155 | */ 156 | 157 | sub, 158 | sup { 159 | font-size: 75%; 160 | line-height: 0; 161 | position: relative; 162 | vertical-align: baseline; 163 | } 164 | 165 | sup { 166 | top: -0.5em; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | /* Embedded content 174 | ========================================================================== */ 175 | 176 | /** 177 | * Remove border when inside `a` element in IE 8/9. 178 | */ 179 | 180 | img { 181 | border: 0; 182 | } 183 | 184 | /** 185 | * Correct overflow displayed oddly in IE 9. 186 | */ 187 | 188 | svg:not(:root) { 189 | overflow: hidden; 190 | } 191 | 192 | /* Grouping content 193 | ========================================================================== */ 194 | 195 | /** 196 | * Address margin not present in IE 8/9 and Safari 5. 197 | */ 198 | 199 | figure { 200 | margin: 1em 40px; 201 | } 202 | 203 | /** 204 | * Address differences between Firefox and other browsers. 205 | */ 206 | 207 | hr { 208 | -moz-box-sizing: content-box; 209 | box-sizing: content-box; 210 | height: 0; 211 | } 212 | 213 | /** 214 | * Contain overflow in all browsers. 215 | */ 216 | 217 | pre { 218 | overflow: auto; 219 | } 220 | 221 | /** 222 | * Address odd `em`-unit font size rendering in all browsers. 223 | */ 224 | 225 | code, 226 | kbd, 227 | pre, 228 | samp { 229 | font-family: monospace, monospace; 230 | font-size: 1em; 231 | } 232 | 233 | /* Forms 234 | ========================================================================== */ 235 | 236 | /** 237 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 238 | * styling of `select`, unless a `border` property is set. 239 | */ 240 | 241 | /** 242 | * 1. Correct color not being inherited. 243 | * Known issue: affects color of disabled elements. 244 | * 2. Correct font properties not being inherited. 245 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 246 | */ 247 | 248 | button, 249 | input, 250 | optgroup, 251 | select, 252 | textarea { 253 | color: inherit; /* 1 */ 254 | font: inherit; /* 2 */ 255 | margin: 0; /* 3 */ 256 | } 257 | 258 | /** 259 | * Address `overflow` set to `hidden` in IE 8/9/10. 260 | */ 261 | 262 | button { 263 | overflow: visible; 264 | } 265 | 266 | /** 267 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 268 | * All other form control elements do not inherit `text-transform` values. 269 | * Correct `button` style inheritance in Firefox, IE 8+, and Opera 270 | * Correct `select` style inheritance in Firefox. 271 | */ 272 | 273 | button, 274 | select { 275 | text-transform: none; 276 | } 277 | 278 | /** 279 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 280 | * and `video` controls. 281 | * 2. Correct inability to style clickable `input` types in iOS. 282 | * 3. Improve usability and consistency of cursor style between image-type 283 | * `input` and others. 284 | */ 285 | 286 | button, 287 | html input[type="button"], /* 1 */ 288 | input[type="reset"], 289 | input[type="submit"] { 290 | -webkit-appearance: button; /* 2 */ 291 | cursor: pointer; /* 3 */ 292 | } 293 | 294 | /** 295 | * Re-set default cursor for disabled elements. 296 | */ 297 | 298 | button[disabled], 299 | html input[disabled] { 300 | cursor: default; 301 | } 302 | 303 | /** 304 | * Remove inner padding and border in Firefox 4+. 305 | */ 306 | 307 | button::-moz-focus-inner, 308 | input::-moz-focus-inner { 309 | border: 0; 310 | padding: 0; 311 | } 312 | 313 | /** 314 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 315 | * the UA stylesheet. 316 | */ 317 | 318 | input { 319 | line-height: normal; 320 | } 321 | 322 | /** 323 | * It's recommended that you don't attempt to style these elements. 324 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 325 | * 326 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 327 | * 2. Remove excess padding in IE 8/9/10. 328 | */ 329 | 330 | input[type="checkbox"], 331 | input[type="radio"] { 332 | box-sizing: border-box; /* 1 */ 333 | padding: 0; /* 2 */ 334 | } 335 | 336 | /** 337 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 338 | * `font-size` values of the `input`, it causes the cursor style of the 339 | * decrement button to change from `default` to `text`. 340 | */ 341 | 342 | input[type="number"]::-webkit-inner-spin-button, 343 | input[type="number"]::-webkit-outer-spin-button { 344 | height: auto; 345 | } 346 | 347 | /** 348 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 349 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 350 | * (include `-moz` to future-proof). 351 | */ 352 | 353 | input[type="search"] { 354 | -webkit-appearance: textfield; /* 1 */ 355 | -moz-box-sizing: content-box; 356 | -webkit-box-sizing: content-box; /* 2 */ 357 | box-sizing: content-box; 358 | } 359 | 360 | /** 361 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 362 | * Safari (but not Chrome) clips the cancel button when the search input has 363 | * padding (and `textfield` appearance). 364 | */ 365 | 366 | input[type="search"]::-webkit-search-cancel-button, 367 | input[type="search"]::-webkit-search-decoration { 368 | -webkit-appearance: none; 369 | } 370 | 371 | /** 372 | * Define consistent border, margin, and padding. 373 | */ 374 | 375 | fieldset { 376 | border: 1px solid #c0c0c0; 377 | margin: 0 2px; 378 | padding: 0.35em 0.625em 0.75em; 379 | } 380 | 381 | /** 382 | * 1. Correct `color` not being inherited in IE 8/9. 383 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 384 | */ 385 | 386 | legend { 387 | border: 0; /* 1 */ 388 | padding: 0; /* 2 */ 389 | } 390 | 391 | /** 392 | * Remove default vertical scrollbar in IE 8/9. 393 | */ 394 | 395 | textarea { 396 | overflow: auto; 397 | } 398 | 399 | /** 400 | * Don't inherit the `font-weight` (applied by a rule above). 401 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 402 | */ 403 | 404 | optgroup { 405 | font-weight: bold; 406 | } 407 | 408 | /* Tables 409 | ========================================================================== */ 410 | 411 | /** 412 | * Remove most spacing between table cells. 413 | */ 414 | 415 | table { 416 | border-collapse: collapse; 417 | border-spacing: 0; 418 | } 419 | 420 | td, 421 | th { 422 | padding: 0; 423 | } 424 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/foundation/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modernizr v2.7.2 3 | * www.modernizr.com 4 | * 5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton 6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/ 7 | */ 8 | window.Modernizr=function(a,b,c){function d(a){t.cssText=a}function e(a,b){return d(x.join(a+";")+(b||""))}function f(a,b){return typeof a===b}function g(a,b){return!!~(""+a).indexOf(b)}function h(a,b){for(var d in a){var e=a[d];if(!g(e,"-")&&t[e]!==c)return"pfx"==b?e:!0}return!1}function i(a,b,d){for(var e in a){var g=b[a[e]];if(g!==c)return d===!1?a[e]:f(g,"function")?g.bind(d||b):g}return!1}function j(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+z.join(d+" ")+d).split(" ");return f(b,"string")||f(b,"undefined")?h(e,b):(e=(a+" "+A.join(d+" ")+d).split(" "),i(e,b,c))}function k(){o.input=function(c){for(var d=0,e=c.length;e>d;d++)E[c[d]]=!!(c[d]in u);return E.list&&(E.list=!(!b.createElement("datalist")||!a.HTMLDataListElement)),E}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),o.inputtypes=function(a){for(var d,e,f,g=0,h=a.length;h>g;g++)u.setAttribute("type",e=a[g]),d="text"!==u.type,d&&(u.value=v,u.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&u.style.WebkitAppearance!==c?(q.appendChild(u),f=b.defaultView,d=f.getComputedStyle&&"textfield"!==f.getComputedStyle(u,null).WebkitAppearance&&0!==u.offsetHeight,q.removeChild(u)):/^(search|tel)$/.test(e)||(d=/^(url|email)$/.test(e)?u.checkValidity&&u.checkValidity()===!1:u.value!=v)),D[a[g]]=!!d;return D}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var l,m,n="2.7.2",o={},p=!0,q=b.documentElement,r="modernizr",s=b.createElement(r),t=s.style,u=b.createElement("input"),v=":)",w={}.toString,x=" -webkit- -moz- -o- -ms- ".split(" "),y="Webkit Moz O ms",z=y.split(" "),A=y.toLowerCase().split(" "),B={svg:"http://www.w3.org/2000/svg"},C={},D={},E={},F=[],G=F.slice,H=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:r+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=r,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=q.style.overflow,q.style.overflow="hidden",q.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),q.style.overflow=i),!!g},I=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return H("@media "+b+" { #"+r+" { position: absolute; } }",function(b){d="absolute"==(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position}),d},J=function(){function a(a,e){e=e||b.createElement(d[a]||"div"),a="on"+a;var g=a in e;return g||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(a,""),g=f(e[a],"function"),f(e[a],"undefined")||(e[a]=c),e.removeAttribute(a))),e=null,g}var d={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return a}(),K={}.hasOwnProperty;m=f(K,"undefined")||f(K.call,"undefined")?function(a,b){return b in a&&f(a.constructor.prototype[b],"undefined")}:function(a,b){return K.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=G.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(G.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(G.call(arguments)))};return d}),C.flexbox=function(){return j("flexWrap")},C.flexboxlegacy=function(){return j("boxDirection")},C.canvas=function(){var a=b.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))},C.canvastext=function(){return!(!o.canvas||!f(b.createElement("canvas").getContext("2d").fillText,"function"))},C.webgl=function(){return!!a.WebGLRenderingContext},C.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:H(["@media (",x.join("touch-enabled),("),r,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=9===a.offsetTop}),c},C.geolocation=function(){return"geolocation"in navigator},C.postmessage=function(){return!!a.postMessage},C.websqldatabase=function(){return!!a.openDatabase},C.indexedDB=function(){return!!j("indexedDB",a)},C.hashchange=function(){return J("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},C.history=function(){return!(!a.history||!history.pushState)},C.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},C.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},C.rgba=function(){return d("background-color:rgba(150,255,150,.5)"),g(t.backgroundColor,"rgba")},C.hsla=function(){return d("background-color:hsla(120,40%,100%,.5)"),g(t.backgroundColor,"rgba")||g(t.backgroundColor,"hsla")},C.multiplebgs=function(){return d("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(t.background)},C.backgroundsize=function(){return j("backgroundSize")},C.borderimage=function(){return j("borderImage")},C.borderradius=function(){return j("borderRadius")},C.boxshadow=function(){return j("boxShadow")},C.textshadow=function(){return""===b.createElement("div").style.textShadow},C.opacity=function(){return e("opacity:.55"),/^0.55$/.test(t.opacity)},C.cssanimations=function(){return j("animationName")},C.csscolumns=function(){return j("columnCount")},C.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return d((a+"-webkit- ".split(" ").join(b+a)+x.join(c+a)).slice(0,-a.length)),g(t.backgroundImage,"gradient")},C.cssreflections=function(){return j("boxReflect")},C.csstransforms=function(){return!!j("transform")},C.csstransforms3d=function(){var a=!!j("perspective");return a&&"webkitPerspective"in q.style&&H("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},C.csstransitions=function(){return j("transition")},C.fontface=function(){var a;return H('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&0===g.indexOf(d.split(" ")[0])}),a},C.generatedcontent=function(){var a;return H(["#",r,"{font:0/0 a}#",r,':after{content:"',v,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},C.video=function(){var a=b.createElement("video"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""))}catch(d){}return c},C.audio=function(){var a=b.createElement("audio"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(d){}return c},C.localstorage=function(){try{return localStorage.setItem(r,r),localStorage.removeItem(r),!0}catch(a){return!1}},C.sessionstorage=function(){try{return sessionStorage.setItem(r,r),sessionStorage.removeItem(r),!0}catch(a){return!1}},C.webworkers=function(){return!!a.Worker},C.applicationcache=function(){return!!a.applicationCache},C.svg=function(){return!!b.createElementNS&&!!b.createElementNS(B.svg,"svg").createSVGRect},C.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==B.svg},C.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(w.call(b.createElementNS(B.svg,"animate")))},C.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(w.call(b.createElementNS(B.svg,"clipPath")))};for(var L in C)m(C,L)&&(l=L.toLowerCase(),o[l]=C[L](),F.push((o[l]?"":"no-")+l));return o.input||k(),o.addTest=function(a,b){if("object"==typeof a)for(var d in a)m(a,d)&&o.addTest(d,a[d]);else{if(a=a.toLowerCase(),o[a]!==c)return o;b="function"==typeof b?b():b,"undefined"!=typeof p&&p&&(q.className+=" "+(b?"":"no-")+a),o[a]=b}return o},d(""),s=u=null,function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=s.elements;return"string"==typeof a?a.split(" "):a}function e(a){var b=r[a[p]];return b||(b={},q++,a[p]=q,r[q]=b),b}function f(a,c,d){if(c||(c=b),k)return c.createElement(a);d||(d=e(c));var f;return f=d.cache[a]?d.cache[a].cloneNode():o.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!f.canHaveChildren||n.test(a)||f.tagUrn?f:d.frag.appendChild(f)}function g(a,c){if(a||(a=b),k)return a.createDocumentFragment();c=c||e(a);for(var f=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)f.createElement(h[g]);return f}function h(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?f(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function i(a){a||(a=b);var d=e(a);return!s.shivCSS||j||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||h(a,d),a}var j,k,l="3.7.0",m=a.html5||{},n=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,o=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,p="_html5shiv",q=0,r={};!function(){try{var a=b.createElement("a");a.innerHTML="",j="hidden"in a,k=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){j=!0,k=!0}}();var s={elements:m.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:l,shivCSS:m.shivCSS!==!1,supportsUnknownElements:k,shivMethods:m.shivMethods!==!1,type:"default",shivDocument:i,createElement:f,createDocumentFragment:g};a.html5=s,i(b)}(this,b),o._version=n,o._prefixes=x,o._domPrefixes=A,o._cssomPrefixes=z,o.mq=I,o.hasEvent=J,o.testProp=function(a){return h([a])},o.testAllProps=j,o.testStyles=H,o.prefixed=function(a,b,c){return b?j(a,b,c):j(a,"pfx")},q.className=q.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(p?" js "+F.join(" "):""),o}(this,this.document); 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Netflix, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /app/assets/stylesheets/workflowable/select2.css: -------------------------------------------------------------------------------- 1 | /* 2 | Version: 3.4.6 Timestamp: Sat Mar 22 22:30:15 EDT 2014 3 | */ 4 | .select2-container { 5 | margin: 0; 6 | position: relative; 7 | display: inline-block; 8 | /* inline-block for ie7 */ 9 | zoom: 1; 10 | *display: inline; 11 | vertical-align: middle; 12 | } 13 | 14 | .select2-container, 15 | .select2-drop, 16 | .select2-search, 17 | .select2-search input { 18 | /* 19 | Force border-box so that % widths fit the parent 20 | container without overlap because of margin/padding. 21 | More Info : http://www.quirksmode.org/css/box.html 22 | */ 23 | -webkit-box-sizing: border-box; /* webkit */ 24 | -moz-box-sizing: border-box; /* firefox */ 25 | box-sizing: border-box; /* css3 */ 26 | } 27 | 28 | .select2-container .select2-choice { 29 | display: block; 30 | height: 26px; 31 | padding: 0 0 0 8px; 32 | overflow: hidden; 33 | position: relative; 34 | 35 | border: 1px solid #aaa; 36 | white-space: nowrap; 37 | line-height: 26px; 38 | color: #444; 39 | text-decoration: none; 40 | 41 | border-radius: 4px; 42 | 43 | background-clip: padding-box; 44 | 45 | -webkit-touch-callout: none; 46 | -webkit-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | 51 | background-color: #fff; 52 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); 53 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); 54 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); 55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); 56 | background-image: linear-gradient(to top, #eee 0%, #fff 50%); 57 | } 58 | 59 | .select2-container.select2-drop-above .select2-choice { 60 | border-bottom-color: #aaa; 61 | 62 | border-radius: 0 0 4px 4px; 63 | 64 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); 65 | background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); 66 | background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); 67 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); 68 | background-image: linear-gradient(to bottom, #eee 0%, #fff 90%); 69 | } 70 | 71 | .select2-container.select2-allowclear .select2-choice .select2-chosen { 72 | margin-right: 42px; 73 | } 74 | 75 | .select2-container .select2-choice > .select2-chosen { 76 | margin-right: 26px; 77 | display: block; 78 | overflow: hidden; 79 | 80 | white-space: nowrap; 81 | 82 | text-overflow: ellipsis; 83 | float: none; 84 | width: auto; 85 | } 86 | 87 | .select2-container .select2-choice abbr { 88 | display: none; 89 | width: 12px; 90 | height: 12px; 91 | position: absolute; 92 | right: 24px; 93 | top: 8px; 94 | 95 | font-size: 1px; 96 | text-decoration: none; 97 | 98 | border: 0; 99 | background: url('select2.png') right top no-repeat; 100 | cursor: pointer; 101 | outline: 0; 102 | } 103 | 104 | .select2-container.select2-allowclear .select2-choice abbr { 105 | display: inline-block; 106 | } 107 | 108 | .select2-container .select2-choice abbr:hover { 109 | background-position: right -11px; 110 | cursor: pointer; 111 | } 112 | 113 | .select2-drop-mask { 114 | border: 0; 115 | margin: 0; 116 | padding: 0; 117 | position: fixed; 118 | left: 0; 119 | top: 0; 120 | min-height: 100%; 121 | min-width: 100%; 122 | height: auto; 123 | width: auto; 124 | opacity: 0; 125 | z-index: 9998; 126 | /* styles required for IE to work */ 127 | background-color: #fff; 128 | filter: alpha(opacity=0); 129 | } 130 | 131 | .select2-drop { 132 | width: 100%; 133 | margin-top: -1px; 134 | position: absolute; 135 | z-index: 9999; 136 | top: 100%; 137 | 138 | background: #fff; 139 | color: #000; 140 | border: 1px solid #aaa; 141 | border-top: 0; 142 | 143 | border-radius: 0 0 4px 4px; 144 | 145 | -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 146 | box-shadow: 0 4px 5px rgba(0, 0, 0, .15); 147 | } 148 | 149 | .select2-drop.select2-drop-above { 150 | margin-top: 1px; 151 | border-top: 1px solid #aaa; 152 | border-bottom: 0; 153 | 154 | border-radius: 4px 4px 0 0; 155 | 156 | -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 157 | box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); 158 | } 159 | 160 | .select2-drop-active { 161 | border: 1px solid #5897fb; 162 | border-top: none; 163 | } 164 | 165 | .select2-drop.select2-drop-above.select2-drop-active { 166 | border-top: 1px solid #5897fb; 167 | } 168 | 169 | .select2-drop-auto-width { 170 | border-top: 1px solid #aaa; 171 | width: auto; 172 | } 173 | 174 | .select2-drop-auto-width .select2-search { 175 | padding-top: 4px; 176 | } 177 | 178 | .select2-container .select2-choice .select2-arrow { 179 | display: inline-block; 180 | width: 18px; 181 | height: 100%; 182 | position: absolute; 183 | right: 0; 184 | top: 0; 185 | 186 | border-left: 1px solid #aaa; 187 | border-radius: 0 4px 4px 0; 188 | 189 | background-clip: padding-box; 190 | 191 | background: #ccc; 192 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); 193 | background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); 194 | background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); 195 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); 196 | background-image: linear-gradient(to top, #ccc 0%, #eee 60%); 197 | } 198 | 199 | .select2-container .select2-choice .select2-arrow b { 200 | display: block; 201 | width: 100%; 202 | height: 100%; 203 | background: url('select2.png') no-repeat 0 1px; 204 | } 205 | 206 | .select2-search { 207 | display: inline-block; 208 | width: 100%; 209 | min-height: 26px; 210 | margin: 0; 211 | padding-left: 4px; 212 | padding-right: 4px; 213 | 214 | position: relative; 215 | z-index: 10000; 216 | 217 | white-space: nowrap; 218 | } 219 | 220 | .select2-search input { 221 | width: 100%; 222 | height: auto !important; 223 | min-height: 26px; 224 | padding: 4px 20px 4px 5px; 225 | margin: 0; 226 | 227 | outline: 0; 228 | font-family: sans-serif; 229 | font-size: 1em; 230 | 231 | border: 1px solid #aaa; 232 | border-radius: 0; 233 | 234 | -webkit-box-shadow: none; 235 | box-shadow: none; 236 | 237 | background: #fff url('select2.png') no-repeat 100% -22px; 238 | background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 239 | background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 240 | background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 241 | background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 242 | } 243 | 244 | .select2-drop.select2-drop-above .select2-search input { 245 | margin-top: 4px; 246 | } 247 | 248 | .select2-search input.select2-active { 249 | background: #fff url('select2-spinner.gif') no-repeat 100%; 250 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); 251 | background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); 252 | background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); 253 | background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; 254 | } 255 | 256 | .select2-container-active .select2-choice, 257 | .select2-container-active .select2-choices { 258 | border: 1px solid #5897fb; 259 | outline: none; 260 | 261 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 262 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 263 | } 264 | 265 | .select2-dropdown-open .select2-choice { 266 | border-bottom-color: transparent; 267 | -webkit-box-shadow: 0 1px 0 #fff inset; 268 | box-shadow: 0 1px 0 #fff inset; 269 | 270 | border-bottom-left-radius: 0; 271 | border-bottom-right-radius: 0; 272 | 273 | background-color: #eee; 274 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); 275 | background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); 276 | background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); 277 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 278 | background-image: linear-gradient(to top, #fff 0%, #eee 50%); 279 | } 280 | 281 | .select2-dropdown-open.select2-drop-above .select2-choice, 282 | .select2-dropdown-open.select2-drop-above .select2-choices { 283 | border: 1px solid #5897fb; 284 | border-top-color: transparent; 285 | 286 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); 287 | background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); 288 | background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); 289 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); 290 | background-image: linear-gradient(to bottom, #fff 0%, #eee 50%); 291 | } 292 | 293 | .select2-dropdown-open .select2-choice .select2-arrow { 294 | background: transparent; 295 | border-left: none; 296 | filter: none; 297 | } 298 | .select2-dropdown-open .select2-choice .select2-arrow b { 299 | background-position: -18px 1px; 300 | } 301 | 302 | .select2-hidden-accessible { 303 | border: 0; 304 | clip: rect(0 0 0 0); 305 | height: 1px; 306 | margin: -1px; 307 | overflow: hidden; 308 | padding: 0; 309 | position: absolute; 310 | width: 1px; 311 | } 312 | 313 | /* results */ 314 | .select2-results { 315 | max-height: 200px; 316 | padding: 0 0 0 4px; 317 | margin: 4px 4px 4px 0; 318 | position: relative; 319 | overflow-x: hidden; 320 | overflow-y: auto; 321 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 322 | } 323 | 324 | .select2-results ul.select2-result-sub { 325 | margin: 0; 326 | padding-left: 0; 327 | } 328 | 329 | .select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px } 330 | .select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px } 331 | .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px } 332 | .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px } 333 | .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px } 334 | .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px } 335 | .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px } 336 | 337 | .select2-results li { 338 | list-style: none; 339 | display: list-item; 340 | background-image: none; 341 | } 342 | 343 | .select2-results li.select2-result-with-children > .select2-result-label { 344 | font-weight: bold; 345 | } 346 | 347 | .select2-results .select2-result-label { 348 | padding: 3px 7px 4px; 349 | margin: 0; 350 | cursor: pointer; 351 | 352 | min-height: 1em; 353 | 354 | -webkit-touch-callout: none; 355 | -webkit-user-select: none; 356 | -moz-user-select: none; 357 | -ms-user-select: none; 358 | user-select: none; 359 | } 360 | 361 | .select2-results .select2-highlighted { 362 | background: #3875d7; 363 | color: #fff; 364 | } 365 | 366 | .select2-results li em { 367 | background: #feffde; 368 | font-style: normal; 369 | } 370 | 371 | .select2-results .select2-highlighted em { 372 | background: transparent; 373 | } 374 | 375 | .select2-results .select2-highlighted ul { 376 | background: #fff; 377 | color: #000; 378 | } 379 | 380 | 381 | .select2-results .select2-no-results, 382 | .select2-results .select2-searching, 383 | .select2-results .select2-selection-limit { 384 | background: #f4f4f4; 385 | display: list-item; 386 | padding-left: 5px; 387 | } 388 | 389 | /* 390 | disabled look for disabled choices in the results dropdown 391 | */ 392 | .select2-results .select2-disabled.select2-highlighted { 393 | color: #666; 394 | background: #f4f4f4; 395 | display: list-item; 396 | cursor: default; 397 | } 398 | .select2-results .select2-disabled { 399 | background: #f4f4f4; 400 | display: list-item; 401 | cursor: default; 402 | } 403 | 404 | .select2-results .select2-selected { 405 | display: none; 406 | } 407 | 408 | .select2-more-results.select2-active { 409 | background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; 410 | } 411 | 412 | .select2-more-results { 413 | background: #f4f4f4; 414 | display: list-item; 415 | } 416 | 417 | /* disabled styles */ 418 | 419 | .select2-container.select2-container-disabled .select2-choice { 420 | background-color: #f4f4f4; 421 | background-image: none; 422 | border: 1px solid #ddd; 423 | cursor: default; 424 | } 425 | 426 | .select2-container.select2-container-disabled .select2-choice .select2-arrow { 427 | background-color: #f4f4f4; 428 | background-image: none; 429 | border-left: 0; 430 | } 431 | 432 | .select2-container.select2-container-disabled .select2-choice abbr { 433 | display: none; 434 | } 435 | 436 | 437 | /* multiselect */ 438 | 439 | .select2-container-multi .select2-choices { 440 | height: auto !important; 441 | height: 1%; 442 | margin: 0; 443 | padding: 0; 444 | position: relative; 445 | 446 | border: 1px solid #aaa; 447 | cursor: text; 448 | overflow: hidden; 449 | 450 | background-color: #fff; 451 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); 452 | background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); 453 | background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); 454 | background-image: linear-gradient(to bottom, #eee 1%, #fff 15%); 455 | } 456 | 457 | .select2-locked { 458 | padding: 3px 5px 3px 5px !important; 459 | } 460 | 461 | .select2-container-multi .select2-choices { 462 | min-height: 26px; 463 | } 464 | 465 | .select2-container-multi.select2-container-active .select2-choices { 466 | border: 1px solid #5897fb; 467 | outline: none; 468 | 469 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); 470 | box-shadow: 0 0 5px rgba(0, 0, 0, .3); 471 | } 472 | .select2-container-multi .select2-choices li { 473 | float: left; 474 | list-style: none; 475 | } 476 | html[dir="rtl"] .select2-container-multi .select2-choices li 477 | { 478 | float: right; 479 | } 480 | .select2-container-multi .select2-choices .select2-search-field { 481 | margin: 0; 482 | padding: 0; 483 | white-space: nowrap; 484 | } 485 | 486 | .select2-container-multi .select2-choices .select2-search-field input { 487 | padding: 5px; 488 | margin: 1px 0; 489 | 490 | font-family: sans-serif; 491 | font-size: 100%; 492 | color: #666; 493 | outline: 0; 494 | border: 0; 495 | -webkit-box-shadow: none; 496 | box-shadow: none; 497 | background: transparent !important; 498 | } 499 | 500 | .select2-container-multi .select2-choices .select2-search-field input.select2-active { 501 | background: #fff url('select2-spinner.gif') no-repeat 100% !important; 502 | } 503 | 504 | .select2-default { 505 | color: #999 !important; 506 | } 507 | 508 | .select2-container-multi .select2-choices .select2-search-choice { 509 | padding: 3px 5px 3px 18px; 510 | margin: 3px 0 3px 5px; 511 | position: relative; 512 | 513 | line-height: 13px; 514 | color: #333; 515 | cursor: default; 516 | border: 1px solid #aaaaaa; 517 | 518 | border-radius: 3px; 519 | 520 | -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 521 | box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); 522 | 523 | background-clip: padding-box; 524 | 525 | -webkit-touch-callout: none; 526 | -webkit-user-select: none; 527 | -moz-user-select: none; 528 | -ms-user-select: none; 529 | user-select: none; 530 | 531 | background-color: #e4e4e4; 532 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); 533 | background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); 534 | background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 535 | background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 536 | background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); 537 | } 538 | html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice 539 | { 540 | margin-left: 0; 541 | margin-right: 5px; 542 | } 543 | .select2-container-multi .select2-choices .select2-search-choice .select2-chosen { 544 | cursor: default; 545 | } 546 | .select2-container-multi .select2-choices .select2-search-choice-focus { 547 | background: #d4d4d4; 548 | } 549 | 550 | .select2-search-choice-close { 551 | display: block; 552 | width: 12px; 553 | height: 13px; 554 | position: absolute; 555 | right: 3px; 556 | top: 4px; 557 | 558 | font-size: 1px; 559 | outline: none; 560 | background: url('select2.png') right top no-repeat; 561 | } 562 | html[dir="rtl"] .select2-search-choice-close { 563 | right: auto; 564 | left: 3px; 565 | } 566 | 567 | .select2-container-multi .select2-search-choice-close { 568 | left: 3px; 569 | } 570 | 571 | .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { 572 | background-position: right -11px; 573 | } 574 | .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { 575 | background-position: right -11px; 576 | } 577 | 578 | /* disabled styles */ 579 | .select2-container-multi.select2-container-disabled .select2-choices { 580 | background-color: #f4f4f4; 581 | background-image: none; 582 | border: 1px solid #ddd; 583 | cursor: default; 584 | } 585 | 586 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { 587 | padding: 3px 5px 3px 5px; 588 | border: 1px solid #ddd; 589 | background-image: none; 590 | background-color: #f4f4f4; 591 | } 592 | 593 | .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; 594 | background: none; 595 | } 596 | /* end multiselect */ 597 | 598 | 599 | .select2-result-selectable .select2-match, 600 | .select2-result-unselectable .select2-match { 601 | text-decoration: underline; 602 | } 603 | 604 | .select2-offscreen, .select2-offscreen:focus { 605 | clip: rect(0 0 0 0) !important; 606 | width: 1px !important; 607 | height: 1px !important; 608 | border: 0 !important; 609 | margin: 0 !important; 610 | padding: 0 !important; 611 | overflow: hidden !important; 612 | position: absolute !important; 613 | outline: 0 !important; 614 | left: 0px !important; 615 | top: 0px !important; 616 | } 617 | 618 | .select2-display-none { 619 | display: none; 620 | } 621 | 622 | .select2-measure-scrollbar { 623 | position: absolute; 624 | top: -10000px; 625 | left: -10000px; 626 | width: 100px; 627 | height: 100px; 628 | overflow: scroll; 629 | } 630 | 631 | /* Retina-ize icons */ 632 | 633 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) { 634 | .select2-search input, 635 | .select2-search-choice-close, 636 | .select2-container .select2-choice abbr, 637 | .select2-container .select2-choice .select2-arrow b { 638 | background-image: url('select2x2.png') !important; 639 | background-repeat: no-repeat !important; 640 | background-size: 60px 40px !important; 641 | } 642 | 643 | .select2-search input { 644 | background-position: 100% -21px !important; 645 | } 646 | } 647 | -------------------------------------------------------------------------------- /app/assets/javascripts/workflowable/dagre-min.js: -------------------------------------------------------------------------------- 1 | (function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var j=typeof require=="function"&&require;if(!h&&j)return j(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}var f=typeof require=="function"&&require;for(var g=0;gMath.abs(e)*h?(f<0&&(h=-h),i=f===0?0:h*e/f,j=h):(e<0&&(g=-g),i=g,j=e===0?0:g*f/e),{x:c+i,y:d+j}}function z(a,b){return"children"in a&&a.children(b).length}function A(a,b){return a.bind?a.bind(b):function(){return a.apply(b,arguments)}}var d=a("dagre").layout,e;try{e=a("d3")}catch(f){e=window.d3}b.exports=g,g.prototype.layout=function(a){return arguments.length?(this._layout=a,this):this._layout},g.prototype.drawNodes=function(a){return arguments.length?(this._drawNodes=A(a,this),this):this._drawNodes},g.prototype.drawEdgeLabels=function(a){return arguments.length?(this._drawEdgeLabels=A(a,this),this):this._drawEdgeLabels},g.prototype.drawEdgePaths=function(a){return arguments.length?(this._drawEdgePaths=A(a,this),this):this._drawEdgePaths},g.prototype.positionNodes=function(a){return arguments.length?(this._positionNodes=A(a,this),this):this._positionNodes},g.prototype.positionEdgeLabels=function(a){return arguments.length?(this._positionEdgeLabels=A(a,this),this):this._positionEdgeLabels},g.prototype.positionEdgePaths=function(a){return arguments.length?(this._positionEdgePaths=A(a,this),this):this._positionEdgePaths},g.prototype.transition=function(a){return arguments.length?(this._transition=A(a,this),this):this._transition},g.prototype.postLayout=function(a){return arguments.length?(this._postLayout=A(a,this),this):this._postLayout},g.prototype.postRender=function(a){return arguments.length?(this._postRender=A(a,this),this):this._postRender},g.prototype.edgeInterpolate=function(a){return arguments.length?(this._edgeInterpolate=a,this):this._edgeInterpolate},g.prototype.edgeTension=function(a){return arguments.length?(this._edgeTension=a,this):this._edgeTension},g.prototype.run=function(a,b){a=h(a),b.selectAll("g.edgePaths, g.edgeLabels, g.nodes").data(["edgePaths","edgeLabels","nodes"]).enter().append("g").attr("class",function(a){return a});var c=this._drawNodes(a,b.select("g.nodes")),d=this._drawEdgeLabels(a,b.select("g.edgeLabels"));c.each(function(b){i(this,a.node(b))}),d.each(function(b){i(this,a.edge(b))});var e=j(a,this._layout);this._postLayout(e,b);var f=this._drawEdgePaths(a,b.select("g.edgePaths"));return this._positionNodes(e,c),this._positionEdgeLabels(e,d),this._positionEdgePaths(e,f),this._postRender(e,b),e};var m=function(a,b){var c=b.selectAll("g.edgePath").classed("enter",!1).data(a.edges(),function(a){return a});return c.enter().append("g").attr("class","edgePath enter").append("path").style("opacity",0).attr("marker-end","url(#arrowhead)"),this._transition(c.exit()).style("opacity",0).remove(),c}},{d3:10,dagre:11}],4:[function(a,b,c){b.exports="0.1.5"},{}],5:[function(a,b,c){c.Set=a("./lib/Set"),c.PriorityQueue=a("./lib/PriorityQueue"),c.version=a("./lib/version")},{"./lib/PriorityQueue":6,"./lib/Set":7,"./lib/version":9}],6:[function(a,b,c){function d(){this._arr=[],this._keyIndices={}}b.exports=d,d.prototype.size=function(){return this._arr.length},d.prototype.keys=function(){return this._arr.map(function(a){return a.key})},d.prototype.has=function(a){return a in this._keyIndices},d.prototype.priority=function(a){var b=this._keyIndices[a];if(b!==undefined)return this._arr[b].priority},d.prototype.min=function(){if(this.size()===0)throw new Error("Queue underflow");return this._arr[0].key},d.prototype.add=function(a,b){var c=this._keyIndices;if(a in c)return!1;var d=this._arr,e=d.length;return c[a]=e,d.push({key:a,priority:b}),this._decrease(e),!0},d.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var a=this._arr.pop();return delete this._keyIndices[a.key],this._heapify(0),a.key},d.prototype.decrease=function(a,b){var c=this._keyIndices[a];if(b>this._arr[c].priority)throw new Error("New priority is greater than current priority. Key: "+a+" Old: "+this._arr[c].priority+" New: "+b);this._arr[c].priority=b,this._decrease(c)},d.prototype._heapify=function(a){var b=this._arr,c=2*a,d=c+1,e=a;c>1;if(b[d].priority>>0,g=!1;1d;++d)a.hasOwnProperty(d)&&(g?e=b(e,a[d],d,a):(e=a[d],g=!0));if(!g)throw new TypeError("Reduce of empty array with no initial value");return e}:c.reduce=function(a,b,c){return a.reduce(b,c)}},{}],9:[function(a,b,c){b.exports="1.1.3"},{}],10:[function(a,b,c){a("./d3"),b.exports=d3,function(){delete this.d3}()},{}],11:[function(a,b,c){c.Digraph=a("graphlib").Digraph,c.Graph=a("graphlib").Graph,c.layout=a("./lib/layout"),c.version=a("./lib/version")},{"./lib/layout":12,"./lib/version":27,graphlib:28}],12:[function(a,b,c){var d=a("./util"),e=a("./rank"),f=a("./order"),g=a("graphlib").CGraph,h=a("graphlib").CDigraph;b.exports=function(){function j(a){var c=new h;a.eachNode(function(a,b){b===undefined&&(b={}),c.addNode(a,{width:b.width,height:b.height}),b.hasOwnProperty("rank")&&(c.node(a).prefRank=b.rank)}),a.parent&&a.nodes().forEach(function(b){c.parent(b,a.parent(b))}),a.eachEdge(function(a,b,d,e){e===undefined&&(e={});var f={e:a,minLen:e.minLen||1,width:e.width||0,height:e.height||0,points:[]};c.addEdge(null,b,d,f)});var d=a.graph()||{};return c.graph({rankDir:d.rankDir||b.rankDir,orderRestarts:d.orderRestarts}),c}function k(a){var g=i.rankSep(),h;try{return h=d.time("initLayoutGraph",j)(a),h.order()===0?h:(h.eachEdge(function(a,b,c,d){d.minLen*=2}),i.rankSep(g/2),d.time("rank.run",e.run)(h,b.rankSimplex),d.time("normalize",l)(h),d.time("order",f)(h,b.orderMaxSweeps),d.time("position",c.run)(h),d.time("undoNormalize",m)(h),d.time("fixupEdgePoints",n)(h),d.time("rank.restoreEdges",e.restoreEdges)(h),d.time("createFinalGraph",o)(h,a.isDirected()))}finally{i.rankSep(g)}}function l(a){var b=0;a.eachEdge(function(c,d,e,f){var g=a.node(d).rank,h=a.node(e).rank;if(g+10),d.log(2,"Order phase start cross count: "+a.graph().orderInitCC);var q,r,s;for(q=0,r=0;r<4&&q0;++q,++r,++i)n(a,h,q),s=e(a),s=0;--i)h(b[i],c,m(a,b[i].nodes()))}var d=a("./util"),e=a("./order/crossCount"),f=a("./order/initLayerGraphs"),g=a("./order/initOrder"),h=a("./order/sortLayer");b.exports=k;var j=24;k.DEFAULT_MAX_SWEEPS=j},{"./order/crossCount":14,"./order/initLayerGraphs":15,"./order/initOrder":16,"./order/sortLayer":17,"./util":26}],14:[function(a,b,c){function e(a){var b=0,c=d.ordering(a);for(var e=1;e0)b%2&&(i+=g[b+1]),b=b-1>>1,++g[b]}),i}var d=a("../util");b.exports=e},{"../util":26}],15:[function(a,b,c){function f(a){function c(d){if(d===null){a.children(d).forEach(function(a){c(a)});return}var f=a.node(d);f.minRank="rank"in f?f.rank:Number.MAX_VALUE,f.maxRank="rank"in f?f.rank:Number.MIN_VALUE;var h=new e;return a.children(d).forEach(function(b){var d=c(b);h=e.union([h,d]),f.minRank=Math.min(f.minRank,a.node(b).minRank),f.maxRank=Math.max(f.maxRank,a.node(b).maxRank)}),"rank"in f&&h.add(f.rank),h.keys().forEach(function(a){a in b||(b[a]=[]),b[a].push(d)}),h}var b=[];c(null);var f=[];return b.forEach(function(b,c){f[c]=a.filterNodes(d(b))}),f}var d=a("graphlib").filter.nodesFromList,e=a("cp-data").Set;b.exports=f},{"cp-data":5,graphlib:28}],16:[function(a,b,c){function f(a,b){var c=[];a.eachNode(function(b,d){var e=c[d.rank];if(a.children&&a.children(b).length>0)return;e||(e=c[d.rank]=[]),e.push(b)}),c.forEach(function(c){b&&e.shuffle(c),c.forEach(function(b,c){a.node(b).order=c})});var f=d(a);a.graph().orderInitCC=f,a.graph().orderCC=Number.MAX_VALUE}var d=a("./crossCount"),e=a("../util");b.exports=f},{"../util":26,"./crossCount":14}],17:[function(a,b,c){function e(a,b,c){var e=[],f={};a.eachNode(function(a,b){e[b.order]=a;var g=c[a];g.length&&(f[a]=d.sum(g)/g.length)});var g=a.nodes().filter(function(a){return f[a]!==undefined});g.sort(function(b,c){return f[b]-f[c]||a.node(b).order-a.node(c).order});for(var h=0,i=0,j=g.length;i=3&&t(d+f,b,c,i[j]),f==="r"&&l(i[j]),f==="r"&&m(c)}),d==="d"&&c.reverse()}),k(b,c,i),b.eachNode(function(a){var c=[];for(var d in i){var e=i[d][a];r(d,b,a,e),c.push(e)}c.sort(function(a,b){return a-b}),q(b,a,(c[1]+c[2])/2)});var j=0,p=b.graph().rankDir==="BT"||b.graph().rankDir==="RL";c.forEach(function(c){var e=d.max(c.map(function(a){return o(b,a)}));j+=e/2,c.forEach(function(a){s(b,a,p?-j:j)}),j+=e/2+a.rankSep});var u=d.min(b.nodes().map(function(a){return q(b,a)-n(b,a)/2})),v=d.min(b.nodes().map(function(a){return s(b,a)-o(b,a)/2}));b.eachNode(function(a){q(b,a,q(b,a)-u),s(b,a,s(b,a)-v)})}function e(a,b){return aj)c[e(g[i],a)]=!0}var c={},d={},f,g,h,i,j;if(b.length<=2)return c;b[1].forEach(function(a,b){d[a]=b});for(var l=1;l0&&(j.sort(function(a,b){return f[a]-f[b]}),k=(j.length-1)/2,j.slice(Math.floor(k),Math.ceil(k)+1).forEach(function(a){h[b]===b&&!c[e(a,b)]&&i0){var h=e[j[d]];m(h),g[b]===b&&(g[b]=g[h]);var i=p(a,j[d])+p(a,d);g[b]!==g[h]?l(g[h],g[b],k[b]-k[h]-i):k[b]=Math.max(k[b],k[h]+i)}d=f[d]}while(d!==b)}}var g={},h={},i={},j={},k={};return b.forEach(function(a){a.forEach(function(b,c){g[b]=b,h[b]={},c>0&&(j[b]=a[c-1])})}),d.values(e).forEach(function(a){m(a)}),b.forEach(function(a){a.forEach(function(a){k[a]=k[e[a]];if(a===e[a]&&a===g[a]){var b=0;a in h&&Object.keys(h[a]).length>0&&(b=d.min(Object.keys(h[a]).map(function(b){return h[a][b]+(b in i?i[b]:0)}))),i[a]=b}})}),b.forEach(function(a){a.forEach(function(a){k[a]+=i[g[e[a]]]||0})}),k}function i(a,b,c){return d.min(b.map(function(a){var b=a[0];return c[b]}))}function j(a,b,c){return d.max(b.map(function(a){var b=a[a.length-1];return c[b]}))}function k(a,b,c){function h(a){c[l][a]+=g[l]}var d={},e={},f,g={},k=Number.POSITIVE_INFINITY;for(var l in c){var m=c[l];d[l]=i(a,b,m),e[l]=j(a,b,m);var n=e[l]-d[l];na.node(d).rank&&(a.delEdge(b),e.reversed=!0,a.addEdge(b,d,c,e))})}function r(a,b){var c=g(a);b&&(d.log(1,"Using network simplex for ranking"),i(a,c)),s(a)}function s(a){var b=d.min(a.nodes().map(function(b){return a.node(b).rank}));a.eachNode(function(a,c){c.rank-=b})}var d=a("./util"),e=a("./rank/acyclic"),f=a("./rank/initRank"),g=a("./rank/feasibleTree"),h=a("./rank/constraints"),i=a("./rank/simplex"),j=a("graphlib").alg.components,k=a("graphlib").filter;c.run=l,c.restoreEdges=m},{"./rank/acyclic":20,"./rank/constraints":21,"./rank/feasibleTree":22,"./rank/initRank":23,"./rank/simplex":25,"./util":26,graphlib:28}],20:[function(a,b,c){function e(a){function f(d){if(d in c)return;c[d]=b[d]=!0,a.outEdges(d).forEach(function(c){var h=a.target(c),i;d===h?console.error('Warning: found self loop "'+c+'" for node "'+d+'"'):h in b?(i=a.edge(c),a.delEdge(c),i.reversed=!0,++e,a.addEdge(c,h,d,i)):f(h)}),delete b[d]}var b={},c={},e=0;return a.eachNode(function(a){f(a)}),d.log(2,"Acyclic Phase: reversed "+e+" edge(s)"),e}function f(a){a.eachEdge(function(b,c,d,e){e.reversed&&(delete e.reversed,a.delEdge(b),a.addEdge(b,d,c,e))})}var d=a("../util");b.exports=e,b.exports.undo=f},{"../util":26}],21:[function(a,b,c){function d(a){return a!=="min"&&a!=="max"&&a.indexOf("same_")!==0?(console.error("Unsupported rank type: "+a),!1):!0}function e(a,b,c,d){a.inEdges(b).forEach(function(b){var e=a.edge(b),f;e.originalEdge?f=e:f={originalEdge:{e:b,u:a.source(b),v:a.target(b),value:e},minLen:a.edge(b).minLen},e.selfLoop&&(d=!1),d?(a.addEdge(null,c,a.source(b),f),f.reversed=!0):a.addEdge(null,a.source(b),c,f)})}function f(a,b,c,d){a.outEdges(b).forEach(function(b){var e=a.edge(b),f;e.originalEdge?f=e:f={originalEdge:{e:b,u:a.source(b),v:a.target(b),value:e},minLen:a.edge(b).minLen},e.selfLoop&&(d=!1),d?(a.addEdge(null,a.target(b),c,f),f.reversed=!0):a.addEdge(null,c,a.target(b),f)})}function g(a,b,c){c!==undefined&&a.children(b).forEach(function(b){b!==c&&!a.outEdges(c,b).length&&!a.node(b).dummy&&a.addEdge(null,c,b,{minLen:0})})}function h(a,b,c){c!==undefined&&a.children(b).forEach(function(b){b!==c&&!a.outEdges(b,c).length&&!a.node(b).dummy&&a.addEdge(null,b,c,{minLen:0})})}c.apply=function(a){function b(c){var i={};a.children(c).forEach(function(g){if(a.children(g).length){b(g);return}var h=a.node(g),j=h.prefRank;if(j!==undefined){if(!d(j))return;j in i?i.prefRank.push(g):i.prefRank=[g];var k=i[j];k===undefined&&(k=i[j]=a.addNode(null,{originalNodes:[]}),a.parent(k,c)),e(a,g,k,j==="min"),f(a,g,k,j==="max"),a.node(k).originalNodes.push({u:g,value:h,parent:c}),a.delNode(g)}}),g(a,c,i.min),h(a,c,i.max)}b(null)},c.relax=function(a){var b=[];a.eachEdge(function(a,c,d,e){var f=e.originalEdge;f&&b.push(f)}),a.eachNode(function(b,c){var d=c.originalNodes;d&&(d.forEach(function(b){b.value.rank=c.rank,a.addNode(b.u,b.value),a.parent(b.u,b.parent)}),a.delNode(b))}),b.forEach(function(b){a.addEdge(b.e,b.u,b.v,b.value)})}},{}],22:[function(a,b,c){function g(a){function g(d){var e=!0;return a.predecessors(d).forEach(function(f){b.has(f)&&!h(a,f,d)&&(b.has(d)&&(c.addNode(d,{}),b.remove(d),c.graph({root:d})),c.addNode(f,{}),c.addEdge(null,f,d,{reversed:!0}),b.remove(f),g(f),e=!1)}),a.successors(d).forEach(function(f){b.has(f)&&!h(a,d,f)&&(b.has(d)&&(c.addNode(d,{}),b.remove(d),c.graph({root:d})),c.addNode(f,{}),c.addEdge(null,d,f,{}),b.remove(f),g(f),e=!1)}),e}function i(){var d=Number.MAX_VALUE;b.keys().forEach(function(c){a.predecessors(c).forEach(function(e){if(!b.has(e)){var f=h(a,e,c);Math.abs(f)0)i=b.source(j[0]),j=b.inEdges(i);b.graph().root=i,b.addEdge(null,e,f,{cutValue:0}),g(a,b),n(a,b)}function n(a,b){function c(d){var e=b.successors(d);e.forEach(function(b){var e=o(a,d,b);a.node(b).rank=a.node(d).rank+e,c(b)})}c(b.graph().root)}function o(a,b,c){var e=a.outEdges(b,c);if(e.length>0)return d.max(e.map(function(b){return a.edge(b).minLen}));var f=a.inEdges(b,c);if(f.length>0)return-d.max(f.map(function(b){return a.edge(b).minLen}))}var d=a("../util"),e=a("./rankUtil");b.exports=f},{"../util":26,"./rankUtil":24}],26:[function(a,b,c){function d(a,b){return function(){var c=(new Date).getTime();try{return b.apply(null,arguments)}finally{e(1,a+" time: "+((new Date).getTime()-c)+"ms")}}}function e(a){e.level>=a&&console.log.apply(console,Array.prototype.slice.call(arguments,1))}c.min=function(a){return Math.min.apply(Math,a)},c.max=function(a){return Math.max.apply(Math,a)},c.all=function(a,b){for(var c=0;c0;--i){var b=Math.floor(Math.random()*(i+1)),c=a[b];a[b]=a[i],a[i]=c}},c.propertyAccessor=function(a,b,c,d){return function(e){return arguments.length?(b[c]=e,d&&d(e),a):b[c]}},c.ordering=function(a){var b=[];return a.eachNode(function(a,c){var d=b[c.rank]||(b[c.rank]=[]);d[c.order]=a}),b},c.filterNonSubgraphs=function(a){return function(b){return a.children(b).length===0}},d.enabled=!1,c.time=d,e.level=0,c.log=e},{}],27:[function(a,b,c){b.exports="0.4.5"},{}],28:[function(a,b,c){c.Graph=a("./lib/Graph"),c.Digraph=a("./lib/Digraph"),c.CGraph=a("./lib/CGraph"),c.CDigraph=a("./lib/CDigraph"),a("./lib/graph-converters"),c.alg={isAcyclic:a("./lib/alg/isAcyclic"),components:a("./lib/alg/components"),dijkstra:a("./lib/alg/dijkstra"),dijkstraAll:a("./lib/alg/dijkstraAll"),findCycles:a("./lib/alg/findCycles"),floydWarshall:a("./lib/alg/floydWarshall"),postorder:a("./lib/alg/postorder"),preorder:a("./lib/alg/preorder"),prim:a("./lib/alg/prim"),tarjan:a("./lib/alg/tarjan"),topsort:a("./lib/alg/topsort")},c.converter={json:a("./lib/converter/json.js")};var d=a("./lib/filter");c.filter={all:d.all,nodesFromList:d.nodesFromList},c.version=a("./lib/version")},{"./lib/CDigraph":30,"./lib/CGraph":31,"./lib/Digraph":32,"./lib/Graph":33,"./lib/alg/components":34,"./lib/alg/dijkstra":35,"./lib/alg/dijkstraAll":36,"./lib/alg/findCycles":37,"./lib/alg/floydWarshall":38,"./lib/alg/isAcyclic":39,"./lib/alg/postorder":40,"./lib/alg/preorder":41,"./lib/alg/prim":42,"./lib/alg/tarjan":43,"./lib/alg/topsort":44,"./lib/converter/json.js":46,"./lib/filter":47,"./lib/graph-converters":48,"./lib/version":50}],29:[function(a,b,c){function e(){this._value=undefined,this._nodes={},this._edges={},this._nextId=0}function f(a,b,c){(a[b]||(a[b]=new d)).add( 2 | c)}function g(a,b,c){var d=a[b];d.remove(c),d.size()===0&&delete a[b]}var d=a("cp-data").Set;b.exports=e,e.prototype.order=function(){return Object.keys(this._nodes).length},e.prototype.size=function(){return Object.keys(this._edges).length},e.prototype.graph=function(a){if(arguments.length===0)return this._value;this._value=a},e.prototype.hasNode=function(a){return a in this._nodes},e.prototype.node=function(a,b){var c=this._strictGetNode(a);if(arguments.length===1)return c.value;c.value=b},e.prototype.nodes=function(){var a=[];return this.eachNode(function(b){a.push(b)}),a},e.prototype.eachNode=function(a){for(var b in this._nodes){var c=this._nodes[b];a(c.id,c.value)}},e.prototype.hasEdge=function(a){return a in this._edges},e.prototype.edge=function(a,b){var c=this._strictGetEdge(a);if(arguments.length===1)return c.value;c.value=b},e.prototype.edges=function(){var a=[];return this.eachEdge(function(b){a.push(b)}),a},e.prototype.eachEdge=function(a){for(var b in this._edges){var c=this._edges[b];a(c.id,c.u,c.v,c.value)}},e.prototype.incidentNodes=function(a){var b=this._strictGetEdge(a);return[b.u,b.v]},e.prototype.addNode=function(a,b){if(a===undefined||a===null){do a="_"+ ++this._nextId;while(this.hasNode(a))}else if(this.hasNode(a))throw new Error("Graph already has node '"+a+"'");return this._nodes[a]={id:a,value:b},a},e.prototype.delNode=function(a){this._strictGetNode(a),this.incidentEdges(a).forEach(function(a){this.delEdge(a)},this),delete this._nodes[a]},e.prototype._addEdge=function(a,b,c,d,e,g){this._strictGetNode(b),this._strictGetNode(c);if(a===undefined||a===null){do a="_"+ ++this._nextId;while(this.hasEdge(a))}else if(this.hasEdge(a))throw new Error("Graph already has edge '"+a+"'");return this._edges[a]={id:a,u:b,v:c,value:d},f(e[c],b,a),f(g[b],c,a),a},e.prototype._delEdge=function(a,b,c){var d=this._strictGetEdge(a);g(b[d.v],d.u,a),g(c[d.u],d.v,a),delete this._edges[a]},e.prototype.copy=function(){var a=new this.constructor;return a.graph(this.graph()),this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(b,c,d,e)}),a._nextId=this._nextId,a},e.prototype.filterNodes=function(a){var b=new this.constructor;return b.graph(this.graph()),this.eachNode(function(c,d){a(c)&&b.addNode(c,d)}),this.eachEdge(function(a,c,d,e){b.hasNode(c)&&b.hasNode(d)&&b.addEdge(a,c,d,e)}),b},e.prototype._strictGetNode=function(a){var b=this._nodes[a];if(b===undefined)throw new Error("Node '"+a+"' is not in graph");return b},e.prototype._strictGetEdge=function(a){var b=this._edges[a];if(b===undefined)throw new Error("Edge '"+a+"' is not in graph");return b}},{"cp-data":5}],30:[function(a,b,c){var d=a("./Digraph"),e=a("./compoundify"),f=e(d);b.exports=f,f.fromDigraph=function(a){var b=new f,c=a.graph();return c!==undefined&&b.graph(c),a.eachNode(function(a,c){c===undefined?b.addNode(a):b.addNode(a,c)}),a.eachEdge(function(a,c,d,e){e===undefined?b.addEdge(null,c,d):b.addEdge(null,c,d,e)}),b},f.prototype.toString=function(){return"CDigraph "+JSON.stringify(this,null,2)}},{"./Digraph":32,"./compoundify":45}],31:[function(a,b,c){var d=a("./Graph"),e=a("./compoundify"),f=e(d);b.exports=f,f.fromGraph=function(a){var b=new f,c=a.graph();return c!==undefined&&b.graph(c),a.eachNode(function(a,c){c===undefined?b.addNode(a):b.addNode(a,c)}),a.eachEdge(function(a,c,d,e){e===undefined?b.addEdge(null,c,d):b.addEdge(null,c,d,e)}),b},f.prototype.toString=function(){return"CGraph "+JSON.stringify(this,null,2)}},{"./Graph":33,"./compoundify":45}],32:[function(a,b,c){function g(){e.call(this),this._inEdges={},this._outEdges={}}var d=a("./util"),e=a("./BaseGraph"),f=a("cp-data").Set;b.exports=g,g.prototype=new e,g.prototype.constructor=g,g.prototype.isDirected=function(){return!0},g.prototype.successors=function(a){return this._strictGetNode(a),Object.keys(this._outEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.predecessors=function(a){return this._strictGetNode(a),Object.keys(this._inEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.neighbors=function(a){return f.union([this.successors(a),this.predecessors(a)]).keys()},g.prototype.sources=function(){var a=this;return this._filterNodes(function(b){return a.inEdges(b).length===0})},g.prototype.sinks=function(){var a=this;return this._filterNodes(function(b){return a.outEdges(b).length===0})},g.prototype.source=function(a){return this._strictGetEdge(a).u},g.prototype.target=function(a){return this._strictGetEdge(a).v},g.prototype.inEdges=function(a,b){this._strictGetNode(a);var c=f.union(d.values(this._inEdges[a])).keys();return arguments.length>1&&(this._strictGetNode(b),c=c.filter(function(a){return this.source(a)===b},this)),c},g.prototype.outEdges=function(a,b){this._strictGetNode(a);var c=f.union(d.values(this._outEdges[a])).keys();return arguments.length>1&&(this._strictGetNode(b),c=c.filter(function(a){return this.target(a)===b},this)),c},g.prototype.incidentEdges=function(a,b){return arguments.length>1?f.union([this.outEdges(a,b),this.outEdges(b,a)]).keys():f.union([this.inEdges(a),this.outEdges(a)]).keys()},g.prototype.toString=function(){return"Digraph "+JSON.stringify(this,null,2)},g.prototype.addNode=function(a,b){return a=e.prototype.addNode.call(this,a,b),this._inEdges[a]={},this._outEdges[a]={},a},g.prototype.delNode=function(a){e.prototype.delNode.call(this,a),delete this._inEdges[a],delete this._outEdges[a]},g.prototype.addEdge=function(a,b,c,d){return e.prototype._addEdge.call(this,a,b,c,d,this._inEdges,this._outEdges)},g.prototype.delEdge=function(a){e.prototype._delEdge.call(this,a,this._inEdges,this._outEdges)},g.prototype._filterNodes=function(a){var b=[];return this.eachNode(function(c){a(c)&&b.push(c)}),b}},{"./BaseGraph":29,"./util":49,"cp-data":5}],33:[function(a,b,c){function g(){e.call(this),this._incidentEdges={}}var d=a("./util"),e=a("./BaseGraph"),f=a("cp-data").Set;b.exports=g,g.prototype=new e,g.prototype.constructor=g,g.prototype.isDirected=function(){return!1},g.prototype.neighbors=function(a){return this._strictGetNode(a),Object.keys(this._incidentEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.incidentEdges=function(a,b){return this._strictGetNode(a),arguments.length>1?(this._strictGetNode(b),b in this._incidentEdges[a]?this._incidentEdges[a][b].keys():[]):f.union(d.values(this._incidentEdges[a])).keys()},g.prototype.toString=function(){return"Graph "+JSON.stringify(this,null,2)},g.prototype.addNode=function(a,b){return a=e.prototype.addNode.call(this,a,b),this._incidentEdges[a]={},a},g.prototype.delNode=function(a){e.prototype.delNode.call(this,a),delete this._incidentEdges[a]},g.prototype.addEdge=function(a,b,c,d){return e.prototype._addEdge.call(this,a,b,c,d,this._incidentEdges,this._incidentEdges)},g.prototype.delEdge=function(a){e.prototype._delEdge.call(this,a,this._incidentEdges,this._incidentEdges)}},{"./BaseGraph":29,"./util":49,"cp-data":5}],34:[function(a,b,c){function e(a){function e(b,d){c.has(b)||(c.add(b),d.push(b),a.neighbors(b).forEach(function(a){e(a,d)}))}var b=[],c=new d;return a.nodes().forEach(function(a){var c=[];e(a,c),c.length>0&&b.push(c)}),b}var d=a("cp-data").Set;b.exports=e},{"cp-data":5}],35:[function(a,b,c){function e(a,b,c,e){function h(b){var d=a.incidentNodes(b),e=d[0]!==i?d[0]:d[1],h=f[e],k=c(b),l=j.distance+k;if(k<0)throw new Error("dijkstra does not allow negative edge weights. Bad edge: "+b+" Weight: "+k);l0){i=g.removeMin(),j=f[i];if(j.distance===Number.POSITIVE_INFINITY)break;e(i).forEach(h)}return f}var d=a("cp-data").PriorityQueue;b.exports=e},{"cp-data":5}],36:[function(a,b,c){function e(a,b,c){var e={};return a.eachNode(function(f){e[f]=d(a,f,b,c)}),e}var d=a("./dijkstra");b.exports=e},{"./dijkstra":35}],37:[function(a,b,c){function e(a){return d(a).filter(function(a){return a.length>1})}var d=a("./tarjan");b.exports=e},{"./tarjan":43}],38:[function(a,b,c){function d(a,b,c){var d={},e=a.nodes();return b=b||function(){return 1},c=c||(a.isDirected()?function(b){return a.outEdges(b)}:function(b){return a.incidentEdges(b)}),e.forEach(function(f){d[f]={},d[f][f]={distance:0},e.forEach(function(a){f!==a&&(d[f][a]={distance:Number.POSITIVE_INFINITY})}),c(f).forEach(function(c){var e=a.incidentNodes(c),h=e[0]!==f?e[0]:e[1],i=b(c);i0){h=g.removeMin();if(h in f)c.addEdge(null,h,f[h]);else{if(j)throw new Error("Input graph is not connected: "+a);j=!0}a.incidentEdges(h).forEach(i)}return c}var d=a("../Graph"),e=a("cp-data").PriorityQueue;b.exports=f},{"../Graph":33,"cp-data":5}],43:[function(a,b,c){function d(a){function f(h){var i=d[h]={onStack:!0,lowlink:b,index:b++};c.push(h),a.successors(h).forEach(function(a){a in d?d[a].onStack&&(i.lowlink=Math.min(i.lowlink,d[a].index)):(f(a),i.lowlink=Math.min(i.lowlink,d[a].lowlink))});if(i.lowlink===i.index){var j=[],k;do k=c.pop(),d[k].onStack=!1,j.push(k);while(h!==k);e.push(j)}}if(!a.isDirected())throw new Error("tarjan can only be applied to a directed graph. Bad input: "+a);var b=0,c=[],d={},e=[];return a.nodes().forEach(function(a){a in d||f(a)}),e}b.exports=d},{}],44:[function(a,b,c){function d(a){function f(g){if(g in c)throw new e;g in b||(c[g]=!0,b[g]=!0,a.predecessors(g).forEach(function(a){f(a)}),delete c[g],d.push(g))}if(!a.isDirected())throw new Error("topsort can only be applied to a directed graph. Bad input: "+a);var b={},c={},d=[],g=a.sinks();if(a.order()!==0&&g.length===0)throw new e;return a.sinks().forEach(function(a){f(a)}),d}function e(){}b.exports=d,d.CycleException=e,e.prototype.toString=function(){return"Graph has at least one cycle"}},{}],45:[function(a,b,c){function e(a){function b(){a.call(this),this._parents={},this._children={},this._children[null]=new d}return b.prototype=new a,b.prototype.constructor=b,b.prototype.parent=function(a,b){this._strictGetNode(a);if(arguments.length<2)return this._parents[a];if(a===b)throw new Error("Cannot make "+a+" a parent of itself");b!==null&&this._strictGetNode(b),this._children[this._parents[a]].remove(a),this._parents[a]=b,this._children[b].add(a)},b.prototype.children=function(a){return a!==null&&this._strictGetNode(a),this._children[a].keys()},b.prototype.addNode=function(b,c){return b=a.prototype.addNode.call(this,b,c),this._parents[b]=null,this._children[b]=new d,this._children[null].add(b),b},b.prototype.delNode=function(b){var c=this.parent(b);return this._children[b].keys().forEach(function(a){this.parent(a,c)},this),this._children[c].remove(b),delete this._parents[b],delete this._children[b],a.prototype.delNode.call(this,b)},b.prototype.copy=function(){var b=a.prototype.copy.call(this);return this.nodes().forEach(function(a){b.parent(a,this.parent(a))},this),b},b.prototype.filterNodes=function(b){function f(a){var b=c.parent(a);return b===null||d.hasNode(b)?(e[a]=b,b):b in e?e[b]:f(b)}var c=this,d=a.prototype.filterNodes.call(this,b),e={};return d.eachNode(function(a){d.parent(a,f(a))}),d},b}var d=a("cp-data").Set;b.exports=e},{"cp-data":5}],46:[function(a,b,c){function h(a){return Object.prototype.toString.call(a).slice(8,-1)}var d=a("../Graph"),e=a("../Digraph"),f=a("../CGraph"),g=a("../CDigraph");c.decode=function(a,b,c){c=c||e;if(h(a)!=="Array")throw new Error("nodes is not an Array");if(h(b)!=="Array")throw new Error("edges is not an Array");if(typeof c=="string")switch(c){case"graph":c=d;break;case"digraph":c=e;break;case"cgraph":c=f;break;case"cdigraph":c=g;break;default:throw new Error("Unrecognized graph type: "+c)}var i=new c;return a.forEach(function(a){i.addNode(a.id,a.value)}),i.parent&&a.forEach(function(a){a.children&&a.children.forEach(function(b){i.parent(b,a.id)})}),b.forEach(function(a){i.addEdge(a.id,a.u,a.v,a.value)}),i},c.encode=function(a){var b=[],c=[];a.eachNode(function(c,d){var e={id:c,value:d};if(a.children){var f=a.children(c);f.length&&(e.children=f)}b.push(e)}),a.eachEdge(function(a,b,d,e){c.push({id:a,u:b,v:d,value:e})});var h;if(a instanceof g)h="cdigraph";else if(a instanceof f)h="cgraph";else if(a instanceof e)h="digraph";else if(a instanceof d)h="graph";else throw new Error("Couldn't determine type of graph: "+a);return{nodes:b,edges:c,type:h}}},{"../CDigraph":30,"../CGraph":31,"../Digraph":32,"../Graph":33}],47:[function(a,b,c){var d=a("cp-data").Set;c.all=function(){return function(){return!0}},c.nodesFromList=function(a){var b=new d(a);return function(a){return b.has(a)}}},{"cp-data":5}],48:[function(a,b,c){var d=a("./Graph"),e=a("./Digraph");d.prototype.toDigraph=d.prototype.asDirected=function(){var a=new e;return this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(null,c,d,e),a.addEdge(null,d,c,e)}),a},e.prototype.toGraph=e.prototype.asUndirected=function(){var a=new d;return this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(b,c,d,e)}),a}},{"./Digraph":32,"./Graph":33}],49:[function(a,b,c){c.values=function(a){var b=Object.keys(a),c=b.length,d=new Array(c),e;for(e=0;e