pakyow-ui 0.11.3 → 1.0.0.rc1

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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/{pakyow-ui/CHANGELOG.md → CHANGELOG.md} +0 -0
  3. data/LICENSE +4 -0
  4. data/{pakyow-ui/README.md → README.md} +1 -2
  5. data/lib/pakyow/ui/behavior/recording.rb +51 -0
  6. data/lib/pakyow/ui/behavior/rendering/install_transforms.rb +47 -0
  7. data/lib/pakyow/ui/behavior/rendering.rb +105 -0
  8. data/lib/pakyow/ui/behavior/timeouts.rb +31 -0
  9. data/lib/pakyow/ui/framework.rb +75 -0
  10. data/lib/pakyow/ui/handler.rb +42 -0
  11. data/lib/pakyow/ui/helpers.rb +19 -0
  12. data/lib/pakyow/ui/recordable/attribute.rb +39 -0
  13. data/lib/pakyow/ui/recordable/attributes.rb +50 -0
  14. data/lib/pakyow/ui/recordable/helpers/client_remapping.rb +30 -0
  15. data/lib/pakyow/ui/recordable.rb +303 -0
  16. data/lib/pakyow/ui.rb +9 -0
  17. metadata +46 -60
  18. data/pakyow-ui/LICENSE +0 -20
  19. data/pakyow-ui/lib/pakyow/ui/base.rb +0 -26
  20. data/pakyow-ui/lib/pakyow/ui/channel_builder.rb +0 -55
  21. data/pakyow-ui/lib/pakyow/ui/config.rb +0 -11
  22. data/pakyow-ui/lib/pakyow/ui/ext/app.rb +0 -52
  23. data/pakyow-ui/lib/pakyow/ui/ext/view_context.rb +0 -30
  24. data/pakyow-ui/lib/pakyow/ui/fetch_view_handler.rb +0 -68
  25. data/pakyow-ui/lib/pakyow/ui/helpers.rb +0 -15
  26. data/pakyow-ui/lib/pakyow/ui/mock_mutation_eval.rb +0 -25
  27. data/pakyow-ui/lib/pakyow/ui/mutable.rb +0 -99
  28. data/pakyow-ui/lib/pakyow/ui/mutable_data.rb +0 -21
  29. data/pakyow-ui/lib/pakyow/ui/mutate_context.rb +0 -64
  30. data/pakyow-ui/lib/pakyow/ui/mutation_set.rb +0 -38
  31. data/pakyow-ui/lib/pakyow/ui/mutation_store.rb +0 -41
  32. data/pakyow-ui/lib/pakyow/ui/mutator.rb +0 -63
  33. data/pakyow-ui/lib/pakyow/ui/no_op_view.rb +0 -87
  34. data/pakyow-ui/lib/pakyow/ui/registries/redis_mutation_registry.rb +0 -70
  35. data/pakyow-ui/lib/pakyow/ui/registries/simple_mutation_registry.rb +0 -37
  36. data/pakyow-ui/lib/pakyow/ui/ui.rb +0 -81
  37. data/pakyow-ui/lib/pakyow/ui/ui_attrs.rb +0 -40
  38. data/pakyow-ui/lib/pakyow/ui/ui_component.rb +0 -68
  39. data/pakyow-ui/lib/pakyow/ui/ui_context.rb +0 -16
  40. data/pakyow-ui/lib/pakyow/ui/ui_instructable.rb +0 -117
  41. data/pakyow-ui/lib/pakyow/ui/ui_request.rb +0 -14
  42. data/pakyow-ui/lib/pakyow/ui/ui_view.rb +0 -200
  43. data/pakyow-ui/lib/pakyow/ui.rb +0 -1
  44. data/pakyow-ui/lib/pakyow-ui.rb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b844430b966145355c6fbe487e29146929844acc
4
- data.tar.gz: 11749702224aaa8734d3f3a833ecb964ecbca8fb
2
+ SHA256:
3
+ metadata.gz: e73cc5b15a5dc52180b3e004a21835916a5c94b91e50716e4d9decc0d8b47129
4
+ data.tar.gz: '095d1cf65f4d828ed90260b501ea7e6a7759dbfe02d1d3ecfd30e9ad4f234b0f'
5
5
  SHA512:
