avo 1.21.1.pre.1 → 1.22.1.pre.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +61 -61
  3. data/README.md +1 -1
  4. data/app/assets/builds/avo.css +4 -4
  5. data/app/assets/builds/avo.js +40 -18
  6. data/app/assets/builds/avo.js.map +2 -2
  7. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +14 -7
  8. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +54 -10
  9. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +31 -8
  10. data/app/components/avo/fields/belongs_to_field/edit_component.rb +37 -0
  11. data/app/components/avo/fields/select_field/edit_component.html.erb +3 -3
  12. data/app/components/avo/navigation_link_component.html.erb +1 -1
  13. data/app/components/avo/navigation_link_component.rb +2 -1
  14. data/app/controllers/avo/application_controller.rb +16 -14
  15. data/app/controllers/avo/base_controller.rb +2 -4
  16. data/app/controllers/avo/relations_controller.rb +1 -4
  17. data/app/javascript/avo.js +1 -0
  18. data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +23 -15
  19. data/app/javascript/js/controllers/fields/date_field_controller.js +10 -2
  20. data/app/javascript/js/controllers/item_selector_controller.js +29 -19
  21. data/app/javascript/js/controllers/search_controller.js +8 -3
  22. data/app/views/avo/partials/_logo.html.erb +3 -1
  23. data/app/views/avo/partials/_turbo_frame_wrap.html.erb +7 -1
  24. data/app/views/avo/relations/new.html.erb +1 -1
  25. data/app/views/avo/sidebar/_sidebar.html.erb +1 -3
  26. data/db/factories.rb +9 -5
  27. data/lib/avo/base_resource.rb +16 -14
  28. data/lib/avo/fields/base_field.rb +7 -4
  29. data/lib/avo/fields/belongs_to_field.rb +92 -11
  30. data/lib/avo/fields/key_value_field.rb +28 -8
  31. data/lib/avo/version.rb +1 -1
  32. data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
  33. data/lib/generators/avo/templates/locales/avo.nb-NO.yml +1 -0
  34. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +1 -0
  35. data/lib/generators/avo/templates/locales/avo.ro.yml +1 -0
  36. data/public/avo-assets/avo.css +4 -4
  37. data/public/avo-assets/avo.js +40 -18
  38. data/public/avo-assets/avo.js.map +2 -2
  39. metadata +2 -2
data/db/factories.rb CHANGED
@@ -42,11 +42,11 @@ FactoryBot.define do
42
42
  end
43
43
 
44
44
  factory :comment do
45
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)) }
46
46
  end
47
47
 
48
48
  factory :review do
49
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
49
+ body { Faker::Lorem.paragraphs(number: rand(4...10)) }
50
50
  end
51
51
 
52
52
  factory :person do
@@ -58,11 +58,15 @@ FactoryBot.define do
58
58
  type { "Spouse" }
59
59
  end
60
60
 
61
+ factory :fish do
62
+ name { %w[Tilapia Salmon Trout Catfish Pangasius Carp].sample }
63
+ end
64
+
61
65
  factory :course do
62
- name { Faker::Company.name }
66
+ name { Faker::Educator.unique.course_name }
63
67
  end
64
68
 
65
- factory :link do
66
- name { Faker::Internet.url }
69
+ factory :course_link, class: "Course::Link" do
70
+ link { Faker::Internet.url }
67
71
  end
68
72
  end
@@ -6,7 +6,7 @@ module Avo
6
6
 
7
7
  include ActionView::Helpers::UrlHelper
8
8
 
9
- delegate :view_context, to: 'Avo::App'
9
+ delegate :view_context, to: "Avo::App"
10
10
  delegate :main_app, to: :view_context
11
11
  delegate :avo, to: :view_context
12
12
  delegate :resource_path, to: :view_context
@@ -21,7 +21,7 @@ module Avo
21
21
  class_attribute :title, default: :id
