uchi 0.1.5 → 0.1.6

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/uchi/application.js +2655 -24
  3. data/app/assets/javascripts/uchi.js +11 -0
  4. data/app/assets/stylesheets/uchi/application.css +982 -1311
  5. data/app/assets/tailwind/uchi.css +4 -4
  6. data/app/components/uchi/field/belongs_to/edit.html.erb +1 -1
  7. data/app/components/uchi/field/belongs_to/index.html.erb +1 -1
  8. data/app/components/uchi/field/belongs_to/show.html.erb +3 -5
  9. data/app/components/uchi/field/belongs_to.rb +30 -7
  10. data/app/components/uchi/field/boolean/edit.html.erb +1 -1
  11. data/app/components/uchi/field/date/edit.html.erb +1 -1
  12. data/app/components/uchi/field/date_time/edit.html.erb +1 -1
  13. data/app/components/uchi/field/file/edit.html.erb +1 -1
  14. data/app/components/uchi/field/file/index.html.erb +2 -1
  15. data/app/components/uchi/field/file/show.html.erb +3 -2
  16. data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +5 -3
  17. data/app/components/uchi/field/has_many/show.html.erb +5 -3
  18. data/app/components/uchi/field/id/index.html.erb +1 -1
  19. data/app/components/uchi/field/id/show.html.erb +1 -1
  20. data/app/components/uchi/field/image/edit.html.erb +1 -1
  21. data/app/components/uchi/field/image/index.html.erb +2 -1
  22. data/app/components/uchi/field/image/show.html.erb +2 -1
  23. data/app/components/uchi/field/number/edit.html.erb +1 -1
  24. data/app/components/uchi/field/string/edit.html.erb +1 -1
  25. data/app/components/uchi/field/text/edit.html.erb +1 -1
  26. data/app/components/{flowbite → uchi/flowbite}/breadcrumb.rb +5 -5
  27. data/app/components/{flowbite → uchi/flowbite}/breadcrumb_home.rb +2 -2
  28. data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item/current.rb +3 -3
  29. data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item/first.rb +4 -4
  30. data/app/components/{flowbite → uchi/flowbite}/breadcrumb_item.rb +4 -4
  31. data/app/components/{flowbite → uchi/flowbite}/breadcrumb_separator.rb +7 -5
  32. data/app/components/uchi/flowbite/button/outline.rb +37 -0
  33. data/app/components/uchi/flowbite/button/pill.rb +40 -0
  34. data/app/components/uchi/flowbite/button.rb +93 -0
  35. data/app/components/uchi/flowbite/card/card.html.erb +7 -0
  36. data/app/components/uchi/flowbite/card/title.rb +37 -0
  37. data/app/components/uchi/flowbite/card.rb +84 -0
  38. data/app/components/{flowbite → uchi/flowbite}/input/checkbox.rb +7 -7
  39. data/app/components/{flowbite → uchi/flowbite}/input/date.rb +1 -1
  40. data/app/components/{flowbite → uchi/flowbite}/input/date_time.rb +1 -1
  41. data/app/components/{flowbite → uchi/flowbite}/input/email.rb +1 -1
  42. data/app/components/{flowbite → uchi/flowbite}/input/field.rb +11 -10
  43. data/app/components/uchi/flowbite/input/file.rb +30 -0
  44. data/app/components/{flowbite → uchi/flowbite}/input/hint.rb +6 -5
  45. data/app/components/{flowbite → uchi/flowbite}/input/label.rb +8 -7
  46. data/app/components/{flowbite → uchi/flowbite}/input/number.rb +1 -1
  47. data/app/components/{flowbite → uchi/flowbite}/input/password.rb +1 -1
  48. data/app/components/{flowbite → uchi/flowbite}/input/phone.rb +1 -1
  49. data/app/components/{flowbite → uchi/flowbite}/input/radio_button.rb +7 -7
  50. data/app/components/{flowbite → uchi/flowbite}/input/select.rb +16 -7
  51. data/app/components/uchi/flowbite/input/textarea.rb +42 -0
  52. data/app/components/{flowbite → uchi/flowbite}/input/url.rb +1 -1
  53. data/app/components/uchi/flowbite/input/validation_error.rb +39 -0
  54. data/app/components/uchi/flowbite/input_field/checkbox.html.erb +9 -0
  55. data/app/components/{flowbite → uchi/flowbite}/input_field/checkbox.rb +10 -6
  56. data/app/components/{flowbite → uchi/flowbite}/input_field/date.rb +2 -2
  57. data/app/components/{flowbite → uchi/flowbite}/input_field/date_time.rb +2 -2
  58. data/app/components/{flowbite → uchi/flowbite}/input_field/email.rb +2 -2
  59. data/app/components/{flowbite → uchi/flowbite}/input_field/file.rb +2 -2
  60. data/app/components/uchi/flowbite/input_field/input_field.html.erb +9 -0
  61. data/app/components/{flowbite → uchi/flowbite}/input_field/number.rb +2 -2
  62. data/app/components/{flowbite → uchi/flowbite}/input_field/password.rb +2 -2
  63. data/app/components/{flowbite → uchi/flowbite}/input_field/phone.rb +2 -2
  64. data/app/components/uchi/flowbite/input_field/radio_button.html.erb +9 -0
  65. data/app/components/{flowbite → uchi/flowbite}/input_field/radio_button.rb +13 -9
  66. data/app/components/{flowbite → uchi/flowbite}/input_field/select.rb +7 -3
  67. data/app/components/{flowbite → uchi/flowbite}/input_field/text.rb +1 -1
  68. data/app/components/{flowbite → uchi/flowbite}/input_field/textarea.rb +2 -2
  69. data/app/components/{flowbite → uchi/flowbite}/input_field/url.rb +2 -2
  70. data/app/components/{flowbite → uchi/flowbite}/input_field.rb +19 -5
  71. data/app/components/uchi/flowbite/link.rb +41 -0
  72. data/app/components/{flowbite → uchi/flowbite}/style.rb +1 -1
  73. data/app/components/{flowbite → uchi/flowbite}/toast/icon.rb +1 -1
  74. data/app/components/uchi/flowbite/toast/toast.html.erb +40 -0
  75. data/app/components/{flowbite → uchi/flowbite}/toast.rb +2 -2
  76. data/app/components/uchi/ui/actions/dropdown/dropdown.html.erb +59 -0
  77. data/app/components/uchi/ui/actions/dropdown.rb +32 -0
  78. data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +4 -4
  79. data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +2 -1
  80. data/app/components/uchi/ui/form/input/collection_checkboxes.rb +12 -17
  81. data/app/components/uchi/ui/frame/frame.html.erb +6 -1
  82. data/app/components/uchi/ui/index/records_table/records_table.html.erb +109 -18
  83. data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -5
  84. data/app/components/uchi/ui/navigation/navigation.html.erb +9 -0
  85. data/app/components/uchi/ui/navigation.rb +37 -0
  86. data/app/components/uchi/ui/no_value/no_value.html.erb +1 -0
  87. data/app/components/uchi/ui/no_value.rb +8 -0
  88. data/app/components/uchi/ui/page_header/page_header.html.erb +7 -2
  89. data/app/components/uchi/ui/pagination/current_link.html.erb +1 -1
  90. data/app/components/uchi/ui/pagination/gap.html.erb +7 -1
  91. data/app/components/uchi/ui/pagination/link.html.erb +1 -1
  92. data/app/components/uchi/ui/pagination/next_link.html.erb +18 -3
  93. data/app/components/uchi/ui/pagination/pagination.html.erb +3 -1
  94. data/app/components/uchi/ui/pagination/previous_link.html.erb +18 -3
  95. data/app/components/uchi/ui/pagination.rb +1 -1
  96. data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +4 -3
  97. data/app/components/uchi/ui/spinner/spinner.html.erb +17 -3
  98. data/app/controllers/uchi/actions/executions_controller.rb +107 -0
  99. data/app/controllers/uchi/repository_controller.rb +21 -1
  100. data/app/views/layouts/uchi/_flash_messages.html.erb +1 -1
  101. data/app/views/layouts/uchi/application.html.erb +10 -16
  102. data/app/views/uchi/navigation/_main.html.erb +9 -0
  103. data/app/views/uchi/repository/edit.html.erb +2 -2
  104. data/app/views/uchi/repository/index.html.erb +12 -3
  105. data/app/views/uchi/repository/new.html.erb +2 -2
  106. data/app/views/uchi/repository/show.html.erb +13 -2
  107. data/lib/generators/uchi/controller/templates/controller.rb.tt +0 -5
  108. data/lib/generators/uchi/scaffold/scaffold_generator.rb +15 -0
  109. data/lib/uchi/action.rb +84 -0
  110. data/lib/uchi/action_response.rb +108 -0
  111. data/lib/uchi/repository/translate.rb +14 -0
  112. data/lib/uchi/repository.rb +25 -0
  113. data/lib/uchi/version.rb +1 -1
  114. data/lib/uchi.rb +2 -0
  115. metadata +63 -50
  116. data/app/components/flowbite/button/outline.rb +0 -22
  117. data/app/components/flowbite/button/pill.rb +0 -40
  118. data/app/components/flowbite/button.rb +0 -92
  119. data/app/components/flowbite/card.rb +0 -45
  120. data/app/components/flowbite/input/file.rb +0 -30
  121. data/app/components/flowbite/input/textarea.rb +0 -42
  122. data/app/components/flowbite/input/validation_error.rb +0 -11
  123. data/app/components/flowbite/input_field/checkbox.html.erb +0 -14
  124. data/app/components/flowbite/input_field/input_field.html.erb +0 -8
  125. data/app/components/flowbite/input_field/radio_button.html.erb +0 -14
  126. data/app/components/flowbite/link.rb +0 -21
  127. data/app/components/flowbite/toast/toast.html.erb +0 -11
  128. /data/app/components/{flowbite → uchi/flowbite}/toast/icon.html.erb +0 -0
