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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +37 -0
- data/app/assets/OLDjavascripts/application.js +25 -0
- data/app/assets/OLDjavascripts/components.es6 +10 -0
- data/app/assets/OLDjavascripts/components/base/base.es6 +197 -0
- data/app/assets/OLDjavascripts/components/base/collection.es6 +199 -0
- data/app/assets/OLDjavascripts/components/base/element.es6 +134 -0
- data/app/assets/OLDjavascripts/components/base/field.es6 +26 -0
- data/app/assets/OLDjavascripts/components/base/inputs/text.es6 +21 -0
- data/app/assets/OLDjavascripts/components/base/pagination.es6 +23 -0
- data/app/assets/OLDjavascripts/vendor/lodash.js +121 -0
- data/app/assets/OLDjavascripts/vendor/moment-twitter.js +86 -0
- data/app/assets/OLDjavascripts/vendor/moment.js +80 -0
- data/app/assets/OLDjavascripts/vendor/pluralize.js +433 -0
- data/app/views/base/_associations.html.haml +11 -0
- data/app/views/base/_custom.html.haml +0 -0
- data/app/views/base/_details.html.haml +3 -0
- data/app/views/base/_edit_header.html.haml +3 -0
- data/app/views/base/_fields.html.haml +11 -0
- data/app/views/base/_form.html.haml +6 -0
- data/app/views/base/_index_header.html.haml +4 -0
- data/app/views/base/_new_header.html.haml +3 -0
- data/app/views/base/_other_form_fields.html.haml +0 -0
- data/app/views/base/_show_header.html.haml +9 -0
- data/app/views/base/_upload_fields.html.haml +5 -0
- data/app/views/base/copy.html.haml +10 -0
- data/app/views/base/edit.html.haml +4 -0
- data/app/views/base/index.csv.haml +8 -0
- data/app/views/base/index.html.haml +7 -0
- data/app/views/base/index.js.haml +2 -0
- data/app/views/base/index.xls.ruby +12 -0
- data/app/views/base/mailer.html.haml +46 -0
- data/app/views/base/new.html.haml +4 -0
- data/app/views/base/notify.html.haml +11 -0
- data/app/views/base/show.html.haml +8 -0
- data/app/views/base/stats.html.haml +36 -0
- data/app/views/base/trombi.html.haml +1 -0
- data/app/views/base/upload.html.haml +24 -0
- data/config/initializers/assets.rb +1 -0
- data/config/routes.rb +2 -0
- data/lib/spyro.rb +24 -0
- data/lib/spyro/active_record_add_on.rb +38 -0
- data/lib/spyro/application_controller_add_on.rb +24 -0
- data/lib/spyro/collections/outputs/admin_table.rb +28 -0
- data/lib/spyro/collections/outputs/bar_graph_table.rb +46 -0
- data/lib/spyro/collections/outputs/base.rb +23 -0
- data/lib/spyro/collections/outputs/csv.rb +34 -0
- data/lib/spyro/collections/outputs/fields.rb +118 -0
- data/lib/spyro/collections/outputs/flatui_table.rb +40 -0
- data/lib/spyro/collections/outputs/inplace_table.rb +29 -0
- data/lib/spyro/collections/outputs/map.rb +22 -0
- data/lib/spyro/collections/outputs/table.rb +126 -0
- data/lib/spyro/collections/outputs/xlsx.rb +39 -0
- data/lib/spyro/collections/parsers/active_ldap_relation.rb +22 -0
- data/lib/spyro/collections/parsers/active_record_relation.rb +138 -0
- data/lib/spyro/collections/parsers/array.rb +38 -0
- data/lib/spyro/collections/parsers/base.rb +60 -0
- data/lib/spyro/collections/parsers/kaminari_array.rb +38 -0
- data/lib/spyro/collections/parsers/model.rb +31 -0
- data/lib/spyro/controllers/strong_parameted.rb +33 -0
- data/lib/spyro/engine.rb +6 -0
- data/lib/spyro/filters_controller_add_on.rb +161 -0
- data/lib/spyro/helpers/action_view_extension.rb +726 -0
- data/lib/spyro/namespace_template_inheritance.rb +30 -0
- data/lib/spyro/usefull_attributes.rb +14 -0
- data/lib/spyro/version.rb +3 -0
- data/lib/tasks/spyro_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/spyro_test.rb +7 -0
- data/test/test_helper.rb +21 -0
- 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
|
data/lib/spyro/engine.rb
ADDED
|
@@ -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
|