pakyow-presenter 1.0.0.rc2 → 1.0.0.rc3

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pakyow/{presenter/actions → actions/presenter}/auto_render.rb +2 -2
  3. data/lib/pakyow/plugin/helpers/rendering.rb +15 -2
  4. data/lib/pakyow/presenter.rb +1 -1
  5. data/lib/pakyow/presenter/attributes.rb +8 -0
  6. data/lib/pakyow/presenter/attributes/attribute.rb +0 -1
  7. data/lib/pakyow/presenter/attributes/boolean.rb +0 -1
  8. data/lib/pakyow/presenter/behavior/error_rendering.rb +1 -0
  9. data/lib/pakyow/presenter/behavior/initializing.rb +1 -1
  10. data/lib/pakyow/presenter/behavior/modes.rb +1 -0
  11. data/lib/pakyow/presenter/binder.rb +2 -0
  12. data/lib/pakyow/presenter/binding_parts.rb +1 -0
  13. data/lib/pakyow/presenter/component.rb +1 -4
  14. data/lib/pakyow/presenter/composers/component.rb +1 -0
  15. data/lib/pakyow/presenter/composers/view.rb +1 -0
  16. data/lib/pakyow/presenter/errors.rb +2 -2
  17. data/lib/pakyow/presenter/framework.rb +22 -25
  18. data/lib/pakyow/presenter/presenter.rb +5 -0
  19. data/lib/pakyow/presenter/presenters/endpoint.rb +3 -3
  20. data/lib/pakyow/presenter/presenters/form.rb +5 -5
  21. data/lib/pakyow/presenter/processor.rb +42 -38
  22. data/lib/pakyow/presenter/renderer.rb +6 -1
  23. data/lib/pakyow/presenter/renderer/behavior/cleanup_prototype_nodes.rb +23 -0
  24. data/lib/pakyow/presenter/renderer/behavior/cleanup_unbound_bindings.rb +37 -0
  25. data/lib/pakyow/presenter/renderer/behavior/create_template_nodes.rb +29 -0
  26. data/lib/pakyow/presenter/renderer/behavior/insert_prototype_bar.rb +103 -0
  27. data/lib/pakyow/presenter/renderer/behavior/install_authenticity.rb +44 -0
  28. data/lib/pakyow/presenter/renderer/behavior/place_in_mode.rb +58 -0
  29. data/lib/pakyow/presenter/renderer/behavior/render_components.rb +281 -0
  30. data/lib/pakyow/presenter/renderer/behavior/set_page_title.rb +37 -0
  31. data/lib/pakyow/presenter/renderer/behavior/setup_endpoints.rb +64 -0
  32. data/lib/pakyow/presenter/renderer/behavior/setup_forms.rb +176 -0
  33. data/lib/pakyow/presenter/significant_nodes.rb +2 -2
  34. data/lib/pakyow/presenter/templates.rb +24 -15
  35. data/lib/pakyow/presenter/versioned_view.rb +1 -0
  36. data/lib/pakyow/presenter/view.rb +11 -9
  37. data/lib/pakyow/presenter/views/form.rb +39 -35
  38. data/lib/pakyow/presenter/views/layout.rb +20 -18
  39. data/lib/pakyow/presenter/views/page.rb +47 -45
  40. data/lib/pakyow/presenter/views/partial.rb +17 -15
  41. data/lib/string_doc.rb +3 -1
  42. data/lib/string_doc/attributes.rb +1 -0
  43. data/lib/string_doc/meta_attributes.rb +1 -0
  44. data/lib/string_doc/meta_node.rb +1 -0
  45. data/lib/string_doc/node.rb +1 -0
  46. metadata +19 -20
  47. data/lib/pakyow/presenter/presentable_error.rb +0 -19
  48. data/lib/pakyow/presenter/rendering/actions/cleanup_prototype_nodes.rb +0 -21
  49. data/lib/pakyow/presenter/rendering/actions/cleanup_unbound_bindings.rb +0 -35
  50. data/lib/pakyow/presenter/rendering/actions/create_template_nodes.rb +0 -27
  51. data/lib/pakyow/presenter/rendering/actions/insert_prototype_bar.rb +0 -101
  52. data/lib/pakyow/presenter/rendering/actions/install_authenticity.rb +0 -42
  53. data/lib/pakyow/presenter/rendering/actions/place_in_mode.rb +0 -56
  54. data/lib/pakyow/presenter/rendering/actions/render_components.rb +0 -279
  55. data/lib/pakyow/presenter/rendering/actions/set_page_title.rb +0 -35
  56. data/lib/pakyow/presenter/rendering/actions/setup_endpoints.rb +0 -62
  57. data/lib/pakyow/presenter/rendering/actions/setup_forms.rb +0 -174
