avo 2.7.0 → 2.9.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.

Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +6 -4
  4. data/README.md +11 -0
  5. data/app/assets/stylesheets/avo.css +4 -4
  6. data/app/assets/stylesheets/css/{components → fields}/code.css +0 -0
  7. data/app/assets/stylesheets/css/{components → fields}/progress.css +0 -0
  8. data/app/assets/stylesheets/css/{components → fields}/status.css +0 -0
  9. data/app/assets/stylesheets/css/fields/trix.css +17 -0
  10. data/app/assets/svgs/download-solid-reversed.svg +2 -2
  11. data/app/components/avo/actions_component.html.erb +5 -13
  12. data/app/components/avo/actions_component.rb +39 -1
  13. data/app/components/avo/alert_component.rb +6 -0
  14. data/app/components/avo/card_component.html.erb +2 -2
  15. data/app/components/avo/common_field_wrapper_component.html.erb +11 -4
  16. data/app/components/avo/common_field_wrapper_component.rb +27 -1
  17. data/app/components/avo/edit/field_wrapper_component.html.erb +1 -1
  18. data/app/components/avo/fields/badge_field/index_component.html.erb +1 -1
  19. data/app/components/avo/fields/badge_field/show_component.html.erb +1 -1
  20. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +21 -10
  21. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +7 -1
  22. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +27 -15
  23. data/app/components/avo/fields/belongs_to_field/edit_component.rb +4 -0
  24. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +1 -1
  25. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +1 -1
  26. data/app/components/avo/fields/boolean_field/edit_component.html.erb +4 -2
  27. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  28. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  29. data/app/components/avo/fields/boolean_group_field/edit_component.html.erb +7 -1
  30. data/app/components/avo/fields/boolean_group_field/index_component.html.erb +1 -1
  31. data/app/components/avo/fields/boolean_group_field/show_component.html.erb +1 -1
  32. data/app/components/avo/fields/code_field/edit_component.html.erb +7 -5
  33. data/app/components/avo/fields/code_field/show_component.html.erb +2 -2
  34. data/app/components/avo/fields/common/key_value_component.html.erb +10 -4
  35. data/app/components/avo/fields/common/key_value_component.rb +2 -0
  36. data/app/components/avo/fields/country_field/edit_component.html.erb +4 -2
  37. data/app/components/avo/fields/country_field/index_component.html.erb +1 -1
  38. data/app/components/avo/fields/country_field/show_component.html.erb +1 -1
  39. data/app/components/avo/fields/date_field/edit_component.html.erb +6 -4
  40. data/app/components/avo/fields/date_field/index_component.html.erb +1 -1
  41. data/app/components/avo/fields/date_field/show_component.html.erb +1 -1
  42. data/app/components/avo/fields/date_time_field/edit_component.html.erb +6 -4
  43. data/app/components/avo/fields/date_time_field/index_component.html.erb +1 -1
  44. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -1
  45. data/app/components/avo/fields/edit_component.rb +7 -0
  46. data/app/components/avo/fields/external_image_field/edit_component.html.erb +5 -2
  47. data/app/components/avo/fields/external_image_field/index_component.html.erb +6 -4
  48. data/app/components/avo/fields/external_image_field/show_component.html.erb +1 -1
  49. data/app/components/avo/fields/file_field/edit_component.html.erb +6 -1
  50. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  51. data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
  52. data/app/components/avo/fields/files_field/edit_component.html.erb +7 -1
  53. data/app/components/avo/fields/files_field/index_component.html.erb +1 -1
  54. data/app/components/avo/fields/files_field/show_component.html.erb +1 -1
  55. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  56. data/app/components/avo/fields/gravatar_field/show_component.html.erb +1 -1
  57. data/app/components/avo/fields/has_one_field/index_component.html.erb +1 -1
  58. data/app/components/avo/fields/hidden_field/edit_component.html.erb +5 -1
  59. data/app/components/avo/fields/id_field/edit_component.html.erb +1 -1
  60. data/app/components/avo/fields/id_field/index_component.html.erb +1 -1
  61. data/app/components/avo/fields/id_field/show_component.html.erb +1 -1
  62. data/app/components/avo/fields/index_component.rb +3 -0
  63. data/app/components/avo/fields/key_value_field/edit_component.html.erb +1 -1
  64. data/app/components/avo/fields/key_value_field/show_component.html.erb +1 -1
  65. data/app/components/avo/fields/markdown_field/edit_component.html.erb +8 -5
  66. data/app/components/avo/fields/markdown_field/show_component.html.erb +1 -1
  67. data/app/components/avo/fields/number_field/edit_component.html.erb +7 -4
  68. data/app/components/avo/fields/number_field/index_component.html.erb +1 -1
  69. data/app/components/avo/fields/number_field/show_component.html.erb +1 -1
  70. data/app/components/avo/fields/password_field/edit_component.html.erb +4 -2
  71. data/app/components/avo/fields/progress_bar_field/edit_component.html.erb +7 -4
  72. data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -1
  73. data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -1
  74. data/app/components/avo/fields/select_field/edit_component.html.erb +9 -3
  75. data/app/components/avo/fields/select_field/index_component.html.erb +1 -1
  76. data/app/components/avo/fields/select_field/show_component.html.erb +1 -1
  77. data/app/components/avo/fields/show_component.rb +3 -0
  78. data/app/components/avo/fields/status_field/edit_component.html.erb +6 -3
  79. data/app/components/avo/fields/status_field/index_component.html.erb +1 -1
  80. data/app/components/avo/fields/status_field/show_component.html.erb +1 -1
  81. data/app/components/avo/fields/tags_field/edit_component.html.erb +19 -11
  82. data/app/components/avo/fields/tags_field/index_component.html.erb +1 -1
  83. data/app/components/avo/fields/tags_field/show_component.html.erb +1 -1
  84. data/app/components/avo/fields/text_field/edit_component.html.erb +5 -2
  85. data/app/components/avo/fields/text_field/index_component.html.erb +1 -1
  86. data/app/components/avo/fields/text_field/show_component.html.erb +1 -1
  87. data/app/components/avo/fields/textarea_field/edit_component.html.erb +6 -3
  88. data/app/components/avo/fields/textarea_field/show_component.html.erb +1 -1
  89. data/app/components/avo/fields/trix_field/edit_component.html.erb +14 -4
  90. data/app/components/avo/fields/trix_field/edit_component.rb +3 -0
  91. data/app/components/avo/fields/trix_field/show_component.html.erb +2 -2
  92. data/app/components/avo/index/field_wrapper_component.html.erb +12 -5
  93. data/app/components/avo/index/field_wrapper_component.rb +27 -3
  94. data/app/components/avo/panel_component.rb +4 -3
  95. data/app/components/avo/resource_component.rb +1 -0
  96. data/app/components/avo/show/field_wrapper_component.html.erb +1 -1
  97. data/app/components/avo/show/field_wrapper_component.rb +2 -1
  98. data/app/components/avo/sidebar/item_switcher_component.html.erb +2 -2
  99. data/app/components/avo/views/resource_edit_component.html.erb +13 -8
  100. data/app/components/avo/views/resource_edit_component.rb +32 -3
  101. data/app/components/avo/views/resource_index_component.html.erb +7 -4
  102. data/app/components/avo/views/resource_index_component.rb +7 -1
  103. data/app/components/avo/views/resource_show_component.html.erb +11 -9
  104. data/app/components/avo/views/resource_show_component.rb +1 -0
  105. data/app/controllers/avo/actions_controller.rb +4 -1
  106. data/app/controllers/avo/base_controller.rb +24 -13
  107. data/app/controllers/avo/cards_controller.rb +25 -0
  108. data/app/controllers/avo/dashboards_controller.rb +2 -8
  109. data/app/controllers/avo/home_controller.rb +8 -1
  110. data/app/controllers/avo/search_controller.rb +7 -1
  111. data/app/helpers/avo/url_helpers.rb +8 -9
  112. data/app/javascript/js/controllers/fields/code_field_controller.js +7 -1
  113. data/app/javascript/js/controllers/fields/key_value_controller.js +1 -0
  114. data/app/javascript/js/controllers/fields/tags_field_controller.js +0 -1
  115. data/app/javascript/js/controllers/menu_controller.js +4 -3
  116. data/app/javascript/js/controllers/resource_edit_controller.js +72 -0
  117. data/app/javascript/js/controllers/resource_index_controller.js +4 -0
  118. data/app/javascript/js/controllers/resource_show_controller.js +4 -0
  119. data/app/javascript/js/controllers/search_controller.js +28 -5
  120. data/app/javascript/js/controllers.js +8 -0
  121. data/app/views/avo/associations/new.html.erb +2 -1
  122. data/app/views/avo/base/_select_filter.html.erb +1 -1
  123. data/app/views/avo/base/_text_filter.html.erb +1 -0
  124. data/app/views/avo/base/edit.html.erb +2 -1
  125. data/app/views/avo/base/new.html.erb +1 -1
  126. data/app/views/avo/{dashboards → cards}/_chartkick_card.html.erb +0 -0
  127. data/app/views/avo/{dashboards → cards}/_metric_card.html.erb +0 -0
  128. data/app/views/avo/{dashboards/card.html.erb → cards/show.html.erb} +0 -0
  129. data/app/views/avo/partials/_custom_tools_alert.html.erb +21 -7
  130. data/app/views/avo/partials/_logo.html.erb +3 -2
  131. data/app/views/avo/partials/_navbar.html.erb +1 -1
  132. data/app/views/avo/partials/_table_header.html.erb +9 -1
  133. data/bin/test +1 -0
  134. data/config/routes.rb +7 -4
  135. data/db/factories.rb +1 -0
  136. data/lib/avo/app.rb +18 -1
  137. data/lib/avo/base_action.rb +16 -4
  138. data/lib/avo/base_card.rb +0 -23
  139. data/lib/avo/base_resource.rb +23 -16
  140. data/lib/avo/concerns/fetches_things.rb +19 -12
  141. data/lib/avo/concerns/has_fields.rb +93 -0
  142. data/lib/avo/concerns/has_html_attributes.rb +110 -0
  143. data/lib/avo/concerns/has_model.rb +11 -0
  144. data/lib/avo/concerns/has_stimulus_controllers.rb +42 -0
  145. data/lib/avo/dynamic_router.rb +1 -1
  146. data/lib/avo/engine.rb +1 -3
  147. data/lib/avo/fields/base_field.rb +24 -13
  148. data/lib/avo/fields/concerns/is_required.rb +17 -0
  149. data/lib/avo/fields/select_field.rb +1 -1
  150. data/lib/avo/grid_collector.rb +4 -4
  151. data/lib/avo/hosts/view_record_host.rb +7 -0
  152. data/lib/avo/html/builder.rb +117 -0
  153. data/lib/avo/licensing/pro_license.rb +1 -0
  154. data/lib/avo/menu/base_item.rb +4 -0
  155. data/lib/avo/menu/dashboard.rb +5 -0
  156. data/lib/avo/menu/resource.rb +5 -0
  157. data/lib/avo/version.rb +1 -1
  158. data/lib/avo.rb +5 -0
  159. data/lib/generators/avo/install_generator.rb +1 -4
  160. data/lib/generators/avo/templates/cards/chartkick_card_sample.tt +11 -1
  161. data/lib/generators/avo/templates/cards/metric_card_sample.tt +11 -1
  162. data/lib/generators/avo/templates/field/components/edit_component.html.erb.tt +1 -1
  163. data/lib/generators/avo/templates/field/components/index_component.html.erb.tt +1 -1
  164. data/lib/generators/avo/templates/field/components/show_component.html.erb.tt +1 -1
  165. data/lib/generators/avo/templates/initializer/avo.tt +1 -1
  166. data/lib/generators/avo/templates/locales/avo.en.yml +3 -3
  167. data/public/avo-assets/avo.css +473 -1055
  168. data/public/avo-assets/avo.js +147 -147
  169. data/public/avo-assets/avo.js.map +3 -3
  170. data/public/avo-assets/logomark.png +0 -0
  171. metadata +21 -11
  172. data/app/components/avo/views/resource_new_component.html.erb +0 -55
  173. data/app/components/avo/views/resource_new_component.rb +0 -38
  174. data/lib/avo/fields_collector.rb +0 -70
