ab_admin 0.8.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +11 -5
  4. data/app/assets/javascripts/ab_admin/components/admin_assets.js.coffee +0 -25
  5. data/app/assets/javascripts/ab_admin/components/google_translate.js.coffee +3 -5
  6. data/app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee +33 -25
  7. data/app/assets/javascripts/ab_admin/core/batch_actions.js.coffee +1 -1
  8. data/app/assets/javascripts/ab_admin/core/columns_hider.js.coffee +24 -23
  9. data/app/assets/javascripts/ab_admin/core/init.js.coffee +7 -2
  10. data/app/assets/javascripts/ab_admin/core/search_form.js.coffee +1 -7
  11. data/app/assets/javascripts/ab_admin/core/ui_utils.js.coffee +23 -7
  12. data/app/assets/javascripts/ab_admin/core/utils.js.coffee +16 -2
  13. data/app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee +1 -0
  14. data/app/assets/javascripts/ab_admin/main.js +3 -4
  15. data/app/assets/stylesheets/ab_admin/bootstrap_and_overrides.scss +71 -25
  16. data/app/assets/stylesheets/ab_admin/components/_base.scss +21 -1
  17. data/app/assets/stylesheets/ab_admin/components/_colored_tabs.scss +1 -1
  18. data/app/assets/stylesheets/ab_admin/components/_form.scss +16 -18
  19. data/app/assets/stylesheets/ab_admin/components/_grid_view.scss +2 -2
  20. data/app/assets/stylesheets/ab_admin/components/_locale_tabs.scss +11 -23
  21. data/app/assets/stylesheets/ab_admin/components/_navigation.scss +7 -11
  22. data/app/assets/stylesheets/ab_admin/components/_table_view.scss +85 -11
  23. data/app/assets/stylesheets/ab_admin/components/_tooltip.scss +81 -0
  24. data/app/assets/stylesheets/ab_admin/components/_tree_view.scss +1 -1
  25. data/app/assets/stylesheets/ab_admin/devise.scss +2 -2
  26. data/app/assets/stylesheets/ab_admin/fileupload.scss +2 -9
  27. data/app/assets/stylesheets/ab_admin/main.scss +1 -2
  28. data/app/controllers/admin/assets_controller.rb +1 -1
  29. data/app/controllers/admin/base_controller.rb +133 -149
  30. data/app/controllers/admin/dashboards_controller.rb +2 -2
  31. data/app/controllers/admin/locators_controller.rb +8 -6
  32. data/app/controllers/admin/manager_controller.rb +19 -49
  33. data/app/controllers/admin/static_pages_controller.rb +0 -4
  34. data/app/controllers/admin/structures_controller.rb +2 -2
  35. data/app/views/ab_admin/devise/sessions/new.html.slim +2 -0
  36. data/app/views/admin/assets/batch_edit.html.slim +3 -2
  37. data/app/views/admin/base/_search_layout.html.slim +7 -6
  38. data/app/views/admin/base/create.js.erb +6 -3
  39. data/app/views/admin/base/edit.js.erb +1 -1
  40. data/app/views/admin/base/index.html.slim +4 -4
  41. data/app/views/admin/base/new.js.erb +1 -1
  42. data/app/views/admin/base/update.js.erb +7 -2
  43. data/app/views/admin/fileupload/_asset_templates.html.slim +1 -2
  44. data/app/views/admin/fileupload/_image.html.slim +1 -2
  45. data/app/views/admin/locators/edit.html.slim +7 -6
  46. data/app/views/admin/manager/_map.html.slim +4 -0
  47. data/app/views/admin/manager/_show_table.html.slim +1 -1
  48. data/app/views/admin/manager/_stats.html.slim +4 -0
  49. data/app/views/admin/manager/_table.html.slim +7 -4
  50. data/app/views/admin/shared/_content_actions.html.slim +35 -30
  51. data/app/views/admin/shared/_flash.html.slim +5 -4
  52. data/app/views/admin/shared/_locale_tabs.html.slim +2 -2
  53. data/app/views/admin/shared/_main_menu.html.slim +1 -1
  54. data/app/views/admin/shared/_save_buttons.html.slim +10 -1
  55. data/app/views/admin/structures/_form.html.slim +1 -1
  56. data/app/views/admin/users/_form.html.slim +3 -3
  57. data/app/views/admin/users/_search_form.html.slim +1 -1
  58. data/app/views/layouts/admin/_footer.html.slim +0 -1
  59. data/app/views/layouts/admin/_navigation.html.slim +1 -1
  60. data/app/views/layouts/admin/application.html.slim +2 -2
  61. data/config/locales/de.yml +1 -2
  62. data/config/locales/en.yml +9 -15
  63. data/config/locales/it.yml +1 -0
  64. data/config/locales/ru.yml +0 -1
  65. data/config/locales/uk.yml +0 -1
  66. data/db/migrate/20130101000001_create_users.rb +1 -4
  67. data/db/migrate/20130101000003_create_assets.rb +1 -1
  68. data/db/migrate/20130101000004_create_headers.rb +5 -5
  69. data/db/migrate/20130101000005_create_static_pages.rb +2 -5
  70. data/db/migrate/20130101000006_create_structures.rb +1 -1
  71. data/db/migrate/20130101000007_base_translations.rb +43 -12
  72. data/db/migrate/20130101000008_create_admin_comments.rb +2 -7
  73. data/db/migrate/20130101000009_create_tracks.rb +4 -8
  74. data/lib/ab_admin/abstract_resource.rb +6 -5
  75. data/lib/ab_admin/carrierwave/base_uploader.rb +87 -75
  76. data/lib/ab_admin/carrierwave/glue.rb +0 -5
  77. data/lib/ab_admin/concerns/admin_addition.rb +15 -27
  78. data/lib/ab_admin/concerns/translations_macro.rb +97 -0
  79. data/lib/ab_admin/concerns/utilities.rb +2 -2
  80. data/lib/ab_admin/config/base.rb +27 -4
  81. data/lib/ab_admin/controllers/callbacks.rb +3 -26
  82. data/lib/ab_admin/core_ext/array.rb +1 -50
  83. data/lib/ab_admin/core_ext/hash.rb +2 -31
  84. data/lib/ab_admin/core_ext/other.rb +0 -6
  85. data/lib/ab_admin/core_ext/string.rb +1 -86
  86. data/lib/ab_admin/devise.rb +7 -0
  87. data/lib/ab_admin/engine.rb +2 -1
  88. data/lib/ab_admin/hooks/ckeditor_lazy.rb +13 -0
  89. data/lib/ab_admin/hooks/will_paginate_id_prefetch.rb +8 -6
  90. data/lib/ab_admin/hooks/will_paginate_no_uri.rb +1 -1
  91. data/lib/ab_admin/i18n_tools/google_translate.rb +3 -1
  92. data/lib/ab_admin/i18n_tools/model_translator.rb +1 -1
  93. data/lib/ab_admin/menu/base_group.rb +0 -1
  94. data/lib/ab_admin/menu/group.rb +2 -4
  95. data/lib/ab_admin/menu/item.rb +4 -8
  96. data/lib/ab_admin/models/asset.rb +7 -10
  97. data/lib/ab_admin/models/header.rb +2 -2
  98. data/lib/ab_admin/models/locator.rb +29 -3
  99. data/lib/ab_admin/models/settings.rb +2 -2
  100. data/lib/ab_admin/models/structure.rb +3 -3
  101. data/lib/ab_admin/models/track.rb +15 -3
  102. data/lib/ab_admin/models/user.rb +12 -48
  103. data/lib/ab_admin/utils/csv_document.rb +8 -6
  104. data/lib/ab_admin/utils/eval_helpers.rb +0 -13
  105. data/lib/ab_admin/utils/logger.rb +12 -2
  106. data/lib/ab_admin/utils/mysql.rb +2 -3
  107. data/lib/ab_admin/utils/xls_document.rb +18 -18
  108. data/lib/ab_admin/utils.rb +0 -5
  109. data/lib/ab_admin/version.rb +1 -1
  110. data/lib/ab_admin/views/admin_helpers.rb +43 -28
  111. data/lib/ab_admin/views/admin_navigation_helpers.rb +18 -16
  112. data/lib/ab_admin/views/form_builder.rb +7 -5
  113. data/lib/ab_admin/views/helpers.rb +0 -9
  114. data/lib/ab_admin/views/inputs/ckeditor_input.rb +1 -5
  115. data/lib/ab_admin/views/manager_helpers.rb +15 -6
  116. data/lib/ab_admin/views/search_form_builder.rb +13 -13
  117. data/lib/ab_admin/views/will_paginate_bootstrap_renderer.rb +60 -0
  118. data/lib/ab_admin.rb +44 -32
  119. data/lib/generators/ab_admin/glob/glob_generator.rb +4 -5
  120. data/lib/generators/ab_admin/glob/templates/migration.erb +10 -7
  121. data/lib/generators/ab_admin/install/templates/config/ab_admin.rb.erb +1 -1
  122. data/lib/generators/ab_admin/install/templates/models/user.rb +1 -13
  123. data/lib/generators/ab_admin/install/templates/spec/spec_helper.rb +0 -1
  124. data/lib/generators/ab_admin/install/templates/spec/support/database_cleaner.rb +8 -11
  125. data/lib/generators/ab_admin/install/templates/uploaders/attachment_file_uploader.rb +1 -1
  126. data/lib/generators/ab_admin/install/templates/uploaders/avatar_uploader.rb +1 -1
  127. data/lib/generators/ab_admin/install/templates/uploaders/picture_uploader.rb +16 -3
  128. data/lib/generators/ab_admin/model/model_generator.rb +3 -4
  129. data/lib/generators/ab_admin/model/templates/resource.erb +5 -2
  130. data/lib/generators/ab_admin/resource/resource_generator.rb +0 -4
  131. data/lib/generators/ab_admin/resource/templates/controller.erb +2 -9
  132. data/lib/tasks/assets.rake +5 -5
  133. metadata +45 -85
  134. data/app/assets/images/admin/Jcrop.gif +0 -0
  135. data/app/assets/images/admin/flags/de.png +0 -0
  136. data/app/assets/images/admin/flags/en.png +0 -0
  137. data/app/assets/images/admin/flags/es.png +0 -0
  138. data/app/assets/images/admin/flags/fr.png +0 -0
  139. data/app/assets/images/admin/flags/it.png +0 -0
  140. data/app/assets/images/admin/flags/ja.png +0 -0
  141. data/app/assets/images/admin/flags/pl.png +0 -0
  142. data/app/assets/images/admin/flags/ru.png +0 -0
  143. data/app/assets/images/admin/flags/uk.png +0 -0
  144. data/app/assets/javascripts/ab_admin/components/croppable_image.js.coffee +0 -33
  145. data/app/assets/stylesheets/ab_admin/components/_columns_hider.scss +0 -5
  146. data/app/assets/stylesheets/ab_admin/components/_perms.scss +0 -39
  147. data/app/views/admin/shared/_columns_hider.html.slim +0 -9
  148. data/lib/ab_admin/hooks/globalize_locale_suffix_accessors.rb +0 -25
  149. data/lib/ab_admin/hooks/globalize_valid_locale.rb +0 -9
  150. data/lib/generators/ab_admin/ckeditor_assets/ckeditor_assets_generator.rb +0 -19
  151. data/lib/generators/template.rb +0 -96