@@ -7,7 +7,7 @@ require "pakyow/support/hookable"
7
7
  require "pakyow/support/core_refinements/proc/introspection"
8
8
  require "pakyow/support/core_refinements/string/normalization"
9
9
 
10
- require "pakyow/presenter/rendering/actions/render_components"
10
+ require "pakyow/presenter/renderer/behavior/render_components"
11
11
 
12
12
  require "pakyow/presenter/composers/view"
13
13
 
@@ -155,6 +155,7 @@ module Pakyow
155
155
  connection.rendered
156
156
  end
157
157
 
158
+ # @api private
158
159
  def render_implicitly(connection)
159
160
  view_path = connection.get(:__endpoint_path) || connection.path
160
161
 
@@ -179,6 +180,7 @@ module Pakyow
179
180
  end
180
181
  end
181
182
 
183
+ # @api private
182
184
  def build!(view, app:, modes:, composer:)
183
185
  @__build_fns.each do |fn|
184
186
  options = {}
@@ -199,6 +201,7 @@ module Pakyow
199
201
  end
200
202
  end
201
203
 
204
+ # @api private
202
205
  def attach!(presenter, app:)
203
206
  @__attach_fns.each do |fn|
204
207
  options = {}
@@ -211,12 +214,14 @@ module Pakyow
211
214
  end
212
215
  end
213
216
 
217
+ # @api private
214
218
  def expose!(connection)
215
219
  @__expose_fns.each do |fn|
216
220
  fn.call(connection)
217
221
  end
218
222
  end
219
223
 
224
+ # @api private
220
225
  def find_presenter(app, path)
221
226
  path = String.normalize_path(path)
