avo 3.1.3 → 3.1.4
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.lock +1 -1
- data/app/components/avo/button_component.rb +1 -1
- data/app/components/avo/fields/edit_component.rb +2 -0
- data/app/components/avo/fields/has_many_field/show_component.html.erb +2 -2
- data/app/components/avo/fields/has_many_field/show_component.rb +7 -0
- data/app/components/avo/fields/index_component.rb +3 -1
- data/app/components/avo/fields/show_component.rb +3 -1
- data/app/components/avo/items/switcher_component.rb +1 -1
- data/app/components/avo/tab_group_component.html.erb +10 -43
- data/app/components/avo/tab_group_component.rb +20 -2
- data/app/components/avo/tab_switcher_component.html.erb +14 -12
- data/app/components/avo/tab_switcher_component.rb +12 -10
- data/app/controllers/avo/base_controller.rb +3 -3
- data/app/javascript/avo.base.js +4 -0
- data/app/javascript/js/controllers/filter_controller.js +22 -11
- data/app/javascript/js/controllers/tabs_controller.js +42 -46
- data/app/javascript/js/local-storage-service.js +19 -0
- data/app/views/avo/home/_actions.html.erb +1 -1
- data/app/views/avo/home/_dashboards.html.erb +1 -1
- data/app/views/avo/home/_filters.html.erb +1 -1
- data/app/views/avo/home/_resources.html.erb +1 -1
- data/lib/avo/base_resource.rb +1 -1
- data/lib/avo/concerns/has_items.rb +17 -10
- data/lib/avo/filters/base_filter.rb +3 -3
- data/lib/avo/resources/items/holder.rb +2 -2
- data/lib/avo/resources/items/tab.rb +5 -0
- data/lib/avo/resources/items/tab_group.rb +17 -3
- data/lib/avo/resources/resource_manager.rb +1 -1
- data/lib/avo/test_helpers.rb +2 -2
- data/lib/avo/version.rb +1 -1
- data/public/avo-assets/avo.base.css +8 -0
- data/public/avo-assets/avo.base.js +124 -124
- data/public/avo-assets/avo.base.js.map +3 -3
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc615ce5edcbf9325987f90d37495c71f641b241fe34d5da9f9fae94018dd94
|
4
|
+
data.tar.gz: 03fae5887aa0b9c0371705d30221cb07f9498ff0d3499f1dd35474fcd449c7f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 504866933b84f2a3969ab026260f8213327a5448bc2f9c50bafef4c45c7de2513373cf1c101e240700f1a3398560c174b530e47caf17621355c6329a3d322f2f
|
7
|
+
data.tar.gz: be6dedf903e9439795adf0da9a278458ad2b53d4841447c00334b42d9cb6a561f62f987ccd687528d9f6fbd05ffae02f1c8a795a8140f027c18c406091c65f65
|
data/Gemfile.lock
CHANGED
@@ -7,6 +7,7 @@ class Avo::Fields::EditComponent < ViewComponent::Base
|
|
7
7
|
attr_reader :field
|
8
8
|
attr_reader :form
|
9
9
|
attr_reader :index
|
10
|
+
attr_reader :kwargs
|
10
11
|
attr_reader :multiple
|
11
12
|
attr_reader :resource
|
12
13
|
attr_reader :stacked
|
@@ -17,6 +18,7 @@ class Avo::Fields::EditComponent < ViewComponent::Base
|
|
17
18
|
@field = field
|
18
19
|
@form = form
|
19
20
|
@index = index
|
21
|
+
@kwargs = kwargs
|
20
22
|
@multiple = multiple
|
21
23
|
@resource = resource
|
22
24
|
@stacked = stacked
|
@@ -1,3 +1,3 @@
|
|
1
|
-
|
1
|
+
<%= turbo_frame_tag @field.turbo_frame, src: @field.frame_url, loading: loading, target: :_top, class: "block" do %>
|
2
2
|
<%= render(Avo::LoadingComponent.new(title: @field.plural_name)) %>
|
3
|
-
|
3
|
+
<% end %>
|
@@ -1,4 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Avo::Fields::HasManyField::ShowComponent < Avo::Fields::ShowComponent
|
4
|
+
include Turbo::FramesHelper
|
5
|
+
|
6
|
+
def turbo_frame_loading = kwargs[:turbo_frame_loading]
|
7
|
+
|
8
|
+
def loading
|
9
|
+
turbo_frame_loading || params[:turbo_frame_loading] || "eager"
|
10
|
+
end
|
4
11
|
end
|
@@ -4,13 +4,15 @@ class Avo::Fields::IndexComponent < Avo::BaseComponent
|
|
4
4
|
include Avo::ResourcesHelper
|
5
5
|
|
6
6
|
attr_reader :field
|
7
|
+
attr_reader :kwargs
|
7
8
|
attr_reader :parent_resource
|
8
9
|
attr_reader :view
|
9
10
|
|
10
|
-
def initialize(field: nil, resource: nil, reflection: nil, index: 0, parent_record: nil, parent_resource: nil)
|
11
|
+
def initialize(field: nil, resource: nil, reflection: nil, index: 0, parent_record: nil, parent_resource: nil, **kwargs)
|
11
12
|
@field = field
|
12
13
|
@resource = resource
|
13
14
|
@index = index
|
15
|
+
@kwargs = kwargs
|
14
16
|
@parent_record = parent_record
|
15
17
|
@parent_resource = parent_resource
|
16
18
|
@view = Avo::ViewInquirer.new("index")
|
@@ -6,18 +6,20 @@ class Avo::Fields::ShowComponent < ViewComponent::Base
|
|
6
6
|
attr_reader :compact
|
7
7
|
attr_reader :field
|
8
8
|
attr_reader :index
|
9
|
+
attr_reader :kwargs
|
9
10
|
attr_reader :resource
|
10
11
|
attr_reader :stacked
|
11
12
|
attr_reader :short
|
12
13
|
attr_reader :view
|
13
14
|
|
14
|
-
def initialize(field: nil, resource: nil, index: 0, form: nil, compact: false, short: false, stacked: nil)
|
15
|
+
def initialize(field: nil, resource: nil, index: 0, form: nil, compact: false, short: false, stacked: nil, **kwargs)
|
15
16
|
@compact = compact
|
16
17
|
@field = field
|
17
18
|
@index = index
|
18
19
|
@resource = resource
|
19
20
|
@stacked = stacked
|
20
21
|
@short = short
|
22
|
+
@kwargs = kwargs
|
21
23
|
@view = Avo::ViewInquirer.new("show")
|
22
24
|
end
|
23
25
|
|
@@ -58,7 +58,7 @@ class Avo::Items::SwitcherComponent < Avo::BaseComponent
|
|
58
58
|
|
59
59
|
def field_component
|
60
60
|
final_item = item.dup.hydrate(resource: @resource, record: @resource.record, user: resource.user, view: view)
|
61
|
-
final_item.component_for_view(@view).new(field: final_item, resource: @resource, index: index, form: form, **@field_component_extra_args)
|
61
|
+
final_item.component_for_view(@view).new(field: final_item, resource: @resource, index: index, form: form, turbo_frame_loading: :lazy, **@field_component_extra_args)
|
62
62
|
end
|
63
63
|
|
64
64
|
def panel_component
|
@@ -4,53 +4,20 @@
|
|
4
4
|
index: index,
|
5
5
|
controller: "tabs",
|
6
6
|
tabs_view_value: view,
|
7
|
-
|
7
|
+
tabs_group_id_value: group.id,
|
8
|
+
tabs_active_tab_value: active_tab_name,
|
9
|
+
tabs_resource_name_value: resource.underscore_name
|
8
10
|
} do %>
|
9
11
|
<% visible_tabs.each_with_index do |tab, index| %>
|
10
|
-
|
11
|
-
args = {
|
12
|
-
# Hide the turbo frames that aren't in the current tab
|
13
|
-
# This way we can lazy load the un-selected tabs on the show view
|
14
|
-
class: "block #{'hidden' unless tab.name == active_tab_name}",
|
15
|
-
data: {
|
16
|
-
# Add a marker to know if we already loaded a turbo frame
|
17
|
-
loaded: tab.name == active_tab_name,
|
18
|
-
tabs_target: :tabPanel,
|
19
|
-
tab_id: tab.name,
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
is_current_tab = active_tab_name.to_s == tab.name.to_s
|
24
|
-
|
25
|
-
# On edit screens we want to load each tab because we wnst the DOM to have the fields present on form submission.
|
26
|
-
# If you have a field which is in the second tab and it's required, the form submission will fail because the required field is not in view, and we don't want that.
|
27
|
-
# We also want to load the current tab
|
28
|
-
should_lazy_load = if @view.in?(%w[edit new update create])
|
29
|
-
false
|
30
|
-
else
|
31
|
-
!is_current_tab
|
32
|
-
end
|
33
|
-
|
34
|
-
if should_lazy_load
|
35
|
-
args[:src] = helpers.resource_path(resource: resource, record: resource.record, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
|
36
|
-
args[:loading] = :lazy
|
37
|
-
end
|
38
|
-
%>
|
39
|
-
<%= turbo_frame_tag tab.turbo_frame_id(parent: @group), **args do %>
|
12
|
+
<%= content_tag :div, **args(tab) do %>
|
40
13
|
<div class="border rounded-lg p-2 -mx-2 -my-2 lg:p-4 lg:-mx-4 lg:-my-4 space-y-4">
|
41
|
-
<%= render Avo::TabSwitcherComponent.new resource: resource, current_tab:
|
42
|
-
<% if
|
43
|
-
<div class="
|
44
|
-
|
14
|
+
<%= render Avo::TabSwitcherComponent.new resource: resource, current_tab: visible_tabs.first, group: group, active_tab_name: tab.name, view: view %>
|
15
|
+
<% if !tab.is_empty? %>
|
16
|
+
<div class="space-y-12">
|
17
|
+
<% tab.visible_items.each do |item| %>
|
18
|
+
<%= render Avo::Items::SwitcherComponent.new resource: resource, item: item, index: index, form: form, view: @view %>
|
19
|
+
<% end %>
|
45
20
|
</div>
|
46
|
-
<% else %>
|
47
|
-
<% if !tab.is_empty? %>
|
48
|
-
<div class="space-y-12">
|
49
|
-
<% tab.visible_items.each do |item| %>
|
50
|
-
<%= render Avo::Items::SwitcherComponent.new resource: resource, item: item, index: index, form: form, view: @view %>
|
51
|
-
<% end %>
|
52
|
-
</div>
|
53
|
-
<% end %>
|
54
21
|
<% end %>
|
55
22
|
</div>
|
56
23
|
<% end %>
|
@@ -26,12 +26,16 @@ class Avo::TabGroupComponent < Avo::BaseComponent
|
|
26
26
|
visible_tabs.present?
|
27
27
|
end
|
28
28
|
|
29
|
+
def group_param
|
30
|
+
group.id
|
31
|
+
end
|
32
|
+
|
29
33
|
def active_tab_name
|
30
|
-
params[
|
34
|
+
CGI.unescape(params[group_param] || group.visible_items&.first&.name)
|
31
35
|
end
|
32
36
|
|
33
37
|
def tabs
|
34
|
-
@group.
|
38
|
+
@group.visible_items.map do |tab|
|
35
39
|
tab.hydrate(view: view)
|
36
40
|
end
|
37
41
|
end
|
@@ -49,4 +53,18 @@ class Avo::TabGroupComponent < Avo::BaseComponent
|
|
49
53
|
tab.name.to_s == active_tab_name.to_s
|
50
54
|
end
|
51
55
|
end
|
56
|
+
|
57
|
+
def args(tab)
|
58
|
+
{
|
59
|
+
# Hide the turbo frames that aren't in the current tab
|
60
|
+
# This way we can lazy load the un-selected tabs on the show view
|
61
|
+
class: "block #{'hidden' unless tab.name == active_tab_name}",
|
62
|
+
data: {
|
63
|
+
# Add a marker to know if we already loaded a turbo frame
|
64
|
+
loaded: tab.name == active_tab_name,
|
65
|
+
tabs_target: :tabPanel,
|
66
|
+
tab_id: tab.name,
|
67
|
+
}
|
68
|
+
}
|
69
|
+
end
|
52
70
|
end
|
@@ -1,17 +1,19 @@
|
|
1
|
-
<div class="flex flex-wrap gap-2 p-2 <%= white_panel_classes %>" data-target="
|
1
|
+
<div class="flex flex-wrap gap-2 p-2 <%= white_panel_classes %>" data-tabs-target="tabSwitcher">
|
2
2
|
<% group.visible_items.each do |tab| %>
|
3
3
|
<%= a_link tab_path(tab),
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
color: current_one?(tab) ? :primary : :gray,
|
5
|
+
style: current_one?(tab) ? :outline : :text,
|
6
|
+
size: :sm,
|
7
|
+
class: current_one?(tab) ? "z-20 bg-primary-100" : "",
|
8
|
+
title: tab.description,
|
9
|
+
data: {
|
10
|
+
tippy: tab.description.present? ? 'tooltip' : '',
|
11
|
+
selected: current_one?(tab),
|
12
|
+
action: 'click->tabs#changeTab',
|
13
|
+
tabs_tab_name_param: tab.name,
|
14
|
+
tabs_group_id_param: group.to_param,
|
15
|
+
tabs_resource_name_param: resource.underscore_name,
|
16
|
+
} do %>
|
15
17
|
<%= tab.name %>
|
16
18
|
<% end %>
|
17
19
|
<% end %>
|
@@ -9,6 +9,7 @@ class Avo::TabSwitcherComponent < Avo::BaseComponent
|
|
9
9
|
attr_reader :current_tab
|
10
10
|
attr_reader :tabs
|
11
11
|
attr_reader :view
|
12
|
+
attr_reader :resource
|
12
13
|
|
13
14
|
delegate :white_panel_classes, to: :helpers
|
14
15
|
|
@@ -40,17 +41,18 @@ class Avo::TabSwitcherComponent < Avo::BaseComponent
|
|
40
41
|
@view.in?(%w[new create])
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
|
44
|
+
# We'll mark the tab as selected if it's the current one
|
45
|
+
def current_one?(tab)
|
46
|
+
tab.name == active_tab_name
|
45
47
|
end
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
private
|
50
|
+
|
51
|
+
def group_param
|
52
|
+
"tab-group_#{group.id}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def tab_param_missing?
|
56
|
+
params[group_param].blank?
|
55
57
|
end
|
56
58
|
end
|
@@ -357,12 +357,12 @@ module Avo
|
|
357
357
|
def set_applied_filters
|
358
358
|
reset_filters if params[:reset_filter]
|
359
359
|
|
360
|
-
@applied_filters =
|
360
|
+
return @applied_filters = {} if (fetched_filters = fetch_filters).blank?
|
361
|
+
|
362
|
+
@applied_filters = Avo::Filters::BaseFilter.decode_filters(fetched_filters)
|
361
363
|
|
362
364
|
# Some filters react to others and will have to be merged into this
|
363
365
|
@applied_filters = @applied_filters.merge reactive_filters
|
364
|
-
rescue
|
365
|
-
@applied_filters = {}
|
366
366
|
end
|
367
367
|
|
368
368
|
def reactive_filters
|
data/app/javascript/avo.base.js
CHANGED
@@ -8,8 +8,12 @@ import { Turbo } from '@hotwired/turbo-rails'
|
|
8
8
|
import Rails from '@rails/ujs'
|
9
9
|
import tippy from 'tippy.js'
|
10
10
|
|
11
|
+
import { LocalStorageService } from './js/local-storage-service'
|
12
|
+
|
11
13
|
import 'chartkick/chart.js/chart.esm'
|
12
14
|
|
15
|
+
window.Avo.localStorage = new LocalStorageService()
|
16
|
+
|
13
17
|
import './js/active-storage'
|
14
18
|
import './js/controllers'
|
15
19
|
import './js/custom-stream-actions'
|
@@ -25,17 +25,28 @@ export default class extends Controller {
|
|
25
25
|
return param
|
26
26
|
}
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
decode(filters) {
|
29
|
+
return JSON.parse(
|
30
|
+
new TextDecoder().decode(
|
31
|
+
Uint8Array.from(
|
32
|
+
atob(
|
33
|
+
decodeURIComponent(filters),
|
34
|
+
), (m) => m.codePointAt(0),
|
35
|
+
),
|
36
|
+
),
|
37
|
+
)
|
34
38
|
}
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
40
|
+
encode(filtered) {
|
41
|
+
return encodeURIComponent(
|
42
|
+
btoa(
|
43
|
+
String.fromCodePoint(
|
44
|
+
...new TextEncoder().encode(
|
45
|
+
JSON.stringify(filtered),
|
46
|
+
),
|
47
|
+
),
|
48
|
+
),
|
49
|
+
)
|
39
50
|
}
|
40
51
|
|
41
52
|
changeFilter() {
|
@@ -47,7 +58,7 @@ export default class extends Controller {
|
|
47
58
|
|
48
59
|
// Decode the filters
|
49
60
|
if (filters) {
|
50
|
-
filters =
|
61
|
+
filters = this.decode(filters)
|
51
62
|
} else {
|
52
63
|
filters = {}
|
53
64
|
}
|
@@ -68,7 +79,7 @@ export default class extends Controller {
|
|
68
79
|
|
69
80
|
// Encode the filters and their values
|
70
81
|
if (filtered && Object.keys(filtered).length > 0) {
|
71
|
-
encodedFilters = this.
|
82
|
+
encodedFilters = this.encode(filtered)
|
72
83
|
}
|
73
84
|
|
74
85
|
this.navigateToURLWithFilters(encodedFilters)
|
@@ -1,74 +1,70 @@
|
|
1
1
|
import { Controller } from '@hotwired/stimulus'
|
2
|
-
import { castBoolean } from '../helpers/cast_boolean'
|
3
2
|
|
4
3
|
export default class extends Controller {
|
5
|
-
static targets = ['tabPanel']
|
4
|
+
static targets = ['tabPanel']
|
6
5
|
|
7
6
|
static values = {
|
8
7
|
view: String,
|
9
8
|
activeTab: String,
|
10
|
-
|
11
|
-
|
12
|
-
get currentTab() {
|
13
|
-
return this.tabPanelTargets.find(
|
14
|
-
(element) => element.dataset.tabId === this.activeTabValue,
|
15
|
-
)
|
9
|
+
groupId: String,
|
10
|
+
resourceName: String,
|
16
11
|
}
|
17
12
|
|
18
|
-
|
19
|
-
|
13
|
+
connect() {
|
14
|
+
this.selectCurrentTab()
|
20
15
|
}
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
e.preventDefault()
|
17
|
+
selectCurrentTab() {
|
18
|
+
const params = {}
|
19
|
+
Array.from(new URL(window.location).searchParams.entries()).forEach(([key, value]) => { params[key] = value })
|
26
20
|
|
27
|
-
const
|
28
|
-
const { id } = params
|
21
|
+
const key = `resources.${this.resourceNameValue}.tabgroups.${this.groupIdValue}.selectedTab`
|
29
22
|
|
30
|
-
|
23
|
+
// LocalStorage value
|
24
|
+
const lsValue = window.Avo.localStorage.get(key)
|
31
25
|
|
32
|
-
|
33
|
-
this.revealTab(id)
|
34
|
-
this.markTabLoaded(id)
|
35
|
-
|
36
|
-
this.activeTabValue = id
|
37
|
-
}
|
26
|
+
let groupId = null
|
38
27
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
// All tabs are loaded beforehand, they have their own height, and the page won't jiggle when the user toggles between them.
|
45
|
-
if (this.viewValue === 'edit' || this.viewValue === 'new') {
|
46
|
-
return
|
28
|
+
// if this tab group has a param in the address, select it
|
29
|
+
if (params[this.groupParam(this.groupIdValue)]) {
|
30
|
+
groupId = params[this.groupParam(this.groupIdValue)]
|
31
|
+
} else if (lsValue) {
|
32
|
+
groupId = lsValue
|
47
33
|
}
|
48
34
|
|
49
|
-
|
50
|
-
|
51
|
-
|
35
|
+
if (this.getTabByName(groupId)) {
|
36
|
+
this.hideAllTabs()
|
37
|
+
this.revealTabByName(groupId)
|
52
38
|
}
|
39
|
+
}
|
53
40
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
this.targetTabPanel(id).style.height = `${height}px`
|
41
|
+
getTabByName(id) {
|
42
|
+
return this.tabPanelTargets.find((element) => element.dataset.tabId === id)
|
43
|
+
}
|
58
44
|
|
59
|
-
|
60
|
-
|
61
|
-
this.targetTabPanel(id).style.height = ''
|
45
|
+
groupParam(groupId) {
|
46
|
+
return encodeURIComponent(`tab-group_${groupId}`)
|
62
47
|
}
|
63
48
|
|
64
|
-
|
65
|
-
|
66
|
-
|
49
|
+
async changeTab(e) {
|
50
|
+
e.preventDefault()
|
51
|
+
const { params } = e
|
52
|
+
const { groupId, tabName, resourceName } = params
|
53
|
+
const key = `resources.${resourceName}.tabgroups.${groupId}.selectedTab`
|
54
|
+
|
55
|
+
const u = new URL(window.location)
|
56
|
+
u.searchParams.set(this.groupParam(groupId), encodeURIComponent(tabName))
|
57
|
+
window.history.replaceState(null, '', u.pathname + u.search)
|
58
|
+
|
59
|
+
window.Avo.localStorage.set(key, tabName)
|
60
|
+
|
61
|
+
this.hideAllTabs()
|
62
|
+
this.revealTabByName(tabName)
|
67
63
|
}
|
68
64
|
|
69
65
|
// We're revealing the new tab that's lazy loaded by Turbo.
|
70
|
-
|
71
|
-
this.
|
66
|
+
revealTabByName(name) {
|
67
|
+
this.getTabByName(name).classList.remove('hidden')
|
72
68
|
}
|
73
69
|
|
74
70
|
hideAllTabs() {
|
@@ -0,0 +1,19 @@
|
|
1
|
+
export class LocalStorageService {
|
2
|
+
prefix = 'avo'
|
3
|
+
|
4
|
+
prefixedKey(key) {
|
5
|
+
return `${this.prefix}.${key}`
|
6
|
+
}
|
7
|
+
|
8
|
+
get(key) {
|
9
|
+
return window.localStorage.getItem(this.prefixedKey(key))
|
10
|
+
}
|
11
|
+
|
12
|
+
set(key, value) {
|
13
|
+
return window.localStorage.setItem(this.prefixedKey(key), value)
|
14
|
+
}
|
15
|
+
|
16
|
+
remove(key) {
|
17
|
+
return window.localStorage.removeItem(this.prefixedKey(key))
|
18
|
+
}
|
19
|
+
}
|
@@ -11,6 +11,6 @@
|
|
11
11
|
</div>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
<a href="https://docs.avohq.io/
|
14
|
+
<a href="https://docs.avohq.io/3.0/actions.html" target="_blank" title="Avo Actions documentation" class="text-bold cursor-pointer block mt-2">Actions in the docs 👉</a>
|
15
15
|
</div>
|
16
16
|
</div>
|
@@ -13,7 +13,7 @@
|
|
13
13
|
</div>
|
14
14
|
|
15
15
|
<p>
|
16
|
-
<a href="https://docs.avohq.io/
|
16
|
+
<a href="https://docs.avohq.io/3.0/dashboards.html" target="_blank" title="Avo Dashboards documentation">Docs</a>
|
17
17
|
</p>
|
18
18
|
</div>
|
19
19
|
|
@@ -11,6 +11,6 @@
|
|
11
11
|
</div>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
<a href="https://docs.avohq.io/
|
14
|
+
<a href="https://docs.avohq.io/3.0/filters.html" target="_blank" title="Avo Filters documentation" class="text-bold cursor-pointer block mt-2">Filters in the docs 👉</a>
|
15
15
|
</div>
|
16
16
|
</div>
|
@@ -35,7 +35,7 @@
|
|
35
35
|
<% end %>
|
36
36
|
|
37
37
|
<p>
|
38
|
-
<a href="https://docs.avohq.io/
|
38
|
+
<a href="https://docs.avohq.io/3.0/resources.html" target="_blank" title="Avo Resources documentation">Docs</a>
|
39
39
|
</p>
|
40
40
|
</div>
|
41
41
|
|
data/lib/avo/base_resource.rb
CHANGED
@@ -231,7 +231,7 @@ module Avo
|
|
231
231
|
delegate :singular_name, to: :class
|
232
232
|
delegate :plural_name, to: :class
|
233
233
|
delegate :underscore_name, to: :class
|
234
|
-
delegate :
|
234
|
+
delegate :to_param, to: :class
|
235
235
|
delegate :find_record, to: :class
|
236
236
|
delegate :model_key, to: :class
|
237
237
|
delegate :tab, to: :items_holder
|
@@ -275,16 +275,23 @@ module Avo
|
|
275
275
|
end
|
276
276
|
end
|
277
277
|
.select do |item|
|
278
|
-
#
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
278
|
+
# Check if record has the setter method
|
279
|
+
# Next if the view is not on forms
|
280
|
+
next true if !view.in?(%w[edit update new create])
|
281
|
+
|
282
|
+
# Skip items that don't have an id
|
283
|
+
next true if !item.respond_to?(:id)
|
284
|
+
|
285
|
+
# Skip tab groups
|
286
|
+
# Skip headings
|
287
|
+
# Skip location fields
|
288
|
+
# On location field we can have field coordinates and setters with different names
|
289
|
+
# like latitude and longitude
|
290
|
+
next true if item.is_a?(Avo::Resources::Items::TabGroup) ||
|
291
|
+
item.is_heading? ||
|
292
|
+
item.is_a?(Avo::Fields::LocationField)
|
293
|
+
|
294
|
+
item.resource.record.respond_to?("#{item.id}=")
|
288
295
|
end
|
289
296
|
.select do |item|
|
290
297
|
# Check if the user is authorized to view it.
|
@@ -23,8 +23,6 @@ module Avo
|
|
23
23
|
class << self
|
24
24
|
def decode_filters(filter_params)
|
25
25
|
JSON.parse(Base64.decode64(filter_params))
|
26
|
-
rescue
|
27
|
-
{}
|
28
26
|
end
|
29
27
|
|
30
28
|
def encode_filters(filter_params)
|
@@ -67,7 +65,9 @@ module Avo
|
|
67
65
|
|
68
66
|
# Fetch the applied filters from the params
|
69
67
|
def applied_filters
|
70
|
-
|
68
|
+
return {} if (filters_from_params = params[PARAM_KEY]).blank?
|
69
|
+
|
70
|
+
self.class.decode_filters filters_from_params
|
71
71
|
end
|
72
72
|
|
73
73
|
def visible_in_view(resource: nil, parent_resource: nil)
|
@@ -37,11 +37,11 @@ class Avo::Resources::Items::Holder
|
|
37
37
|
add_item field_parser.instance
|
38
38
|
end
|
39
39
|
|
40
|
-
def tabs(tab = nil, **kwargs, &block)
|
40
|
+
def tabs(tab = nil, id: nil, name: nil, **kwargs, &block)
|
41
41
|
if tab.present?
|
42
42
|
add_item tab
|
43
43
|
else
|
44
|
-
add_item Avo::Resources::Items::TabGroup::Builder.parse_block(parent: @parent, **kwargs, &block)
|
44
|
+
add_item Avo::Resources::Items::TabGroup::Builder.parse_block(parent: @parent, id: id, name: name, **kwargs, &block)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -24,6 +24,11 @@ class Avo::Resources::Items::Tab
|
|
24
24
|
Avo::ExecutionContext.new(target: @name).handle
|
25
25
|
end
|
26
26
|
|
27
|
+
def id
|
28
|
+
name.to_s.parameterize
|
29
|
+
end
|
30
|
+
alias_method :to_param, :id
|
31
|
+
|
27
32
|
def turbo_frame_id(parent: nil)
|
28
33
|
id = "#{Avo::Resources::Items::Tab.to_s.parameterize} #{name}".parameterize
|
29
34
|
|