@@ -1,15 +1,46 @@
1
- class BaseTranslations < ActiveRecord::Migration
2
- def self.up
3
- StaticPage.create_translation_table! title: :string, content: :text
4
- Header.create_translation_table! title: :string, h1: :string, keywords: :string, description: :text, seo_block: :text
5
- Structure.create_translation_table! title: :string, redirect_url: :string
6
- Asset.create_translation_table! name: :string, alt: :string
7
- end
1
+ class BaseTranslations < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :static_page_translations do |t|
4
+ t.references :static_page, null: false
5
+ t.string :locale, limit: 5, null: false
6
+ t.string :title
7
+ t.text :content
8
+
9
+ t.timestamps
10
+ end
11
+ add_index :static_page_translations, [:static_page_id, :locale], unique: true, name: 'static_pages_ts_static_page_id_locale'
12
+
13
+ create_table :header_translations do |t|
14
+ t.references :header, null: false
15
+ t.string :locale, limit: 5, null: false
16
+ t.string :title
17
+ t.string :h1
18
+ t.string :keywords
19
+ t.text :description
20
+ t.text :seo_block
21
+
22
+ t.timestamps
23
+ end
24
+ add_index :header_translations, [:header_id, :locale], unique: true, name: 'headers_ts_header_id_locale'
25
+
26
+ create_table :structure_translations do |t|
27
+ t.references :structure, null: false
28
+ t.string :locale, limit: 5, null: false
29
+ t.string :title
30
+ t.string :redirect_url
31
+
32
+ t.timestamps
33
+ end
34
+ add_index :structure_translations, [:structure_id, :locale], unique: true, name: 'structures_ts_structure_id_locale'
35
+
36
+ create_table :asset_translations do |t|
37
+ t.references :asset, null: false
38
+ t.string :locale, limit: 5, null: false
39
+ t.string :name
40
+ t.string :alt
8
41
 
