activeadmin_favorites 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/CONTRIBUTING.md +29 -0
- data/GOVERNANCE.md +29 -0
- data/LICENSE.md +21 -0
- data/README.md +146 -0
- data/SECURITY.md +27 -0
- data/activeadmin_favorites.gemspec +68 -0
- data/app/assets/controllers/activeadmin_favorites/personalization_controller.js +199 -0
- data/app/models/active_admin/favorites/application_record.rb +9 -0
- data/app/models/active_admin/favorites/combined_favorite.rb +9 -0
- data/app/models/active_admin/favorites/favorite/macro_builder.rb +43 -0
- data/app/models/active_admin/favorites/favorite/macro_catalog.rb +118 -0
- data/app/models/active_admin/favorites/favorite/macro_resolver.rb +86 -0
- data/app/models/active_admin/favorites/favorite/macro_validator.rb +87 -0
- data/app/models/active_admin/favorites/favorite/query_params.rb +41 -0
- data/app/models/active_admin/favorites/favorite/range_detector.rb +54 -0
- data/app/models/active_admin/favorites/favorite.rb +339 -0
- data/app/models/active_admin/favorites/filters_favorite.rb +8 -0
- data/app/models/active_admin/favorites/lens_favorite.rb +9 -0
- data/app/models/active_admin/favorites/view_lens/layout.rb +86 -0
- data/app/models/active_admin/favorites/view_lens_default.rb +40 -0
- data/app/models/active_admin/favorites.rb +6 -0
- data/app/views/active_admin/favorites/_lens_catalog_data.html.erb +9 -0
- data/app/views/active_admin/favorites/_macro_fields.html.erb +53 -0
- data/app/views/active_admin/favorites/_personalization_trigger.html.erb +69 -0
- data/app/views/active_admin/resource/index.html.arb +99 -0
- data/app/views/active_admin/resource/show.html.arb +13 -0
- data/config/locales/activeadmin_favorites.en.yml +60 -0
- data/config/locales/activeadmin_favorites.host.yml.example +17 -0
- data/config/polyrun_coverage.yml +8 -0
- data/db/migrate/20260709100000_create_active_admin_view_lens_defaults.rb +24 -0
- data/db/migrate/20260709130000_create_admin_favorites.rb +33 -0
- data/lib/activeadmin/favorites/configuration.rb +31 -0
- data/lib/activeadmin/favorites/engine.rb +75 -0
- data/lib/activeadmin/favorites/hash_coercion.rb +34 -0
- data/lib/activeadmin/favorites/install.rb +121 -0
- data/lib/activeadmin/favorites/migration_helpers.rb +34 -0
- data/lib/activeadmin/favorites/migration_support.rb +19 -0
- data/lib/activeadmin/favorites/register_favorites.rb +272 -0
- data/lib/activeadmin/favorites/register_view_lens_preferences.rb +98 -0
- data/lib/activeadmin/favorites/user_record.rb +26 -0
- data/lib/activeadmin/favorites/version.rb +7 -0
- data/lib/activeadmin/favorites/view_lens/applicator.rb +150 -0
- data/lib/activeadmin/favorites/view_lens/catalog.rb +108 -0
- data/lib/activeadmin/favorites/view_lens/current.rb +28 -0
- data/lib/activeadmin/favorites/view_lens/patches.rb +95 -0
- data/lib/activeadmin/favorites/view_lens/resource_dsl.rb +44 -0
- data/lib/activeadmin/favorites/view_lens.rb +8 -0
- data/lib/activeadmin/favorites.rb +30 -0
- data/lib/activeadmin_favorites.rb +5 -0
- data/lib/generators/activeadmin_favorites/install/templates/favorite_policy.rb +56 -0
- data/lib/generators/activeadmin_favorites/install/templates/initializer.rb +16 -0
- data/lib/generators/activeadmin_favorites/install/templates/locale.en.yml +60 -0
- data/lib/generators/activeadmin_favorites/install_generator.rb +37 -0
- data/lib/tasks/activeadmin_favorites_tasks.rake +39 -0
- data/sig/activeadmin/favorites.rbs +5 -0
- metadata +418 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module RegisterFavorites
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
return if resource_registered?
|
|
10
|
+
|
|
11
|
+
ActiveAdmin.register Favorite, as: "favorites" do
|
|
12
|
+
menu label: proc { I18n.t("active_admin.favorites.menu") }, priority: 2
|
|
13
|
+
|
|
14
|
+
actions :index, :new, :create, :edit, :update, :destroy
|
|
15
|
+
|
|
16
|
+
config.filters = false
|
|
17
|
+
config.sort_order = "created_at_desc"
|
|
18
|
+
|
|
19
|
+
permit_params :name, :note, :path, :query_string, :resource_key, :published, :macros_json,
|
|
20
|
+
:use_advanced_macros, :kind, :type, :action, :include_layout, layout: {}
|
|
21
|
+
|
|
22
|
+
controller do
|
|
23
|
+
helper_method :current_favorites_user, :favorites_kind_tab_active?
|
|
24
|
+
|
|
25
|
+
def current_favorites_user
|
|
26
|
+
send(ActiveAdmin::Favorites.config.current_user_method)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_new_resource
|
|
30
|
+
kind = params[:kind].presence || "filters"
|
|
31
|
+
resource = Favorite.sti_class_for_kind(kind).new
|
|
32
|
+
assign_new_favorite_defaults(resource)
|
|
33
|
+
resource
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_resource
|
|
37
|
+
get_resource_ivar || begin
|
|
38
|
+
kind = params.dig(:favorite, :kind).presence || params[:kind].presence || "filters"
|
|
39
|
+
record = Favorite.sti_class_for_kind(kind).new
|
|
40
|
+
record = assign_attributes(record, resource_params)
|
|
41
|
+
authorize_resource!(record)
|
|
42
|
+
set_resource_ivar(record)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create
|
|
47
|
+
build_resource
|
|
48
|
+
assign_favorite_owner(resource)
|
|
49
|
+
apply_macros_from_params(resource) if macro_form_submitted?
|
|
50
|
+
apply_layout_from_params(resource)
|
|
51
|
+
|
|
52
|
+
if resource.errors.any? || !resource.save
|
|
53
|
+
render :new, status: :unprocessable_entity
|
|
54
|
+
else
|
|
55
|
+
redirect_to admin_favorites_path(kind: resource.kind), notice: create_notice_for(resource)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def update
|
|
60
|
+
resource.assign_attributes(
|
|
61
|
+
permitted_params[:favorite].except(
|
|
62
|
+
:macros_json, :use_advanced_macros, :include_layout, :layout, :kind,
|
|
63
|
+
:path, :query_string, :resource_key, :action
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
apply_macros_from_params(resource) if macro_form_submitted?
|
|
67
|
+
apply_layout_from_params(resource)
|
|
68
|
+
|
|
69
|
+
if resource.errors.any? || !resource.save
|
|
70
|
+
render :edit, status: :unprocessable_entity
|
|
71
|
+
else
|
|
72
|
+
redirect_to admin_favorites_path(kind: resource.kind), notice: I18n.t("active_admin.favorites.updated")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def scoped_collection
|
|
77
|
+
association = ActiveAdmin::Favorites.config.user_association_name
|
|
78
|
+
scope = super.includes(association)
|
|
79
|
+
kind = params[:kind].presence
|
|
80
|
+
return scope if kind.blank?
|
|
81
|
+
return scope unless Favorite::KINDS.include?(kind)
|
|
82
|
+
|
|
83
|
+
scope.merge(Favorite.sti_class_for_kind(kind).all)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def assign_new_favorite_defaults(resource)
|
|
89
|
+
if params[:path].present?
|
|
90
|
+
resource.path = params[:path]
|
|
91
|
+
resource.query_string = Favorite.sanitize_query_string(params[:query_string])
|
|
92
|
+
resource.resource_key = params[:resource_key]
|
|
93
|
+
end
|
|
94
|
+
resource.action = params[:action_name] if params[:action_name].present?
|
|
95
|
+
resource.action ||= "index"
|
|
96
|
+
resource.kind = params[:kind] if params[:kind].present?
|
|
97
|
+
|
|
98
|
+
if resource.layout.blank? && resource.resource_key.present?
|
|
99
|
+
default = ViewLensDefault.for_user(
|
|
100
|
+
current_favorites_user,
|
|
101
|
+
resource_key: resource.resource_key,
|
|
102
|
+
action: resource.action
|
|
103
|
+
)
|
|
104
|
+
resource.layout = default.layout if default
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if resource.name.blank? && resource.resource_key.present?
|
|
108
|
+
resource.name = Favorite.suggested_name(
|
|
109
|
+
resource_key: resource.resource_key,
|
|
110
|
+
query_string: resource.query_string,
|
|
111
|
+
kind: resource.kind
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if ActiveModel::Type::Boolean.new.cast(params[:include_layout])
|
|
116
|
+
resource.layout = layout_from_params || resource.layout
|
|
117
|
+
if resource.kind == "filters" && resource.query_string.present? && resource.layout.present?
|
|
118
|
+
resource = resource.promote_to_kind!("combined")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def assign_favorite_owner(resource)
|
|
124
|
+
resource.public_send(:"#{ActiveAdmin::Favorites.config.user_foreign_key}=", current_favorites_user.id)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def apply_macros_from_params(favorite)
|
|
128
|
+
favorite.use_advanced_macros = params.dig(:favorite, :use_advanced_macros)
|
|
129
|
+
favorite.macros_json = params.dig(:favorite, :macros_json)
|
|
130
|
+
favorite.assign_macros_from_form!(
|
|
131
|
+
template_assignments: params[:macro_template_assignments],
|
|
132
|
+
macros_json: favorite.macros_json,
|
|
133
|
+
use_advanced_macros: favorite.use_advanced_macros
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_layout_from_params(favorite)
|
|
138
|
+
return favorite unless ActiveModel::Type::Boolean.new.cast(params.dig(:favorite, :include_layout))
|
|
139
|
+
|
|
140
|
+
favorite.layout = layout_from_params || favorite.layout
|
|
141
|
+
if favorite.kind == "filters" && favorite.query_string.present? && favorite.layout.present?
|
|
142
|
+
favorite = favorite.promote_to_kind!("combined")
|
|
143
|
+
set_resource_ivar(favorite)
|
|
144
|
+
end
|
|
145
|
+
favorite
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def layout_from_params
|
|
149
|
+
raw_layout = params.dig(:favorite, :layout)
|
|
150
|
+
return if raw_layout.blank?
|
|
151
|
+
|
|
152
|
+
parsed =
|
|
153
|
+
if raw_layout.is_a?(String)
|
|
154
|
+
JSON.parse(raw_layout)
|
|
155
|
+
else
|
|
156
|
+
raw_layout
|
|
157
|
+
end
|
|
158
|
+
ViewLens::Layout.normalize(parsed)
|
|
159
|
+
rescue JSON::ParserError
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def macro_form_submitted?
|
|
164
|
+
params[:macro_form_submitted].present?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def favorites_kind_tab_active?(kind)
|
|
168
|
+
if kind.nil?
|
|
169
|
+
params[:kind].blank?
|
|
170
|
+
else
|
|
171
|
+
params[:kind] == kind
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def create_notice_for(resource)
|
|
176
|
+
case resource.kind
|
|
177
|
+
when "lens"
|
|
178
|
+
I18n.t("active_admin.favorites.lens_created")
|
|
179
|
+
when "combined"
|
|
180
|
+
I18n.t("active_admin.favorites.combined_created")
|
|
181
|
+
else
|
|
182
|
+
I18n.t("active_admin.favorites.created")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
index title: proc { I18n.t("active_admin.favorites.title") } do
|
|
188
|
+
nav class: "flex flex-wrap gap-3 mb-4", "aria-label": I18n.t("active_admin.favorites.kind_navigation") do
|
|
189
|
+
link_to I18n.t("active_admin.favorites.kinds.all"),
|
|
190
|
+
admin_favorites_path,
|
|
191
|
+
class: favorites_kind_tab_active?(nil) ? "font-semibold underline" : "text-gray-600 dark:text-gray-300"
|
|
192
|
+
Favorite::KINDS.each do |kind|
|
|
193
|
+
link_to I18n.t("active_admin.favorites.kinds.#{kind}"),
|
|
194
|
+
admin_favorites_path(kind: kind),
|
|
195
|
+
class: favorites_kind_tab_active?(kind) ? "font-semibold underline" : "text-gray-600 dark:text-gray-300"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
column :name do |favorite|
|
|
200
|
+
link_to favorite.name, favorite.open_url, class: "no-underline font-medium"
|
|
201
|
+
end
|
|
202
|
+
column I18n.t("active_admin.favorites.kind_column"), &:kind_label
|
|
203
|
+
column :resource, &:resource_label
|
|
204
|
+
column I18n.t("active_admin.favorites.filters_column") do |favorite|
|
|
205
|
+
(favorite.kind == "lens") ? "—" : favorite.filter_summary
|
|
206
|
+
end
|
|
207
|
+
column I18n.t("active_admin.favorites.visibility_column") do |favorite|
|
|
208
|
+
if favorite.published?
|
|
209
|
+
I18n.t("active_admin.favorites.visibility.published", time: l(favorite.published_at, default: "—"))
|
|
210
|
+
else
|
|
211
|
+
I18n.t("active_admin.favorites.visibility.mine")
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
column I18n.t("active_admin.favorites.creator_column") do |favorite|
|
|
215
|
+
next "—" if favorite.owned_by?(current_favorites_user)
|
|
216
|
+
|
|
217
|
+
owner = favorite.public_send(ActiveAdmin::Favorites.config.user_association_name)
|
|
218
|
+
label_method = ActiveAdmin::Favorites.config.user_label_method
|
|
219
|
+
owner&.public_send(label_method) || "—"
|
|
220
|
+
end
|
|
221
|
+
actions defaults: false do |favorite|
|
|
222
|
+
item I18n.t("active_admin.favorites.open"), favorite.open_url, class: "member_link"
|
|
223
|
+
if authorized?(:update, favorite)
|
|
224
|
+
item I18n.t("active_admin.edit"), edit_admin_favorite_path(favorite), class: "member_link"
|
|
225
|
+
item I18n.t("active_admin.delete"), admin_favorite_path(favorite),
|
|
226
|
+
class: "member_link",
|
|
227
|
+
method: :delete,
|
|
228
|
+
data: {confirm: I18n.t("active_admin.delete_confirmation")}
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
form do |f|
|
|
234
|
+
f.semantic_errors(*f.object.errors.attribute_names)
|
|
235
|
+
f.inputs I18n.t("active_admin.favorites.form_panel") do
|
|
236
|
+
f.input :name, input_html: {autofocus: true}
|
|
237
|
+
f.input :note, as: :text, input_html: {rows: 3}
|
|
238
|
+
f.input :published, as: :boolean,
|
|
239
|
+
label: I18n.t("active_admin.favorites.published")
|
|
240
|
+
if f.object.new_record?
|
|
241
|
+
f.input :path, as: :hidden
|
|
242
|
+
f.input :query_string, as: :hidden
|
|
243
|
+
f.input :resource_key, as: :hidden
|
|
244
|
+
f.input :kind, as: :hidden
|
|
245
|
+
f.input :action, as: :hidden
|
|
246
|
+
end
|
|
247
|
+
if f.object.new_record? && f.object.kind == "filters"
|
|
248
|
+
f.input :include_layout, as: :boolean,
|
|
249
|
+
label: I18n.t("active_admin.favorites.include_layout")
|
|
250
|
+
f.input :layout, as: :hidden,
|
|
251
|
+
input_html: {value: (f.object.layout || {}).to_json}
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if f.object.kind != "lens"
|
|
256
|
+
render partial: "active_admin/favorites/macro_fields", locals: {f: f}
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
f.actions
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
config.remove_action_item :new
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def resource_registered?
|
|
267
|
+
ActiveAdmin.application.namespace(ActiveAdmin::Favorites.config.namespace_name)
|
|
268
|
+
.resources.any? { |resource| resource.resource_class == Favorite }
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module RegisterViewLensPreferences
|
|
6
|
+
PAGE_NAME = "ViewLensPreferences"
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def parse_layout_param(raw_layout)
|
|
11
|
+
parsed =
|
|
12
|
+
if raw_layout.is_a?(String)
|
|
13
|
+
JSON.parse(raw_layout)
|
|
14
|
+
elsif raw_layout.respond_to?(:to_unsafe_h)
|
|
15
|
+
raw_layout.to_unsafe_h
|
|
16
|
+
else
|
|
17
|
+
raw_layout
|
|
18
|
+
end
|
|
19
|
+
ViewLens::Layout.normalize(parsed || {})
|
|
20
|
+
rescue JSON::ParserError
|
|
21
|
+
ViewLens::Layout.empty.layout
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
return if page_registered?
|
|
26
|
+
|
|
27
|
+
ActiveAdmin.register_page PAGE_NAME, as: "view_lens_preferences" do
|
|
28
|
+
menu false
|
|
29
|
+
|
|
30
|
+
controller do
|
|
31
|
+
def layout_from_request
|
|
32
|
+
ActiveAdmin::Favorites::RegisterViewLensPreferences.parse_layout_param(params[:layout])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def authorize_view_lens_page_action!(action_name)
|
|
36
|
+
return unless respond_to?(:authorized?, true)
|
|
37
|
+
|
|
38
|
+
return if authorized?(action_name, active_admin_config)
|
|
39
|
+
|
|
40
|
+
redirect_to admin_root_path,
|
|
41
|
+
alert: I18n.t("active_admin.access_denied.message", default: "You are not authorized to perform this action.")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
page_action :update, method: :patch do
|
|
46
|
+
authorize_view_lens_page_action!(:update)
|
|
47
|
+
user = send(ActiveAdmin::Favorites.config.current_user_method)
|
|
48
|
+
layout = ActiveAdmin::Favorites::RegisterViewLensPreferences.parse_layout_param(params[:layout])
|
|
49
|
+
ViewLensDefault.upsert_for!(
|
|
50
|
+
user: user,
|
|
51
|
+
resource_key: params.fetch(:resource_key),
|
|
52
|
+
action: params.fetch(:view_lens_action),
|
|
53
|
+
layout: layout
|
|
54
|
+
)
|
|
55
|
+
redirect_back fallback_location: admin_root_path, notice: I18n.t("active_admin.favorites.layout_saved")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
page_action :reset, method: :delete do
|
|
59
|
+
authorize_view_lens_page_action!(:reset)
|
|
60
|
+
user = send(ActiveAdmin::Favorites.config.current_user_method)
|
|
61
|
+
record = ViewLensDefault.for_user(
|
|
62
|
+
user,
|
|
63
|
+
resource_key: params.fetch(:resource_key),
|
|
64
|
+
action: params.fetch(:view_lens_action)
|
|
65
|
+
)
|
|
66
|
+
record&.destroy
|
|
67
|
+
redirect_back fallback_location: admin_root_path, notice: I18n.t("active_admin.favorites.layout_reset")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
page_action :save_lens_favorite, method: :post do
|
|
71
|
+
authorize_view_lens_page_action!(:save_lens_favorite)
|
|
72
|
+
user = send(ActiveAdmin::Favorites.config.current_user_method)
|
|
73
|
+
bookmark = LensFavorite.new(
|
|
74
|
+
name: params.fetch(:name),
|
|
75
|
+
resource_key: params.fetch(:resource_key),
|
|
76
|
+
path: params.fetch(:path),
|
|
77
|
+
action: params.fetch(:view_lens_action),
|
|
78
|
+
layout: ActiveAdmin::Favorites::RegisterViewLensPreferences.parse_layout_param(params[:layout]),
|
|
79
|
+
published: ActiveModel::Type::Boolean.new.cast(params[:published]) || false
|
|
80
|
+
)
|
|
81
|
+
bookmark.public_send(:"#{ActiveAdmin::Favorites.config.user_foreign_key}=", user.id)
|
|
82
|
+
|
|
83
|
+
if bookmark.save
|
|
84
|
+
redirect_to admin_favorites_path(kind: "lens"), notice: I18n.t("active_admin.favorites.lens_created")
|
|
85
|
+
else
|
|
86
|
+
redirect_back fallback_location: admin_root_path, alert: bookmark.errors.full_messages.to_sentence
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def page_registered?
|
|
93
|
+
ActiveAdmin.application.namespace(ActiveAdmin::Favorites.config.namespace_name)
|
|
94
|
+
.resources.any? { |resource| resource.is_a?(ActiveAdmin::Page) && resource.name == PAGE_NAME }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module UserRecord
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def define_favorites_user_association!
|
|
10
|
+
return if favorites_user_association_defined?
|
|
11
|
+
|
|
12
|
+
config = ActiveAdmin::Favorites.config
|
|
13
|
+
belongs_to config.user_association_name,
|
|
14
|
+
class_name: config.user_class,
|
|
15
|
+
foreign_key: config.user_foreign_key
|
|
16
|
+
|
|
17
|
+
self.favorites_user_association_defined = true
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
included do
|
|
22
|
+
class_attribute :favorites_user_association_defined, instance_accessor: false, default: false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module ViewLens
|
|
6
|
+
class Applicator
|
|
7
|
+
class << self
|
|
8
|
+
def current_layout
|
|
9
|
+
value = Current.layout
|
|
10
|
+
return Layout.empty if value.nil?
|
|
11
|
+
return value if value.is_a?(Layout)
|
|
12
|
+
|
|
13
|
+
Layout.new(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def current_layout=(value)
|
|
17
|
+
Current.layout = value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def current_catalog
|
|
21
|
+
Current.catalog
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def current_catalog=(value)
|
|
25
|
+
Current.catalog = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def registered_columns
|
|
29
|
+
Current.registered_columns
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def registered_panels
|
|
33
|
+
Current.registered_panels
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def registered_rows
|
|
37
|
+
Current.registered_rows
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reset!
|
|
41
|
+
Current.reset
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve_layout(controller:)
|
|
45
|
+
user = controller.send(ActiveAdmin::Favorites.config.current_user_method)
|
|
46
|
+
config = controller.send(:active_admin_config)
|
|
47
|
+
action = controller.action_name
|
|
48
|
+
resource_key = config.resource_name.route_key
|
|
49
|
+
|
|
50
|
+
layout = ViewLens::Layout.empty
|
|
51
|
+
default = ViewLensDefault.for_user(user, resource_key: resource_key, action: action)
|
|
52
|
+
layout = layout.merge(default.layout_object) if default
|
|
53
|
+
|
|
54
|
+
favorite_id = controller.params[:favorite_id]
|
|
55
|
+
if favorite_id.present?
|
|
56
|
+
bookmark = Favorite.visible_to(user).find_by(id: favorite_id)
|
|
57
|
+
if bookmark&.layout.present? && bookmark.resource_key == resource_key && bookmark.action == action
|
|
58
|
+
layout = layout.merge(bookmark.layout_object)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Current.layout = layout
|
|
63
|
+
Current.catalog_context = {
|
|
64
|
+
active_admin_config: config,
|
|
65
|
+
action: action,
|
|
66
|
+
render_context: controller.helpers
|
|
67
|
+
}
|
|
68
|
+
layout
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build_catalog(active_admin_config:, action:, render_context: nil)
|
|
72
|
+
Catalog.for(
|
|
73
|
+
active_admin_config: active_admin_config,
|
|
74
|
+
action: action,
|
|
75
|
+
render_context: render_context
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def catalog_payload(active_admin_config:, action:, render_context: nil)
|
|
80
|
+
build_catalog(
|
|
81
|
+
active_admin_config: active_admin_config,
|
|
82
|
+
action: action,
|
|
83
|
+
render_context: render_context
|
|
84
|
+
).map do |entry|
|
|
85
|
+
{id: entry.id, label: entry.label, group: entry.group}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def register_column(resource_key:, id:, label:)
|
|
90
|
+
return if id.blank?
|
|
91
|
+
|
|
92
|
+
registered_columns[resource_key] << {id: id.to_s, label: label.to_s}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def register_panel(resource_key:, id:, label:)
|
|
96
|
+
return if id.blank?
|
|
97
|
+
|
|
98
|
+
registered_panels[resource_key] << {id: id.to_s, label: label.to_s}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def register_row(resource_key:, id:, label:)
|
|
102
|
+
return if id.blank?
|
|
103
|
+
|
|
104
|
+
registered_rows[resource_key] << {id: id.to_s, label: label.to_s}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_column_id(args, resource_key: nil)
|
|
108
|
+
title = args[0]
|
|
109
|
+
return title.to_s if title.is_a?(Symbol)
|
|
110
|
+
return title.to_s.parameterize(separator: "_") if title.is_a?(String) && title.present?
|
|
111
|
+
|
|
112
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
|
113
|
+
return options[:lens_id].to_s if options[:lens_id].present?
|
|
114
|
+
|
|
115
|
+
next_block_column_id(resource_key) if resource_key.present?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def column_label_from_args(args, column_id:)
|
|
119
|
+
title = args[0]
|
|
120
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
|
121
|
+
return options[:label].to_s if options[:label].present?
|
|
122
|
+
return title.to_s.humanize if title.is_a?(Symbol)
|
|
123
|
+
return title if title.is_a?(String) && title.present?
|
|
124
|
+
|
|
125
|
+
if column_id.to_s.start_with?("block_column_")
|
|
126
|
+
position = column_id.to_s.delete_prefix("block_column_")
|
|
127
|
+
return I18n.t("active_admin.favorites.block_column", position: position, default: "Custom column %{position}")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
column_id.to_s.humanize
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def next_block_column_id(resource_key)
|
|
134
|
+
sequence = Current.block_column_sequence
|
|
135
|
+
sequence[resource_key] += 1
|
|
136
|
+
"block_column_#{sequence[resource_key]}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def extract_panel_id(title, options)
|
|
140
|
+
options[:lens_id]&.to_s.presence || title.to_s.parameterize(separator: "_")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def extract_row_id(title, options)
|
|
144
|
+
options[:lens_id]&.to_s.presence || title.to_s.parameterize(separator: "_")
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module ViewLens
|
|
6
|
+
class Catalog
|
|
7
|
+
Entry = Data.define(:id, :label, :group)
|
|
8
|
+
|
|
9
|
+
def self.for(active_admin_config:, action:, render_context: nil)
|
|
10
|
+
new(active_admin_config:, action:, render_context:).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(active_admin_config:, action:, render_context: nil)
|
|
14
|
+
@active_admin_config = active_admin_config
|
|
15
|
+
@action = action.to_s
|
|
16
|
+
@render_context = render_context
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
case @action
|
|
21
|
+
when "index"
|
|
22
|
+
index_entries
|
|
23
|
+
when "show"
|
|
24
|
+
show_entries
|
|
25
|
+
else
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def index_entries
|
|
33
|
+
entries = []
|
|
34
|
+
entries.concat(column_entries)
|
|
35
|
+
entries.concat(filter_entries)
|
|
36
|
+
entries.concat(action_item_entries(:index))
|
|
37
|
+
entries
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def show_entries
|
|
41
|
+
entries = []
|
|
42
|
+
entries.concat(panel_entries)
|
|
43
|
+
entries.concat(row_entries)
|
|
44
|
+
entries.concat(action_item_entries(:show))
|
|
45
|
+
entries
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def column_entries
|
|
49
|
+
registered = ViewLens::Applicator.registered_columns[@active_admin_config.resource_name.route_key] || []
|
|
50
|
+
registered.map do |column|
|
|
51
|
+
Entry.new(
|
|
52
|
+
id: column[:id],
|
|
53
|
+
label: column[:label],
|
|
54
|
+
group: "columns"
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def filter_entries
|
|
60
|
+
return [] unless @active_admin_config.filters_enabled?
|
|
61
|
+
|
|
62
|
+
filters = @active_admin_config.filters
|
|
63
|
+
return [] if filters.blank?
|
|
64
|
+
|
|
65
|
+
filters.keys.map do |filter_key|
|
|
66
|
+
filter = @active_admin_config.filters[filter_key]
|
|
67
|
+
label = filter[:label] || filter_key.to_s.titleize
|
|
68
|
+
Entry.new(id: filter_key.to_s, label: label.to_s, group: "filters")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def action_item_entries(action)
|
|
73
|
+
action_items = @active_admin_config.action_items
|
|
74
|
+
return [] if action_items.blank?
|
|
75
|
+
|
|
76
|
+
action_items
|
|
77
|
+
.select { |item| item.display_on?(action, @render_context) }
|
|
78
|
+
.reject { |item| item.name.to_s.start_with?("personalize_page") }
|
|
79
|
+
.map do |item|
|
|
80
|
+
Entry.new(
|
|
81
|
+
id: item.name.to_s,
|
|
82
|
+
label: I18n.t("active_admin.favorites.action_items.#{item.name}", default: item.name.to_s.humanize),
|
|
83
|
+
group: "action_items"
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def panel_entries
|
|
89
|
+
registered = ViewLens::Applicator.registered_panels[@active_admin_config.resource_name.route_key] || []
|
|
90
|
+
registered.map do |panel|
|
|
91
|
+
Entry.new(id: panel[:id], label: panel[:label], group: "panels")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def row_entries
|
|
96
|
+
registered = ViewLens::Applicator.registered_rows[@active_admin_config.resource_name.route_key] || []
|
|
97
|
+
registered.map do |row|
|
|
98
|
+
Entry.new(id: row[:id], label: row[:label], group: "show_rows")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.grouped(entries)
|
|
103
|
+
entries.group_by(&:group)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module Favorites
|
|
5
|
+
module ViewLens
|
|
6
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
7
|
+
attribute :layout, :catalog, :catalog_context
|
|
8
|
+
attribute :registered_columns, :registered_panels, :registered_rows, :block_column_sequence
|
|
9
|
+
|
|
10
|
+
def registered_columns
|
|
11
|
+
super || self.registered_columns = Hash.new { |hash, key| hash[key] = [] }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def registered_panels
|
|
15
|
+
super || self.registered_panels = Hash.new { |hash, key| hash[key] = [] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def registered_rows
|
|
19
|
+
super || self.registered_rows = Hash.new { |hash, key| hash[key] = [] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def block_column_sequence
|
|
23
|
+
super || self.block_column_sequence = Hash.new(0)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|