ab_admin 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ab_admin/components/in_place_edit.js.coffee +28 -24
  3. data/app/assets/javascripts/ab_admin/core/batch_actions.js.coffee +1 -1
  4. data/app/assets/javascripts/ab_admin/core/init.js.coffee +1 -0
  5. data/app/assets/javascripts/ab_admin/core/ui_utils.js.coffee +21 -1
  6. data/app/assets/javascripts/ab_admin/core/utils.js.coffee +8 -0
  7. data/app/assets/javascripts/ab_admin/inputs/datetime_input.js.coffee +1 -0
  8. data/app/assets/javascripts/ab_admin/main.js +1 -2
  9. data/app/assets/stylesheets/ab_admin/bootstrap_and_overrides.scss +44 -23
  10. data/app/assets/stylesheets/ab_admin/components/_colored_tabs.scss +1 -1
  11. data/app/assets/stylesheets/ab_admin/components/_form.scss +3 -1
  12. data/app/assets/stylesheets/ab_admin/components/_navigation.scss +7 -2
  13. data/app/assets/stylesheets/ab_admin/components/_table_view.scss +75 -9
  14. data/app/assets/stylesheets/ab_admin/components/_tooltip.scss +1 -0
  15. data/app/assets/stylesheets/ab_admin/main.scss +1 -1
  16. data/app/controllers/admin/assets_controller.rb +1 -1
  17. data/app/controllers/admin/base_controller.rb +87 -107
  18. data/app/controllers/admin/manager_controller.rb +17 -47
  19. data/app/views/ab_admin/devise/sessions/new.html.slim +2 -0
  20. data/app/views/admin/assets/batch_edit.html.slim +2 -1
  21. data/app/views/admin/base/_search_layout.html.slim +1 -1
  22. data/app/views/admin/base/create.js.erb +1 -1
  23. data/app/views/admin/base/edit.js.erb +1 -1
  24. data/app/views/admin/base/new.js.erb +1 -1
  25. data/app/views/admin/base/update.js.erb +3 -3
  26. data/app/views/admin/manager/_show_table.html.slim +1 -1
  27. data/app/views/admin/manager/_stats.html.slim +4 -0
  28. data/app/views/admin/manager/_table.html.slim +6 -3
  29. data/app/views/admin/shared/_content_actions.html.slim +22 -15
  30. data/app/views/admin/shared/_save_buttons.html.slim +10 -1
  31. data/app/views/admin/users/_form.html.slim +2 -2
  32. data/config/locales/en.yml +8 -13
  33. data/config/locales/it.yml +1 -0
  34. data/db/migrate/20130101000001_create_users.rb +1 -4
  35. data/db/migrate/20130101000003_create_assets.rb +1 -1
  36. data/db/migrate/20130101000004_create_headers.rb +5 -5
  37. data/db/migrate/20130101000005_create_static_pages.rb +2 -5
  38. data/db/migrate/20130101000006_create_structures.rb +1 -1
  39. data/db/migrate/20130101000007_base_translations.rb +43 -12
  40. data/db/migrate/20130101000008_create_admin_comments.rb +2 -7
  41. data/db/migrate/20130101000009_create_tracks.rb +4 -8
  42. data/lib/ab_admin/abstract_resource.rb +6 -5
  43. data/lib/ab_admin/carrierwave/base_uploader.rb +87 -74
  44. data/lib/ab_admin/carrierwave/glue.rb +0 -5
  45. data/lib/ab_admin/concerns/admin_addition.rb +19 -1
  46. data/lib/ab_admin/concerns/utilities.rb +1 -1
  47. data/lib/ab_admin/config/base.rb +20 -4
  48. data/lib/ab_admin/core_ext/array.rb +0 -5
  49. data/lib/ab_admin/core_ext/string.rb +1 -6
  50. data/lib/ab_admin/devise.rb +7 -0
  51. data/lib/ab_admin/engine.rb +1 -1
  52. data/lib/ab_admin/hooks/ckeditor_lazy.rb +13 -0
  53. data/lib/ab_admin/menu/base_group.rb +0 -1
  54. data/lib/ab_admin/menu/group.rb +2 -4
  55. data/lib/ab_admin/menu/item.rb +4 -8
  56. data/lib/ab_admin/models/asset.rb +7 -10
  57. data/lib/ab_admin/models/locator.rb +1 -1
  58. data/lib/ab_admin/models/settings.rb +2 -2
  59. data/lib/ab_admin/models/structure.rb +3 -3
  60. data/lib/ab_admin/models/track.rb +15 -3
  61. data/lib/ab_admin/utils/csv_document.rb +4 -4
  62. data/lib/ab_admin/utils/eval_helpers.rb +0 -16
  63. data/lib/ab_admin/utils/logger.rb +12 -2
  64. data/lib/ab_admin/utils/mysql.rb +2 -3
  65. data/lib/ab_admin/utils/xls_document.rb +1 -3
  66. data/lib/ab_admin/utils.rb +0 -5
  67. data/lib/ab_admin/version.rb +1 -1
  68. data/lib/ab_admin/views/admin_helpers.rb +33 -16
  69. data/lib/ab_admin/views/admin_navigation_helpers.rb +12 -9
  70. data/lib/ab_admin/views/inputs/ckeditor_input.rb +1 -5
  71. data/lib/ab_admin/views/manager_helpers.rb +9 -3
  72. data/lib/ab_admin/views/search_form_builder.rb +12 -12
  73. data/lib/ab_admin.rb +13 -2
  74. data/lib/generators/ab_admin/install/templates/models/user.rb +1 -2
  75. data/lib/generators/ab_admin/install/templates/uploaders/attachment_file_uploader.rb +1 -1
  76. data/lib/generators/ab_admin/install/templates/uploaders/avatar_uploader.rb +1 -1
  77. data/lib/generators/ab_admin/install/templates/uploaders/picture_uploader.rb +16 -3
  78. data/lib/generators/ab_admin/resource/resource_generator.rb +0 -4
  79. data/lib/generators/ab_admin/resource/templates/controller.erb +0 -7
  80. data/lib/tasks/assets.rake +5 -5
  81. metadata +28 -26