9
- def self.down
10
- StaticPage.drop_translation_table!
11
- Header.drop_translation_table!
12
- Structure.drop_translation_table!
13
- Asset.drop_translation_table!
42
+ t.timestamps
43
+ end
44
+ add_index :asset_translations, [:asset_id, :locale], unique: true, name: 'assets_ts_asset_id_locale'
14
45
  end
15
46
  end
@@ -1,18 +1,13 @@
1
- class CreateAdminComments < ActiveRecord::Migration
1
+ class CreateAdminComments < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :admin_comments do |t|
4
4
  t.references :user
5
5
  t.string :user_name
6
- t.integer :resource_id, null: false
7
- t.string :resource_type, limit: 50, null: false
6
+ t.references :resource, polymorphic: true
8
7
  t.references :resource_user
9
8
  t.text :body
10
9
 
11
10
  t.timestamps
12
11
  end
13
-
14
- add_index :admin_comments, :user_id
15
- add_index :admin_comments, :resource_user_id
16
- add_index :admin_comments, [:resource_type, :resource_id]
17
12
  end
18
13
  end
@@ -1,20 +1,16 @@
1
- class CreateTracks < ActiveRecord::Migration
1
+ class CreateTracks < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :tracks do |t|
4
4
  t.string :name
5
5
  t.string :key
6
- t.belongs_to :trackable, polymorphic: true
7
- t.belongs_to :user
8
- t.belongs_to :owner
6
+ t.references :trackable, polymorphic: true
7
+ t.references :user
8
+ t.references :owner
9
9
  t.column :trackable_changes, :mediumtext