22
22
  class_attribute :description, default: :id
23
23
  class_attribute :search_query, default: nil
24
- class_attribute :search_query_help, default: ''
24
+ class_attribute :search_query_help, default: ""
25
25
  class_attribute :includes, default: []
26
26
  class_attribute :model_class
27
27
  class_attribute :translation_key
@@ -60,7 +60,7 @@ module Avo
60
60
  # This is the search_query scope
61
61
  # This should be removed and passed to the search block
62
62
  def scope
63
- self.query_scope
63
+ query_scope
64
64
  end
65
65
 
66
66
  # This resolves the scope when doing "where" queries (not find queries)
@@ -128,17 +128,19 @@ module Avo
128
128
  end
129
129
  .select do |field|
130
130
  # Strip out the reflection field in index queries with a parent association.
131
- if reflection.present? &&
132
- reflection.options.present? &&
133
- field.respond_to?(:polymorphic_as) &&
134
- field.polymorphic_as.to_s == reflection.options[:as].to_s
135
- next
136
- end
137
- if field.respond_to?(:foreign_key) &&
138
- reflection.present? &&
139
- reflection.respond_to?(:foreign_key) &&
140
- reflection.foreign_key != field.foreign_key
141
- next
131
+ if reflection.present?
132
+ if reflection.options.present? &&
133
+ field.respond_to?(:polymorphic_as) &&
134
+ field.polymorphic_as.to_s == reflection.options[:as].to_s
135
+ next
136
+ end
137
+
138
+ if field.respond_to?(:foreign_key) &&
139
+ reflection.respond_to?(:foreign_key) &&
140
+ reflection.foreign_key != field.foreign_key &&
141
+ @params[:resource_name] == field.resource.model_key
142
+ next
143
+ end
142
144
  end
143
145
 
144
146
  true
@@ -128,11 +128,14 @@ module Avo
128
128
  # Get model value
129
129
  final_value = @model.send(property) if (model_or_class(@model) == "model") && @model.respond_to?(property)
130
130
 
131
+ # On new views and actions modals we need to prefill the fields
131
132
  if (@view === :new) || @action.present?
132
- final_value = if default.present? && default.respond_to?(:call)
133
- default.call
134
- else
135
- default
133
+ if default.present?
134
+ final_value = if default.respond_to?(:call)
135
+ default.call
136
+ else
137
+ default
138
+ end
136
139
  end
137
140
  end
138
141
 
@@ -1,5 +1,62 @@
1
1
  module Avo
2
2
  module Fields
3
+
4
+ # The field can be in multiple scenarios where it needs different types of data and displays the state differently.
5
+ # For example the non-polymorphic, non-searchable variant is the easiest to support. You only need to populate a simple select with the ID of the associated record and the list of records.
6
+ # For the searchable polymorphic variant you need to provide the type of the association (Post, Project, Team), the label of the associated record ("Cool post title") and the ID of that record.
7
+ # Furthermore, the way Avo works, it needs to do some queries on the back-end to fetch the required information.
8
+ #
9
+ # Field scenarios:
10
+ # 1. Create new record
11
+ # List of records
12
+ # 2. Create new record as association
13
+ # List of records, the ID
14
+ # 3. Create new searchable record
15
+ # Nothing really. The records will be fetched from the search API
16
+ # 4. Create new searchable record as association
17
+ # The associated record label and ID. The records will be fetched from the search API
18
+ # 5. Create new polymorphic record
19
+ # Type & ID
20
+ # 6. Create new polymorphic record as association
21
+ # Type, list of records, and ID
22
+ # 7. Create new polymorphic searchable record
23
+ # Type, Label and ID
24
+ # 8. Create new polymorphic searchable record as association
25
+ # Type, Label and ID
26
+ # 9. Edit a record
27
+ # List of records & ID
28
+ # 10. Edit a record as searchable
29
+ # Label and ID
30
+ # 11. Edit a record as an association
31
+ # List and ID
32
+ # 12. Edit a record as an searchable association
33
+ # Label and ID
34
+ # 13. Edit a polymorphic record
35
+ # Type, List of records & ID
36
+ # 14. Edit a polymorphic record as searchable
37
+ # Type, Label and ID
38
+ # 15. Edit a polymorphic record as an association
39
+ # Type, List and ID
40
+ # 16. Edit a polymorphic record as an searchable association
41
+ # Type, Label and ID
42
+ # Also all of the above with a namespaced model `Course/Link`
43
+
44
+ # Variants
45
+ # 1. Select belongs to
46
+ # 2. Searchable belongs to
47
+ # 3. Select Polymorphic belongs to
48
+ # 4. Searchable Polymorphic belongs to
49
+
50
+ # Requirements
51
+ # - list
52
+ # - ID
53
+ # - label
54
+ # - Type
55
+ # - foreign_key
56
+ # - foreign_key for poly type
57
+ # - foreign_key for poly id
58
+ # - is_disabled?
59
+
3
60
  class BelongsToField < BaseField