@@ -18,22 +18,13 @@ en:
18
18
  actions:
19
19
  activate:
20
20
  link: Activate
21
- batch_destroy:
22
- confirmation: Are you sure you want to delete the selected records?
23
- link: Delete selected
24
- title: Multi-remove
25
- batch_publish:
26
- link: Publish selected
27
- title: Multi-publish
28
- batch_un_publish:
29
- link: Unpublish selected
30
- title: Multi-hiding
31
21
  create:
32
22
  link: Create
33
23
  title: Creation
34
24
  destroy:
35
- link: Remove
36
- title: Removal
25
+ link: Destroy
26
+ title: Destroy
27
+ confirmation: Are you sure you want to delete the selected records?
37
28
  edit:
38
29
  link: Edit
39
30
  title: Editing
@@ -94,7 +85,7 @@ en:
94
85
  comments:
95
86
  add: Comment
96
87
  title_content: Comments (%{count})
97
- delete: Remove
88
+ delete: Destroy
98
89
  delete_confirmation: Are you sure you want to delete this?
99
90
  empty: Empty
100
91
  exit: Output
@@ -109,6 +100,8 @@ en:
109
100
  form:
110
101
  cancel: Cancel
111
102
  save: Save
103
+ force_save: Force save
104
+ refresh: Refresh
112
105
  save_and_add_another: Save and add new
113
106
  save_and_edit: Save and continue editing
114
107
  save_and_edit_next: Save and next.
@@ -245,6 +238,7 @@ en:
245
238
  updater_id: 'Updater'
246
239
  remember_me: Remember
247
240
  password: Password
241
+ otp_attempt: OTP
248
242
  flash:
249
243
  admin:
250
244
  actions:
@@ -290,5 +284,6 @@ en:
290
284
  size_too_big: is too big (should be at most %{file_size})
291
285
  carrierwave_processing_error: Cannot resize image.
292
286
  carrierwave_integrity_error: Not an image.
287
+ changed: 'Record was changed by someone else, "Refresh" to get the latest version and repeat changes or "Force save" to overwrite'
293
288
  unauthorized:
294
289
  default: You are not authorized to access this page.
@@ -103,6 +103,7 @@ it:
103
103
  form:
104
104
  cancel: Cancellazione
105
105
  save: Salvare