10
10
  t.text :parameters
11
11
 
12
12
  t.timestamps
13
13
  end
14
-
15
- add_index :tracks, [:trackable_type, :trackable_id]
16
- add_index :tracks, :owner_id
17
- add_index :tracks, :user_id
18
14
  add_index :tracks, :key
19
15
  end
20
16
  end
@@ -6,14 +6,15 @@ module AbAdmin
6
6
  ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy, :preview, :batch, :rebuild, :custom_action, :history]
7
7
  end
8
8
 
9
- attr_accessor :model, :table, :search, :export, :form, :chart, :modal_form, :show, :preview_path, :actions, :custom_settings,
10
- :batch_action_list, :action_items, :disabled_action_items, :resource_action_items, :tree_node_renderer,
9
+ attr_accessor :model, :table, :search, :export, :form, :chart, :stats, :map, :modal_form, :show,
10
+ :preview_path, :actions, :custom_settings,
11
+ :batch_actions, :action_items, :disabled_action_items, :resource_action_items, :tree_node_renderer,
11
12
  :parent_resources, :custom_actions, :permitted_params, :scopes
12
13
 
13
14
  def initialize
14
15
  @actions = ACTIONS
15
16
  @custom_settings = {}
16
- @batch_action_list = [AbAdmin::Config::BatchAction.new(:destroy, confirm: I18n.t('admin.delete_confirmation'))]
17
+ @batch_actions= [AbAdmin::Config::BatchAction.new(:destroy, confirm: I18n.t('admin.delete_confirmation'))]
17
18
  @action_items = []
18
19
  @disabled_action_items = []
19
20
  @default_action_items_for = {}
@@ -87,9 +88,9 @@ module AbAdmin
87
88
 
88
89
  def batch_action(name, options={}, &block)
89
90
  if options
90
- instance.batch_action_list << AbAdmin::Config::BatchAction.new(name.to_sym, options, &block)
91
+ instance.batch_actions << AbAdmin::Config::BatchAction.new(name.to_sym, options, &block)
91
92
  else
92
- instance.batch_action_list.reject!{|a| a.name == name.to_sym }
93
+ instance.batch_actions.reject!{|a| a.name == name.to_sym }
93
94
  end
94
95
  end
95
96
 
@@ -1,4 +1,3 @@
1
- require 'mime/types'
2
1
  require 'mini_magick'
3
2
  require 'carrierwave/processing/mini_magick'
4
3
 
@@ -8,8 +7,7 @@ module AbAdmin
8
7
  include ::CarrierWave::MiniMagick
9
8
  include AbAdmin::Utils::EvalHelpers
10
9
 
11
- class_attribute :transliterate, :human_filenames
12
- self.transliterate = true
10
+ class_attribute :human_filenames
13
11
  self.human_filenames = true
14
12
 
15
13
  attr_accessor :internal_identifier
@@ -26,41 +24,48 @@ module AbAdmin
26
24
 
27
25
  process :set_model_info
28
26
 
29
- def strict_filename(for_file=filename)
30
- "#{version_name || secure_token}#{File.extname(for_file).downcase}"
27
+ def filename
28
+ "#{[human_part, secure_token].compact.join('_')}#{extension}"
31
29
  end
32
30
 
33
- def save_original_name(file)
34
- model.original_name ||= file.original_filename if file.respond_to?(:original_filename)
31
+ def full_filename(*)
32
+ return filename unless version_name
33
+ base = "#{version_filename_part}#{version_extension}"
34
+ return base unless human_filenames
35
+ [human_part, base].compact.join('_')
35
36
  end
36
37
 
37
- def base_filename_part
38
- return if version_name == :default
39
- return secure_token unless version_name
40
- version_name.to_s.start_with?('retina_') ? "#{version_name.to_s.sub(/^retina_/, '')}@2x" : version_name.to_s
38
+ def human_part
39
+ normalize_filename(model.send("#{mounted_as}_file_name").to_s.strip.remove(/\.\w+$/)).remove(secure_token).chomp('_').presence
41
40
  end
42
41
 
43
- def full_filename(for_file=filename)
44
- human_filenames ? human_full_filename(for_file) : strict_filename(for_file)
42
+ def extension
43
+ File.extname(model.original_name).downcase
45
44
  end
46
45
 
47
- def human_full_filename(for_file=filename)
48
- ext = File.extname(for_file)
49
- system_part = base_filename_part
50
- human_filename_part = for_file.chomp(ext)
51
- return "#{system_part || version_name}#{ext}" if human_filename_part == secure_token
52
- system_part ? "#{human_filename_part}_#{system_part}#{ext}" : "#{human_filename_part}#{ext}"
46
+ def version_extension
47
+ webp? ? '.webp' : extension
53
48
  end
54
49
 