@@ -0,0 +1,9 @@
1
+ <nav
2
+ class="
3
+ w-64 h-full px-3 py-4 overflow-y-auto shrink-0
4
+ bg-neutral-secondary-medium
5
+ "
6
+ aria-label="Main"
7
+ >
8
+ <%= render Uchi::Ui::Navigation.new %>
9
+ </nav>
@@ -14,7 +14,7 @@
14
14
  <% end %>
15
15
 
16
16
  <div class="mx-4 md:mx-0">
17
- <%= render(Flowbite::Card.new) do %>
17
+ <%= render(Uchi::Flowbite::Card.new) do %>
18
18
  <%= form_with model: @record, url: @repository.routes.path_for(:update, id: @record.id) do |form| %>
19
19
  <div class="space-y-6">
20
20
  <% @repository.fields_for_edit.each do |field| %>
@@ -31,7 +31,7 @@
31
31
  <% end %>
32
32
 
33
33
  <%= render(Uchi::Ui::Form::Footer.new) do |footer| %>
34
- <%= footer.with_action { render(Flowbite::Button.new) { @repository.translate.submit_button } } %>
34
+ <%= footer.with_action { render(Uchi::Flowbite::Button.new) { @repository.translate.submit_button } } %>
35
35
  <%= footer.with_action { link_to(@repository.translate.link_to_cancel, path_for_cancel(default: @repository.routes.path_for(:show, id: @record.id))) } %>
