avo 4.0.13 → 4.0.15
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/Gemfile.lock +1 -1
- data/app/assets/builds/avo/application.css +1 -1
- data/app/assets/builds/avo/application.js +1 -1
- data/app/assets/builds/avo/application.js.map +2 -2
- data/app/assets/stylesheets/css/focus.css +1 -1
- data/app/components/avo/fields/index_component.rb +6 -2
- data/app/components/avo/index/field_wrapper_component.html.erb +2 -2
- data/app/components/avo/index/field_wrapper_component.rb +16 -0
- data/app/components/avo/u_i/count_component.html.erb +1 -1
- data/app/components/avo/u_i/count_component.rb +3 -0
- data/app/components/avo/u_i/tabs/tab_component.html.erb +1 -0
- data/app/components/avo/u_i/tabs/tab_component.rb +11 -0
- data/app/javascript/js/controllers/table_row_controller.js +5 -5
- data/lib/avo/configuration.rb +2 -0
- data/lib/avo/plugin_manager.rb +39 -6
- data/lib/avo/version.rb +1 -1
- data/lib/avo.rb +18 -10
- data/lib/generators/avo/templates/initializer/avo.tt +1 -0
- metadata +3 -5
|
@@ -8,7 +8,7 @@ class Avo::Fields::IndexComponent < Avo::BaseComponent
|
|
|
8
8
|
attr_reader :parent_resource
|
|
9
9
|
attr_reader :view
|
|
10
10
|
|
|
11
|
-
def initialize(field: nil, resource: nil, reflection: nil, index: 0, parent_record: nil, parent_resource: nil, **kwargs)
|
|
11
|
+
def initialize(field: nil, resource: nil, reflection: nil, index: 0, parent_record: nil, parent_resource: nil, density: nil, field_wrapper: {}, **kwargs)
|
|
12
12
|
@field = field
|
|
13
13
|
@resource = resource
|
|
14
14
|
@index = index
|
|
@@ -17,6 +17,8 @@ class Avo::Fields::IndexComponent < Avo::BaseComponent
|
|
|
17
17
|
@parent_resource = parent_resource
|
|
18
18
|
@view = Avo::ViewInquirer.new("index")
|
|
19
19
|
@reflection = reflection
|
|
20
|
+
@density = density
|
|
21
|
+
@field_wrapper_options = field_wrapper
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def resource_view_path
|
|
@@ -35,7 +37,9 @@ class Avo::Fields::IndexComponent < Avo::BaseComponent
|
|
|
35
37
|
def field_wrapper_args
|
|
36
38
|
{
|
|
37
39
|
field: @field,
|
|
38
|
-
resource: @resource
|
|
40
|
+
resource: @resource,
|
|
41
|
+
density: @density,
|
|
42
|
+
**@field_wrapper_options
|
|
39
43
|
}
|
|
40
44
|
end
|
|
41
45
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<%= content_tag
|
|
1
|
+
<%= content_tag @html_tag,
|
|
2
2
|
class: class_names(
|
|
3
3
|
"group/clipboard px-2 leading-none whitespace-nowrap h-full text-content text-sm",
|
|
4
4
|
@classes,
|
|
5
5
|
@field.get_html(:classes, view: @view, element: :wrapper),
|
|
6
6
|
{
|
|
7
|
-
|
|
7
|
+
density_padding_class => !@flush
|
|
8
8
|
}
|
|
9
9
|
),
|
|
10
10
|
data: {
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Avo::Index::FieldWrapperComponent < Avo::BaseComponent
|
|
4
|
+
# Vertical padding for each density step. `nil` density falls back to
|
|
5
|
+
# `Avo.configuration.density`.
|
|
6
|
+
DENSITY_PADDING = {
|
|
7
|
+
tight: "py-2",
|
|
8
|
+
normal: "py-4",
|
|
9
|
+
relaxed: "py-6"
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
4
12
|
prop :field
|
|
5
13
|
prop :resource
|
|
6
14
|
prop :dash_if_blank, default: true
|
|
7
15
|
prop :center_content, default: false
|
|
8
16
|
prop :flush, default: false
|
|
17
|
+
prop :density, default: nil
|
|
18
|
+
# The wrapper element. Defaults to a table cell; non-table hosts (e.g. the
|
|
19
|
+
# dashboards list card) render the same field components inside a :div.
|
|
20
|
+
prop :html_tag, default: :td
|
|
9
21
|
prop :args, kind: :**, default: {}.freeze
|
|
10
22
|
|
|
11
23
|
def after_initialize
|
|
@@ -31,4 +43,8 @@ class Avo::Index::FieldWrapperComponent < Avo::BaseComponent
|
|
|
31
43
|
def render_dash?
|
|
32
44
|
@field.value.blank? && @dash_if_blank
|
|
33
45
|
end
|
|
46
|
+
|
|
47
|
+
def density_padding_class
|
|
48
|
+
DENSITY_PADDING[(@density || Avo.configuration.density)&.to_sym] || DENSITY_PADDING[:normal]
|
|
49
|
+
end
|
|
34
50
|
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<%= content_tag :span, @count, class: class_names("count", @classes), data: @data %>
|
|
1
|
+
<%= content_tag :span, @count, class: class_names("count", @classes), data: @data, "aria-label": @label %>
|
|
@@ -8,4 +8,7 @@ class Avo::UI::CountComponent < Avo::BaseComponent
|
|
|
8
8
|
prop :count
|
|
9
9
|
prop :classes, default: nil
|
|
10
10
|
prop :data, default: -> { {} }
|
|
11
|
+
# Optional accessible name (e.g. "1,234 records") when the visible +count+ is
|
|
12
|
+
# abbreviated or purely numeric. Rendered as aria-label; omitted when nil.
|
|
13
|
+
prop :label, default: nil
|
|
11
14
|
end
|
|
@@ -19,6 +19,17 @@ module Avo
|
|
|
19
19
|
prop :data, default: {}.freeze
|
|
20
20
|
prop :classes
|
|
21
21
|
prop :title
|
|
22
|
+
prop :badge, default: nil
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# The badge may be a component instance (eager count pill) or a
|
|
27
|
+
# pre-rendered safe string (deferred counter frame); render accordingly.
|
|
28
|
+
def render_badge
|
|
29
|
+
return unless @badge.present?
|
|
30
|
+
|
|
31
|
+
@badge.respond_to?(:render_in) ? render(@badge) : @badge
|
|
32
|
+
end
|
|
22
33
|
end
|
|
23
34
|
end
|
|
24
35
|
end
|
|
@@ -17,8 +17,8 @@ export default class extends Controller {
|
|
|
17
17
|
anchor = null
|
|
18
18
|
|
|
19
19
|
mouseEntered(event) {
|
|
20
|
-
const row = event.target.closest('
|
|
21
|
-
const url = row
|
|
20
|
+
const row = event.target.closest('[data-visit-path]')
|
|
21
|
+
const url = row?.dataset?.visitPath
|
|
22
22
|
|
|
23
23
|
if (url) {
|
|
24
24
|
this.anchor = this.createAnchor(url)
|
|
@@ -64,14 +64,14 @@ export default class extends Controller {
|
|
|
64
64
|
return // Don't navigate if a link or button is clicked
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const row = event.target.closest('
|
|
68
|
-
const url = row
|
|
67
|
+
const row = event.target.closest('[data-visit-path]')
|
|
68
|
+
const url = row?.dataset?.visitPath
|
|
69
69
|
|
|
70
70
|
if (!row || !url) {
|
|
71
71
|
return
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
if (event.metaKey || event.ctrlKey) {
|
|
74
|
+
if (event.metaKey || event.ctrlKey || row.dataset.visitTarget === '_blank') {
|
|
75
75
|
this.#visitInNewTab(url)
|
|
76
76
|
} else {
|
|
77
77
|
this.#visitInSameTab(url)
|
data/lib/avo/configuration.rb
CHANGED
|
@@ -51,6 +51,7 @@ module Avo
|
|
|
51
51
|
attr_accessor :resource_parent_controller
|
|
52
52
|
attr_accessor :default_url_options
|
|
53
53
|
attr_accessor :click_row_to_view_record
|
|
54
|
+
attr_accessor :density
|
|
54
55
|
attr_accessor :alert_dismiss_time
|
|
55
56
|
attr_accessor :is_admin_method
|
|
56
57
|
attr_accessor :is_developer_method
|
|
@@ -182,6 +183,7 @@ module Avo
|
|
|
182
183
|
@default_url_options = []
|
|
183
184
|
@pagination = {}
|
|
184
185
|
@click_row_to_view_record = true
|
|
186
|
+
@density = :normal
|
|
185
187
|
@alert_dismiss_time = 5000
|
|
186
188
|
@is_admin_method = :is_admin?
|
|
187
189
|
@is_developer_method = :is_developer?
|
data/lib/avo/plugin_manager.rb
CHANGED
|
@@ -1,18 +1,47 @@
|
|
|
1
1
|
module Avo
|
|
2
2
|
class PluginManager
|
|
3
3
|
attr_reader :plugins
|
|
4
|
-
|
|
4
|
+
attr_reader :engines
|
|
5
5
|
|
|
6
6
|
alias_method :all, :plugins
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@plugins = []
|
|
10
10
|
@engines = []
|
|
11
|
+
@building_plugins = []
|
|
12
|
+
@building_engines = []
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# Starts a re-registration cycle without touching the currently published
|
|
16
|
+
# @plugins/@engines, so concurrent readers (e.g. mount_avo's route-drawing
|
|
17
|
+
# loop) keep seeing the complete pre-reload list until #commit_reload
|
|
18
|
+
# publishes the new one. Called by Avo.boot inside its Mutex, so only one
|
|
19
|
+
# reload is ever building at a time.
|
|
20
|
+
def begin_reload
|
|
21
|
+
@building_plugins = []
|
|
22
|
+
@building_engines = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Publishes the registrations collected since #begin_reload. Each of the
|
|
26
|
+
# two reassignments below is individually an atomic pointer swap, so a
|
|
27
|
+
# reader of .engines alone (or .plugins alone) always sees the complete
|
|
28
|
+
# old list or the complete new one, never a partially rebuilt one. The
|
|
29
|
+
# two fields are not published jointly -- a reader combining both in one
|
|
30
|
+
# operation could observe them from different reload generations. No
|
|
31
|
+
# current reader does that (mount_avo reads only .engines; installed?
|
|
32
|
+
# reads only .plugins).
|
|
33
|
+
def commit_reload
|
|
34
|
+
@plugins = @building_plugins
|
|
35
|
+
@engines = @building_engines
|
|
36
|
+
# Reset to fresh arrays rather than nil: register/mount_engine must
|
|
37
|
+
# stay callable even outside a begin_reload/commit_reload window --
|
|
38
|
+
# e.g. avo-permissions calls Avo.plugin_manager.register at Rails
|
|
39
|
+
# initializer time, before Avo.boot ever runs. Such a call is simply
|
|
40
|
+
# discarded by the next #begin_reload rather than raising, matching
|
|
41
|
+
# the pre-atomic-publish behavior where register/mount_engine never
|
|
42
|
+
# crashed regardless of timing.
|
|
43
|
+
@building_plugins = []
|
|
44
|
+
@building_engines = []
|
|
16
45
|
end
|
|
17
46
|
|
|
18
47
|
def register(name, priority: 10)
|
|
@@ -21,7 +50,7 @@ module Avo
|
|
|
21
50
|
# (e.g. `:rhino` for `avo-rhino_field`), so the name alone isn't enough.
|
|
22
51
|
registered_from = caller_locations(1, 1)&.first&.path
|
|
23
52
|
|
|
24
|
-
@
|
|
53
|
+
@building_plugins << Plugin.new(name:, priority: priority, registered_from: registered_from)
|
|
25
54
|
end
|
|
26
55
|
|
|
27
56
|
def register_view_type(name, component:, icon:, active_icon:, translation_key: nil)
|
|
@@ -80,7 +109,11 @@ module Avo
|
|
|
80
109
|
end
|
|
81
110
|
|
|
82
111
|
def mount_engine(klass, **options)
|
|
83
|
-
|
|
112
|
+
# Dedup by class so a plugin hook that (by bug) calls mount_engine
|
|
113
|
+
# twice for the same engine within one boot can't leave a duplicate
|
|
114
|
+
# entry for #commit_reload to publish.
|
|
115
|
+
@building_engines.delete_if { |engine| engine[:klass] == klass }
|
|
116
|
+
@building_engines << {klass:, options:}
|
|
84
117
|
end
|
|
85
118
|
end
|
|
86
119
|
|
data/lib/avo/version.rb
CHANGED
data/lib/avo.rb
CHANGED
|
@@ -55,6 +55,11 @@ module Avo
|
|
|
55
55
|
|
|
56
56
|
class ViewTypeComponentNotFoundError < StandardError; end
|
|
57
57
|
|
|
58
|
+
# Serializes Avo.boot so concurrent code reloads (config.to_prepare fires on
|
|
59
|
+
# every reload, from every request thread) can't interleave the plugin
|
|
60
|
+
# registry reset with its repopulation. See PluginManager#begin_reload.
|
|
61
|
+
@boot_mutex = Mutex.new
|
|
62
|
+
|
|
58
63
|
# Exception raised when a resource is missing
|
|
59
64
|
class MissingResourceError < StandardError
|
|
60
65
|
def initialize(model_class, field)
|
|
@@ -92,16 +97,19 @@ module Avo
|
|
|
92
97
|
|
|
93
98
|
# Runs when the app boots up
|
|
94
99
|
def boot
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
@boot_mutex.synchronize do
|
|
101
|
+
Turbo::Streams::TagBuilder.prepend(Avo::TurboStreamActionsHelper)
|
|
102
|
+
@logger = Avo.configuration.logger
|
|
103
|
+
@field_manager = Avo::Fields::FieldManager.build
|
|
104
|
+
@view_type_manager = nil # force re-init with defaults on next access
|
|
105
|
+
@cache_store = Avo.configuration.cache_store
|
|
106
|
+
Avo.plugin_manager.begin_reload
|
|
107
|
+
# Run load hooks for plugins to include them in the app.
|
|
108
|
+
# This is useful for plugins that need to include modules in the app that will be used on avo_boot hook.
|
|
109
|
+
ActiveSupport.run_load_hooks(:avo_plugin_include, self)
|
|
110
|
+
ActiveSupport.run_load_hooks(:avo_boot, self)
|
|
111
|
+
Avo.plugin_manager.commit_reload
|
|
112
|
+
end
|
|
105
113
|
eager_load_actions
|
|
106
114
|
end
|
|
107
115
|
|
|
@@ -118,6 +118,7 @@ Avo.configure do |config|
|
|
|
118
118
|
|
|
119
119
|
## == Customization ==
|
|
120
120
|
config.click_row_to_view_record = true
|
|
121
|
+
# config.density = :normal # :tight, :normal, :relaxed
|
|
121
122
|
# config.app_name = 'Avocadelicious'
|
|
122
123
|
# config.timezone = 'UTC'
|
|
123
124
|
# config.currency = 'USD'
|
metadata
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: avo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrian Marin
|
|
8
8
|
- Mihai Marin
|
|
9
9
|
- Paul Bob
|
|
10
|
-
autorequire:
|
|
11
10
|
bindir: bin
|
|
12
11
|
cert_chain: []
|
|
13
|
-
date:
|
|
12
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
14
13
|
dependencies:
|
|
15
14
|
- !ruby/object:Gem::Dependency
|
|
16
15
|
name: activerecord
|
|
@@ -1199,8 +1198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1199
1198
|
- !ruby/object:Gem::Version
|
|
1200
1199
|
version: '0'
|
|
1201
1200
|
requirements: []
|
|
1202
|
-
rubygems_version:
|
|
1203
|
-
signing_key:
|
|
1201
|
+
rubygems_version: 4.0.10
|
|
1204
1202
|
specification_version: 4
|
|
1205
1203
|
summary: Admin panel framework and Content Management System for Ruby on Rails.
|
|
1206
1204
|
test_files: []
|