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,368 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uchi
|
|
4
|
+
class Repository
|
|
5
|
+
class Translate
|
|
6
|
+
attr_reader :repository
|
|
7
|
+
|
|
8
|
+
# Returns the breadcrumb label for the given page.
|
|
9
|
+
#
|
|
10
|
+
# Example translation key:
|
|
11
|
+
# uchi.repository.author.breadcrumb.edit.label
|
|
12
|
+
def breadcrumb_label(page, record: nil)
|
|
13
|
+
return breadcrumb_label_for_index if page.to_sym == :index
|
|
14
|
+
|
|
15
|
+
default = {
|
|
16
|
+
show: title_for_record(record)
|
|
17
|
+
}[page.to_sym].presence
|
|
18
|
+
default ||= translate("common.#{page}", default: page.to_s.capitalize)
|
|
19
|
+
|
|
20
|
+
translate(
|
|
21
|
+
"label",
|
|
22
|
+
default: default,
|
|
23
|
+
model: singular_name,
|
|
24
|
+
record: title_for_record(record),
|
|
25
|
+
scope: i18n_scope("breadcrumb.#{page}")
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the breadcrumb label for the index page.
|
|
30
|
+
#
|
|
31
|
+
# Returns the first of the following that is present:
|
|
32
|
+
# 1. Translation from "uchi.repository.author.breadcrumb.index.label"
|
|
33
|
+
# 2. Translation from "uchi.repository.author.index.title"
|
|
34
|
+
# 3. plural name of the model
|
|
35
|
+
# 4. Translation from "common.index"
|
|
36
|
+
# 5. Capitalized page name ("Index")
|
|
37
|
+
def breadcrumb_label_for_index
|
|
38
|
+
first_present_value(
|
|
39
|
+
translate(i18n_scope("breadcrumb.index.label"), default: nil),
|
|
40
|
+
translate(i18n_scope("index.title"), default: nil),
|
|
41
|
+
plural_name,
|
|
42
|
+
translate("common.index", default: nil),
|
|
43
|
+
"Index"
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the label for the root breadcrumb item. Defaults to the
|
|
48
|
+
# application name.
|
|
49
|
+
#
|
|
50
|
+
# To customize this provide a translation for the key:
|
|
51
|
+
# `uchi.breadcrumb.root.label`
|
|
52
|
+
def breadcrumb_label_for_root
|
|
53
|
+
first_present_value(
|
|
54
|
+
translate("breadcrumb.root.label", default: nil),
|
|
55
|
+
Rails.application.name.titlecase
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns a description for the given page, or nil if none is found.
|
|
60
|
+
# This description is intended to provide additional context for the page
|
|
61
|
+
# being shown.
|
|
62
|
+
def description(page, record: nil)
|
|
63
|
+
translate(
|
|
64
|
+
"description",
|
|
65
|
+
default: nil,
|
|
66
|
+
model: singular_name,
|
|
67
|
+
record: record,
|
|
68
|
+
scope: i18n_scope(page)
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns a title for a dialog with the given name, e.g. :destroy.
|
|
73
|
+
# The title may include interpolation keys such as %{record}.
|
|
74
|
+
#
|
|
75
|
+
# Example translation key:
|
|
76
|
+
# uchi.repository.author.dialog.destroy.title
|
|
77
|
+
#
|
|
78
|
+
# Example default value:
|
|
79
|
+
# Are you sure you want to delete %{record}?
|
|
80
|
+
#
|
|
81
|
+
# Note that the default value itself is also looked up in the "common"
|
|
82
|
+
# scope, so that it can be shared across repositories.
|
|
83
|
+
#
|
|
84
|
+
# Example fallback translation key:
|
|
85
|
+
# common.dialog.destroy.title
|
|
86
|
+
#
|
|
87
|
+
# Example fallback default value:
|
|
88
|
+
# Are you sure you want to delete this record?
|
|
89
|
+
def destroy_dialog_title(record = nil)
|
|
90
|
+
translate(
|
|
91
|
+
"dialog.destroy.title",
|
|
92
|
+
default: translate(
|
|
93
|
+
"common.dialog.destroy.title",
|
|
94
|
+
default: "Are you sure?",
|
|
95
|
+
record: repository.title(record)
|
|
96
|
+
),
|
|
97
|
+
record: repository.title(record),
|
|
98
|
+
scope: "uchi.repository.#{i18n_key}"
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def failed_destroy
|
|
103
|
+
translate(
|
|
104
|
+
"destroy.failure",
|
|
105
|
+
default: translate(
|
|
106
|
+
"destroy.failure",
|
|
107
|
+
default: "The record could not be deleted",
|
|
108
|
+
scope: "common"
|
|
109
|
+
),
|
|
110
|
+
scope: "uchi.repository.#{i18n_key}"
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def initialize(repository:)
|
|
115
|
+
@repository = repository
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns the label for the given field.
|
|
119
|
+
def field_label(field)
|
|
120
|
+
translate(
|
|
121
|
+
"label",
|
|
122
|
+
default: model.human_attribute_name(field.name),
|
|
123
|
+
scope: i18n_scope("field.#{field.name}")
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns the hint for the given field, or nil if none is found.
|
|
128
|
+
def field_hint(field)
|
|
129
|
+
translate(
|
|
130
|
+
"hint",
|
|
131
|
+
default: nil,
|
|
132
|
+
scope: i18n_scope("field.#{field.name}")
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def link_to_cancel
|
|
137
|
+
translate("common.cancel", default: "Cancel")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def link_to_destroy(record)
|
|
141
|
+
first_present_value(
|
|
142
|
+
translate(
|
|
143
|
+
"link_to_destroy",
|
|
144
|
+
default: nil,
|
|
145
|
+
model: singular_name,
|
|
146
|
+
record: repository.title(record),
|
|
147
|
+
scope: i18n_scope("button")
|
|
148
|
+
),
|
|
149
|
+
translate(
|
|
150
|
+
"common.destroy",
|
|
151
|
+
default: nil,
|
|
152
|
+
model: singular_name,
|
|
153
|
+
record: repository.title(record)
|
|
154
|
+
),
|
|
155
|
+
"Delete"
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def link_to_edit(record)
|
|
160
|
+
first_present_value(
|
|
161
|
+
translate(
|
|
162
|
+
"link_to_edit",
|
|
163
|
+
default: nil,
|
|
164
|
+
model: singular_name,
|
|
165
|
+
record: repository.title(record),
|
|
166
|
+
scope: i18n_scope("button")
|
|
167
|
+
),
|
|
168
|
+
translate(
|
|
169
|
+
"common.edit",
|
|
170
|
+
default: nil,
|
|
171
|
+
model: singular_name,
|
|
172
|
+
record: repository.title(record)
|
|
173
|
+
),
|
|
174
|
+
"Edit"
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns the text for the "new" action link.
|
|
179
|
+
#
|
|
180
|
+
# Returns the first of the following translations that is present:
|
|
181
|
+
# 1. Translation from "uchi.repository.[name].button.link_to_new"
|
|
182
|
+
# 2. Translation from "uchi.common.new" with interpolation key %{model}
|
|
183
|
+
# 3. Default string "New %{model}"
|
|
184
|
+
def link_to_new
|
|
185
|
+
translate(
|
|
186
|
+
"link_to_new",
|
|
187
|
+
default: translate("common.new", default: "New %{model}"),
|
|
188
|
+
model: singular_name,
|
|
189
|
+
scope: i18n_scope("button")
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def loading_message
|
|
194
|
+
translate("loading", default: "Loading...", scope: "uchi.repository.common")
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns the label for the navigation link to this repository's index
|
|
198
|
+
# page.
|
|
199
|
+
#
|
|
200
|
+
# Returns the first of the following that is present:
|
|
201
|
+
# 1. Translation from "uchi.repository.[name].navigation.label"
|
|
202
|
+
# 2. Translation from "uchi.repository.[name].index.title"
|
|
203
|
+
# 3. plural name of the model
|
|
204
|
+
def navigation_label
|
|
205
|
+
first_present_value(
|
|
206
|
+
translate(i18n_scope("navigation.label"), default: nil),
|
|
207
|
+
translate(i18n_scope("index.title"), default: nil),
|
|
208
|
+
plural_name
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Returns the localized, human-readable plural name of the model this
|
|
213
|
+
# repository manages.
|
|
214
|
+
def plural_name
|
|
215
|
+
::I18n.translate(
|
|
216
|
+
"uchi.repository.#{i18n_key}.model",
|
|
217
|
+
count: 2,
|
|
218
|
+
default: model.model_name.plural.humanize
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def no_records_found
|
|
223
|
+
translate("no_records_found", default: "No records found", scope: "uchi.common")
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def search_label
|
|
227
|
+
translate("common.search", default: "Search")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def search_button
|
|
231
|
+
translate("common.search", default: "Search")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns the label for a generic submit button
|
|
235
|
+
def submit_button
|
|
236
|
+
translate("common.save", default: "Save")
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def successful_create
|
|
240
|
+
translate(
|
|
241
|
+
"create.success",
|
|
242
|
+
default: translate(
|
|
243
|
+
"create.success",
|
|
244
|
+
default: "Your changes have been saved",
|
|
245
|
+
scope: "common"
|
|
246
|
+
),
|
|
247
|
+
scope: "uchi.repository.#{i18n_key}"
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def successful_destroy
|
|
252
|
+
translate(
|
|
253
|
+
"destroy.success",
|
|
254
|
+
default: translate(
|
|
255
|
+
"destroy.success",
|
|
256
|
+
default: "The record has been deleted",
|
|
257
|
+
scope: "common"
|
|
258
|
+
),
|
|
259
|
+
scope: "uchi.repository.#{i18n_key}"
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def successful_update
|
|
264
|
+
translate(
|
|
265
|
+
"update.success",
|
|
266
|
+
default: translate(
|
|
267
|
+
"update.success",
|
|
268
|
+
default: "Your changes have been saved",
|
|
269
|
+
scope: "common"
|
|
270
|
+
),
|
|
271
|
+
scope: "uchi.repository.#{i18n_key}"
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def title_for_edit(record)
|
|
276
|
+
first_present_value(
|
|
277
|
+
translate(
|
|
278
|
+
"title",
|
|
279
|
+
default: nil,
|
|
280
|
+
model: singular_name,
|
|
281
|
+
record: repository.title(record),
|
|
282
|
+
scope: i18n_scope(:edit)
|
|
283
|
+
),
|
|
284
|
+
link_to_edit(record)
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def title_for_index
|
|
289
|
+
translate(
|
|
290
|
+
"title",
|
|
291
|
+
default: plural_name,
|
|
292
|
+
model: singular_name,
|
|
293
|
+
scope: i18n_scope(:index)
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def title_for_show(record)
|
|
298
|
+
return repository.title(record) if record
|
|
299
|
+
|
|
300
|
+
translate(
|
|
301
|
+
"title",
|
|
302
|
+
default: plural_name,
|
|
303
|
+
model: singular_name,
|
|
304
|
+
scope: i18n_scope(:show)
|
|
305
|
+
)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Returns the title for the "new" page.
|
|
309
|
+
#
|
|
310
|
+
# Returns the first of the following translations that is present:
|
|
311
|
+
# 1. Translation from "uchi.repository.[name].new.title"
|
|
312
|
+
# 2. Translation from "uchi.repository.[name].button.link_to_new"
|
|
313
|
+
# 3. Translation from "uchi.common.new" with interpolation key %{model}
|
|
314
|
+
# 4. Default string "New %{model}"
|
|
315
|
+
def title_for_new
|
|
316
|
+
first_present_value(
|
|
317
|
+
translate(i18n_scope("new.title"), default: nil),
|
|
318
|
+
link_to_new
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
private
|
|
323
|
+
|
|
324
|
+
# Returns the first translation that yields a present value by looking
|
|
325
|
+
# through each key in keys in order.
|
|
326
|
+
def first_present_value(*values)
|
|
327
|
+
values.find(&:presence)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Returns the segment of the i18n key specific to this repository.
|
|
331
|
+
def i18n_key
|
|
332
|
+
@repository.model.model_name.i18n_key
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def i18n_scope(section = nil)
|
|
336
|
+
[
|
|
337
|
+
"uchi.repository",
|
|
338
|
+
i18n_key,
|
|
339
|
+
section
|
|
340
|
+
].compact.join(".")
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def model
|
|
344
|
+
@model ||= repository.model
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Returns the localized, human-readable singular name of the model this
|
|
348
|
+
# repository manages.
|
|
349
|
+
def singular_name
|
|
350
|
+
::I18n.translate(
|
|
351
|
+
"uchi.repository.#{i18n_key}.model",
|
|
352
|
+
count: 1,
|
|
353
|
+
default: model.model_name.human(count: 1)
|
|
354
|
+
)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def title_for_record(record)
|
|
358
|
+
return nil unless record
|
|
359
|
+
|
|
360
|
+
repository.title(record)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def translate(key, **options)
|
|
364
|
+
Uchi::I18n.translate(key, **options)
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "repository/routes"
|
|
4
|
+
|
|
5
|
+
module Uchi
|
|
6
|
+
class Repository
|
|
7
|
+
class << self
|
|
8
|
+
# Returns all defined Uchi::Repository classes
|
|
9
|
+
def all
|
|
10
|
+
Uchi::Repositories.constants.map { |const_name|
|
|
11
|
+
Uchi::Repositories.const_get(const_name)
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the repository for the given model, or nil if none is found.
|
|
16
|
+
def for_model(model)
|
|
17
|
+
all.find { |repository| repository.model.to_s == model.to_s }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the model class this repository manages.
|
|
21
|
+
def model
|
|
22
|
+
@model ||= name.demodulize.constantize
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns a new, unsaved instance of the model this repository manages.
|
|
27
|
+
def build(attributes = {})
|
|
28
|
+
model.new(attributes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the "name" of the controller that handles requests for this
|
|
32
|
+
# repository. Note that this is different from the controllers class name
|
|
33
|
+
# and is intended for generating URLs.
|
|
34
|
+
def controller_name
|
|
35
|
+
model_param_key.pluralize
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default_sort_order
|
|
39
|
+
SortOrder.new(:id, :asc)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns an array of fields to show on the edit page.
|
|
43
|
+
def fields_for_edit
|
|
44
|
+
fields.select { |field| field.on.include?(:edit) }.each { |field| field.repository = self }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns an array of fields to show on the index page.
|
|
48
|
+
def fields_for_index
|
|
49
|
+
fields.select { |field| field.on.include?(:index) }.each { |field| field.repository = self }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns an array of fields to show on the show page.
|
|
53
|
+
def fields_for_show
|
|
54
|
+
fields.select { |field| field.on.include?(:show) }.each { |field| field.repository = self }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_all(search: nil, scope: model.all, sort_order: default_sort_order)
|
|
58
|
+
scope ||= model.all
|
|
59
|
+
query = scope.includes(includes)
|
|
60
|
+
query = apply_search(query, search)
|
|
61
|
+
apply_sort_order(query, sort_order)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def find(id)
|
|
65
|
+
model.find(id)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the list of associations to include when querying for records.
|
|
69
|
+
#
|
|
70
|
+
# See
|
|
71
|
+
# https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes
|
|
72
|
+
# for further details.
|
|
73
|
+
def includes
|
|
74
|
+
[]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def model
|
|
78
|
+
self.class.model
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def model_param_key
|
|
82
|
+
model.model_name.param_key
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns an instance of Uchi::Repository::Routes for this repository,
|
|
86
|
+
# which can be used to generate paths and URLs.
|
|
87
|
+
#
|
|
88
|
+
# @return [Uchi::Repository::Routes]
|
|
89
|
+
def routes
|
|
90
|
+
@routes ||= Routes.new(self)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns true if this repository has at least one searchable field.
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean]
|
|
96
|
+
def searchable?
|
|
97
|
+
searchable_fields.any?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the title to show for a given record. By default, this method
|
|
101
|
+
# returns the value of the first of the following methods that exist:
|
|
102
|
+
#
|
|
103
|
+
# 1. `name`
|
|
104
|
+
# 2. `title`
|
|
105
|
+
# 3. `to_s`
|
|
106
|
+
#
|
|
107
|
+
# You can override this method in your repository subclass to provide
|
|
108
|
+
# custom logic.
|
|
109
|
+
def title(record)
|
|
110
|
+
return nil unless record
|
|
111
|
+
|
|
112
|
+
[:name, :title, :to_s].each do |method|
|
|
113
|
+
if record.respond_to?(method)
|
|
114
|
+
return record.public_send(method)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Provides access to translation helpers specific to this repository.
|
|
120
|
+
def translate
|
|
121
|
+
@translate ||= Translate.new(repository: self)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def apply_search(query, search)
|
|
127
|
+
return query unless search.present?
|
|
128
|
+
return query if searchable_fields.empty?
|
|
129
|
+
|
|
130
|
+
search = search.strip
|
|
131
|
+
conditions = searchable_fields.map { |field|
|
|
132
|
+
arel_field = model.arel_table[field.name]
|
|
133
|
+
Arel::Nodes::NamedFunction.new(
|
|
134
|
+
"CAST",
|
|
135
|
+
[arel_field.as(Arel::Nodes::SqlLiteral.new("VARCHAR"))]
|
|
136
|
+
).matches("%#{search}%")
|
|
137
|
+
}
|
|
138
|
+
query.where(conditions.inject(:or))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def apply_sort_order(query, sort_order)
|
|
142
|
+
field_to_sort_by = fields.find { |field| field.name == sort_order.name }
|
|
143
|
+
return query unless field_to_sort_by
|
|
144
|
+
|
|
145
|
+
if field_to_sort_by.sortable.respond_to?(:call)
|
|
146
|
+
field_to_sort_by.sortable.call(query, sort_order.direction)
|
|
147
|
+
else
|
|
148
|
+
sort_order.apply(query)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def searchable_fields
|
|
153
|
+
@searchable_fields ||= fields.select { |field| field.searchable? }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Uchi
|
|
2
|
+
class SortOrder
|
|
3
|
+
attr_reader :name, :direction
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def from_params(params)
|
|
7
|
+
sort_params = params[:sort] || {}
|
|
8
|
+
return nil if sort_params.empty?
|
|
9
|
+
|
|
10
|
+
by = sort_params[:by]
|
|
11
|
+
return by unless by
|
|
12
|
+
|
|
13
|
+
direction = sort_params[:direction] || :asc
|
|
14
|
+
new(by, direction)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ascending?
|
|
19
|
+
direction == :asc
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def apply(query)
|
|
23
|
+
query.order(name => direction)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def descending?
|
|
27
|
+
direction == :desc
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(name, direction)
|
|
31
|
+
@name = name.to_sym
|
|
32
|
+
@direction = direction.to_sym
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/uchi/version.rb
ADDED
data/lib/uchi.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
gem "turbo-rails"
|
|
2
|
+
require "turbo-rails"
|
|
3
|
+
|
|
4
|
+
gem "view_component"
|
|
5
|
+
require "view_component"
|
|
6
|
+
|
|
7
|
+
require "uchi/version"
|
|
8
|
+
require "uchi/engine"
|
|
9
|
+
|
|
10
|
+
require "uchi/field"
|
|
11
|
+
require "uchi/i18n"
|
|
12
|
+
require "uchi/repository"
|
|
13
|
+
require "uchi/sort_order"
|
|
14
|
+
require "uchi/repository/translate"
|
|
15
|
+
|
|
16
|
+
module Uchi
|
|
17
|
+
# Your code goes here...
|
|
18
|
+
end
|
data/uchi.gemspec
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/uchi/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "uchi"
|
|
7
|
+
spec.version = Uchi::VERSION
|
|
8
|
+
spec.authors = ["Jakob Skjerning"]
|
|
9
|
+
spec.email = ["jakob@substancelab.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Build usable and extensible admin panels for your Ruby on Rails application in minutes."
|
|
12
|
+
spec.description = "Level up your scaffolds with a modern admin backend framework, designed for Rails developers who demand both beauty, functionality, and extensibility. Uchi provides a set of components and conventions for creating user interfaces that are both powerful and easy to use."
|
|
13
|
+
spec.homepage = "https://www.uchiadmin.com/"
|
|
14
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["changelog_uri"] = "https://github.com/substancelab/uchi/blob/main/CHANGELOG.md"
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/substancelab/uchi"
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
|
+
spec.files = \
|
|
23
|
+
Dir["app/**/*"] +
|
|
24
|
+
Dir["lib/**/*"] +
|
|
25
|
+
[
|
|
26
|
+
"README.md",
|
|
27
|
+
"uchi.gemspec"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
spec.add_dependency "rails", ">= 7.2"
|
|
33
|
+
spec.add_dependency "turbo-rails"
|
|
34
|
+
spec.add_dependency "view_component", ">= 4.0"
|
|
35
|
+
end
|