55
- def full_original_filename
56
- "#{base_filename_part}#{File.extname(store_filename)}"
50
+ def version_filename_part
51
+ return secure_token unless version_name
52
+ strict_version_name = version_name.to_s.remove('retina_').remove('_webp')
53
+ strict_version_name = nil if strict_version_name.to_sym == :default
54
+ "#{strict_version_name}#{'@2x' if retina?}"
57
55
  end
58
56
 
59
- # use secure token in the filename for non processed image
60
57
  def secure_token
61
58
  model.data_secure_token ||= AbAdmin.friendly_token(20).downcase
62
59
  end
63
60
 
61
+ def retina?
62
+ version_name.to_s.start_with?('retina_')
63
+ end
64
+
65
+ def webp?
66
+ version_name.to_s.end_with?('_webp')
67
+ end
68
+
64
69
  def store_model_filename(record)
65
70
  old_file_name = filename
66
71
  new_file_name = model_filename(old_file_name, record)
@@ -68,69 +73,81 @@ module AbAdmin
68
73
  rename_via_move(new_file_name)
69
74
  end
70
75
 
71
- alias_method :store_filename, :filename
72
-
73
- def filename
74
- internal_identifier || model.send("#{mounted_as}_file_name") || (store_filename && "#{secure_token}#{File.extname(store_filename).downcase}")
75
- end
76
-
77
- def write_internal_identifier(internal_identifier)
78
- self.internal_identifier = internal_identifier
79
- versions.values.each{|v| v.internal_identifier = internal_identifier }
76
+ def save_original_name(file)
77
+ model.original_name ||= file.original_filename if file.respond_to?(:original_filename)
80
78
  end
81
79
 
82
- # transliterate original filename
83
- # allow to build custom filename
84
80
  def model_filename(base_filename, record)
85
81
  custom_file_name = model.build_filename(base_filename, record)
86
82
  return unless custom_file_name
87
- normalize_filename(custom_file_name) + File.extname(base_filename).downcase
83
+ normalize_filename(custom_file_name)
88
84
  end
85
+ private :model_filename
89
86
 
90
87
  def normalize_filename(raw_filename)
91
- parameterize_args = ActiveSupport::VERSION::MAJOR > 4 ? {separator: '_'} : '_'
92
- I18n.transliterate(raw_filename).parameterize(parameterize_args).gsub(/[\-_]+/, '_').downcase
93
- end
94
-
95
- # rename files via move
96
- def rename_via_move(new_file_name)
97
- if human_filenames
98
- dir = File.dirname(path)
99
-
100
- moves = []
101
- versions.values.unshift(self).each do |v|
102
- from_path = File.join(dir, v.full_filename)
103
- to_path = File.join(dir, v.full_filename(new_file_name))
104
- next if from_path == to_path || !File.exists?(from_path)
105
- moves << [from_path, to_path]
106
- end
107
- moves.each { |move| FileUtils.mv(*move) }
88
+ I18n.transliterate(raw_filename.unicode_normalize).parameterize(separator: '_').gsub(/[\-_]+/, '_').downcase
89
+ end
90
+
91
+ def rename_via_move(new_filename)
92
+ dir = File.dirname(path)
93
+ old_names = versions.values.unshift(self).map(&:full_filename)
94
+ model.send("#{mounted_as}_file_name=", "#{[new_filename.presence, secure_token].compact.join('_')}#{extension}")
95
+ new_names = versions.values.unshift(self).map(&:full_filename)
96
+ old_names.zip(new_names).each do |old_name, new_name|
97
+ old_path, new_path = File.join(dir, old_name), File.join(dir, new_name)
98
+ next if old_path == new_path || !File.exist?(old_path)
99
+ FileUtils.mv(old_path, new_path)
108
100
  end
109
-
110
- write_internal_identifier new_file_name
111
- model.send("write_#{mounted_as}_identifier")
112
- retrieve_from_store!(new_file_name) if human_filenames
113
-
114
- new_file_name
101
+ retrieve_from_store!(model.send("#{mounted_as}_file_name"))
115
102
  end
116
103
 
117
- private :write_internal_identifier, :store_filename, :model_filename
118
-
119
- def rmagick_included?
120
- self.class.included_modules.map(&:to_s).include?('CarrierWave::RMagick')
121
- end
122
-
123
- # prevent large number of subdirectories
124
104
  def store_dir
125
105
  str_id = model.id.to_s.rjust(4, '0')
126
106
  [AbAdmin.uploads_dir, model.class.to_s.underscore, str_id[0..2], str_id[3..-1]].join('/')
127
107
  end
128
108
 