6
- metadata.gz: 24877fe45b5e018abd93bdee153398beca25fd06ac74e135e1220716618ceeee54c024ac8387ac52441c7b7ab160f0df18dc76ec4824af9b4e3f88fec1ce45b7
7
- data.tar.gz: 056c6222bec4874a0c4a07768fb8c8aa8d71f7a05389762c34f59f6fe99d22f12ce6e35acaab62e30fa9f81b448b557b9d924afe0f64720bf0fa23cc26309bad
6
+ metadata.gz: 78cee63f23c55096e8fd1fc2fcc13526766172ba9346cf54ffb00c25679945f641921a39f6cec6c5c5aeedabacc89ef4434ea302787babfa52d1b701947b610c
7
+ data.tar.gz: 937625badf07777bc7ebd71413d86aa27cc1e9af28f9d25e5c595a376d0528d8984168e6db12ebe8730515d161b7951f314a2b1cb6bc81d92de7be7dfa3fb770
File without changes
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) Metabahn, LLC
2
+
3
+ Pakyow UI is an open-source project licensed under the terms of the LGPLv3 license.
4
+ See <https://choosealicense.com/licenses/lgpl-3.0/> for license text.
@@ -307,8 +307,7 @@ Source code can be downloaded as part of the Pakyow project on Github:
307
307
 
308
308
  # License
309
309
 
