flexi_admin 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/Rakefile +7 -0
  4. data/lib/flexi_admin/components/actions/checkbox_component.html.slim +8 -0
  5. data/lib/flexi_admin/components/actions/checkbox_component.rb +13 -0
  6. data/lib/flexi_admin/components/actions/select_component.html.slim +11 -0
  7. data/lib/flexi_admin/components/actions/select_component.rb +17 -0
  8. data/lib/flexi_admin/components/base_component.rb +19 -0
  9. data/lib/flexi_admin/components/form/field_component.rb +11 -0
  10. data/lib/flexi_admin/components/form/label_component.html.slim +2 -0
  11. data/lib/flexi_admin/components/form/label_component.rb +13 -0
  12. data/lib/flexi_admin/components/form/rows_component.rb +9 -0
  13. data/lib/flexi_admin/components/form/text_input_component.html.slim +5 -0
  14. data/lib/flexi_admin/components/form/text_input_component.rb +17 -0
  15. data/lib/flexi_admin/components/helpers/action_button_helper.rb +28 -0
  16. data/lib/flexi_admin/components/helpers/action_helper.rb +12 -0
  17. data/lib/flexi_admin/components/helpers/icon_helper.rb +11 -0
  18. data/lib/flexi_admin/components/helpers/link_helper.rb +8 -0
  19. data/lib/flexi_admin/components/helpers/resource_helper.rb +71 -0
  20. data/lib/flexi_admin/components/helpers/selectable.rb +11 -0
  21. data/lib/flexi_admin/components/helpers/url_helper.rb +8 -0
  22. data/lib/flexi_admin/components/helpers/value_formatter.rb +32 -0
  23. data/lib/flexi_admin/components/nav/floating_toc_component.html.slim +2 -0
  24. data/lib/flexi_admin/components/nav/floating_toc_component.rb +6 -0
  25. data/lib/flexi_admin/components/resource/autocomplete_component.html.slim +30 -0
  26. data/lib/flexi_admin/components/resource/autocomplete_component.rb +83 -0
  27. data/lib/flexi_admin/components/resource/button_select_component.html.slim +13 -0
  28. data/lib/flexi_admin/components/resource/button_select_component.rb +19 -0
  29. data/lib/flexi_admin/components/resource/form_component.rb +18 -0
  30. data/lib/flexi_admin/components/resource/form_element_component.html.slim +3 -0
  31. data/lib/flexi_admin/components/resource/form_element_component.rb +31 -0
  32. data/lib/flexi_admin/components/resource/form_mixin.rb +287 -0
  33. data/lib/flexi_admin/components/resource/link_action_component.html.slim +8 -0
  34. data/lib/flexi_admin/components/resource/link_action_component.rb +16 -0
  35. data/lib/flexi_admin/components/resource/show_page_component.rb +21 -0
  36. data/lib/flexi_admin/components/resource/view_component.html.slim +26 -0
  37. data/lib/flexi_admin/components/resource/view_component.rb +22 -0
  38. data/lib/flexi_admin/components/resources/bulk_action/button_component.html.slim +7 -0
  39. data/lib/flexi_admin/components/resources/bulk_action/button_component.rb +29 -0
  40. data/lib/flexi_admin/components/resources/bulk_action/modal_component.html.slim +32 -0
  41. data/lib/flexi_admin/components/resources/bulk_action/modal_component.rb +54 -0
  42. data/lib/flexi_admin/components/resources/grid_view/card_component.html.slim +18 -0
  43. data/lib/flexi_admin/components/resources/grid_view/card_component.rb +65 -0
  44. data/lib/flexi_admin/components/resources/grid_view/grid_component.html.slim +10 -0
  45. data/lib/flexi_admin/components/resources/grid_view/grid_component.rb +16 -0
  46. data/lib/flexi_admin/components/resources/grid_view_component.rb +89 -0
  47. data/lib/flexi_admin/components/resources/index_page_component.html.slim +13 -0
  48. data/lib/flexi_admin/components/resources/index_page_component.rb +19 -0
  49. data/lib/flexi_admin/components/resources/list_view/cell_component.html.slim +2 -0
  50. data/lib/flexi_admin/components/resources/list_view/cell_component.rb +26 -0
  51. data/lib/flexi_admin/components/resources/list_view/table_component.html.slim +17 -0
  52. data/lib/flexi_admin/components/resources/list_view/table_component.rb +21 -0
  53. data/lib/flexi_admin/components/resources/list_view_component.rb +80 -0
  54. data/lib/flexi_admin/components/resources/pagination_component.html.slim +42 -0
  55. data/lib/flexi_admin/components/resources/pagination_component.rb +64 -0
  56. data/lib/flexi_admin/components/resources/resources_component.rb +34 -0
  57. data/lib/flexi_admin/components/resources/switch_view_component.html.slim +16 -0
  58. data/lib/flexi_admin/components/resources/switch_view_component.rb +45 -0
  59. data/lib/flexi_admin/components/resources/view_component.html.slim +12 -0
  60. data/lib/flexi_admin/components/resources/view_component.rb +15 -0
  61. data/lib/flexi_admin/components/shared/alert_component.html.slim +2 -0
  62. data/lib/flexi_admin/components/shared/alert_component.rb +11 -0
  63. data/lib/flexi_admin/components/shared/autocomplete/results_component.html.slim +15 -0
  64. data/lib/flexi_admin/components/shared/autocomplete/results_component.rb +50 -0
  65. data/lib/flexi_admin/components/shared/autocomplete.rb +6 -0
  66. data/lib/flexi_admin/components/shared/datalist_component.html.slim +24 -0
  67. data/lib/flexi_admin/components/shared/datalist_component.rb +20 -0
  68. data/lib/flexi_admin/components/shared/link_component.html.slim +3 -0
  69. data/lib/flexi_admin/components/shared/link_component.rb +15 -0
  70. data/lib/flexi_admin/components/shared/medium_component.html.slim +7 -0
  71. data/lib/flexi_admin/components/shared/medium_component.rb +51 -0
  72. data/lib/flexi_admin/components/shared/table/header_item_component.html.slim +9 -0
  73. data/lib/flexi_admin/components/shared/table/header_item_component.rb +78 -0
  74. data/lib/flexi_admin/components/shared/trix_component.html.slim +22 -0
  75. data/lib/flexi_admin/components/shared/trix_component.rb +21 -0
  76. data/lib/flexi_admin/components.rb +87 -0
  77. data/lib/flexi_admin/config.rb +24 -0
  78. data/lib/flexi_admin/controllers/modals_controller.rb +13 -0
  79. data/lib/flexi_admin/controllers/resources_controller.rb +240 -0
  80. data/lib/flexi_admin/controllers.rb +9 -0
  81. data/lib/flexi_admin/engine.rb +34 -0
  82. data/lib/flexi_admin/helpers/application_helper.rb +4 -0
  83. data/lib/flexi_admin/helpers.rb +8 -0
  84. data/lib/flexi_admin/javascript/controllers/application.js +9 -0
  85. data/lib/flexi_admin/javascript/controllers/autocomplete_controller.js +142 -0
  86. data/lib/flexi_admin/javascript/controllers/bulk_action_controller.js +158 -0
  87. data/lib/flexi_admin/javascript/controllers/button_select_controller.js +32 -0
  88. data/lib/flexi_admin/javascript/controllers/datalist_controller.js +104 -0
  89. data/lib/flexi_admin/javascript/controllers/floating_toc_controller.js +39 -0
  90. data/lib/flexi_admin/javascript/controllers/form_controller.js +17 -0
  91. data/lib/flexi_admin/javascript/controllers/form_validation_controller.js +86 -0
  92. data/lib/flexi_admin/javascript/controllers/index.js +44 -0
  93. data/lib/flexi_admin/javascript/controllers/pagination_controller.js +13 -0
  94. data/lib/flexi_admin/javascript/controllers/sorting_controller.js +17 -0
  95. data/lib/flexi_admin/javascript/controllers/switch_view_controller.js +15 -0
  96. data/lib/flexi_admin/javascript/controllers/toast_controller.js +18 -0
  97. data/lib/flexi_admin/javascript/controllers/trix_controller.js +32 -0
  98. data/lib/flexi_admin/javascript/controllers/uploads_controller.js +164 -0
  99. data/lib/flexi_admin/javascript/flexi_admin.js +4 -0
  100. data/lib/flexi_admin/javascript/utils.js +26 -0
  101. data/lib/flexi_admin/models/concerns/application_resource.rb +25 -0
  102. data/lib/flexi_admin/models/concerns/parentable.rb +17 -0
  103. data/lib/flexi_admin/models/context_params.rb +127 -0
  104. data/lib/flexi_admin/models/resources/context.rb +59 -0
  105. data/lib/flexi_admin/models/struct.rb +29 -0
  106. data/lib/flexi_admin/models/toast.rb +23 -0
  107. data/lib/flexi_admin/models.rb +20 -0
  108. data/lib/flexi_admin/prompts/codegen-system-prompt.md +50 -0
  109. data/lib/flexi_admin/railtie.rb +78 -0
  110. data/lib/flexi_admin/routes.rb +15 -0
  111. data/lib/flexi_admin/services/code_gen/code_export.rb +22 -0
  112. data/lib/flexi_admin/services/code_gen/gemini.rb +104 -0
  113. data/lib/flexi_admin/services/code_gen/gpt.rb +68 -0
  114. data/lib/flexi_admin/services/code_gen/runner.rb +210 -0
  115. data/lib/flexi_admin/services/code_gen.rb +14 -0
  116. data/lib/flexi_admin/services/create_resource.rb +32 -0
  117. data/lib/flexi_admin/services/update_resource.rb +30 -0
  118. data/lib/flexi_admin/services.rb +10 -0
  119. data/lib/flexi_admin/version.rb +10 -0
  120. data/lib/flexi_admin/views/shared/_redirect.slim +2 -0
  121. data/lib/flexi_admin/views/shared/_reload.slim +2 -0
  122. data/lib/flexi_admin/views/shared/_toasts.slim +15 -0
  123. data/lib/flexi_admin/views/shared/not_authorized.slim +11 -0
  124. data/lib/flexi_admin.rb +58 -0
  125. data/lib/tasks/flexi_admin.rake +49 -0
  126. data/lib/tasks/semantic.rake +57 -0
  127. metadata +333 -0
