ab_admin 0.8.3 → 0.10.0

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