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.
- checksums.yaml +5 -5
- data/{pakyow-ui/CHANGELOG.md → CHANGELOG.md} +0 -0
- data/LICENSE +4 -0
- data/{pakyow-ui/README.md → README.md} +1 -2
- data/lib/pakyow/ui/behavior/recording.rb +51 -0
- data/lib/pakyow/ui/behavior/rendering/install_transforms.rb +47 -0
- data/lib/pakyow/ui/behavior/rendering.rb +105 -0
- data/lib/pakyow/ui/behavior/timeouts.rb +31 -0
- data/lib/pakyow/ui/framework.rb +75 -0
- data/lib/pakyow/ui/handler.rb +42 -0
- data/lib/pakyow/ui/helpers.rb +19 -0
- data/lib/pakyow/ui/recordable/attribute.rb +39 -0
- data/lib/pakyow/ui/recordable/attributes.rb +50 -0
- data/lib/pakyow/ui/recordable/helpers/client_remapping.rb +30 -0
- data/lib/pakyow/ui/recordable.rb +303 -0
- data/lib/pakyow/ui.rb +9 -0
- metadata +46 -60
- data/pakyow-ui/LICENSE +0 -20
- data/pakyow-ui/lib/pakyow/ui/base.rb +0 -26
- data/pakyow-ui/lib/pakyow/ui/channel_builder.rb +0 -55
- data/pakyow-ui/lib/pakyow/ui/config.rb +0 -11
- data/pakyow-ui/lib/pakyow/ui/ext/app.rb +0 -52
- data/pakyow-ui/lib/pakyow/ui/ext/view_context.rb +0 -30
- data/pakyow-ui/lib/pakyow/ui/fetch_view_handler.rb +0 -68
- data/pakyow-ui/lib/pakyow/ui/helpers.rb +0 -15
- data/pakyow-ui/lib/pakyow/ui/mock_mutation_eval.rb +0 -25
- data/pakyow-ui/lib/pakyow/ui/mutable.rb +0 -99
- data/pakyow-ui/lib/pakyow/ui/mutable_data.rb +0 -21
- data/pakyow-ui/lib/pakyow/ui/mutate_context.rb +0 -64
- data/pakyow-ui/lib/pakyow/ui/mutation_set.rb +0 -38
- data/pakyow-ui/lib/pakyow/ui/mutation_store.rb +0 -41
- data/pakyow-ui/lib/pakyow/ui/mutator.rb +0 -63
- data/pakyow-ui/lib/pakyow/ui/no_op_view.rb +0 -87
- data/pakyow-ui/lib/pakyow/ui/registries/redis_mutation_registry.rb +0 -70
- data/pakyow-ui/lib/pakyow/ui/registries/simple_mutation_registry.rb +0 -37
- data/pakyow-ui/lib/pakyow/ui/ui.rb +0 -81
- data/pakyow-ui/lib/pakyow/ui/ui_attrs.rb +0 -40
- data/pakyow-ui/lib/pakyow/ui/ui_component.rb +0 -68
- data/pakyow-ui/lib/pakyow/ui/ui_context.rb +0 -16
- data/pakyow-ui/lib/pakyow/ui/ui_instructable.rb +0 -117
- data/pakyow-ui/lib/pakyow/ui/ui_request.rb +0 -14
- data/pakyow-ui/lib/pakyow/ui/ui_view.rb +0 -200
- data/pakyow-ui/lib/pakyow/ui.rb +0 -1
- data/pakyow-ui/lib/pakyow-ui.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e73cc5b15a5dc52180b3e004a21835916a5c94b91e50716e4d9decc0d8b47129
|
4
|
+
data.tar.gz: '095d1cf65f4d828ed90260b501ea7e6a7759dbfe02d1d3ecfd30e9ad4f234b0f'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78cee63f23c55096e8fd1fc2fcc13526766172ba9346cf54ffb00c25679945f641921a39f6cec6c5c5aeedabacc89ef4434ea302787babfa52d1b701947b610c
|
7
|
+
data.tar.gz: 937625badf07777bc7ebd71413d86aa27cc1e9af28f9d25e5c595a376d0528d8984168e6db12ebe8730515d161b7951f314a2b1cb6bc81d92de7be7dfa3fb770
|
File without changes
|
data/LICENSE
ADDED
@@ -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
|
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
|