ruby_ui_admin 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +165 -0
- data/app/assets/stylesheets/ruby_ui_admin/application.tailwind.css +111 -0
- data/app/components/ruby_ui_admin/ui/base.rb +26 -0
- data/app/components/ruby_ui_admin/ui/icon.rb +79 -0
- data/app/components/ruby_ui_admin/ui/native_select.rb +37 -0
- data/app/components/ruby_ui_admin/ui/native_select_group.rb +18 -0
- data/app/components/ruby_ui_admin/ui/native_select_icon.rb +42 -0
- data/app/components/ruby_ui_admin/ui/native_select_option.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination.rb +62 -0
- data/app/components/ruby_ui_admin/ui/pagination_content.rb +18 -0
- data/app/components/ruby_ui_admin/ui/pagination_ellipsis.rb +45 -0
- data/app/components/ruby_ui_admin/ui/pagination_item.rb +32 -0
- data/app/components/ruby_ui_admin/ui/select.rb +29 -0
- data/app/components/ruby_ui_admin/ui/toast_action.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_cancel.rb +30 -0
- data/app/components/ruby_ui_admin/ui/toast_close.rb +43 -0
- data/app/components/ruby_ui_admin/ui/toast_description.rb +21 -0
- data/app/components/ruby_ui_admin/ui/toast_icon.rb +69 -0
- data/app/components/ruby_ui_admin/ui/toast_item.rb +75 -0
- data/app/components/ruby_ui_admin/ui/toast_region.rb +147 -0
- data/app/components/ruby_ui_admin/ui/toast_title.rb +21 -0
- data/app/components/ruby_ui_admin/ui.rb +12 -0
- data/app/components/ruby_ui_admin/views/action.rb +37 -0
- data/app/components/ruby_ui_admin/views/action_form.rb +104 -0
- data/app/components/ruby_ui_admin/views/action_trigger.rb +148 -0
- data/app/components/ruby_ui_admin/views/actions_menu.rb +34 -0
- data/app/components/ruby_ui_admin/views/base.rb +301 -0
- data/app/components/ruby_ui_admin/views/card_component.rb +69 -0
- data/app/components/ruby_ui_admin/views/dashboard.rb +40 -0
- data/app/components/ruby_ui_admin/views/docs.rb +91 -0
- data/app/components/ruby_ui_admin/views/field_input.rb +156 -0
- data/app/components/ruby_ui_admin/views/field_value.rb +301 -0
- data/app/components/ruby_ui_admin/views/filter_bar.rb +111 -0
- data/app/components/ruby_ui_admin/views/form.rb +151 -0
- data/app/components/ruby_ui_admin/views/home.rb +35 -0
- data/app/components/ruby_ui_admin/views/index.rb +324 -0
- data/app/components/ruby_ui_admin/views/path_helpers.rb +36 -0
- data/app/components/ruby_ui_admin/views/rails_helpers.rb +15 -0
- data/app/components/ruby_ui_admin/views/show.rb +77 -0
- data/app/components/ruby_ui_admin/views/show_fields.rb +37 -0
- data/app/components/ruby_ui_admin/views/show_tab.rb +58 -0
- data/app/components/ruby_ui_admin/views/structure_renderer.rb +148 -0
- data/app/components/ruby_ui_admin/views/translation.rb +12 -0
- data/app/controllers/ruby_ui_admin/actions_controller.rb +162 -0
- data/app/controllers/ruby_ui_admin/application_controller.rb +81 -0
- data/app/controllers/ruby_ui_admin/dashboards_controller.rb +12 -0
- data/app/controllers/ruby_ui_admin/docs_controller.rb +166 -0
- data/app/controllers/ruby_ui_admin/home_controller.rb +18 -0
- data/app/controllers/ruby_ui_admin/resources_controller.rb +259 -0
- data/config/locales/ruby_ui_admin.en.yml +59 -0
- data/config/locales/ruby_ui_admin.pt-BR.yml +59 -0
- data/config/routes/dynamic_routes.rb +11 -0
- data/config/routes.rb +23 -0
- data/docs/README.md +53 -0
- data/docs/actions/overview.md +112 -0
- data/docs/authorization/action-policy.md +72 -0
- data/docs/authorization/adapters.md +101 -0
- data/docs/authorization/explicit-mode.md +45 -0
- data/docs/authorization/field-authorization.md +53 -0
- data/docs/authorization/policies.md +62 -0
- data/docs/authorization/scopes.md +48 -0
- data/docs/customization/controllers.md +62 -0
- data/docs/customization/ejecting.md +55 -0
- data/docs/customization/javascript.md +161 -0
- data/docs/customization/theming-rubyui.md +80 -0
- data/docs/dashboards/overview.md +65 -0
- data/docs/fields/overview.md +94 -0
- data/docs/filters/overview.md +73 -0
- data/docs/generators-and-tasks/overview.md +42 -0
- data/docs/getting-started/authentication.md +98 -0
- data/docs/getting-started/configuration.md +174 -0
- data/docs/getting-started/installation.md +201 -0
- data/docs/getting-started/internationalization.md +50 -0
- data/docs/getting-started/practical-guide.md +310 -0
- data/docs/resources/associations.md +66 -0
- data/docs/resources/index-customization.md +68 -0
- data/docs/resources/overview.md +50 -0
- data/docs/resources/scopes.md +54 -0
- data/docs/resources/tabs-panels.md +65 -0
- data/lib/generators/ruby_ui_admin/action/action_generator.rb +21 -0
- data/lib/generators/ruby_ui_admin/action/templates/action.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/assets/assets_generator.rb +71 -0
- data/lib/generators/ruby_ui_admin/card/card_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/card/templates/chart_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/metric_card.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/card/templates/partial_card.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/components/components_generator.rb +43 -0
- data/lib/generators/ruby_ui_admin/controller/controller_generator.rb +30 -0
- data/lib/generators/ruby_ui_admin/controller/templates/controller.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/dashboard/dashboard_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/dashboard/templates/dashboard.rb.tt +14 -0
- data/lib/generators/ruby_ui_admin/eject/eject_generator.rb +55 -0
- data/lib/generators/ruby_ui_admin/filter/filter_generator.rb +28 -0
- data/lib/generators/ruby_ui_admin/filter/templates/boolean_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/filter/templates/select_filter.rb.tt +19 -0
- data/lib/generators/ruby_ui_admin/filter/templates/text_filter.rb.tt +13 -0
- data/lib/generators/ruby_ui_admin/install/install_generator.rb +62 -0
- data/lib/generators/ruby_ui_admin/install/templates/initializer.rb.tt +41 -0
- data/lib/generators/ruby_ui_admin/locales/locales_generator.rb +22 -0
- data/lib/generators/ruby_ui_admin/policy/policy_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/policy/templates/policy.rb.tt +24 -0
- data/lib/generators/ruby_ui_admin/resource/resource_generator.rb +84 -0
- data/lib/generators/ruby_ui_admin/resource/templates/resource.rb.tt +15 -0
- data/lib/generators/ruby_ui_admin/scope/scope_generator.rb +18 -0
- data/lib/generators/ruby_ui_admin/scope/templates/scope.rb.tt +11 -0
- data/lib/ruby_ui_admin/authorization/action_policy_adapter.rb +69 -0
- data/lib/ruby_ui_admin/authorization/adapter.rb +80 -0
- data/lib/ruby_ui_admin/authorization/can_can_can_adapter.rb +70 -0
- data/lib/ruby_ui_admin/authorization/pundit_adapter.rb +86 -0
- data/lib/ruby_ui_admin/authorization.rb +40 -0
- data/lib/ruby_ui_admin/base_action.rb +211 -0
- data/lib/ruby_ui_admin/base_dashboard.rb +70 -0
- data/lib/ruby_ui_admin/base_policy.rb +38 -0
- data/lib/ruby_ui_admin/base_resource.rb +503 -0
- data/lib/ruby_ui_admin/cards/base_card.rb +59 -0
- data/lib/ruby_ui_admin/cards/chart_card.rb +16 -0
- data/lib/ruby_ui_admin/cards/metric_card.rb +22 -0
- data/lib/ruby_ui_admin/cards/partial_card.rb +14 -0
- data/lib/ruby_ui_admin/configuration.rb +177 -0
- data/lib/ruby_ui_admin/current.rb +20 -0
- data/lib/ruby_ui_admin/dashboard_manager.rb +52 -0
- data/lib/ruby_ui_admin/engine.rb +80 -0
- data/lib/ruby_ui_admin/execution_context.rb +36 -0
- data/lib/ruby_ui_admin/fields/association_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/badge_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/base_field.rb +208 -0
- data/lib/ruby_ui_admin/fields/belongs_to_field.rb +49 -0
- data/lib/ruby_ui_admin/fields/boolean_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/boolean_group_field.rb +54 -0
- data/lib/ruby_ui_admin/fields/code_field.rb +18 -0
- data/lib/ruby_ui_admin/fields/date_field.rb +34 -0
- data/lib/ruby_ui_admin/fields/date_time_field.rb +37 -0
- data/lib/ruby_ui_admin/fields/field_manager.rb +27 -0
- data/lib/ruby_ui_admin/fields/file_field.rb +67 -0
- data/lib/ruby_ui_admin/fields/files_field.rb +62 -0
- data/lib/ruby_ui_admin/fields/has_and_belongs_to_many_field.rb +9 -0
- data/lib/ruby_ui_admin/fields/has_many_field.rb +24 -0
- data/lib/ruby_ui_admin/fields/has_one_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/hidden_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/id_field.rb +19 -0
- data/lib/ruby_ui_admin/fields/key_value_field.rb +41 -0
- data/lib/ruby_ui_admin/fields/number_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/password_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/record_link_field.rb +45 -0
- data/lib/ruby_ui_admin/fields/select_field.rb +71 -0
- data/lib/ruby_ui_admin/fields/status_field.rb +21 -0
- data/lib/ruby_ui_admin/fields/text_field.rb +14 -0
- data/lib/ruby_ui_admin/fields/textarea_field.rb +13 -0
- data/lib/ruby_ui_admin/fields/url_field.rb +21 -0
- data/lib/ruby_ui_admin/filters/base_filter.rb +81 -0
- data/lib/ruby_ui_admin/filters/boolean_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/multiple_select_filter.rb +11 -0
- data/lib/ruby_ui_admin/filters/select_filter.rb +9 -0
- data/lib/ruby_ui_admin/filters/text_filter.rb +9 -0
- data/lib/ruby_ui_admin/menu/builder.rb +94 -0
- data/lib/ruby_ui_admin/resource_manager.rb +82 -0
- data/lib/ruby_ui_admin/scopes/base_scope.rb +107 -0
- data/lib/ruby_ui_admin/services/authorization_service.rb +38 -0
- data/lib/ruby_ui_admin/structure.rb +40 -0
- data/lib/ruby_ui_admin/tailwind_source.rb +65 -0
- data/lib/ruby_ui_admin/version.rb +5 -0
- data/lib/ruby_ui_admin/view.rb +34 -0
- data/lib/ruby_ui_admin.rb +110 -0
- data/lib/tasks/ruby_ui_admin.rake +71 -0
- data/public/ruby-ui-admin-assets/application.css +2 -0
- data/public/ruby-ui-admin-assets/controllers/index.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/rua--bulk-select_controller.js +19 -0
- data/public/ruby-ui-admin-assets/controllers/rua--confirm_controller.js +51 -0
- data/public/ruby-ui-admin-assets/controllers/rua--dialog_controller.js +55 -0
- data/public/ruby-ui-admin-assets/controllers/rua--row-link_controller.js +16 -0
- data/public/ruby-ui-admin-assets/controllers/rua--tabs_controller.js +36 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--combobox_controller.js +197 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_content_controller.js +7 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sheet_controller.js +15 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--sidebar_controller.js +67 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toast_controller.js +151 -0
- data/public/ruby-ui-admin-assets/controllers/ruby_ui--toaster_controller.js +314 -0
- data/public/ruby-ui-admin-assets/vendor/stimulus.js +2567 -0
- data/public/ruby-ui-admin-assets/vendor/turbo.js +7178 -0
- metadata +379 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Resources — Overview
|
|
2
|
+
|
|
3
|
+
A resource maps an ActiveRecord model to the admin UI. Resources live in
|
|
4
|
+
`app/ruby_ui_admin/resources` and are namespaced under `RubyUIAdmin::Resources`.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
module RubyUIAdmin
|
|
8
|
+
module Resources
|
|
9
|
+
class Post < RubyUIAdmin::BaseResource
|
|
10
|
+
self.title = :title # attribute (or lambda) used as the record label
|
|
11
|
+
self.includes = [:user] # eager-loaded associations
|
|
12
|
+
self.model_class = Post # optional; inferred from the class name
|
|
13
|
+
self.index_query = -> { query.order(created_at: :desc) } # optional default scope
|
|
14
|
+
self.authorization_policy = RubyUIAdmin::Policies::PostPolicy
|
|
15
|
+
|
|
16
|
+
def fields
|
|
17
|
+
field :id, as: :id
|
|
18
|
+
field :title, as: :text, link_to_record: true, sortable: true
|
|
19
|
+
field :body, as: :text, only_on: %i[show new edit]
|
|
20
|
+
field :published, as: :boolean
|
|
21
|
+
field :user, as: :belongs_to
|
|
22
|
+
field :created_at, as: :date_time, only_on: %i[index show]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Class options
|
|
30
|
+
|
|
31
|
+
| Option | Purpose |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `self.title` | Attribute symbol or lambda used as the record's display label |
|
|
34
|
+
| `self.includes` | Associations to eager-load on the index query |
|
|
35
|
+
| `self.model_class` | Override the inferred model class |
|
|
36
|
+
| `self.index_query` | Lambda returning the default index scope (`query` is available) |
|
|
37
|
+
| `self.authorization_policy` | The action_policy policy class for this resource |
|
|
38
|
+
| `self.visible_on_sidebar` | Hide the resource from the sidebar when `false` |
|
|
39
|
+
| `self.description` | Subtitle shown under the index title |
|
|
40
|
+
| `self.record_selector` | Set `false` to hide the bulk-select checkbox column |
|
|
41
|
+
| `self.row_controls` | `->(record) { ... }` rendering extra per-row controls on the index (helpers: `show_button(record)`, `edit_button(record)`, `create_button`, `control_link`) |
|
|
42
|
+
| `self.row_controls_config` | Layout for the row-controls cell: `{ placement: :left/:right, float: true, show_on_hover: true }` |
|
|
43
|
+
| `self.countless` | Paginate without a `COUNT` query (pagy countless) — for very large tables. Can also be enabled globally via `config.pagination` |
|
|
44
|
+
| `self.remove_scope_all` | Hide the "All" scope tab (use with a default [scope](scopes.md)) |
|
|
45
|
+
|
|
46
|
+
## Fields
|
|
47
|
+
|
|
48
|
+
`field :name, as: :type, **options` — see the [field catalog](../fields/overview.md) for all
|
|
49
|
+
types and options, and [tabs & panels](tabs-panels.md) for grouping. Fields can also be
|
|
50
|
+
auto-derived from the schema with `discover_columns` / `discover_associations`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Scopes
|
|
2
|
+
|
|
3
|
+
Named scopes partition a resource's index into tabs. Define them in
|
|
4
|
+
`app/ruby_ui_admin/scopes` under `RubyUIAdmin::Scopes`, then declare them on the resource.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
module RubyUIAdmin
|
|
8
|
+
module Scopes
|
|
9
|
+
class Published < RubyUIAdmin::Scopes::BaseScope
|
|
10
|
+
self.name = "Published"
|
|
11
|
+
self.description = "Only published posts" # optional: tooltip on the scope tab
|
|
12
|
+
self.scope = -> { query.where(published: true) } # lambda, or...
|
|
13
|
+
# self.scope = :published # ...a Symbol = the model's named scope
|
|
14
|
+
self.default = true # optional: applied when no scope is selected
|
|
15
|
+
# self.visible = -> { user.admin? } # optional: show the tab conditionally
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Attach to a resource:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
def scopes
|
|
25
|
+
scope RubyUIAdmin::Scopes::Published, default: true # mark default at attachment
|
|
26
|
+
scope RubyUIAdmin::Scopes::Drafts
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Hide the "All" tab (pair with a default scope):
|
|
30
|
+
self.remove_scope_all = true
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Behaviour
|
|
34
|
+
|
|
35
|
+
- The index renders a tab bar: **All** + one tab per scope.
|
|
36
|
+
- **`self.scope`** is a lambda (receiving `query` — the base relation, already narrowed by the
|
|
37
|
+
policy scope — and `params`) returning a narrowed relation, **or** a Symbol naming a model
|
|
38
|
+
scope/class method (e.g. `:published` → `Post.published`).
|
|
39
|
+
- Selecting a tab reloads the index with `?scope=<key>` (the key is the underscored class
|
|
40
|
+
name, e.g. `Published` → `published`). `?scope=all` clears any scope.
|
|
41
|
+
- **`self.default = true`** (or `scope Klass, default: true` at attachment) makes a scope apply
|
|
42
|
+
when no `?scope=` param is present; otherwise the index opens on **All**. The attachment option
|
|
43
|
+
wins over the class setting.
|
|
44
|
+
- **`self.remove_scope_all = true`** on the resource hides the **All** tab (use with a default scope).
|
|
45
|
+
- **`self.visible`** (a lambda) hides a scope's tab when it returns false.
|
|
46
|
+
- Scopes compose with the policy scope and [filters](../filters/overview.md): policy scope →
|
|
47
|
+
named scope → filters → sorting. The active scope is preserved when applying filters and
|
|
48
|
+
paginating.
|
|
49
|
+
|
|
50
|
+
## Generator
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
rails g ruby_ui_admin:scope Published
|
|
54
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Tabs & Panels
|
|
2
|
+
|
|
3
|
+
Group fields on the show and form views with `panel`, `tabs` and `tab`.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
def fields
|
|
7
|
+
field :id, as: :id
|
|
8
|
+
|
|
9
|
+
panel "Basic info" do
|
|
10
|
+
field :title, as: :text
|
|
11
|
+
field :status, as: :badge, options: {"draft" => :warning, "published" => :success}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
tabs do
|
|
15
|
+
tab "Content", description: "The main content" do # description shown in the panel
|
|
16
|
+
field :body, as: :textarea
|
|
17
|
+
field :metadata, as: :key_value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
tab "Relations" do
|
|
21
|
+
field :user, as: :belongs_to
|
|
22
|
+
field :comments, as: :has_many
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Behaviour
|
|
29
|
+
|
|
30
|
+
- **`panel(name = nil)`** — renders its fields inside a titled card. The name can also be
|
|
31
|
+
passed as a keyword (`panel name: "Basic info"`); both forms are equivalent.
|
|
32
|
+
- **`tabs`** — declares a group of tabs; only `tab` calls belong directly inside it.
|
|
33
|
+
- **`tab(name, description: nil)`** — a named section inside `tabs`; can contain fields and
|
|
34
|
+
panels. A bare field placed directly inside `tabs` (not wrapped in a `tab`) is treated as
|
|
35
|
+
an implicit single-field tab.
|
|
36
|
+
- Bare top-level fields (not inside a panel/tab) are grouped into a default card.
|
|
37
|
+
- Containers are pruned per view: a tab/panel whose fields are all hidden on a view
|
|
38
|
+
(via `only_on` / `hide_on`) isn't rendered there.
|
|
39
|
+
- The **index** view ignores grouping and shows the flattened, visible fields as columns.
|
|
40
|
+
|
|
41
|
+
### Redundant-label de-duplication
|
|
42
|
+
|
|
43
|
+
To avoid showing the same word twice, two labels are suppressed automatically:
|
|
44
|
+
|
|
45
|
+
- A **tab `description:` that equals the tab name** isn't rendered (the tab bar already shows
|
|
46
|
+
the name).
|
|
47
|
+
- A **panel `name:` that equals the name of the tab containing it** isn't rendered (so a
|
|
48
|
+
`tab "Permissions" { panel name: "Permissions" { … } }` shows the title once, not twice).
|
|
49
|
+
|
|
50
|
+
Distinct descriptions and panel names still render normally.
|
|
51
|
+
|
|
52
|
+
## Tab switching (JavaScript)
|
|
53
|
+
|
|
54
|
+
Tabs render as **stacked, titled cards without JavaScript**. With the bundled JS, the tab bar
|
|
55
|
+
is revealed, the redundant per-panel headings are hidden, and clicking a tab shows only that
|
|
56
|
+
panel (no server round-trip). See [JavaScript](../customization/javascript.md#tabs).
|
|
57
|
+
|
|
58
|
+
### Lazy tab loading
|
|
59
|
+
|
|
60
|
+
With `config.lazy_tabs = true`, only the **first (active) tab** of each group renders up front;
|
|
61
|
+
the others fetch their content the first time they're opened (showing a spinner meanwhile),
|
|
62
|
+
so their fields and association queries only run on demand. This is handy for show pages with
|
|
63
|
+
several heavy association tabs. Without JS the active tab still renders and the deferred tabs
|
|
64
|
+
show a "requires JavaScript" notice. See
|
|
65
|
+
[JavaScript › Lazy tabs](../customization/javascript.md#lazy-tabs).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
class ActionGenerator < Rails::Generators::NamedBase
|
|
8
|
+
namespace "ruby_ui_admin:action"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Generates a RubyUI Admin action."
|
|
12
|
+
|
|
13
|
+
class_option :standalone, type: :boolean, default: false,
|
|
14
|
+
desc: "Action does not require selected records"
|
|
15
|
+
|
|
16
|
+
def create_action
|
|
17
|
+
template "action.rb.tt", File.join("app/ruby_ui_admin/actions", "#{file_path}.rb")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Actions
|
|
5
|
+
class <%= class_name %> < RubyUIAdmin::BaseAction
|
|
6
|
+
self.name = "<%= human_name %>"
|
|
7
|
+
<% if options[:standalone] -%>
|
|
8
|
+
self.standalone = true
|
|
9
|
+
<% end -%>
|
|
10
|
+
|
|
11
|
+
def fields
|
|
12
|
+
# field :note, as: :text
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def handle(query:, fields:, current_user:, **)
|
|
16
|
+
query.each do |record|
|
|
17
|
+
# do something with each record
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
succeed "Done!"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
# Wires the admin's Stimulus controllers into the host app, tailored to its JS setup. The gem
|
|
8
|
+
# ships them flat (e.g. `rua--bulk-select_controller.js`) plus an `index.js` that imports and
|
|
9
|
+
# registers every one under the identifier its markup expects (`rua--bulk-select`,
|
|
10
|
+
# `ruby-ui--toaster`, …).
|
|
11
|
+
#
|
|
12
|
+
# rails g ruby_ui_admin:assets
|
|
13
|
+
#
|
|
14
|
+
# - **Bundler (esbuild/jsbundling/vite):** copies the whole directory verbatim into
|
|
15
|
+
# `app/javascript/ruby_ui_admin/` (a sibling of `controllers/`, so the host's
|
|
16
|
+
# `eagerLoadControllersFrom("controllers", …)` doesn't re-register them under bogus names).
|
|
17
|
+
# The bundler inlines `index.js`'s relative imports, so you just `import "./ruby_ui_admin"`.
|
|
18
|
+
# - **Importmap + Propshaft:** does NOT copy. Copied files get content-digested, and `index.js`'s
|
|
19
|
+
# relative `./x.js` imports resolve to the *undigested* URLs Propshaft won't serve → 404. Instead
|
|
20
|
+
# it pins the copy the engine already serves **undigested** via Rack::Static at
|
|
21
|
+
# `<ASSETS_MOUNT_PATH>/controllers/index.js`, where relative imports work and `@hotwired/stimulus`
|
|
22
|
+
# / `@hotwired/turbo` resolve through the host's own import map.
|
|
23
|
+
#
|
|
24
|
+
# Either way, no `stimulus:manifest:update` — the shipped `index.js` does the registration.
|
|
25
|
+
class AssetsGenerator < Rails::Generators::Base
|
|
26
|
+
namespace "ruby_ui_admin:assets"
|
|
27
|
+
source_root RubyUIAdmin::Engine.root.to_s
|
|
28
|
+
|
|
29
|
+
desc "Sets up the admin's Stimulus controllers (copies for bundlers; pins the served " \
|
|
30
|
+
"entrypoint for importmap)."
|
|
31
|
+
|
|
32
|
+
DEST = "app/javascript/ruby_ui_admin"
|
|
33
|
+
# The engine serves its controllers undigested here (see Engine's Rack::Static mount).
|
|
34
|
+
SERVED_INDEX = "#{RubyUIAdmin::ASSETS_MOUNT_PATH}/controllers/index.js"
|
|
35
|
+
|
|
36
|
+
def install
|
|
37
|
+
if importmap?
|
|
38
|
+
print_importmap_wiring
|
|
39
|
+
else
|
|
40
|
+
directory "public/ruby-ui-admin-assets/controllers", DEST
|
|
41
|
+
print_bundler_wiring
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def print_importmap_wiring
|
|
48
|
+
say "\nImportmap detected — nothing copied (copied files get digested and index.js's", :green
|
|
49
|
+
say "relative imports would 404 under Propshaft). The engine serves them undigested; pin that:", :green
|
|
50
|
+
say "\n # config/importmap.rb"
|
|
51
|
+
say %( pin "ruby_ui_admin", to: "#{SERVED_INDEX}"), :yellow
|
|
52
|
+
say "\n // app/javascript/application.js"
|
|
53
|
+
say %( import "ruby_ui_admin"), :yellow
|
|
54
|
+
say "\n(Stimulus + Turbo must be pinned too — stimulus-rails/turbo-rails do that already.)"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def print_bundler_wiring
|
|
58
|
+
say "\nCopied the admin's Stimulus controllers to #{DEST}/.", :green
|
|
59
|
+
say "Add this import to your JavaScript entrypoint so they're registered:", :yellow
|
|
60
|
+
say "\n // app/javascript/application.js"
|
|
61
|
+
say %( import "./ruby_ui_admin"), :yellow
|
|
62
|
+
say "\nThen rebuild your JS bundle. No stimulus:manifest:update needed — the copied"
|
|
63
|
+
say "index.js registers every controller under the right identifier."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def importmap?
|
|
67
|
+
File.exist?(File.join(destination_root, "config/importmap.rb"))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
class CardGenerator < Rails::Generators::NamedBase
|
|
8
|
+
namespace "ruby_ui_admin:card"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Generates a RubyUI Admin card. Use --type metric|chart|partial."
|
|
12
|
+
|
|
13
|
+
class_option :type, type: :string, default: "metric",
|
|
14
|
+
desc: "Card type: metric, chart or partial"
|
|
15
|
+
|
|
16
|
+
def create_card
|
|
17
|
+
template "#{card_type}_card.rb.tt", File.join("app/ruby_ui_admin/cards", "#{file_path}.rb")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def card_type
|
|
23
|
+
type = options[:type].to_s
|
|
24
|
+
%w[metric chart partial].include?(type) ? type : "metric"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Cards
|
|
5
|
+
class <%= class_name %> < RubyUIAdmin::Cards::PartialCard
|
|
6
|
+
self.label = "<%= human_name %>"
|
|
7
|
+
|
|
8
|
+
def query
|
|
9
|
+
"<p class='text-sm text-muted-foreground'>Hello from <%= human_name %>.</p>"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
# Generates the RubyUI components the admin's views render, by shelling out to
|
|
8
|
+
# `ruby_ui:component` once per component (RubyUI's generator takes one at a time). Without these,
|
|
9
|
+
# the admin references `RubyUI::*` constants your app hasn't generated and blows up with a
|
|
10
|
+
# NameError — this automates installing exactly the set it needs.
|
|
11
|
+
#
|
|
12
|
+
# rails g ruby_ui_admin:components
|
|
13
|
+
#
|
|
14
|
+
# Safe to re-run: passes `--skip`, so components you already have are left untouched.
|
|
15
|
+
class ComponentsGenerator < Rails::Generators::Base
|
|
16
|
+
namespace "ruby_ui_admin:components"
|
|
17
|
+
|
|
18
|
+
desc "Generates the RubyUI components the admin renders (via ruby_ui:component, one per command)."
|
|
19
|
+
|
|
20
|
+
# The RubyUI components the admin's views reference. These are RubyUI *group* names (what you
|
|
21
|
+
# pass to `ruby_ui:component`), not individual classes:
|
|
22
|
+
# - Typography provides InlineLink (there is no standalone `InlineLink` component).
|
|
23
|
+
# - Card/Table/Sidebar/Combobox each bring their whole family (CardHeader, TableRow, …).
|
|
24
|
+
# - Sheet isn't rendered directly — it's a dependency of RubyUI's mobile Sidebar.
|
|
25
|
+
# Kept in sync with the engine by test/generators/generators_test.rb.
|
|
26
|
+
COMPONENTS = %w[
|
|
27
|
+
Card Badge Button Checkbox Combobox Typography Link Input Textarea Table Sidebar Sheet
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
def generate_components
|
|
31
|
+
say "Generating the #{COMPONENTS.size} RubyUI components the admin needs " \
|
|
32
|
+
"(one ruby_ui:component call each)…", :green
|
|
33
|
+
|
|
34
|
+
COMPONENTS.each do |component|
|
|
35
|
+
run "bin/rails generate ruby_ui:component #{component} --skip"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
say "\nDone. Re-run any time — existing components are skipped, not overwritten.", :green
|
|
39
|
+
say "If these fail, install RubyUI first (see docs/getting-started/installation.md).", :yellow
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
# Generates a per-resource controller so you can override CRUD lifecycle hooks.
|
|
8
|
+
# rails g ruby_ui_admin:controller Buyer
|
|
9
|
+
class ControllerGenerator < Rails::Generators::NamedBase
|
|
10
|
+
namespace "ruby_ui_admin:controller"
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Generates a per-resource controller (subclass of ResourcesController)."
|
|
14
|
+
|
|
15
|
+
def create_controller
|
|
16
|
+
template "controller.rb.tt", File.join("app/controllers/ruby_ui_admin", "#{plural_file_name}_controller.rb")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def plural_file_name
|
|
22
|
+
file_name.pluralize
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def controller_class_name
|
|
26
|
+
"#{class_name.pluralize}Controller"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
class <%= controller_class_name %> < RubyUIAdmin::ResourcesController
|
|
5
|
+
# Override CRUD lifecycle hooks as needed. Defaults live in ResourcesController.
|
|
6
|
+
#
|
|
7
|
+
# def after_create_path = resources_index_path
|
|
8
|
+
#
|
|
9
|
+
# def create_success_action
|
|
10
|
+
# super # or: redirect_to after_create_path, notice: "Created!"
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# def update_success_action
|
|
14
|
+
# redirect_to after_update_path, notice: "Updated!"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# def destroy_fail_action
|
|
18
|
+
# redirect_to resources_index_path, alert: "Could not delete."
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# You can also override the CRUD actions themselves (create/update/destroy)
|
|
22
|
+
# and call `super`, or reimplement them.
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
class DashboardGenerator < Rails::Generators::NamedBase
|
|
8
|
+
namespace "ruby_ui_admin:dashboard"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Generates a RubyUI Admin dashboard."
|
|
12
|
+
|
|
13
|
+
def create_dashboard
|
|
14
|
+
template "dashboard.rb.tt", File.join("app/ruby_ui_admin/dashboards", "#{file_path}.rb")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Dashboards
|
|
5
|
+
class <%= class_name %> < RubyUIAdmin::BaseDashboard
|
|
6
|
+
self.name = "<%= human_name %>"
|
|
7
|
+
# self.grid_columns = 3
|
|
8
|
+
|
|
9
|
+
def cards
|
|
10
|
+
# card RubyUIAdmin::Cards::SomeMetric
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
# Copies an engine controller / view component / UI primitive into the host app at
|
|
8
|
+
# the same path. The host's copy shadows the engine's (Zeitwerk loads the app's file
|
|
9
|
+
# first), so you can customize it freely.
|
|
10
|
+
#
|
|
11
|
+
# rails g ruby_ui_admin:eject --view index
|
|
12
|
+
# rails g ruby_ui_admin:eject --view layout
|
|
13
|
+
# rails g ruby_ui_admin:eject --controller resources
|
|
14
|
+
# rails g ruby_ui_admin:eject --ui button
|
|
15
|
+
class EjectGenerator < Rails::Generators::Base
|
|
16
|
+
namespace "ruby_ui_admin:eject"
|
|
17
|
+
source_root RubyUIAdmin::Engine.root.to_s
|
|
18
|
+
|
|
19
|
+
desc "Copies an engine file into your app so you can customize it."
|
|
20
|
+
|
|
21
|
+
class_option :controller, type: :string, desc: "Controller to eject (e.g. resources, application, actions)"
|
|
22
|
+
class_option :view, type: :string, desc: "View component to eject (e.g. index, show, form, layout)"
|
|
23
|
+
class_option :ui, type: :string, desc: "UI primitive to eject (e.g. button, card, table, badge)"
|
|
24
|
+
|
|
25
|
+
def eject
|
|
26
|
+
path = resolve_path
|
|
27
|
+
return if path.nil?
|
|
28
|
+
|
|
29
|
+
unless File.exist?(File.join(self.class.source_root, path))
|
|
30
|
+
say "RubyUI Admin: nothing to eject at #{path}", :red
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
copy_file path, path
|
|
35
|
+
say "\nEjected #{path}. Edit it in your app to customize.", :green
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def resolve_path
|
|
41
|
+
if options[:controller]
|
|
42
|
+
"app/controllers/ruby_ui_admin/#{options[:controller]}_controller.rb"
|
|
43
|
+
elsif options[:view]
|
|
44
|
+
file = (options[:view] == "layout") ? "base" : options[:view]
|
|
45
|
+
"app/components/ruby_ui_admin/views/#{file}.rb"
|
|
46
|
+
elsif options[:ui]
|
|
47
|
+
"app/components/ruby_ui_admin/ui/#{options[:ui]}.rb"
|
|
48
|
+
else
|
|
49
|
+
say "Specify what to eject: --controller NAME, --view NAME (or layout), or --ui NAME", :yellow
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module RubyUIAdmin
|
|
6
|
+
module Generators
|
|
7
|
+
class FilterGenerator < Rails::Generators::NamedBase
|
|
8
|
+
namespace "ruby_ui_admin:filter"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Generates a RubyUI Admin filter. Use --type text|select|boolean."
|
|
12
|
+
|
|
13
|
+
class_option :type, type: :string, default: "text",
|
|
14
|
+
desc: "Filter type: text, select or boolean"
|
|
15
|
+
|
|
16
|
+
def create_filter
|
|
17
|
+
template "#{filter_type}_filter.rb.tt", File.join("app/ruby_ui_admin/filters", "#{file_path}_filter.rb")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def filter_type
|
|
23
|
+
type = options[:type].to_s
|
|
24
|
+
%w[text select boolean].include?(type) ? type : "text"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Filters
|
|
5
|
+
class <%= class_name %>Filter < RubyUIAdmin::Filters::BooleanFilter
|
|
6
|
+
self.name = "<%= human_name %>"
|
|
7
|
+
|
|
8
|
+
def apply(request, query, value)
|
|
9
|
+
query.where(<%= singular_name %>: value == "true")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Filters
|
|
5
|
+
class <%= class_name %>Filter < RubyUIAdmin::Filters::SelectFilter
|
|
6
|
+
self.name = "<%= human_name %>"
|
|
7
|
+
|
|
8
|
+
def apply(request, query, value)
|
|
9
|
+
query.where(<%= singular_name %>: value)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def options
|
|
13
|
+
{
|
|
14
|
+
# "value" => "Label"
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyUIAdmin
|
|
4
|
+
module Filters
|
|
5
|
+
class <%= class_name %>Filter < RubyUIAdmin::Filters::TextFilter
|
|
6
|
+
self.name = "<%= human_name %>"
|
|
7
|
+
|
|
8
|
+
def apply(request, query, value)
|
|
9
|
+
query.where("<%= singular_name %> LIKE ?", "%#{value}%")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|