109
+ def convert_to_webp(options = {})
110
+ webp_path = "#{File.dirname(path)}/#{full_filename}"
111
+ WebP.encode(path, webp_path, options_for_webp(options))
112
+ @file = ::CarrierWave::SanitizedFile.new(tempfile: webp_path, filename: webp_path, content_type: 'image/webp')
113
+ end
114
+
115
+ def options_for_webp(options)
116
+ w, h = width, height
117
+ options = options.dup
118
+ ratio = w.to_f / h
119
+ if options[:resize_to_fill]
120
+ res_w, res_h = options[:resize_to_fill]
121
+ res_ratio = res_w.to_f / res_h
122
+ options.update(resize_w: res_w, resize_h: res_h) unless w == res_w && h == res_h
123
+ if ratio > res_ratio
124
+ crop_res_w = h * res_ratio
125
+ crop_res_h = h
126
+ options.update(crop_x: ((w - crop_res_w) / 2).to_i, crop_y: 0, crop_w: crop_res_w.to_i, crop_h: crop_res_h.to_i)
127
+ elsif ratio < res_ratio
128
+ crop_res_w = w
129
+ crop_res_h = w / res_ratio
130
+ options.update(crop_x: 0, crop_y: ((h - crop_res_h) / 2).to_i, crop_w: crop_res_w.to_i, crop_h: crop_res_h.to_i)
131
+ end
132
+ elsif options[:resize_to_fit]
133
+ res_w, res_h = options[:resize_to_fit]
134
+ res_ratio = res_w.to_f / res_h
135
+ if ratio == res_ratio
136
+ options.update(resize_w: res_w, resize_h: res_h) unless w == res_w && h == res_h
137
+ elsif ratio > res_ratio
138
+ options.update(resize_w: res_h)
139
+ elsif ratio < res_ratio
140
+ options.update(resize_h: res_w)
141
+ end
142
+ end
143
+ options.except(:resize_to_fill, :resize_to_fit)
144
+ end
145
+
129
146
  # Strips out all embedded information from the image
130
147
  # process :strip
131
148
  #
132
149
  def strip
133
- manipulate! do |img|
150
+ minimagick! do |img|
134
151
  img.strip
135
152
  img = yield(img) if block_given?
136
153
  img
@@ -144,7 +161,7 @@ module AbAdmin
144
161
  percentage = normalize_param(percentage)
145
162
 
146
163
  unless percentage.blank?
147
- manipulate! do |img|
164
+ minimagick! do |img|
148
165
  img.quality percentage.to_s
149
166
  img = yield(img) if block_given?
150
167
  img
@@ -160,7 +177,7 @@ module AbAdmin
160
177
 
161
178
  unless degrees.blank?
162
179
  manipulate! do |img|
163
- rmagick_included? ? img.rotate!(degrees.to_i) : img.rotate(degrees.to_s)
180
+ self.class.included_modules.map(&:to_s).include?('CarrierWave::RMagick') ? img.rotate!(degrees.to_i) : img.rotate(degrees.to_s)
164
181
  img = yield(img) if block_given?
165
182
  img
166
183
  end
@@ -176,7 +193,7 @@ module AbAdmin
176
193
  geometry = normalize_param(geometry[0]) if geometry.size == 1
177
194
 
178
195
  if geometry && geometry.size == 4
179
- manipulate! do |img|
196
+ minimagick! do |img|
180
197
  img.crop '%ix%i+%i+%i' % geometry
181
198
  img = yield(img) if block_given?
182
199
  img
@@ -185,7 +202,7 @@ module AbAdmin
185
202
  end
186
203
 
187
204
  def watermark(watermark_path, gravity='SouthEast')
188
- manipulate! do |img|
205
+ minimagick! do |img|
189
206
  resolved_path = watermark_path.is_a?(Symbol) ? send(watermark_path) : watermark_path
190
207
  watermark_image = ::MiniMagick::Image.open(resolved_path)
191
208
  img.composite(watermark_image) { |c| c.gravity gravity }
@@ -202,12 +219,7 @@ module AbAdmin
202
219
  end
203
220
 
204
221
  def dimensions
205
- [magick[:width], magick[:height]]
206
- end
207
-
208
- def magick
209
- #@magick ||= ::MiniMagick::Image.new(current_path)
210
- ::MiniMagick::Image.new(current_path)
222
+ [width, height]
211
223
  end
212
224
 
213
225
  protected
@@ -11,11 +11,6 @@ module AbAdmin
11
11
  mount_uploader(:data, uploader, options, &block)
12
12
  end
13
13
 
14
- def sunrise_uploader(*args)
15
- ActiveSupport::Deprecation.warn('`sunrise_uploader` is deprecated, use `ab_admin_uploader` instead')
16
- ab_admin_uploader(*args)
17
- end
18
-
19
14
  def validates_filesize_of(*attr_names)
20
15
  validates_with FileSizeValidator, _merge_attributes(attr_names)
21
16
  end
@@ -4,19 +4,23 @@ module AbAdmin
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
+ attr_accessor :last_updated_timestamp
8
+ validate :do_not_overwrite, if: :last_updated_timestamp
7
9
  scope(:admin, proc { all }) unless respond_to?(:admin)
8
10
  scope(:base, -> { all }) unless respond_to?(:base)
9
- scope :by_ids, lambda { |ids| where("#{quoted_table_name}.id IN (?)", AbAdmin.val_to_array(ids).push(0)) } unless respond_to?(:by_ids)
10
11
 
