avo 2.9.2.pre1 → 2.10.0
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 +5 -7
- data/README.md +4 -0
- data/app/assets/stylesheets/css/buttons.css +4 -1
- data/app/components/avo/actions_component.rb +6 -2
- data/app/components/avo/base_component.rb +2 -0
- data/app/components/avo/button_component.rb +3 -1
- data/app/components/avo/fields/common/key_value_component.html.erb +2 -2
- data/app/components/avo/fields/common/single_file_viewer_component.rb +1 -1
- data/app/components/avo/fields/date_field/edit_component.html.erb +1 -0
- data/app/components/avo/fields/date_time_field/edit_component.html.erb +10 -25
- data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -9
- data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -9
- data/app/components/avo/fields/edit_component.rb +5 -0
- data/app/components/avo/fields/show_component.rb +1 -1
- data/app/components/avo/index/ordering/button_component.rb +2 -12
- data/app/components/avo/index/resource_controls_component.html.erb +2 -2
- data/app/components/avo/index/resource_controls_component.rb +5 -1
- data/app/components/avo/index/resource_table_component.html.erb +1 -1
- data/app/components/avo/index/table_row_component.html.erb +1 -1
- data/app/components/avo/item_switcher_component.html.erb +19 -0
- data/app/components/avo/item_switcher_component.rb +45 -0
- data/app/components/avo/panel_component.html.erb +23 -24
- data/app/components/avo/panel_component.rb +8 -5
- data/app/components/avo/tab_group_component.html.erb +53 -0
- data/app/components/avo/tab_group_component.rb +51 -0
- data/app/components/avo/tab_switcher_component.html.erb +21 -0
- data/app/components/avo/tab_switcher_component.rb +86 -0
- data/app/components/avo/views/resource_edit_component.html.erb +34 -56
- data/app/components/avo/views/resource_edit_component.rb +11 -1
- data/app/components/avo/views/resource_index_component.html.erb +1 -1
- data/app/components/avo/views/resource_index_component.rb +3 -3
- data/app/components/avo/views/resource_show_component.html.erb +58 -89
- data/app/components/avo/views/resource_show_component.rb +2 -2
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/application_controller.rb +20 -3
- data/app/helpers/avo/application_helper.rb +0 -6
- data/app/helpers/avo/url_helpers.rb +1 -1
- data/app/javascript/avo.js +5 -1
- data/app/javascript/js/controllers/fields/date_field_controller.js +25 -87
- data/app/javascript/js/controllers/loading_button_controller.js +25 -21
- data/app/javascript/js/controllers/tabs_controller.js +86 -0
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/base/index.html.erb +1 -1
- data/app/views/avo/base/show.html.erb +1 -1
- data/app/views/avo/cards/show.html.erb +1 -1
- data/app/views/avo/debug/index.html.erb +1 -1
- data/app/views/avo/home/_actions.html.erb +1 -1
- data/app/views/avo/home/_dashboards.html.erb +19 -0
- data/app/views/avo/home/_filters.html.erb +1 -1
- data/app/views/avo/home/_resources.html.erb +1 -1
- data/app/views/avo/home/failed_to_load.html.erb +1 -1
- data/app/views/avo/home/index.html.erb +14 -2
- data/app/views/avo/partials/_javascript.html.erb +1 -1
- data/app/views/avo/partials/_tabs_toggle.html.erb +20 -0
- data/app/views/avo/private/design.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/db/migrate/20210421064037_add_color_to_teams.rb +5 -0
- data/db/migrate/20210423075924_add_progress_to_projects.rb +5 -0
- data/db/migrate/20210525143134_add_slug_to_users.rb +6 -0
- data/lib/avo/app.rb +11 -4
- data/lib/avo/base_action.rb +2 -19
- data/lib/avo/base_card.rb +1 -7
- data/lib/avo/base_resource.rb +1 -95
- data/lib/avo/base_resource_tool.rb +3 -1
- data/lib/avo/concerns/handles_field_args.rb +1 -1
- data/lib/avo/concerns/has_fields.rb +247 -50
- data/lib/avo/concerns/has_html_attributes.rb +1 -1
- data/lib/avo/concerns/is_resource_item.rb +36 -0
- data/lib/avo/concerns/model_class_constantized.rb +23 -0
- data/lib/avo/dashboards/base_dashboard.rb +1 -1
- data/lib/avo/dsl/field_parser.rb +83 -0
- data/lib/avo/fields/base_field.rb +19 -2
- data/lib/avo/fields/date_field.rb +2 -0
- data/lib/avo/fields/date_time_field.rb +9 -21
- data/lib/avo/fields/field_extensions/visible_in_different_views.rb +18 -1
- data/lib/avo/fields/has_base_field.rb +20 -1
- data/lib/avo/fields/has_one_field.rb +4 -1
- data/lib/avo/grid_collector.rb +6 -3
- data/lib/avo/items_holder.rb +68 -0
- data/lib/avo/licensing/h_q.rb +10 -0
- data/lib/avo/main_panel.rb +3 -0
- data/lib/avo/menu/builder.rb +8 -7
- data/lib/avo/panel.rb +25 -0
- data/lib/avo/panel_builder.rb +23 -0
- data/lib/avo/services/uri_service.rb +71 -0
- data/lib/avo/tab.rb +78 -0
- data/lib/avo/tab_builder.rb +25 -0
- data/lib/avo/tab_group.rb +40 -0
- data/lib/avo/tab_group_builder.rb +43 -0
- data/lib/avo/version.rb +1 -1
- data/lib/avo.rb +1 -0
- data/lib/generators/avo/templates/locales/avo.fr.yml +115 -0
- data/lib/generators/avo/templates/resource/controller.tt +2 -0
- data/lib/generators/avo/templates/resource_tools/partial.tt +1 -1
- data/lib/generators/avo/templates/tool/view.tt +1 -1
- data/public/avo-assets/avo.css +27 -3
- data/public/avo-assets/avo.js +77 -77
- data/public/avo-assets/avo.js.map +3 -3
- metadata +28 -13
- data/app/assets/builds/action_cable.js +0 -2
- data/app/assets/builds/action_cable.js.map +0 -7
- data/app/assets/builds/application.js +0 -2
- data/app/assets/builds/application.js.map +0 -7
- data/app/assets/builds/avo.css +0 -9028
- data/app/assets/builds/avo.js +0 -512
- data/app/assets/builds/avo.js.map +0 -7
- data/app/assets/builds/avo_custom.js +0 -6
- data/app/assets/builds/avo_custom.js.map +0 -7
- data/lib/avo/concerns/has_tools.rb +0 -47
@@ -2,125 +2,63 @@ import { Controller } from '@hotwired/stimulus'
|
|
2
2
|
import { DateTime } from 'luxon'
|
3
3
|
import flatpickr from 'flatpickr'
|
4
4
|
|
5
|
+
import { castBoolean } from '../../helpers/cast_boolean'
|
6
|
+
|
5
7
|
// Get the DateTime with the TZ offset applied.
|
6
8
|
function universalTimestamp(timestampStr) {
|
7
9
|
return new Date(new Date(timestampStr).getTime() + (new Date(timestampStr).getTimezoneOffset() * 60 * 1000))
|
8
10
|
}
|
9
11
|
|
10
12
|
export default class extends Controller {
|
11
|
-
static targets = ['input'
|
12
|
-
|
13
|
-
static values = {
|
14
|
-
view: String,
|
15
|
-
timezone: String,
|
16
|
-
format: String,
|
17
|
-
enableTime: Boolean,
|
18
|
-
pickerFormat: String,
|
19
|
-
firstDayOfWeek: Number,
|
20
|
-
time24Hr: Boolean,
|
21
|
-
}
|
22
|
-
|
23
|
-
get browserZone() {
|
24
|
-
const time = DateTime.local()
|
25
|
-
|
26
|
-
return time.zoneName
|
27
|
-
}
|
28
|
-
|
29
|
-
get initialValue() {
|
30
|
-
if (this.isOnShow || this.isOnIndex) {
|
31
|
-
return this.context.element.innerText
|
32
|
-
} if (this.isOnEdit) {
|
33
|
-
return this.inputTarget.value
|
34
|
-
}
|
35
|
-
|
36
|
-
return null
|
37
|
-
}
|
38
|
-
|
39
|
-
get isOnIndex() {
|
40
|
-
return this.viewValue === 'index'
|
41
|
-
}
|
42
|
-
|
43
|
-
get isOnEdit() {
|
44
|
-
return this.viewValue === 'edit'
|
45
|
-
}
|
46
|
-
|
47
|
-
get isOnShow() {
|
48
|
-
return this.viewValue === 'show'
|
49
|
-
}
|
50
|
-
|
51
|
-
// Parse the time as if it were UTC
|
52
|
-
get parsedValue() {
|
53
|
-
return DateTime.fromISO(this.initialValue, { zone: 'UTC' })
|
54
|
-
}
|
55
|
-
|
56
|
-
get displayTimezone() {
|
57
|
-
return this.timezoneValue || this.browserZone
|
58
|
-
}
|
13
|
+
static targets = ['input']
|
59
14
|
|
60
15
|
connect() {
|
61
|
-
if (this.isOnShow || this.isOnIndex) {
|
62
|
-
this.initShow()
|
63
|
-
} else if (this.isOnEdit) {
|
64
|
-
this.initEdit()
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
// Turns the value in the controller wrapper into the timezone of the browser
|
69
|
-
initShow() {
|
70
|
-
this.context.element.innerText = this.parsedValue.setZone(this.displayTimezone).toFormat(this.formatValue)
|
71
|
-
}
|
72
|
-
|
73
|
-
initEdit() {
|
74
16
|
const options = {
|
75
17
|
enableTime: false,
|
76
18
|
enableSeconds: false,
|
77
19
|
// eslint-disable-next-line camelcase
|
78
|
-
time_24hr:
|
20
|
+
time_24hr: false,
|
79
21
|
locale: {
|
80
22
|
firstDayOfWeek: 0,
|
81
23
|
},
|
82
24
|
altInput: true,
|
83
|
-
onChange: this.onChange.bind(this),
|
84
25
|
}
|
26
|
+
const enableTime = castBoolean(this.inputTarget.dataset.enableTime)
|
85
27
|
|
86
28
|
// Set the format of the displayed input field.
|
87
|
-
options.altFormat = this.
|
29
|
+
options.altFormat = this.inputTarget.dataset.pickerFormat
|
30
|
+
|
31
|
+
// Disable native input in mobile browsers
|
32
|
+
options.disableMobile = this.inputTarget.dataset.disableMobile
|
88
33
|
|
89
34
|
// Set first day of the week.
|
90
|
-
options.locale.firstDayOfWeek = this.
|
35
|
+
options.locale.firstDayOfWeek = this.inputTarget.dataset.firstDayOfWeek
|
91
36
|
|
92
37
|
// Enable time if needed.
|
93
|
-
options.enableTime =
|
94
|
-
options.enableSeconds =
|
38
|
+
options.enableTime = enableTime
|
39
|
+
options.enableSeconds = enableTime
|
40
|
+
|
41
|
+
let currentValue
|
95
42
|
|
96
43
|
// enable timezone display
|
97
|
-
if (
|
98
|
-
|
44
|
+
if (enableTime) {
|
45
|
+
currentValue = DateTime.fromISO(this.inputTarget.value, { zone: window.Avo.configuration.timezone })
|
46
|
+
currentValue = currentValue.setZone(this.inputTarget.dataset.timezone)
|
47
|
+
currentValue = currentValue.toISO()
|
99
48
|
|
100
49
|
options.dateFormat = 'Y-m-d H:i:S'
|
50
|
+
// eslint-disable-next-line camelcase
|
51
|
+
options.time_24hr = castBoolean(this.inputTarget.dataset.time24hr)
|
52
|
+
// this.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
53
|
+
options.appTimezone = this.inputTarget.dataset.timezone
|
101
54
|
} else {
|
102
55
|
// Because the browser treats the date like a timestamp and updates it ot 00:00 hour, when on a western timezone the date will be converted with one day offset.
|
103
56
|
// Ex: 2022-01-30 will render as 2022-01-29 on an American timezone
|
104
|
-
|
57
|
+
currentValue = universalTimestamp(this.inputTarget.value)
|
105
58
|
}
|
106
59
|
|
107
|
-
|
108
|
-
|
109
|
-
this.updateRealInput(this.parsedValue.setZone(this.displayTimezone).toISO())
|
110
|
-
}
|
111
|
-
|
112
|
-
onChange(selectedDates) {
|
113
|
-
let time
|
114
|
-
|
115
|
-
if (this.timezoneValue) {
|
116
|
-
time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: true })
|
117
|
-
} else {
|
118
|
-
time = DateTime.fromISO(selectedDates[0].toISOString()).setZone('UTC', { keepLocalTime: false })
|
119
|
-
}
|
120
|
-
this.updateRealInput(time)
|
121
|
-
}
|
60
|
+
options.defaultDate = currentValue
|
122
61
|
|
123
|
-
|
124
|
-
this.inputTarget.value = value
|
62
|
+
flatpickr(this.inputTarget, options)
|
125
63
|
}
|
126
64
|
}
|
@@ -7,33 +7,37 @@ export default class extends Controller {
|
|
7
7
|
<div class="double-bounce2"></div>
|
8
8
|
</div>`;
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
10
|
+
static values = {
|
11
|
+
confirmationMessage: String,
|
12
|
+
confirmed: Boolean,
|
13
|
+
}
|
14
|
+
|
15
|
+
attemptSubmit(e) {
|
16
|
+
// If the user has to confirm the action
|
17
|
+
if (this.confirmationMessageValue) {
|
18
|
+
this.confirmAndApply(e)
|
19
|
+
} else {
|
20
|
+
this.applyLoader()
|
21
|
+
}
|
22
|
+
|
23
|
+
return null
|
24
|
+
}
|
25
|
+
|
26
|
+
confirmAndApply(e) {
|
27
|
+
// Intervene only if not confirmed
|
28
|
+
if (!this.confirmedValue) {
|
29
|
+
e.preventDefault()
|
30
|
+
|
31
|
+
if (window.confirm(this.confirmationMessageValue)) {
|
24
32
|
this.applyLoader()
|
25
33
|
}
|
26
|
-
}
|
34
|
+
}
|
27
35
|
}
|
28
36
|
|
29
37
|
get button() {
|
30
38
|
return this.context.scope.element
|
31
39
|
}
|
32
40
|
|
33
|
-
get confirmationMessage() {
|
34
|
-
return this.context.scope.element.getAttribute('data-avo-confirm')
|
35
|
-
}
|
36
|
-
|
37
41
|
applyLoader() {
|
38
42
|
const { button } = this
|
39
43
|
|
@@ -50,10 +54,10 @@ export default class extends Controller {
|
|
50
54
|
}
|
51
55
|
|
52
56
|
markConfirmed() {
|
53
|
-
this.
|
57
|
+
this.confirmedValue = true
|
54
58
|
}
|
55
59
|
|
56
60
|
markUnconfirmed() {
|
57
|
-
this.
|
61
|
+
this.confirmedValue = false
|
58
62
|
}
|
59
63
|
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import { AttributeObserver } from '@stimulus/mutation-observers'
|
2
|
+
import { Controller } from '@hotwired/stimulus'
|
3
|
+
import { castBoolean } from '../helpers/cast_boolean'
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static targets = ['tab'];
|
7
|
+
|
8
|
+
static values = {
|
9
|
+
view: String,
|
10
|
+
activeTab: String,
|
11
|
+
};
|
12
|
+
|
13
|
+
get currentTab() {
|
14
|
+
return this.tabTargets.find(
|
15
|
+
(element) => element.dataset.tabId === this.activeTabValue,
|
16
|
+
)
|
17
|
+
}
|
18
|
+
|
19
|
+
targetTab(id) {
|
20
|
+
return this.tabTargets.find((element) => element.dataset.tabId === id)
|
21
|
+
}
|
22
|
+
|
23
|
+
changeTab(e) {
|
24
|
+
e.preventDefault()
|
25
|
+
|
26
|
+
const { params } = e
|
27
|
+
const { id } = params
|
28
|
+
|
29
|
+
this.setTheTargetPanelHeight(id)
|
30
|
+
|
31
|
+
this.hideTabs()
|
32
|
+
this.showTab(id)
|
33
|
+
this.markTabLoaded(id)
|
34
|
+
|
35
|
+
this.activeTabValue = id
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Sets the target container height to the previous panel height so we don't get jerky tab changes.
|
40
|
+
*/
|
41
|
+
setTheTargetPanelHeight(id) {
|
42
|
+
// Ignore this on edit.
|
43
|
+
// All tabs are loaded beforehand, they have their own height, and the page won't jiggle when the user toggles between them.
|
44
|
+
if (this.viewValue === 'edit' || this.viewValue === 'new') {
|
45
|
+
return
|
46
|
+
}
|
47
|
+
|
48
|
+
// We don't need to add a height to this panel because it was loaded before
|
49
|
+
if (castBoolean(this.targetTab(id).dataset.loaded)) {
|
50
|
+
return
|
51
|
+
}
|
52
|
+
|
53
|
+
// Get the height of the active panel
|
54
|
+
const { height } = this.currentTab.getBoundingClientRect()
|
55
|
+
// Set it to the target panel
|
56
|
+
this.targetTab(id).style.height = `${height}px`
|
57
|
+
|
58
|
+
// Wait until the panel loaded it's content and then remove the forced height
|
59
|
+
const observer = new AttributeObserver(this.targetTab(id), 'busy', {
|
60
|
+
elementUnmatchedAttribute: () => {
|
61
|
+
// The content is not available in an instant so delay the height reset a bit.
|
62
|
+
setTimeout(() => {
|
63
|
+
this.targetTab(id).style.height = ''
|
64
|
+
}, 300)
|
65
|
+
if (observer) observer.stop()
|
66
|
+
},
|
67
|
+
})
|
68
|
+
observer.start()
|
69
|
+
}
|
70
|
+
|
71
|
+
markTabLoaded(id) {
|
72
|
+
this.targetTab(id).dataset.loaded = true
|
73
|
+
}
|
74
|
+
|
75
|
+
showTab(id) {
|
76
|
+
this.tabTargets.forEach((element) => {
|
77
|
+
if (element.dataset.tabId === id) {
|
78
|
+
element.classList.remove('hidden')
|
79
|
+
}
|
80
|
+
})
|
81
|
+
}
|
82
|
+
|
83
|
+
hideTabs() {
|
84
|
+
this.tabTargets.map((element) => element.classList.add('hidden'))
|
85
|
+
}
|
86
|
+
}
|
@@ -27,6 +27,7 @@ import SearchController from './controllers/search_controller'
|
|
27
27
|
import SelectController from './controllers/select_controller'
|
28
28
|
import SelectFilterController from './controllers/select_filter_controller'
|
29
29
|
import SimpleMdeController from './controllers/fields/simple_mde_controller'
|
30
|
+
import TabsController from './controllers/tabs_controller'
|
30
31
|
import TagsFieldController from './controllers/fields/tags_field_controller'
|
31
32
|
import TextFilterController from './controllers/text_filter_controller'
|
32
33
|
import TippyController from './controllers/tippy_controller'
|
@@ -55,6 +56,7 @@ application.register('resource-show', ResourceShowController)
|
|
55
56
|
application.register('search', SearchController)
|
56
57
|
application.register('select', SelectController)
|
57
58
|
application.register('select-filter', SelectFilterController)
|
59
|
+
application.register('tabs', TabsController)
|
58
60
|
application.register('tags-field', TagsFieldController)
|
59
61
|
application.register('text-filter', TextFilterController)
|
60
62
|
application.register('tippy', TippyController)
|
@@ -14,7 +14,7 @@
|
|
14
14
|
hq_payload = Avo::Licensing::HQ.new(request).payload
|
15
15
|
%>
|
16
16
|
<div class="flex flex-col">
|
17
|
-
<%= render Avo::PanelComponent.new
|
17
|
+
<%= render Avo::PanelComponent.new(title: 'Debug Avo', description: 'Use this page to debug the Avo license.') do |c| %>
|
18
18
|
<% c.tools do %>
|
19
19
|
<% end %>
|
20
20
|
<% c.bare_content do %>
|
@@ -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/2.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>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<div>
|
2
|
+
<h3>Dashboards</h3>
|
3
|
+
|
4
|
+
<div>There comes the point in your app's life when you need to display the data in an aggregated form like a metric or chart. That's what Avo's Dashboards are all about.</div>
|
5
|
+
|
6
|
+
<div>Generate a dashboard using the command below 👇👇</div>
|
7
|
+
|
8
|
+
|
9
|
+
<div class="mt-2">
|
10
|
+
<div class="mt-1">
|
11
|
+
<code class="p-1 rounded bg-sky-500 text-white">bin/rails generate avo:dashboard dashy</code>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<a href="https://docs.avohq.io/2.0/dashboards.html" target="_blank" title="Avo Dashboards documentation">Docs</a>
|
17
|
+
</p>
|
18
|
+
</div>
|
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/2.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/2.0/resources.html" target="_blank" title="Avo Resources documentation">Docs</a>
|
39
39
|
</p>
|
40
40
|
</div>
|
41
41
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="flex flex-col">
|
2
|
-
<%= render Avo::PanelComponent.new
|
2
|
+
<%= render Avo::PanelComponent.new(title: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
|
3
3
|
<% c.body do %>
|
4
4
|
<div class="flex flex-col justify-between py-6 min-h-24">
|
5
5
|
<div class="px-6 space-y-4">
|
@@ -23,10 +23,20 @@
|
|
23
23
|
<li><span class="mr-2">👍</span> Grid view</li>
|
24
24
|
<li><span class="mr-2">👍</span> Authorization</li>
|
25
25
|
<li><span class="mr-2">👍</span> Localization</li>
|
26
|
+
<li><span class="mr-2">👍</span> Resource tools</li>
|
26
27
|
<li><span class="mr-2">👍</span> Custom tools</li>
|
27
28
|
<li><span class="mr-2">👍</span> Custom fields</li>
|
29
|
+
<li><span class="mr-2">👍</span> Menu editor</li>
|
28
30
|
<li><span class="mr-2">👍</span> Search</li>
|
29
|
-
<li><span class="mr-2"
|
31
|
+
<li><span class="mr-2">👍</span> Dashboards</li>
|
32
|
+
<li><span class="mr-2">👍</span> Responsive design</li>
|
33
|
+
<li><span class="mr-2">👍</span> Menu editor</li>
|
34
|
+
<li><span class="mr-2">👍</span> Stimulus JS integration</li>
|
35
|
+
<li><span class="mr-2">👍</span> Tabs and panels</li>
|
36
|
+
<li><span class="mr-2">🕐</span> Resource cards</li>
|
37
|
+
<li><span class="mr-2">🕐</span> Track resource changes</li>
|
38
|
+
<li><span class="mr-2">🕐</span> Smart resource generation</li>
|
39
|
+
<li><span class="mr-2">🕐</span> Records preview</li>
|
30
40
|
<li><span class="mr-2">🕐</span> Themes</li>
|
31
41
|
</ul>
|
32
42
|
</p>
|
@@ -37,6 +47,8 @@
|
|
37
47
|
<div class="space">
|
38
48
|
<%= render partial: 'resources' %>
|
39
49
|
<hr class="my-6">
|
50
|
+
<%= render partial: 'dashboards' %>
|
51
|
+
<hr class="my-6">
|
40
52
|
<%= render partial: 'filters' %>
|
41
53
|
<hr class="my-6">
|
42
54
|
<%= render partial: 'actions' %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= javascript_tag nonce: true do %>
|
2
2
|
window.Avo = window.Avo || { configuration: {} }
|
3
3
|
Avo.configuration.timezone = '<%= Avo.configuration.timezone %>'
|
4
|
-
Avo.configuration.root_path = '<%= Avo
|
4
|
+
Avo.configuration.root_path = '<%= Avo.configuration.root_path %>'
|
5
5
|
Avo.configuration.search_debounce = '<%= Avo.configuration.search_debounce %>'
|
6
6
|
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="flex">
|
2
|
+
<div class="button-group">
|
3
|
+
<% tabs.each do |tab| %>
|
4
|
+
<% is_active_view = tab.name.to_s == active_tab_name.to_s %>
|
5
|
+
<%= a_link resource_path(resource: @resource, model: @resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id),
|
6
|
+
color: is_active_view ? :gray : :primary,
|
7
|
+
rounded: false,
|
8
|
+
size: :sm,
|
9
|
+
class: is_active_view ? ' bg-gray-100 border-gray-300' : ' z-20',
|
10
|
+
title: tab.description || tab.name,
|
11
|
+
data: {
|
12
|
+
tippy: 'tooltip',
|
13
|
+
'turbo-frame': group.turbo_frame_id,
|
14
|
+
control: "view-type-toggle-#{tab}"
|
15
|
+
} do %>
|
16
|
+
<%= tab.name %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
20
|
+
</div>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="flex flex-col">
|
2
|
-
<%= render Avo::PanelComponent.new
|
2
|
+
<%= render Avo::PanelComponent.new(title: 'Welcome to Avo', description: 'This page is visible only in development. It will be hidden in other environments.') do |c| %>
|
3
3
|
<% c.tools do %>
|
4
4
|
<%= a_link('/admin', icon: 'arrow-left', style: :primary, is_link: true) do %>
|
5
5
|
Primary
|
data/config/routes.rb
CHANGED
@@ -23,7 +23,7 @@ Avo::Engine.routes.draw do
|
|
23
23
|
delete "/:resource_name/:id/active_storage_attachments/:attachment_name/:attachment_id", to: "attachments#destroy"
|
24
24
|
|
25
25
|
# Ordering
|
26
|
-
patch "/:resource_name/:id/order", to: "resources#order"
|
26
|
+
patch "/:resource_name/:id/order", to: "resources#order", as: "order"
|
27
27
|
patch "/:resource_name/:id/:related_name/:related_id/order", to: "associations#order", as: "associations_order"
|
28
28
|
|
29
29
|
# Actions
|
data/lib/avo/app.rb
CHANGED
@@ -29,14 +29,21 @@ module Avo
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
# Renerate a dynamic root path using the URIService
|
33
|
+
def root_path(paths: [], query: {}, **args)
|
34
|
+
Avo::Services::URIService.parse(view_context.avo.root_url.to_s)
|
35
|
+
.append_paths(paths)
|
36
|
+
.append_query(query)
|
37
|
+
.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def init(request:, context:, current_user:, view_context:, params:)
|
33
41
|
self.error_messages = []
|
34
|
-
self.request = request
|
35
42
|
self.context = context
|
36
43
|
self.current_user = current_user
|
37
|
-
self.root_path = root_path
|
38
|
-
self.view_context = view_context
|
39
44
|
self.params = params
|
45
|
+
self.request = request
|
46
|
+
self.view_context = view_context
|
40
47
|
|
41
48
|
self.license = Licensing::LicenseManager.new(Licensing::HQ.new(request).response).license
|
42
49
|
self.translation_enabled = license.has(:localization)
|
data/lib/avo/base_action.rb
CHANGED
@@ -13,7 +13,6 @@ module Avo
|
|
13
13
|
class_attribute :view
|
14
14
|
class_attribute :user
|
15
15
|
class_attribute :resource
|
16
|
-
class_attribute :invalid_fields
|
17
16
|
class_attribute :standalone, default: false
|
18
17
|
class_attribute :visible
|
19
18
|
class_attribute :may_download_file, default: false
|
@@ -22,7 +21,8 @@ module Avo
|
|
22
21
|
attr_accessor :model
|
23
22
|
attr_accessor :resource
|
24
23
|
attr_accessor :user
|
25
|
-
|
24
|
+
|
25
|
+
delegate :view, to: :class
|
26
26
|
|
27
27
|
class << self
|
28
28
|
def form_data_attributes
|
@@ -68,23 +68,6 @@ module Avo
|
|
68
68
|
self.class.context
|
69
69
|
end
|
70
70
|
|
71
|
-
def get_field_definitions
|
72
|
-
return [] if self.class.fields.blank?
|
73
|
-
|
74
|
-
self.class.fields.map do |field|
|
75
|
-
field.hydrate(action: self)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def get_fields
|
80
|
-
get_field_definitions.map do |field|
|
81
|
-
field.hydrate(action: self, model: @model)
|
82
|
-
end
|
83
|
-
.select do |field|
|
84
|
-
field.visible?
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
71
|
def get_attributes_for_action
|
89
72
|
get_fields.map do |field|
|
90
73
|
[field.id, field.value]
|
data/lib/avo/base_card.rb
CHANGED
@@ -59,13 +59,7 @@ module Avo
|
|
59
59
|
def frame_url(enforced_range: nil, params: {})
|
60
60
|
enforced_range ||= initial_range || ranges.first
|
61
61
|
|
62
|
-
|
63
|
-
begin
|
64
|
-
other_params = "&#{params.permit!.to_h.map { |k, v| "#{k}=#{v}" }.join("&")}"
|
65
|
-
rescue
|
66
|
-
end
|
67
|
-
|
68
|
-
"#{Avo::App.root_path}/dashboards/#{dashboard.id}/cards/#{id}?turbo_frame=#{turbo_frame}&index=#{index}&range=#{enforced_range}#{other_params}"
|
62
|
+
Avo::App.view_context.avo.dashboard_card_path(dashboard.id, id, turbo_frame: turbo_frame, index: index, range: enforced_range, **params.permit!)
|
69
63
|
end
|
70
64
|
|
71
65
|
def card_classes
|