uchi 0.1.3 → 0.1.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.
- 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 +5 -0
- data/app/components/uchi/field/belongs_to.rb +112 -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 +368 -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 +189 -97
- data/.github/dependabot.yml +0 -17
- data/.github/workflows/build.yml +0 -23
- data/.github/workflows/lint.yml +0 -30
- data/CHANGELOG.md +0 -29
- data/docs/fields.md +0 -82
- data/docs/repositories.md +0 -63
- 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_and_belongs_to_many_test.rb +0 -144
- 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/components/uchi/field/text_test.rb +0 -160
- data/test/components/uchi/ui/form/input/collection_checkboxes_test.rb +0 -171
- data/test/controllers/uchi/authors_controller_test.rb +0 -120
- 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 -5
- data/test/dummy/app/models/book.rb +0 -4
- 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 -52
- 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/migrate/20251031140958_add_author_books_join_table.rb +0 -9
- data/test/dummy/db/schema.rb +0 -44
- 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 -77
- 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 -272
- 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,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class PageHeader < ViewComponent::Base
|
|
6
|
+
attr_reader :description, :title
|
|
7
|
+
|
|
8
|
+
renders_many :actions
|
|
9
|
+
renders_one :breadcrumb, Uchi::Ui::Breadcrumb
|
|
10
|
+
|
|
11
|
+
def initialize(title:, description: nil)
|
|
12
|
+
super()
|
|
13
|
+
@description = description
|
|
14
|
+
@title = title
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(@paginator.page, page_url(page_number), aria: { current: "page" }, class: "z-10 flex items-center justify-center px-3 h-8 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white") %>
|
|
3
|
+
</li>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class Pagination
|
|
6
|
+
class Item < ViewComponent::Base
|
|
7
|
+
attr_reader :page_number, :paginator
|
|
8
|
+
|
|
9
|
+
# @param paginator [Uchi::Pagination::Page] The page object
|
|
10
|
+
def initialize(paginator:, page_number: nil)
|
|
11
|
+
super()
|
|
12
|
+
@page_number = page_number
|
|
13
|
+
@paginator = paginator
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns the URL for a given page. page can be a number, of :first,
|
|
17
|
+
# :last, :previous, :next, :current.
|
|
18
|
+
def page_url(page)
|
|
19
|
+
paginator.page_url(page)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(page_number, page_url(page_number), class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") %>
|
|
3
|
+
</li>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(url, class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") do %>
|
|
3
|
+
<span class="sr-only">Next</span>
|
|
4
|
+
<svg class="w-3 h-3 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
5
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
6
|
+
</svg>
|
|
7
|
+
<% end %>
|
|
8
|
+
</li>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class Pagination
|
|
6
|
+
class NextLink < Item
|
|
7
|
+
# Returns true if there is a next page
|
|
8
|
+
def next?
|
|
9
|
+
paginator.next.present?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def url
|
|
13
|
+
if next?
|
|
14
|
+
page_url(:next)
|
|
15
|
+
else
|
|
16
|
+
page_url(:last)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<nav aria-label="<%= aria_label %>">
|
|
2
|
+
<ul class="flex items-center -space-x-px h-8 text-sm">
|
|
3
|
+
<%= render(Uchi::Ui::Pagination::PreviousLink.new(paginator: @paginator)) %>
|
|
4
|
+
<% pages.each do |page_number| %>
|
|
5
|
+
<% if page_number.nil? %>
|
|
6
|
+
<%= render(Uchi::Ui::Pagination::Gap.new(page_number: page_number, paginator: @paginator)) %>
|
|
7
|
+
<% elsif page_number == @paginator.page %>
|
|
8
|
+
<%= render(Uchi::Ui::Pagination::CurrentLink.new(page_number: page_number, paginator: @paginator)) %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<%= render(Uchi::Ui::Pagination::Link.new(page_number: page_number, paginator: @paginator)) %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<%= render(Uchi::Ui::Pagination::NextLink.new(paginator: @paginator)) %>
|
|
14
|
+
</ul>
|
|
15
|
+
</nav>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<li>
|
|
2
|
+
<%= link_to(url, class: "flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") do %>
|
|
3
|
+
<span class="sr-only">Previous</span>
|
|
4
|
+
<svg class="w-3 h-3 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
5
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 1 1 5l4 4"/>
|
|
6
|
+
</svg>
|
|
7
|
+
<% end %>
|
|
8
|
+
</li>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class Pagination
|
|
6
|
+
class PreviousLink < Item
|
|
7
|
+
# Returns true if there is a previous page
|
|
8
|
+
def previous?
|
|
9
|
+
paginator.page > 1
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def url
|
|
13
|
+
if previous?
|
|
14
|
+
page_url(:previous)
|
|
15
|
+
else
|
|
16
|
+
page_url(:first)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
class Pagination < ViewComponent::Base
|
|
6
|
+
attr_reader :paginator
|
|
7
|
+
|
|
8
|
+
def aria_label
|
|
9
|
+
"Page navigation"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param paginator [Pagy::Pagy] The Pagy object
|
|
13
|
+
def initialize(paginator:)
|
|
14
|
+
super()
|
|
15
|
+
@paginator = paginator
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the URL for a given page. page can be a number, of :first,
|
|
19
|
+
# :last, :previous, :next, :current.
|
|
20
|
+
def page_url(page)
|
|
21
|
+
paginator.page_url(page)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns an array of page numbers to display in the pagination component
|
|
25
|
+
def pages
|
|
26
|
+
pages = []
|
|
27
|
+
pages += [1]
|
|
28
|
+
pages += (paginator.page - 2..paginator.page + 2).to_a
|
|
29
|
+
pages += [paginator.last]
|
|
30
|
+
pages = pages.select { |page| page.between?(1, paginator.last) }.uniq.sort
|
|
31
|
+
|
|
32
|
+
result = []
|
|
33
|
+
pages.each_with_index do |page, index|
|
|
34
|
+
result << page
|
|
35
|
+
next_page = pages[index + 1]
|
|
36
|
+
if index < pages.size - 1 && next_page - page > 1
|
|
37
|
+
result << nil # Add a gap
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render?
|
|
44
|
+
paginator.present?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%= render(Uchi::Ui::Frame.new) do %>
|
|
2
|
+
<dl class="divide-y divide-gray-200 dark:divide-white/5">
|
|
3
|
+
<% attribute_fields.each do |field| %>
|
|
4
|
+
<div class="px-4 py-4 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
|
|
5
|
+
<dt class="font-semibold text-gray-900 dark:text-white">
|
|
6
|
+
<%= repository.translate.field_label(field) %>
|
|
7
|
+
</dt>
|
|
8
|
+
<dd class="font-light text-gray-500 dark:text-gray-400 col-span-3">
|
|
9
|
+
<%= render(field.show_component(record: record, repository: repository)) %>
|
|
10
|
+
</dd>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</dl>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
module Show
|
|
6
|
+
class AttributeFields < ViewComponent::Base
|
|
7
|
+
attr_reader :attribute_fields, :record, :repository
|
|
8
|
+
|
|
9
|
+
def initialize(attribute_fields:, record:, repository:)
|
|
10
|
+
super()
|
|
11
|
+
@attribute_fields = attribute_fields
|
|
12
|
+
@record = record
|
|
13
|
+
@repository = repository
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<div role="status">
|
|
2
|
+
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
|
4
|
+
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
|
5
|
+
</svg>
|
|
6
|
+
<span class="sr-only"><%= message %></span>
|
|
7
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
module Ui
|
|
5
|
+
# https://flowbite.com/docs/components/spinner/
|
|
6
|
+
class Spinner < ViewComponent::Base
|
|
7
|
+
attr_reader :message
|
|
8
|
+
|
|
9
|
+
def initialize(message: "Loading...")
|
|
10
|
+
super()
|
|
11
|
+
@message = message
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uchi/pagination/controller"
|
|
4
|
+
|
|
5
|
+
module Uchi
|
|
6
|
+
class RepositoryController < Uchi::ApplicationController
|
|
7
|
+
include Uchi::Pagination::Controller
|
|
8
|
+
|
|
9
|
+
before_action :set_repository
|
|
10
|
+
|
|
11
|
+
def create
|
|
12
|
+
@record = build_record
|
|
13
|
+
if @record.save
|
|
14
|
+
flash[:success] = @repository.translate.successful_create
|
|
15
|
+
redirect_to(@repository.routes.path_for(:show, id: @record.id), status: :see_other)
|
|
16
|
+
else
|
|
17
|
+
render :new, status: :unprocessable_entity
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def destroy
|
|
22
|
+
@record = find_record
|
|
23
|
+
if @record.destroy
|
|
24
|
+
flash[:success] = @repository.translate.successful_destroy
|
|
25
|
+
redirect_to(@repository.routes.path_for(:index), status: :see_other)
|
|
26
|
+
else
|
|
27
|
+
flash[:alert] = @repository.translate.failed_destroy
|
|
28
|
+
redirect_to(@repository.routes.path_for(:show, id: @record.id), status: :see_other)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def edit
|
|
33
|
+
@record = find_record
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def index
|
|
37
|
+
if params[:scope]
|
|
38
|
+
# Handle being shown inline in another record's show view
|
|
39
|
+
parent_repository = Uchi::Repository.for_model(params[:scope][:model])&.new
|
|
40
|
+
parent_record = parent_repository.find(params[:scope][:id])
|
|
41
|
+
field_name = params[:scope][:field]
|
|
42
|
+
inverse_of = params[:scope][:inverse_of]&.to_sym
|
|
43
|
+
|
|
44
|
+
@columns = @repository.fields_for_index
|
|
45
|
+
@columns = @columns.reject { |field| field.name == inverse_of } if inverse_of
|
|
46
|
+
@records = find_all_records_from_association(name: field_name, parent_record: parent_record)
|
|
47
|
+
@paginator, @records = paginate(@records, records_per_page: scoped_records_per_page)
|
|
48
|
+
else
|
|
49
|
+
# Handle the normal case
|
|
50
|
+
@columns = @repository.fields_for_index
|
|
51
|
+
@records = find_all_records
|
|
52
|
+
@paginator, @records = paginate(@records, records_per_page: index_records_per_page)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def new
|
|
57
|
+
@record = build_record
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def show
|
|
61
|
+
@record = find_record
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def update
|
|
65
|
+
@record = find_record
|
|
66
|
+
if @record.update(record_params)
|
|
67
|
+
flash[:success] = @repository.translate.successful_update
|
|
68
|
+
redirect_to(@repository.routes.path_for(:show, id: @record.id, uniq: rand), status: :see_other)
|
|
69
|
+
else
|
|
70
|
+
render :edit, status: :unprocessable_entity
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def build_record
|
|
77
|
+
@repository.build(record_params)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the path to use for the cancel link
|
|
81
|
+
helper_method def path_for_cancel(default:)
|
|
82
|
+
return default unless scoped?
|
|
83
|
+
|
|
84
|
+
parent_model_name = scope[:model]
|
|
85
|
+
parent_repository = Uchi::Repository.for_model(parent_model_name)&.new
|
|
86
|
+
raise NameError, "No repository found for scoped model #{parent_model_name}" unless parent_repository
|
|
87
|
+
|
|
88
|
+
parent_model_id = scope[:id]
|
|
89
|
+
parent_repository.routes.path_for(:show, id: parent_model_id)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
helper_method def current_sort_order
|
|
93
|
+
@current_sort_order ||= SortOrder.from_params(params) || @repository.default_sort_order
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def find_all_records(scope: nil)
|
|
97
|
+
@repository
|
|
98
|
+
.find_all(
|
|
99
|
+
scope: scope,
|
|
100
|
+
search: params[:query],
|
|
101
|
+
sort_order: current_sort_order
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def find_all_records_from_association(name:, parent_record:)
|
|
106
|
+
association = parent_record.class.reflect_on_association(name.to_sym)
|
|
107
|
+
raise NameError, "No association named #{name} on #{parent_record.class}" unless association
|
|
108
|
+
|
|
109
|
+
source_repository = Uchi::Repository.for_model(association.active_record)&.new
|
|
110
|
+
raise NameError, "No repository found for scoped model #{association.active_record}" unless source_repository
|
|
111
|
+
|
|
112
|
+
associated_repository = Uchi::Repository.for_model(association.klass)&.new
|
|
113
|
+
raise NameError, "No repository found for associated model #{association.klass}" unless associated_repository
|
|
114
|
+
|
|
115
|
+
field = source_repository.fields.find { |f| f.name == name.to_sym }
|
|
116
|
+
raise NameError, "No field named #{name} on #{source_repository.model}" unless field
|
|
117
|
+
|
|
118
|
+
scope = parent_record.association(name.to_sym).scope
|
|
119
|
+
find_all_records(scope: scope)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def find_record
|
|
123
|
+
@record = @repository.find(params[:id])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns the number of records per page to show in index views
|
|
127
|
+
def index_records_per_page
|
|
128
|
+
25
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def record_params
|
|
132
|
+
permitted_params = @repository.fields_for_edit.map(&:permitted_param)
|
|
133
|
+
(params[@repository.model_param_key] || ActionController::Parameters.new)
|
|
134
|
+
.permit(*permitted_params)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def repository_class
|
|
138
|
+
raise NotImplementedError, "Subclasses must implement repository_class"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
helper_method def scope_params
|
|
142
|
+
if scoped?
|
|
143
|
+
scope.permit(:field, :id, :inverse_of, :model)
|
|
144
|
+
else
|
|
145
|
+
ActionController::Parameters.new
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Returns the scope that we're currently operating within, if any.
|
|
150
|
+
def scope
|
|
151
|
+
params[:scope]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
helper_method def scoped?
|
|
155
|
+
scope.present?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def scoped_records_per_page
|
|
159
|
+
5
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def set_repository
|
|
163
|
+
@repository = repository_class.new
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Uchi
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
# Maps Rails flash types to Flowbite Toast styles
|
|
4
|
+
def flash_style(flash_type)
|
|
5
|
+
case flash_type.to_sym
|
|
6
|
+
when :success
|
|
7
|
+
:success
|
|
8
|
+
when :alert, :error
|
|
9
|
+
:danger
|
|
10
|
+
when :warning
|
|
11
|
+
:warning
|
|
12
|
+
else
|
|
13
|
+
:default
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= content_for?(:page_title) ? yield(:page_title) : "Uchi" %></title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
|
|
9
|
+
<%= javascript_include_tag "uchi/application", "data-turbo-track" => "reload" %>
|
|
10
|
+
<%= stylesheet_link_tag "uchi/application", media: "all", "data-turbo-track": "reload" %>
|
|
11
|
+
</head>
|
|
12
|
+
<body class="antialiased bg-gray-50 dark:bg-gray-900 p-2">
|
|
13
|
+
<div class="md:flex">
|
|
14
|
+
<nav class="w-64 h-full px-3 py-4 overflow-y-auto shrink-0 bg-gray-50 dark:bg-gray-800" aria-label="Main">
|
|
15
|
+
<ul class="space-y-2 font-medium">
|
|
16
|
+
<% Uchi::Repository.all.map { |repository| repository.new }.sort_by { |repository| repository.translate.navigation_label.downcase }.each do |repository| %>
|
|
17
|
+
<li>
|
|
18
|
+
<%= link_to(repository.routes.path_for(:index), :class => "flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group") do %>
|
|
19
|
+
<span class="ms-3"><%= repository.translate.navigation_label %></span>
|
|
20
|
+
<% end %>
|
|
21
|
+
</li>
|
|
22
|
+
<% end %>
|
|
23
|
+
</ul>
|
|
24
|
+
</nav>
|
|
25
|
+
|
|
26
|
+
<main class="py-6 overflow-x-hidden grow md:px-6">
|
|
27
|
+
<%= yield %>
|
|
28
|
+
</main>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<%= render "layouts/uchi/flash_messages" %>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<%- content_for(:page_title) { @repository.translate.title_for_edit(@record) } %>
|
|
2
|
+
|
|
3
|
+
<%= render(
|
|
4
|
+
Uchi::Ui::PageHeader.new(
|
|
5
|
+
description: @repository.translate.description(:edit),
|
|
6
|
+
title: @repository.translate.title_for_edit(@record),
|
|
7
|
+
)) do |header| %>
|
|
8
|
+
<% header.with_breadcrumb(items: [
|
|
9
|
+
{href: @repository.routes.root_path, label: @repository.translate.breadcrumb_label_for_root },
|
|
10
|
+
{href: @repository.routes.path_for(:index), label: @repository.translate.breadcrumb_label(:index) },
|
|
11
|
+
{href: @repository.routes.path_for(:show, id: @record.id), label: @repository.translate.breadcrumb_label(:show, record: @record) },
|
|
12
|
+
{href: @repository.routes.path_for(:edit, id: @record.id), label: @repository.translate.breadcrumb_label(:edit, record: @record) }
|
|
13
|
+
]) %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<div class="mx-4 md:mx-0">
|
|
17
|
+
<%= render(Flowbite::Card.new) do %>
|
|
18
|
+
<%= form_with model: @record, url: @repository.routes.path_for(:update, id: @record.id) do |form| %>
|
|
19
|
+
<div class="space-y-6">
|
|
20
|
+
<% @repository.fields_for_edit.each do |field| %>
|
|
21
|
+
<div>
|
|
22
|
+
<%= render(
|
|
23
|
+
field.edit_component(
|
|
24
|
+
form: form,
|
|
25
|
+
repository: @repository,
|
|
26
|
+
label: @repository.translate.field_label(field),
|
|
27
|
+
hint: @repository.translate.field_hint(field)
|
|
28
|
+
)
|
|
29
|
+
) %>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<%= render(Uchi::Ui::Form::Footer.new) do |footer| %>
|
|
34
|
+
<%= footer.with_action { render(Flowbite::Button.new) { @repository.translate.submit_button } } %>
|
|
35
|
+
<%= footer.with_action { link_to(@repository.translate.link_to_cancel, path_for_cancel(default: @repository.routes.path_for(:show, id: @record.id))) } %>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<%- content_for(:page_title) { @repository.translate.title_for_index } %>
|
|
2
|
+
|
|
3
|
+
<%= render(
|
|
4
|
+
Uchi::Ui::PageHeader.new(
|
|
5
|
+
description: @repository.translate.description(:index),
|
|
6
|
+
title: @repository.translate.title_for_index,
|
|
7
|
+
)) do |header| %>
|
|
8
|
+
<% header.with_action do %>
|
|
9
|
+
<%= link_to(@repository.translate.link_to_new, @repository.routes.path_for(:new), class: Flowbite::Button.classes) %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% header.with_breadcrumb(items: [
|
|
12
|
+
{href: @repository.routes.root_path, label: @repository.translate.breadcrumb_label_for_root },
|
|
13
|
+
{href: @repository.routes.path_for(:index), label: @repository.translate.breadcrumb_label(:index) }
|
|
14
|
+
]) %>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<div class="mx-0">
|
|
18
|
+
<%= render(Uchi::Ui::Index::TurboFrame.new(
|
|
19
|
+
repository: @repository,
|
|
20
|
+
scope: (scoped? ? scope_params : nil),
|
|
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
|
+
<% if @repository.searchable? %>
|
|
24
|
+
<div class="px-3 py-3">
|
|
25
|
+
<%= render(Uchi::Ui::Index::RecordsTable::SearchForm.new(params: params, repository: @repository)) %>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% if @records.any? %>
|
|
29
|
+
<%= render(Uchi::Ui::Index::RecordsTable.new(
|
|
30
|
+
columns: @columns,
|
|
31
|
+
sort_order: current_sort_order,
|
|
32
|
+
query: params[:query],
|
|
33
|
+
records: @records,
|
|
34
|
+
repository: @repository,
|
|
35
|
+
scope: (scoped? ? scope_params : nil),
|
|
36
|
+
)) %>
|
|
37
|
+
<% else %>
|
|
38
|
+
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
|
|
39
|
+
<%= @repository.translate.no_records_found %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<% if @records.any? %>
|
|
44
|
+
<div class="my-4 flex justify-center">
|
|
45
|
+
<%= render(Uchi::Ui::Pagination.new(paginator: @paginator)) %>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|