4
61
  attr_reader :polymorphic_as
5
62
  attr_reader :relation_method
@@ -21,7 +78,13 @@ module Avo
21
78
  end
22
79
 
23
80
  def value
24
- super(polymorphic_as)
81
+ if is_polymorphic?
82
+ # Get the value from the pre-filled assoociation record
83
+ super(polymorphic_as)
84
+ else
85
+ # Get the value from the pre-filled assoociation record
86
+ super(relation_method)
87
+ end
25
88
  end
26
89
 
27
90
  # The value
@@ -39,22 +102,40 @@ module Avo
39
102
  end
40
103
 
41
104
  def options
42
- ::Avo::Services::AuthorizationService.apply_policy(user, target_resource.class.query_scope).all.map do |model|
43
- {
44
- value: model.id,
45
- label: model.send(target_resource.class.title)
46
- }
47
- end
105
+ values_for_type
48
106
  end
49
107
 
50
- def values_for_type(type)
51
- ::Avo::Services::AuthorizationService.apply_policy(user, type).all.map do |model|
52
- [model.send(App.get_resource_by_model_name(type).class.title), model.id]
108
+ def values_for_type(model = nil)
109
+ resource = target_resource
110
+ resource = App.get_resource_by_model_name model if model.present?
111
+
112
+ ::Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope).all.map do |model|
113
+ [model.send(resource.class.title), model.id]
53
114
  end
54
115
  end
55
116
 
56
117
  def database_value
57
118
  target_resource.id
119
+ rescue
120
+ nil
121
+ end
122
+
123
+ def type_input_foreign_key
124
+ if is_polymorphic?
125
+ "#{foreign_key}_type"
126
+ end
127
+ end
128
+
129
+ def id_input_foreign_key
130
+ if is_polymorphic?
131
+ "#{foreign_key}_id"
132
+ else
133
+ foreign_key
134
+ end
135
+ end
136
+
137
+ def is_polymorphic?
138
+ polymorphic_as.present?
58
139
  end
59
140
 
60
141
  def foreign_key
@@ -118,7 +199,7 @@ module Avo
118
199
  end
119
200
 
120
201
  def target_resource
121
- if polymorphic_as.present?
202
+ if is_polymorphic?
122
203
  if value.present?
123
204
  return App.get_resource_by_model_name(value.class)
124
205
  else
@@ -3,10 +3,6 @@ require "json"
3
3
  module Avo
4
4
  module Fields
5
5
  class KeyValueField < BaseField
6
- attr_reader :key_label
7
- attr_reader :value_label
8
- attr_reader :action_text
9
- attr_reader :delete_text
10
6
  attr_reader :disable_editing_keys
11
7
  attr_reader :disable_adding_rows
12
8
 
@@ -15,10 +11,10 @@ module Avo
15
11
 