222
227
  unless presenter = @__presenters_by_path[path]
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module CleanupPrototypeNodes
10
+ extend Support::Extension
11
+
12
+ apply_extension do
13
+ build do |view|
14
+ unless Pakyow.env?(:prototype)
15
+ view.object.each_significant_node(:prototype, descend: true).map(&:itself).each(&:remove)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module CleanupUnboundBindings
10
+ extend Support::Extension
11
+
12
+ apply_extension do
13
+ attach do |presenter|
14
+ unless Pakyow.env?(:prototype)
15
+ # Cleanup unbound bindings. We don't do this in prototype mode because it's important
16
+ # for the prototype to be complete, showing everything to the designer.
17
+ #
18
+ presenter.render node: -> {
19
+ object.find_significant_nodes(:binding, descend: true).map { |node|
20
+ View.from_object(node)
21
+ }
22
+ }, priority: :low do
23
+ # We check that the node is still labeled as a binding in case the node was replaced
24
+ # during a previous transformation with a node that isn't a binding.
25
+ #
26
+ unless !view.object.labeled?(:binding) || view.object.labeled?(:bound) || view.object.labeled?(:failed) || view.object.label(:version) == :empty
27
+ remove
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module CreateTemplateNodes
10
+ extend Support::Extension
11
+
12
+ apply_extension do
13
+ build do |view|
14
+ unless Pakyow.env?(:prototype)
15
+ view.each_binding_scope(descend: true) do |node_with_binding|
16
+ attributes = node_with_binding.attributes.attributes_hash.each_with_object({}) do |(attribute, value), acc|
17
+ acc[attribute] = value if attribute.to_s.start_with?("data")
18
+ end
19
+
20
+ node_with_binding.after("<script type=\"text/template\"#{StringDoc::Attributes.new(attributes).to_s}>#{node_with_binding.render}</script>")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module InsertPrototypeBar
10
+ extend Support::Extension
11
+
12
+ apply_extension do
13
+ attach do |presenter|
14
+ if Pakyow.env?(:prototype)
15
+ presenter.render node: -> {
16
+ if body = object.find_first_significant_node(:body)
17
+ View.from_object(body)
18
+ end
19
+ } do
20
+ view.object.append_html <<~HTML
21
+ <style>
22
+ .pw-prototype {
23
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
24
+ display:flex;
25
+ align-items: center;
26
+ position: fixed;
27
+ z-index: 1000;
28
+ right: 0;
29
+ bottom: 0;
30
+ background: #156eed;
31
+ color: #fff;
32
+ font-size: 11px;
33
+ line-height: 11px;
34
+ font-weight: 500;
35
+ border-top-left-radius: 1px;
36
+ padding-left: 5px;
37
+ }
38
+
39
+ .pw-prototype-tag {
40
+ background: #ff8b6c;
41
+ color: #fff;
42
+ text-transform: uppercase;
43
+ font-size: 10px;
44
+ line-height: 12px;
45
+ font-weight: 600;
46
+ padding: 5px 5px 4px 5px;
47
+ margin-left: 10px;
48
+ }
49
+ </style>
50
+
51
+ <div class="pw-prototype">
52
+ #{InsertPrototypeBar.ui_modes_html(view, __modes || [:default])}
53
+
54
+ <div class="pw-prototype-tag">
55
+ Prototype
56
+ </div>
57
+ </div>
58
+ HTML
59
+ end
60
+ end
61
+ end
62
+
63
+ expose do |connection|
64
+ if Pakyow.env?(:prototype)
65
+ connection.set(:__modes, connection.params[:modes])
66
+ end
67
+ end
68
+ end
69
+
70
+ # @api private
71
+ def self.ui_modes_html(view, current_modes)
72
+ current_modes = current_modes.map(&:to_sym)
73
+
74
+ modes = view.object.each_significant_node(:mode).map { |node|
75
+ node.label(:mode)
76
+ }
77
+
78
+ modes.unshift(
79
+ (view.info(:mode) || :default).to_sym
80
+ ).uniq!
81
+
82
+ options = modes.map { |each_mode|
83
+ selected = if current_modes.include?(each_mode)
84
+ " selected=\"selected\""
85
+ else
86
+ ""
87
+ end
88
+
89
+ nice_mode = Support.inflector.humanize(Support.inflector.underscore(each_mode))
90
+ "<option value=\"#{each_mode}\"#{selected}>#{nice_mode}</option>"
91
+ }.join
92
+
93
+ <<~HTML
94
+ UI Mode: <select onchange="document.location = window.location.pathname + '?modes[]=' + this.value " style="-webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; -o-appearance: none; appearance: none; font-size: 11px; font-weight: 500; line-height: 20px; background: none; border: none; color: #fff; outline: none; margin: 0; margin-left: 5px;">
95
+ #{options}
96
+ </select>
97
+ HTML
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+ require "pakyow/support/message_verifier"
5
+ require "pakyow/support/safe_string"
6
+
7
+ module Pakyow
8
+ module Presenter
9
+ class Renderer
10
+ module Behavior
11
+ module InstallAuthenticity
12
+ extend Support::Extension
13
+
14
+ apply_extension do
15
+ build do |view, app:|
16
+ if app.config.presenter.embed_authenticity_token && head = view.head
17
+ head.append(Support::SafeStringHelpers.html_safe("<meta name=\"pw-authenticity-token\">"))
18
+ head.append(Support::SafeStringHelpers.html_safe("<meta name=\"pw-authenticity-param\" content=\"#{app.config.security.csrf.param}\">"))
19
+ end
20
+ end
21
+
22
+ attach do |presenter|
23
+ presenter.render node: -> {
24
+ node = object.each_significant_node(:meta).find { |meta_node|
25
+ meta_node.attributes[:name] == "pw-authenticity-token"
26
+ }
27
+
28
+ unless node.nil?
29
+ View.from_object(node)
30
+ end
31
+ } do
32
+ attributes[:content] = @presentables[:__verifier].sign(Support::MessageVerifier.key)
33
+ end
34
+ end
35
+
36
+ expose do |connection|
37
+ connection.set(:__verifier, connection.verifier)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module PlaceInMode
10
+ extend Support::Extension
11
+
12
+ apply_extension do
13
+ build do |view, modes:|
14
+ unless Pakyow.env?(:prototype)
15
+ PlaceInMode.perform(view, modes)
16
+ end
17
+ end
18
+
19
+ attach do |presenter|
20
+ if Pakyow.env?(:prototype)
21
+ presenter.render node: -> {
22
+ object.find_significant_nodes(:mode, descend: true).map { |node|
23
+ View.from_object(node)
24
+ }
25
+ } do
26
+ PlaceInMode.perform(view, __modes)
27
+ end
28
+ end
29
+ end
30
+
31
+ expose do |connection|
32
+ if Pakyow.env?(:prototype)
33
+ connection.set(:__modes, connection.params[:modes] || [:default])
34
+ end
35
+ end
36
+ end
37
+
38
+ # @api private
39
+ def self.perform(view, modes)
40
+ if modes.length == 1 && modes.first.to_sym == :default
41
+ modes = view.info(:modes) || modes
42
+ end
43
+
44
+ modes.map!(&:to_sym)
45
+
46
+ if view.object.is_a?(StringDoc::Node) && view.object.significant?(:mode) && !modes.include?(view.object.label(:mode))
47
+ view.remove
48
+ else
49
+ view.object.each_significant_node(:mode, descend: true).select { |node|
50
+ !modes.include?(node.label(:mode))
51
+ }.each(&:remove)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ require "pakyow/presenter/composers/component"
6
+
7
+ module Pakyow
8
+ module Presenter
9
+ class Renderer
10
+ module Behavior
11
+ module RenderComponents
12
+ extend Support::Extension
13
+
14
+ apply_extension do
15
+ build do |view, app:, composer:, modes:|
16
+ unless Pakyow.env?(:prototype)
17
+ initial_path = case composer
18
+ when Composers::Component
19
+ composer.component_path
20
+ else
21
+ []
22
+ end
23
+
24
+ component_view = case composer
25
+ when Composers::Component
26
+ composer.class.follow_path(composer.component_path, view)
27
+ else
28
+ view
29
+ end
30
+
31
+ RenderComponents.initialize_renderable_components(
32
+ component_view, app: app, composer: composer, modes: modes, path: initial_path
33
+ )
34
+ end
35
+ end
36
+
37
+ expose do |connection|
38
+ # Prevent state from leaking from the component to the rest of the app.
39
+ #
40
+ component_connection = connection.dup
41
+
42
+ # Expose the component connection for performing from each component.
43
+ #
44
+ connection.set(:__component_connection, component_connection)
45
+ end
46
+ end
47
+
48
+ # @api private
49
+ def self.initialize_renderable_components(view, app:, composer:, modes:, path: [])
50
+ view.components.each_with_index do |component_view, i|
51
+ current_path = path.dup
52
+ current_path << i
53
+
54
+ # If view will be rendered from the app, look for the component on the app.
55
+ #
56
+ component_state = if app.is_a?(Plugin) && app.parent.view?(composer.view_path)
57
+ app.parent.state(:component)
58
+ else
59
+ app.state(:component)
60
+ end
61
+
62
+ components = component_view.object.label(:components).each_with_object([]) { |component_label, arr|
63
+ component_class = component_state.find { |component|
64
+ component.__object_name.name == component_label[:name]
65
+ }
66
+
67
+ if component_class
68
+ # Turn the component into a renderable component. Once an instance is attached on the
69
+ # backend, the component will not be traversed by renders from its parent instead being
70
+ # rendered by its own renderer instance.
71
+ #
72
+ # We don't want the same restriction for non-renderable components because a change to
73
+ # the view should not affect how things work on the backend.
74
+ #
75
+ component_label[:renderable] = true
76
+
77
+ arr << {
78
+ class: component_class,
79
+ config: component_label[:config]
80
+ }
81
+ end
82
+ }
83
+
84
+ if components.any?
85
+ # Since one or more attached components is renderable, we no longer want to descend.
86
+ #
87
+ component_view.object.set_label(:descend, false)
88
+
89
+ # Define the render function that calls the component and renders it at render time.
90
+ #
91
+ component_render = app.isolated(:Presenter).send(:render_proc, component_view) { |node, _context, string|
92
+ presentable_component_connection = presentables[:__component_connection]
93
+ component_connection = presentable_component_connection.dup
94
+
95
+ components.each do |component|
96
+ presentables.each do |key, value|
97
+ if key.to_s.start_with?("__")
98
+ component_connection.set(key, value)
99
+ end
100
+ end
101
+
102
+ # If the component was defined in an app but being called inside a plugin, set the app to the app instead of the plugin.
103
+ #
104
+ if component_connection.app.is_a?(Plugin) && component[:class].ancestors.include?(component_connection.app.parent.isolated(:Component))
105
+ component_connection = component_connection.class.from_connection(component_connection, :@app => component_connection.app.parent)
106
+ end
107
+
108
+ component_instance = component[:class].new(
109
+ connection: component_connection,
110
+ config: component[:config]
111
+ )
112
+
113
+ # Call the component.
114
+ #
115
+ component_instance.perform
116
+ end
117
+
118
+ # Build a compound component presenter.
119
+ #
120
+ component_presenter = if components.length > 1
121
+ RenderComponents.find_compound_presenter(
122
+ app, components.map { |c| c[:class] }
123
+ )
124
+ else
125
+ components.first[:class].__presenter_class
126
+ end
127
+
128
+ # Setup the renderer for the component.
129
+ #
130
+ renderer = app.isolated(:Renderer).new(
131
+ app: app,
132
+ presentables: component_connection.values,
133
+ presenter_class: component_presenter,
134
+ composer: Composers::Component.new(
135
+ composer.view_path, current_path, app: app, labels: node.labels
136
+ ),
137
+ modes: modes
138
+ )
139
+
140
+ # Render to the main buffer.
141
+ #
142
+ renderer.perform(string)
143
+
144
+ # Return nil so nothing else gets written.
145
+ #
146
+ nil
147
+ }
148
+
149
+ # Attach the above render function to the render node.
150
+ #
151
+ component_view.object.transform do |node, context, string|
152
+ component_render.call(node, context, string); nil
153
+ end
154
+ else
155
+ initialize_renderable_components(
156
+ component_view, app: app, composer: composer, modes: modes, path: current_path
157
+ )
158
+ end
159
+ end
160
+ end
161
+
162
+ # @api private
163
+ def self.find_renderable_components(view, components = [])
164
+ view.components.each do |component_view|
165
+ find_renderable_components(component_view, components)
166
+
167
+ if component_view.object.label(:components).any? { |c| c[:renderable] }
168
+ components << component_view
169
+ end
170
+ end
171
+
172
+ components
173
+ end
174
+
175
+ # @api private
176
+ def self.wrap_block(block, context_class)
177
+ Proc.new do
178
+ @app.presenter_for_context(
179
+ context_class.__presenter_class, self
180
+ ).instance_eval(&block)
181
+ end
182
+ end
183
+
184
+ # @api private
185
+ def self.find_compound_presenter(app, component_classes)
186
+ compound_name = component_classes.map { |component_class|
187
+ component_class.__object_name.name.to_s
188
+ }.join("_")
189
+
190
+ object_name = Support::ObjectName.namespace(
191
+ app.class.__object_name.namespace.parts[0], :components, compound_name, :presenter
192
+ )
193
+
194
+ if const_defined?(object_name.constant)
195
+ const_get(object_name.constant)
196
+ else
197
+ nil
198
+ end
199
+ end
200
+
201
+ # @api private
202
+ #
203
+ def self.find_or_build_compound_presenter(app, component_classes)
204
+ compound_name = component_classes.map { |component_class|
205
+ component_class.__object_name.name.to_s
206
+ }.join("_")
207
+
208
+ object_name = Support::ObjectName.namespace(
209
+ app.class.__object_name.namespace.parts[0], :components, compound_name, :presenter
210
+ )
211
+
212
+ if const_defined?(object_name.constant)
213
+ const_get(object_name.constant)
214
+ else
215
+ component_presenter = Class.new(app.isolated(:Presenter))
216
+ Support::ObjectMaker.define_const_for_object_with_name(component_presenter, object_name)
217
+
218
+ component_classes.each do |component_class|
219
+ # Copy unique attached renders.
220
+ #
221
+ component_class.__presenter_class.__attached_renders.each_with_index do |attached_render, i|
222
+ component_presenter.__attached_renders.insert(i, {
223
+ binding_path: attached_render[:binding_path],
224
+ channel: attached_render[:channel],
225
+ node: attached_render[:node],
226
+ priority: attached_render[:priority],
227
+ block: wrap_block(attached_render[:block], component_class),
228
+ })
229
+ end
230
+
231
+ # Copy unique global options.
232
+ #
233
+ component_class.__presenter_class.__global_options.each do |form_binding, field_binding_values|
234
+ field_binding_values.each do |field_binding, field_binding_value|
235
+ component_presenter.options_for(
236
+ form_binding,
237
+ field_binding,
238
+ field_binding_value[:options],
239
+ &wrap_block(field_binding_value[:block], component_class)
240
+ )
241
+ end
242
+ end
243
+
244
+ # Copy unique presentation logic.
245
+ #
246
+ component_class.__presenter_class.__presentation_logic.each do |binding_name, logic_arr|
247
+ unless component_presenter.__presentation_logic.include?(binding_name)
248
+ component_presenter.__presentation_logic[binding_name] = []
249
+ end
250
+
251
+ logic_arr.each_with_index do |logic, i|
252
+ component_presenter.__presentation_logic[binding_name].insert(i, {
253
+ block: wrap_block(logic[:block], component_class),
254
+ channel: logic[:channel]
255
+ })
256
+ end
257
+ end
258
+
259
+ # Copy unique versioning logic.
260
+ #
261
+ component_class.__presenter_class.__versioning_logic.each do |binding_name, logic_arr|
262
+ unless component_presenter.__versioning_logic.include?(binding_name)
263
+ component_presenter.__versioning_logic[binding_name] = []
264
+ end
265
+
266
+ logic_arr.each_with_index do |logic, i|
267
+ component_presenter.__versioning_logic[binding_name].insert(i, {
268
+ block: wrap_block(logic[:block], component_class)
269
+ })
270
+ end
271
+ end
272
+ end
273
+
274
+ component_presenter
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end