11
12
  class_attribute :batch_actions, instance_writer: false
12
13
  self.batch_actions = [:destroy]
13
14
  end
14
15
 
15
- module ClassMethods
16
- def for_input_token(r, attr=nil)
17
- text = attr ? r[attr] : (r['name_ru'] || r['name'])
18
- {id: r.id, text: text}
16
+ def updated_timestamp(associations: true)
17
+ res = [updated_at]
18
+ res += translations.map(&:updated_at) if self.class.translates?
19
+ if associations
20
+ associations = self.class.nested_attributes_options.keys unless associations.is_a?(Array)
21
+ res += associations.flat_map{|assoc| Array(send(assoc)).map(&:updated_timestamp) }
19
22
  end
23
+ res.compact.max
20
24
  end
21
25
 
22
26
  def for_input_token
@@ -27,28 +31,6 @@ module AbAdmin
27
31
  "#{self.class.model_name.human(count: 1)} ##{self.id} #{AbAdmin.safe_display_name(self)}"
28
32
  end
29
33
 
30
- def translated_any(attr)
31
- send(attr).presence || translations.detect { |r| r.send(attr).present? }.try!(attr)
32
- end
33
-
34
- def new_changes
35
- excluded_attrs = [:updated_at]
36
- excluded_attrs += translated_attribute_names if self.class.translates?
37
- all_changes = changes.except(*excluded_attrs).map { |k, v| [k, v[1]] }.to_h
38
- if self.class.translates?
39
- globalize.dirty.each do |attr, changes|
40
- changes.each do |change|
41
- all_changes["#{attr}_#{change[0]}"] = send("#{attr}_#{change[0]}")
42
- end
43
- end
44
- end
45
- all_changes
46
- end
47
-
48
- def admin_comments_count_non_zero
49
- self[:admin_comments_count].to_i.zero? ? nil : self[:admin_comments_count]
50
- end
51
-
52
34
  def token_data(method, options={})
53
35
  assoc = self.class.reflect_on_association(method)
54
36
  scope = send(method)
@@ -93,6 +75,12 @@ module AbAdmin
93
75
  scope.where(sql, val: send(order_col)).ransack(query[:q]).result(distinct: true).first
94
76
  end
95
77
 
78
+ private
79
+
80
+ def do_not_overwrite
81
+ return if new_record? || last_updated_timestamp.blank?
82
+ errors.add(:base, :changed) if updated_timestamp.to_i > last_updated_timestamp.to_i
83
+ end
96
84
  end
97
85
  end
98
86
  end
@@ -0,0 +1,97 @@
1
+ module AbAdmin
2
+ module Concerns
3
+ module TranslationsMacro
4
+ def translates(*attr_names)
5
+ options = attr_names.extract_options!
6
+ setup_translations(options) unless translates?
7
+ add_translated_attributes(attr_names.map(&:to_sym) - translated_attribute_names)
8
+ end
9
+
10
+ def setup_translations(options)
11
+ include InstanceMethods
12
+ extend ClassMethods
13
+
14
+ class_attribute :translated_attribute_names, :translation_options
15
+ self.translated_attribute_names = []
16
+ self.translation_options = options
17
+
18
+ has_many :translations, class_name: translation_class.name, foreign_key: class_name.foreign_key, dependent: :destroy, autosave: true, inverse_of: :translated_model
19
+ end
20
+
21
+ def add_translated_attributes(attr_names)
22
+ attr_names.each do |attr_name|
23
+ define_translation_accessors(attr_name)
24
+ self.translated_attribute_names << attr_name
25
+ end
26
+ end
27
+
28
+ def define_translation_accessors(attr_name)
29
+ define_method("#{attr_name}_translation") { translation_for_locale(I18n.locale).try!(:send, attr_name) }
30
+ define_method("#{attr_name}_translation=") {|v| translation_for_locale(I18n.locale).try!(:send, "#{attr_name}=", v) }
31
+ alias_method attr_name, "#{attr_name}_translation"
32
+ alias_method "#{attr_name}=", "#{attr_name}_translation="
33
+ define_method("#{attr_name}_default"){ translation_for_locale(I18n.default_locale).try!(:send, attr_name) }
34
+ AbAdmin.translated_locales.each do |l|
35
+ define_method("#{attr_name}_#{l}") {translation_for_locale(l).try!(:send, attr_name)}
36
+ define_method("#{attr_name}_#{l}=") {|v| translation_for_locale(l).try!(:send, "#{attr_name}=", v)}
37
+ end
38
+ end
39
+
40
+ def class_name
41
+ name.split('::').last
42
+ end
43
+
44
+ def translates?
45
+ included_modules.include?(InstanceMethods)
46
+ end
47
+
48
+ module InstanceMethods
49
+ def translation_for_locale(l)
50
+ return unless AbAdmin.translated_locales.include?(l)
51
+ translations.detect{|r| r.locale == l.to_s} || translations.new(locale: l.to_s)
52
+ end
53
+
54
+ def translated_attributes
55
+ translated_attribute_names.map{|attr| [attr, send(attr)] }.to_h.stringify_keys
56
+ end
57
+
58
+ def attributes
59
+ super.merge!(translated_attributes)
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+ def translation_class
65
+ @translation_class ||= begin
66
+ klass = self.const_defined?(:Translation, false) ? self.const_get(:Translation, false) : self.const_set(:Translation, Class.new(BaseTranslation))
67
+ klass.belongs_to :translated_model, class_name: self.name, foreign_key: class_name.foreign_key, inverse_of: :translations, touch: translation_options.fetch(:touch, false)
68
+ klass
69
+ end
70
+ end
71
+
72
+ def translations_table_name
73
+ translation_class.table_name
74
+ end
75
+
76
+ def translated?(name)
77
+ translated_attribute_names.include?(name.to_sym)
78
+ end
79
+ end
80
+ end
81
+
82
+ class BaseTranslation < ::ActiveRecord::Base
83
+ self.abstract_class = true
84
+
85
+ validates :locale, presence: true
86
+ after_initialize :init
87
+
88
+ def init
89
+ self.locale ||= I18n.locale.to_s
90
+ end
91
+
92
+ def self.table_exists?
93
+ table_name.present? && super
94
+ end
95
+ end
96
+ end
97
+ end
@@ -70,7 +70,7 @@ module AbAdmin
70
70
  add_cond << assoc.klass.instance_exec(&assoc.scope).to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1] if assoc.scope
