spyro 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/OLDjavascripts/application.js +25 -0
  6. data/app/assets/OLDjavascripts/components.es6 +10 -0
  7. data/app/assets/OLDjavascripts/components/base/base.es6 +197 -0
  8. data/app/assets/OLDjavascripts/components/base/collection.es6 +199 -0
  9. data/app/assets/OLDjavascripts/components/base/element.es6 +134 -0
  10. data/app/assets/OLDjavascripts/components/base/field.es6 +26 -0
  11. data/app/assets/OLDjavascripts/components/base/inputs/text.es6 +21 -0
  12. data/app/assets/OLDjavascripts/components/base/pagination.es6 +23 -0
  13. data/app/assets/OLDjavascripts/vendor/lodash.js +121 -0
  14. data/app/assets/OLDjavascripts/vendor/moment-twitter.js +86 -0
  15. data/app/assets/OLDjavascripts/vendor/moment.js +80 -0
  16. data/app/assets/OLDjavascripts/vendor/pluralize.js +433 -0
  17. data/app/views/base/_associations.html.haml +11 -0
  18. data/app/views/base/_custom.html.haml +0 -0
  19. data/app/views/base/_details.html.haml +3 -0
  20. data/app/views/base/_edit_header.html.haml +3 -0
  21. data/app/views/base/_fields.html.haml +11 -0
  22. data/app/views/base/_form.html.haml +6 -0
  23. data/app/views/base/_index_header.html.haml +4 -0
  24. data/app/views/base/_new_header.html.haml +3 -0
  25. data/app/views/base/_other_form_fields.html.haml +0 -0
  26. data/app/views/base/_show_header.html.haml +9 -0
  27. data/app/views/base/_upload_fields.html.haml +5 -0
  28. data/app/views/base/copy.html.haml +10 -0
  29. data/app/views/base/edit.html.haml +4 -0
  30. data/app/views/base/index.csv.haml +8 -0
  31. data/app/views/base/index.html.haml +7 -0
  32. data/app/views/base/index.js.haml +2 -0
  33. data/app/views/base/index.xls.ruby +12 -0
  34. data/app/views/base/mailer.html.haml +46 -0
  35. data/app/views/base/new.html.haml +4 -0
  36. data/app/views/base/notify.html.haml +11 -0
  37. data/app/views/base/show.html.haml +8 -0
  38. data/app/views/base/stats.html.haml +36 -0
  39. data/app/views/base/trombi.html.haml +1 -0
  40. data/app/views/base/upload.html.haml +24 -0
  41. data/config/initializers/assets.rb +1 -0
  42. data/config/routes.rb +2 -0
  43. data/lib/spyro.rb +24 -0
  44. data/lib/spyro/active_record_add_on.rb +38 -0
  45. data/lib/spyro/application_controller_add_on.rb +24 -0
  46. data/lib/spyro/collections/outputs/admin_table.rb +28 -0
  47. data/lib/spyro/collections/outputs/bar_graph_table.rb +46 -0
  48. data/lib/spyro/collections/outputs/base.rb +23 -0
  49. data/lib/spyro/collections/outputs/csv.rb +34 -0
  50. data/lib/spyro/collections/outputs/fields.rb +118 -0
  51. data/lib/spyro/collections/outputs/flatui_table.rb +40 -0
  52. data/lib/spyro/collections/outputs/inplace_table.rb +29 -0
  53. data/lib/spyro/collections/outputs/map.rb +22 -0
  54. data/lib/spyro/collections/outputs/table.rb +126 -0
  55. data/lib/spyro/collections/outputs/xlsx.rb +39 -0
  56. data/lib/spyro/collections/parsers/active_ldap_relation.rb +22 -0
  57. data/lib/spyro/collections/parsers/active_record_relation.rb +138 -0
  58. data/lib/spyro/collections/parsers/array.rb +38 -0
  59. data/lib/spyro/collections/parsers/base.rb +60 -0
  60. data/lib/spyro/collections/parsers/kaminari_array.rb +38 -0
  61. data/lib/spyro/collections/parsers/model.rb +31 -0
  62. data/lib/spyro/controllers/strong_parameted.rb +33 -0
  63. data/lib/spyro/engine.rb +6 -0
  64. data/lib/spyro/filters_controller_add_on.rb +161 -0
  65. data/lib/spyro/helpers/action_view_extension.rb +726 -0
  66. data/lib/spyro/namespace_template_inheritance.rb +30 -0
  67. data/lib/spyro/usefull_attributes.rb +14 -0
  68. data/lib/spyro/version.rb +3 -0
  69. data/lib/tasks/spyro_tasks.rake +4 -0
  70. data/test/dummy/README.rdoc +28 -0
  71. data/test/dummy/Rakefile +6 -0
  72. data/test/dummy/app/assets/javascripts/application.js +13 -0
  73. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  74. data/test/dummy/app/controllers/application_controller.rb +5 -0
  75. data/test/dummy/app/helpers/application_helper.rb +2 -0
  76. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  77. data/test/dummy/bin/bundle +3 -0
  78. data/test/dummy/bin/rails +4 -0
  79. data/test/dummy/bin/rake +4 -0
  80. data/test/dummy/bin/setup +29 -0
  81. data/test/dummy/config.ru +4 -0
  82. data/test/dummy/config/application.rb +26 -0
  83. data/test/dummy/config/boot.rb +5 -0
  84. data/test/dummy/config/database.yml +25 -0
  85. data/test/dummy/config/environment.rb +5 -0
  86. data/test/dummy/config/environments/development.rb +41 -0
  87. data/test/dummy/config/environments/production.rb +79 -0
  88. data/test/dummy/config/environments/test.rb +42 -0
  89. data/test/dummy/config/initializers/assets.rb +11 -0
  90. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  91. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  92. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  93. data/test/dummy/config/initializers/inflections.rb +16 -0
  94. data/test/dummy/config/initializers/mime_types.rb +4 -0
  95. data/test/dummy/config/initializers/session_store.rb +3 -0
  96. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  97. data/test/dummy/config/locales/en.yml +23 -0
  98. data/test/dummy/config/routes.rb +4 -0
  99. data/test/dummy/config/secrets.yml +22 -0
  100. data/test/dummy/public/404.html +67 -0
  101. data/test/dummy/public/422.html +67 -0
  102. data/test/dummy/public/500.html +66 -0
  103. data/test/dummy/public/favicon.ico +0 -0
  104. data/test/integration/navigation_test.rb +8 -0
  105. data/test/spyro_test.rb +7 -0
  106. data/test/test_helper.rb +21 -0
  107. metadata +256 -0