106
+ force_save: Force save
106
107
  save_and_add_another: Salva e aggiungere nuovi
107
108
  save_and_edit: Salva e continuare a modificare
108
109
  save_and_edit_next: Salva e traccia.
@@ -1,4 +1,4 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ class CreateUsers < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table(:users) do |t|
4
4
  t.string :login
@@ -35,9 +35,6 @@ class CreateUsers < ActiveRecord::Migration
35
35
  t.string :current_sign_in_ip
36
36
  t.string :last_sign_in_ip
37
37
 
38
- ## Encryptable
39
- t.string :password_salt
40
-
41
38
  ## Confirmable
42
39
  t.string :confirmation_token
43
40
  t.datetime :confirmed_at
@@ -1,4 +1,4 @@
1
- class CreateAssets < ActiveRecord::Migration
1
+ class CreateAssets < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :assets do |t|
4
4
  t.string :data_file_name, null: false
@@ -1,12 +1,12 @@
1
- class CreateHeaders < ActiveRecord::Migration
1
+ class CreateHeaders < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :headers do |t|
4
- t.string :headerable_type, limit: 50, null: false
5
- t.integer :headerable_id, null: false
6
-
4
+ t.string :headerable_type, limit: 50, null: false
5
+ t.integer :headerable_id, null: false
6
+
7
7
  t.timestamps
8
8
  end
9
-
9
+
10
10
  add_index :headers, [:headerable_type, :headerable_id], unique: true
11
11
  end
12
12
  end
@@ -1,14 +1,11 @@
1
- class CreateStaticPages < ActiveRecord::Migration
1
+ class CreateStaticPages < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :static_pages do |t|
4
- t.integer :structure_id, null: false
4
+ t.references :structure, null: false
5
5
  t.references :user
6
6
  t.boolean :is_visible, default: true, null: false
7
7
 
8
8
  t.timestamps
9
9
  end
10
-
11
- add_index :static_pages, :user_id
12
- add_index :static_pages, :structure_id
13
10
  end
14
11
  end
@@ -1,4 +1,4 @@
1
- class CreateStructures < ActiveRecord::Migration
1
+ class CreateStructures < ActiveRecord::Migration[5.2]
2
2
  def change
3
3
  create_table :structures do |t|
4
4
  t.string :slug, null: false
@@ -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, :map, :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
 
@@ -7,8 +7,7 @@ module AbAdmin
7
7
  include ::CarrierWave::MiniMagick
8
8
  include AbAdmin::Utils::EvalHelpers
9
9
 
10
- class_attribute :transliterate, :human_filenames
11
- self.transliterate = true
10
+ class_attribute :human_filenames
12
11
  self.human_filenames = true
13
12
 
14
13
  attr_accessor :internal_identifier
@@ -25,41 +24,48 @@ module AbAdmin
25
24
 
26
25
  process :set_model_info
27
26
 
28
- def strict_filename(for_file=filename)
29
- "#{version_name || secure_token}#{File.extname(for_file).downcase}"
27
+ def filename
28
+ "#{[human_part, secure_token].compact.join('_')}#{extension}"
30
29
  end
31
30
 
32
- def save_original_name(file)
33
- 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('_')
34
36
  end
35
37
 
36
- def base_filename_part
37
- return if version_name == :default
38
- return secure_token unless version_name
39
- 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
40
40
  end
41
41
 
42
- def full_filename(for_file=filename)
43
- human_filenames ? human_full_filename(for_file) : strict_filename(for_file)
42
+ def extension
43
+ File.extname(model.original_name).downcase
44
44
  end
45
45
 
46
- def human_full_filename(for_file=filename)
47
- ext = File.extname(for_file)
48
- system_part = base_filename_part
49
- human_filename_part = for_file.chomp(ext)
50
- return "#{system_part || version_name}#{ext}" if human_filename_part == secure_token
51
- system_part ? "#{human_filename_part}_#{system_part}#{ext}" : "#{human_filename_part}#{ext}"
46
+ def version_extension
47
+ webp? ? '.webp' : extension
52
48
  end
53
49
 
54
- def full_original_filename
55
- "#{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?}"
56
55
  end
57
56
 
58
- # use secure token in the filename for non processed image
59
57
  def secure_token