16
12
  hide_on :index
17
13
 
18
- @key_label = args[:key_label].present? ? args[:key_label].to_s : I18n.translate('avo.key_value_field.key')
19
- @value_label = args[:value_label].present? ? args[:value_label].to_s : I18n.translate('avo.key_value_field.value')
20
- @action_text = args[:action_text].present? ? args[:action_text].to_s : I18n.translate('avo.key_value_field.add_row')
21
- @delete_text = args[:delete_text].present? ? args[:delete_text].to_s : I18n.translate('avo.key_value_field.delete_row')
14
+ @key_label = args[:key_label] if args[:key_label].present?
15
+ @value_label = args[:value_label]if args[:value_label].present?
16
+ @action_text = args[:action_text] if args[:action_text].present?
17
+ @delete_text = args[:delete_text] if args[:delete_text].present?
22
18
 
23
19
  @disable_editing_keys = args[:disable_editing_keys].present? ? args[:disable_editing_keys] : false
24
20
  # disabling editing keys also disables adding rows (doesn't take into account the value of disable_adding_rows)
@@ -32,6 +28,30 @@ module Avo
32
28
  @disable_deleting_rows = args[:disable_deleting_rows].present? ? args[:disable_deleting_rows] : false
33
29
  end
34
30
 
31
+ def key_label
32
+ return @key_label if @key_label.present?
33
+
34
+ I18n.translate('avo.key_value_field.key')
35
+ end
36
+
37
+ def value_label
38
+ return @value_label if @value_label.present?
39
+
40
+ I18n.translate('avo.key_value_field.value')
41
+ end
42
+
43
+ def action_text
44
+ return @action_text if @action_text.present?
45
+
46
+ I18n.translate('avo.key_value_field.add_row')
47
+ end
48
+
49
+ def delete_text
50
+ return @delete_text if @delete_text.present?
51
+
52
+ I18n.translate('avo.key_value_field.delete_row')
53
+ end
54
+
35
55
  def to_permitted_param
36
56
  [:"#{id}", "#{id}": {}]
37
57
  end
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "1.21.1.pre.1"
2
+ VERSION = "1.22.1.pre.2"
3
3
  end
@@ -94,3 +94,4 @@ en:
94
94
  was_successfully_created: 'was successfully created'
95
95
  was_successfully_updated: 'was successfully updated'
96
96
  clear_value: "Clear value"
97
+ tools: Tools
@@ -81,3 +81,4 @@ nb-NO:
81
81
  search_placeholder: 'Søk'
82
82
  search_cancel_button: 'Avbryt'
83
83
  sign_out: 'Logg ut'
84
+ tools: Redskapene
@@ -83,3 +83,4 @@ pt-BR:
83
83
  placeholder: 'Procurar'
84
84
  cancel_button: 'Cancelar'
85
85
  sign_out: 'sair'
86
+ tools: Ferramentas
@@ -78,3 +78,4 @@ ro:
78
78
  delete_row: 'Sterge rand'
79
79
  was_successfully_created: 'a fost creat'
80
80
  was_successfully_updated: 'a fost actualizat'
81
+ tools: Instrumente
@@ -6651,6 +6651,10 @@ progress[value]::-moz-progress-bar{
6651
6651
  height:10rem
6652
6652
  }
6653
6653
 
6654
+ .h-16{
6655
+ height:4rem
6656
+ }
6657
+
6654
6658
  .h-full{
6655
6659
  height:100%
6656
6660
  }
@@ -6659,10 +6663,6 @@ progress[value]::-moz-progress-bar{
6659
6663
  height:3rem
6660
6664
  }
6661
6665
 
6662
- .h-16{
6663
- height:4rem
6664
- }
6665
-
6666
6666
  .h-3{
6667
6667
  height:0.75rem
6668
6668
  }
@@ -81101,18 +81101,18 @@
81101
81101
  const { type } = target.dataset;
