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,32 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="trix"
4
+ export default class extends Controller {
5
+ static values = { disabled: Boolean, collapsed: Boolean };
6
+ static targets = ["editor", "trigger", "container"];
7
+
8
+ connect() {
9
+ if (this.disabledValue === false) {
10
+ // Set background color to white
11
+ this.editorTarget.style.backgroundColor = "white";
12
+ }
13
+ }
14
+
15
+ toggleCollapse() {
16
+ if (this.collapsedValue === true) {
17
+ this.containerTarget.classList.remove("collapsed");
18
+ this.containerTarget.classList.add("expanded");
19
+ this.collapsedValue = false;
20
+ } else {
21
+ this.containerTarget.classList.remove("expanded");
22
+ this.containerTarget.classList.add("collapsed");
23
+ this.collapsedValue = true;
24
+ }
25
+
26
+ if (this.triggerTarget.innerHTML === "rozbalit") {
27
+ this.triggerTarget.innerHTML = "skrýt";
28
+ } else {
29
+ this.triggerTarget.innerHTML = "rozbalit";
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,164 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { CSRFToken } from "../utils";
3
+
4
+ export default class extends Controller {
5
+ static targets = [
6
+ "form",
7
+ "list",
8
+ "globalProgress",
9
+ "globalProgressBar",
10
+ "resetButton",
11
+ "filesInput",
12
+ ];
13
+
14
+ connect() {
15
+ this.element.addEventListener("direct-uploads:start", (event) => {
16
+ this._initGlobalProgress();
17
+ });
18
+
19
+ this.element.addEventListener("direct-upload:initialize", (event) => {
20
+ const { id, file } = event.detail;
21
+ this._initializeFileCard(id, file);
22
+ });
23
+
24
+ this.element.addEventListener("direct-upload:progress", (event) => {
25
+ const { id, file, progress } = event.detail;
26
+ this._updateImageProgress(id, progress);
27
+ });
28
+
29
+ this.element.addEventListener("direct-upload:end", (event) => {
30
+ const { id, file } = event.detail;
31
+ this._reportCompletion(file, id);
32
+ this._updateImageProgress(id, 100);
33
+ this._updateGlobalProgress();
34
+ });
35
+
36
+ this.element.addEventListener("direct-uploads:end", (event) => {
37
+ this.resetButtonTarget.classList.remove("d-none");
38
+ });
39
+
40
+ this.element.addEventListener("direct-upload:error", (event) => {
41
+ const { id } = event.detail;
42
+ this._setErrorProgress(id);
43
+ });
44
+ }
45
+
46
+ resetForm() {
47
+ this.formTarget.reset();
48
+ // hide global progress
49
+ this.globalProgressTarget.classList.remove("show");
50
+ // hide reset button
51
+ this.resetButtonTarget.classList.add("d-none");
52
+ // reset counters
53
+ this.totalCount = 0;
54
+ this.currentCount = 0;
55
+ // clear list
56
+ this.listTarget.innerHTML = "";
57
+ // enable the input field
58
+ this.filesInputTarget.disabled = false;
59
+ }
60
+
61
+ _initGlobalProgress(totalCount) {
62
+ const filesInput = this.filesInputTarget;
63
+ this.totalCount = filesInput.files.length;
64
+ this.currentCount = 0;
65
+
66
+ this.globalProgressTarget.classList.add("show");
67
+ this.globalProgressBarTarget.style.width = `${0}%`;
68
+ this.globalProgressBarTarget.setAttribute("aria-valuenow", 0);
69
+ this.globalProgressBarTarget.textContent = `0/${totalCount}`;
70
+ }
71
+
72
+ _updateGlobalProgress() {
73
+ const globalProgressBar = this.globalProgressBarTarget;
74
+ this.currentCount += 1;
75
+
76
+ const perCent = (this.currentCount / this.totalCount) * 100;
77
+ globalProgressBar.style.width = `${perCent}%`;
78
+ globalProgressBar.setAttribute("aria-valuenow", perCent);
79
+ globalProgressBar.textContent = `${this.currentCount}/${this.totalCount}`;
80
+ }
81
+
82
+ _setErrorProgress(id) {
83
+ const element = document.getElementById(`upload-${id}`);
84
+ const progressBar = element.querySelector(".progress-bar");
85
+ progressBar.classList.add("bg-danger");
86
+ progressBar.style.width = "100%";
87
+ progressBar.setAttribute("aria-valuenow", 100);
88
+ }
89
+
90
+ _updateImageProgress(id, progress) {
91
+ const element = document.getElementById(`upload-${id}`);
92
+ const progressBar = element.querySelector(".progress-bar");
93
+ progressBar.style.width = `${progress}%`;
94
+ progressBar.setAttribute("aria-valuenow", progress);
95
+ }
96
+
97
+ _switchToVideo(id, url) {
98
+ const element = document.getElementById(`upload-${id}`);
99
+ const videoContainer = element.querySelector(".upload-video");
100
+ const video = element.querySelector(".upload-video video");
101
+ const icon = element.querySelector(".upload-icon");
102
+ video.src = url;
103
+ icon.classList.add("d-none");
104
+ videoContainer.classList.add("show");
105
+ }
106
+
107
+ _switchToImage(id, url) {
108
+ const element = document.getElementById(`upload-${id}`);
109
+ const imageContainer = element.querySelector(".upload-image");
110
+ const image = element.querySelector(".upload-image img");
111
+ const icon = element.querySelector(".upload-icon");
112
+ image.src = url;
113
+ icon.classList.add("d-none");
114
+ imageContainer.classList.add("show");
115
+ }
116
+
117
+ _reportCompletion(file, id) {
118
+ const name = file.name;
119
+ const size = file.size;
120
+
121
+ fetch("/uploads/soft_completed", {
122
+ method: "POST",
123
+ headers: {
124
+ "Content-Type": "application/json",
125
+ "X-CSRF-Token": CSRFToken(),
126
+ Accept: "application/json",
127
+ },
128
+ body: JSON.stringify({ name, size }),
129
+ })
130
+ .then((response) => response.json())
131
+ .then((data) => {
132
+ const { url, kind } = data;
133
+ if (url) {
134
+ if (kind === "video") {
135
+ this._switchToVideo(id, url);
136
+ } else {
137
+ this._switchToImage(id, url);
138
+ }
139
+ }
140
+ })
141
+ .catch((error) => console.error("Error:", error));
142
+ }
143
+
144
+ _initializeFileCard(id, file) {
145
+ const name = file.name;
146
+ const size = file.size;
147
+ const contentType = file.type;
148
+
149
+ fetch("/uploads/initialize_upload", {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ "X-CSRF-Token": CSRFToken(),
154
+ Accept: "text/html",
155
+ },
156
+ body: JSON.stringify({ id, name, size, contentType }),
157
+ })
158
+ .then((response) => response.text())
159
+ .then((html) => {
160
+ this.listTarget.insertAdjacentHTML("afterbegin", html);
161
+ })
162
+ .catch((error) => console.error("Error:", error));
163
+ }
164
+ }
@@ -0,0 +1,4 @@
1
+ import "./controllers";
2
+ import { fetchTurboContent, CSRFToken } from "./utils";
3
+
4
+ export { fetchTurboContent, CSRFToken };
@@ -0,0 +1,26 @@
1
+ export function CSRFToken() {
2
+ const metaTag = document.querySelector('meta[name="csrf-token"]');
3
+ return metaTag ? metaTag.getAttribute("content") : null;
4
+ }
5
+
6
+ export function fetchTurboContent(event, path) {
7
+ event.preventDefault();
8
+
9
+ fetch(path, {
10
+ headers: {
11
+ Accept: "text/vnd.turbo-stream.html",
12
+ },
13
+ })
14
+ .then((response) => response.text())
15
+ .then((html) => {
16
+ Turbo.renderStreamMessage(html);
17
+ })
18
+ .catch((error) => {
19
+ console.error("Error fetching content:", error);
20
+ });
21
+ }
22
+
23
+ export function markValid(event) {
24
+ event.target.setCustomValidity("");
25
+ event.target.checkValidity();
26
+ }
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models::Concerns::ApplicationResource
4
+ extend ActiveSupport::Concern
5
+ include FlexiAdmin::Models::Concerns::Parentable
6
+
7
+ def identifier
8
+ "#{self.class.name.underscore}_#{id}"
9
+ end
10
+ alias frame_id identifier
11
+
12
+ def form_id
13
+ "#{identifier}_form"
14
+ end
15
+
16
+ def gid_param
17
+ gid = to_gid
18
+
19
+ URI.encode_www_form_component(gid)
20
+ end
21
+
22
+ def raw(attribute)
23
+ read_attribute_before_type_cast(attribute)
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models::Concerns::Parentable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :with_parent, lambda { |parent_instance|
8
+ return all if parent_instance.blank?
9
+
10
+ plural_name = parent_instance.class.model_name.plural
11
+
12
+ association_name = model.reflect_on_all_associations.find { |a| a.plural_name == plural_name }.name
13
+
14
+ joins(association_name).where(association_name => { id: parent_instance.id })
15
+ }
16
+ end
17
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models
4
+ class ContextParams
5
+ MAP = {
6
+ scope: 'fa_scope',
7
+ page: 'fa_page',
8
+ per_page: 'fa_per_page',
9
+ parent: 'fa_parent',
10
+ frame: 'fa_frame', # target turbolinks frame
11
+ sort: 'fa_sort',
12
+ order: 'fa_order',
13
+ form_disabled: 'fa_form_disabled',
14
+ view: 'fa_view',
15
+ ac_action: 'ac_action', # autocomplete action
16
+ ac_fields: { 'ac_fields': [] }, # autocomplete result fields
17
+ ac_path: 'ac_path' # autocomplete path for opening resource in new window
18
+ }
19
+
20
+ def self.permitted_params_keys
21
+ MAP.values
22
+ end
23
+
24
+ def self.permitted_keys
25
+ MAP.keys.map(&:to_sym)
26
+ end
27
+
28
+ def self.params_map
29
+ MAP.each_with_object({}) do |(key, value), acc|
30
+ acc[key] = value.is_a?(Hash) ? value.keys.first.to_s : value
31
+ end
32
+ end
33
+
34
+ attr_reader :params
35
+
36
+ def initialize(params)
37
+ @params = params.to_h.with_indifferent_access.each_with_object({}) do |(key, value), acc|
38
+ acc[self.class.params_map.invert[key.to_s]] = value
39
+ end.with_indifferent_access
40
+ end
41
+
42
+ def [](key)
43
+ @params[key]
44
+ end
45
+
46
+ def merge(params)
47
+ new_params = @params.dup.merge(params.to_h.with_indifferent_access)
48
+
49
+ if params.keys.any? { |key| !self.class.permitted_keys.include?(key.to_sym) }
50
+ msg = "ContextParams: invalid params: "
51
+ msg += params.keys.select { |key| !self.class.permitted_keys.include?(key.to_sym) }.join(", ")
52
+ Rails.logger.warn msg
53
+ end
54
+
55
+ new_params = new_params.transform_keys { |key| self.class.params_map[key.to_sym] || key.to_sym }
56
+
57
+ self.class.new(new_params)
58
+ end
59
+
60
+ def with_parent(parent_instance)
61
+ return self if parent_instance.blank?
62
+
63
+ merge(parent: parent_instance.gid_param)
64
+ end
65
+
66
+ def with_parent!(parent_instance)
67
+ params[:parent] = parent_instance.gid_param
68
+ self
69
+ end
70
+
71
+ def except(*keys)
72
+ new_params = @params.dup.except(*keys)
73
+ new_params = new_params.transform_keys { |key| self.class.params_map.invert[key.to_s] }
74
+ self.class.new(new_params)
75
+ end
76
+
77
+ def to_params
78
+ self.class.params_map.map { |k, v| [v, params[k]] }
79
+ .reject { |_k, v| v.blank? }.to_h
80
+ end
81
+
82
+ def to_path(path)
83
+ path + "?" + to_params.to_query
84
+ end
85
+
86
+ def parent
87
+ params[:parent]
88
+ end
89
+
90
+ def per_page
91
+ params[:per_page] || 13
92
+ end
93
+
94
+ def page
95
+ params[:page] || 1
96
+ end
97
+
98
+ def frame
99
+ params[:frame] || '_top'
100
+ end
101
+
102
+ # Not sure if this is needed
103
+ def scope
104
+ params[:scope]
105
+ end
106
+
107
+ def form_disabled
108
+ params[:form_disabled] || false
109
+ end
110
+
111
+ def current_view
112
+ params[:view]
113
+ end
114
+
115
+ def pagination
116
+ { page:, per_page: }
117
+ end
118
+
119
+ def sort
120
+ params[:sort]
121
+ end
122
+
123
+ def order
124
+ params[:order]
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models::Resources
4
+ class Context
5
+ attr_reader :resources, :scope, :params, :options
6
+
7
+ def initialize(resources, scope, params, options)
8
+ @resources = resources
9
+ @scope = scope
10
+ @params = params
11
+ @options = options
12
+ end
13
+
14
+ def resource
15
+ resources
16
+ end
17
+
18
+ def self.build(resource: nil, scope: nil, params: FlexiAdmin::Models::ContextParams.new({}), **options)
19
+ new(resource, scope, params, options)
20
+ end
21
+
22
+ def self.from_params(context_params, resources = nil)
23
+ parent = load_parent(context_params)
24
+
25
+ new(
26
+ resources,
27
+ context_params.scope,
28
+ context_params,
29
+ {}.merge(parent:)
30
+ )
31
+ end
32
+
33
+ def self.load_parent(context_params)
34
+ return nil if context_params.parent.blank?
35
+
36
+ gid = URI.decode_www_form_component(context_params.parent)
37
+
38
+ GlobalID::Locator.locate(gid)
39
+ end
40
+
41
+ def views
42
+ options[:views] || %w[list]
43
+ end
44
+
45
+ def paginate?
46
+ options.key?(:paginate) || true
47
+ end
48
+
49
+ def parent
50
+ options[:parent]
51
+ end
52
+
53
+ def to_params
54
+ params.merge(scope:)
55
+ .with_parent(parent)
56
+ .to_params
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models
4
+ class Struct
5
+ def self.new(*positional_attrs, **keyword_attrs)
6
+ Class.new do
7
+ attr_reader(*positional_attrs, *keyword_attrs.keys)
8
+
9
+ define_method :initialize do |*args, **kwargs|
10
+ if args.size != positional_attrs.size
11
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{positional_attrs.size})"
12
+ end
13
+
14
+ unknown_kwargs = kwargs.keys - keyword_attrs.keys
15
+ raise ArgumentError, "unknown keywords: #{unknown_kwargs.join(", ")}" unless unknown_kwargs.empty?
16
+
17
+ positional_attrs.zip(args).each do |attr, value|
18
+ instance_variable_set("@#{attr}", value)
19
+ end
20
+
21
+ keyword_attrs.each do |attr, default|
22
+ value = kwargs.key?(attr) ? kwargs[attr] : default
23
+ instance_variable_set("@#{attr}", value)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin::Models
4
+ class Toast
5
+ DELAY = 4
6
+ attr_reader :message, :options, :autohide, :delay_sec
7
+
8
+ def initialize(message, **options)
9
+ @message = message
10
+ @options = options
11
+ @autohide = options.fetch(:autohide, true)
12
+ @delay_sec = options.fetch(:delay, DELAY)
13
+ end
14
+
15
+ def to_s
16
+ message
17
+ end
18
+
19
+ def delay
20
+ delay_sec * 1000
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ module Models
5
+ module Concerns; end
6
+ module Resources; end
7
+ end
8
+ end
9
+
10
+ # Core
11
+ require_relative "models/context_params"
12
+ require_relative "models/struct"
13
+ require_relative "models/toast"
14
+
15
+ # Concerns
16
+ require_relative "models/concerns/parentable"
17
+ require_relative "models/concerns/application_resource"
18
+
19
+ # Resources
20
+ require_relative "models/resources/context"
@@ -0,0 +1,50 @@
1
+ # Act as a Code Generation Assistant
2
+
3
+ - **You are** an advanced **Code Generation Assistant** skilled in generating structured code based on provided application architecture patterns in the following technologies: Ruby on Rails, Javascript (Stimulus JS) and Bootstrap.
4
+ - follow instructions in the <Task> section.
5
+
6
+ ## Objective
7
+
8
+ Your objective is to:
9
+
10
+ 1. **Identify** which files need to be created based on the <Task> description, <CodeSamples> and known Rails file conventions.
11
+ 2. **Generate** code for each file with patterns similar to the provided examples in <CodeSamples> section, following naming conventions, structural organization and class inheritance.
12
+
13
+ ## Task Requirements
14
+
15
+ 1. **Adhere** to comments provided in the file samples in <CodeSamples> section, tagged with "CodeGen:"
16
+ 2. **Omit** any comments marked as "CodeGen:" in the final output.
17
+ 3. **Return** a array of JSON objects, where each object always includes:
18
+ - **filename**: the determined filename for the generated code.
19
+ - **code**: the generated code as per the task requirements.
20
+
21
+ ## Output schema
22
+
23
+ ```json
24
+ {
25
+ "files": [
26
+ {
27
+ "filename": "string",
28
+ "code": "string"
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ ---
35
+
36
+ <CodeSamples>
37
+
38
+ ```plaintext
39
+ {{sample_files}}
40
+ ```
41
+
42
+ </CodeSamples>
43
+
44
+ ---
45
+
46
+ <Task>
47
+
48
+ {{task}}
49
+
50
+ </Task>
@@ -0,0 +1,78 @@
1
+ require "rails"
2
+
3
+ # Don't need this for now
4
+ # require "flexi_admin/engine"
5
+
6
+ module FlexiAdmin
7
+ class Railtie < Rails::Railtie
8
+ rake_tasks do
9
+ load "tasks/flexi_admin.rake"
10
+ end
11
+
12
+ initializer "flexi_admin.asset_paths" do |_app|
13
+ # This enables
14
+ setup_node_path
15
+ setup_sass_path
16
+ end
17
+
18
+ initializer "flexi_admin.configure" do |app|
19
+ # Add any additional configuration here
20
+ end
21
+
22
+ initializer "flexi_admin.assets" do |app|
23
+ # Add asset paths to host application
24
+ app.config.assets.paths << absolute_gem_path("lib/flexi_admin/javascript")
25
+ app.config.assets.paths << absolute_gem_path("app/assets/stylesheets")
26
+ end
27
+
28
+ # # Configure importmap if using it
29
+ # initializer "flexi_admin.importmap", before: "importmap" do |app|
30
+ # if app.config.respond_to?(:importmap)
31
+ # app.config.importmap.paths << absolute_gem_path("app/flexi_admin/javascript")
32
+ # end
33
+ # end
34
+
35
+ # initializer "flexi_admin.helpers" do
36
+ # ActiveSupport.on_load(:action_controller) do
37
+ # include FlexiAdmin::Helpers::ApplicationHelper
38
+ # end
39
+ # end
40
+
41
+ initializer "flexi_admin.add_view_paths" do |_app|
42
+ # Assuming your gem has a `app/views` directory
43
+ custom_views_path = File.expand_path("../flexi_admin/views", __dir__)
44
+
45
+ # Add the path to the application's view paths
46
+ ActiveSupport.on_load(:action_controller) do
47
+ ActionController::Base.prepend_view_path(custom_views_path)
48
+ end
49
+ end
50
+
51
+ def absolute_gem_path(path)
52
+ [Gem::Specification.find_by_name("flexi_admin").gem_dir, path].join("/")
53
+ end
54
+
55
+ def setup_node_path
56
+ node_paths ||= []
57
+ node_paths << "./node_modules"
58
+ node_paths << absolute_gem_path("lib/flexi_admin/javascript")
59
+ ENV["NODE_PATH"] = node_paths.join(":")
60
+
61
+ # Set the NODE_PATH for the current bash session
62
+ # https://nodejs.org/api/modules.html#loading-from-the-global-folders
63
+ system("export NODE_PATH=#{ENV["NODE_PATH"]}")
64
+ end
65
+
66
+ def setup_sass_path
67
+ sass_paths ||= []
68
+ sass_paths << "./node_modules"
69
+ sass_paths << absolute_gem_path("app/assets/stylesheets")
70
+ sass_paths << absolute_gem_path("app/assets/stylesheets/components")
71
+ ENV["SASS_PATH"] = sass_paths.join(":")
72
+
73
+ # Set the SASS_PATH for the current bash session
74
+ # https://sass-lang.com/documentation/cli/ruby-sass/#load-path
75
+ system("export SASS_PATH=#{ENV["SASS_PATH"]}")
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlexiAdmin
4
+ module Routes
5
+ def self.extended(router)
6
+ router.instance_eval do
7
+ concern :flexi_admin_resourceful do
8
+ post :bulk_action, on: :collection
9
+ get :datalist, on: :collection
10
+ get :autocomplete, on: :collection
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end