60
58
  model.data_secure_token ||= AbAdmin.friendly_token(20).downcase
61
59
  end
62
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
+
63
69
  def store_model_filename(record)
64
70
  old_file_name = filename
65
71
  new_file_name = model_filename(old_file_name, record)
@@ -67,69 +73,81 @@ module AbAdmin
67
73
  rename_via_move(new_file_name)
68
74
  end
69
75
 
70
- alias_method :store_filename, :filename
71
-
72
- def filename
73
- internal_identifier || model.send("#{mounted_as}_file_name") || (store_filename && "#{secure_token}#{File.extname(store_filename).downcase}")
74
- end
75
-
76
- def write_internal_identifier(internal_identifier)
77
- self.internal_identifier = internal_identifier
78
- 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)
79
78
  end
80
79
 
81
- # transliterate original filename
82
- # allow to build custom filename
83
80
  def model_filename(base_filename, record)
84
81
  custom_file_name = model.build_filename(base_filename, record)
85
82
  return unless custom_file_name
86
- normalize_filename(custom_file_name) + File.extname(base_filename).downcase
83
+ normalize_filename(custom_file_name)
87
84
  end
85
+ private :model_filename
88
86
 
89
87
  def normalize_filename(raw_filename)
90
- parameterize_args = ActiveSupport::VERSION::MAJOR > 4 ? {separator: '_'} : '_'
91
- I18n.transliterate(raw_filename).parameterize(**parameterize_args).gsub(/[\-_]+/, '_').downcase
92
- end
93
-
94
- # rename files via move
95
- def rename_via_move(new_file_name)
96
- if human_filenames
97
- dir = File.dirname(path)
98
-
99
- moves = []
100
- versions.values.unshift(self).each do |v|
101
- from_path = File.join(dir, v.full_filename)
102
- to_path = File.join(dir, v.full_filename(new_file_name))
103
- next if from_path == to_path || !File.exists?(from_path)
104
- moves << [from_path, to_path]
105
- end
106
- 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)
107
100
  end
108
-
109
- write_internal_identifier new_file_name
110
- model.send("write_#{mounted_as}_identifier")
111
- retrieve_from_store!(new_file_name) if human_filenames
112
-
113
- new_file_name
101
+ retrieve_from_store!(model.send("#{mounted_as}_file_name"))
114
102
  end
115
103
 
116
- private :write_internal_identifier, :store_filename, :model_filename
117
-
118
- def rmagick_included?
119
- self.class.included_modules.map(&:to_s).include?('CarrierWave::RMagick')
120
- end
121
-
122
- # prevent large number of subdirectories
123
104
  def store_dir
124
105
  str_id = model.id.to_s.rjust(4, '0')
125
106
  [AbAdmin.uploads_dir, model.class.to_s.underscore, str_id[0..2], str_id[3..-1]].join('/')
126
107
  end
127
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
+
128
146
  # Strips out all embedded information from the image
129
147
  # process :strip
130
148
  #
131
149
  def strip
132
- manipulate! do |img|
150
+ minimagick! do |img|
133
151
  img.strip
134
152
  img = yield(img) if block_given?
135
153
  img
@@ -143,7 +161,7 @@ module AbAdmin
143
161
  percentage = normalize_param(percentage)
144
162
 
145
163
  unless percentage.blank?
146
- manipulate! do |img|
164
+ minimagick! do |img|
147
165
  img.quality percentage.to_s
148
166
  img = yield(img) if block_given?
149
167
  img
@@ -159,7 +177,7 @@ module AbAdmin
159
177
 
160
178
  unless degrees.blank?
161
179
  manipulate! do |img|
162
- 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)
163
181
  img = yield(img) if block_given?
164
182
  img
165
183
  end
@@ -175,7 +193,7 @@ module AbAdmin
175
193
  geometry = normalize_param(geometry[0]) if geometry.size == 1
176
194
 
177
195
  if geometry && geometry.size == 4
178
- manipulate! do |img|
196
+ minimagick! do |img|
179
197
  img.crop '%ix%i+%i+%i' % geometry
180
198
  img = yield(img) if block_given?
181
199
  img
@@ -184,7 +202,7 @@ module AbAdmin
184
202
  end
185
203
 