36
36
  <% end %>
37
37
  </div>
@@ -6,8 +6,9 @@
6
6
  title: @repository.translate.title_for_index,
7
7
  )) do |header| %>
8
8
  <% header.with_action do %>
9
- <%= link_to(@repository.translate.link_to_new, @repository.routes.path_for(:new), class: Flowbite::Button.classes) %>
9
+ <%= link_to(@repository.translate.link_to_new, @repository.routes.path_for(:new), class: Uchi::Flowbite::Button.classes) %>
10
10
  <% end %>
11
+
11
12
  <% header.with_breadcrumb(items: [
12
13
  {href: @repository.routes.root_path, label: @repository.translate.breadcrumb_label_for_root },
13
14
  {href: @repository.routes.path_for(:index), label: @repository.translate.breadcrumb_label(:index) }
@@ -19,12 +20,19 @@
19
20
  repository: @repository,
20
21
  scope: (scoped? ? scope_params : nil),
21
22
  )) do %>
22
- <div class="relative overflow-x-auto bg-white border border-gray-200 shadow-sm md:rounded-lg dark:bg-gray-800 dark:border-gray-700">
23
+ <%# Styles from https://flowbite.com/docs/components/tables/#default-table %>
24
+ <div
25
+ class="
26
+ relative overflow-x-auto bg-neutral-primary-soft rounded-base border
27
+ border-default shadow-xs
28
+ "
29
+ >
23
30
  <% if @repository.searchable? %>
24
31
  <div class="px-3 py-3">
25
32
  <%= render(Uchi::Ui::Index::RecordsTable::SearchForm.new(params: params, repository: @repository)) %>
26
33
  </div>
27
34
  <% end %>
35
+
28
36
  <% if @records.any? %>
29
37
  <%= render(Uchi::Ui::Index::RecordsTable.new(
30
38
  columns: @columns,
@@ -35,11 +43,12 @@
35
43
  scope: (scoped? ? scope_params : nil),
36
44
  )) %>
37
45
  <% else %>
38
- <div class="p-6 text-center text-gray-500 dark:text-gray-400">
46
+ <div class="p-6 text-center text-body-subtle">
39
47
  <%= @repository.translate.no_records_found %>
40
48
  </div>
41
49
  <% end %>
42
50
  </div>
51
+
43
52
  <% if @records.any? %>
44
53
  <div class="my-4 flex justify-center">
45
54
  <%= render(Uchi::Ui::Pagination.new(paginator: @paginator)) %>
@@ -12,7 +12,7 @@
12
12
  ]) %>
13
13
  <% end %>
14
14
 
15
- <%= render(Flowbite::Card.new) do %>
15
+ <%= render(Uchi::Flowbite::Card.new) do %>
16
16
  <%= form_with model: @record, url: @repository.routes.path_for(:create) do |form| %>
17
17
  <div class="space-y-6">
18
18
  <% @repository.fields_for_edit.each do |field| %>
@@ -29,7 +29,7 @@
29
29
  <% end %>
30
30
 
31
31
  <%= render(Uchi::Ui::Form::Footer.new) do |footer| %>
32
- <%= footer.with_action { render(Flowbite::Button.new) { @repository.translate.submit_button } } %>
32
+ <%= footer.with_action { render(Uchi::Flowbite::Button.new) { @repository.translate.submit_button } } %>
33
33
  <%= footer.with_action { link_to(@repository.translate.link_to_cancel, path_for_cancel(default: @repository.routes.path_for(:index))) } %>
34
34
  <% end %>
35
35
  </div>
@@ -6,12 +6,22 @@
6
6
  title: @repository.translate.title_for_show(@record),
