plutonium 0.63.0 → 0.65.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 +4 -4
- data/.claude/skills/plutonium/SKILL.md +4 -1
- data/.claude/skills/plutonium-app/SKILL.md +14 -0
- data/.claude/skills/plutonium-dashboard/SKILL.md +161 -0
- data/.claude/skills/plutonium-resource/SKILL.md +94 -14
- data/.claude/skills/plutonium-ui/SKILL.md +22 -1
- data/.standard.yml +1 -1
- data/CHANGELOG.md +65 -0
- data/CLAUDE.md +282 -210
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +175 -0
- data/docs/blog/introducing-plutonium-i18n.md +91 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- data/docs/guides/dashboards.md +300 -0
- data/docs/guides/index.md +1 -0
- data/docs/guides/kanban.md +2 -2
- data/docs/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +2 -2
- data/docs/reference/app/portals.md +2 -0
- data/docs/reference/configuration.md +84 -27
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/i18n.md +239 -0
- data/docs/reference/index.md +10 -1
- data/docs/reference/kanban/dsl.md +1 -1
- data/docs/reference/kanban/positioning.md +2 -2
- data/docs/reference/resource/actions.md +1 -1
- data/docs/reference/resource/definition.md +96 -18
- data/docs/reference/resource/export.md +5 -1
- data/docs/reference/resource/index.md +2 -0
- data/docs/reference/{positioning.md → resource/positioning.md} +2 -1
- data/docs/reference/resource/query.md +2 -0
- data/docs/reference/ui/assets.md +8 -0
- data/docs/reference/ui/displays.md +5 -8
- data/docs/reference/ui/index.md +1 -1
- data/docs/reference/wizard/dsl.md +2 -2
- data/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +3 -3
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +2 -2
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +15 -30
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +91 -0
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +71 -31
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
|
@@ -88,10 +88,10 @@ module Plutonium
|
|
|
88
88
|
) do
|
|
89
89
|
div(class: "px-6 pt-5 pb-4 border-b border-[var(--pu-border)]") do
|
|
90
90
|
h2(id: "pu-dirty-guard-title", class: "text-lg font-semibold text-[var(--pu-text)]") do
|
|
91
|
-
"
|
|
91
|
+
t("plutonium.ui.form.discard_title")
|
|
92
92
|
end
|
|
93
93
|
p(id: "pu-dirty-guard-desc", class: "mt-1 text-sm text-[var(--pu-text-muted)]") do
|
|
94
|
-
"
|
|
94
|
+
t("plutonium.ui.form.discard_description")
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
div(class: "flex items-center justify-end gap-2 px-6 py-4") do
|
|
@@ -99,12 +99,12 @@ module Plutonium
|
|
|
99
99
|
type: "button",
|
|
100
100
|
class: "pu-btn pu-btn-md pu-btn-outline",
|
|
101
101
|
data: {action: "dirty-form-guard#keepEditing"}
|
|
102
|
-
) { "
|
|
102
|
+
) { t("plutonium.ui.form.keep_editing") }
|
|
103
103
|
button(
|
|
104
104
|
type: "button",
|
|
105
105
|
class: "pu-btn pu-btn-md pu-btn-danger",
|
|
106
106
|
data: {action: "dirty-form-guard#discard"}
|
|
107
|
-
) { "
|
|
107
|
+
) { t("plutonium.ui.form.discard_changes") }
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
end
|
|
@@ -125,6 +125,12 @@ module Plutonium
|
|
|
125
125
|
div(class: themed(:sections_wrapper, nil)) do
|
|
126
126
|
sections.each { |rs| render_form_section(rs) }
|
|
127
127
|
end
|
|
128
|
+
# Fields of sections dropped because every one of them is
|
|
129
|
+
# condition-hidden. They still go through the normal render path,
|
|
130
|
+
# which records them without emitting markup (see
|
|
131
|
+
# render_simple_resource_field), so the form keeps extracting their
|
|
132
|
+
# values exactly as it does for a hidden field in a visible section.
|
|
133
|
+
@condition_hidden_section_fields.each { |name| render_resource_field name }
|
|
128
134
|
end
|
|
129
135
|
end
|
|
130
136
|
|
|
@@ -134,6 +140,7 @@ module Plutonium
|
|
|
134
140
|
# helpers live — same context as input/section `condition:`). Returns nil
|
|
135
141
|
# when no form_layout is declared (caller falls back to a single grid).
|
|
136
142
|
def resolve_form_layout
|
|
143
|
+
@condition_hidden_section_fields = []
|
|
137
144
|
sections = resource_definition.resolve_form_sections(resource_fields)
|
|
138
145
|
return nil if sections.nil?
|
|
139
146
|
|
|
@@ -146,12 +153,19 @@ module Plutonium
|
|
|
146
153
|
# filtered out by the permitted set (policy, per-action, scoping,
|
|
147
154
|
# nesting). Rendering the chrome (heading + empty grid) for these
|
|
148
155
|
# litters the form with empty headings (notably on `+ New`, where
|
|
149
|
-
# fewer attributes are permitted than declared).
|
|
150
|
-
# `condition:` is evaluated later, at render — a section whose fields
|
|
151
|
-
# are all condition-hidden is the author's call to gate via the
|
|
152
|
-
# section's own `condition:`.
|
|
156
|
+
# fewer attributes are permitted than declared).
|
|
153
157
|
next if resolved.fields.empty?
|
|
154
158
|
|
|
159
|
+
# Likewise drop sections whose fields are ALL hidden by their own
|
|
160
|
+
# `condition:` on this render (e.g. a "Timing" section of fields that
|
|
161
|
+
# only apply to some record types, re-evaluated on `pre_submit`).
|
|
162
|
+
# Their fields are kept aside and rendered after the sections, so
|
|
163
|
+
# the hidden values are still recorded.
|
|
164
|
+
if resolved.fields.all? { |name| field_condition_hidden?(name) }
|
|
165
|
+
@condition_hidden_section_fields.concat(resolved.fields)
|
|
166
|
+
next
|
|
167
|
+
end
|
|
168
|
+
|
|
155
169
|
# `columns` stays a validated literal (the Builder rejects a non-Integer
|
|
156
170
|
# at declaration); `label`/`description`/`collapsible`/`collapsed` may be
|
|
157
171
|
# procs, resolved by the SAME arity rule as a field/input option so
|
|
@@ -268,7 +282,7 @@ module Plutonium
|
|
|
268
282
|
end
|
|
269
283
|
|
|
270
284
|
def render_submit_and_continue_button
|
|
271
|
-
label = object.new_record? ? "
|
|
285
|
+
label = object.new_record? ? t("plutonium.ui.form.create_and_add_another") : t("plutonium.ui.form.update_and_continue_editing")
|
|
272
286
|
|
|
273
287
|
button(
|
|
274
288
|
type: :submit,
|
|
@@ -315,19 +329,22 @@ module Plutonium
|
|
|
315
329
|
|
|
316
330
|
tag = input_options[:as] || field_options[:as]
|
|
317
331
|
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
|
|
322
|
-
field_level_options = input_options.slice(*
|
|
332
|
+
# Route the input's own field-level options to form.field(); strip the
|
|
333
|
+
# full union from the tag attributes so a display-only key (e.g.
|
|
334
|
+
# :description) declared here is dropped rather than leaked as an HTML
|
|
335
|
+
# attribute.
|
|
336
|
+
field_level_options = input_options.slice(*FORM_FIELD_LEVEL_KEYS)
|
|
323
337
|
field_options = field_options.merge(field_level_options)
|
|
338
|
+
# Slots the definition left blank fall back to the locale convention
|
|
339
|
+
# (plutonium.fields.<model>.<attr>.placeholder / .hint).
|
|
340
|
+
field_options = Plutonium::Translation.fill_field_text(field_options, form.object.class, name, :placeholder, :hint)
|
|
324
341
|
|
|
325
342
|
# Staging options are stripped here as well as in Wizard::StepAdapter,
|
|
326
343
|
# because an interaction's inputs reach this method directly. A
|
|
327
344
|
# Class-valued `uploader:` is not an HTML attribute and Phlex refuses
|
|
328
345
|
# it, so leaving it in raises rather than rendering.
|
|
329
346
|
tag_attributes = input_options.except(
|
|
330
|
-
:wrapper, :as, :pre_submit, :condition, *
|
|
347
|
+
:wrapper, :as, :pre_submit, :condition, *FIELD_LEVEL_KEYS,
|
|
331
348
|
*Plutonium::Attachments::STAGING_ONLY_INPUT_OPTIONS
|
|
332
349
|
)
|
|
333
350
|
if input_options[:pre_submit]
|
|
@@ -344,10 +361,15 @@ module Plutonium
|
|
|
344
361
|
# renders correctly. The Builder consumes `:as` before Phlexi sees
|
|
345
362
|
# the options, so this cannot leak into a Phlexi field option.
|
|
346
363
|
field_options = field_options.merge(as: tag) if tag
|
|
347
|
-
field_options = field_options.except(:condition)
|
|
348
364
|
|
|
365
|
+
# Check for conditional rendering — read `:condition` from
|
|
366
|
+
# `field_options` BEFORE stripping it, so a `field :name,
|
|
367
|
+
# condition: -> {...}` declaration is honoured, not just
|
|
368
|
+
# `input :name, condition: -> {...}`. Mirrors the display
|
|
369
|
+
# renderer, which reads then removes (see display/resource.rb).
|
|
349
370
|
condition = input_options[:condition] || field_options[:condition]
|
|
350
371
|
conditionally_hidden = condition && !instance_exec(&condition)
|
|
372
|
+
field_options = field_options.except(:condition)
|
|
351
373
|
if conditionally_hidden
|
|
352
374
|
# Do not render the field, but still create field
|
|
353
375
|
# Phlexi form will record it without rendering it, allowing us to extract its value
|
|
@@ -411,18 +433,24 @@ module Plutonium
|
|
|
411
433
|
# is why it cannot take a `form` argument the way an option does.
|
|
412
434
|
# Resolving it here would also collapse it to a boolean before the render
|
|
413
435
|
# site that owns it gets to ask.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
436
|
+
# Whether +name+ is hidden on this render by its own `condition:` — the
|
|
437
|
+
# same check render_resource_field makes before rendering it: a nested
|
|
438
|
+
# input's condition, else the `input`/`field` condition. Structured
|
|
439
|
+
# inputs have no condition of their own and always count as visible.
|
|
440
|
+
def field_condition_hidden?(name)
|
|
441
|
+
definition = resource_definition
|
|
442
|
+
condition =
|
|
443
|
+
if definition.respond_to?(:defined_nested_inputs) && (nested = definition.defined_nested_inputs[name])
|
|
444
|
+
nested[:options]&.fetch(:condition, nil)
|
|
445
|
+
elsif definition.respond_to?(:defined_structured_inputs) && definition.defined_structured_inputs[name]
|
|
446
|
+
nil
|
|
447
|
+
else
|
|
448
|
+
definition.defined_inputs.dig(name, :options, :condition) ||
|
|
449
|
+
definition.defined_fields.dig(name, :options, :condition)
|
|
450
|
+
end
|
|
451
|
+
condition.present? && !instance_exec(&condition)
|
|
420
452
|
end
|
|
421
453
|
|
|
422
|
-
def resolvable_proc?(key, value) = key != :condition && value.is_a?(Proc)
|
|
423
|
-
|
|
424
|
-
def call_option_proc(value) = value.arity.zero? ? value.call : value.call(self)
|
|
425
|
-
|
|
426
454
|
def when_permitted(name, &)
|
|
427
455
|
return unless resource_fields.include? name
|
|
428
456
|
|
|
@@ -66,10 +66,10 @@ module Plutonium
|
|
|
66
66
|
div(data: {controller: %w[frame-navigator]}) do
|
|
67
67
|
Panel do |panel|
|
|
68
68
|
panel.with_title @title
|
|
69
|
-
panel.with_item PanelItem.new(label: "
|
|
70
|
-
panel.with_item PanelItem.new(label: "
|
|
71
|
-
panel.with_item PanelItem.new(label: "
|
|
72
|
-
panel.with_item PanelLink.new(label: "
|
|
69
|
+
panel.with_item PanelItem.new(label: t("plutonium.ui.home"), icon: Phlex::TablerIcons::Home2, data_frame_navigator_target: "homeButton")
|
|
70
|
+
panel.with_item PanelItem.new(label: t("plutonium.ui.frame_navigator.back"), icon: Phlex::TablerIcons::ChevronLeft, data_frame_navigator_target: "backButton")
|
|
71
|
+
panel.with_item PanelItem.new(label: t("plutonium.ui.frame_navigator.refresh"), icon: Phlex::TablerIcons::RefreshDot, data_frame_navigator_target: "refreshButton")
|
|
72
|
+
panel.with_item PanelLink.new(label: t("plutonium.ui.frame_navigator.maximize"), icon: Phlex::TablerIcons::WindowMaximize, href: @src, data_frame_navigator_target: "maximizeLink")
|
|
73
73
|
panel.with_content PanelContent.new(id: @panel_id, src: @src)
|
|
74
74
|
end
|
|
75
75
|
end
|
|
@@ -200,7 +200,7 @@ module Plutonium
|
|
|
200
200
|
label = helpers.display_name_of(value)
|
|
201
201
|
variant = badge.variant_for(label)
|
|
202
202
|
else
|
|
203
|
-
label = badge.humanize(value)
|
|
203
|
+
label = Plutonium::Translation.value_label(@record.class, name, value) || badge.humanize(value)
|
|
204
204
|
variant = badge.variant_for(value)
|
|
205
205
|
end
|
|
206
206
|
|
|
@@ -269,8 +269,8 @@ module Plutonium
|
|
|
269
269
|
data: {row_click_target: "show", turbo_frame: @show_turbo_frame || show.turbo_frame(resource_definition)},
|
|
270
270
|
class: "sr-only",
|
|
271
271
|
tabindex: "-1",
|
|
272
|
-
"aria-label": "
|
|
273
|
-
})) { plain "
|
|
272
|
+
"aria-label": t("plutonium.ui.grid.open_record", name: header_text)
|
|
273
|
+
})) { plain t("plutonium.ui.grid.open") }
|
|
274
274
|
end
|
|
275
275
|
|
|
276
276
|
# ---------------------------------------------------------------
|
|
@@ -63,7 +63,7 @@ module Plutonium
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def render_empty_card
|
|
66
|
-
EmptyCard("
|
|
66
|
+
EmptyCard(t("plutonium.ui.empty_state.none_available", resources: resource_name_plural(resource_class).downcase)) {
|
|
67
67
|
action = resource_definition.defined_actions[:new]
|
|
68
68
|
if action&.permitted_by?(current_policy)
|
|
69
69
|
url = route_options_to_url(action.route_options, resource_class)
|
|
@@ -160,7 +160,7 @@ module Plutonium
|
|
|
160
160
|
"data-[open]:translate-x-0 " \
|
|
161
161
|
"flex flex-col",
|
|
162
162
|
role: "dialog",
|
|
163
|
-
aria: {label: "
|
|
163
|
+
aria: {label: t("plutonium.ui.table.filters"), hidden: "true", modal: "true"},
|
|
164
164
|
data: {filter_panel_target: "panel"}
|
|
165
165
|
) do
|
|
166
166
|
render Plutonium::UI::Table::Components::FilterForm.new(
|
|
@@ -181,7 +181,7 @@ module Plutonium
|
|
|
181
181
|
|
|
182
182
|
def render_footer
|
|
183
183
|
div(class: "lg:sticky bottom-[-2px] mt-1 p-4 pb-6 w-full z-30 bg-[var(--pu-body)]") {
|
|
184
|
-
TableInfo(pagy_instance)
|
|
184
|
+
TableInfo(pagy_instance, item_name: resource_name(resource_class, pagy_instance.count))
|
|
185
185
|
TablePagination(pagy_instance)
|
|
186
186
|
}
|
|
187
187
|
end
|
|
@@ -118,7 +118,7 @@ module Plutonium
|
|
|
118
118
|
def render_status
|
|
119
119
|
div(class: "flex items-center gap-2 flex-wrap") do
|
|
120
120
|
span(class: "pu-badge pu-badge-#{BADGE_VARIANTS.fetch(run.outcome, "neutral")}") do
|
|
121
|
-
plain run.outcome.humanize
|
|
121
|
+
plain(Plutonium::Translation.value_label(run.class, :outcome, run.outcome) || run.outcome.humanize)
|
|
122
122
|
end
|
|
123
123
|
# The count rides alongside the badge rather than only in the list
|
|
124
124
|
# below: a partially-applied :continue run ends as "completed", and
|
|
@@ -126,7 +126,7 @@ module Plutonium
|
|
|
126
126
|
# do everything it was asked to.
|
|
127
127
|
if run.error_count.positive?
|
|
128
128
|
span(class: "text-xs font-medium text-[var(--pu-text-danger)]") do
|
|
129
|
-
plain "
|
|
129
|
+
plain t("plutonium.async.progress.error_count", count: run.error_count)
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
end
|
|
@@ -151,7 +151,7 @@ module Plutonium
|
|
|
151
151
|
div(class: "h-2 rounded-full bg-primary-600", style: "width: #{percent}%")
|
|
152
152
|
end
|
|
153
153
|
div(class: "text-xs text-[var(--pu-text-muted)]") do
|
|
154
|
-
plain "
|
|
154
|
+
plain t("plutonium.async.progress.targets", done: run.progress_done, total: run.progress_total, percent: percent)
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
end
|
|
@@ -175,7 +175,7 @@ module Plutonium
|
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
div(class: "text-xs text-[var(--pu-text-muted)]") do
|
|
178
|
-
plain(run.in_progress? ? "
|
|
178
|
+
plain(run.in_progress? ? t("plutonium.async.progress.working") : t("plutonium.async.progress.no_total"))
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
end
|
|
@@ -204,7 +204,7 @@ module Plutonium
|
|
|
204
204
|
def render_failures
|
|
205
205
|
div(class: "space-y-1") do
|
|
206
206
|
div(class: "text-[10px] font-semibold uppercase tracking-wider text-[var(--pu-text-muted)]") do
|
|
207
|
-
plain "
|
|
207
|
+
plain t("plutonium.async.progress.failures")
|
|
208
208
|
end
|
|
209
209
|
ul(class: "pu-run-progress-failures text-xs space-y-1") do
|
|
210
210
|
run.errors_log.each do |entry|
|
|
@@ -218,7 +218,7 @@ module Plutonium
|
|
|
218
218
|
# not a target whose id happens to be blank.
|
|
219
219
|
def failure_text(entry)
|
|
220
220
|
target_id = entry["target_id"]
|
|
221
|
-
target_id.nil? ? entry["message"].to_s : "
|
|
221
|
+
target_id.nil? ? entry["message"].to_s : t("plutonium.async.progress.target_failure", target_id: target_id, message: entry["message"])
|
|
222
222
|
end
|
|
223
223
|
end
|
|
224
224
|
end
|
|
@@ -51,10 +51,10 @@ module Plutonium
|
|
|
51
51
|
# own page renders the pair.
|
|
52
52
|
span(class: "font-medium text-[var(--pu-text)]") { plain run.to_label }
|
|
53
53
|
plain " "
|
|
54
|
-
span(class: "text-[var(--pu-text-muted)]") { plain run.state.humanize }
|
|
54
|
+
span(class: "text-[var(--pu-text-muted)]") { plain(Plutonium::Translation.value_label(run.class, :state, run.state) || run.state.humanize) }
|
|
55
55
|
end
|
|
56
56
|
a(href: resource_url_for(run), class: "pu-btn pu-btn-xs pu-btn-soft-primary shrink-0") do
|
|
57
|
-
plain "
|
|
57
|
+
plain t("plutonium.async.banner.view_progress")
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -162,7 +162,7 @@ module Plutonium
|
|
|
162
162
|
button(
|
|
163
163
|
class: "p-0.5 rounded text-[var(--pu-text-muted)] " \
|
|
164
164
|
"hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]",
|
|
165
|
-
title: "
|
|
165
|
+
title: t("plutonium.kanban.column.expand", column: column.label),
|
|
166
166
|
type: "button",
|
|
167
167
|
data: {
|
|
168
168
|
action: "click->kanban#toggleColumn",
|
|
@@ -214,7 +214,7 @@ module Plutonium
|
|
|
214
214
|
button(
|
|
215
215
|
class: "shrink-0 p-0.5 rounded text-[var(--pu-text-muted)] " \
|
|
216
216
|
"hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]",
|
|
217
|
-
title: "
|
|
217
|
+
title: t("plutonium.kanban.column.collapse", column: column.label),
|
|
218
218
|
type: "button",
|
|
219
219
|
data: {
|
|
220
220
|
action: "click->kanban#toggleColumn",
|
|
@@ -237,7 +237,7 @@ module Plutonium
|
|
|
237
237
|
"pu-badge text-xs font-mono",
|
|
238
238
|
over ? "pu-badge-danger" : "pu-badge-neutral"
|
|
239
239
|
),
|
|
240
|
-
title: over ? "
|
|
240
|
+
title: over ? t("plutonium.kanban.column.wip_limit_exceeded") : t("plutonium.kanban.column.wip_limit", limit: column.wip)
|
|
241
241
|
) do
|
|
242
242
|
plain "#{cards.size}/#{column.wip}"
|
|
243
243
|
end
|
|
@@ -281,7 +281,7 @@ module Plutonium
|
|
|
281
281
|
def render_more_footer
|
|
282
282
|
div(class: "px-3 py-2 border-t border-[var(--pu-border)] bg-[var(--pu-surface)]") do
|
|
283
283
|
span(class: "text-xs text-[var(--pu-text-muted)]") do
|
|
284
|
-
plain "
|
|
284
|
+
plain t("plutonium.kanban.column.more", count: more_count)
|
|
285
285
|
end
|
|
286
286
|
end
|
|
287
287
|
end
|
|
@@ -296,10 +296,10 @@ module Plutonium
|
|
|
296
296
|
link_to(
|
|
297
297
|
@column_add_url,
|
|
298
298
|
class: "pu-btn pu-btn-ghost pu-btn-xs text-[var(--pu-text-muted)]",
|
|
299
|
-
title: "
|
|
299
|
+
title: t("plutonium.kanban.column.add_to", column: column.label),
|
|
300
300
|
data: {turbo_frame: Plutonium::REMOTE_MODAL_FRAME}
|
|
301
301
|
) do
|
|
302
|
-
plain "
|
|
302
|
+
plain t("plutonium.kanban.column.add")
|
|
303
303
|
end
|
|
304
304
|
end
|
|
305
305
|
|
|
@@ -21,7 +21,7 @@ module Plutonium
|
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
|
-
def lang =
|
|
24
|
+
def lang = I18n.locale.to_s
|
|
25
25
|
|
|
26
26
|
def page_title = view_context.controller.instance_variable_get(:@page_title)
|
|
27
27
|
|
|
@@ -35,6 +35,7 @@ module Plutonium
|
|
|
35
35
|
head {
|
|
36
36
|
render_title
|
|
37
37
|
render_metatags
|
|
38
|
+
render_i18n
|
|
38
39
|
render_assets
|
|
39
40
|
render_pre_paint_scripts
|
|
40
41
|
}
|
|
@@ -175,6 +176,23 @@ module Plutonium
|
|
|
175
176
|
render_scripts
|
|
176
177
|
end
|
|
177
178
|
|
|
179
|
+
# The `plutonium.js` locale subtree as JSON for src/js/i18n.js, in a
|
|
180
|
+
# <meta> rather than a <script>: Turbo Drive replaces provisional head
|
|
181
|
+
# elements on every visit but only ever appends scripts, so a locale
|
|
182
|
+
# switch mid-session would otherwise keep serving the first blob.
|
|
183
|
+
#
|
|
184
|
+
# Merged over the default locale so a partially translated locale
|
|
185
|
+
# falls back per key instead of dropping every untranslated string.
|
|
186
|
+
def render_i18n
|
|
187
|
+
meta(name: "pu-i18n", content: i18n_blob.to_json)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def i18n_blob
|
|
191
|
+
defaults = I18n.t("plutonium.js", locale: I18n.default_locale, default: {})
|
|
192
|
+
current = (I18n.locale == I18n.default_locale) ? {} : I18n.t("plutonium.js", default: {})
|
|
193
|
+
defaults.deep_merge(current)
|
|
194
|
+
end
|
|
195
|
+
|
|
178
196
|
def render_styles
|
|
179
197
|
render_external_styles
|
|
180
198
|
|
|
@@ -109,7 +109,7 @@ module Plutonium
|
|
|
109
109
|
render Phlex::TablerIcons::X.new(class: "w-6 h-6")
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
span(class: "sr-only") { "
|
|
112
|
+
span(class: "sr-only") { t("plutonium.ui.layout.toggle_sidebar") }
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# Renders the action buttons section
|
|
@@ -38,7 +38,7 @@ module Plutonium
|
|
|
38
38
|
aside(
|
|
39
39
|
id: "sidebar-navigation",
|
|
40
40
|
data: {controller: "sidebar icon-rail"},
|
|
41
|
-
aria: {label: "
|
|
41
|
+
aria: {label: t("plutonium.ui.layout.sidebar_navigation")},
|
|
42
42
|
class: "fixed top-0 left-0 z-40 h-dvh " \
|
|
43
43
|
"bg-[var(--pu-surface)] border-r border-[var(--pu-border)] " \
|
|
44
44
|
"flex flex-col transition-[width] duration-200 overflow-x-hidden " \
|
|
@@ -56,7 +56,7 @@ module Plutonium
|
|
|
56
56
|
div(class: "h-12 flex items-center justify-center border-b border-[var(--pu-border)] shrink-0") do
|
|
57
57
|
next unless brand_slot?
|
|
58
58
|
|
|
59
|
-
a(href: root_path, aria: {label: "
|
|
59
|
+
a(href: root_path, aria: {label: t("plutonium.ui.home")}, class: "flex items-center justify-center") do
|
|
60
60
|
render brand_slot
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -81,8 +81,8 @@ module Plutonium
|
|
|
81
81
|
def render_pin_button
|
|
82
82
|
button(
|
|
83
83
|
type: "button",
|
|
84
|
-
title: "
|
|
85
|
-
aria: {label: "
|
|
84
|
+
title: t("plutonium.ui.layout.toggle_sidebar"),
|
|
85
|
+
aria: {label: t("plutonium.ui.layout.toggle_sidebar_pin")},
|
|
86
86
|
data: {action: "icon-rail#togglePin"},
|
|
87
87
|
class: "flex items-center justify-center w-10 h-10 rounded-md transition-colors " \
|
|
88
88
|
"text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]"
|
|
@@ -45,7 +45,7 @@ module Plutonium
|
|
|
45
45
|
def render_links
|
|
46
46
|
link_to root_path, class: "mt-4 inline-flex items-center gap-1.5 font-medium text-secondary-600 dark:text-secondary-400 hover:underline transition-colors" do
|
|
47
47
|
render Phlex::TablerIcons::Home2.new(class: "size-5")
|
|
48
|
-
plain "
|
|
48
|
+
plain t("plutonium.ui.home")
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -17,7 +17,7 @@ module Plutonium
|
|
|
17
17
|
aside(
|
|
18
18
|
data: {controller: "sidebar"},
|
|
19
19
|
id: "sidebar-navigation",
|
|
20
|
-
aria: {label: "
|
|
20
|
+
aria: {label: t("plutonium.ui.layout.sidebar_navigation")},
|
|
21
21
|
class: "fixed top-0 left-0 z-40 w-64 h-dvh pt-14 transition-transform -translate-x-full lg:translate-x-0"
|
|
22
22
|
) do
|
|
23
23
|
div(
|
|
@@ -55,7 +55,7 @@ module Plutonium
|
|
|
55
55
|
type: "button",
|
|
56
56
|
data_action: "resource-header#toggleDrawer",
|
|
57
57
|
aria_controls: "#sidebar-navigation",
|
|
58
|
-
aria_label: "
|
|
58
|
+
aria_label: t("plutonium.ui.layout.toggle_sidebar"),
|
|
59
59
|
class: "p-1.5 -ml-1.5 text-[var(--pu-text-muted)] rounded-md " \
|
|
60
60
|
"hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] " \
|
|
61
61
|
"lg:hidden transition-colors"
|
|
@@ -71,7 +71,7 @@ module Plutonium
|
|
|
71
71
|
span(data_resource_header_target: "closeIcon", class: "hidden", aria_hidden: "true") do
|
|
72
72
|
render Phlex::TablerIcons::X.new(class: "w-5 h-5")
|
|
73
73
|
end
|
|
74
|
-
span(class: "sr-only") { "
|
|
74
|
+
span(class: "sr-only") { t("plutonium.ui.layout.toggle_sidebar") }
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def render_breadcrumbs_section
|
|
@@ -60,7 +60,7 @@ module Plutonium
|
|
|
60
60
|
if @title
|
|
61
61
|
attrs[:"aria-labelledby"] = title_id
|
|
62
62
|
else
|
|
63
|
-
attrs[:"aria-label"] = "
|
|
63
|
+
attrs[:"aria-label"] = t("plutonium.ui.modal.dialog")
|
|
64
64
|
end
|
|
65
65
|
attrs[:"aria-describedby"] = description_id if @description.present?
|
|
66
66
|
attrs
|
|
@@ -130,8 +130,8 @@ module Plutonium
|
|
|
130
130
|
target: "_blank",
|
|
131
131
|
rel: "noopener",
|
|
132
132
|
class: "p-1.5 text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] rounded-md transition-colors",
|
|
133
|
-
"aria-label": "
|
|
134
|
-
title: "
|
|
133
|
+
"aria-label": t("plutonium.ui.modal.open_full_page_new_tab"),
|
|
134
|
+
title: t("plutonium.ui.modal.open_full_page")
|
|
135
135
|
) do
|
|
136
136
|
render Phlex::TablerIcons::ArrowsDiagonal.new(class: "w-5 h-5")
|
|
137
137
|
end
|
|
@@ -145,7 +145,7 @@ module Plutonium
|
|
|
145
145
|
type: "button",
|
|
146
146
|
class: "p-1.5 text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] rounded-md transition-colors",
|
|
147
147
|
data: {action: "remote-modal#close"},
|
|
148
|
-
"aria-label": "
|
|
148
|
+
"aria-label": t("plutonium.ui.modal.close")
|
|
149
149
|
) do
|
|
150
150
|
render Phlex::TablerIcons::X.new(class: "w-5 h-5")
|
|
151
151
|
end
|
|
@@ -70,7 +70,7 @@ module Plutonium
|
|
|
70
70
|
data: {resource_drop_down_target: "trigger"},
|
|
71
71
|
class: "p-2 text-[var(--pu-text-muted)] rounded-[var(--pu-radius-md)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] focus:ring-2 focus:ring-[var(--pu-border)] transition-colors"
|
|
72
72
|
) do
|
|
73
|
-
span(class: "sr-only") { "
|
|
73
|
+
span(class: "sr-only") { t("plutonium.ui.nav_grid_menu.view", label: @label) }
|
|
74
74
|
render @icon.new(class: "w-6 h-6")
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -76,8 +76,8 @@ module Plutonium
|
|
|
76
76
|
id: "user-nav-dropdown-toggle",
|
|
77
77
|
data: {resource_drop_down_target: "trigger"}
|
|
78
78
|
) do
|
|
79
|
-
span(class: "sr-only") { "
|
|
80
|
-
Avatar(@record, src: @avatar_url, size: :sm, alt: "
|
|
79
|
+
span(class: "sr-only") { t("plutonium.ui.nav_user.open_menu") }
|
|
80
|
+
Avatar(@record, src: @avatar_url, size: :sm, alt: t("plutonium.ui.nav_user.avatar_alt"), class: "ring-2 ring-[var(--pu-border)]")
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -64,11 +64,19 @@ module Plutonium
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def render_page_header
|
|
67
|
-
return unless
|
|
67
|
+
return unless resolved_page_title
|
|
68
68
|
|
|
69
|
-
PageHeader(title:
|
|
69
|
+
PageHeader(title: resolved_page_title, description: resolved_page_description, actions: page_actions)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# Titles and descriptions may be lazy translations (a proc built by
|
|
73
|
+
# the definition's `t`). Every surface that renders them (the page
|
|
74
|
+
# header, the modal chrome) goes through these so the lookup runs in
|
|
75
|
+
# the request's locale, the same way inputs and labels are resolved.
|
|
76
|
+
def resolved_page_title = Plutonium::Translation.resolve(page_title)
|
|
77
|
+
|
|
78
|
+
def resolved_page_description = Plutonium::Translation.resolve(page_description)
|
|
79
|
+
|
|
72
80
|
def render_toolbar
|
|
73
81
|
# Implement toolbar content
|
|
74
82
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module UI
|
|
5
|
+
module Page
|
|
6
|
+
# The page a registered dashboard renders: the standard page header
|
|
7
|
+
# (title + description from `presents`) over the card grid. Keeps the
|
|
8
|
+
# `render_before_*` / `render_after_*` hooks of {Base} so an app can
|
|
9
|
+
# subclass it and add a toolbar or footer without owning the grid.
|
|
10
|
+
class Dashboard < Base
|
|
11
|
+
def initialize(dashboard:)
|
|
12
|
+
@dashboard = dashboard
|
|
13
|
+
super(page_title: dashboard.class.label, page_description: dashboard.class.description)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template(&)
|
|
17
|
+
DynaFrameContent() do
|
|
18
|
+
div(class: tokens("pu-dashboard-page", width_classes)) do
|
|
19
|
+
render_before_header
|
|
20
|
+
render_header
|
|
21
|
+
render_after_header
|
|
22
|
+
|
|
23
|
+
render_before_content
|
|
24
|
+
render_content
|
|
25
|
+
render_after_content
|
|
26
|
+
|
|
27
|
+
render_before_footer
|
|
28
|
+
render_footer
|
|
29
|
+
render_after_footer
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :dashboard
|
|
37
|
+
|
|
38
|
+
def render_content
|
|
39
|
+
render Plutonium::UI::Dashboard::Board.new(dashboard:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A dashboard has no resource definition to consult and is its own
|
|
43
|
+
# navigation root, so it never renders a breadcrumb trail.
|
|
44
|
+
def render_breadcrumbs? = false
|
|
45
|
+
|
|
46
|
+
def width_classes
|
|
47
|
+
Plutonium::UI::PageWidth.classes_for(dashboard.class.width)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def page_type = :dashboard
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -7,7 +7,7 @@ module Plutonium
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def page_title
|
|
10
|
-
current_definition.edit_page_title || super || "
|
|
10
|
+
current_definition.edit_page_title || super || t("plutonium.ui.page.edit_title", resource: resource_name(resource_class, 1))
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def page_description
|
|
@@ -25,8 +25,8 @@ module Plutonium
|
|
|
25
25
|
def render_modal_form
|
|
26
26
|
modal_class = Plutonium::UI::Modal::Base.class_for_mode(current_definition.modal_mode)
|
|
27
27
|
render modal_class.new(
|
|
28
|
-
title:
|
|
29
|
-
description:
|
|
28
|
+
title: resolved_page_title,
|
|
29
|
+
description: resolved_page_description,
|
|
30
30
|
size: current_definition.modal_size,
|
|
31
31
|
# Opens this form standalone in a NEW TAB (target=_blank), so the
|
|
32
32
|
# modal — and anything already typed into it — stays put.
|
|
@@ -23,8 +23,8 @@ module Plutonium
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
render modal_class.new(
|
|
26
|
-
title:
|
|
27
|
-
description:
|
|
26
|
+
title: resolved_page_title,
|
|
27
|
+
description: resolved_page_description,
|
|
28
28
|
size: current_interactive_action.modal_size(current_definition),
|
|
29
29
|
# The action's own GET path renders the standalone branch below,
|
|
30
30
|
# and opens in a NEW TAB, so the modal keeps whatever has already
|
|
@@ -7,7 +7,7 @@ module Plutonium
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def page_title
|
|
10
|
-
current_definition.new_page_title || super || "
|
|
10
|
+
current_definition.new_page_title || super || t("plutonium.ui.page.new_title", resource: resource_name(resource_class, 1))
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def page_description
|
|
@@ -25,8 +25,8 @@ module Plutonium
|
|
|
25
25
|
def render_modal_form
|
|
26
26
|
modal_class = Plutonium::UI::Modal::Base.class_for_mode(current_definition.modal_mode)
|
|
27
27
|
render modal_class.new(
|
|
28
|
-
title:
|
|
29
|
-
description:
|
|
28
|
+
title: resolved_page_title,
|
|
29
|
+
description: resolved_page_description,
|
|
30
30
|
size: current_definition.modal_size,
|
|
31
31
|
# Opens this form standalone in a NEW TAB (target=_blank), so the
|
|
32
32
|
# modal — and anything already typed into it — stays put.
|