186
204
  def watermark(watermark_path, gravity='SouthEast')
187
- manipulate! do |img|
205
+ minimagick! do |img|
188
206
  resolved_path = watermark_path.is_a?(Symbol) ? send(watermark_path) : watermark_path
189
207
  watermark_image = ::MiniMagick::Image.open(resolved_path)
190
208
  img.composite(watermark_image) { |c| c.gravity gravity }
@@ -201,12 +219,7 @@ module AbAdmin
201
219
  end
202
220
 
203
221
  def dimensions
204
- [magick[:width], magick[:height]]
205
- end
206
-
207
- def magick
208
- #@magick ||= ::MiniMagick::Image.new(current_path)
209
- ::MiniMagick::Image.new(current_path)
222
+ [width, height]
210
223
  end
211
224
 
212
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,14 +4,25 @@ 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
 
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) }
22
+ end
23
+ res.compact.max
24
+ end
25
+
15
26
  def for_input_token
16
27
  {id: id, text: AbAdmin.safe_display_name(self).to_s}
17
28
  end
@@ -63,6 +74,13 @@ module AbAdmin
63
74
  sql = "(#{quoted_order_col} #{predicate} :val OR (#{quoted_order_col} = :val AND #{self.class.quote_column('id')} #{id_predicate} #{id}))"
64
75
  scope.where(sql, val: send(order_col)).ransack(query[:q]).result(distinct: true).first
65
76
  end
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
66
84
  end
67
85
  end
68
86
  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
@@ -32,6 +32,11 @@ module AbAdmin
32
32
 
33
33
  class Table < BaseBuilder
34
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
35
40
  end
36
41
 
37
42
  class Search < BaseBuilder
@@ -42,6 +47,10 @@ module AbAdmin
42
47
  self.partial_name = 'chart'
43
48
  end
44
49
 
50
+ class Stats < BaseBuilder
51
+ self.partial_name = 'stats'
52
+ end
53
+
45
54
  class Map < BaseBuilder
46
55
  self.partial_name = 'map'
47
56
  end
@@ -118,7 +127,7 @@ module AbAdmin
118
127
  end
119
128
 
120
129
  class BatchAction
121
- attr_reader :name, :options, :data, :title, :form
130
+ attr_reader :name, :options, :data, :form
122
131
 
123
132
  def initialize(name, options={}, &block)
124
133
  @name = name
@@ -126,10 +135,13 @@ module AbAdmin
126
135
  if options.has_key?(:form)
127
136
  @form = options[:form].is_a?(String) ? options[:form] : "##{name}_batch_form"
128
137
  end
129
- @title = options[:title] || I18n.t("admin.actions.batch_#{name}.link", default: name.to_s.humanize)
130
138
  @data = block_given? ? block : name.to_sym
131
139
  end
132
140
 
141
+ def title
142
+ options[:title] || I18n.t("admin.actions.#{name}.title", default: name.to_s.humanize)
143
+ end
144
+
133
145
  def confirm
134
146
  options[:confirm]
135
147
  end
@@ -176,8 +188,12 @@ module AbAdmin
176
188
  @data = block
177
189
  end
178
190
 
179
- def apply(relation, params)
180
- 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)
181
197
  end
182
198
  end
183
199
  end
@@ -1,9 +1,4 @@
1
1
  class Array
2
- # TOREMOVE after ruby 2.7.0
3
- def tally
4
- each_with_object(Hash.new(0)) { |v, h| h[v] += 1 }
5
- end
6
-
7
2
  def deep_merge_hashes
8
3
  self.inject({}) do |res, h|
9
4
  raise Exception.new("Not a hash #{h}") unless h.is_a?(Hash)
@@ -1,11 +1,6 @@
1
1
  class String
2
2
  def no_html
3
- str = self.dup
4
- str.gsub!(/<br\/?>/, ' ')
5
- str.gsub!(/<\/?[^>]*>/, '')
6
- str.strip!
7
- str.gsub!('&nbsp;', ' ')
8
- str
3
+ self.dup.gsub(/<br\/?>/, ' ').gsub(/<\/?[^>]*>/, '').strip.gsub('&nbsp;', ' ').gsub('&gt;', '>').gsub('&lt;', '<')
9
4
  end
10
5
  end
11
6