@@ -40,7 +40,6 @@ export default class extends BaseController {
40
40
  blacklist: this.disallowedItems,
41
41
  enforceWhitelist: this.enforceSuggestions,
42
42
  delimiters: this.delimiters.join('|'),
43
- maxTags: 10,
44
43
  dropdown: {
45
44
  maxItems: 20,
46
45
  enabled: 0,
@@ -22,10 +22,11 @@ export default class extends Controller {
22
22
  }
23
23
 
24
24
  get initiallyCollapsed() {
25
- if (this.userState === 'collapsed' || this.defaultState === 'collapsed') return true
26
- if (this.userState === 'expanded' || this.defaultState === 'expanded') return false
25
+ if (this.defaultState === 'collapsed') {
26
+ return this.userState === 'collapsed'
27
+ }
27
28
 
28
- return false
29
+ return this.userState === 'collapsed'
29
30
  }
30
31
 
31
32
  connect() {
@@ -0,0 +1,72 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import camelCase from 'lodash/camelCase'
3
+
4
+ export default class extends Controller {
5
+ static targets = []
6
+
7
+ static values = {
8
+ view: String,
9
+ }
10
+
11
+ debugOnInput(e) {
12
+ // eslint-disable-next-line no-console
13
+ console.log('onInput', e, e.target.value)
14
+ }
15
+
16
+ toggle({ params }) {
17
+ const { toggleTarget, toggleTargets } = params
18
+
19
+ if (toggleTarget) {
20
+ this.toggleAvoTarget(toggleTarget)
21
+ }
22
+
23
+ if (toggleTargets && toggleTargets.length > 0) {
24
+ toggleTargets.forEach(this.toggleAvoTarget.bind(this))
25
+ }
26
+ }
27
+
28
+ disable({ params }) {
29
+ const { disableTarget, disableTargets } = params
30
+
31
+ if (disableTarget) {
32
+ this.disableAvoTarget(disableTarget)
33
+ }
34
+
35
+ if (disableTargets && disableTargets.length > 0) {
36
+ disableTargets.forEach(this.disableAvoTarget.bind(this))
37
+ }
38
+ }
39
+
40
+ // Private
41
+
42
+ toggleAvoTarget(targetName) {
43
+ // compose the default wrapper data value
44
+ const target = camelCase(targetName)
45
+ const element = document.querySelector(`[data-resource-edit-target="${target}"]`)
46
+
47
+ if (element) {
48
+ element.classList.toggle('hidden')
49
+ }
50
+ }
51
+
52
+ disableAvoTarget(targetName) {
53
+ // compose the default wrapper data value
54
+ const target = camelCase(targetName)
55
+
56
+ // find & disable direct selector
57
+ document.querySelectorAll(`[data-resource-edit-target="${target}"]`).forEach(this.toggleItemDisabled)
58
+
59
+ // find & disable inputs
60
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] input`).forEach(this.toggleItemDisabled)
61
+
62
+ // find & disable select fields
63
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] select`).forEach(this.toggleItemDisabled)
64
+
65
+ // find & disable buttons for belongs_to
66
+ document.querySelectorAll(`[data-resource-edit-target="${target}"] [data-slot="value"] button`).forEach(this.toggleItemDisabled)
67
+ }
68
+
69
+ toggleItemDisabled(item) {
70
+ item.disabled = !item.disabled
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ }
@@ -0,0 +1,4 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ }
@@ -55,7 +55,10 @@ export default class extends Controller {
55
55
  searchUrl(query) {
56
56
  const url = URI()
57
57
 
58
- let params = { q: query }
58
+ return url.segment(this.searchSegments()).search(this.searchParams(query)).toString()
59
+ }
60
+
61
+ searchSegments() {
59
62
  let segments = [
60
63
  window.Avo.configuration.root_path,
61
64
  'avo_api',
@@ -67,6 +70,19 @@ export default class extends Controller {
67
70
  segments = [window.Avo.configuration.root_path, 'avo_api', 'search']
68
71
  }
69
72
 
73
+ return segments
74
+ }
75
+
76
+ searchParams(query) {
77
+ let params = {
78
+ q: query,
79
+ global: false,
80
+ }
81
+
82
+ if (this.isGlobalSearch) {
83
+ params.global = true
84
+ }
85
+
70
86
  if (this.isBelongsToSearch) {
71
87
  params = {
72
88
  ...params,
@@ -85,13 +101,13 @@ export default class extends Controller {
85
101
  }
86
102
  }
87
103
 
88
- return url.segment(segments).search(params).toString()
104
+ return params
89
105
  }
90
106
 
91
107
  handleOnSelect({ item }) {
92
108
  if (this.isBelongsToSearch) {
93
- this.hiddenIdTarget.setAttribute('value', item._id)
94
- this.buttonTarget.setAttribute('value', item._label)
109
+ this.updateFieldAttribute(this.hiddenIdTarget, 'value', item._id)
110
+ this.updateFieldAttribute(this.buttonTarget, 'value', item._label)
95
111
 
96
112
  document.querySelector('.aa-DetachedOverlay').remove()
97
113
 
@@ -180,7 +196,7 @@ export default class extends Controller {
180
196
  }
181
197
 
182
198
  clearValue() {
183
- this.clearValueTargets.map((e) => e.setAttribute('value', ''))
199
+ this.clearValueTargets.map((t) => this.updateFieldAttribute(t, 'value', ''))
184
200
  this.clearButtonTarget.classList.add('hidden')
185
201
  }
186
202
 
@@ -229,4 +245,11 @@ export default class extends Controller {
229
245
  this.buttonTarget.removeAttribute('disabled')
230
246
  }
231
247
  }
248
+
249
+ // Private
250
+
251
+ updateFieldAttribute(target, attribute, value) {
252
+ target.setAttribute(attribute, value)
253
+ target.dispatchEvent(new Event('input'))
254
+ }
232
255
  }
@@ -20,6 +20,9 @@ import MobileController from './controllers/mobile_controller'
20
20
  import ModalController from './controllers/modal_controller'
21
21
  import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
22
22
  import PerPageController from './controllers/per_page_controller'
23
+ import ResourceEditController from './controllers/resource_edit_controller'
24
+ import ResourceIndexController from './controllers/resource_index_controller'
25
+ import ResourceShowController from './controllers/resource_show_controller'
23
26
  import SearchController from './controllers/search_controller'
24
27
  import SelectController from './controllers/select_controller'
25
28
  import SelectFilterController from './controllers/select_filter_controller'
@@ -46,6 +49,9 @@ application.register('mobile', MobileController)
46
49
  application.register('modal', ModalController)
47
50
  application.register('multiple-select-filter', MultipleSelectFilterController)
48
51
  application.register('per-page', PerPageController)
52
+ application.register('resource-edit', ResourceEditController)
53
+ application.register('resource-index', ResourceIndexController)
54
+ application.register('resource-show', ResourceShowController)
49
55
  application.register('search', SearchController)
50
56
  application.register('select', SelectController)
51
57
  application.register('select-filter', SelectFilterController)
@@ -61,3 +67,5 @@ application.register('date-field', DateFieldController)
61
67
  application.register('key-value', KeyValueController)
62
68
  application.register('simple-mde', SimpleMdeController)
63
69
  application.register('trix-field', TrixFieldController)
70
+
71
+ // Custom controllers
@@ -16,7 +16,8 @@
16
16
  field: @field,
17
17
  model_key: @field.target_resource&.model_key,
18
18
  foreign_key: 'related_id',
19
- resource: @resource
19
+ resource: @resource,
20
+ view: :new
20
21
  %>
21
22
  <% else %>
22
23
  <div class="flex-1 flex flex-col items-center justify-center px-0 md:px-24 text-base">
@@ -6,7 +6,7 @@
6
6
  <%= filter_wrapper name: filter.name do %>
7
7
  <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
8
8
  class: input_classes('w-full mb-0'),
9
- include_blank: '—',
9
+ include_blank: filter.class.empty_message || '—',
10
10
  id: "avo_filters_#{filter.id.parameterize.underscore}",
11
11
  'data-filter-class': filter.class,
12
12
  'data-select-filter-target': 'selector',
@@ -7,6 +7,7 @@
7
7
  <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
8
8
  class: input_classes('w-full mb-0'),
9
9
  id: "avo_filters_#{filter.id.parameterize.underscore}",
10
+ placeholder: filter.class.empty_message,
10
11
  'data-filter-class': filter.class.to_s,
11
12
  'data-text-filter-target': 'text',
12
13
  'data-action': 'keypress->text-filter#tryToSubmit'
@@ -1 +1,2 @@
1
- <%= render Avo::Views::ResourceEditComponent.new(resource: @resource) %>
1
+ <%= render Avo::Views::ResourceEditComponent.new(resource: @resource, view: @view, actions: @actions) %>
2
+
@@ -1 +1 @@
1
- <%= render Avo::Views::ResourceNewComponent.new(resource: @resource, model: @model) %>
1
+ <%= render Avo::Views::ResourceEditComponent.new(resource: @resource, model: @model, view: @view, actions: @actions) %>
@@ -5,13 +5,27 @@
5
5
  </a>
6
6
  </div>
7
7
  <% end %>
8
-
9
8
  <% if Avo::App.error_messages.present? %>
10
- <% Avo::App.error_messages.each do |message| %>
11
- <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
12
- <a href="https://avohq.io/pricing" target="_blank" class="rounded bg-orange-700 text-white py-2 px-4 text-sm block items-center flex leading-tight">
13
- <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %> <%= message %>
14
- </a>
15
- </div>
9
+ <% Avo::App.error_messages.each do |error| %>
10
+ <% if error.is_a? Hash %>
11
+ <%
12
+ url, message, target = error.values_at :url, :message, :target
13
+ %>
14
+ <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
15
+ <a href="<%= url %>" target="<%= target %>" class="rounded bg-orange-700 text-white py-2 px-4 text-sm items-center flex leading-tight space-x-2">
16
+ <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %>
17
+ <div>
18
+ <%= simple_format message %>
19
+ </div>
20
+ </a>
21
+ </div>
22
+ <% elsif error.is_a? String %>
23
+ <div class="w-full inset-auto bottom-0 z-50 mb-4 opacity-75 hover:opacity-100 transition-opacity duration-150">
24
+ <div class="rounded bg-orange-700 text-white py-2 px-4 text-sm items-center flex leading-tight space-x-2">
25
+ <%= svg "exclamation", class: "h-6 inline mr-2 text-bold flex-shrink-0 mr-1" %>
26
+ <div><%= simple_format error %></div>
27
+ </div>
28
+ </div>
29
+ <% end %>
16
30
  <% end %>
17
31
  <% end %>
@@ -1,3 +1,4 @@
1
- <%= link_to root_path, class: 'logo-placeholder h-10 bg-white flex justify-start' do %>
2
- <%= image_tag '/avo-assets/logo.png', class: 'h-full', title: 'Avo' %>
1
+ <%= link_to root_path, class: 'logo-placeholder h-full w-full flex justify-start' do %>
2
+ <%= image_tag '/avo-assets/logo.png', class: 'hidden sm:block object-contain', title: 'Avo' %>
3
+ <%= image_tag '/avo-assets/logomark.png', class: 'sm:hidden object-contain', title: 'Avo' %>
3
4
  <% end %>
@@ -2,7 +2,7 @@
2
2
  class="fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
3
  v-if="layout !== 'blank'"
4
4
  >
5
- <div class="flex items-center space-x-2 lg:space-x-0 w-32 sm:w-64 flex-shrink-0">
5
+ <div class="flex items-center space-x-2 lg:space-x-0 w-auto lg:w-64 flex-shrink-0 h-full">
6
6
  <%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->mobile#toggleSidebar' } %>
7
7
  <%= render partial: "avo/partials/logo" %>
8
8
  </div>
@@ -30,6 +30,14 @@
30
30
  sort_direction = 'desc'
31
31
  end
32
32
  classes = "text-gray-500 tracking-tight leading-tight text-xs font-semibold"
33
+ classes += case field.index_text_align.to_sym
34
+ when :right
35
+ " text-right"
36
+ when :center
37
+ " text-center"
38
+ else
39
+ ""
40
+ end
33
41
  %>
34
42
  <th class="text-left uppercase px-3 py-2 whitespace-nowrap rounded-l">
35
43
  <% if field.sortable %>
@@ -38,7 +46,7 @@
38
46
  <%= render partial: 'avo/partials/sortable_component', locals: {field: field} %>
39
47
  <% end %>
40
48
  <% else %>
41
- <div class="flex items-center <%= classes %>">
49
+ <div class="block w-full <%= classes %>">
42
50
  <%= field.name %>
43
51
  </div>
44
52
  <% end %>
data/bin/test CHANGED
@@ -12,6 +12,7 @@ fi;
12
12
  # Run system tests
13
13
  if [[ -z "$1" ]] || [[ "$1" == "system" ]]; then
14
14
  yarn build:js
15
+ yarn build:custom-js
15
16
  yarn build:css
16
17
  bundle exec rspec ./spec --tag type:system
17
18
  fi;
data/config/routes.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  Avo::Engine.routes.draw do
2
2
  root "home#index"
3
3
 
4
- get "resources", to: redirect("/admin")
4
+ get "resources", to: redirect(Avo.configuration.root_path)
5
+ get "dashboards", to: redirect(Avo.configuration.root_path)
6
+
5
7
  post "/rails/active_storage/direct_uploads", to: "/active_storage/direct_uploads#create"
6
8
 
7
- get "/dashboards/:dashboard_id", to: "dashboards#show"
8
- get "/dashboards/:dashboard_id/cards/:card_id", to: "dashboards#card"
9
+ resources :dashboards do
10
+ resources :cards
11
+ end
9
12
 
10
13
  scope "avo_api", as: "avo_api" do
11
14
  get "/search", to: "search#index"
@@ -45,7 +48,7 @@ Avo::Engine.routes.draw do
45
48
  post "/debug/refresh_license", to: "debug#refresh_license"
46
49
  end
47
50
 
48
- if Rails.env.development? or Rails.env.staging?
51
+ if Rails.env.development? || Rails.env.staging?
49
52
  scope "/avo_private", as: "avo_private" do
50
53
  get "/design", to: "private#design"
51
54
  end
data/db/factories.rb CHANGED
@@ -72,5 +72,6 @@ FactoryBot.define do
72
72
 
73
73
  factory :course_link, class: "Course::Link" do
74
74
  link { Faker::Internet.url }
75
+ course { create :course }
75
76
  end
76
77
  end
data/lib/avo/app.rb CHANGED
@@ -14,7 +14,7 @@ module Avo
14
14
  class_attribute :view_context, default: nil
15
15
  class_attribute :params, default: {}
16
16
  class_attribute :translation_enabled, default: false
17
- class_attribute :error_messages, default: []
17
+ class_attribute :error_messages
18
18
 
19
19
  class << self
20
20
  def boot
@@ -52,6 +52,7 @@ module Avo
52
52
  Rails.logger.debug "[Avo] Failed to set ActiveStorage::Current.url_options, #{exception.inspect}"
53
53
  end
54
54
 
55
+ check_bad_resources
55
56
  init_resources
56
57
  init_dashboards if license.has_with_trial(:dashboards)
57
58
  end
@@ -80,6 +81,22 @@ module Avo
80
81
  )
81
82
  end
82
83
 
84
+ def check_bad_resources
85
+ resources.each do |resource|
86
+ has_model = resource.model_class.present?
87
+
88
+ unless has_model
89
+ possible_model = resource.class.to_s.gsub 'Resource', ''
90
+
91
+ Avo::App.error_messages.push({
92
+ url: "https://docs.avohq.io/2.0/resources.html#custom-model-class",
93
+ target: "_blank",
94
+ message: "#{resource.class.to_s} does not have a valid model assigned. It failed to find the #{possible_model} model. \n\r Please create that model or assign one using self.model_class = YOUR_MODEL"
95
+ })
96
+ end
97
+ end
98
+ end
99
+
83
100
  def init_resources
84
101
  self.resources = BaseResource.descendants
85
102
  .select do |resource|
@@ -1,8 +1,9 @@
1
1
  module Avo
2
2
  class BaseAction
3
- extend FieldsCollector
4
3
  extend HasContext
5
4
 
5
+ include Avo::Concerns::HasFields
6
+
6
7
  class_attribute :name, default: nil
7
8
  class_attribute :message
8
9
  class_attribute :confirm_button_label
@@ -12,7 +13,6 @@ module Avo
12
13
  class_attribute :view
13
14
  class_attribute :user
14
15
  class_attribute :resource
15
- class_attribute :fields
16
16
  class_attribute :invalid_fields
17
17
  class_attribute :standalone, default: false
18
18
  class_attribute :visible
@@ -120,9 +120,14 @@ module Avo
120
120
  end
121
121
 
122
122
  def visible_in_view
123
- return true unless visible.present?
123
+ # Run the visible block if available
124
+ return instance_exec(resource: self.class.resource, view: view, &visible) if visible.present?
125
+
126
+ # Hide on the :new view by default
127
+ return false if view == :new
124
128
 
125
- instance_exec(resource: self.class.resource, view: view, &visible)
129
+ # Show on all other views
130
+ true
126
131
  end
127
132
 
128
133
  def param_id
@@ -153,6 +158,13 @@ module Avo
153
158
  self
154
159
  end
155
160
 
161
+ # Add a placeholder silent message from when a user wants to do a redirect action or something similar
162
+ def silent
163
+ add_message nil, :silent
164
+
165
+ self
166
+ end
167
+
156
168
  def redirect_to(path = nil, &block)
157
169
  response[:type] = :redirect
158
170
  response[:path] = if block.present?
data/lib/avo/base_card.rb CHANGED
@@ -52,29 +52,6 @@ module Avo
52
52
  @refresh_every || self.class.refresh_every
53
53
  end
54
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
55
  def turbo_frame
79
56
  "#{dashboard.id}_#{id}"
80
57
  end
@@ -1,13 +1,16 @@
1
1
  module Avo
2
2
  class BaseResource
3
3
  extend ActiveSupport::DescendantsTracker
4
- extend FieldsCollector
5
4
  extend HasContext
6
5
 
7
6
  include ActionView::Helpers::UrlHelper
8
7
  include Avo::Concerns::HasTools
8
+ include Avo::Concerns::HasModel
9
+ include Avo::Concerns::HasFields
10
+ include Avo::Concerns::HasStimulusControllers
9
11
 
10
- delegate :view_context, to: "Avo::App"
12
+ delegate :view_context, to: ::Avo::App
13
+ delegate :simple_format, :content_tag, to: :view_context
11
14
  delegate :main_app, to: :view_context
12
15
  delegate :avo, to: :view_context
13
16
  delegate :resource_path, to: :view_context
@@ -28,12 +31,10 @@ module Avo
28
31
  class_attribute :includes, default: []
29
32
  class_attribute :model_class
30
33
  class_attribute :translation_key
31
- class_attribute :translation_enabled, default: false
32
34
  class_attribute :default_view_type, default: :table
33
35
  class_attribute :devise_password_optional, default: false
34
36
  class_attribute :actions_loader
35
37
  class_attribute :filters_loader
36
- class_attribute :fields
37
38
  class_attribute :grid_loader
38
39
  class_attribute :visible_on_sidebar, default: true
39
40
  class_attribute :unscoped_queries_on_index, default: false
@@ -106,8 +107,6 @@ module Avo
106
107
  self.class.model_class = model_class.base_class
107
108
  end
108
109
  end
109
-
110
- self.class.translation_enabled = ::Avo::App.translation_enabled
111
110
  end
112
111
 
113
112
  def hydrate(model: nil, view: nil, user: nil, params: nil)
@@ -128,7 +127,7 @@ module Avo
128
127
  return [] if self.class.fields.blank?
129
128
 
130
129
  fields = self.class.fields.map do |field|
131
- field.hydrate(resource: self, panel_name: default_panel_name, user: user, translation_enabled: translation_enabled)
130
+ field.hydrate(resource: self, panel_name: default_panel_name, user: user)
132
131
  end
133
132
 
134
133
  if Avo::App.license.lacks_with_trial(:custom_fields)
@@ -139,7 +138,7 @@ module Avo
139
138
 
140
139
  if Avo::App.license.lacks_with_trial(:advanced_fields)
141
140
  fields = fields.reject do |field|
142
- field.type == 'tags'
141
+ field.type == "tags"
143
142
  end
144
143
  end
145
144
 
@@ -288,7 +287,7 @@ module Avo
288
287
  end
289
288
 
290
289
  def translation_key
291
- return "avo.resource_translations.#{class_name_without_resource.underscore}" if self.class.translation_enabled
290
+ return "avo.resource_translations.#{class_name_without_resource.underscore}" if ::Avo::App.translation_enabled
292
291
 
293
292
  self.class.translation_key
294
293
  end
@@ -298,9 +297,11 @@ module Avo
298
297
 
299
298
  return @name if @name.present?
300
299
 
301
- return t(translation_key, count: 1, default: default).capitalize if translation_key
302
-
303
- default
300
+ if translation_key && ::Avo::App.translation_enabled
301
+ t(translation_key, count: 1, default: default).capitalize
302
+ else
303
+ default
304
+ end
304
305
  end
305
306
 
306
307
  def singular_name
@@ -310,9 +311,11 @@ module Avo
310
311
  def plural_name
311
312
  default = name.pluralize
312
313
 
313
- return t(translation_key, count: 2, default: default).capitalize if translation_key
314
-
315
- default
314
+ if translation_key && ::Avo::App.translation_enabled
315
+ t(translation_key, count: 2, default: default).capitalize
316
+ else
317
+ default
318
+ end
316
319
  end
317
320
 
318
321
  def underscore_name
@@ -433,7 +436,11 @@ module Avo
433
436
  end
434
437
 
435
438
  def route_key
436
- model_class.model_name.route_key
439
+ class_name_without_resource.underscore.pluralize
440
+ end
441
+
442
+ def singular_route_key
443
+ route_key.singularize
437
444
  end
438
445
 
439
446
  # This is used as the model class ID