81102
81102
  if (this.isSearchable) {
81103
81103
  const textInput = target.querySelector('input[type="text"]');
81104
- if (textInput) {
81105
- textInput.setAttribute("valid-name", textInput.getAttribute("name"));
81106
- }
81104
+ if (textInput)
81105
+ this.nameToValidName(textInput);
81107
81106
  const hiddenInput = target.querySelector('input[type="hidden"]');
81108
- if (hiddenInput) {
81109
- hiddenInput.setAttribute("valid-name", hiddenInput.getAttribute("name"));
81110
- }
81107
+ if (hiddenInput)
81108
+ this.nameToValidName(hiddenInput);
81111
81109
  } else {
81112
81110
  const select = target.querySelector("select");
81113
- if (select) {
81114
- select.setAttribute("valid-name", select.getAttribute("name"));
81115
- }
81111
+ if (select)
81112
+ this.nameToValidName(select);
81113
+ const hiddenInput = target.querySelector('input[type="hidden"]');
81114
+ if (hiddenInput)
81115
+ this.nameToValidName(hiddenInput);
81116
81116
  if (this.selectedType !== type) {
81117
81117
  select.selectedIndex = 0;
81118
81118
  }
@@ -81130,11 +81130,15 @@
81130
81130
  if (this.isSearchable) {
81131
81131
  const textInput = target.querySelector('input[type="text"]');
81132
81132
  const hiddenInput = target.querySelector('input[type="hidden"]');
81133
- textInput.setAttribute("name", textInput.getAttribute("valid-name"));
81134
- hiddenInput.setAttribute("name", hiddenInput.getAttribute("valid-name"));
81133
+ this.validNameToName(textInput);
81134
+ this.validNameToName(hiddenInput);
81135
81135
  } else {
81136
81136
  const select = target.querySelector("select");
81137
- select.setAttribute("name", select.getAttribute("valid-name"));
81137
+ const hiddenInput = target.querySelector('input[type="hidden"]');
81138
+ this.validNameToName(select);
81139
+ if (hiddenInput) {
81140
+ this.validNameToName(hiddenInput);
81141
+ }
81138
81142
  }
81139
81143
  }
81140
81144
  invalidateTarget(target) {
@@ -81147,10 +81151,17 @@
81147
81151
  } else if (target) {
81148
81152
  try {
81149
81153
  target.querySelector("select").setAttribute("name", "");
81154
+ target.querySelector('input[type="hidden"]').setAttribute("name", "");
81150
81155
  } catch (error2) {
81151
81156
  }
81152
81157
  }
81153
81158
  }
81159
+ validNameToName(target) {
81160
+ target.setAttribute("name", target.getAttribute("valid-name"));
81161
+ }
81162
+ nameToValidName(target) {
81163
+ target.setAttribute("valid-name", target.getAttribute("name"));
81164
+ }
81154
81165
  };
81155
81166
  __publicField(belongs_to_field_controller_default, "targets", ["select", "type", "loadAssociationLink"]);
81156
81167
 
@@ -83368,6 +83379,9 @@
83368
83379
  var esm_default = flatpickr;
83369
83380
 
83370
83381
  // app/javascript/js/controllers/fields/date_field_controller.js
83382
+ function universalTimestamp(timestampStr) {
83383
+ return new Date(new Date(timestampStr).getTime() + new Date(timestampStr).getTimezoneOffset() * 60 * 1e3);
83384
+ }
83371
83385
  var date_field_controller_default = class extends Controller {
83372
83386
  connect() {
83373
83387
  const options = {
@@ -83393,7 +83407,7 @@
83393
83407
  options.time_24hr = castBoolean(this.inputTarget.dataset.time24hr);
83394
83408
  options.appTimezone = this.inputTarget.dataset.timezone;
83395
83409
  } else {
83396
- currentValue = new Date(this.inputTarget.value);
83410
+ currentValue = universalTimestamp(this.inputTarget.value);
83397
83411
  }
83398
83412
  options.defaultDate = currentValue;
83399
83413
  esm_default(this.inputTarget, options);
@@ -83443,6 +83457,9 @@
83443
83457
  return [];
83444
83458
  }
83445
83459
  }