@@ -0,0 +1,7 @@
1
+ - if media?
2
+ = render_media
3
+ - else
4
+ - if preview_missing.present?
5
+ = preview_missing
6
+ - else
7
+ = render EmptyImageComponent.new('Náhled není k dispozici')
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Components::Shared
4
+ class MediumComponent < FlexiAdmin::Components::BaseComponent
5
+ attr_accessor :attachment, :variant, :autoplay, :css_class
6
+
7
+ renders_one :preview_missing
8
+
9
+ # attachment: ActiveStorage::Attached::One
10
+ def initialize(attachment:, variant:, autoplay: false, css_class: nil)
11
+ @attachment = attachment
12
+ @variant = variant
13
+ @autoplay = autoplay
14
+ @css_class = css_class
15
+ end
16
+
17
+ def media?
18
+ return false if attachment.blank?
19
+
20
+ true
21
+ end
22
+
23
+ def image_src
24
+ return if attachment.blank?
25
+ return unless attachment.attached?
26
+
27
+ if attachment.class != ActiveStorage::Attached::One
28
+ raise "ActiveStorage::Attached::One required, got #{attachment.class}"
29
+ end
30
+
31
+ return helpers.url_for(attachment) unless variant
32
+
33
+ helpers.url_for(attachment.variant(variant))
34
+ rescue ActiveStorage::InvariableError
35
+ helpers.url_for(attachment)
36
+ end
37
+
38
+ def render_media
39
+ case attachment.content_type.split("/").first
40
+ when "image"
41
+ content_tag :img, nil, src: image_src, class: css_class
42
+ when "video"
43
+ content_tag :video, nil, src: image_src,
44
+ controls: true,
45
+ class: css_class,
46
+ onmouseover: autoplay ? "this.play()" : nil,
47
+ onmouseout: autoplay ? "this.pause()" : nil
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+
2
+ div class="#{merged_classes}" data-controller="sorting" data-sorting-sort-path-value="#{sort_path}"
3
+ - if column.sortable?
4
+ a href="#" class="a text-decoration-none text-secondary" data-action="sorting#sort"
5
+ .d-flex class="#{justify_class}"
6
+ = label
7
+ i.bi.text-primary.opacity-75.ms-2 class="#{sort_icon}"
8
+ - else
9
+ = label
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Components::Shared::Table
4
+ class HeaderItemComponent < FlexiAdmin::Components::BaseComponent
5
+ attr_reader :column, :attr, :justify, :width, :sort_by
6
+
7
+ def initialize(column)
8
+ @column = column
9
+ @justify = column.options[:justify] || :start
10
+ @width = column.options[:width] || ""
11
+ @attr = column.attribute
12
+ @sort_by = column.sort_by
13
+ @label = column.label
14
+ end
15
+
16
+ def label
17
+ @label || attr.to_s.humanize
18
+ end
19
+
20
+ def sort_path
21
+ if order.blank?
22
+ view_context.context.params.to_path(request.path)
23
+ else
24
+ view_context.context.params.merge({ sort: sort_by, order: }).to_path(request.path)
25
+ end
26
+ end
27
+
28
+ def merged_classes
29
+ [width_class, justify_class].join(" ")
30
+ end
31
+
32
+ def width_class
33
+ width.present? ? "col-#{width}" : "col"
34
+ end
35
+
36
+ def justify_class
37
+ case justify.to_sym
38
+ when :start
39
+ "justify-content-start"
40
+ when :end
41
+ "justify-content-end"
42
+ else
43
+ "justify-content-start"
44
+ end
45
+ end
46
+
47
+ def sort_icon
48
+ return "bi-arrow-up-square" if view_context.context.params[:sort].to_s != sort_by.to_s
49
+
50
+ case view_context.context.params[:order]
51
+ when "asc"
52
+ "bi-arrow-up-square-fill"
53
+ when "desc"
54
+ "bi-arrow-down-square-fill"
55
+ else
56
+ "bi-arrow-up-square"
57
+ end
58
+ end
59
+
60
+ def order
61
+ # Sorting by other column, so we need to reset the order
62
+ return "asc" if view_context.context.params[:sort].to_s != sort_by.to_s
63
+
64
+ order = case view_context.context.params[:order]
65
+ when "asc"
66
+ "desc"
67
+ when "desc"
68
+ "default"
69
+ when "default"
70
+ "asc"
71
+ else
72
+ "asc"
73
+ end
74
+
75
+ order
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,22 @@
1
+ .trix-container.collapsed [data-controller="trix"
2
+ data-trix-disabled-value="#{disabled}"
3
+ data-trix-collapsed-value="true"]
4
+ - if disabled && value.present?
5
+ .collapsed data-trix-target="container"
6
+ - if short_text?
7
+ = value&.html_safe
8
+ - else
9
+ span.trigger [type="button"
10
+ data-trix-target="trigger"
11
+ data-action="click->trix#toggleCollapse"]
12
+ | rozbalit
13
+ span.text data-trix-target="text"
14
+ = value&.html_safe
15
+
16
+ - if !disabled
17
+ input [type="hidden"
18
+ name="#{attr_name}"
19
+ value="#{value}"
20
+ id="#{element_id}"]
21
+ trix-editor [input="#{element_id}"
22
+ data-trix-target="editor"]
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Components::Shared
4
+ class TrixComponent < FlexiAdmin::Components::BaseComponent
5
+ attr_reader :attr_name, :value, :disabled
6
+
7
+ def initialize(attr_name:, value:, disabled: false)
8
+ @attr_name = attr_name
9
+ @value = value
10
+ @disabled = disabled
11
+ end
12
+
13
+ def element_id
14
+ "trix-input-#{@attr_name}" || SecureRandom.uuid
15
+ end
16
+
17
+ def short_text?
18
+ value.present? && value.size < 150
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ module Components
5
+ module Actions; end
6
+ module Form; end
7
+ module Helpers; end
8
+ module Resource; end
9
+
10
+ module Resources
11
+ module ListView; end
12
+ module GridView; end
13
+ module BulkAction; end
14
+ end
15
+
16
+ module Shared
17
+ module Table; end
18
+ end
19
+ end
20
+ end
21
+
22
+ # Base Component
23
+ require_relative "components/base_component"
24
+
25
+ # Nav Components
26
+ require_relative "components/nav/floating_toc_component"
27
+
28
+ # Helpers
29
+ require_relative "components/helpers/action_button_helper"
30
+ require_relative "components/helpers/action_helper"
31
+ require_relative "components/helpers/icon_helper"
32
+ require_relative "components/helpers/link_helper"
33
+ require_relative "components/helpers/url_helper"
34
+ require_relative "components/helpers/resource_helper"
35
+ require_relative "components/helpers/selectable"
36
+ require_relative "components/helpers/value_formatter"
37
+
38
+ require_relative "components/resource/form_mixin"
39
+
40
+ # Action Components
41
+ require_relative "components/actions/checkbox_component"
42
+ require_relative "components/actions/select_component"
43
+
44
+ # Form Components
45
+ require_relative "components/form/field_component"
46
+ require_relative "components/form/label_component"
47
+ require_relative "components/form/rows_component"
48
+ require_relative "components/form/text_input_component"
49
+
50
+ # Resource Components
51
+ require_relative "components/resource/autocomplete_component"
52
+ require_relative "components/resource/button_select_component"
53
+ require_relative "components/resource/form_component"
54
+ require_relative "components/resource/form_element_component"
55
+ require_relative "components/resource/link_action_component"
56
+ require_relative "components/resource/show_page_component"
57
+ require_relative "components/resource/view_component"
58
+
59
+ # Resources Component
60
+ require_relative "components/resources/index_page_component"
61
+ require_relative "components/resources/list_view_component"
62
+ require_relative "components/resources/pagination_component"
63
+ require_relative "components/resources/resources_component"
64
+ require_relative "components/resources/switch_view_component"
65
+ require_relative "components/resources/view_component"
66
+
67
+ # Shared Components
68
+ require_relative "components/shared/autocomplete/results_component"
69
+ require_relative "components/shared/datalist_component"
70
+ require_relative "components/shared/medium_component"
71
+ require_relative "components/shared/table/header_item_component"
72
+ require_relative "components/shared/trix_component"
73
+ require_relative "components/shared/link_component"
74
+
75
+ # Resources List View Components
76
+ require_relative "components/resources/list_view/cell_component"
77
+ require_relative "components/resources/list_view/table_component"
78
+
79
+ # Resources Grid View Components
80
+ require_relative "components/resources/grid_view/card_component"
81
+ require_relative "components/resources/grid_view/grid_component"
82
+ require_relative "components/resources/grid_view_component"
83
+ require_relative "components/resources/list_view_component"
84
+
85
+ # Bulk Action Components
86
+ require_relative "components/resources/bulk_action/modal_component"
87
+ require_relative "components/resources/bulk_action/button_component"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ class Config
5
+ class Store
6
+ attr_accessor :chat_client, :llm_client
7
+ attr_reader :functions
8
+
9
+ attr_writer :controller_class
10
+ end
11
+
12
+ class << self
13
+ attr_writer :configuration
14
+
15
+ def configuration
16
+ @configuration ||= FlexiAdmin::Config::Store.new
17
+ end
18
+
19
+ def configure
20
+ yield(configuration)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Controllers::ModalsController
4
+ def show
5
+ component_class = params[:kind].gsub("-", "/").camelize.constantize
6
+
7
+ raise ArgumentError, "scope is required" if context_params.scope.blank?
8
+
9
+ context = FlexiAdmin::Models::Resources::Context.from_params(context_params)
10
+
11
+ render turbo_stream: turbo_stream.update("modalx_#{context.scope}", component_class.new(context))
12
+ end
13
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Controllers::ResourcesController
4
+ extend ActiveSupport::Concern
5
+ # rescue_from RuntimeError, with: :handle_runtime_error
6
+
7
+ included do
8
+ before_action :context_params
9
+
10
+ rescue_from CanCan::AccessDenied do |exception|
11
+ flash[:error] = FlexiAdmin::Models::Toast.new(exception.message)
12
+ respond_to do |format|
13
+ format.html do
14
+ render 'shared/not_authorized'
15
+ end
16
+ format.turbo_stream do
17
+ render_toasts
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def render_toasts
24
+ render turbo_stream: turbo_stream.append('toasts', partial: 'shared/toasts')
25
+ end
26
+
27
+ def append_toasts
28
+ turbo_stream.append('toasts', partial: 'shared/toasts')
29
+ end
30
+
31
+ def render_index(resources, target: nil)
32
+ target ||= context_params.frame
33
+ respond_to do |format|
34
+ format.html do
35
+ component_class = "Admin::#{resource_class.name}::IndexPageComponent".constantize
36
+ puts "component_class: #{component_class}"
37
+ render component_class.new(resources, context_params:, scope: resource_class.to_s.downcase)
38
+ end
39
+ format.turbo_stream do
40
+ component_class = "Admin::#{resource_class.name}::ResourcesComponent".constantize
41
+ render turbo_stream: turbo_stream.replace(target, component_class.new(resources, context_params:, scope: resource_class.to_s.downcase))
42
+ end
43
+ end
44
+ end
45
+
46
+ # Deprecated
47
+ def reload_page
48
+ render turbo_stream: [
49
+ turbo_stream.append('system', partial: 'shared/reload'),
50
+ turbo_stream.append('toasts', partial: 'shared/toasts')
51
+ ]
52
+ end
53
+
54
+ def redirect_to_path(path)
55
+ render turbo_stream: turbo_stream.append('system', partial: 'shared/redirect', locals: { path: })
56
+ end
57
+
58
+ def context_params
59
+ @context_params ||= FlexiAdmin::Models::ContextParams.new(context_permitted_params)
60
+ end
61
+
62
+ def context_permitted_params
63
+ @context_permitted_params ||= params.permit(*FlexiAdmin::Models::ContextParams.permitted_params_keys)
64
+ end
65
+
66
+
67
+ def handle_runtime_error(error)
68
+ return BugTracker.notify(error) if true
69
+
70
+ flash[:error] = FlexiAdmin::Models::Toast.new(error.message)
71
+
72
+ render_toasts
73
+ end
74
+
75
+ def create
76
+ authorize! :create, resource_class
77
+
78
+ create_service = begin
79
+ "#{resource_class.model_name.plural.camelize}::Services::Create".constantize
80
+ rescue NameError
81
+ FlexiAdmin::Services::CreateResource
82
+ end
83
+
84
+ result = create_service.run(resource_class:, params: create_params)
85
+
86
+ if result.valid?
87
+ redirect_to_path polymorphic_path([:admin, result.resource])
88
+ else
89
+ render_new_resource_form(result.resource)
90
+ end
91
+ end
92
+
93
+ def show
94
+ @resource = resource_class.find(params[:id])
95
+ authorize! :show, @resource
96
+
97
+ respond_to do |format|
98
+ format.html do
99
+ component ||= "Admin::#{resource_class.name}::Show::PageComponent".constantize
100
+ render component.new(@resource, context_params:, scope: resource_class.to_s.downcase)
101
+ end
102
+ format.turbo_stream do
103
+ render turbo_stream: turbo_stream.replace(context_params.frame)
104
+ end
105
+ end
106
+ end
107
+
108
+ def edit
109
+ @resource = resource_class.find(params[:id])
110
+ authorize! :update, @resource
111
+
112
+ render_edit_resource_form(disabled: disabled?(context_params.form_disabled))
113
+ end
114
+
115
+ def update
116
+ @resource = resource_class.find(params[:id])
117
+ authorize! :update, @resource
118
+
119
+ update_service = begin
120
+ "#{resource_class.model_name.plural.camelize}::Services::Update".constantize
121
+ rescue NameError
122
+ FlexiAdmin::Services::UpdateResource
123
+ end
124
+
125
+ result = update_service.run(resource: @resource, params: resource_params)
126
+
127
+ if result.valid?
128
+ render_edit_resource_form(disabled: disabled?(true))
129
+ else
130
+ render_edit_resource_form(disabled: disabled?(false))
131
+ end
132
+ end
133
+
134
+ def render_edit_resource_form(disabled: true)
135
+ respond_to do |format|
136
+ format.html do
137
+ render turbo_stream: turbo_stream.replace(@resource.form_id, edit_form_component_instance(disabled?(disabled)))
138
+ end
139
+ format.turbo_stream do
140
+ render turbo_stream: turbo_stream.replace(@resource.form_id, edit_form_component_instance(disabled?(disabled)))
141
+ end
142
+ end
143
+ end
144
+
145
+ def render_new_resource_form(resource)
146
+ respond_to do |format|
147
+ format.html do
148
+ render new_form_component_instance(resource)
149
+ end
150
+ format.turbo_stream do
151
+ render turbo_stream: turbo_stream.replace(context_params.frame, new_form_component_instance(resource))
152
+ end
153
+ end
154
+ end
155
+
156
+ def bulk_action
157
+ ids = JSON.parse(params[:ids])
158
+
159
+ # Unscoped is needed to get the resources that are not deleted, archived, etc.
160
+ # It should be ok, since we control the ids in the frontend
161
+ @resources = resource_class.unscoped.where(id: ids)
162
+ authorize! :edit, @resources
163
+
164
+ bulk_processor = "#{params[:processor].gsub('-', '/').camelize}::Processor".constantize.new(@resources, params)
165
+ result = bulk_processor.perform
166
+
167
+ redirect_to_path result.path and return if result.result == :redirect
168
+
169
+ flash[result.result] = FlexiAdmin::Models::Toast.new(result.message)
170
+
171
+ reload_page
172
+ end
173
+
174
+ def autocomplete(includes: nil)
175
+ base_query = resource_class.with_parent(parent_instance)
176
+ .fulltext(params[:q])
177
+ base_query = base_query.includes(includes) if includes.present?
178
+ results_count = base_query.count
179
+ results = base_query.limit(100)
180
+
181
+ render FlexiAdmin::Components::Shared::Autocomplete::ResultsComponent.new(results:,
182
+ results_count:,
183
+ context_params:), layout: false
184
+ end
185
+
186
+ def datalist
187
+ results = resource_class.fulltext(params[:q])
188
+ .limit(100)
189
+ .order(*context_params.params[:ac_fields].map(&:to_sym))
190
+ .pluck(*context_params.params[:ac_fields].map(&:to_sym))
191
+ .uniq
192
+ render FlexiAdmin::Components::Shared::Autocomplete::ResultsComponent.new(results:,
193
+ context_params:), layout: false
194
+ end
195
+
196
+ private
197
+
198
+ def parent_instance
199
+ @parent_instance ||= locate_resource(context_params.parent)
200
+ end
201
+
202
+ def edit_form_component_instance(disabled)
203
+ [FlexiAdmin::NAMESPACE, "#{@resource.class.name}::Show::EditFormComponent"].join("::").constantize.new(@resource, disabled: disabled?(disabled))
204
+ end
205
+
206
+ def new_form_component_instance(resource)
207
+ "#{resource.class.name}::NewFormComponent".constantize.new(resource, parent: parent_instance)
208
+ end
209
+
210
+ def locate_resource(encoded_gid)
211
+ return nil if encoded_gid.blank?
212
+
213
+ gid = URI.decode_www_form_component(encoded_gid)
214
+ resource = GlobalID::Locator.locate(gid)
215
+
216
+ raise ActiveRecord::RecordNotFound, "Resource not found: #{encoded_gid}" if resource.blank?
217
+
218
+ resource
219
+ end
220
+
221
+ def resource_class
222
+ controller_name.classify.constantize
223
+ end
224
+
225
+ def fa_sorted?
226
+ fa_sort.present? && fa_order.present? && fa_order != "default"
227
+ end
228
+
229
+ def fa_sort
230
+ context_params[:sort]
231
+ end
232
+
233
+ def fa_order
234
+ context_params[:order]
235
+ end
236
+
237
+ def disabled?(form_disabled = false)
238
+ !current_ability&.can?(:update, resource_class) || form_disabled
239
+ end
240
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ module Controllers
5
+ end
6
+ end
7
+
8
+ require_relative "controllers/resources_controller"
9
+ require_relative "controllers/modals_controller"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace FlexiAdmin
6
+
7
+ initializer "flexi_admin.assets" do |app|
8
+ app.config.assets.precompile += %w[flexi_admin_main.scss flexi_admin.js]
9
+ app.config.assets.paths << root.join("app/flexi_admin/javascript")
10
+ end
11
+
12
+ initializer "flexi_admin.helpers" do
13
+ ActiveSupport.on_load(:action_controller) do
14
+ include FlexiAdmin::Helpers::ApplicationHelper
15
+ end
16
+
17
+ ActiveSupport.on_load(:view_component) do
18
+ include Rails.application.routes.url_helpers
19
+ end
20
+ end
21
+
22
+ # initializer "flexi_admin.stimulus_controllers" do |app|
23
+ # app.config.stimulus_modules |= [
24
+ # { name: "flexi-admin", path: "flexi_admin/controllers" }
25
+ # ]
26
+ # end
27
+
28
+ config.to_prepare do
29
+ Dir.glob(Engine.root.join("app", "**", "*_decorator*.rb")).each do |c|
30
+ require_dependency(c)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Helpers::ApplicationHelper
4
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ module Helpers
5
+ end
6
+ end
7
+
8
+ require_relative "helpers/application_helper"
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }