spree_admin 5.2.0.rc2 → 5.2.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 +4 -4
- data/app/assets/stylesheets/spree/admin/application.scss +1 -1
- data/app/assets/stylesheets/spree/admin/components/_alerts.scss +1 -1
- data/app/assets/stylesheets/spree/admin/components/_buttons.scss +5 -4
- data/app/assets/stylesheets/spree/admin/components/_dialogs.scss +0 -1
- data/app/assets/stylesheets/spree/admin/components/_dropdowns.scss +6 -0
- data/app/assets/stylesheets/spree/admin/components/_main.scss +2 -233
- data/app/assets/stylesheets/spree/admin/components/_sidebar.scss +693 -0
- data/app/assets/stylesheets/spree/admin/components/_tables.scss +2 -2
- data/app/assets/stylesheets/spree/admin/components/_variants_form.scss +1 -2
- data/app/assets/stylesheets/spree/admin/global/_variables.scss +15 -12
- data/app/assets/stylesheets/spree/admin/shared/_base.scss +9 -3
- data/app/assets/stylesheets/spree/admin/shared/_forms.scss +5 -6
- data/app/assets/stylesheets/spree/admin/views/_dashboard.scss +14 -0
- data/app/controllers/spree/admin/admin_users_controller.rb +0 -2
- data/app/controllers/spree/admin/checkouts_controller.rb +1 -4
- data/app/controllers/spree/admin/coupon_codes_controller.rb +0 -14
- data/app/controllers/spree/admin/customer_returns_controller.rb +0 -13
- data/app/controllers/spree/admin/digital_assets_controller.rb +2 -2
- data/app/controllers/spree/admin/exports_controller.rb +2 -9
- data/app/controllers/spree/admin/gift_cards_controller.rb +7 -14
- data/app/controllers/spree/admin/integrations_controller.rb +1 -1
- data/app/controllers/spree/admin/invitations_controller.rb +0 -2
- data/app/controllers/spree/admin/metafields_controller.rb +1 -1
- data/app/controllers/spree/admin/oauth_applications_controller.rb +0 -10
- data/app/controllers/spree/admin/option_types_controller.rb +0 -10
- data/app/controllers/spree/admin/orders_controller.rb +1 -1
- data/app/controllers/spree/admin/page_blocks_controller.rb +1 -1
- data/app/controllers/spree/admin/pages_controller.rb +1 -1
- data/app/controllers/spree/admin/payment_methods_controller.rb +1 -11
- data/app/controllers/spree/admin/policies_controller.rb +4 -0
- data/app/controllers/spree/admin/posts_controller.rb +2 -10
- data/app/controllers/spree/admin/promotion_actions_controller.rb +1 -1
- data/app/controllers/spree/admin/promotion_rules_controller.rb +1 -1
- data/app/controllers/spree/admin/promotions_controller.rb +1 -1
- data/app/controllers/spree/admin/properties_controller.rb +0 -12
- data/app/controllers/spree/admin/reports_controller.rb +1 -1
- data/app/controllers/spree/admin/resource_controller.rb +27 -17
- data/app/controllers/spree/admin/return_authorizations_controller.rb +0 -10
- data/app/controllers/spree/admin/shipping_methods_controller.rb +4 -0
- data/app/controllers/spree/admin/stock_items_controller.rb +8 -11
- data/app/controllers/spree/admin/stock_locations_controller.rb +1 -1
- data/app/controllers/spree/admin/stock_transfers_controller.rb +0 -10
- data/app/controllers/spree/admin/store_credits_controller.rb +35 -35
- data/app/controllers/spree/admin/taxonomies_controller.rb +0 -10
- data/app/controllers/spree/admin/taxons_controller.rb +1 -1
- data/app/controllers/spree/admin/themes_controller.rb +6 -2
- data/app/controllers/spree/admin/translations_controller.rb +1 -1
- data/app/controllers/spree/admin/users_controller.rb +7 -17
- data/app/controllers/spree/admin/webhooks_subscribers_controller.rb +0 -10
- data/app/controllers/spree/admin/zones_controller.rb +0 -7
- data/app/helpers/spree/admin/base_helper.rb +1 -1
- data/app/helpers/spree/admin/drawer_helper.rb +6 -6
- data/app/helpers/spree/admin/dropdown_helper.rb +26 -16
- data/app/helpers/spree/admin/modal_helper.rb +2 -0
- data/app/helpers/spree/admin/navigation_helper.rb +47 -4
- data/app/helpers/spree/admin/orders_filters_helper.rb +3 -0
- data/app/helpers/spree/admin/promotion_actions_helper.rb +1 -1
- data/app/helpers/spree/admin/promotion_rules_helper.rb +1 -1
- data/app/helpers/spree/admin/translations_helper.rb +1 -1
- data/app/javascript/spree/admin/application.js +2 -1
- data/app/javascript/spree/admin/controllers/dropdown_controller.js +85 -14
- data/app/javascript/spree/admin/controllers/sidebar_controller.js +231 -0
- data/app/javascript/spree/admin/controllers/tooltip_controller.js +84 -31
- data/app/models/spree/admin/form_builder.rb +76 -17
- data/app/models/spree/admin/navigation/builder.rb +82 -0
- data/app/models/spree/admin/navigation/item.rb +177 -0
- data/app/models/spree/admin/navigation.rb +193 -0
- data/app/views/layouts/spree/admin.html.erb +1 -1
- data/app/views/spree/admin/assets/edit.html.erb +1 -1
- data/app/views/spree/admin/custom_domains/_custom_domains.html.erb +1 -1
- data/app/views/spree/admin/custom_domains/_form.html.erb +2 -14
- data/app/views/spree/admin/digital_assets/_table.html.erb +1 -1
- data/app/views/spree/admin/gift_cards/_filters.html.erb +25 -16
- data/app/views/spree/admin/gift_cards/index.html.erb +1 -1
- data/app/views/spree/admin/integrations/index.html.erb +20 -8
- data/app/views/spree/admin/invitations/new.html.erb +2 -1
- data/app/views/spree/admin/metafield_definitions/_filters.html.erb +1 -1
- data/app/views/spree/admin/newsletter_subscribers/_filters.html.erb +1 -1
- data/app/views/spree/admin/newsletter_subscribers/_table_header.html.erb +2 -2
- data/app/views/spree/admin/oauth_applications/_table_header.html.erb +1 -1
- data/app/views/spree/admin/orders/_customer.html.erb +3 -3
- data/app/views/spree/admin/orders/_filters.html.erb +33 -25
- data/app/views/spree/admin/orders/_header.html.erb +0 -5
- data/app/views/spree/admin/orders/_list.html.erb +3 -3
- data/app/views/spree/admin/orders/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/page_blocks/edit.html.erb +3 -3
- data/app/views/spree/admin/page_blocks/forms/_image.html.erb +2 -5
- data/app/views/spree/admin/page_builder/_add_block.html.erb +1 -1
- data/app/views/spree/admin/page_builder/_header.html.erb +1 -1
- data/app/views/spree/admin/page_builder/_pages_dropdown.html.erb +2 -2
- data/app/views/spree/admin/page_builder/_sidebar_block.html.erb +1 -1
- data/app/views/spree/admin/page_builder/_sidebar_colors.html.erb +2 -2
- data/app/views/spree/admin/page_builder/_sidebar_fonts.html.erb +3 -3
- data/app/views/spree/admin/page_builder/_sidebar_section.html.erb +1 -1
- data/app/views/spree/admin/page_links/_form.html.erb +4 -13
- data/app/views/spree/admin/page_links/_list.html.erb +1 -1
- data/app/views/spree/admin/page_links/edit.html.erb +1 -1
- data/app/views/spree/admin/page_sections/edit.html.erb +3 -3
- data/app/views/spree/admin/page_sections/forms/_header.html.erb +0 -2
- data/app/views/spree/admin/page_sections/new.html.erb +1 -1
- data/app/views/spree/admin/pages/_table_header.html.erb +3 -3
- data/app/views/spree/admin/payment_methods/index.html.erb +5 -1
- data/app/views/spree/admin/payments/_payment.html.erb +7 -0
- data/app/views/spree/admin/policies/_filters.html.erb +1 -1
- data/app/views/spree/admin/posts/_form.html.erb +1 -4
- data/app/views/spree/admin/posts/filters.html.erb +18 -8
- data/app/views/spree/admin/products/_bulk_operations.html.erb +2 -2
- data/app/views/spree/admin/products/_filters.html.erb +17 -6
- data/app/views/spree/admin/products/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/products/edit.html.erb +0 -2
- data/app/views/spree/admin/products/form/_status.html.erb +0 -3
- data/app/views/spree/admin/products/form/_variants.html.erb +1 -1
- data/app/views/spree/admin/profile/edit.html.erb +9 -61
- data/app/views/spree/admin/promotions/_filters.html.erb +23 -13
- data/app/views/spree/admin/promotions/_table_filter_dropdown.html.erb +1 -1
- data/app/views/spree/admin/promotions/_table_header.html.erb +1 -1
- data/app/views/spree/admin/promotions/form/_kind.html.erb +4 -4
- data/app/views/spree/admin/promotions/form/_settings.html.erb +2 -13
- data/app/views/spree/admin/refund_reasons/_table_header.html.erb +1 -1
- data/app/views/spree/admin/refunds/_form.html.erb +1 -9
- data/app/views/spree/admin/reimbursement_types/_table_header.html.erb +1 -1
- data/app/views/spree/admin/return_authorization_reasons/_table_header.html.erb +1 -1
- data/app/views/spree/admin/return_authorizations/filters.html.erb +1 -1
- data/app/views/spree/admin/roles/index.html.erb +1 -1
- data/app/views/spree/admin/shared/_audit_nav.html.erb +2 -0
- data/app/views/spree/admin/shared/_calendar_range_picker.html.erb +2 -2
- data/app/views/spree/admin/shared/_content_header.html.erb +5 -2
- data/app/views/spree/admin/shared/_developers_nav.html.erb +2 -4
- data/app/views/spree/admin/shared/_header.html.erb +5 -7
- data/app/views/spree/admin/shared/_index_table.html.erb +5 -4
- data/app/views/spree/admin/shared/_index_table_options.html.erb +1 -1
- data/app/views/spree/admin/shared/_navigation.html.erb +5 -0
- data/app/views/spree/admin/shared/_navigation_item.html.erb +64 -0
- data/app/views/spree/admin/shared/_new_item_dropdown.html.erb +2 -2
- data/app/views/spree/admin/shared/_page_section_image.html.erb +2 -5
- data/app/views/spree/admin/shared/_page_section_logo.html.erb +1 -1
- data/app/views/spree/admin/shared/_returns_and_refunds_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_shipping_nav.html.erb +3 -2
- data/app/views/spree/admin/shared/_sidebar.html.erb +33 -7
- data/app/views/spree/admin/shared/_stock_nav.html.erb +6 -3
- data/app/views/spree/admin/shared/_tax_nav.html.erb +1 -2
- data/app/views/spree/admin/shared/_team_nav.html.erb +2 -3
- data/app/views/spree/admin/shared/_user_dropdown.html.erb +29 -19
- data/app/views/spree/admin/shared/sidebar/_customers_nav.html.erb +7 -0
- data/app/views/spree/admin/shared/sidebar/_orders_nav.html.erb +22 -2
- data/app/views/spree/admin/shared/sidebar/_products_nav.html.erb +21 -0
- data/app/views/spree/admin/shared/sidebar/_promotions_nav.html.erb +8 -0
- data/app/views/spree/admin/shared/sidebar/_returns_nav.html.erb +12 -0
- data/app/views/spree/admin/shared/sidebar/_store_dropdown.html.erb +4 -2
- data/app/views/spree/admin/shared/sidebar/_store_nav.html.erb +15 -1
- data/app/views/spree/admin/shared/sidebar/_storefront_nav.html.erb +25 -3
- data/app/views/spree/admin/shared/sortable_tree/_taxonomy.html.erb +1 -1
- data/app/views/spree/admin/shipping_categories/_table_header.html.erb +1 -1
- data/app/views/spree/admin/shipping_methods/_table_header.html.erb +1 -1
- data/app/views/spree/admin/stock_items/_filters.html.erb +18 -8
- data/app/views/spree/admin/stock_locations/_table_header.html.erb +2 -2
- data/app/views/spree/admin/stock_locations/_table_row.html.erb +1 -1
- data/app/views/spree/admin/stock_transfers/_filters.html.erb +19 -9
- data/app/views/spree/admin/store_credit_categories/index.html.erb +1 -1
- data/app/views/spree/admin/store_credits/_list.html.erb +3 -3
- data/app/views/spree/admin/storefront/edit.html.erb +2 -14
- data/app/views/spree/admin/stores/form/_basic.html.erb +2 -8
- data/app/views/spree/admin/stores/form/_checkout.html.erb +2 -2
- data/app/views/spree/admin/stores/form/_checkout_links.html.erb +1 -1
- data/app/views/spree/admin/stores/form/_emails.html.erb +1 -1
- data/app/views/spree/admin/tax_categories/_table_header.html.erb +2 -2
- data/app/views/spree/admin/tax_rates/_form.html.erb +1 -10
- data/app/views/spree/admin/tax_rates/_table_header.html.erb +2 -2
- data/app/views/spree/admin/taxonomies/_table_header.html.erb +1 -1
- data/app/views/spree/admin/taxons/_form.html.erb +2 -9
- data/app/views/spree/admin/themes/_theme.html.erb +1 -1
- data/app/views/spree/admin/translations/translation_rows/_permalink_field_row.html.erb +1 -12
- data/app/views/spree/admin/users/_filters.html.erb +26 -17
- data/app/views/spree/admin/users/index.html.erb +1 -1
- data/config/initializers/spree_admin_navigation.rb +510 -0
- data/config/locales/en.yml +6 -0
- data/lib/generators/spree/admin/scaffold/templates/controller.rb.tt +3 -1
- data/lib/generators/spree/admin/scaffold/templates/views/_filters.html.erb.tt +1 -1
- data/lib/generators/spree/admin/scaffold/templates/views/_table_header.html.erb.tt +2 -2
- data/lib/generators/spree/admin/scaffold/templates/views/_table_row.html.erb.tt +8 -6
- data/lib/spree/admin/engine.rb +64 -2
- data/lib/spree/admin/runtime_configuration.rb +1 -0
- data/lib/spree/admin.rb +20 -0
- metadata +17 -15
- data/app/assets/stylesheets/spree/admin/components/_offcanvas.scss +0 -26
- data/app/javascript/spree/admin/helpers/canvas.js +0 -29
- data/app/views/spree/admin/shared/_offcanvas_nav.html.erb +0 -12
|
@@ -24,13 +24,18 @@ module Spree
|
|
|
24
24
|
# - :required [Boolean] whether field is required
|
|
25
25
|
# - :help [String] help text to display below the field
|
|
26
26
|
# - :help_bubble [String] help bubble text
|
|
27
|
+
# - :prepend [String] text to prepend before the input field
|
|
28
|
+
# - :append [String] text to append after the input field
|
|
27
29
|
# @return [String] HTML string containing the complete form group
|
|
28
30
|
def spree_text_field(method, options = {})
|
|
29
31
|
options[:class] ||= 'form-control'
|
|
32
|
+
prepend = options.delete(:prepend)
|
|
33
|
+
append = options.delete(:append)
|
|
34
|
+
|
|
30
35
|
@template.content_tag(:div, class: 'form-group') do
|
|
31
36
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
32
|
-
@template.text_field(@object_name, method, objectify_options(options)) +
|
|
33
|
-
@template.error_message_on(@object_name, method) +
|
|
37
|
+
wrap_with_input_group(@template.text_field(@object_name, method, objectify_options(options)), prepend, append) +
|
|
38
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
@@ -41,10 +46,13 @@ module Spree
|
|
|
41
46
|
# @return [String] HTML string containing the complete form group
|
|
42
47
|
def spree_number_field(method, options = {})
|
|
43
48
|
options[:class] ||= 'form-control'
|
|
49
|
+
prepend = options.delete(:prepend)
|
|
50
|
+
append = options.delete(:append)
|
|
51
|
+
|
|
44
52
|
@template.content_tag(:div, class: 'form-group') do
|
|
45
53
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
46
|
-
@template.number_field(@object_name, method, objectify_options(options)) +
|
|
47
|
-
@template.error_message_on(@object_name, method) +
|
|
54
|
+
wrap_with_input_group(@template.number_field(@object_name, method, objectify_options(options)), prepend, append) +
|
|
55
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
48
56
|
end
|
|
49
57
|
end
|
|
50
58
|
|
|
@@ -58,7 +66,7 @@ module Spree
|
|
|
58
66
|
@template.content_tag(:div, class: 'form-group') do
|
|
59
67
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
60
68
|
@template.email_field(@object_name, method, objectify_options(options)) +
|
|
61
|
-
@template.error_message_on(@object_name, method) +
|
|
69
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
62
70
|
end
|
|
63
71
|
end
|
|
64
72
|
|
|
@@ -72,7 +80,7 @@ module Spree
|
|
|
72
80
|
@template.content_tag(:div, class: 'form-group') do
|
|
73
81
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
74
82
|
@template.date_field(@object_name, method, objectify_options(options)) +
|
|
75
|
-
@template.error_message_on(@object_name, method) +
|
|
83
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
76
84
|
end
|
|
77
85
|
end
|
|
78
86
|
|
|
@@ -86,7 +94,7 @@ module Spree
|
|
|
86
94
|
@template.content_tag(:div, class: 'form-group') do
|
|
87
95
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
88
96
|
@template.datetime_field(@object_name, method, objectify_options(options)) +
|
|
89
|
-
@template.error_message_on(@object_name, method) +
|
|
97
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
90
98
|
end
|
|
91
99
|
end
|
|
92
100
|
|
|
@@ -107,7 +115,7 @@ module Spree
|
|
|
107
115
|
|
|
108
116
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
109
117
|
@template.text_area(@object_name, method, objectify_options(options)) +
|
|
110
|
-
@template.error_message_on(@object_name, method) +
|
|
118
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
111
119
|
end
|
|
112
120
|
end
|
|
113
121
|
|
|
@@ -122,7 +130,7 @@ module Spree
|
|
|
122
130
|
@template.content_tag(:div, class: 'trix-container') do
|
|
123
131
|
@template.rich_text_area(@object_name, method, objectify_options(options))
|
|
124
132
|
end +
|
|
125
|
-
@template.error_message_on(@object_name, method) +
|
|
133
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
126
134
|
end
|
|
127
135
|
end
|
|
128
136
|
|
|
@@ -147,7 +155,7 @@ module Spree
|
|
|
147
155
|
@template.content_tag(:div, class: 'form-group') do
|
|
148
156
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
149
157
|
@template.select(@object_name, method, choices, objectify_options(options), html_options, &block) +
|
|
150
|
-
@template.error_message_on(@object_name, method) +
|
|
158
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
151
159
|
end
|
|
152
160
|
end
|
|
153
161
|
|
|
@@ -173,7 +181,7 @@ module Spree
|
|
|
173
181
|
@template.content_tag(:div, class: 'form-group') do
|
|
174
182
|
@template.label(@object_name, method, get_label(method, options)) +
|
|
175
183
|
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), html_options) +
|
|
176
|
-
@template.error_message_on(@object_name, method) +
|
|
184
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
177
185
|
end
|
|
178
186
|
end
|
|
179
187
|
|
|
@@ -187,7 +195,7 @@ module Spree
|
|
|
187
195
|
@template.content_tag(:div, class: 'custom-control custom-checkbox') do
|
|
188
196
|
@template.check_box(@object_name, method, objectify_options(options.merge(class: 'custom-control-input'))) +
|
|
189
197
|
@template.label(@object_name, method, get_label(method, options), class: 'custom-control-label')
|
|
190
|
-
end + @template.error_message_on(@object_name, method) +
|
|
198
|
+
end + @template.error_message_on(@object_name, method) + spree_field_help(method, options.merge!(class: 'form-text mt-2 ml-4'))
|
|
191
199
|
end
|
|
192
200
|
end
|
|
193
201
|
|
|
@@ -204,22 +212,43 @@ module Spree
|
|
|
204
212
|
@template.content_tag(:div, class: 'custom-control custom-radio') do
|
|
205
213
|
@template.radio_button(@object_name, method, tag_value, objectify_options(options.merge(class: 'custom-control-input'))) +
|
|
206
214
|
@template.label(@object_name, method, get_label(method, options), class: 'custom-control-label', for: options[:id])
|
|
207
|
-
end + @template.error_message_on(@object_name, method) +
|
|
215
|
+
end + @template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
208
216
|
end
|
|
209
217
|
end
|
|
210
218
|
|
|
219
|
+
# Create a direct file upload field with Spree form styling
|
|
220
|
+
#
|
|
221
|
+
# @param method [Symbol] the field name
|
|
222
|
+
# @param options [Hash] field options
|
|
223
|
+
# @option options [Boolean] :crop whether to crop the image
|
|
224
|
+
# @option options [Boolean] :auto_submit whether to auto-submit the form when the file is uploaded
|
|
225
|
+
# @option options [Boolean] :can_delete whether to show the delete button
|
|
226
|
+
# @option options [Boolean] :inline whether to display the uploader inline
|
|
227
|
+
# @option options [Integer] :height the height of the uploader
|
|
228
|
+
# @option options [Integer] :width the width of the uploader
|
|
229
|
+
# @option options [Array] :allowed_file_types the allowed file types, defaults to image types
|
|
230
|
+
# @return [String] HTML string containing the complete form group with direct file upload field
|
|
231
|
+
def spree_file_field(method, options = {})
|
|
232
|
+
@template.content_tag(:div, class: 'form-group') do
|
|
233
|
+
@template.label(@object_name, method, get_label(method, options)) +
|
|
234
|
+
@template.render('active_storage/upload_form', form: self, field_name: method, **options) +
|
|
235
|
+
@template.error_message_on(@object_name, method) + spree_field_help(method, options.merge(class: 'form-text mt-2'))
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
private
|
|
240
|
+
|
|
211
241
|
# Generate help text for a field
|
|
212
242
|
#
|
|
213
243
|
# @param _method [Symbol] the field name (unused but kept for consistency)
|
|
214
244
|
# @param options [Hash] field options
|
|
215
245
|
# @option options [String] :help help text to display
|
|
216
246
|
# @return [String] HTML string containing the help text or empty string
|
|
217
|
-
def
|
|
218
|
-
|
|
247
|
+
def spree_field_help(_method, options = {})
|
|
248
|
+
options[:class] ||= 'form-text mt-2'
|
|
249
|
+
@template.content_tag(:span, options[:help], class: options[:class])
|
|
219
250
|
end
|
|
220
251
|
|
|
221
|
-
private
|
|
222
|
-
|
|
223
252
|
# Generate the label for a field with required indicator and help bubble
|
|
224
253
|
#
|
|
225
254
|
# @param method [Symbol] the field name
|
|
@@ -245,6 +274,36 @@ module Spree
|
|
|
245
274
|
|
|
246
275
|
@template.raw(translated_label + required_label + help_bubble)
|
|
247
276
|
end
|
|
277
|
+
|
|
278
|
+
# Wrap a field with an input group if prepend or append is specified
|
|
279
|
+
#
|
|
280
|
+
# @param field_html [String] the HTML for the field
|
|
281
|
+
# @param prepend [String, nil] text to prepend before the input field
|
|
282
|
+
# @param append [String, nil] text to append after the input field
|
|
283
|
+
# @return [String] HTML string with input group wrapper or the original field
|
|
284
|
+
def wrap_with_input_group(field_html, prepend = nil, append = nil)
|
|
285
|
+
return field_html if prepend.nil? && append.nil?
|
|
286
|
+
|
|
287
|
+
@template.content_tag(:div, class: 'input-group') do
|
|
288
|
+
prepend_html = if prepend.present?
|
|
289
|
+
@template.content_tag(:div, class: 'input-group-prepend') do
|
|
290
|
+
@template.content_tag(:span, prepend, class: 'input-group-text')
|
|
291
|
+
end
|
|
292
|
+
else
|
|
293
|
+
''.html_safe
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
append_html = if append.present?
|
|
297
|
+
@template.content_tag(:div, class: 'input-group-append') do
|
|
298
|
+
@template.content_tag(:span, append, class: 'input-group-text')
|
|
299
|
+
end
|
|
300
|
+
else
|
|
301
|
+
''.html_safe
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
prepend_html + field_html + append_html
|
|
305
|
+
end
|
|
306
|
+
end
|
|
248
307
|
end
|
|
249
308
|
end
|
|
250
309
|
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
class Builder
|
|
5
|
+
attr_reader :registry, :parent_item
|
|
6
|
+
|
|
7
|
+
def initialize(registry, parent_item = nil)
|
|
8
|
+
@registry = registry
|
|
9
|
+
@parent_item = parent_item
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Add a navigation item
|
|
13
|
+
# If parent_item is set, the item becomes a child
|
|
14
|
+
def add(key, **options, &block)
|
|
15
|
+
# If we have a parent item, set it in the options
|
|
16
|
+
if parent_item
|
|
17
|
+
options[:parent] = parent_item.key
|
|
18
|
+
# Adjust position to be relative to parent
|
|
19
|
+
options[:position] ||= parent_item.children.size * 10
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
item = registry.add(key, **options, &block)
|
|
23
|
+
item
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Add a section (group of items)
|
|
27
|
+
def section(key, label: nil, &block)
|
|
28
|
+
section_item = add(key, section_label: label || key.to_s.humanize)
|
|
29
|
+
|
|
30
|
+
if block_given?
|
|
31
|
+
builder = self.class.new(registry, section_item)
|
|
32
|
+
# Support both block styles: |nav| nav.add or just add
|
|
33
|
+
if block.arity > 0
|
|
34
|
+
# Block expects parameter: do |nav| nav.add ... end
|
|
35
|
+
block.call(builder)
|
|
36
|
+
else
|
|
37
|
+
# Block uses implicit self: do add ... end
|
|
38
|
+
builder.instance_eval(&block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
section_item
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Remove an item
|
|
46
|
+
def remove(key)
|
|
47
|
+
registry.remove(key)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Update an item
|
|
51
|
+
def update(key, **options)
|
|
52
|
+
registry.update(key, **options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Insert before another item
|
|
56
|
+
def insert_before(target_key, new_key, **options)
|
|
57
|
+
registry.insert_before(target_key, new_key, **options)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Insert after another item
|
|
61
|
+
def insert_after(target_key, new_key, **options)
|
|
62
|
+
registry.insert_after(target_key, new_key, **options)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Move an item
|
|
66
|
+
def move(key, **position_options)
|
|
67
|
+
registry.move(key, **position_options)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Replace an item
|
|
71
|
+
def replace(key, **options, &block)
|
|
72
|
+
registry.replace(key, **options, &block)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Reorder items with a block
|
|
76
|
+
def reorder(&block)
|
|
77
|
+
instance_eval(&block) if block_given?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
class Item
|
|
5
|
+
attr_accessor :key, :label, :url, :icon, :position, :parent_key,
|
|
6
|
+
:condition, :badge, :badge_class, :tooltip, :target, :data_attributes, :children, :section_label, :active_condition
|
|
7
|
+
|
|
8
|
+
def initialize(key, **options)
|
|
9
|
+
@key = key.to_sym
|
|
10
|
+
@label = options[:label]
|
|
11
|
+
@url = options[:url]
|
|
12
|
+
@icon = options[:icon]
|
|
13
|
+
@position = options[:position] || 999
|
|
14
|
+
@parent_key = options[:parent]
|
|
15
|
+
@active_condition = options[:active]
|
|
16
|
+
@condition = options.key?(:if) ? options[:if] : options[:condition]
|
|
17
|
+
@badge = options[:badge]
|
|
18
|
+
@badge_class = options[:badge_class]
|
|
19
|
+
@tooltip = options[:tooltip]
|
|
20
|
+
@target = options[:target]
|
|
21
|
+
@data_attributes = options[:data_attributes] || {}
|
|
22
|
+
@section_label = options[:section_label]
|
|
23
|
+
@children = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Check if this item should be visible for the given user/context
|
|
27
|
+
# @param user_or_context [Object] Either a user object or a view context
|
|
28
|
+
def visible?(user_or_context = nil)
|
|
29
|
+
return true if condition.nil?
|
|
30
|
+
|
|
31
|
+
if condition.respond_to?(:call)
|
|
32
|
+
# If we have a view context with instance_exec, use it to evaluate the condition
|
|
33
|
+
# This allows access to can? and other helper methods
|
|
34
|
+
if user_or_context.respond_to?(:instance_exec)
|
|
35
|
+
user_or_context.instance_exec(&condition)
|
|
36
|
+
else
|
|
37
|
+
# Otherwise, call with the user object
|
|
38
|
+
condition.call(user_or_context)
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
condition
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if this item is active based on current path
|
|
46
|
+
# @param current_path [String] The current request path
|
|
47
|
+
# @param context [Object] View context with access to route helpers
|
|
48
|
+
def active?(current_path, context = nil)
|
|
49
|
+
# Use custom active condition if provided (most flexible)
|
|
50
|
+
if active_condition.respond_to?(:call)
|
|
51
|
+
if context&.respond_to?(:instance_exec)
|
|
52
|
+
return context.instance_exec(&active_condition)
|
|
53
|
+
else
|
|
54
|
+
return active_condition.call
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Match exact path
|
|
59
|
+
item_url = resolve_url(context)
|
|
60
|
+
return true if item_url && current_path == item_url
|
|
61
|
+
|
|
62
|
+
# Check if any child item is active
|
|
63
|
+
return true if children.any? { |child| child.active?(current_path, context) }
|
|
64
|
+
|
|
65
|
+
# Default: match if path starts with url (handled by active_link_to)
|
|
66
|
+
if item_url
|
|
67
|
+
current_path.start_with?(item_url)
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Resolve URL (handles symbols, procs, and strings)
|
|
74
|
+
# @param context [Object] View context with access to route helpers
|
|
75
|
+
def resolve_url(context = nil)
|
|
76
|
+
case url
|
|
77
|
+
when Symbol
|
|
78
|
+
# Try to call the route helper on the context (which has spree routes)
|
|
79
|
+
if context&.respond_to?(url)
|
|
80
|
+
context.send(url)
|
|
81
|
+
elsif context&.respond_to?(:spree)
|
|
82
|
+
context.spree.send(url) rescue url.to_s
|
|
83
|
+
else
|
|
84
|
+
url.to_s
|
|
85
|
+
end
|
|
86
|
+
when Proc
|
|
87
|
+
# Evaluate proc in the context where route helpers are available
|
|
88
|
+
if context&.respond_to?(:instance_exec)
|
|
89
|
+
context.instance_exec(&url)
|
|
90
|
+
else
|
|
91
|
+
url.call
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
url
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Resolve label (handles i18n keys)
|
|
99
|
+
def resolve_label
|
|
100
|
+
return label unless label.is_a?(String) || label.is_a?(Symbol)
|
|
101
|
+
|
|
102
|
+
# Use Spree.t for translation which handles the spree namespace
|
|
103
|
+
Spree.t(label, default: label.to_s.humanize)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Compute badge value
|
|
107
|
+
# @param view_context [Object] View context with access to helper methods
|
|
108
|
+
def badge_value(view_context = nil)
|
|
109
|
+
return nil unless badge
|
|
110
|
+
|
|
111
|
+
if badge.respond_to?(:call)
|
|
112
|
+
# Evaluate badge in view context if available (for access to helpers)
|
|
113
|
+
if view_context&.respond_to?(:instance_exec)
|
|
114
|
+
view_context.instance_exec(&badge)
|
|
115
|
+
else
|
|
116
|
+
badge.call
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
badge
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check if this is a section header
|
|
124
|
+
def section?
|
|
125
|
+
section_label.present?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Add a child item
|
|
129
|
+
def add_child(item)
|
|
130
|
+
children << item
|
|
131
|
+
item.parent_key = key
|
|
132
|
+
sort_children!
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Remove a child item
|
|
136
|
+
def remove_child(key)
|
|
137
|
+
children.reject! { |child| child.key == key }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Sort children by position
|
|
141
|
+
def sort_children!
|
|
142
|
+
children.sort_by! { |child| [child.position, child.key.to_s] }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Deep clone for modifications
|
|
146
|
+
def deep_clone
|
|
147
|
+
cloned = self.class.new(key, to_h)
|
|
148
|
+
cloned.children = children.map(&:deep_clone)
|
|
149
|
+
cloned
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Convert to hash
|
|
153
|
+
def to_h
|
|
154
|
+
{
|
|
155
|
+
label: label,
|
|
156
|
+
url: url,
|
|
157
|
+
icon: icon,
|
|
158
|
+
position: position,
|
|
159
|
+
parent: parent_key,
|
|
160
|
+
active: active_condition,
|
|
161
|
+
condition: condition,
|
|
162
|
+
badge: badge,
|
|
163
|
+
badge_class: badge_class,
|
|
164
|
+
tooltip: tooltip,
|
|
165
|
+
target: target,
|
|
166
|
+
data_attributes: data_attributes,
|
|
167
|
+
section_label: section_label
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def inspect
|
|
172
|
+
"#<Spree::Admin::Navigation::Item key=#{key} label=#{label} children=#{children.size}>"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
class Navigation
|
|
4
|
+
attr_reader :items, :context
|
|
5
|
+
|
|
6
|
+
def initialize(context)
|
|
7
|
+
@context = context
|
|
8
|
+
@items = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Add a navigation item
|
|
12
|
+
def add(key, **options, &block)
|
|
13
|
+
key = key.to_sym
|
|
14
|
+
item = Item.new(key, **options)
|
|
15
|
+
|
|
16
|
+
@items[key] = item
|
|
17
|
+
|
|
18
|
+
# If block provided, it's for children
|
|
19
|
+
if block_given?
|
|
20
|
+
builder = Builder.new(self, item)
|
|
21
|
+
# Support both block styles: |nav| nav.add or just add
|
|
22
|
+
if block.arity > 0
|
|
23
|
+
# Block expects parameter: do |nav| nav.add ... end
|
|
24
|
+
block.call(builder)
|
|
25
|
+
else
|
|
26
|
+
# Block uses implicit self: do add ... end
|
|
27
|
+
builder.instance_eval(&block)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sort_items!
|
|
32
|
+
item
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Remove a navigation item
|
|
36
|
+
def remove(key)
|
|
37
|
+
key = key.to_sym
|
|
38
|
+
removed = @items.delete(key)
|
|
39
|
+
|
|
40
|
+
# Also remove from any parent's children
|
|
41
|
+
@items.each_value do |item|
|
|
42
|
+
item.remove_child(key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
removed
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Update an existing navigation item
|
|
49
|
+
def update(key, **options)
|
|
50
|
+
key = key.to_sym
|
|
51
|
+
item = @items[key]
|
|
52
|
+
|
|
53
|
+
return nil unless item
|
|
54
|
+
|
|
55
|
+
options.each do |attr, value|
|
|
56
|
+
item.send("#{attr}=", value) if item.respond_to?("#{attr}=")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sort_items!
|
|
60
|
+
item
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Find a navigation item
|
|
64
|
+
def find(key)
|
|
65
|
+
@items[key.to_sym]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if item exists
|
|
69
|
+
def exists?(key)
|
|
70
|
+
@items.key?(key.to_sym)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Insert item before another item
|
|
74
|
+
def insert_before(target_key, new_key, **options)
|
|
75
|
+
target = find(target_key)
|
|
76
|
+
return nil unless target
|
|
77
|
+
|
|
78
|
+
new_position = target.position - 1
|
|
79
|
+
add(new_key, **options.merge(position: new_position))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Insert item after another item
|
|
83
|
+
def insert_after(target_key, new_key, **options)
|
|
84
|
+
target = find(target_key)
|
|
85
|
+
return nil unless target
|
|
86
|
+
|
|
87
|
+
new_position = target.position + 1
|
|
88
|
+
add(new_key, **options.merge(position: new_position))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Move item to a new position
|
|
92
|
+
def move(key, position: nil, before: nil, after: nil)
|
|
93
|
+
item = find(key)
|
|
94
|
+
return nil unless item
|
|
95
|
+
|
|
96
|
+
if before
|
|
97
|
+
target = find(before)
|
|
98
|
+
item.position = target.position - 1 if target
|
|
99
|
+
elsif after
|
|
100
|
+
target = find(after)
|
|
101
|
+
item.position = target.position + 1 if target
|
|
102
|
+
elsif position == :first
|
|
103
|
+
item.position = -999
|
|
104
|
+
elsif position == :last
|
|
105
|
+
item.position = 999
|
|
106
|
+
elsif position.is_a?(Integer)
|
|
107
|
+
item.position = position
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sort_items!
|
|
111
|
+
item
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Replace an item
|
|
115
|
+
def replace(key, **options, &block)
|
|
116
|
+
remove(key)
|
|
117
|
+
add(key, **options, &block)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get all root items (items without a parent)
|
|
121
|
+
def root_items
|
|
122
|
+
@items.values.select { |item| item.parent_key.nil? }.sort_by { |item| [item.position, item.key.to_s] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get all items that are visible to the user
|
|
126
|
+
def visible_items(user = nil, parent_key = nil)
|
|
127
|
+
items_to_filter = if parent_key
|
|
128
|
+
find(parent_key)&.children || []
|
|
129
|
+
else
|
|
130
|
+
root_items
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
items_to_filter.select { |item| item.visible?(user) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Build tree structure
|
|
137
|
+
def build_tree
|
|
138
|
+
# First, clear all children
|
|
139
|
+
@items.each_value { |item| item.children.clear }
|
|
140
|
+
|
|
141
|
+
# Then rebuild the tree
|
|
142
|
+
@items.each_value do |item|
|
|
143
|
+
if item.parent_key && (parent = @items[item.parent_key])
|
|
144
|
+
parent.add_child(item)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
root_items
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Clear all items
|
|
152
|
+
def clear
|
|
153
|
+
@items.clear
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get all registered paths (for settings_area? detection)
|
|
157
|
+
def registered_paths(context = nil)
|
|
158
|
+
@items.values.map { |item| item.resolve_url(context) }.compact
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Add a section
|
|
162
|
+
def section(key, label: nil, &block)
|
|
163
|
+
# Create a section header item
|
|
164
|
+
section_item = add(key, section_label: label || key.to_s.humanize, position: @items.size * 100)
|
|
165
|
+
|
|
166
|
+
if block_given?
|
|
167
|
+
builder = Builder.new(self, section_item)
|
|
168
|
+
builder.instance_eval(&block)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
section_item
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Deep clone the registry
|
|
175
|
+
def deep_clone
|
|
176
|
+
cloned = self.class.new(context)
|
|
177
|
+
@items.each do |key, item|
|
|
178
|
+
cloned.items[key] = item.deep_clone
|
|
179
|
+
end
|
|
180
|
+
cloned.build_tree
|
|
181
|
+
cloned
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def sort_items!
|
|
187
|
+
# Sort items by position, then rebuild tree
|
|
188
|
+
@items = @items.sort_by { |_key, item| [item.position, item.key.to_s] }.to_h
|
|
189
|
+
build_tree
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</head>
|
|
7
7
|
|
|
8
8
|
<body class="admin min-vh-100 <%= controller_name %> <%= action_name %>"
|
|
9
|
-
data-controller="admin"
|
|
9
|
+
data-controller="admin sidebar"
|
|
10
10
|
data-action="keydown.ctrl+s->admin#save keydown.meta+s->admin#save"
|
|
11
11
|
>
|
|
12
12
|
<%= render_admin_partials(:body_start_partials) %>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<%= dialog_header(Spree.t(:edit) + ' ' + Spree.t(:image)) %>
|
|
3
3
|
<%= form_with model: @asset, url: spree.admin_asset_path(@asset), method: :put, scope: :asset do |f| %>
|
|
4
4
|
<div class="dialog-body pb-0" data-turbo-permanent id="asset-<%= @asset.key.parameterize %>">
|
|
5
|
-
<%=
|
|
5
|
+
<%= f.spree_file_field :attachment, width: 200, height: 200, can_delete: false, label: Spree.t(:image) %>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="dialog-body" style="min-height: 200px">
|
|
8
8
|
<%= f.spree_text_area :alt, label: Spree.t(:alt_text), rows: 4 %>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<table class="table">
|
|
4
4
|
<thead>
|
|
5
5
|
<tr>
|
|
6
|
-
<th scope="col"><%= sort_link
|
|
6
|
+
<th scope="col"><%= sort_link @search, :name, Spree.t(:name) %></th>
|
|
7
7
|
<th scope="col"><%= Spree.t(:active) %>?</th>
|
|
8
8
|
<th scope="col"><%= Spree.t(:default) %>?</th>
|
|
9
9
|
<th scope="col"></th>
|