83460
+ get actionLinks() {
83461
+ return document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]');
83462
+ }
83446
83463
  set currentIds(value) {
83447
83464
  this.stateHolderElement.dataset.selectedResources = JSON.stringify(value);
83448
83465
  if (this.actionsPanelPresent) {
@@ -83476,7 +83493,7 @@
83476
83493
  }
83477
83494
  }
83478
83495
  enableResourceActions() {
83479
- document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]').forEach((link) => {
83496
+ this.actionLinks.forEach((link) => {
83480
83497
  link.classList.add("text-gray-700");
83481
83498
  link.classList.remove("text-gray-500");
83482
83499
  link.setAttribute("data-href", link.getAttribute("href"));
@@ -83484,7 +83501,7 @@
83484
83501
  });
83485
83502
  }
83486
83503
  disableResourceActions() {
83487
- document.querySelectorAll('.js-actions-dropdown a[data-actions-picker-target="resourceAction"]').forEach((link) => {
83504
+ this.actionLinks.forEach((link) => {
83488
83505
  link.classList.remove("text-gray-700");
83489
83506
  link.classList.add("text-gray-500");
83490
83507
  link.setAttribute("href", link.getAttribute("data-href"));
@@ -87339,7 +87356,9 @@
87339
87356
  this.hiddenIdTarget.setAttribute("value", item._id);
87340
87357
  this.buttonTarget.setAttribute("value", item._label);
87341
87358
  document.querySelector(".aa-DetachedOverlay").remove();
87342
- this.clearButtonTarget.classList.remove("hidden");
87359
+ if (this.hasClearButtonTarget) {
87360
+ this.clearButtonTarget.classList.remove("hidden");
87361
+ }
87343
87362
  } else {
87344
87363
  turbo_es2017_esm_exports.visit(item._url, { action: "advance" });
87345
87364
  }
@@ -87398,7 +87417,7 @@
87398
87417
  const that = this;
87399
87418
  this.buttonTarget.onclick = () => this.showSearchPanel();
87400
87419
  this.clearValueTargets.forEach((target) => {
87401
- if (target.getAttribute("value")) {
87420
+ if (target.getAttribute("value") && this.hasClearButtonTarget) {
87402
87421
  this.clearButtonTarget.classList.remove("hidden");
87403
87422
  }
87404
87423
  });
@@ -87418,7 +87437,9 @@
87418
87437
  return that.debouncedFetch(endpoint).then((response) => response.json()).then((data) => Object.keys(data).map((resourceName) => that.addSource(resourceName, data[resourceName])));
87419
87438
  }
87420
87439
  });
87421
- this.buttonTarget.removeAttribute("disabled");
87440
+ if (this.buttonTarget.dataset.shouldBeDisabled !== "true") {
87441
+ this.buttonTarget.removeAttribute("disabled");
87442
+ }
87422
87443
  }
87423
87444
  };
87424
87445
  __publicField(search_controller_default, "targets", [
@@ -87779,6 +87800,7 @@
87779
87800
  });
87780
87801
  document.addEventListener("turbo:visit", () => document.body.classList.add("turbo-loading"));
87781
87802
  document.addEventListener("turbo:submit-start", () => document.body.classList.add("turbo-loading"));
87803
+ document.addEventListener("turbo:submit-end", () => document.body.classList.remove("turbo-loading"));
87782
87804
  document.addEventListener("turbo:before-cache", () => {
87783
87805
  document.querySelectorAll("[data-turbo-remove-before-cache]").forEach((element) => element.remove());
87784
87806
  });