uchi 0.1.2 → 0.1.4
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.
- checksums.yaml +4 -4
- data/README.md +222 -0
- data/app/assets/config/uchi_manifest.js +2 -0
- data/app/assets/javascripts/uchi/application.js +6095 -0
- data/app/assets/javascripts/uchi.js +4 -0
- data/app/assets/stylesheets/uchi/application.css +3971 -0
- data/app/assets/stylesheets/uchi/uchi.css +17 -0
- data/app/assets/tailwind/uchi.css +21 -0
- data/app/components/flowbite/breadcrumb.rb +33 -0
- data/app/components/flowbite/breadcrumb_home.rb +26 -0
- data/app/components/flowbite/breadcrumb_item/current.rb +33 -0
- data/app/components/flowbite/breadcrumb_item/first.rb +35 -0
- data/app/components/flowbite/breadcrumb_item.rb +48 -0
- data/app/components/flowbite/breadcrumb_separator.rb +30 -0
- data/app/components/flowbite/button/outline.rb +22 -0
- data/app/components/flowbite/button/pill.rb +40 -0
- data/app/components/flowbite/button.rb +92 -0
- data/app/components/flowbite/card.rb +45 -0
- data/app/components/flowbite/input/checkbox.rb +73 -0
- data/app/components/flowbite/input/date.rb +11 -0
- data/app/components/flowbite/input/date_time.rb +11 -0
- data/app/components/flowbite/input/email.rb +12 -0
- data/app/components/flowbite/input/field.rb +117 -0
- data/app/components/flowbite/input/file.rb +30 -0
- data/app/components/flowbite/input/hint.rb +57 -0
- data/app/components/flowbite/input/label.rb +82 -0
- data/app/components/flowbite/input/number.rb +11 -0
- data/app/components/flowbite/input/password.rb +11 -0
- data/app/components/flowbite/input/phone.rb +11 -0
- data/app/components/flowbite/input/radio_button.rb +50 -0
- data/app/components/flowbite/input/select.rb +49 -0
- data/app/components/flowbite/input/textarea.rb +42 -0
- data/app/components/flowbite/input/url.rb +12 -0
- data/app/components/flowbite/input/validation_error.rb +11 -0
- data/app/components/flowbite/input_field/checkbox.html.erb +14 -0
- data/app/components/flowbite/input_field/checkbox.rb +54 -0
- data/app/components/flowbite/input_field/date.rb +13 -0
- data/app/components/flowbite/input_field/date_time.rb +13 -0
- data/app/components/flowbite/input_field/email.rb +13 -0
- data/app/components/flowbite/input_field/file.rb +13 -0
- data/app/components/flowbite/input_field/input_field.html.erb +8 -0
- data/app/components/flowbite/input_field/number.rb +13 -0
- data/app/components/flowbite/input_field/password.rb +13 -0
- data/app/components/flowbite/input_field/phone.rb +13 -0
- data/app/components/flowbite/input_field/radio_button.html.erb +14 -0
- data/app/components/flowbite/input_field/radio_button.rb +86 -0
- data/app/components/flowbite/input_field/select.rb +31 -0
- data/app/components/flowbite/input_field/text.rb +8 -0
- data/app/components/flowbite/input_field/textarea.rb +13 -0
- data/app/components/flowbite/input_field/url.rb +13 -0
- data/app/components/flowbite/input_field.rb +192 -0
- data/app/components/flowbite/link.rb +21 -0
- data/app/components/flowbite/style.rb +13 -0
- data/app/components/flowbite/toast/icon.html.erb +5 -0
- data/app/components/flowbite/toast/icon.rb +57 -0
- data/app/components/flowbite/toast/toast.html.erb +11 -0
- data/app/components/flowbite/toast.rb +34 -0
- data/app/components/uchi/field/base.rb +57 -0
- data/app/components/uchi/field/belongs_to/edit.html.erb +1 -0
- data/app/components/uchi/field/belongs_to/index.html.erb +1 -0
- data/app/components/uchi/field/belongs_to/show.html.erb +3 -0
- data/app/components/uchi/field/belongs_to.rb +105 -0
- data/app/components/uchi/field/blank/edit.html.erb +1 -0
- data/app/components/uchi/field/blank/index.html.erb +1 -0
- data/app/components/uchi/field/blank/show.html.erb +1 -0
- data/app/components/uchi/field/blank.rb +16 -0
- data/app/components/uchi/field/boolean/edit.html.erb +1 -0
- data/app/components/uchi/field/boolean/index.html.erb +9 -0
- data/app/components/uchi/field/boolean/show.html.erb +9 -0
- data/app/components/uchi/field/boolean.rb +27 -0
- data/app/components/uchi/field/date/edit.html.erb +1 -0
- data/app/components/uchi/field/date/index.html.erb +1 -0
- data/app/components/uchi/field/date/show.html.erb +1 -0
- data/app/components/uchi/field/date.rb +27 -0
- data/app/components/uchi/field/date_time/edit.html.erb +1 -0
- data/app/components/uchi/field/date_time/index.html.erb +1 -0
- data/app/components/uchi/field/date_time/show.html.erb +1 -0
- data/app/components/uchi/field/date_time.rb +27 -0
- data/app/components/uchi/field/file/edit.html.erb +1 -0
- data/app/components/uchi/field/file/index.html.erb +6 -0
- data/app/components/uchi/field/file/show.html.erb +8 -0
- data/app/components/uchi/field/file.rb +37 -0
- data/app/components/uchi/field/has_and_belongs_to_many/edit.html.erb +9 -0
- data/app/components/uchi/field/has_and_belongs_to_many/index.html.erb +1 -0
- data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +28 -0
- data/app/components/uchi/field/has_and_belongs_to_many.rb +131 -0
- data/app/components/uchi/field/has_many/edit.html.erb +1 -0
- data/app/components/uchi/field/has_many/index.html.erb +1 -0
- data/app/components/uchi/field/has_many/show.html.erb +28 -0
- data/app/components/uchi/field/has_many.rb +107 -0
- data/app/components/uchi/field/id/index.html.erb +4 -0
- data/app/components/uchi/field/id/show.html.erb +4 -0
- data/app/components/uchi/field/id.rb +26 -0
- data/app/components/uchi/field/image/edit.html.erb +1 -0
- data/app/components/uchi/field/image/index.html.erb +6 -0
- data/app/components/uchi/field/image/show.html.erb +6 -0
- data/app/components/uchi/field/image.rb +38 -0
- data/app/components/uchi/field/number/edit.html.erb +1 -0
- data/app/components/uchi/field/number/index.html.erb +1 -0
- data/app/components/uchi/field/number/show.html.erb +1 -0
- data/app/components/uchi/field/number.rb +32 -0
- data/app/components/uchi/field/string/edit.html.erb +1 -0
- data/app/components/uchi/field/string/index.html.erb +1 -0
- data/app/components/uchi/field/string/show.html.erb +1 -0
- data/app/components/uchi/field/string.rb +33 -0
- data/app/components/uchi/field/text/edit.html.erb +1 -0
- data/app/components/uchi/field/text/index.html.erb +1 -0
- data/app/components/uchi/field/text/show.html.erb +1 -0
- data/app/components/uchi/field/text.rb +38 -0
- data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +13 -0
- data/app/components/uchi/ui/breadcrumb.rb +14 -0
- data/app/components/uchi/ui/form/footer/footer.html.erb +5 -0
- data/app/components/uchi/ui/form/footer.rb +15 -0
- data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +32 -0
- data/app/components/uchi/ui/form/input/collection_checkboxes.rb +125 -0
- data/app/components/uchi/ui/frame/frame.html.erb +3 -0
- data/app/components/uchi/ui/frame.rb +10 -0
- data/app/components/uchi/ui/index/records_table/records_table.html.erb +67 -0
- data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -0
- data/app/components/uchi/ui/index/records_table/search_form.rb +49 -0
- data/app/components/uchi/ui/index/records_table.rb +29 -0
- data/app/components/uchi/ui/index/turbo_frame.rb +50 -0
- data/app/components/uchi/ui/page_header/page_header.html.erb +24 -0
- data/app/components/uchi/ui/page_header.rb +18 -0
- data/app/components/uchi/ui/pagination/current_link.html.erb +3 -0
- data/app/components/uchi/ui/pagination/current_link.rb +10 -0
- data/app/components/uchi/ui/pagination/gap.html.erb +3 -0
- data/app/components/uchi/ui/pagination/gap.rb +10 -0
- data/app/components/uchi/ui/pagination/item.rb +24 -0
- data/app/components/uchi/ui/pagination/link.html.erb +3 -0
- data/app/components/uchi/ui/pagination/link.rb +10 -0
- data/app/components/uchi/ui/pagination/next_link.html.erb +8 -0
- data/app/components/uchi/ui/pagination/next_link.rb +22 -0
- data/app/components/uchi/ui/pagination/pagination.html.erb +15 -0
- data/app/components/uchi/ui/pagination/previous_link.html.erb +8 -0
- data/app/components/uchi/ui/pagination/previous_link.rb +22 -0
- data/app/components/uchi/ui/pagination.rb +48 -0
- data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +14 -0
- data/app/components/uchi/ui/show/attribute_fields.rb +18 -0
- data/app/components/uchi/ui/spinner/spinner.html.erb +7 -0
- data/app/components/uchi/ui/spinner.rb +15 -0
- data/app/controllers/uchi/application_controller.rb +4 -0
- data/app/controllers/uchi/controller.rb +13 -0
- data/app/controllers/uchi/repository_controller.rb +166 -0
- data/app/helpers/uchi/application_helper.rb +17 -0
- data/app/jobs/uchi/application_job.rb +4 -0
- data/app/mailers/uchi/application_mailer.rb +6 -0
- data/app/views/layouts/uchi/_flash_messages.html.erb +10 -0
- data/app/views/layouts/uchi/application.html.erb +33 -0
- data/app/views/uchi/repository/edit.html.erb +40 -0
- data/app/views/uchi/repository/index.html.erb +49 -0
- data/app/views/uchi/repository/new.html.erb +37 -0
- data/app/views/uchi/repository/show.html.erb +41 -0
- data/lib/generators/uchi/controller/controller_generator.rb +16 -0
- data/lib/generators/uchi/controller/templates/controller.rb.tt +11 -0
- data/lib/generators/uchi/install/install_generator.rb +13 -0
- data/lib/generators/uchi/repository/repository_generator.rb +16 -0
- data/lib/generators/uchi/repository/templates/repository.rb.tt +11 -0
- data/lib/tasks/uchi_tasks.rake +4 -0
- data/lib/uchi/application_record.rb +5 -0
- data/lib/uchi/engine.rb +24 -0
- data/lib/uchi/field/configuration.rb +142 -0
- data/lib/uchi/field.rb +80 -0
- data/lib/uchi/i18n.rb +13 -0
- data/lib/uchi/pagination/controller.rb +26 -0
- data/lib/uchi/pagination/page.rb +20 -0
- data/lib/uchi/pagy/LICENSE.txt +21 -0
- data/lib/uchi/pagy/classes/exceptions.rb +35 -0
- data/lib/uchi/pagy/classes/offset/offset.rb +56 -0
- data/lib/uchi/pagy/classes/request.rb +38 -0
- data/lib/uchi/pagy/modules/abilities/configurable.rb +38 -0
- data/lib/uchi/pagy/modules/abilities/linkable.rb +62 -0
- data/lib/uchi/pagy/modules/abilities/rangeable.rb +17 -0
- data/lib/uchi/pagy/modules/abilities/shiftable.rb +14 -0
- data/lib/uchi/pagy/modules/console.rb +40 -0
- data/lib/uchi/pagy/toolbox/helpers/loader.rb +19 -0
- data/lib/uchi/pagy/toolbox/helpers/page_url.rb +25 -0
- data/lib/uchi/pagy/toolbox/paginators/method.rb +21 -0
- data/lib/uchi/pagy/toolbox/paginators/offset.rb +35 -0
- data/lib/uchi/pagy.rb +60 -0
- data/lib/uchi/repository/routes.rb +62 -0
- data/lib/uchi/repository/translate.rb +284 -0
- data/lib/uchi/repository.rb +156 -0
- data/lib/uchi/sort_order.rb +35 -0
- data/lib/uchi/version.rb +5 -0
- data/lib/uchi.rb +18 -0
- data/uchi.gemspec +35 -0
- metadata +197 -107
- data/.github/dependabot.yml +0 -17
- data/.github/workflows/build.yml +0 -23
- data/.github/workflows/lint.yml +0 -30
- data/package.json +0 -31
- data/sig/uchi.rbs +0 -4
- data/test/components/uchi/field/belongs_to_test.rb +0 -134
- data/test/components/uchi/field/blank_test.rb +0 -119
- data/test/components/uchi/field/boolean_test.rb +0 -163
- data/test/components/uchi/field/date_test.rb +0 -163
- data/test/components/uchi/field/date_time_test.rb +0 -152
- data/test/components/uchi/field/has_many_test.rb +0 -138
- data/test/components/uchi/field/id_test.rb +0 -113
- data/test/components/uchi/field/number_test.rb +0 -163
- data/test/components/uchi/field/string_test.rb +0 -159
- data/test/controllers/uchi/authors_controller_test.rb +0 -119
- data/test/controllers/uchi/repository_controller_test.rb +0 -93
- data/test/controllers/uchi/scoped_repository_controller_test.rb +0 -73
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -4
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/controllers/uchi/authors_controller.rb +0 -7
- data/test/dummy/app/controllers/uchi/books_controller.rb +0 -7
- data/test/dummy/app/controllers/uchi/titles_controller.rb +0 -7
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/jobs/application_job.rb +0 -7
- data/test/dummy/app/mailers/application_mailer.rb +0 -4
- data/test/dummy/app/models/application_record.rb +0 -3
- data/test/dummy/app/models/author.rb +0 -3
- data/test/dummy/app/models/book.rb +0 -3
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/title.rb +0 -3
- data/test/dummy/app/uchi/repositories/author.rb +0 -20
- data/test/dummy/app/uchi/repositories/book.rb +0 -16
- data/test/dummy/app/uchi/repositories/title.rb +0 -17
- data/test/dummy/app/views/layouts/application.html.erb +0 -27
- data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
- data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
- data/test/dummy/app/views/pwa/manifest.json.erb +0 -22
- data/test/dummy/app/views/pwa/service-worker.js +0 -26
- data/test/dummy/bin/dev +0 -2
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -34
- data/test/dummy/config/application.rb +0 -29
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/cable.yml +0 -10
- data/test/dummy/config/database.yml +0 -32
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -69
- data/test/dummy/config/environments/production.rb +0 -89
- data/test/dummy/config/environments/test.rb +0 -53
- data/test/dummy/config/initializers/content_security_policy.rb +0 -25
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -8
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/locales/da.yml +0 -51
- data/test/dummy/config/locales/en.yml +0 -31
- data/test/dummy/config/puma.rb +0 -38
- data/test/dummy/config/routes.rb +0 -9
- data/test/dummy/config/storage.yml +0 -34
- data/test/dummy/config.ru +0 -6
- data/test/dummy/db/migrate/20251002183635_create_authors.rb +0 -11
- data/test/dummy/db/migrate/20251005131726_create_books.rb +0 -9
- data/test/dummy/db/migrate/20251005131811_create_titles.rb +0 -11
- data/test/dummy/db/schema.rb +0 -38
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/public/400.html +0 -114
- data/test/dummy/public/404.html +0 -114
- data/test/dummy/public/406-unsupported-browser.html +0 -114
- data/test/dummy/public/422.html +0 -114
- data/test/dummy/public/500.html +0 -114
- data/test/dummy/public/icon.png +0 -0
- data/test/dummy/public/icon.svg +0 -3
- data/test/dummy/storage/.keep +0 -0
- data/test/dummy/test/fixtures/authors.yml +0 -11
- data/test/dummy/test/models/author_test.rb +0 -7
- data/test/dummy/tmp/.keep +0 -0
- data/test/dummy/tmp/pids/.keep +0 -0
- data/test/dummy/tmp/storage/.keep +0 -0
- data/test/test_helper.rb +0 -15
- data/test/uchi/field_test.rb +0 -63
- data/test/uchi/i18n_test.rb +0 -18
- data/test/uchi/repository/routes_test.rb +0 -49
- data/test/uchi/repository/translate_test.rb +0 -263
- data/test/uchi/repository_test.rb +0 -137
- data/test/uchi/sort_order_test.rb +0 -47
- data/test/uchi_test.rb +0 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Checkbox.new(**options)) %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% if field.value(record) %>
|
|
2
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
3
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.5 11.5 11 14l4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
4
|
+
</svg>
|
|
5
|
+
<% else %>
|
|
6
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
7
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7.757 12h8.486M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
8
|
+
</svg>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% if field.value(record) %>
|
|
2
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
3
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.5 11.5 11 14l4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
4
|
+
</svg>
|
|
5
|
+
<% else %>
|
|
6
|
+
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
7
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7.757 12h8.486M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
8
|
+
</svg>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Boolean < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Show < Uchi::Field::Base::Show
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Date.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Date < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Show < Uchi::Field::Base::Show
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::DateTime.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class DateTime < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Show < Uchi::Field::Base::Show
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::File.new(**options)) %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% attachment = field.value(record) %>
|
|
2
|
+
<% if attachment.attached? %>
|
|
3
|
+
<%= render Flowbite::Link.new(href: Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)) do %>
|
|
4
|
+
<%= attachment.filename %>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% else %>
|
|
7
|
+
<span class="text-gray-400">—</span>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class File < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Show < Uchi::Field::Base::Show
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def default_searchable?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def default_sortable?
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record).count %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="my-10">
|
|
2
|
+
<h2 class="px-4 my-6 text-2xl font-bold md:px-0"><%= repository.translate.field_label(field) %></h2>
|
|
3
|
+
|
|
4
|
+
<%= render(Uchi::Ui::Index::TurboFrame.new(
|
|
5
|
+
repository: associated_repository,
|
|
6
|
+
scope: {
|
|
7
|
+
field: field.name,
|
|
8
|
+
id: record.id,
|
|
9
|
+
inverse_of: inverse_association&.name,
|
|
10
|
+
model: record.model_name,
|
|
11
|
+
},
|
|
12
|
+
src: associated_repository.routes.path_for(
|
|
13
|
+
:index,
|
|
14
|
+
scope: {
|
|
15
|
+
field: field.name,
|
|
16
|
+
id: record.id,
|
|
17
|
+
inverse_of: inverse_association&.name,
|
|
18
|
+
model: record.model_name,
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
)) 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
|
+
<div class="grid grid place-items-center p-8">
|
|
24
|
+
<%= render(Uchi::Ui::Spinner.new(message: repository.translate.loading_message)) %>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class HasAndBelongsToMany < Field
|
|
6
|
+
DEFAULT_COLLECTION_QUERY = ->(query) { query }.freeze
|
|
7
|
+
|
|
8
|
+
class Edit < Uchi::Field::Base::Edit
|
|
9
|
+
def collection
|
|
10
|
+
associated_repository = field.associated_repository(record: record)
|
|
11
|
+
|
|
12
|
+
field
|
|
13
|
+
.collection(record: record)
|
|
14
|
+
.map { |associated_record|
|
|
15
|
+
[
|
|
16
|
+
associated_repository.title(associated_record),
|
|
17
|
+
associated_record.id
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Index < Uchi::Field::Base::Index
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Show < Uchi::Field::Base::Show
|
|
27
|
+
def associated_records
|
|
28
|
+
records = field.value(record)
|
|
29
|
+
|
|
30
|
+
associated_repository.find_all(scope: records)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def associated_repository
|
|
34
|
+
field.associated_repository(record: record)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Returns the Fields to be displayed for each associated record.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Uchi::Field>]
|
|
42
|
+
def fields
|
|
43
|
+
all_fields = associated_repository.fields_for_index
|
|
44
|
+
return all_fields unless inverse_association
|
|
45
|
+
|
|
46
|
+
# Remove the field that represents the inverse association to avoid
|
|
47
|
+
# a column containing nothing but the scoped record.
|
|
48
|
+
all_fields.reject { |field| field.name == inverse_association.name }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the inverse association reflection, if any, for this
|
|
52
|
+
# association.
|
|
53
|
+
#
|
|
54
|
+
# @return [ActiveRecord::Reflection, nil]
|
|
55
|
+
def inverse_association
|
|
56
|
+
reflection = record.class.reflect_on_association(field.name)
|
|
57
|
+
reflection&.inverse_of
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the ActiveRecord::Reflection for this association.
|
|
61
|
+
#
|
|
62
|
+
# @return [ActiveRecord::Reflection]
|
|
63
|
+
def reflection
|
|
64
|
+
@reflection ||= record.class.reflect_on_association(field.name)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def associated_repository(record:)
|
|
69
|
+
reflection = record.class.reflect_on_association(name)
|
|
70
|
+
model = reflection.klass
|
|
71
|
+
repository_class = Uchi::Repository.for_model(model)
|
|
72
|
+
raise NameError, "No repository found for associated model #{model}" unless repository_class
|
|
73
|
+
|
|
74
|
+
repository_class.new
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def collection(record:)
|
|
78
|
+
query = associated_repository(record: record).find_all
|
|
79
|
+
@collection_query.call(query)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def initialize(name)
|
|
83
|
+
super
|
|
84
|
+
@collection_query = DEFAULT_COLLECTION_QUERY
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Sets or gets a custom query for filtering the collection of associated records.
|
|
88
|
+
#
|
|
89
|
+
# When called with an argument, sets the query and returns self for chaining.
|
|
90
|
+
# When called without arguments, returns the current query.
|
|
91
|
+
#
|
|
92
|
+
# @param query_proc [Proc, Symbol] A callable that receives an ActiveRecord query
|
|
93
|
+
# and returns a modified query.
|
|
94
|
+
# @return [self, Proc] Returns self for method chaining when setting,
|
|
95
|
+
# or the query proc when getting
|
|
96
|
+
#
|
|
97
|
+
# @example Setting
|
|
98
|
+
# Field::HasAndBelongsToMany.new(:tags).collection_query(->(query) {
|
|
99
|
+
# query.where(active: true)
|
|
100
|
+
# })
|
|
101
|
+
#
|
|
102
|
+
# @example Getting
|
|
103
|
+
# field.collection_query # => #<Proc...>
|
|
104
|
+
def collection_query(query_proc = Configuration::Unset)
|
|
105
|
+
return @collection_query if query_proc == Configuration::Unset
|
|
106
|
+
|
|
107
|
+
@collection_query = query_proc
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def group_as(_action)
|
|
112
|
+
:associations
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the key to use for this field in params
|
|
116
|
+
def param_key
|
|
117
|
+
association.association_foreign_key.pluralize.to_sym
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def permitted_param
|
|
121
|
+
{param_key => []}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
protected
|
|
125
|
+
|
|
126
|
+
def association
|
|
127
|
+
@association ||= repository.model.reflect_on_association(name)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%# This space intentionally left blank %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record).count %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="my-10">
|
|
2
|
+
<h2 class="px-4 my-6 text-2xl font-bold md:px-0"><%= repository.translate.field_label(field) %></h2>
|
|
3
|
+
|
|
4
|
+
<%= render(Uchi::Ui::Index::TurboFrame.new(
|
|
5
|
+
repository: associated_repository,
|
|
6
|
+
scope: {
|
|
7
|
+
field: field.name,
|
|
8
|
+
id: record.id,
|
|
9
|
+
inverse_of: inverse_association&.name,
|
|
10
|
+
model: record.model_name,
|
|
11
|
+
},
|
|
12
|
+
src: associated_repository.routes.path_for(
|
|
13
|
+
:index,
|
|
14
|
+
scope: {
|
|
15
|
+
field: field.name,
|
|
16
|
+
id: record.id,
|
|
17
|
+
inverse_of: inverse_association&.name,
|
|
18
|
+
model: record.model_name,
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
)) 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
|
+
<div class="grid grid place-items-center p-8">
|
|
24
|
+
<%= render(Uchi::Ui::Spinner.new(message: repository.translate.loading_message)) %>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class HasMany < Field
|
|
6
|
+
DEFAULT_COLLECTION_QUERY = ->(query) { query }.freeze
|
|
7
|
+
|
|
8
|
+
class Edit < Uchi::Field::Base::Edit
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Index < Uchi::Field::Base::Index
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Show < Uchi::Field::Base::Show
|
|
15
|
+
def associated_records
|
|
16
|
+
records = field.value(record)
|
|
17
|
+
|
|
18
|
+
associated_repository.find_all(scope: records)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def associated_repository
|
|
22
|
+
reflection = record.class.reflect_on_association(field.name)
|
|
23
|
+
model = reflection.klass
|
|
24
|
+
repository_class = Uchi::Repository.for_model(model)
|
|
25
|
+
raise NameError, "No repository found for associated model #{model}" unless repository_class
|
|
26
|
+
|
|
27
|
+
repository_class.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Returns the Fields to be displayed for each associated record.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<Uchi::Field>]
|
|
35
|
+
def fields
|
|
36
|
+
all_fields = associated_repository.fields_for_index
|
|
37
|
+
return all_fields unless inverse_association
|
|
38
|
+
|
|
39
|
+
# Remove the field that represents the inverse association to avoid
|
|
40
|
+
# a column containing nothing but the scoped record.
|
|
41
|
+
all_fields.reject { |field| field.name == inverse_association.name }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns the inverse association reflection, if any, for this
|
|
45
|
+
# association.
|
|
46
|
+
#
|
|
47
|
+
# @return [ActiveRecord::Reflection, nil]
|
|
48
|
+
def inverse_association
|
|
49
|
+
reflection = record.class.reflect_on_association(field.name)
|
|
50
|
+
reflection&.inverse_of
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the ActiveRecord::Reflection for this association.
|
|
54
|
+
#
|
|
55
|
+
# @return [ActiveRecord::Reflection]
|
|
56
|
+
def reflection
|
|
57
|
+
@reflection ||= record.class.reflect_on_association(field.name)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initialize(name)
|
|
62
|
+
super
|
|
63
|
+
@collection_query = DEFAULT_COLLECTION_QUERY
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Sets or gets a custom query for filtering the collection of associated records.
|
|
67
|
+
#
|
|
68
|
+
# When called with an argument, sets the query and returns self for chaining.
|
|
69
|
+
# When called without arguments, returns the current query.
|
|
70
|
+
#
|
|
71
|
+
# @param query_proc [Proc, Symbol] A callable that receives an ActiveRecord query
|
|
72
|
+
# and returns a modified query.
|
|
73
|
+
# @return [self, Proc] Returns self for method chaining when setting,
|
|
74
|
+
# or the query proc when getting
|
|
75
|
+
#
|
|
76
|
+
# @example Setting
|
|
77
|
+
# Field::HasMany.new(:users).collection_query(->(query) {
|
|
78
|
+
# query.where(active: true)
|
|
79
|
+
# })
|
|
80
|
+
#
|
|
81
|
+
# @example Getting
|
|
82
|
+
# field.collection_query # => #<Proc...>
|
|
83
|
+
def collection_query(query_proc = Configuration::Unset)
|
|
84
|
+
return @collection_query if query_proc == Configuration::Unset
|
|
85
|
+
|
|
86
|
+
@collection_query = query_proc
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def group_as(_action)
|
|
91
|
+
:associations
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def param_key
|
|
95
|
+
# TODO: This is too naive. We need to match this to the actual foreign
|
|
96
|
+
# key of the model.
|
|
97
|
+
:"#{name}_id"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
protected
|
|
101
|
+
|
|
102
|
+
def default_on
|
|
103
|
+
[:show]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
# The Id field is intended for unique identifiers, such as primary keys. It
|
|
6
|
+
# contains a link to the records show page, and is not considered editable.
|
|
7
|
+
# As such it is only displayed in index and show views by default.
|
|
8
|
+
class Id < Number
|
|
9
|
+
class Index < Uchi::Field::Base::Index
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Show < Uchi::Field::Base::Show
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
def default_on
|
|
18
|
+
[:index, :show]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default_searchable?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::File.new(**options)) %>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Image < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label},
|
|
14
|
+
input: {options: {accept: "image/*"}}
|
|
15
|
+
}
|
|
16
|
+
options[:hint] = {content: hint} if hint.present?
|
|
17
|
+
options
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Index < Uchi::Field::Base::Index
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Show < Uchi::Field::Base::Show
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def default_searchable?
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def default_sortable?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Number.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Field
|
|
5
|
+
class Number < Field
|
|
6
|
+
class Edit < Uchi::Field::Base::Edit
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def options
|
|
10
|
+
options = {
|
|
11
|
+
attribute: field.name,
|
|
12
|
+
form: form,
|
|
13
|
+
label: {content: label}
|
|
14
|
+
}
|
|
15
|
+
options[:hint] = {content: hint} if hint.present?
|
|
16
|
+
options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Index < Uchi::Field::Base::Index
|
|
21
|
+
class << self
|
|
22
|
+
def classes_for_table_cell
|
|
23
|
+
super + ["text-right"]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Show < Uchi::Field::Base::Show
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render(Flowbite::InputField::Text.new(**options)) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= field.value(record) %>
|