@@ -0,0 +1,38 @@
1
+ require_relative "active_record_relation"
2
+
3
+ module Spyro
4
+ module ActionViewExtension
5
+
6
+ module CollectionForHelper
7
+ module Parser
8
+ class Array < Parser::ActiveRecordRelation
9
+ @@types[::Array] = self
10
+
11
+ def initialize model, helper, options
12
+ super
13
+ @array = @collection
14
+ end
15
+
16
+ def parse
17
+ if @array.first.try(:is_a?, ActiveRecord::Base)
18
+ super
19
+ return
20
+ end
21
+ if @unicollection.rows.empty?
22
+ @array.each do |row|
23
+ @unicollection.row do |r|
24
+ [*row].each do |elem|
25
+ r.add_elem UniData::Element.new(value: elem, name: "header", type: elem.class)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ @unicollection.add_meta :header, @unicollection.meta[:data]
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+
2
+ module Spyro
3
+ module ActionViewExtension
4
+
5
+ module CollectionForHelper
6
+ module Parser
7
+ class Base
8
+
9
+ mattr_accessor :types
10
+ attr_accessor :unicollection
11
+
12
+ delegate :polymorphic_path, :edit_polymorphic_path, to: :@h
13
+
14
+ @@types = {}
15
+
16
+ def initialize collection, helper, options
17
+ @h = helper
18
+ @collection = collection
19
+ @options = options
20
+
21
+ @unicollection = UniData::Collection.new
22
+ options.each do |key, value|
23
+ @unicollection.add_meta key, value
24
+ end
25
+ end
26
+
27
+ def link model_belong
28
+ final_link = polymorphic_path([@options[:parents].first, model_belong]) rescue nil
29
+ final_link = polymorphic_path([*@options[:parents], model_belong]) rescue nil if final_link.nil?
30
+ final_link
31
+ end
32
+
33
+ def hash_for_name model, name
34
+ #value = model[name] # en vrai
35
+ value = model.send(name) # intra
36
+
37
+ if name =~ /(.+)_id$/
38
+ model_belong = model.send($1) rescue nil
39
+ if model_belong
40
+ link = link(model_belong) rescue nil
41
+ return {value: model_belong.try(:to_desc) || model_belong.try(:name) || model_belong.id,
42
+ type: String, name: name, db_type: String, link: link}
43
+ end
44
+ end
45
+ {value: value, type: value.class, name: name, db_type: (model.has_attribute?(name) ? model.type_for_attribute(name) : nil)}
46
+ end
47
+
48
+ def method_missing method, args
49
+ @unicollection.add_meta method, args
50
+ end
51
+
52
+ def parse_default
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,38 @@
1
+ # require_relative "array"
2
+
3
+ # require "rubygems"
4
+ # require "kaminari/models/array_extension"
5
+
6
+ # module Spyro
7
+ # module ActionViewExtension
8
+
9
+ # module CollectionForHelper
10
+ # module Parser
11
+ # class KaminariArray < Array
12
+ # @@types[::Kaminari::PaginatableArray] = self
13
+
14
+ # def initialize *args
15
+ # super
16
+
17
+ # header [:id, :login, :email], :actions => true
18
+ # end
19
+
20
+ # def parse
21
+ # @array.each do |model|
22
+ # @unicollection.row do |r|
23
+ # @unicollection.meta[:data].each do |name|
24
+ # hash = hash_for_name model, name
25
+ # hash[:link] = link model if name == :name
26
+ # r.add_elem UniData::Element.new(hash)
27
+ # end
28
+ # r.add_elem_meta :actions, populate_actions(model, r, @options, [:show, :edit, :destroy])
29
+ # r.add_elem_meta :id, model.id
30
+ # end
31
+ # end
32
+ # end
33
+
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ # end
@@ -0,0 +1,31 @@
1
+ require_relative "base"
2
+
3
+ module Spyro
4
+ module ActionViewExtension
5
+ module CollectionForHelper
6
+ module Parser
7
+ class Model < Parser::Base
8
+ @@types[::ActiveRecord::Base] = self
9
+
10
+ def initialize model, helper, options
11
+ super
12
+ @model = @collection
13
+ @unicollection.add_meta :model_class, model.class
14
+ @unicollection.add_meta :show, polymorphic_path([*options[:parents], model]) rescue nil
15
+ end
16
+
17
+ def parse
18
+ (@unicollection.meta[:data] || @model.attributes).each do |name, val|
19
+ @unicollection.row do |r|
20
+ hash = hash_for_name(@model, name)
21
+ r.add_elem UniData::Element.new(value: name, name: "header", type: String, translate: true)
22
+ r.add_elem UniData::Element.new(hash)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module StrongParametedAddOn
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ # Strong parameted
8
+ def strong_parameted attrs = nil
9
+ cattr_accessor :strong_attributes
10
+
11
+ model = self.name.to_s[0..-11].split('::').last.singularize
12
+ model_sym = model.underscore.to_sym
13
+ if attrs
14
+ attrs.map! {|elem| (elem == :all ? (model.constantize.column_names - ['id', 'updated_at', 'created_at']).map!(&:to_sym) : elem) }
15
+ else
16
+ attrs = (model.constantize.column_names - ['id', 'updated_at', 'created_at']).map!(&:to_sym)
17
+ end
18
+
19
+ self.strong_attributes = attrs
20
+
21
+ define_method :resource_params do
22
+ return [] if request.get?
23
+ model_hash = params.require(model_sym)
24
+ return (model_hash.respond_to?(:permit) ? [model_hash.permit(attrs)] : [])
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ class ActionController::Base
32
+ include StrongParametedAddOn
33
+ end
@@ -0,0 +1,6 @@
1
+ module Spyro
2
+ class Engine < ::Rails::Engine
3
+ p "Spyro 🐉"
4
+ isolate_namespace Spyro
5
+ end
6
+ end
@@ -0,0 +1,161 @@
1
+
2
+ module FiltersControllerAddOn
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ {paginated: :page, ordered: :order}.each do |method, value|
7
+ define_method "is_#{method}?" do
8
+ !!scopes_configuration[value]
9
+ end
10
+ end
11
+
12
+ def paginate
13
+ has_scope :page, :default => 1, :only => [:index], :if => lambda {|c| (c.params[:format].try(:to_sym) || :html).in? [:html, :js] }
14
+ end
15
+
16
+ def paginated
17
+ paginate
18
+ end
19
+
20
+ # in:name CPP in:campus a
21
+ def searched *args # recode moi avec `or` avec Rails 5 :'(
22
+ cattr_accessor :searched_keys
23
+ cattr_accessor :searched_args
24
+
25
+ self.searched_keys = args.map {|arg| (arg.is_a?(Hash) ? arg.to_a[0].first : arg)}
26
+ self.searched_args = args
27
+
28
+ has_scope :search, :only => [:index] do |controller, scope, values|
29
+ sql = values.reverse.split(/\s+(?!\S+:\S+)/).map(&:reverse).reverse.map do |value| # feinte du variable-length look behind
30
+ in_search, value = value.match(/(in:(?<filter>\S+)\s+)?(?<search>.+)/).values_at 1, 2
31
+
32
+ sql = args.map do |arg|
33
+ key = (arg.is_a?(Hash) ? arg.to_a[0].first : arg)
34
+ next if !in_search.nil? and in_search != key.to_s
35
+
36
+ if arg.is_a? Hash
37
+ scope.instance_exec(value, &(arg.to_a[0].last)).to_sql
38
+ else
39
+ scope.where("#{resource_class.table_name}.#{arg} ilike ?", "%#{value}%").to_sql
40
+ end
41
+ end.reject(&:blank?)
42
+
43
+ sql = (sql.any? ? sql.join(' UNION ') : scope.where("1=0").to_sql)
44
+ resource_class.from("(#{sql}) #{resource_class.table_name}").to_sql
45
+ end
46
+
47
+ sql = (sql.any? ? sql.join(' INTERSECT ') : scope.where("1=0").to_sql)
48
+
49
+ resource_class.from("(#{sql}) #{resource_class.table_name}")
50
+ end
51
+ end
52
+
53
+ def ranged *args
54
+ cattr_accessor :ranged_keys
55
+ cattr_accessor :ranged_values
56
+ cattr_accessor :ranged_args
57
+
58
+ args = args.map {|arg| arg.is_a?(Hash) ? arg.to_a[0] : [arg, nil]}.to_h
59
+ self.ranged_keys = args.keys
60
+ self.ranged_values = self.ranged_keys.map {|k| [k, (args[k] ? args[k][2].call : resource_class.min_max(k))] }.to_h
61
+ self.ranged_args = args
62
+
63
+ has_scope :range, :only => [:index] do |controller, scope, values| #http://localhost:3000/exams?range=created_at:2015-06-03%2000:00%3bmax_people:50,100&sort=name
64
+ values.split(";").each do |value|
65
+ name, min, max = *value.match(/(\w+):([^,]+)?,?([^,]+)?/).values_at(1, 2, 3)
66
+ if name.to_sym.in? args.keys
67
+ scopes = args[name.to_sym]
68
+ if scopes
69
+ scope = scope.instance_exec(min, &(scopes[0])) if min
70
+ scope = scope.instance_exec(max, &(scopes[1])) if max
71
+ else
72
+ scope = scope.where("#{resource_class.table_name}.#{name} >= ?", min) if min
73
+ scope = scope.where("#{resource_class.table_name}.#{name} <= ?", max) if max
74
+ end
75
+ end
76
+ end
77
+
78
+ scope
79
+ end
80
+ end
81
+
82
+ def filtered *args #filter=campus:Cluj;
83
+ cattr_accessor :filtered_keys
84
+ cattr_accessor :filtered_values
85
+ cattr_accessor :filtered_args
86
+
87
+ args = args.map {|arg| arg.is_a?(Hash) ? arg.to_a[0] : [arg, nil]}.to_h
88
+ self.filtered_keys = args.keys
89
+ self.filtered_values = self.filtered_keys.map {|k| [k, (args[k] ? args[k][1].call : resource_class.pluck(k).uniq)] }.to_h
90
+ self.filtered_args = args
91
+
92
+ has_scope :filter, :only => [:index] do |controller, scope, values|
93
+ values.split(";").each do |value|
94
+ name, filter = *value.split(':', 2)
95
+ if name.to_sym.in? args.keys
96
+ scopes = args[name.to_sym]
97
+ if scopes
98
+ scope = scope.instance_exec(filter.split(','), &(scopes[0]))
99
+ else
100
+ scope = scope.where(name => filter.split(','))
101
+ end
102
+ end
103
+ end
104
+
105
+ scope
106
+ end
107
+ end
108
+
109
+
110
+ def ordered *args
111
+ cattr_accessor :ordered_keys
112
+ cattr_accessor :ordered_options
113
+
114
+ self.ordered_options = (args.count > 1 ? args.extract_options! : {})
115
+ def self.execute_order value, scope, keys, args, desc
116
+ value = keys.first unless keys.include? value.to_sym
117
+
118
+ my_scope = args.find {|arg| arg.is_a?(Hash) and arg[value.to_sym]}
119
+ scope = if my_scope
120
+ scope.instance_exec &(my_scope[value.to_sym])
121
+ else
122
+ scope.order(value.to_sym)
123
+ end
124
+
125
+ if desc
126
+ scope = (scope.respond_to?(:desc) ? scope.desc : scope.reverse_order)
127
+ end
128
+ scope = execute_order(keys.first, scope, keys, args, ordered_options[:default] == :desc) unless value.to_sym == keys.first.to_sym
129
+
130
+ return scope
131
+ end
132
+
133
+ args = [*args].flatten
134
+ keys = args.flat_map {|arg| arg.is_a?(Hash) ? arg.map(&:first) : arg}.map(&:to_sym)
135
+ self.ordered_keys = keys
136
+
137
+ raise "Missing params" if keys.empty? or (keys.length == 1 and keys.first.to_sym == :default)
138
+ raise "Bad value for default" if self.ordered_options and !self.ordered_options[:default].in? [nil, :desc]
139
+
140
+ has_scope :order, :default => keys.first.to_s, :only => [:index], as: :sort do |controller, scope, value|
141
+ if !controller.params[:sort].blank? or controller.params[:rev_sort].blank?
142
+ execute_order(value, scope, keys, args, false).send((controller.params[:sort].nil? and self.ordered_options[:default] == :desc) ? :desc : :all)
143
+ else
144
+ scope.all
145
+ end
146
+ end
147
+
148
+ has_scope :rev_order, :default => keys.first.to_s, :only => [:index], as: :rev_sort do |controller, scope, value|
149
+ if !controller.params[:rev_sort].blank? and controller.params[:sort].blank?
150
+ execute_order(value, scope, keys, args, true)
151
+ else
152
+ scope.all
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ class ActionController::Base
160
+ include FiltersControllerAddOn
161
+ end
@@ -0,0 +1,726 @@
1
+
2
+
3
+ module Spyro
4
+ module ActionViewExtension
5
+
6
+ def default_destroy_link_attributes
7
+ {:data => {:confirm => "Are you sure?", :method => "delete", :'confirm-title' => "Confirmation"}}
8
+ end
9
+
10
+ module BasicWidgetsHelper
11
+ def mini_button name, link, options = {}
12
+ options[:class] = (options[:class] || []) + ['btn-xs', 'btn', 'btn-default']
13
+ button name, link, options
14
+ end
15
+
16
+ [:danger, :success, :warning, :primary, :info, :inverse].each do |klass|
17
+ define_method "mini_button_#{klass}" do |name, link, options = {}|
18
+ options[:class] = ([*options[:class]] || []) + ['btn-xs', 'btn', "btn-#{klass}"]
19
+ button name, link, options
20
+ end
21
+ end
22
+
23
+ alias_method :mini_button_error, :mini_button_danger
24
+
25
+ def button name, link, options = {}
26
+ @has_button = true
27
+ options[:class] ||= ['btn btn-default']
28
+ text = ""
29
+ text = I18n.t("#{params[:controller].gsub('/', '.')}.#{params[:action]}.button.#{name.downcase}", :default => I18n.t("scaffold.buttons.#{name.downcase}", :default => name.to_s.humanize)) unless name.blank?
30
+ text = "<span class='glyphicon glyphicon-#{options[:icon]}'></span>#{text}".html_safe if options[:icon]
31
+ link_to text, link, options
32
+ end
33
+
34
+ [:danger, :success, :warning, :primary, :info, :inverse].each do |klass|
35
+ define_method "button_#{klass}" do |name, link, options = {}|
36
+ options[:class] = [*options[:class]]
37
+ options[:class] << "btn btn-#{klass}"
38
+ button name, link, options
39
+ end
40
+ end
41
+
42
+ alias_method :button_error, :button_danger
43
+
44
+ def group &block
45
+ @has_group = true
46
+ capture_haml do
47
+ haml_tag :div, :class => ['btn-group'] do
48
+ haml_concat capture(&block)
49
+ end
50
+ end
51
+ end
52
+
53
+ def alert text, options = {}
54
+ @has_alert = true
55
+ options[:class] ||= ['alert']
56
+ capture_haml do
57
+ haml_tag :div, options do
58
+ haml_concat text
59
+ end
60
+ end
61
+ end
62
+
63
+ [:danger, :success, :warning, :action, :info, :inverse].each do |klass|
64
+ define_method "alert_#{klass}" do |text, options = {}|
65
+ options[:class] ||= ['alert', "alert-#{klass}"]
66
+ alert text, options
67
+ end
68
+ end
69
+
70
+ alias_method :alert_error, :alert_danger
71
+
72
+ end
73
+
74
+ module HeadersHelper
75
+ def header name = "", &block
76
+ if @inside_box
77
+ box_header name, &block
78
+ else
79
+ page_header name, &block
80
+ end
81
+ end
82
+
83
+ def page_header name, &block
84
+ @has_group = false
85
+
86
+ content_for :title do
87
+ name
88
+ end
89
+
90
+ capture_haml do
91
+ haml_tag :h1, :class => ['page-header'] do
92
+ haml_concat name
93
+ if block_given?
94
+ haml_tag :div, :class => ['pull-right'] do
95
+ content = capture(&block)
96
+ haml_concat (@has_group ? content : group { content })
97
+ end
98
+ haml_tag :div, :class => ['clear']
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def box_header name, &block
105
+ @has_group = false
106
+
107
+ capture_haml do
108
+ haml_tag :div, :class => ["panel-heading"] do
109
+ haml_tag :div, :class => "row" do
110
+ haml_tag :div, :class => "col-lg-12" do
111
+ haml_tag :h2, :class => ["panel-title", 'col-sm-8'], :style => "padding: 8px 0px;" do
112
+ haml_concat name
113
+ end
114
+ if block_given?
115
+ haml_tag :div, :class => ['controls', 'col-sm-4', 'text-right'] do
116
+ content = capture(&block)
117
+ haml_concat (@has_group ? content : group { content })
118
+ end
119
+ haml_tag :div, :class => ['clear']
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ module ContainerHelper
129
+ def box opts = {}, &block
130
+ @inside_box = true
131
+ opts[:html] ||= {}
132
+ opts[:html][:class] = [*opts[:html][:class], "panel", "panel-default"]
133
+ content = capture_haml do
134
+ haml_tag :div, opts[:html] do
135
+ # haml_tag :div, :class => "caption" do
136
+ content = capture(&block)
137
+ haml_concat content
138
+ # end
139
+ end
140
+ end
141
+ @inside_box = false
142
+ content
143
+ end
144
+ end
145
+
146
+
147
+ module TabsHelper
148
+ def tabs
149
+ @tabs = {}
150
+
151
+ def tab name, options = {}, &block
152
+ @tabs[name] = options.merge :content => capture(&block)
153
+ end
154
+
155
+ yield
156
+
157
+ capture_haml do
158
+ haml_tag :ul, :class => "nav nav-tabs nav-justified", role: "tablist" do
159
+ @tabs.keys.each_with_index do |name, index|
160
+ haml_tag :li, :class => (index == 0 ? "active" : "") do
161
+ haml_concat link_to(name, "##{name}", "data-toggle" => "tab")
162
+ end
163
+ end
164
+ end
165
+
166
+ haml_tag :div, :class => "tab-content" do
167
+ @tabs.each_with_index do |(name, values), index|
168
+ haml_tag :div, :id => name, :class => ["tab-pane", (index == 0 ? "active" : "")] do
169
+ haml_concat values[:content]
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ module BreadCrumbHelper
178
+ def breadcrumb opts = {}
179
+ def format_class arg
180
+ arg.to_s.parameterize.underscore.downcase
181
+ end
182
+
183
+ elems = []
184
+ part_parents = []
185
+
186
+ elems << {:link => opts[:root], :label => "Root", :class => "root"} if opts[:root]
187
+
188
+
189
+ parents.compact.each do |elem|
190
+ part_parents << elem
191
+ elems << {:link => polymorphic_path([opts[:namespace], *part_parents]), :label => elem.to_desc, :class => "action_show #{format_class elem.class}"}
192
+ end
193
+
194
+
195
+ if params[:id]
196
+ elems << {:link => polymorphic_path([opts[:namespace], *part_parents, resource_class]), :label => resource_class.to_s.pluralize.titleize, :class => "action_index #{format_class resource_class}"}
197
+ elems << {:label => resource.to_desc, :active => true, :class => "action_#{params[:action]} #{format_class resource_class}"}
198
+ else
199
+ elems << {:label => resource_class.to_s.humanize.pluralize.titleize, :active => true, :class => "action_#{params[:action]} #{format_class resource_class}"}
200
+ end
201
+
202
+ "<ol class='breadcrumb'>#{elems.map { |elem|
203
+ html_class = elem[:class] || format_class(elem[:label])
204
+ elem[:active] ? "<li class='active #{html_class}'>#{elem[:label]}</li>" : "<li class='#{html_class}'>#{link_to elem[:label], elem[:link]}</li>"
205
+ }.join("")}</ol>".html_safe
206
+ end
207
+ end
208
+
209
+
210
+ module ProgressBarHelper
211
+ def progress prct
212
+ <<EOP
213
+ <div class="progress">
214
+ <div class="progress-bar" role="progressbar" aria-valuenow="#{prct}"
215
+ aria-valuemin="0" aria-valuemax="100"
216
+ style="width: #{prct}%;">
217
+ </div>
218
+ </div>
219
+ EOP
220
+ end
221
+ end
222
+
223
+
224
+ module WidgetsHelper
225
+ include BreadCrumbHelper
226
+ include BasicWidgetsHelper
227
+ include TabsHelper
228
+ include HeadersHelper
229
+ include ContainerHelper
230
+ include ProgressBarHelper
231
+ end
232
+
233
+ module ApplicationHelper
234
+ include WidgetsHelper
235
+
236
+ def parents
237
+ [*(respond_to?(:association_chain) ? (association_chain) : ([]))]
238
+ end
239
+
240
+ def basic_params
241
+ params.dup.keep_if {|k, _| [:action, :controller, /_id$/].any? {|elem| elem === k }}
242
+ end
243
+
244
+ def menu
245
+ params_dup = Hash[*params.keys.keep_if { |key| key.ends_with? "_id" }.map { |elem| [elem, nil] }.flatten]
246
+ (ActiveRecord::Base.connection.tables - ["schema_migrations"]).sort.map do |name|
247
+ url_for(params_dup.merge(:controller => name.pluralize, :action => :index)) rescue nil if can? :index, name.classify.constantize rescue nil
248
+ end.compact.map { |name| content_tag :li, (link_to (name.split('/').last || "Root").humanize, name) }.join.html_safe
249
+ end
250
+
251
+ def mime_types controller
252
+ mimes = []
253
+ (controller || "#{params[:controller].pluralize}_controller".classify.constantize).mimes_for_respond_to.each do |key, values|
254
+ mimes << key if (values[:only] == nil or values[:only].include? params[:action]) and (values[:expect] == nil or !values[:expect].include? params[:action])
255
+ end
256
+ mimes
257
+ end
258
+
259
+ def download_mime_type_btns controller = nil, &block
260
+ if download_mime_types(controller).size == 1
261
+ if block_given?
262
+ link = block.call(download_mime_types(controller).first) rescue nil
263
+ else
264
+ link = collection_url(format: download_mime_types(controller).first) rescue nil
265
+ end
266
+
267
+ button "Download as #{download_mime_types(controller).first}", link if link
268
+ elsif download_mime_types(controller).size > 1
269
+ res = link_to "#", :class => 'btn btn-default dropdown-toggle', :'data-toggle' => 'dropdown' do
270
+ ("Download " + content_tag(:span, "", :class => "caret")).html_safe
271
+ end
272
+ res + content_tag(:ul, :class => "dropdown-menu") do
273
+ download_mime_types(controller).map do |mime|
274
+ if block_given?
275
+ link = block.call(mime) rescue nil
276
+ else
277
+ link = collection_url(format: mime) rescue nil
278
+ end
279
+ content_tag :li, (link_to "as #{mime}", link) if link
280
+ end.join('').html_safe
281
+ end
282
+ end
283
+ end
284
+
285
+ def download_mime_types(controller)
286
+ @mimes = {}
287
+ @mimes[controller] ||= mime_types(controller)
288
+ @mimes[controller] & [:csv, :xls]
289
+ end
290
+
291
+ def sorted_header table, args = [], options = {}
292
+ table.header args, options do |header, text|
293
+ if @order_attributes and header.to_s.in? @order_attributes
294
+ params_dup = params.dup
295
+ params_dup[:order] = header
296
+ if params_dup[:desc]
297
+ params_dup.delete :desc
298
+ else
299
+ params_dup[:desc] = true if params[:order] == header.to_s
300
+ end
301
+ link_to text, url_for(params_dup)
302
+ else
303
+ text
304
+ end
305
+ end
306
+ end
307
+
308
+ def vertical_form_for(record, options={}, &block)
309
+ backup = SimpleForm.form_class
310
+ SimpleForm.form_class = options[:form_class]
311
+ result = simple_form_for(record, options, &block)
312
+ SimpleForm.form_class = backup
313
+ result
314
+ end
315
+
316
+ def associations klass, macro
317
+ klass = klass.to_s.classify.constantize if Symbol === klass
318
+
319
+ klass.reflections.select { |s, r| s and r.macro == macro }
320
+ end
321
+
322
+ end
323
+
324
+
325
+ module CollectionForHelper
326
+ module UniData
327
+ class Collection
328
+ attr_reader :rows, :meta
329
+
330
+ def initialize
331
+ @rows = []
332
+ @meta = {}
333
+ end
334
+
335
+ def add_meta key, meta
336
+ @meta[key] = meta
337
+ end
338
+
339
+ def try_add_meta key, meta
340
+ @meta[key] ||= meta
341
+ end
342
+
343
+ def has_meta? key
344
+ !@meta[key].nil?
345
+ end
346
+
347
+ def row &block
348
+ @rows << {:data => [], :meta => {}}
349
+
350
+ def add_elem elem
351
+ @rows.last[:data] << elem
352
+ end
353
+
354
+ def add_elem_meta key, value
355
+ @rows.last[:meta][key] = value
356
+ end
357
+
358
+ block.call self
359
+ end
360
+
361
+ def to_s
362
+ "rows: #{@rows} ; meta: #{@meta}"
363
+ end
364
+ end
365
+
366
+ class Element
367
+ @@vars = [:value, :name, :type, :link, :as, :translate, :db_type, :html]
368
+ attr_accessor *@@vars
369
+
370
+ def initialize opts = {}
371
+ @@vars.each do |var|
372
+ self.send("#{var}=", opts[var]) if opts[var]
373
+ end
374
+ end
375
+
376
+ def to_s
377
+ s = @@vars.map do |var|
378
+ "#{var}: #{self.send(var)}"
379
+ end.join(" ; ")
380
+
381
+ "(#{s})"
382
+ end
383
+ end
384
+ end
385
+
386
+ def collection_for collection, options = {}, &block
387
+ parser = nil
388
+
389
+ Parser::Base.types.each do |klass_collection, klass_parser|
390
+ parser = klass_parser.new collection, self, options if collection.is_a? klass_collection
391
+ end
392
+
393
+ return if parser.nil?
394
+
395
+ output = "Spyro::ActionViewExtension::CollectionForHelper::Output::#{(options[:output] || :table).to_s.classify}".constantize
396
+
397
+ if block_given?
398
+ block.call parser
399
+ end
400
+
401
+ parser.parse
402
+ # p parser.unicollection
403
+
404
+ output.new(parser.unicollection, self, parser.class).render
405
+ end
406
+ end
407
+
408
+ module CollectionForHelper
409
+ def sortable_header header
410
+ if controller.ordered_keys.include? header
411
+ params_dup = params.dup
412
+ default_sort = controller.ordered_keys.first
413
+ sort = params_dup.delete :sort
414
+ rev_sort = params_dup.delete :rev_sort
415
+ css_class = ''
416
+
417
+ if sort
418
+ css_class = "sort" if sort.to_sym == header.to_sym
419
+ elsif rev_sort
420
+ css_class = "sort rev" if rev_sort.to_sym == header.to_sym
421
+ elsif default_sort.to_sym == header.to_sym
422
+ css_class = "sort#{controller.ordered_options[:default] == :desc ? ' rev' : ''}"
423
+ end
424
+
425
+ params_dup[:rev_sort] = params_dup[:sort] = nil
426
+ if css_class == "sort"
427
+ params_dup[:rev_sort] = header
428
+ else
429
+ params_dup[:sort] = header
430
+ end
431
+
432
+ link_to header.to_s.humanize, url_for(params_dup), class: css_class
433
+ else
434
+ header.to_s.humanize
435
+ end
436
+ end
437
+
438
+ include CollectionForHelper::Parser
439
+ include CollectionForHelper::Output
440
+ end
441
+
442
+ module ApplicationHelper
443
+ include CollectionForHelper
444
+ end
445
+
446
+ module D3Helper
447
+ def d3pie data
448
+ data.map { |label, value| {label: label, value: value} }.to_json
449
+ end
450
+
451
+ def d3bar data, opts = {}
452
+ default_values = opts[:range].to_a.map { |i| {label: i, value: 0} } if opts[:range]
453
+
454
+ data.map do |key, values|
455
+ values = (values.map { |key, value| {label: key, value: value} } + (default_values || [])).uniq { |a| a[:label] }.sort { |a, b| a[:label] <=> b[:label] }
456
+ {key: key, values: values}
457
+ end.to_json
458
+ end
459
+
460
+ def d3line data, opts = {}
461
+ opts = {:field => :created_at, :interval => 3.hours}.merge opts
462
+ data.map do |key, values|
463
+ values = values.order(opts[:field]).pluck(opts[:field])
464
+ break {} if values.empty?
465
+ start, res, count = values.first, [[(values.first - opts[:interval]).to_i * 1000, 0]], 0
466
+ values.each do |vote|
467
+ if vote.between?(start, start + opts[:interval])
468
+ count += 1
469
+ else
470
+ res.push [start.to_i * 1000, count]
471
+ start, count = start + opts[:interval], 0
472
+ end
473
+ end
474
+ res.push [start.to_i * 1000, count], [(start + opts[:interval]).to_i * 1000, 0]
475
+ {key: key, values: res}
476
+ end.to_json
477
+ end
478
+ end
479
+
480
+ module ApplicationHelper
481
+ include D3Helper
482
+
483
+ def app_name
484
+ @cache_app_name ||= Rails.application.class.parent_name.underscore
485
+ end
486
+ end
487
+
488
+ module ProjectsHelper
489
+
490
+ def get_project_status_icon project, user = current_user, opts = {}
491
+ # @TODO mettre des vraies icons, fuck pour l'instant
492
+ ""
493
+ end
494
+
495
+ def get_project_status_text project, user = current_user, opts = {class: ""}
496
+ s = get_project_status(project, user, opts)
497
+ get_uncached_project_status_text(s, project)
498
+ end
499
+
500
+ def get_uncached_project_status_text status, project, opts = {class: ""}
501
+ case status
502
+ when :not_registered
503
+ "<span class='#{opts[:class]} #{opts[:class]}-default'>Not registered</span>"
504
+ when :searching_a_group
505
+ "<span class='#{opts[:class]} #{opts[:class]}-default'>searching a group</span>"
506
+ when :finished
507
+ "<span class='#{opts[:class]} #{opts[:class]}-success'>finished</span>"
508
+ when :in_progress
509
+ "<span class='#{opts[:class]} #{opts[:class]}-warning'>in progress</span>"
510
+ when :waiting_for_correction
511
+ "<span class='#{opts[:class]} #{opts[:class]}-success'>waiting for correction</span>"
512
+ when :waiting_to_start
513
+ "<span class='#{opts[:class]} #{opts[:class]}-default'>waiting to start (<span data-long-date='#{project.begin_at}'></span>)</span>"
514
+ when :in_progress
515
+ "<span class='#{opts[:class]} #{opts[:class]}-warning'>in progress</span>"
516
+ when :creating_group
517
+ "<span class='#{opts[:class]} #{opts[:class]}-default'>creating group</span>"
518
+ when :parent
519
+ "<span class='project-status-icon project-status-parent'></span>"
520
+ else
521
+ "<span class='project-status-icon project-status-no-idea'></span>"
522
+ end
523
+ end
524
+
525
+
526
+ # Va renvoyer juste le texte, avec du cache
527
+ def get_project_status project, user = current_user, opts = {}
528
+ cache [project, user.id, (!project.begin_at.nil? and project.begin_at.future?), opts] do
529
+ get_uncached_project_status project, current_user, opts
530
+ end
531
+ end
532
+
533
+
534
+ # Va renvoyer juste le texte
535
+ def get_uncached_project_status project, user = current_user, opts = {}
536
+ raise "The hash need has_children, team and pu keys" unless (opts.has_key?(:has_children) and opts.has_key?(:team) and opts.has_key?(:pu))
537
+ if opts[:pu].nil?
538
+ :not_registered
539
+ elsif opts[:team].nil? and not opts[:has_children]# and user.teams.where(:project_id => project.id).empty?
540
+ :searching_a_group
541
+ elsif opts[:team]
542
+ if opts[:team].closed?
543
+ if opts[:team].final_mark
544
+ :finished
545
+ elsif opts[:has_children]
546
+ :in_progress
547
+ else
548
+ :waiting_for_correction
549
+ end
550
+ elsif opts[:team].locked?
551
+ if project.begin_at and project.begin_at.future?
552
+ :waiting_to_start
553
+ else
554
+ :in_progress
555
+ end
556
+ else
557
+ :creating_group
558
+ end
559
+
560
+ elsif opts[:has_children]
561
+ :parent
562
+ else
563
+ :unknown
564
+ end
565
+ end
566
+
567
+ # View Helper.......
568
+ def project_progress_percentage project, user = User.current_user
569
+ progr = {:recommended_pct => 0, :warning_pct => 0, :success_pct => 0}
570
+
571
+ has_estimations = (project.estimate_time)
572
+ has_begin_at = !project.begin_at.nil?
573
+ has_project_user = project.ar_projects_user(user).any?
574
+
575
+ return progr unless (has_estimations or project.begin_at) and has_project_user
576
+
577
+ if project.begin_at
578
+ start = Time.zone.now.to_i - project.begin_at.to_i
579
+ progr[:recommended_pct] = ((start * 100) / (project.end_at.to_i - project.begin_at.to_i))
580
+ progr[:recommended_pct] = 100 if progr[:recommended_pct] > 100
581
+ else
582
+ start = Time.zone.now - project.ar_projects_user(user).first.created_at
583
+ progr[:recommended_pct] = (start * 100) / project.estimate_time
584
+
585
+ if progr[:recommended_pct] > 100
586
+ progr[:recommended_pct] = ((project.estimate_time * 100) / project.estimate_time)
587
+ progr[:warning_pct] = ((start * 100) / project.estimate_time) - progr[:recommended_pct]
588
+ progr[:warning_pct] = 100 - progr[:recommended_pct] if progr[:warning_pct] > (100 - progr[:recommended_pct])
589
+ end
590
+
591
+ if project.finished?
592
+ progr[:success_pct] = 100 - (progr[:recommended_pct] + progr[:warning_pct])
593
+ end
594
+
595
+ end
596
+
597
+ return progr
598
+ end
599
+
600
+ end
601
+
602
+
603
+ module ReleaseHelper
604
+
605
+ # Helper de l'enfer
606
+ def release_at date, &block
607
+ if date.past? or not Rails.env.production? or User.current_user.try(:has_role?, :intrateam)
608
+ block.call
609
+ end
610
+ end
611
+ end
612
+
613
+ module ForumHelper
614
+ def url_for_message m
615
+ base = "#{Rails.application.config.module_url['forum']}/topics/#{m.messageable_id}/messages"
616
+ page, ref = m.get_coordinates(can?(:read_deleted, Message))
617
+ return "#{base}?page=#{page}##{ref}"
618
+ end
619
+ end
620
+
621
+ module FeatureHelper
622
+ def feature name, &block
623
+ if User.current_user.has_feature? name
624
+ capture(&block)
625
+ else
626
+ ""
627
+ end
628
+ end
629
+ end
630
+
631
+ module EtecHelper
632
+ def user_eta user
633
+ user_experiences = Experience.joins(:skill).where(user_id: user.id).where(cursus_id: 1).order(:created_at)
634
+
635
+ # TROLOLO
636
+ return Time.zone.now + 150.years if user_experiences.empty?
637
+
638
+ xp = user_experiences.sum(:experience) # nb d'XP total aujourd'hui pour l'étudiant
639
+ first_xp = user_experiences.first.experience # la premiere entree d'XP pour un etudiant (la plus ancienne donc)
640
+ date_first_xp = user_experiences.first.created_at # la date de cette premiere entree
641
+
642
+ estimated_end_date_from_xp(xp, first_xp, date_first_xp)
643
+ end
644
+
645
+ def estimated_end_date_from_xp xp, first_xp, date_first_xp
646
+ return Time.zone.now + 150.years if date_first_xp.nil?
647
+
648
+ current_weekly_xp = Math.log(1 + xp.to_f / 50 / 180) * 235
649
+ first_weekly_xp = Math.log(1 + first_xp.to_f / 50 / 180) * 235
650
+
651
+ weeks = (Time.zone.now - date_first_xp).to_i / 1.week
652
+ weeks = 1 if weeks == 0
653
+ ratio = (current_weekly_xp - first_weekly_xp) / weeks
654
+ if ratio > 0
655
+ ((182 - current_weekly_xp) / ratio).weeks.from_now
656
+ else
657
+ Time.zone.now + 150.years
658
+ end
659
+ end
660
+
661
+ def user_accel_eta user
662
+ user_experiences = Experience.where(user_id: user.id).where(cursus_id: 1).order(:created_at)
663
+ xp = user_experiences.sum(:experience) # nb d'XP total aujourd'hui pour l'étudiant
664
+ earlier_xp = user_experiences.where("created_at < ?", 5.weeks.ago).sum(:experience) # nb d'XP qu'avait l'etudiant il y a 5 semaines
665
+ first_xp = user_experiences.first.experience # la premiere entree d'XP pour un etudiant (la plus ancienne donc)
666
+ date_first_xp = user_experiences.first.created_at # la date de cette premiere entree
667
+
668
+ estimated_accel_from_xp(xp, earlier_xp, first_xp, date_first_xp)
669
+ end
670
+
671
+ def estimated_accel_from_xp xp, earlier_xp, first_xp, date_first_xp
672
+ current_weekly_xp = Math.log(1 + xp.to_f / 50 / 180) * 235
673
+ earlier_weekly_xp = Math.log(1 + earlier_xp.to_f / 50 / 180) * 235
674
+ first_weekly_xp = Math.log(1 + first_xp.to_f / 50 / 180) * 235
675
+
676
+ ratio = (current_weekly_xp - earlier_weekly_xp) / 5
677
+ if ratio > 0
678
+ date1 = ((182 - current_weekly_xp) / ratio).weeks.from_now
679
+ else
680
+ date1 = nil
681
+ end
682
+
683
+ date2 = estimated_end_date_from_xp(xp, first_xp, date_first_xp)
684
+
685
+ if (date2 - date1).weeks > 4
686
+ 1
687
+ elsif (date2 - date1).weeks < -4
688
+ -1
689
+ else
690
+ 0
691
+ end
692
+ end
693
+ end
694
+
695
+ module PageTitle
696
+ def page_title key, options
697
+ prefix = I18n.t('page_title.prefix', :raise => true) rescue []
698
+ suffix = I18n.t('page_title.suffix', :raise => true) rescue []
699
+
700
+ key = [*key]
701
+ title = ""
702
+ while key.any? and title.blank?
703
+ title = I18n.t(['page_title', *key].join('.'), :raise => true) rescue nil
704
+ key.pop if key.last == "default"
705
+ key[-1] = "default" if key.any?
706
+ end
707
+
708
+ title = (title.blank? ? options[:default] : [prefix, title, suffix].flatten.compact.join(options[:separator] || ' - '))
709
+ title.gsub(/\{\{([^\}]+)\}\}/) { $1.split('.').inject(self) { |base, method| base.send(method) } }
710
+ rescue
711
+ options[:default].gsub(/\{\{([^\}]+)\}\}/) { $1.split('.').inject(self) { |base, method| base.send(method) } }
712
+ end
713
+ end
714
+
715
+ module ApplicationHelper
716
+ include ProjectsHelper
717
+ include ReleaseHelper
718
+ include ForumHelper
719
+ include FeatureHelper
720
+ include EtecHelper
721
+ include PageTitle
722
+ end
723
+
724
+ include ApplicationHelper
725
+ end
726
+ end