310
- Pakyow UI is released free and open-source under the [MIT
311
- License](http://opensource.org/licenses/MIT).
310
+ Pakyow UI is free and open-source under the [LGPLv3 license](https://choosealicense.com/licenses/lgpl-3.0/).
312
311
 
313
312
  # Support
314
313
 
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ require "pakyow/ui/recordable"
6
+
7
+ module Pakyow
8
+ module UI
9
+ module Behavior
10
+ module Recording
11
+ extend Support::Extension
12
+
13
+ def find_ui_presenter_for(presenter_class)
14
+ if is_a?(Plugin)
15
+ # Look for the presenter in the plugin first, falling back to the app.
16
+ #
17
+ ui_presenter_class = parent.ui_presenters.find { |klass|
18
+ klass.ancestors.include?(presenter_class)
19
+ }
20
+ end
21
+
22
+ ui_presenter_class ||= @ui_presenters.find { |klass|
23
+ klass.ancestors.include?(presenter_class)
24
+ }
25
+ end
26
+
27
+ apply_extension do
28
+ # Create subclasses of each presenter, then make the subclasses recordable.
29
+ # These subclasses will be used when performing a ui presentation instead
30
+ # of the original presenter, but they'll behave identically!
31
+ #
32
+ after "initialize" do
33
+ @ui_presenters = [isolated(:Presenter)].concat(
34
+ state(:presenter)
35
+ ).concat(
36
+ state(:component).map(&:__presenter_class)
37
+ ).map { |presenter_class|
38
+ Class.new(presenter_class) do
39
+ include Recordable
40
+ end
41
+ }
42
+ end
43
+
44
+ class_eval do
45
+ attr_reader :ui_presenters
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ require "pakyow/presenter/view"
6
+
7
+ module Pakyow
8
+ module UI
9
+ module Behavior
10
+ module Rendering
11
+ module InstallTransforms
12
+ extend Support::Extension
13
+
14
+ apply_extension do
15
+ attach do |presenter|
16
+ presenter.render node: -> {
17
+ nodes = []
18
+
19
+ if html_node = object.find_first_significant_node(:html)
20
+ nodes << Pakyow::Presenter::View.from_object(html_node)
21
+ end
22
+
23
+ if !object.is_a?(StringDoc) && object.significant?(:component)
24
+ nodes << Pakyow::Presenter::View.from_object(object)
25
+ end
26
+
27
+ object.each_significant_node_without_descending_into_type(:component, descend: true) do |node|
28
+ if node.label(:components).any? { |c| c[:renderable ] }
29
+ nodes << Pakyow::Presenter::View.from_object(node)
30
+ end
31
+ end
32
+
33
+ nodes
34
+ } do
35
+ if transformation_id = presentables[:__transformation_id]
36
+ # Set the transformation_id on the target node so that transformations can be applied to the right place.
37
+ #
38
+ attributes[:"data-t"] = transformation_id
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ require "pakyow/support/deep_dup"
6
+ require "pakyow/support/extension"
7
+ require "pakyow/support/inflector"
8
+
9
+ require "pakyow/realtime/helpers/subscriptions"
10
+
11
+ require "pakyow/ui/handler"
12
+
13
+ module Pakyow
14
+ module UI
15
+ module Behavior
16
+ module Rendering
17
+ extend Support::Extension
18
+
19
+ apply_extension do
20
+ isolated :Renderer do
21
+ include Realtime::Helpers::Subscriptions
22
+
23
+ def socket_client_id
24
+ presentables[:__socket_client_id]
25
+ end
26
+
27
+ on "render" do
28
+ unless presentables.include?(:__ui_transform) || subscribables.empty?
29
+ # To keep up with the node(s) that matter for the transformation, a `data-t` attribute
30
+ # is added to the node that contains the transformation_id. When the transformation is
31
+ # triggered in the future, the client knows what node to apply tranformations to.
32
+ #
33
+ # Note that when we're presenting an entire view, `data-t` is set on the `html` node.
34
+ #
35
+ transformation_target = case @presenter.view.object
36
+ when StringDoc
37
+ @presenter.view.object.find_first_significant_node(:html)
38
+ when StringDoc::Node
39
+ @presenter.view.object
40
+ end
41
+
42
+ if transformation_target
43
+ ui_renderer_instance = @app.isolated(:UIRenderer).allocate
44
+
45
+ instance_variables.each do |ivar|
46
+ ui_renderer_instance.instance_variable_set(ivar, instance_variable_get(ivar))
47
+ end
48
+
49
+ metadata = {
50
+ renderer: ui_renderer_instance
51
+ }
52
+
53
+ payload = {
54
+ metadata: Marshal.dump(metadata)
55
+ }
56
+
57
+ # Generate a unique id based on the value of the metadata. This guarantees that the
58
+ # transformation id will be consistent across subscriptions.
59
+ #
60
+ transformation_id = Digest::SHA1.hexdigest(payload[:metadata])
61
+ presentables[:__transformation_id] = transformation_id
62
+ payload[:transformation_id] = transformation_id
63
+
64
+ # Find every subscribable presentable, creating a data subscription for each.
65
+ #
66
+ subscribables.each do |subscribable|
67
+ subscribable.subscribe(socket_client_id, handler: Handler, payload: payload) do |ids|
68
+ # Subscribe the subscriptions to the "transformation" channel.
69
+ #
70
+ subscribe(:transformation, *ids.uniq)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ using Support::DeepDup
78
+
79
+ private def subscribables
80
+ @subscribables ||= presentables.reject { |presentable_name, _|
81
+ presentable_name.to_s.start_with?("__")
82
+ }.map { |_, value|
83
+ proxy = if value.is_a?(Data::Proxy)
84
+ value
85
+ elsif value.instance_variable_defined?(:@__proxy)
86
+ value.instance_variable_get(:@__proxy)
87
+ else
88
+ nil
89
+ end
90
+
91
+ if proxy && proxy.subscribable?
92
+ proxy.deep_dup
93
+ else
94
+ nil
95
+ end
96
+ }.compact.uniq { |subscribable|
97
+ subscribable.source.__getobj__
98
+ }
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module UI
7
+ module Behavior
8
+ module Timeouts
9
+ extend Support::Extension
10
+
11
+ apply_extension do
12
+ on :join do
13
+ @connection.app.data.persist(@id)
14
+ end
15
+
16
+ on :leave do
17
+ @connection.app.data.expire(@id, config.realtime.timeouts.disconnect)
18
+ end
19
+
20
+ isolated :Renderer do
21
+ after "render" do
22
+ # Expire subscriptions if the connection is never established.
23
+ #
24
+ @app.data.expire(presentables[:__socket_client_id], @app.config.realtime.timeouts.initial)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/framework"
4
+
5
+ require "pakyow/ui/helpers"
6
+
7
+ require "pakyow/ui/behavior/recording"
8
+ require "pakyow/ui/behavior/rendering"
9
+ require "pakyow/ui/behavior/timeouts"
10
+
11
+ require "pakyow/ui/behavior/rendering/install_transforms"
12
+
13
+ module Pakyow
14
+ module UI
15
+ class Framework < Pakyow::Framework(:ui)
16
+ # @api private
17
+ module PresenterForContext
18
+ def presenter_for_context(presenter_class, context)
19
+ if context.presentables.include?(:__ui_transform)
20
+ instance = find_ui_presenter_for(presenter_class).new(
21
+ context.view, app: context.app, presentables: context.presentables
22
+ )
23
+
24
+ instance.instance_variable_set(:@calls, context.calls)
25
+ instance
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+
32
+ def boot
33
+ object.class_eval do
34
+ register_helper :passive, Helpers
35
+
36
+ include Behavior::Recording
37
+ include Behavior::Rendering
38
+ include Behavior::Timeouts
39
+
40
+ isolated :Renderer do
41
+ include Behavior::Rendering::InstallTransforms
42
+ end
43
+
44
+ prepend PresenterForContext
45
+
46
+ ui_renderer = Class.new(isolated(:Renderer)) do
47
+ def marshal_load(state)
48
+ deserialize(state)
49
+ @presenter_class = @app.find_ui_presenter_for(@presenter_class)
50
+ initialize_presenter
51
+ end
52
+
53
+ def perform(*)
54
+ @presenter.to_html
55
+ end
56
+ end
57
+
58
+ # Delete the render_components build step since ui will not be invoking the component.
59
+ #
60
+ ui_renderer.__build_fns.delete_if { |fn|
61
+ fn.source_location[0].end_with?("render_components.rb")
62
+ }
63
+
64
+ unless const_defined?(:UIRenderer, false)
65
+ const_set(:UIRenderer, ui_renderer)
66
+ end
67
+
68
+ after :initialize do
69
+ config.data.subscriptions.version = config.version
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/connection"
4
+
5
+ require "pakyow/data/proxy"
6
+ require "pakyow/data/sources/ephemeral"
7
+
8
+ require "pakyow/realtime/channel"
9
+
10
+ require "pakyow/ui/recordable"
11
+
12
+ module Pakyow
13
+ module UI
14
+ class Handler
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(args, subscription: nil, result: nil)
20
+ renderer = Marshal.restore(args[:metadata])[:renderer]
21
+ renderer.presentables[:__ui_transform] = true
22
+
23
+ # If an ephemeral caused the update, replace the value for any matching presentables.
24
+ #
25
+ if result.is_a?(Data::Sources::Ephemeral)
26
+ renderer.presentables.each do |key, value|
27
+ if value.is_a?(Data::Proxy) && value.source.is_a?(Data::Sources::Ephemeral) && value.source.type == result.type && value.source.qualifications == result.qualifications
28
+ value.instance_variable_set(:@source, result)
29
+ end
30
+ end
31
+ end
32
+
33
+ renderer.perform
34
+
35
+ @app.websocket_server.subscription_broadcast(
36
+ Realtime::Channel.new(:transformation, subscription[:id]),
37
+ { id: args[:transformation_id], calls: renderer.presenter }
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module UI
5
+ module Helpers
6
+ def ui?
7
+ @connection.request_header?("pw-ui")
8
+ end
9
+
10
+ def ui
11
+ @connection.request_header("pw-ui")
12
+ end
13
+
14
+ def ui_transform?
15
+ @connection.set?(:__ui_transform)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "json"
5
+
6
+ require "pakyow/ui/recordable/helpers/client_remapping"
7
+
8
+ module Pakyow
9
+ module UI
10
+ module Recordable
11
+ class Attribute < SimpleDelegator
12
+ include Helpers::ClientRemapping
13
+
14
+ def initialize(attribute)
15
+ __setobj__(attribute)
16
+ @calls = []
17
+ end
18
+
19
+ %i([] []= << delete clear add).each do |method_name|
20
+ define_method method_name do |*args|
21
+ super(*args).tap do
22
+ @calls << [remap_for_client(method_name), args, [], []]
23
+ end
24
+ end
25
+ end
26
+
27
+ def to_json(*)
28
+ @calls.to_json
29
+ end
30
+
31
+ # Fixes an issue using pp inside a delegator.
32
+ #
33
+ def pp(*args)
34
+ Kernel.pp(*args)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ require "pakyow/ui/recordable/attribute"
6
+ require "pakyow/ui/recordable/helpers/client_remapping"
7
+
8
+ module Pakyow
9
+ module UI
10
+ module Recordable
11
+ class Attributes < Pakyow::Presenter::Attributes
12
+ include Helpers::ClientRemapping
13
+
14
+ %i([] []=).each do |method_name|
15
+ define_method method_name do |*args|
16
+ result = super(*args)
17
+ result = case method_name
18
+ when :[]
19
+ Attribute.new(result)
20
+ else
21
+ result
22
+ end
23
+
24
+ result.tap do
25
+ subsequent = if result.is_a?(Attribute)
26
+ result
27
+ else
28
+ []
29
+ end
30
+
31
+ @calls << [remap_for_client(method_name), args, [], subsequent]
32
+ end
33
+ end
34
+ end
35
+
36
+ def to_json(*)
37
+ @calls.to_json
38
+ end
39
+
40
+ class << self
41
+ def from_attributes(attributes)
42
+ new(attributes.instance_variable_get(:@attributes)).tap { |instance|
43
+ instance.instance_variable_set(:@calls, [])
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module UI
5
+ module Recordable
6
+ module Helpers
7
+ module ClientRemapping
8
+ def remap_for_client(method_name)
9
+ case method_name
10
+ when :[]
11
+ :get
12
+ when :[]=
13
+ :set
14
+ when :<<
15
+ :add
16
+ when :title=
17
+ :setTitle
18
+ when :html=
19
+ :setHtml
20
+ when :endpoint_action
21
+ :endpointAction
22
+ else
23
+ method_name
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end