71
71
  if assoc.klass.default_scopes.present?
72
72
  assoc.klass.default_scopes.each do |scope|
73
- add_cond << scope.call.to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1]
73
+ add_cond << scope.scope.call.to_sql[/WHERE(.*?)(?:(?:ORDER|LIMIT).*)?$/, 1]
74
74
  end
75
75
  end
76
76
  count_klass = assoc_count.klass
@@ -85,7 +85,7 @@ module AbAdmin
85
85
 
86
86
  def all_translated_attribute_names
87
87
  if translates?
88
- ::Globalize.available_locales.map do |loc|
88
+ AbAdmin.translated_locales.map do |loc|
89
89
  translated_attribute_names.map { |attr| "#{attr}_#{loc}" }
90
90
  end.flatten
91
91
  else
@@ -23,12 +23,20 @@ module AbAdmin
23
23
  next if column_name == :id || options[:skip].try(:include?, column_name)
24
24
  builder.field(column_name)
25
25
  end
26
+ model.translated_attribute_names.each do |column_name|
27
+ builder.field(column_name, sortable: false)
28
+ end if model.translates?
26
29
  end
27
30
  end
28
31
  end
29
32
 
30
33
  class Table < BaseBuilder
31
34
  self.partial_name = 'table'
35
+
36
+ def row_html_class
37
+ return :row_html_class if options[:row_html_class].is_a?(TrueClass)
38
+ options[:row_html_class]
39
+ end
32
40
  end
33
41
 
34
42
  class Search < BaseBuilder
@@ -39,6 +47,14 @@ module AbAdmin
39
47
  self.partial_name = 'chart'
40
48
  end
41
49
 
50
+ class Stats < BaseBuilder
51
+ self.partial_name = 'stats'
52
+ end
53
+
54
+ class Map < BaseBuilder
55
+ self.partial_name = 'map'
56
+ end
57
+
42
58
  class Export < BaseBuilder
43
59
  def render_options
44
60
  {column_names: fields.map(&:name), column_data: fields.map(&:data),
@@ -111,7 +127,7 @@ module AbAdmin
111
127
  end
112
128
 
113
129
  class BatchAction
114
- attr_reader :name, :options, :data, :title, :form
130
+ attr_reader :name, :options, :data, :form
115
131
 
116
132
  def initialize(name, options={}, &block)
117
133
  @name = name
@@ -119,10 +135,13 @@ module AbAdmin
119
135
  if options.has_key?(:form)
120
136
  @form = options[:form].is_a?(String) ? options[:form] : "##{name}_batch_form"
121
137
  end
122
- @title = options[:title] || I18n.t("admin.actions.batch_#{name}.link", default: name.to_s.humanize)
123
138
  @data = block_given? ? block : name.to_sym
124
139
  end
125
140
 
141
+ def title
142
+ options[:title] || I18n.t("admin.actions.#{name}.title", default: name.to_s.humanize)
143
+ end
144
+
126
145
  def confirm
127
146
  options[:confirm]
128
147
  end
@@ -169,8 +188,12 @@ module AbAdmin
169
188
  @data = block
170
189
  end
171
190
 
172
- def apply(relation, params)
173
- data.is_a?(Proc) ? data.call(relation, params) : relation.public_send(name)
191
+ def param_name
192
+ options[:as] || name
193
+ end
194
+
195
+ def apply(context, relation)
196
+ data.is_a?(Proc) ? data.call(context, relation) : relation.public_send(name)
174
197
  end
175
198
  end
176
199
  end