avo 2.0.0 → 2.1.2.pre1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/app/assets/builds/avo.css +8810 -0
- data/app/assets/builds/avo.js +423 -0
- data/app/assets/builds/avo.js.map +7 -0
- data/app/assets/stylesheets/avo.css +1 -1
- data/app/assets/svgs/save.svg +8 -1
- data/app/components/avo/actions_component.html.erb +1 -1
- data/app/components/avo/alert_component.html.erb +1 -1
- data/app/components/avo/alert_component.rb +6 -6
- data/app/components/avo/blank_field_component.html.erb +0 -0
- data/app/components/avo/blank_field_component.rb +4 -0
- data/app/components/avo/card_component.html.erb +7 -2
- data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
- data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
- data/app/components/avo/fields/external_image_field/index_component.html.erb +1 -2
- data/app/components/avo/fields/file_field/index_component.html.erb +3 -3
- data/app/components/avo/fields/file_field/index_component.rb +11 -0
- data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
- data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
- data/app/components/avo/index/field_wrapper_component.html.erb +1 -1
- data/app/components/avo/index/field_wrapper_component.rb +12 -1
- data/app/components/avo/index/resource_controls_component.html.erb +5 -1
- data/app/components/avo/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/panel_component.html.erb +15 -5
- data/app/components/avo/panel_component.rb +9 -0
- data/app/components/avo/referrer_params_component.html.erb +4 -0
- data/app/components/avo/referrer_params_component.rb +9 -0
- data/app/components/avo/sidebar_component.html.erb +2 -0
- data/app/components/avo/sidebar_component.rb +3 -1
- data/app/components/avo/views/resource_edit_component.html.erb +14 -1
- data/app/components/avo/views/resource_new_component.html.erb +13 -0
- data/app/components/avo/views/resource_show_component.html.erb +11 -0
- data/app/components/avo/views/resource_show_component.rb +5 -0
- data/app/controllers/avo/application_controller.rb +1 -1
- data/app/controllers/avo/base_controller.rb +87 -37
- data/app/controllers/avo/dashboards_controller.rb +2 -6
- data/app/controllers/avo/search_controller.rb +5 -1
- data/app/helpers/avo/url_helpers.rb +6 -2
- data/app/javascript/avo.js +4 -3
- data/app/views/avo/actions/show.html.erb +1 -0
- data/app/views/avo/associations/new.html.erb +1 -0
- data/app/views/avo/dashboards/show.html.erb +4 -1
- data/app/views/avo/home/failed_to_load.html.erb +21 -1
- data/app/views/avo/partials/_sidebar_extra.html.erb +0 -0
- data/app/views/layouts/avo/application.html.erb +7 -0
- data/bin/dev +7 -6
- data/lib/avo/app.rb +8 -1
- data/lib/avo/base_action.rb +5 -4
- data/lib/avo/base_card.rb +175 -0
- data/lib/avo/base_resource.rb +34 -8
- data/lib/avo/configuration.rb +2 -0
- data/lib/avo/dashboards/base_dashboard.rb +37 -2
- data/lib/avo/dashboards/base_divider.rb +3 -1
- data/lib/avo/dashboards/chartkick_card.rb +1 -1
- data/lib/avo/dashboards/dashboard_card.rb +6 -0
- data/lib/avo/dashboards/partial_card.rb +1 -1
- data/lib/avo/fields/base_field.rb +19 -6
- data/lib/avo/fields/belongs_to_field.rb +1 -1
- data/lib/avo/fields/date_field.rb +1 -1
- data/lib/avo/fields/external_image_field.rb +2 -2
- data/lib/avo/fields_collector.rb +7 -2
- data/lib/avo/hosts/dashboard_card.rb +1 -0
- data/lib/avo/hosts/dashboard_visibility.rb +19 -0
- data/lib/avo/licensing/pro_license.rb +1 -0
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/dashboards/dashboard.tt +4 -1
- data/public/avo-assets/avo.css +55 -34
- data/public/avo-assets/avo.js +166 -165
- data/public/avo-assets/avo.js.map +3 -3
- metadata +15 -6
- data/app/views/avo/partials/_failed_state.html.erb +0 -16
- data/lib/avo/dashboards/base_card.rb +0 -151
@@ -10,7 +10,11 @@ module Avo
|
|
10
10
|
def index
|
11
11
|
raise ActionController::BadRequest.new("This feature requires the pro license https://avohq.io/purchase/pro") if App.license.lacks_with_trial(:global_search)
|
12
12
|
|
13
|
-
|
13
|
+
resources = Avo::App.resources.reject do |resource|
|
14
|
+
resource.class.hide_from_global_search
|
15
|
+
end
|
16
|
+
|
17
|
+
render json: search_resources(resources)
|
14
18
|
end
|
15
19
|
|
16
20
|
def show
|
@@ -12,8 +12,12 @@ module Avo
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
#
|
16
|
-
|
15
|
+
# Create the `route_key` from the model key so the namespaced models get the proper path (SomeModule::Post -> some_module_post).
|
16
|
+
# Add the `_index` suffix for the uncountable models so they get the correct path (`fish_index`)
|
17
|
+
route_key = resource.model_key
|
18
|
+
route_key << "_index" if resource.model_name.singular == resource.model_name.plural
|
19
|
+
|
20
|
+
avo.send :"resources_#{route_key}_path", **existing_params, **args
|
17
21
|
end
|
18
22
|
|
19
23
|
def resource_path(
|
data/app/javascript/avo.js
CHANGED
@@ -71,12 +71,13 @@ document.addEventListener('turbo:frame-load', () => {
|
|
71
71
|
initTippy()
|
72
72
|
})
|
73
73
|
|
74
|
-
document.addEventListener('turbo:before-fetch-response', (e) => {
|
74
|
+
document.addEventListener('turbo:before-fetch-response', async (e) => {
|
75
75
|
if (e.detail.fetchResponse.response.status === 500) {
|
76
|
-
const id = e.
|
77
|
-
e.
|
76
|
+
const { id, src } = e.target
|
77
|
+
e.target.src = `${window.Avo.configuration.root_path}/failed_to_load?turbo_frame=${id}&src=${src}`
|
78
78
|
}
|
79
79
|
})
|
80
|
+
|
80
81
|
document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
|
81
82
|
document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
|
82
83
|
document.addEventListener('turbo:submit-end', () => document.body.classList.remove('turbo-loading'))
|
@@ -8,7 +8,10 @@
|
|
8
8
|
<%= render Avo::Dashboards::DividerComponent.new(divider: item) %>
|
9
9
|
<% elsif item.is_card? %>
|
10
10
|
<%= content_tag(:div, class: "relative bg-white rounded shadow-panel space-y-2 #{item.card_classes} overflow-hidden") do %>
|
11
|
-
<turbo-frame id="<%= item.turbo_frame %>"
|
11
|
+
<turbo-frame id="<%= item.turbo_frame %>"
|
12
|
+
src="<%= item.frame_url(enforced_range: @range, params: params) %>"
|
13
|
+
data-card-index="<%= item.index %>"
|
14
|
+
>
|
12
15
|
<%= render(Avo::LoadingComponent.new(title: item.label)) %>
|
13
16
|
</turbo-frame>
|
14
17
|
<% end %>
|
@@ -1,3 +1,23 @@
|
|
1
1
|
<%= turbo_frame_wrap(params[:turbo_frame]) do %>
|
2
|
-
|
2
|
+
<%
|
3
|
+
classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
|
4
|
+
label = t 'avo.failed_to_load'
|
5
|
+
%>
|
6
|
+
<div class="relative flex-1 py-12">
|
7
|
+
<div class="relative block text-gray-300 h-40 w-full">
|
8
|
+
<%= svg 'avocado', class: "#{classes} h-20 text-gray-400" %>
|
9
|
+
<%= svg 'code', class: "#{classes} h-8 -ml-20 -mt-12" %>
|
10
|
+
<%= svg 'fire', class: "#{classes} h-8 -ml-10 -mt-24" %>
|
11
|
+
<%= svg 'color-swatch', class: "#{classes} h-8 ml-8 -mt-24" %>
|
12
|
+
<%= svg 'globe', class: "#{classes} h-8 ml-20 -mt-12" %>
|
13
|
+
<%= svg 'library', class: "#{classes} h-8 -ml-20 mt-4" %>
|
14
|
+
<%= svg 'photograph', class: "#{classes} h-8 ml-20 mt-4" %>
|
15
|
+
</div>
|
16
|
+
<div class="relative block text-center text-lg text-gray-400 font-semibold -mt-10"><%= label %> <span class="border-b-2 border-dashed"><%= params[:turbo_frame].to_s.humanize.downcase if params[:turbo_frame].present? %></span> frame</div>
|
17
|
+
</div>
|
18
|
+
<% if Rails.env.development? %>
|
19
|
+
<div class="text-center text-sm w-full">
|
20
|
+
This is not an issue with Avo. Click <%= link_to 'this link', params[:src], target: :_blank %> to see why this frame failed to load.
|
21
|
+
</div>
|
22
|
+
<% end %>
|
3
23
|
<% end %>
|
File without changes
|
@@ -42,6 +42,13 @@
|
|
42
42
|
|
43
43
|
<%= turbo_frame_tag 'alerts', class: "fixed inset-0 bottom-0 flex flex-col space-y-4 items-end justify-right px-4 py-6 sm:p-6 justify-end z-50 pointer-events-none" do %>
|
44
44
|
<%= render Avo::FlashAlertsComponent.new flashes: flash %>
|
45
|
+
|
46
|
+
<% # In case we have other general error messages %>
|
47
|
+
<% if @errors.present? %>
|
48
|
+
<% @errors.each do |message| %>
|
49
|
+
<%= render Avo::AlertComponent.new :error, message %>
|
50
|
+
<% end %>
|
51
|
+
<% end %>
|
45
52
|
<% end %>
|
46
53
|
|
47
54
|
<%= render partial: "avo/partials/scripts" %>
|
data/bin/dev
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
echo "Installing foreman..."
|
6
|
-
gem install foreman
|
7
|
-
fi
|
3
|
+
PORT="${PORT:-3030}"
|
4
|
+
export PORT
|
8
5
|
|
9
|
-
|
6
|
+
if command -v overmind &> /dev/null; then
|
7
|
+
overmind start -f Procfile.dev "$@"
|
8
|
+
else
|
9
|
+
foreman start -f Procfile.dev "$@"
|
10
|
+
fi
|
data/lib/avo/app.rb
CHANGED
@@ -10,6 +10,8 @@ module Avo
|
|
10
10
|
class_attribute :current_user, default: nil
|
11
11
|
class_attribute :root_path, default: nil
|
12
12
|
class_attribute :view_context, default: nil
|
13
|
+
class_attribute :params, default: {}
|
14
|
+
class_attribute :translation_enabled, default: false
|
13
15
|
|
14
16
|
class << self
|
15
17
|
def boot
|
@@ -24,14 +26,16 @@ module Avo
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
def init(request:, context:, current_user:, root_path:, view_context:)
|
29
|
+
def init(request:, context:, current_user:, root_path:, view_context:, params:)
|
28
30
|
self.request = request
|
29
31
|
self.context = context
|
30
32
|
self.current_user = current_user
|
31
33
|
self.root_path = root_path
|
32
34
|
self.view_context = view_context
|
35
|
+
self.params = params
|
33
36
|
|
34
37
|
self.license = Licensing::LicenseManager.new(Licensing::HQ.new(request).response).license
|
38
|
+
self.translation_enabled = license.has(:localization)
|
35
39
|
|
36
40
|
# Set the current host for ActiveStorage
|
37
41
|
begin
|
@@ -89,6 +93,9 @@ module Avo
|
|
89
93
|
.select do |dashboard|
|
90
94
|
dashboard != Dashboards::BaseDashboard
|
91
95
|
end
|
96
|
+
.uniq do |dashboard|
|
97
|
+
dashboard.id
|
98
|
+
end
|
92
99
|
end
|
93
100
|
|
94
101
|
# Returns the Avo dashboard by id
|
data/lib/avo/base_action.rb
CHANGED
@@ -13,6 +13,7 @@ module Avo
|
|
13
13
|
class_attribute :user
|
14
14
|
class_attribute :resource
|
15
15
|
class_attribute :fields
|
16
|
+
class_attribute :invalid_fields
|
16
17
|
class_attribute :standalone, default: false
|
17
18
|
class_attribute :visible
|
18
19
|
class_attribute :may_download_file, default: false
|
@@ -27,16 +28,16 @@ module Avo
|
|
27
28
|
def form_data_attributes
|
28
29
|
# We can't respond with a file download from Turbo se we disable it on the form
|
29
30
|
if may_download_file
|
30
|
-
{
|
31
|
+
{turbo: false, remote: false}
|
31
32
|
else
|
32
|
-
{
|
33
|
+
{"turbo-frame": "_top", "action-target": "form"}
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
37
|
# We can't respond with a file download from Turbo se we disable close the modal manually after a while (it's a hack, we know)
|
37
38
|
def submit_button_data_attributes
|
38
39
|
if may_download_file
|
39
|
-
{
|
40
|
+
{action: "click->modal#delayedClose"}
|
40
41
|
else
|
41
42
|
{}
|
42
43
|
end
|
@@ -109,7 +110,7 @@ module Avo
|
|
109
110
|
args = {
|
110
111
|
fields: processed_fields,
|
111
112
|
current_user: current_user,
|
112
|
-
resource: resource
|
113
|
+
resource: resource
|
113
114
|
}
|
114
115
|
|
115
116
|
args[:models] = models unless standalone
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Avo
|
2
|
+
class BaseCard
|
3
|
+
class_attribute :id
|
4
|
+
class_attribute :label
|
5
|
+
class_attribute :description
|
6
|
+
class_attribute :cols, default: 1
|
7
|
+
class_attribute :rows, default: 1
|
8
|
+
class_attribute :initial_range
|
9
|
+
class_attribute :ranges, default: []
|
10
|
+
class_attribute :refresh_every
|
11
|
+
class_attribute :display_header, default: true
|
12
|
+
# private
|
13
|
+
class_attribute :result_data
|
14
|
+
class_attribute :query_block
|
15
|
+
|
16
|
+
attr_accessor :dashboard
|
17
|
+
attr_accessor :options
|
18
|
+
attr_accessor :index
|
19
|
+
attr_accessor :params
|
20
|
+
|
21
|
+
delegate :context, to: ::Avo::App
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def query(&block)
|
25
|
+
self.query_block = block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(dashboard:, options: {}, index: 0, cols: nil, rows: nil, label: nil, description: nil, refresh_every: nil)
|
30
|
+
@dashboard = dashboard
|
31
|
+
@options = options
|
32
|
+
@index = index
|
33
|
+
@cols = cols
|
34
|
+
@rows = rows
|
35
|
+
@label = label
|
36
|
+
@refresh_every = refresh_every
|
37
|
+
@description = description
|
38
|
+
end
|
39
|
+
|
40
|
+
def label
|
41
|
+
return @label.to_s if @label.present?
|
42
|
+
return self.class.label.to_s if self.class.label.present?
|
43
|
+
|
44
|
+
self.class.id.to_s.humanize
|
45
|
+
end
|
46
|
+
|
47
|
+
def description
|
48
|
+
@description || self.class.description
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_every
|
52
|
+
@refresh_every || self.class.refresh_every
|
53
|
+
end
|
54
|
+
|
55
|
+
def translated_range(range)
|
56
|
+
return "#{range} days" if range.is_a? Integer
|
57
|
+
|
58
|
+
case range
|
59
|
+
when "MTD"
|
60
|
+
"Month to date"
|
61
|
+
when "QTD"
|
62
|
+
"Quarter to date"
|
63
|
+
when "YTD"
|
64
|
+
"Year to date"
|
65
|
+
when "TODAY"
|
66
|
+
"Today"
|
67
|
+
else
|
68
|
+
range
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parsed_ranges
|
73
|
+
return unless ranges.present?
|
74
|
+
|
75
|
+
ranges.map { |range| [translated_range(range), range] }
|
76
|
+
end
|
77
|
+
|
78
|
+
def turbo_frame
|
79
|
+
"#{dashboard.id}_#{id}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def frame_url(enforced_range: nil, params: {})
|
83
|
+
enforced_range ||= initial_range || ranges.first
|
84
|
+
|
85
|
+
# append the parent params to the card request
|
86
|
+
begin
|
87
|
+
other_params = "&#{params.permit!.to_h.map { |k, v| "#{k}=#{v}" }.join("&")}"
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
|
91
|
+
"#{Avo::App.root_path}/dashboards/#{dashboard.id}/cards/#{id}?turbo_frame=#{turbo_frame}&index=#{index}&range=#{enforced_range}#{other_params}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def card_classes
|
95
|
+
result = ""
|
96
|
+
|
97
|
+
# Writing down the classes so TailwindCSS knows not to purge them
|
98
|
+
classes_for_cols = {
|
99
|
+
1 => " sm:col-span-1",
|
100
|
+
2 => " sm:col-span-2",
|
101
|
+
3 => " sm:col-span-3",
|
102
|
+
4 => " sm:col-span-4",
|
103
|
+
5 => " sm:col-span-5",
|
104
|
+
6 => " sm:col-span-6"
|
105
|
+
}
|
106
|
+
|
107
|
+
classes_for_rows = {
|
108
|
+
1 => " h-36",
|
109
|
+
2 => " h-72",
|
110
|
+
3 => " h-[27rem]",
|
111
|
+
4 => " h-[36rem]",
|
112
|
+
5 => " h-[45rem]",
|
113
|
+
6 => " h-[54rem]"
|
114
|
+
}
|
115
|
+
# puts ["cols->", cols, classes_for_cols, classes_for_rows, classes_for_cols[cols.to_i]].inspect
|
116
|
+
|
117
|
+
result += classes_for_cols[cols.to_i] if classes_for_cols[cols.to_i].present?
|
118
|
+
result += classes_for_rows[rows.to_i] if classes_for_rows[rows.to_i].present?
|
119
|
+
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
def type
|
124
|
+
return :metric if self.class.superclass == ::Avo::Dashboards::MetricCard
|
125
|
+
return :chartkick if self.class.superclass == ::Avo::Dashboards::ChartkickCard
|
126
|
+
return :partial if self.class.superclass == ::Avo::Dashboards::PartialCard
|
127
|
+
end
|
128
|
+
|
129
|
+
def compute_result
|
130
|
+
Avo::Hosts::DashboardCard.new(card: self, dashboard: dashboard, params: params, context: context, range: range, options: options)
|
131
|
+
.compute_result
|
132
|
+
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def hydrate(dashboard: nil, params: nil)
|
137
|
+
@dashboard = dashboard if dashboard.present?
|
138
|
+
@params = params if params.present?
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def range
|
144
|
+
return params[:range] if params.dig(:range).present?
|
145
|
+
|
146
|
+
return initial_range if initial_range.present?
|
147
|
+
|
148
|
+
ranges.first
|
149
|
+
end
|
150
|
+
|
151
|
+
def result(data)
|
152
|
+
self.result_data = data
|
153
|
+
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
def is_card?
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
def is_divider?
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def cols
|
168
|
+
@cols || self.class.cols
|
169
|
+
end
|
170
|
+
|
171
|
+
def rows
|
172
|
+
@rows || self.class.rows
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
data/lib/avo/base_resource.rb
CHANGED
@@ -11,6 +11,7 @@ module Avo
|
|
11
11
|
delegate :avo, to: :view_context
|
12
12
|
delegate :resource_path, to: :view_context
|
13
13
|
delegate :resources_path, to: :view_context
|
14
|
+
delegate :t, to: ::I18n
|
14
15
|
|
15
16
|
attr_accessor :view
|
16
17
|
attr_accessor :model
|
@@ -26,7 +27,7 @@ module Avo
|
|
26
27
|
class_attribute :includes, default: []
|
27
28
|
class_attribute :model_class
|
28
29
|
class_attribute :translation_key
|
29
|
-
class_attribute :translation_enabled
|
30
|
+
class_attribute :translation_enabled, default: false
|
30
31
|
class_attribute :default_view_type, default: :table
|
31
32
|
class_attribute :devise_password_optional, default: false
|
32
33
|
class_attribute :actions_loader
|
@@ -38,8 +39,14 @@ module Avo
|
|
38
39
|
class_attribute :resolve_query_scope
|
39
40
|
class_attribute :resolve_find_scope
|
40
41
|
class_attribute :ordering
|
42
|
+
class_attribute :hide_from_global_search, default: false
|
43
|
+
class_attribute :after_create_path, default: :show
|
44
|
+
class_attribute :after_update_path, default: :show
|
45
|
+
class_attribute :invalid_fields
|
41
46
|
|
42
47
|
class << self
|
48
|
+
delegate :t, to: ::I18n
|
49
|
+
|
43
50
|
def grid(&block)
|
44
51
|
grid_collector = GridCollector.new
|
45
52
|
grid_collector.instance_eval(&block)
|
@@ -96,6 +103,8 @@ module Avo
|
|
96
103
|
self.class.model_class = model_class.base_class
|
97
104
|
end
|
98
105
|
end
|
106
|
+
|
107
|
+
self.class.translation_enabled = ::Avo::App.translation_enabled
|
99
108
|
end
|
100
109
|
|
101
110
|
def hydrate(model: nil, view: nil, user: nil, params: nil)
|
@@ -215,7 +224,7 @@ module Avo
|
|
215
224
|
when :edit
|
216
225
|
model_title
|
217
226
|
when :new
|
218
|
-
|
227
|
+
t("avo.create_new_item", item: name.downcase).upcase_first
|
219
228
|
end
|
220
229
|
end
|
221
230
|
|
@@ -244,9 +253,18 @@ module Avo
|
|
244
253
|
class_name_without_resource.safe_constantize
|
245
254
|
end
|
246
255
|
|
256
|
+
def model_id
|
257
|
+
@model.send id
|
258
|
+
end
|
259
|
+
|
247
260
|
def model_title
|
248
|
-
return
|
261
|
+
return name if @model.nil?
|
262
|
+
|
263
|
+
the_title = @model.send title
|
264
|
+
return the_title if the_title.present?
|
249
265
|
|
266
|
+
model_id
|
267
|
+
rescue
|
250
268
|
name
|
251
269
|
end
|
252
270
|
|
@@ -267,11 +285,13 @@ module Avo
|
|
267
285
|
end
|
268
286
|
|
269
287
|
def name
|
288
|
+
default = class_name_without_resource.titlecase
|
289
|
+
|
270
290
|
return @name if @name.present?
|
271
291
|
|
272
|
-
return
|
292
|
+
return t(translation_key, count: 1, default: default).capitalize if translation_key
|
273
293
|
|
274
|
-
|
294
|
+
default
|
275
295
|
end
|
276
296
|
|
277
297
|
def singular_name
|
@@ -279,9 +299,11 @@ module Avo
|
|
279
299
|
end
|
280
300
|
|
281
301
|
def plural_name
|
282
|
-
|
302
|
+
default = name.pluralize
|
303
|
+
|
304
|
+
return t(translation_key, count: 2, default: default).capitalize if translation_key
|
283
305
|
|
284
|
-
|
306
|
+
default
|
285
307
|
end
|
286
308
|
|
287
309
|
def underscore_name
|
@@ -408,11 +430,15 @@ module Avo
|
|
408
430
|
# This is used as the model class ID
|
409
431
|
# We use this instead of the route_key to maintain compatibility with uncountable models
|
410
432
|
# With uncountable models route key appends an _index suffix (Fish->fish_index)
|
411
|
-
# Example: User->users, MediaItem->
|
433
|
+
# Example: User->users, MediaItem->media_items, Fish->fish
|
412
434
|
def model_key
|
413
435
|
model_class.model_name.plural
|
414
436
|
end
|
415
437
|
|
438
|
+
def model_name
|
439
|
+
model_class.model_name
|
440
|
+
end
|
441
|
+
|
416
442
|
def singular_model_key
|
417
443
|
model_class.model_name.singular
|
418
444
|
end
|
data/lib/avo/configuration.rb
CHANGED
@@ -28,6 +28,7 @@ module Avo
|
|
28
28
|
attr_accessor :current_user_resource_name
|
29
29
|
attr_accessor :raise_error_on_missing_policy
|
30
30
|
attr_accessor :disabled_features
|
31
|
+
attr_accessor :buttons_on_form_footers
|
31
32
|
|
32
33
|
def initialize
|
33
34
|
@root_path = "/avo"
|
@@ -68,6 +69,7 @@ module Avo
|
|
68
69
|
@current_user_resource_name = "user"
|
69
70
|
@raise_error_on_missing_policy = false
|
70
71
|
@disabled_features = []
|
72
|
+
@buttons_on_form_footers = false
|
71
73
|
end
|
72
74
|
|
73
75
|
def locale_tag
|
@@ -8,12 +8,31 @@ module Avo
|
|
8
8
|
class_attribute :description
|
9
9
|
class_attribute :items_holder
|
10
10
|
class_attribute :grid_cols, default: 3
|
11
|
+
class_attribute :visible, default: true
|
12
|
+
class_attribute :index, default: 0
|
11
13
|
|
12
14
|
class << self
|
13
|
-
def card(klass)
|
15
|
+
def card(klass, label: nil, description: nil, cols: nil, rows: nil, refresh_every: nil, options: {})
|
14
16
|
self.items_holder ||= []
|
15
17
|
|
16
|
-
self.items_holder << klass.new(dashboard: self
|
18
|
+
self.items_holder << klass.new(dashboard: self,
|
19
|
+
label: label,
|
20
|
+
description: description,
|
21
|
+
cols: cols,
|
22
|
+
rows: rows,
|
23
|
+
refresh_every: refresh_every,
|
24
|
+
options: options,
|
25
|
+
index: index
|
26
|
+
)
|
27
|
+
self.index += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def item_at_index(index)
|
31
|
+
items.find do |item|
|
32
|
+
next if item.index.blank?
|
33
|
+
|
34
|
+
item.index == index
|
35
|
+
end
|
17
36
|
end
|
18
37
|
|
19
38
|
def divider(**args)
|
@@ -48,6 +67,22 @@ module Avo
|
|
48
67
|
def navigation_path
|
49
68
|
"#{Avo::App.root_path}/dashboards/#{id}"
|
50
69
|
end
|
70
|
+
|
71
|
+
def is_visible?
|
72
|
+
# Default is true
|
73
|
+
return true if visible == true
|
74
|
+
|
75
|
+
# Hide if false
|
76
|
+
return false if visible == false
|
77
|
+
|
78
|
+
if visible.respond_to? :call
|
79
|
+
::Avo::Hosts::DashboardVisibility.new(block: visible, dashboard: self).handle
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_hidden?
|
84
|
+
!is_visible?
|
85
|
+
end
|
51
86
|
end
|
52
87
|
end
|
53
88
|
end
|
@@ -3,12 +3,14 @@ module Avo
|
|
3
3
|
class BaseDivider
|
4
4
|
attr_reader :label
|
5
5
|
attr_reader :invisible
|
6
|
+
attr_reader :index
|
6
7
|
|
7
8
|
class_attribute :id
|
8
9
|
|
9
|
-
def initialize(label: nil, invisible: false)
|
10
|
+
def initialize(label: nil, invisible: false, index: nil)
|
10
11
|
@label = label
|
11
12
|
@invisible = invisible
|
13
|
+
@index = index
|
12
14
|
end
|
13
15
|
|
14
16
|
def is_divider?
|
@@ -10,6 +10,7 @@ module Avo
|
|
10
10
|
delegate :view_context, to: "Avo::App"
|
11
11
|
delegate :main_app, to: :view_context
|
12
12
|
delegate :avo, to: :view_context
|
13
|
+
delegate :t, to: ::I18n
|
13
14
|
|
14
15
|
attr_reader :id
|
15
16
|
attr_reader :block
|
@@ -48,7 +49,7 @@ module Avo
|
|
48
49
|
@id = id
|
49
50
|
@name = args[:name]
|
50
51
|
@translation_key = args[:translation_key]
|
51
|
-
@translation_enabled =
|
52
|
+
@translation_enabled = ::Avo::App.translation_enabled
|
52
53
|
@block = block
|
53
54
|
@required = args[:required] || false
|
54
55
|
@readonly = args[:readonly] || false
|
@@ -94,18 +95,26 @@ module Avo
|
|
94
95
|
@translation_key
|
95
96
|
end
|
96
97
|
|
98
|
+
# Getting the name of the resource (user/users, post/posts)
|
99
|
+
# We'll first check to see if the user passed a name
|
100
|
+
# Secondly we'll try to find a translation key
|
101
|
+
# We'll fallback to humanizing the id
|
97
102
|
def name
|
103
|
+
default = @id.to_s.humanize(keep_id_suffix: true)
|
104
|
+
|
98
105
|
return @name if @name.present?
|
99
106
|
|
100
|
-
return
|
107
|
+
return t(translation_key, count: 1, default: default).capitalize if translation_key
|
101
108
|
|
102
|
-
|
109
|
+
default
|
103
110
|
end
|
104
111
|
|
105
112
|
def plural_name
|
106
|
-
|
113
|
+
default = name.pluralize
|
114
|
+
|
115
|
+
return t(translation_key, count: 2, default: default).capitalize if translation_key
|
107
116
|
|
108
|
-
|
117
|
+
default
|
109
118
|
end
|
110
119
|
|
111
120
|
def placeholder
|
@@ -181,8 +190,12 @@ module Avo
|
|
181
190
|
"#{type.camelize}Field"
|
182
191
|
end
|
183
192
|
|
193
|
+
# Try and build the component class or fallback to a blank one
|
184
194
|
def component_for_view(view = :index)
|
185
|
-
"Avo::Fields::#{view_component_name}::#{view.to_s.camelize}Component"
|
195
|
+
component_class = "::Avo::Fields::#{view_component_name}::#{view.to_s.camelize}Component"
|
196
|
+
component_class.constantize
|
197
|
+
rescue
|
198
|
+
::Avo::BlankFieldComponent
|
186
199
|
end
|
187
200
|
|
188
201
|
def model_errors
|