Module: Avo::ApplicationHelper
- Includes:
- ResourcesHelper, SummaryChartHelper
- Included in:
- ActionsComponent, AlertComponent, BacktraceAlertComponent, BaseApplicationController, BaseComponent, Debug::StatusComponent, DeveloperWarningComponent, Fields::Common::KeyValueComponent, Fields::HasOneField::ShowComponent, FiltersComponent, FlashAlertsComponent, Index::ResourceControlsComponent, Items::PanelComponent, MediaLibrary::ItemDetailsComponent, MediaLibrary::ListComponent, UI::Tabs::TabComponent, ViewTypes::TableComponent, Views::ResourceEditComponent, Views::ResourceIndexComponent, Views::ResourceShowComponent
- Defined in:
- app/helpers/avo/application_helper.rb
Constant Summary
collapse
- UNRENDERABLE_IMAGE_CONTENT_TYPES =
The image formats no browser paints: HEIC and HEIF decode in Safari alone,
TIFF and PSD nowhere. Not ActiveStorage.web_image_content_types -- that
one picks a variant's output format, and browsers render plenty it leaves
out (AVIF always, WebP unless the app loads Rails 7.2 defaults).
%w[image/heic image/heif image/tiff image/vnd.adobe.photoshop].freeze
Instance Method Summary
collapse
-
#a_button(**args, &block) ⇒ Object
-
#a_link(path = nil, **args, &block) ⇒ Object
-
#appearance_neutral_labels(neutrals) ⇒ Object
-
#avo_appearance_t(key, **options) ⇒ Object
Translates appearance UI strings with English fallback when the active locale omits Avo 4 appearance keys.
-
#body_classes ⇒ Object
-
#button_classes(extra_classes = nil, color: nil, variant: nil, size: :md, active: false) ⇒ Object
-
#chart_color(index) ⇒ Object
-
#container_classes ⇒ Object
-
#custom_sidebar_width_default ⇒ Object
nil when the host has not changed the starting width, so the pre-paint carrier emits nothing at all and the stylesheet's own --spacing(64) fallback stands — same discipline as sidebar_width returning nil for "no cookie".
-
#d(value) ⇒ Object
-
#decode_filter_params(encoded_params) ⇒ Object
-
#e(value) ⇒ Object
-
#editor_file_path(path) ⇒ Object
-
#editor_url(path) ⇒ Object
-
#empty_state(**args) ⇒ Object
-
#encode_filter_params(filter_params) ⇒ Object
-
#frame_id(resource) ⇒ Object
-
#get_model_class(model) ⇒ Object
-
#input_classes(extra_classes = "", has_error: false, size: :md) ⇒ Object
-
#manual_frame_cookie_name(url) ⇒ Object
The cookie name that remembers an opened manual frame.
-
#manual_frame_remembered?(url, auto_load_for) ⇒ Boolean
Whether this manual frame was opened recently enough to skip the placeholder and render a real auto-loading <turbo-frame src> (so the Load button never appears).
-
#mount_path ⇒ Object
-
#number_to_social(number, start_at: 10_000) ⇒ Object
-
#possibly_rails_authentication? ⇒ Boolean
-
#render_header_menu_items ⇒ Object
-
#render_license_warning(title: "", message: "", icon: "exclamation") ⇒ Object
-
#root_path_without_url ⇒ Object
-
#rtl? ⇒ Boolean
Check if the current locale is RTL (Right-to-Left).
-
#safe_blob_path(blob) ⇒ Object
-
#safe_blob_representation_url(representation) ⇒ Object
-
#safe_blob_url(blob) ⇒ Object
Active Storage URL helpers raise UrlGenerationError when a blob's filename is blank (the route requires a :filename segment).
-
#safe_image_url(attachment) ⇒ Object
Serves an attachment the browser can't paint through a variant, which ActiveStorage renders as PNG.
-
#sidebar_width_default ⇒ Object
Already clamped into the bounds by Avo::Configuration.
-
#sidebar_width_max ⇒ Object
-
#sidebar_width_min ⇒ Object
Resizable-sidebar width bounds for the JS layer (window.Avo.configuration).
-
#text_direction ⇒ Object
Returns "rtl" or "ltr" based on current locale.
-
#ui ⇒ Object
-
#wrap_in_modal(content) ⇒ Object
#summary_chart_params_for
#field_wrapper, #index_field_wrapper, #item_selector_data_attributes, #record_path, #record_title, #resource_for_record, #resource_grid, #resource_show_path, #resource_table
Instance Method Details
108
109
110
111
112
|
# File 'app/helpers/avo/application_helper.rb', line 108
def a_button(**args, &block)
render Avo::ButtonComponent.new(is_link: false, **args) do
capture(&block) if block.present?
end
end
|
#a_link(path = nil, **args, &block) ⇒ Object
114
115
116
117
118
|
# File 'app/helpers/avo/application_helper.rb', line 114
def a_link(path = nil, **args, &block)
render Avo::ButtonComponent.new(path, is_link: true, **args) do
capture(&block) if block.present?
end
end
|
#appearance_neutral_labels(neutrals) ⇒ Object
302
303
304
|
# File 'app/helpers/avo/application_helper.rb', line 302
def appearance_neutral_labels(neutrals)
neutrals.index_with { |theme| avo_appearance_t("neutrals_list.#{theme}", default: theme.capitalize) }
end
|
#avo_appearance_t(key, **options) ⇒ Object
Translates appearance UI strings with English fallback when the active locale
omits Avo 4 appearance keys. Returns plain text safe for HTML attributes.
294
295
296
297
298
299
300
|
# File 'app/helpers/avo/application_helper.rb', line 294
def avo_appearance_t(key, **options)
full_key = "avo.appearance.#{key}"
defaults = []
defaults << I18n.t(full_key, locale: :en, raise: false) if I18n.locale != :en
defaults << options[:default] if options.key?(:default)
I18n.t(full_key, **options.except(:default), default: defaults.presence)
end
|
#body_classes ⇒ Object
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
# File 'app/helpers/avo/application_helper.rb', line 214
def body_classes
os_class = request.user_agent.to_s.include?("Mac") ? "os-mac" : "os-pc"
view_class = if controller.class.superclass.to_s == "Avo::ResourcesController"
case action_name.to_sym
when :index
"resource-index-view"
when :show
"resource-show-view"
when :edit, :update
"resource-edit-view"
when :new, :create
"resource-new-view"
end
end
[os_class, view_class, *Array.wrap(Avo.configuration.body_classes)]
end
|
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
# File 'app/helpers/avo/application_helper.rb', line 120
def button_classes( = nil, color: nil, variant: nil, size: :md, active: false)
classes = "inline-flex grow-0 items-center text-sm font-semibold leading-6 fill-current whitespace-nowrap transition duration-100 rounded-sm transform transition duration-100 active:translate-x-px active:translate-y-px cursor-pointer disabled:cursor-not-allowed #{}"
if color.present?
if variant.present? && (variant.to_sym == :outlined)
classes += " bg-white border"
classes += " hover:border-#{color}-700 border-#{color}-500 text-#{color}-600 hover:text-#{color}-700 disabled:border-gray-300 disabled:text-gray-600"
else
classes += " text-white bg-#{color}-500 hover:bg-#{color}-600 disabled:bg-#{color}-300"
end
else
classes += " text-gray-700 bg-white hover:bg-gray-100 disabled:bg-gray-300"
end
size = size.present? ? size.to_sym : :md
classes += case size
when :xs
" p-2 py-1"
when :sm
" py-1 px-4"
when :md
" py-2 px-4"
when :xl
" py-3 px-4"
else
" p-4"
end
classes
end
|
#chart_color(index) ⇒ Object
205
206
207
208
|
# File 'app/helpers/avo/application_helper.rb', line 205
def chart_color(index)
colors = Avo.configuration.appearance.chart_colors
colors[index % colors.length]
end
|
#container_classes ⇒ Object
243
244
245
246
247
248
|
# File 'app/helpers/avo/application_helper.rb', line 243
def container_classes
return "container-#{container_width_css_suffix(@container_size.to_sym)}" if @container_size.present?
width = Avo.configuration.container_width.fetch(@view&.to_sym, :large)
"container-#{container_width_css_suffix(width)}"
end
|
nil when the host has not changed the starting width, so the pre-paint
carrier emits nothing at all and the stylesheet's own --spacing(64)
fallback stands — same discipline as sidebar_width returning nil for "no
cookie". Keeps the CSP surface and the bytes at zero for everyone who never
configures this.
321
322
323
324
325
|
# File 'app/helpers/avo/application_helper.rb', line 321
def
width =
width unless width == SIDEBAR_WIDTH_DEFAULT
end
|
#d(value) ⇒ Object
257
258
259
260
261
262
|
# File 'app/helpers/avo/application_helper.rb', line 257
def d(value)
decoded = Base64.urlsafe_decode64(value.to_s)
Avo::Services::EncryptionService.decrypt(message: decoded, purpose: :return_to, serializer: Marshal)
rescue
nil
end
|
#decode_filter_params(encoded_params) ⇒ Object
#e(value) ⇒ Object
251
252
253
254
|
# File 'app/helpers/avo/application_helper.rb', line 251
def e(value)
encrypted = Avo::Services::EncryptionService.encrypt(message: value, purpose: :return_to, serializer: Marshal)
Base64.urlsafe_encode64(encrypted, padding: false)
end
|
#editor_file_path(path) ⇒ Object
272
273
274
|
# File 'app/helpers/avo/application_helper.rb', line 272
def editor_file_path(path)
editor_url(Object.const_source_location(path.class.to_s)&.first)
end
|
#editor_url(path) ⇒ Object
276
277
278
|
# File 'app/helpers/avo/application_helper.rb', line 276
def editor_url(path)
Avo.configuration.default_editor_url.gsub("%{path}", path)
end
|
#empty_state(**args) ⇒ Object
104
105
106
|
# File 'app/helpers/avo/application_helper.rb', line 104
def empty_state(**args)
render Avo::EmptyStateComponent.new(**args)
end
|
#encode_filter_params(filter_params) ⇒ Object
#frame_id(resource) ⇒ Object
201
202
203
|
# File 'app/helpers/avo/application_helper.rb', line 201
def frame_id(resource)
["frame", resource.model_name.singular, resource.record_param].compact.join("-")
end
|
#get_model_class(model) ⇒ Object
160
161
162
163
164
165
166
|
# File 'app/helpers/avo/application_helper.rb', line 160
def get_model_class(model)
if model.instance_of?(Class)
model
else
model.class
end
end
|
152
153
154
155
156
157
158
|
# File 'app/helpers/avo/application_helper.rb', line 152
def input_classes( = "", has_error: false, size: :md)
classes = ""
classes += "input--size-#{size}" if [:sm, :md, :lg].include?(size)
classes += " input-field--error" if has_error
classes += " #{}"
classes.strip
end
|
#manual_frame_cookie_name(url) ⇒ Object
The cookie name that remembers an opened manual frame. Derived from the
frame's deferred URL (which already encodes resource + record + frame), so
the memory is scoped per record + association/tab. Hashed to keep the name
short and cookie-safe. The manual-frame Stimulus controller writes this
same name client-side on a successful load (see manual_frame_controller.js).
13
14
15
|
# File 'app/helpers/avo/application_helper.rb', line 13
def manual_frame_cookie_name(url)
"amf_#{Digest::MD5.hexdigest(url.to_s)}"
end
|
#manual_frame_remembered?(url, auto_load_for) ⇒ Boolean
Whether this manual frame was opened recently enough to skip the placeholder
and render a real auto-loading <turbo-frame src> (so the Load button never
appears). Presence-based: the cookie carries max-age, so the browser drops
it when the window lapses — a present cookie means "still remembered".
Sliding: every render that finds it remembered refreshes the cookie, so the
window keeps moving forward as long as the user keeps coming back.
Returns false (and touches nothing) for plain loading: :manual frames,
which carry no auto_load_for window.
27
28
29
30
31
32
33
34
35
|
# File 'app/helpers/avo/application_helper.rb', line 27
def manual_frame_remembered?(url, auto_load_for)
return false if auto_load_for.blank?
name = manual_frame_cookie_name(url)
return false if request.cookies[name].blank?
cookies[name] = {value: "1", path: "/", max_age: auto_load_for.to_i, same_site: :lax}
true
end
|
#mount_path ⇒ Object
174
175
176
|
# File 'app/helpers/avo/application_helper.rb', line 174
def mount_path
Avo::Engine.routes.find_script_name(params.permit!.to_h.symbolize_keys)
end
|
#number_to_social(number, start_at: 10_000) ⇒ Object
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
# File 'app/helpers/avo/application_helper.rb', line 186
def number_to_social(number, start_at: 10_000)
return number_with_delimiter(number) if number < start_at
number_to_human(number,
precision: 1,
significant: false,
round_mode: :down,
format: "%n%u",
units: {
thousand: "K",
million: "M",
billion: "B"
})
end
|
#possibly_rails_authentication? ⇒ Boolean
210
211
212
|
# File 'app/helpers/avo/application_helper.rb', line 210
def possibly_rails_authentication?
defined?(Authentication) && Authentication.private_instance_methods.include?(:require_authentication) && Authentication.private_instance_methods.include?(:authenticated?)
end
|
280
281
282
283
284
285
286
287
288
289
290
|
# File 'app/helpers/avo/application_helper.rb', line 280
def
items = Avo.configuration.
return link_to(Avo.configuration.app_name, "/", target: :_blank) if items.blank?
safe_join(items.map { |item|
link_to(item.name, item.to_path,
target: item.target,
title: item.title,
data: item.method ? {turbo_method: item.method} : nil)
})
end
|
#render_license_warning(title: "", message: "", icon: "exclamation") ⇒ Object
96
97
98
99
100
101
102
|
# File 'app/helpers/avo/application_helper.rb', line 96
def render_license_warning(title: "", message: "", icon: "exclamation")
render partial: "avo/sidebar/license_warning", locals: {
title: title,
message: message,
icon: icon
}
end
|
#root_path_without_url ⇒ Object
168
169
170
171
172
|
# File 'app/helpers/avo/application_helper.rb', line 168
def root_path_without_url
"#{Avo.configuration.prefix_path}#{mount_path}"
rescue
Avo.configuration.root_path
end
|
#rtl? ⇒ Boolean
Check if the current locale is RTL (Right-to-Left)
234
235
236
|
# File 'app/helpers/avo/application_helper.rb', line 234
def rtl?
Avo.configuration.rtl?
end
|
#safe_blob_path(blob) ⇒ Object
51
52
53
54
55
56
57
58
59
|
# File 'app/helpers/avo/application_helper.rb', line 51
def safe_blob_path(blob)
if blob.filename.to_s.blank?
main_app.rails_service_blob_path(blob.signed_id, routable_blob_filename(blob))
else
main_app.rails_blob_path(blob)
end
rescue ActionController::UrlGenerationError
nil
end
|
#safe_blob_representation_url(representation) ⇒ Object
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
# File 'app/helpers/avo/application_helper.rb', line 80
def safe_blob_representation_url(representation)
blob = representation.blob
if blob.filename.to_s.blank?
main_app.rails_blob_representation_url(
blob.signed_id,
representation.variation.key,
routable_blob_filename(blob)
)
else
main_app.url_for(representation)
end
rescue ActionController::UrlGenerationError
nil
end
|
#safe_blob_url(blob) ⇒ Object
Active Storage URL helpers raise UrlGenerationError when a blob's filename
is blank (the route requires a :filename segment). Use a synthetic filename
so attach mode and other callers still get a routable URL; return nil only
when generation still fails for another reason.
41
42
43
44
45
46
47
48
49
|
# File 'app/helpers/avo/application_helper.rb', line 41
def safe_blob_url(blob)
if blob.filename.to_s.blank?
main_app.rails_service_blob_url(blob.signed_id, routable_blob_filename(blob))
else
main_app.url_for(blob)
end
rescue ActionController::UrlGenerationError
nil
end
|
#safe_image_url(attachment) ⇒ Object
Serves an attachment the browser can't paint through a variant, which
ActiveStorage renders as PNG. Everything else keeps serving the original,
so apps without an image processor are unaffected.
70
71
72
73
74
75
76
77
78
|
# File 'app/helpers/avo/application_helper.rb', line 70
def safe_image_url(attachment)
blob = attachment.try(:blob)
if blob&.variable? && blob.content_type.in?(UNRENDERABLE_IMAGE_CONTENT_TYPES)
safe_blob_representation_url(blob.variant({})) || main_app.url_for(attachment)
else
main_app.url_for(attachment)
end
end
|
Already clamped into the bounds by Avo::Configuration.
314
|
# File 'app/helpers/avo/application_helper.rb', line 314
def = Avo.configuration.[:default_width]
|
311
|
# File 'app/helpers/avo/application_helper.rb', line 311
def = SIDEBAR_WIDTH_MAX
|
Resizable-sidebar width bounds for the JS layer (window.Avo.configuration).
Read the private_constant bounds via unqualified lexical lookup — the
constants themselves stay non-public API (see lib/avo.rb).
309
|
# File 'app/helpers/avo/application_helper.rb', line 309
def = SIDEBAR_WIDTH_MIN
|
#text_direction ⇒ Object
Returns "rtl" or "ltr" based on current locale
239
240
241
|
# File 'app/helpers/avo/application_helper.rb', line 239
def text_direction
Avo.configuration.text_direction
end
|
#ui ⇒ Object
6
|
# File 'app/helpers/avo/application_helper.rb', line 6
def ui = Avo::UIInstance
|
#wrap_in_modal(content) ⇒ Object
264
265
266
267
268
269
270
|
# File 'app/helpers/avo/application_helper.rb', line 264
def wrap_in_modal(content)
turbo_frame_tag Avo::MODAL_FRAME_ID do
render(Avo::ModalComponent.new(width: :xl, body_class: "bg-application")) do |c|
content
end
end
end
|