plutonium 0.42.0 → 0.43.1
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-controller/SKILL.md +38 -1
- data/.claude/skills/plutonium-definition/SKILL.md +14 -0
- data/.claude/skills/plutonium-forms/SKILL.md +16 -1
- data/.claude/skills/plutonium-profile/SKILL.md +276 -0
- data/.claude/skills/plutonium-views/SKILL.md +23 -1
- data/CHANGELOG.md +42 -0
- data/app/assets/plutonium.css +2 -2
- data/app/views/plutonium/_resource_header.html.erb +6 -27
- data/app/views/plutonium/_resource_sidebar.html.erb +1 -2
- data/app/views/resource/_resource_details.rabl +3 -2
- data/app/views/resource/index.rabl +3 -2
- data/app/views/resource/show.rabl +3 -2
- data/docs/guides/user-profile.md +322 -0
- data/docs/reference/controller/index.md +38 -1
- data/docs/reference/definition/index.md +16 -0
- data/docs/reference/views/forms.md +15 -0
- data/docs/reference/views/index.md +23 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +12 -0
- data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +11 -0
- data/lib/generators/pu/core/typespec/templates/common.tsp.tt +95 -0
- data/lib/generators/pu/core/typespec/templates/main.tsp.tt +27 -0
- data/lib/generators/pu/core/typespec/templates/main_multi.tsp.tt +25 -0
- data/lib/generators/pu/core/typespec/templates/model.tsp.tt +226 -0
- data/lib/generators/pu/core/typespec/typespec_generator.rb +342 -0
- data/lib/generators/pu/invites/USAGE +0 -1
- data/lib/generators/pu/invites/install_generator.rb +62 -15
- data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +2 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +1 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +5 -5
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +4 -4
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +29 -0
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +6 -23
- data/lib/generators/pu/pkg/portal/portal_generator.rb +5 -1
- data/lib/generators/pu/profile/USAGE +59 -0
- data/lib/generators/pu/profile/concerns/profile_arguments.rb +27 -0
- data/lib/generators/pu/profile/conn/USAGE +33 -0
- data/lib/generators/pu/profile/conn_generator.rb +167 -0
- data/lib/generators/pu/profile/install_generator.rb +119 -0
- data/lib/generators/pu/profile/setup/USAGE +42 -0
- data/lib/generators/pu/profile/setup_generator.rb +73 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -4
- data/lib/generators/pu/rodauth/install_generator.rb +2 -2
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
- data/lib/generators/pu/saas/api_client_generator.rb +0 -2
- data/lib/generators/pu/saas/membership_generator.rb +68 -19
- data/lib/generators/pu/saas/setup_generator.rb +7 -2
- data/lib/generators/pu/saas/user_generator.rb +0 -2
- data/lib/plutonium/auth/rodauth.rb +8 -0
- data/lib/plutonium/core/controller.rb +7 -4
- data/lib/plutonium/core/controllers/authorizable.rb +5 -1
- data/lib/plutonium/definition/base.rb +7 -0
- data/lib/plutonium/helpers/display_helper.rb +6 -0
- data/lib/plutonium/profile/security_section.rb +118 -0
- data/lib/plutonium/resource/controller.rb +17 -7
- data/lib/plutonium/resource/controllers/interactive_actions.rb +11 -25
- data/lib/plutonium/resource/controllers/presentable.rb +46 -3
- data/lib/plutonium/resource/record/associated_with.rb +7 -1
- data/lib/plutonium/routing/mapper_extensions.rb +18 -18
- data/lib/plutonium/routing/route_set_extensions.rb +23 -2
- data/lib/plutonium/ui/breadcrumbs.rb +111 -131
- data/lib/plutonium/ui/dyna_frame/content.rb +12 -2
- data/lib/plutonium/ui/form/resource.rb +26 -19
- data/lib/plutonium/ui/page/base.rb +14 -14
- data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -74
- data/lib/plutonium/ui/table/components/selection_column.rb +6 -2
- data/lib/plutonium/ui/table/resource.rb +3 -2
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +6 -6
- data/package.json +1 -1
- metadata +17 -3
- data/lib/generators/pu/rodauth/concerns/gem_helpers.rb +0 -19
|
@@ -44,16 +44,28 @@ module Plutonium
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Draws routes wrapped in entity scope when using path-based entity scoping.
|
|
48
|
+
#
|
|
49
|
+
# @param scope_params [Hash, nil] Scope params from RouteSetExtensions, or nil if no scoping
|
|
50
|
+
# @param block [Proc] The block containing route definitions.
|
|
51
|
+
# @return [void]
|
|
52
|
+
def draw_routes_with_entity_scope(scope_params, &block)
|
|
53
|
+
if scope_params
|
|
54
|
+
scope scope_params[:name], **scope_params[:options] do
|
|
55
|
+
instance_exec(&block)
|
|
56
|
+
materialize_resource_routes
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
instance_exec(&block)
|
|
60
|
+
materialize_resource_routes
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
47
64
|
# Materializes all registered resource routes.
|
|
48
65
|
#
|
|
49
66
|
# @return [void]
|
|
50
67
|
def materialize_resource_routes
|
|
51
|
-
|
|
52
|
-
scope_params = determine_scope_params(engine)
|
|
53
|
-
|
|
54
|
-
scope scope_params[:name], scope_params[:options] do
|
|
55
|
-
concerns resource_route_concern_names.sort
|
|
56
|
-
end
|
|
68
|
+
concerns resource_route_concern_names.sort
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
# @return [ActionDispatch::Routing::RouteSet] The current route set.
|
|
@@ -149,18 +161,6 @@ module Plutonium
|
|
|
149
161
|
as: :commit_interactive_resource_action
|
|
150
162
|
end
|
|
151
163
|
end
|
|
152
|
-
|
|
153
|
-
# Determines the scope parameters based on the engine configuration.
|
|
154
|
-
#
|
|
155
|
-
# @param engine [Class] The current engine.
|
|
156
|
-
# @return [Hash] Scope name and options.
|
|
157
|
-
def determine_scope_params(engine)
|
|
158
|
-
scoped_entity_param_key = engine.scoped_entity_param_key if engine.scoped_entity_strategy == :path
|
|
159
|
-
{
|
|
160
|
-
name: scoped_entity_param_key.present? ? ":#{scoped_entity_param_key}" : "",
|
|
161
|
-
options: scoped_entity_param_key.present? ? {as: scoped_entity_param_key} : {}
|
|
162
|
-
}
|
|
163
|
-
end
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
end
|
|
@@ -34,11 +34,11 @@ module Plutonium
|
|
|
34
34
|
# @yield Executes the given block in the context of route drawing.
|
|
35
35
|
def draw(&block)
|
|
36
36
|
if self.class.supported_engine?(engine)
|
|
37
|
+
scope_params = entity_scope_params_for_path_strategy
|
|
37
38
|
ActiveSupport::Notifications.instrument("plutonium.resource_routes.draw", app: engine.to_s) do
|
|
38
39
|
super do
|
|
39
40
|
setup_shared_resource_concerns
|
|
40
|
-
|
|
41
|
-
materialize_resource_routes
|
|
41
|
+
draw_routes_with_entity_scope(scope_params, &block)
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
else
|
|
@@ -46,6 +46,19 @@ module Plutonium
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Determines entity scope parameters for path-based scoping.
|
|
50
|
+
#
|
|
51
|
+
# @return [Hash, nil] Scope params if path-based scoping is enabled, nil otherwise
|
|
52
|
+
def entity_scope_params_for_path_strategy
|
|
53
|
+
return nil unless engine.scoped_entity_strategy == :path
|
|
54
|
+
|
|
55
|
+
param_key = engine.scoped_entity_param_key
|
|
56
|
+
{
|
|
57
|
+
name: ":#{param_key}",
|
|
58
|
+
options: {as: param_key}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
# Registers a resource for routing.
|
|
50
63
|
#
|
|
51
64
|
# @param resource [Class] The resource class to be registered.
|
|
@@ -75,6 +88,14 @@ module Plutonium
|
|
|
75
88
|
resource_route_config_lookup.slice(*routes).values
|
|
76
89
|
end
|
|
77
90
|
|
|
91
|
+
# Checks if a resource is registered as a singular route.
|
|
92
|
+
#
|
|
93
|
+
# @param route_key [String] The route key (e.g., "users" or "users/profiles")
|
|
94
|
+
# @return [Boolean] true if the resource is a singular route, false otherwise
|
|
95
|
+
def singular_resource_route?(route_key)
|
|
96
|
+
resource_route_config_for(route_key)[0]&.[](:route_type) == :resource
|
|
97
|
+
end
|
|
98
|
+
|
|
78
99
|
# Returns the current engine for the routes.
|
|
79
100
|
#
|
|
80
101
|
# @return [Class] The engine class (Rails application or custom engine).
|
|
@@ -12,147 +12,127 @@ module Plutonium
|
|
|
12
12
|
ol(
|
|
13
13
|
class: "inline-flex items-center gap-1 md:gap-2"
|
|
14
14
|
) do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class: "w-3 h-3 me-2.5",
|
|
23
|
-
aria_hidden: "true",
|
|
24
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
25
|
-
fill: "currentColor",
|
|
26
|
-
viewbox: "0 0 20 20"
|
|
27
|
-
) do |s|
|
|
28
|
-
s.path(
|
|
29
|
-
d:
|
|
30
|
-
"m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
plain " Dashboard "
|
|
34
|
-
end
|
|
35
|
-
end
|
|
15
|
+
render_dashboard_link
|
|
16
|
+
render_parent_breadcrumbs if current_parent.present?
|
|
17
|
+
render_resource_breadcrumbs if resource_record?
|
|
18
|
+
render_trailing_separator
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
if current_parent.present?
|
|
39
|
-
# Parent Resource
|
|
40
|
-
li(class: "flex items-center") do
|
|
41
|
-
svg(
|
|
42
|
-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
|
|
43
|
-
aria_hidden: "true",
|
|
44
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
45
|
-
fill: "none",
|
|
46
|
-
viewbox: "0 0 6 10"
|
|
47
|
-
) do |s|
|
|
48
|
-
s.path(
|
|
49
|
-
stroke: "currentColor",
|
|
50
|
-
stroke_linecap: "round",
|
|
51
|
-
stroke_linejoin: "round",
|
|
52
|
-
stroke_width: "2",
|
|
53
|
-
d: "m1 9 4-4-4-4"
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
link_to resource_name_plural(current_parent.class),
|
|
57
|
-
resource_url_for(current_parent.class, parent: nil),
|
|
58
|
-
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
59
|
-
end
|
|
23
|
+
private
|
|
60
24
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
link_to display_name_of(current_parent),
|
|
79
|
-
resource_url_for(current_parent, parent: nil),
|
|
80
|
-
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
81
|
-
end
|
|
25
|
+
def render_dashboard_link
|
|
26
|
+
li(class: "inline-flex items-center") do
|
|
27
|
+
a(
|
|
28
|
+
href: root_path,
|
|
29
|
+
class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
|
|
30
|
+
) do
|
|
31
|
+
svg(
|
|
32
|
+
class: "w-3 h-3 me-2.5",
|
|
33
|
+
aria_hidden: "true",
|
|
34
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
35
|
+
fill: "currentColor",
|
|
36
|
+
viewbox: "0 0 20 20"
|
|
37
|
+
) do |s|
|
|
38
|
+
s.path(
|
|
39
|
+
d:
|
|
40
|
+
"m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
|
|
41
|
+
)
|
|
82
42
|
end
|
|
43
|
+
plain " Dashboard "
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
aria_hidden: "true",
|
|
92
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
93
|
-
fill: "none",
|
|
94
|
-
viewbox: "0 0 6 10"
|
|
95
|
-
) do |s|
|
|
96
|
-
s.path(
|
|
97
|
-
stroke: "currentColor",
|
|
98
|
-
stroke_linecap: "round",
|
|
99
|
-
stroke_linejoin: "round",
|
|
100
|
-
stroke_width: "2",
|
|
101
|
-
d: "m1 9 4-4-4-4"
|
|
102
|
-
)
|
|
103
|
-
end
|
|
104
|
-
link_to nestable_resource_name_plural(resource_class),
|
|
105
|
-
resource_url_for(resource_class),
|
|
106
|
-
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
107
|
-
end
|
|
108
|
-
end
|
|
48
|
+
def render_parent_breadcrumbs
|
|
49
|
+
# Parent Resource
|
|
50
|
+
render_breadcrumb_item do
|
|
51
|
+
link_to resource_name_plural(current_parent.class),
|
|
52
|
+
resource_url_for(current_parent.class, parent: nil),
|
|
53
|
+
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
54
|
+
end
|
|
109
55
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
128
|
-
link_to display_name_of(resource_record!),
|
|
129
|
-
resource_url_for(resource_record!),
|
|
130
|
-
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
56
|
+
# Parent Itself
|
|
57
|
+
render_breadcrumb_item do
|
|
58
|
+
link_to display_name_of(current_parent),
|
|
59
|
+
resource_url_for(current_parent, parent: nil),
|
|
60
|
+
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_resource_breadcrumbs
|
|
65
|
+
is_singular_route = current_engine.routes.resource_route_config_lookup[resource_class.model_name.plural][:route_type] == :resource
|
|
66
|
+
|
|
67
|
+
if is_singular_route
|
|
68
|
+
render_singular_resource_breadcrumb
|
|
69
|
+
else
|
|
70
|
+
render_plural_resource_breadcrumbs
|
|
71
|
+
end
|
|
72
|
+
end
|
|
134
73
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
s.path(
|
|
145
|
-
stroke: "currentColor",
|
|
146
|
-
stroke_linecap: "round",
|
|
147
|
-
stroke_linejoin: "round",
|
|
148
|
-
stroke_width: "2",
|
|
149
|
-
d: "m1 9 4-4-4-4"
|
|
150
|
-
)
|
|
151
|
-
end
|
|
74
|
+
def render_singular_resource_breadcrumb
|
|
75
|
+
render_breadcrumb_item do
|
|
76
|
+
if resource_record!.persisted? && action_name != "show"
|
|
77
|
+
link_to resource_name(resource_class),
|
|
78
|
+
resource_url_for(resource_record!),
|
|
79
|
+
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
80
|
+
else
|
|
81
|
+
span(class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] md:ms-2") do
|
|
82
|
+
plain resource_name(resource_class)
|
|
152
83
|
end
|
|
153
84
|
end
|
|
154
85
|
end
|
|
155
86
|
end
|
|
87
|
+
|
|
88
|
+
def render_plural_resource_breadcrumbs
|
|
89
|
+
# Resource index link
|
|
90
|
+
render_breadcrumb_item do
|
|
91
|
+
link_to nestable_resource_name_plural(resource_class),
|
|
92
|
+
resource_url_for(resource_class),
|
|
93
|
+
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Record itself (for non-singular routes only)
|
|
97
|
+
return unless resource_record!.persisted? && action_name != "show"
|
|
98
|
+
|
|
99
|
+
render_breadcrumb_item do
|
|
100
|
+
link_to display_name_of(resource_record!),
|
|
101
|
+
resource_url_for(resource_record!),
|
|
102
|
+
class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def render_trailing_separator
|
|
107
|
+
li(class: "flex items-center") do
|
|
108
|
+
render_chevron_separator
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def render_breadcrumb_item(&)
|
|
113
|
+
li(class: "flex items-center") do
|
|
114
|
+
render_chevron_separator
|
|
115
|
+
yield
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def render_chevron_separator
|
|
120
|
+
svg(
|
|
121
|
+
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
|
|
122
|
+
aria_hidden: "true",
|
|
123
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
124
|
+
fill: "none",
|
|
125
|
+
viewbox: "0 0 6 10"
|
|
126
|
+
) do |s|
|
|
127
|
+
s.path(
|
|
128
|
+
stroke: "currentColor",
|
|
129
|
+
stroke_linecap: "round",
|
|
130
|
+
stroke_linejoin: "round",
|
|
131
|
+
stroke_width: "2",
|
|
132
|
+
d: "m1 9 4-4-4-4"
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
156
136
|
end
|
|
157
137
|
end
|
|
158
138
|
end
|
|
@@ -4,16 +4,26 @@ module Plutonium
|
|
|
4
4
|
class Content < Plutonium::UI::Component::Base
|
|
5
5
|
include Phlex::Rails::Helpers::TurboFrameTag
|
|
6
6
|
|
|
7
|
+
def initialize(content = nil)
|
|
8
|
+
@content = content
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
def view_template
|
|
8
12
|
if current_turbo_frame.present?
|
|
13
|
+
# Frame request: render only the turbo-frame with content
|
|
9
14
|
turbo_frame_tag(current_turbo_frame) do
|
|
10
15
|
render partial("flash")
|
|
11
|
-
|
|
16
|
+
@content&.call
|
|
12
17
|
end
|
|
13
18
|
else
|
|
14
|
-
yield
|
|
19
|
+
# Regular request: yield self so caller can call frame.render_content
|
|
20
|
+
yield(self)
|
|
15
21
|
end
|
|
16
22
|
end
|
|
23
|
+
|
|
24
|
+
def render_content
|
|
25
|
+
@content&.call
|
|
26
|
+
end
|
|
17
27
|
end
|
|
18
28
|
end
|
|
19
29
|
end
|
|
@@ -6,14 +6,15 @@ module Plutonium
|
|
|
6
6
|
class Resource < Base
|
|
7
7
|
include Plutonium::UI::Form::Concerns::RendersNestedResourceFields
|
|
8
8
|
|
|
9
|
-
attr_reader :resource_fields, :resource_definition
|
|
9
|
+
attr_reader :resource_fields, :resource_definition, :singular_resource
|
|
10
10
|
|
|
11
11
|
alias_method :record, :object
|
|
12
12
|
|
|
13
|
-
def initialize(*, resource_fields:, resource_definition:, **, &)
|
|
13
|
+
def initialize(*, resource_fields:, resource_definition:, singular_resource: false, **, &)
|
|
14
14
|
super(*, **, &)
|
|
15
15
|
@resource_fields = resource_fields
|
|
16
16
|
@resource_definition = resource_definition
|
|
17
|
+
@singular_resource = singular_resource
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def form_template
|
|
@@ -35,28 +36,34 @@ module Plutonium
|
|
|
35
36
|
input name: "return_to", value: request.params[:return_to], type: :hidden, hidden: true
|
|
36
37
|
|
|
37
38
|
actions_wrapper {
|
|
38
|
-
if
|
|
39
|
-
if object.new_record?
|
|
40
|
-
button(
|
|
41
|
-
type: :submit,
|
|
42
|
-
name: "return_to",
|
|
43
|
-
value: request.url,
|
|
44
|
-
class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
|
|
45
|
-
) { "Create and add another" }
|
|
46
|
-
else
|
|
47
|
-
button(
|
|
48
|
-
type: :submit,
|
|
49
|
-
name: "return_to",
|
|
50
|
-
value: request.url,
|
|
51
|
-
class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
|
|
52
|
-
) { "Update and continue editing" }
|
|
53
|
-
end
|
|
54
|
-
end
|
|
39
|
+
render_submit_and_continue_button if show_submit_and_continue?
|
|
55
40
|
|
|
56
41
|
render submit_button
|
|
57
42
|
}
|
|
58
43
|
end
|
|
59
44
|
|
|
45
|
+
def show_submit_and_continue?
|
|
46
|
+
return false unless object.respond_to?(:new_record?)
|
|
47
|
+
|
|
48
|
+
# Check explicit configuration first
|
|
49
|
+
configured = resource_definition.submit_and_continue
|
|
50
|
+
return configured unless configured.nil?
|
|
51
|
+
|
|
52
|
+
# Auto-detect: hide for singular resources
|
|
53
|
+
!singular_resource
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def render_submit_and_continue_button
|
|
57
|
+
label = object.new_record? ? "Create and add another" : "Update and continue editing"
|
|
58
|
+
|
|
59
|
+
button(
|
|
60
|
+
type: :submit,
|
|
61
|
+
name: "return_to",
|
|
62
|
+
value: request.url,
|
|
63
|
+
class: "px-4 py-2 bg-secondary-600 text-white rounded-md hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-secondary-500"
|
|
64
|
+
) { label }
|
|
65
|
+
end
|
|
66
|
+
|
|
60
67
|
def form_action
|
|
61
68
|
return @form_action unless object.present? && @form_action != false && view_context.present?
|
|
62
69
|
|
|
@@ -10,18 +10,20 @@ module Plutonium
|
|
|
10
10
|
@page_actions = page_actions
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def view_template(&)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
def view_template(&block)
|
|
14
|
+
DynaFrameContent(page_content(block)) do |frame|
|
|
15
|
+
render_before_header
|
|
16
|
+
render_header
|
|
17
|
+
render_after_header
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
render_before_content
|
|
20
|
+
frame.render_content
|
|
21
|
+
render_after_content
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
render_before_footer
|
|
24
|
+
render_footer
|
|
25
|
+
render_after_footer
|
|
26
|
+
end
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
@@ -64,10 +66,8 @@ module Plutonium
|
|
|
64
66
|
# Implement toolbar content
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
def
|
|
68
|
-
block
|
|
69
|
-
|
|
70
|
-
DynaFrameContent(&block)
|
|
69
|
+
def page_content(block)
|
|
70
|
+
block || proc { render_default_content }
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def render_default_content
|
|
@@ -9,9 +9,7 @@ module Plutonium
|
|
|
9
9
|
|
|
10
10
|
def view_template
|
|
11
11
|
div(
|
|
12
|
-
class:
|
|
13
|
-
# "flex flex-wrap justify-between items-center gap-4 p-4 bg-white border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 mb-4"
|
|
14
|
-
"flex flex-wrap justify-between items-center gap-4 mb-4"
|
|
12
|
+
class: "flex flex-wrap justify-between items-center gap-4 mb-4"
|
|
15
13
|
) do
|
|
16
14
|
div(class: "flex flex-wrap items-center gap-2") do
|
|
17
15
|
render_all_scope_button
|
|
@@ -19,76 +17,6 @@ module Plutonium
|
|
|
19
17
|
render_scope_button(name)
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
|
-
|
|
23
|
-
# div(class: "flex flex-wrap items-center gap-2") do
|
|
24
|
-
# button(
|
|
25
|
-
# class:
|
|
26
|
-
# "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
|
|
27
|
-
# ) do
|
|
28
|
-
# svg(
|
|
29
|
-
# class: "w-4 h-4 mr-2",
|
|
30
|
-
# fill: "none",
|
|
31
|
-
# stroke: "currentColor",
|
|
32
|
-
# viewbox: "0 0 24 24",
|
|
33
|
-
# xmlns: "http://www.w3.org/2000/svg"
|
|
34
|
-
# ) do |s|
|
|
35
|
-
# s.path(
|
|
36
|
-
# stroke_linecap: "round",
|
|
37
|
-
# stroke_linejoin: "round",
|
|
38
|
-
# stroke_width: "2",
|
|
39
|
-
# d:
|
|
40
|
-
# "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
41
|
-
# )
|
|
42
|
-
# end
|
|
43
|
-
# plain " Delete Selected "
|
|
44
|
-
# end
|
|
45
|
-
# button(
|
|
46
|
-
# class:
|
|
47
|
-
# "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-yellow-700 rounded-lg hover:bg-yellow-800 focus:ring-4 focus:outline-none focus:ring-yellow-300 dark:bg-yellow-600 dark:hover:bg-yellow-700 dark:focus:ring-yellow-800"
|
|
48
|
-
# ) do
|
|
49
|
-
# svg(
|
|
50
|
-
# class: "w-4 h-4 mr-2",
|
|
51
|
-
# fill: "none",
|
|
52
|
-
# stroke: "currentColor",
|
|
53
|
-
# viewbox: "0 0 24 24",
|
|
54
|
-
# xmlns: "http://www.w3.org/2000/svg"
|
|
55
|
-
# ) do |s|
|
|
56
|
-
# s.path(
|
|
57
|
-
# stroke_linecap: "round",
|
|
58
|
-
# stroke_linejoin: "round",
|
|
59
|
-
# stroke_width: "2",
|
|
60
|
-
# d:
|
|
61
|
-
# "M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
|
62
|
-
# )
|
|
63
|
-
# end
|
|
64
|
-
# plain " Archive Selected "
|
|
65
|
-
# end
|
|
66
|
-
# button(
|
|
67
|
-
# id: "dropdownActionButton",
|
|
68
|
-
# data_dropdown_toggle: "dropdownAction",
|
|
69
|
-
# class:
|
|
70
|
-
# "inline-flex items-center text-gray-500 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-3 py-2 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700",
|
|
71
|
-
# type: "button"
|
|
72
|
-
# ) do
|
|
73
|
-
# span(class: "sr-only") { "Action button" }
|
|
74
|
-
# plain " More Actions "
|
|
75
|
-
# svg(
|
|
76
|
-
# class: "w-2.5 h-2.5 ml-2.5",
|
|
77
|
-
# aria_hidden: "true",
|
|
78
|
-
# xmlns: "http://www.w3.org/2000/svg",
|
|
79
|
-
# fill: "none",
|
|
80
|
-
# viewbox: "0 0 10 6"
|
|
81
|
-
# ) do |s|
|
|
82
|
-
# s.path(
|
|
83
|
-
# stroke: "currentColor",
|
|
84
|
-
# stroke_linecap: "round",
|
|
85
|
-
# stroke_linejoin: "round",
|
|
86
|
-
# stroke_width: "2",
|
|
87
|
-
# d: "m1 1 4 4 4-4"
|
|
88
|
-
# )
|
|
89
|
-
# end
|
|
90
|
-
# end
|
|
91
|
-
# end
|
|
92
20
|
end
|
|
93
21
|
end
|
|
94
22
|
|
|
@@ -114,7 +42,7 @@ module Plutonium
|
|
|
114
42
|
|
|
115
43
|
def current_scope
|
|
116
44
|
# Use the effective scope (includes default when no selection)
|
|
117
|
-
current_query_object.selected_scope
|
|
45
|
+
current_query_object.selected_scope&.to_s
|
|
118
46
|
end
|
|
119
47
|
|
|
120
48
|
def all_scope_active?
|
|
@@ -12,7 +12,7 @@ module Plutonium
|
|
|
12
12
|
|
|
13
13
|
def data_cell(wrapped_object)
|
|
14
14
|
allowed_actions = compute_allowed_actions(wrapped_object.unwrapped)
|
|
15
|
-
SelectionDataCell.new(wrapped_object.field(
|
|
15
|
+
SelectionDataCell.new(wrapped_object.field(value_key).dom.value, allowed_actions)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Add hidden class and Stimulus target to header cell
|
|
@@ -34,6 +34,10 @@ module Plutonium
|
|
|
34
34
|
|
|
35
35
|
private
|
|
36
36
|
|
|
37
|
+
def value_key
|
|
38
|
+
options[:value_key] || sample.class.primary_key.to_sym
|
|
39
|
+
end
|
|
40
|
+
|
|
37
41
|
def bulk_actions
|
|
38
42
|
options[:bulk_actions] || []
|
|
39
43
|
end
|
|
@@ -43,7 +47,7 @@ module Plutonium
|
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def compute_allowed_actions(record)
|
|
46
|
-
return
|
|
50
|
+
return bulk_actions.map { |a| a.name.to_s } unless policy_resolver
|
|
47
51
|
|
|
48
52
|
policy = policy_resolver.call(record)
|
|
49
53
|
bulk_actions.select { |action|
|
|
@@ -49,8 +49,9 @@ module Plutonium
|
|
|
49
49
|
|
|
50
50
|
render Plutonium::UI::Table::Base.new(collection) do |table|
|
|
51
51
|
# Selection column for bulk actions (hidden by default, Stimulus shows it)
|
|
52
|
-
#
|
|
53
|
-
|
|
52
|
+
# Use :_selection as column key to avoid conflicts with field columns
|
|
53
|
+
# value_key defaults to model's primary_key
|
|
54
|
+
table.selection_column :_selection,
|
|
54
55
|
bulk_actions:,
|
|
55
56
|
policy_resolver: ->(record) { policy_for(record:) }
|
|
56
57
|
|
data/lib/plutonium/version.rb
CHANGED