7
7
  )
8
8
  ) do |header| %>
9
+ <% if @repository.actions.any? %>
10
+ <% header.with_action do %>
11
+ <%= render(Uchi::Ui::Actions::Dropdown.new(
12
+ actions: @repository.actions,
13
+ record: @record,
14
+ repository: @repository
15
+ )) %>
16
+ <% end %>
17
+ <% end %>
18
+
9
19
  <% header.with_action do %>
10
20
  <%= button_to(
11
21
  @repository.translate.link_to_destroy(@record),
12
22
  @repository.routes.path_for(:destroy, id: @record.id),
13
23
  method: :delete,
14
- class: Flowbite::Button.classes(style: :red),
24
+ class: Uchi::Flowbite::Button.classes(style: :danger),
15
25
  data: {
16
26
  :"turbo-confirm" => @repository.translate.destroy_dialog_title(@record)
17
27
  }
@@ -19,7 +29,7 @@
19
29
  <% end %>
20
30
 
21
31
  <% header.with_action do %>
22
- <%= link_to(@repository.translate.link_to_edit(@record), @repository.routes.path_for(:edit, id: @record.id), class: Flowbite::Button.classes) %>
32
+ <%= link_to(@repository.translate.link_to_edit(@record), @repository.routes.path_for(:edit, id: @record.id), class: Uchi::Flowbite::Button.classes) %>
23
33
  <% end %>
24
34
 
25
35
  <% header.with_breadcrumb(items: [
@@ -36,6 +46,7 @@
36
46
  )) %>
37
47
 
38
48
  <% association_fields = @repository.fields_for_show.select{ |field| field.group_as(:show) == :associations } %>
49
+
39
50
  <% association_fields.each do |field| %>
40
51
  <%= render(field.show_component(record: @record, repository: @repository)) %>
41
52
  <% end %>
@@ -2,10 +2,5 @@
2
2
 
3
3
  module Uchi
4
4
  class <%= name.pluralize %>Controller < Uchi::RepositoryController
5
- private
6
-
7
- def repository_class
8
- Uchi::Repositories::<%= class_name %>
9
- end
10
5
  end
11
6
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/rails/resource/resource_generator"
4
+
5
+ module Uchi
6
+ class ScaffoldGenerator < Rails::Generators::ResourceGenerator # :nodoc:
7
+ remove_hook_for :resource_controller
8
+ remove_hook_for :resource_route
9
+ remove_class_option :actions
10
+
11
+ def generate_repository
12
+ generate("uchi:repository", name)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ # Base class for all Uchi actions.
5
+ #
6
+ # Actions allow you to perform custom operations on one or more records from
7
+ # a repository. Examples include publishing posts, exporting data, or sending
8
+ # notifications.
9
+ #
10
+ # To create an action, subclass this class and implement the `perform` method:
11
+ #
12
+ # class PublishPost < Uchi::Action
13
+ # def perform(records, input = {})
14
+ # records.each { |record| record.update!(published: true) }
15
+ # Uchi::ActionResponse.success("Published #{records.size} posts")
16
+ # end
17
+ # end
18
+ #
19
+ # Actions are registered on repositories via the `actions` method:
20
+ #
21
+ # class PostRepository < Uchi::Repository
22
+ # def actions
23
+ # [PublishPost.new]
24
+ # end
25
+ # end
26
+ class Action
27
+ # Returns the display name for this action.
28
+ #
29
+ # By default, this looks up the translation key
30
+ # `uchi.action.[action_key].name` and falls back to the humanized class
31
+ # name.
32
+ #
33
+ # @return [String]
34
+ def name
35
+ translate(:name, default: self.class.name.demodulize.titleize)
36
+ end
37
+
38
+ # Returns the list of fields to show in the action form.
39
+ #
40
+ # Fields are instances of Uchi::Field subclasses (e.g., Field::String,
41
+ # Field::Boolean).
42
+ #
43
+ # @return [Array<Uchi::Field>]
44
+ def fields
45
+ []
46
+ end
47
+
48
+ # Performs the action on the given records.
49
+ #
50
+ # This method must be implemented in subclasses.
51
+ #
52
+ # @param records [ActiveRecord::Relation, Array] - The records to operate on
53
+ # @param input [Hash] - Hash of field values from the action form
54
+ # @return [Uchi::ActionResponse]
55
+ def perform(records, input = {})
56
+ raise NotImplementedError, "#{self.class}#perform must be implemented"
57
+ end
58
+
59
+ # Returns true if this action requires input fields.
60
+ #
61
+ # @return [Boolean]
62
+ def requires_input?
63
+ fields.any?
64
+ end
65
+
66
+ private
67
+
68
+ # Looks up i18n key with fallback
69
+ # @param key [Symbol] - the key to look up (e.g., :name)
70
+ # @param default [String] - fallback value
71
+ # @param options [Hash] - additional i18n options (count, etc.)
72
+ # @return [String, nil]
73
+ def translate(key, default:, **options)
74
+ i18n_key = "action.#{translation_key}.#{key}"
75
+ Uchi::I18n.translate(i18n_key, default: default, **options)
76
+ end
77
+
78
+ # Returns the i18n key segment for this action
79
+ # Example: PublishPost -> "publish_post"
80
+ def translation_key
81
+ self.class.name.underscore.tr("/", ".")
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ # Represents the response from executing an action.
5
+ #
6
+ # ActionResponse uses a builder pattern for fluent chaining:
7
+ #
8
+ # ActionResponse.success("Done").redirect_to(path: "/posts")
9
+ # ActionResponse.error("Failed").message("Try again later")
10
+ # ActionResponse.success("Exported").download(file_path: path, filename: "data.csv")
11
+ class ActionResponse
12
+ attr_reader :status, :message_text, :redirect_path, :file_path, :filename, :turbo_stream_block
13
+
14
+ # Creates a successful response with an optional message.
15
+ #
16
+ # @param message [String, nil] - Success message to display
17
+ # @return [ActionResponse]
18
+ def self.success(message = nil)
19
+ new(status: :success, message: message)
20
+ end
21
+
22
+ # Creates an error response with an optional message.
23
+ #
24
+ # @param message [String, nil] - Error message to display
25
+ # @return [ActionResponse]
26
+ def self.error(message = nil)
27
+ new(status: :error, message: message)
28
+ end
29
+
30
+ def initialize(status:, message: nil)
31
+ @status = status
32
+ @message_text = message
33
+ end
34
+
35
+ # Sets the message to display.
36
+ #
37
+ # @param text [String] - The message text
38
+ # @return [ActionResponse] self for chaining
39
+ def message(text)
40
+ @message_text = text
41
+ self
42
+ end
43
+
44
+ # Sets a redirect path.
45
+ #
46
+ # @param path [String] - The path to redirect to
47
+ # @return [ActionResponse] self for chaining
48
+ def redirect_to(path:)
49
+ @redirect_path = path
50
+ self
51
+ end
52
+
53
+ # Sets a file to download.
54
+ #
55
+ # @param file_path [String] - Path to the file
56
+ # @param filename [String] - Name for the downloaded file
57
+ # @return [ActionResponse] self for chaining
58
+ def download(file_path:, filename:)
59
+ @file_path = file_path
60
+ @filename = filename
61
+ self
62
+ end
63
+
64
+ # Sets a custom Turbo Stream response.
65
+ #
66
+ # @yield [stream] Provides a Turbo Stream builder
67
+ # @return [ActionResponse] self for chaining
68
+ def turbo_stream(&block)
69
+ @turbo_stream_block = block
70
+ self
71
+ end
72
+
73
+ # Returns true if this is a success response.
74
+ #
75
+ # @return [Boolean]
76
+ def success?
77
+ status == :success
78
+ end
79
+
80
+ # Returns true if this is an error response.
81
+ #
82
+ # @return [Boolean]
83
+ def error?
84
+ status == :error
85
+ end
86
+
87
+ # Returns true if this response includes a redirect.
88
+ #
89
+ # @return [Boolean]
90
+ def redirect?
91
+ !redirect_path.nil?
92
+ end
93
+
94
+ # Returns true if this response includes a download.
95
+ #
96
+ # @return [Boolean]
97
+ def download?
98
+ !file_path.nil?
99
+ end
100
+
101
+ # Returns true if this response includes a custom Turbo Stream.
102
+ #
103
+ # @return [Boolean]
104
+ def turbo_stream?
105
+ !turbo_stream_block.nil?
106
+ end
107
+ end
108
+ end
@@ -5,6 +5,20 @@ module Uchi
5
5
  class Translate
6
6
  attr_reader :repository
7
7
 
8
+ # Returns the label for the actions button in the UI.
9
+ #
10
+ # Returns the first of the following that is present:
11
+ # 1. uchi.repository.author.button.actions
12
+ # 2. uchi.common.actions
13
+ # 3. "Actions"
14
+ def actions_button_label
15
+ first_present_value(
16
+ translate(i18n_scope("button.actions"), default: nil),
17
+ translate("common.actions", default: nil),
18
+ "Actions"
19
+ )
20
+ end
21
+
8
22
  # Returns the breadcrumb label for the given page.
9
23
  #
10
24
  # Example translation key:
@@ -61,6 +61,16 @@ module Uchi
61
61
  apply_sort_order(query, sort_order)
62
62
  end
63
63
 
64
+ # Finds multiple records by their IDs. If a record is not found, it is
65
+ # ignored.
66
+ #
67
+ # @param ids [Array<Integer>] The IDs of the records to find
68
+ #
69
+ # @return [ActiveRecord::Relation] The found records
70
+ def find_many(ids)
71
+ model.where(id: ids)
72
+ end
73
+
64
74
  def find(id)
65
75
  model.find(id)
66
76
  end
@@ -74,6 +84,21 @@ module Uchi
74
84
  []
75
85
  end
76
86
 
87
+ # Returns the list of actions available for this repository.
88
+ #
89
+ # Actions are instances of Uchi::Action subclasses that can be executed
90
+ # on one or more records.
91
+ #
92
+ # Example:
93
+ # def actions
94
+ # [PublishPost.new, ExportToCsv.new]
95
+ # end
96
+ #
97
+ # @return [Array<Uchi::Action>]
98
+ def actions
99
+ []
100
+ end
101
+
77
102
  def model
78
103
  self.class.model
79
104
  end
data/lib/uchi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uchi
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/uchi.rb CHANGED
@@ -7,6 +7,8 @@ require "view_component"
7
7
  require "uchi/version"
8
8
  require "uchi/engine"
9
9
 
10
+ require "uchi/action"
11
+ require "uchi/action_response"
10
12
  require "uchi/field"
11
13
  require "uchi/i18n"
12
14
  require "uchi/repository"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakob Skjerning
@@ -68,55 +68,6 @@ files:
68
68
  - app/assets/stylesheets/uchi/application.css
69
69
  - app/assets/stylesheets/uchi/uchi.css
70
70
  - app/assets/tailwind/uchi.css
71
- - app/components/flowbite/breadcrumb.rb
72
- - app/components/flowbite/breadcrumb_home.rb
73
- - app/components/flowbite/breadcrumb_item.rb
74
- - app/components/flowbite/breadcrumb_item/current.rb
75
- - app/components/flowbite/breadcrumb_item/first.rb
76
- - app/components/flowbite/breadcrumb_separator.rb
77
- - app/components/flowbite/button.rb
78
- - app/components/flowbite/button/outline.rb
79
- - app/components/flowbite/button/pill.rb
80
- - app/components/flowbite/card.rb
81
- - app/components/flowbite/input/checkbox.rb
82
- - app/components/flowbite/input/date.rb
83
- - app/components/flowbite/input/date_time.rb
84
- - app/components/flowbite/input/email.rb
85
- - app/components/flowbite/input/field.rb
86
- - app/components/flowbite/input/file.rb
87
- - app/components/flowbite/input/hint.rb
88
- - app/components/flowbite/input/label.rb
89
- - app/components/flowbite/input/number.rb
90
- - app/components/flowbite/input/password.rb
91
- - app/components/flowbite/input/phone.rb
92
- - app/components/flowbite/input/radio_button.rb
93
- - app/components/flowbite/input/select.rb
94
- - app/components/flowbite/input/textarea.rb
95
- - app/components/flowbite/input/url.rb
96
- - app/components/flowbite/input/validation_error.rb
97
- - app/components/flowbite/input_field.rb
98
- - app/components/flowbite/input_field/checkbox.html.erb
99
- - app/components/flowbite/input_field/checkbox.rb
100
- - app/components/flowbite/input_field/date.rb
101
- - app/components/flowbite/input_field/date_time.rb
102
- - app/components/flowbite/input_field/email.rb
103
- - app/components/flowbite/input_field/file.rb
104
- - app/components/flowbite/input_field/input_field.html.erb
105
- - app/components/flowbite/input_field/number.rb
106
- - app/components/flowbite/input_field/password.rb
107
- - app/components/flowbite/input_field/phone.rb
108
- - app/components/flowbite/input_field/radio_button.html.erb
109
- - app/components/flowbite/input_field/radio_button.rb
110
- - app/components/flowbite/input_field/select.rb
111
- - app/components/flowbite/input_field/text.rb
112
- - app/components/flowbite/input_field/textarea.rb
113
- - app/components/flowbite/input_field/url.rb
114
- - app/components/flowbite/link.rb
115
- - app/components/flowbite/style.rb
116
- - app/components/flowbite/toast.rb
117
- - app/components/flowbite/toast/icon.html.erb
118
- - app/components/flowbite/toast/icon.rb
119
- - app/components/flowbite/toast/toast.html.erb
120
71
  - app/components/uchi/field/base.rb
121
72
  - app/components/uchi/field/belongs_to.rb
122
73
  - app/components/uchi/field/belongs_to/edit.html.erb
@@ -169,6 +120,59 @@ files:
169
120
  - app/components/uchi/field/text/edit.html.erb
170
121
  - app/components/uchi/field/text/index.html.erb
171
122
  - app/components/uchi/field/text/show.html.erb
123
+ - app/components/uchi/flowbite/breadcrumb.rb
124
+ - app/components/uchi/flowbite/breadcrumb_home.rb
125
+ - app/components/uchi/flowbite/breadcrumb_item.rb
126
+ - app/components/uchi/flowbite/breadcrumb_item/current.rb
127
+ - app/components/uchi/flowbite/breadcrumb_item/first.rb
128
+ - app/components/uchi/flowbite/breadcrumb_separator.rb
129
+ - app/components/uchi/flowbite/button.rb
130
+ - app/components/uchi/flowbite/button/outline.rb
131
+ - app/components/uchi/flowbite/button/pill.rb
132
+ - app/components/uchi/flowbite/card.rb
133
+ - app/components/uchi/flowbite/card/card.html.erb
134
+ - app/components/uchi/flowbite/card/title.rb
135
+ - app/components/uchi/flowbite/input/checkbox.rb
136
+ - app/components/uchi/flowbite/input/date.rb
137
+ - app/components/uchi/flowbite/input/date_time.rb
138
+ - app/components/uchi/flowbite/input/email.rb
139
+ - app/components/uchi/flowbite/input/field.rb
140
+ - app/components/uchi/flowbite/input/file.rb
141
+ - app/components/uchi/flowbite/input/hint.rb
142
+ - app/components/uchi/flowbite/input/label.rb
143
+ - app/components/uchi/flowbite/input/number.rb
144
+ - app/components/uchi/flowbite/input/password.rb
145
+ - app/components/uchi/flowbite/input/phone.rb
146
+ - app/components/uchi/flowbite/input/radio_button.rb
147
+ - app/components/uchi/flowbite/input/select.rb
148
+ - app/components/uchi/flowbite/input/textarea.rb
149
+ - app/components/uchi/flowbite/input/url.rb
150
+ - app/components/uchi/flowbite/input/validation_error.rb
151
+ - app/components/uchi/flowbite/input_field.rb
152
+ - app/components/uchi/flowbite/input_field/checkbox.html.erb
153
+ - app/components/uchi/flowbite/input_field/checkbox.rb
154
+ - app/components/uchi/flowbite/input_field/date.rb
155
+ - app/components/uchi/flowbite/input_field/date_time.rb
156
+ - app/components/uchi/flowbite/input_field/email.rb
157
+ - app/components/uchi/flowbite/input_field/file.rb
158
+ - app/components/uchi/flowbite/input_field/input_field.html.erb
159
+ - app/components/uchi/flowbite/input_field/number.rb
160
+ - app/components/uchi/flowbite/input_field/password.rb
161
+ - app/components/uchi/flowbite/input_field/phone.rb
162
+ - app/components/uchi/flowbite/input_field/radio_button.html.erb
163
+ - app/components/uchi/flowbite/input_field/radio_button.rb
164
+ - app/components/uchi/flowbite/input_field/select.rb
165
+ - app/components/uchi/flowbite/input_field/text.rb
166
+ - app/components/uchi/flowbite/input_field/textarea.rb
167
+ - app/components/uchi/flowbite/input_field/url.rb
168
+ - app/components/uchi/flowbite/link.rb
169
+ - app/components/uchi/flowbite/style.rb
170
+ - app/components/uchi/flowbite/toast.rb
171
+ - app/components/uchi/flowbite/toast/icon.html.erb
172
+ - app/components/uchi/flowbite/toast/icon.rb
173
+ - app/components/uchi/flowbite/toast/toast.html.erb
174
+ - app/components/uchi/ui/actions/dropdown.rb
175
+ - app/components/uchi/ui/actions/dropdown/dropdown.html.erb
172
176
  - app/components/uchi/ui/breadcrumb.rb
173
177
  - app/components/uchi/ui/breadcrumb/breadcrumb.html.erb
174
178
  - app/components/uchi/ui/form/footer.rb
@@ -182,6 +186,10 @@ files:
182
186
  - app/components/uchi/ui/index/records_table/search_form.rb
183
187
  - app/components/uchi/ui/index/records_table/search_form/search_form.html.erb
184
188
  - app/components/uchi/ui/index/turbo_frame.rb
189
+ - app/components/uchi/ui/navigation.rb
190
+ - app/components/uchi/ui/navigation/navigation.html.erb
191
+ - app/components/uchi/ui/no_value.rb
192
+ - app/components/uchi/ui/no_value/no_value.html.erb
185
193
  - app/components/uchi/ui/page_header.rb
186
194
  - app/components/uchi/ui/page_header/page_header.html.erb
187
195
  - app/components/uchi/ui/pagination.rb
@@ -201,6 +209,7 @@ files:
201
209
  - app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb
202
210
  - app/components/uchi/ui/spinner.rb
203
211
  - app/components/uchi/ui/spinner/spinner.html.erb
212
+ - app/controllers/uchi/actions/executions_controller.rb
204
213
  - app/controllers/uchi/application_controller.rb
205
214
  - app/controllers/uchi/controller.rb
206
215
  - app/controllers/uchi/repository_controller.rb
@@ -209,6 +218,7 @@ files:
209
218
  - app/mailers/uchi/application_mailer.rb
210
219
  - app/views/layouts/uchi/_flash_messages.html.erb
211
220
  - app/views/layouts/uchi/application.html.erb
221
+ - app/views/uchi/navigation/_main.html.erb
212
222
  - app/views/uchi/repository/edit.html.erb
213
223
  - app/views/uchi/repository/index.html.erb
214
224
  - app/views/uchi/repository/new.html.erb
@@ -218,8 +228,11 @@ files:
218
228
  - lib/generators/uchi/install/install_generator.rb
219
229
  - lib/generators/uchi/repository/repository_generator.rb
220
230
  - lib/generators/uchi/repository/templates/repository.rb.tt
231
+ - lib/generators/uchi/scaffold/scaffold_generator.rb
221
232
  - lib/tasks/uchi_tasks.rake
222
233
  - lib/uchi.rb
234
+ - lib/uchi/action.rb
235
+ - lib/uchi/action_response.rb
223
236
  - lib/uchi/application_record.rb
224
237
  - lib/uchi/engine.rb
225
238
  - lib/uchi/field.rb
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Flowbite
4
- class Button
5
- class Outline < Flowbite::Button
6
- class << self
7
- # rubocop:disable Layout/LineLength
8
- def styles
9
- {
10
- default: Flowbite::Style.new(
11
- default: ["text-blue-700", "hover:text-white", "border", "border-blue-700", "hover:bg-blue-800", "focus:ring-4", "focus:outline-none", "focus:ring-blue-300", "font-medium", "rounded-lg", "text-center", "me-2", "mb-2", "dark:border-blue-500", "dark:text-blue-500", "dark:hover:text-white", "dark:hover:bg-blue-500", "dark:focus:ring-blue-800"]
12
- ),
13
- green: Flowbite::Style.new(
14
- default: ["text-green-700", "hover:text-white", "border", "border-green-700", "hover:bg-green-800", "focus:ring-4", "focus:outline-none", "focus:ring-green-300", "font-medium", "rounded-lg", "text-center", "me-2", "mb-2", "dark:border-green-500", "dark:text-green-500", "dark:hover:text-white", "dark:hover:bg-green-600", "dark:focus:ring-green-800"]
15
- )
16
- }
17
- end
18
- # rubocop:enable Layout/LineLength
19
- end
20
- end
21
- end
22
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Flowbite
4
- class Button
5
- class Pill < Flowbite::Button
6
- class << self
7
- # rubocop:disable Layout/LineLength, Metrics/MethodLength
8
- def styles
9
- {
10
- alternative: Flowbite::Style.new(
11
- default: ["text-sm", "font-medium", "text-gray-900", "focus:outline-none", "bg-white", "rounded-full", "border", "border-gray-200", "hover:bg-gray-100", "hover:text-blue-700", "focus:z-10", "focus:ring-4", "focus:ring-gray-100", "dark:focus:ring-gray-700", "dark:bg-gray-800", "dark:text-gray-400", "dark:border-gray-600", "dark:hover:text-white", "dark:hover:bg-gray-700"]
12
- ),
13
- dark: Flowbite::Style.new(
14
- default: ["text-white", "bg-gray-800", "hover:bg-gray-900", "focus:outline-none", "focus:ring-4", "focus:ring-gray-300", "font-medium", "rounded-full", "dark:bg-gray-800", "dark:hover:bg-gray-700", "dark:focus:ring-gray-700", "dark:border-gray-700"]
15
- ),
16
- default: Flowbite::Style.new(
17
- default: ["text-white", "bg-blue-700", "hover:bg-blue-800", "focus:outline-none", "focus:ring-4", "focus:ring-blue-300", "font-medium", "rounded-full", "text-center", "dark:bg-blue-600", "dark:hover:bg-blue-700", "dark:focus:ring-blue-800"]
18
- ),
19
- green: Flowbite::Style.new(
20
- default: ["text-white", "bg-green-700", "hover:bg-green-800", "focus:outline-none", "focus:ring-4", "focus:ring-green-300", "font-medium", "rounded-full", "text-center", "dark:bg-green-600", "dark:hover:bg-green-700", "dark:focus:ring-green-800"]
21
- ),
22
- light: Flowbite::Style.new(
23
- default: ["text-gray-900", "bg-white", "border", "border-gray-300", "focus:outline-none", "hover:bg-gray-100", "focus:ring-4", "focus:ring-gray-100", "font-medium", "rounded-full", "dark:bg-gray-800", "dark:text-white", "dark:border-gray-600", "dark:hover:bg-gray-700", "dark:hover:border-gray-600", "dark:focus:ring-gray-700"]
24
- ),
25
- purple: Flowbite::Style.new(
26
- default: ["text-white", "bg-purple-700", "hover:bg-purple-800", "focus:outline-none", "focus:ring-4", "focus:ring-purple-300", "font-medium", "rounded-full", "text-center", "dark:bg-purple-600", "dark:hover:bg-purple-700", "dark:focus:ring-purple-900"]
27
- ),
28
- red: Flowbite::Style.new(
29
- default: ["text-white", "bg-red-700", "hover:bg-red-800", "focus:outline-none", "focus:ring-4", "focus:ring-red-300", "font-medium", "rounded-full", "text-center", "dark:bg-red-600", "dark:hover:bg-red-700", "dark:focus:ring-red-900"]
30
- ),
31
- yellow: Flowbite::Style.new(
32
- default: ["text-white", "bg-yellow-400", "hover:bg-yellow-500", "focus:outline-none", "focus:ring-4", "focus:ring-yellow-300", "font-medium", "rounded-full", "text-center", "dark:focus:ring-yellow-900"]
33
- )
34
- }
35
- end
36
- # rubocop:enable Layout/LineLength, Metrics/MethodLength
37
- end
38
- end
39
- end
40
- end