motion 0.2.0 → 0.4.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/lib/generators/motion/templates/motion.js +8 -1
- data/lib/generators/motion/templates/motion.rb +26 -8
- data/lib/motion.rb +6 -0
- data/lib/motion/action_cable_extentions.rb +6 -0
- data/lib/motion/action_cable_extentions/declarative_notifications.rb +101 -0
- data/lib/motion/action_cable_extentions/declarative_streams.rb +9 -43
- data/lib/motion/action_cable_extentions/synchronization.rb +34 -0
- data/lib/motion/callback.rb +35 -0
- data/lib/motion/channel.rb +12 -2
- data/lib/motion/component.rb +4 -0
- data/lib/motion/component/broadcasts.rb +40 -26
- data/lib/motion/component/callbacks.rb +19 -0
- data/lib/motion/component/lifecycle.rb +91 -9
- data/lib/motion/component/motions.rb +26 -16
- data/lib/motion/component/periodic_timers.rb +68 -0
- data/lib/motion/component/rendering.rb +25 -16
- data/lib/motion/component_connection.rb +18 -2
- data/lib/motion/configuration.rb +18 -10
- data/lib/motion/errors.rb +96 -68
- data/lib/motion/event.rb +9 -1
- data/lib/motion/log_helper.rb +2 -0
- data/lib/motion/markup_transformer.rb +6 -1
- data/lib/motion/revision_calculator.rb +48 -0
- data/lib/motion/serializer.rb +15 -2
- data/lib/motion/version.rb +1 -1
- metadata +23 -3
data/lib/motion/errors.rb
CHANGED
@@ -10,6 +10,7 @@ module Motion
|
|
10
10
|
|
11
11
|
def initialize(component, message = nil)
|
12
12
|
super(message)
|
13
|
+
|
13
14
|
@component = component
|
14
15
|
end
|
15
16
|
end
|
@@ -20,13 +21,13 @@ module Motion
|
|
20
21
|
attr_reader :motion
|
21
22
|
|
22
23
|
def initialize(component, motion)
|
23
|
-
super(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
map_motion :#{motion}
|
29
|
-
|
24
|
+
super(
|
25
|
+
component,
|
26
|
+
"No component motion handler mapped for motion `#{motion}` in " \
|
27
|
+
"component `#{component.class}`.\n" \
|
28
|
+
"\n" \
|
29
|
+
"Hint: Consider adding `map_motion :#{motion}` to `#{component.class}`."
|
30
|
+
)
|
30
31
|
|
31
32
|
@motion = motion
|
32
33
|
end
|
@@ -34,20 +35,32 @@ module Motion
|
|
34
35
|
|
35
36
|
class BlockNotAllowedError < ComponentRenderingError
|
36
37
|
def initialize(component)
|
37
|
-
super(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
super(
|
39
|
+
component,
|
40
|
+
"Motion does not support rendering with a block.\n" \
|
41
|
+
"\n" \
|
42
|
+
"Hint: Try wrapping a plain component with a motion component."
|
43
|
+
)
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
47
|
class MultipleRootsError < ComponentRenderingError
|
46
48
|
def initialize(component)
|
47
|
-
super(
|
48
|
-
|
49
|
+
super(
|
50
|
+
component,
|
51
|
+
"The template for #{component.class} can only have one root " \
|
52
|
+
"element.\n" \
|
53
|
+
"\n" \
|
54
|
+
"Hint: Wrap all elements in a single element, such as `<div>` or " \
|
55
|
+
"`<section>`."
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
49
59
|
|
50
|
-
|
60
|
+
class RenderAborted < ComponentRenderingError
|
61
|
+
def initialize(component)
|
62
|
+
super(component, <<~MSG)
|
63
|
+
Rendering #{component.class} was aborted by a callback.
|
51
64
|
MSG
|
52
65
|
end
|
53
66
|
end
|
@@ -56,18 +69,19 @@ module Motion
|
|
56
69
|
|
57
70
|
class UnrepresentableStateError < InvalidComponentStateError
|
58
71
|
def initialize(component, cause)
|
59
|
-
super(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
more
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
72
|
+
super(
|
73
|
+
component,
|
74
|
+
"Some state prevented `#{component.class}` from being serialized " \
|
75
|
+
"into a string. Motion components must be serializable using " \
|
76
|
+
"`Marshal.dump`. Many types of objects are not serializable " \
|
77
|
+
"including procs, references to anonymous classes, and more. See the " \
|
78
|
+
"documentation for `Marshal.dump` for more information.\n" \
|
79
|
+
"\n" \
|
80
|
+
"The specific error from `Marshal.dump` was: #{cause}\n" \
|
81
|
+
"\n" \
|
82
|
+
"Hint: Ensure that any exotic state variables in " \
|
83
|
+
"`#{component.class}` are removed or replaced."
|
84
|
+
)
|
71
85
|
end
|
72
86
|
end
|
73
87
|
|
@@ -75,44 +89,48 @@ module Motion
|
|
75
89
|
|
76
90
|
class InvalidSerializedStateError < SerializedComponentError
|
77
91
|
def initialize
|
78
|
-
super(
|
79
|
-
The serialized state of your component is not valid
|
80
|
-
|
81
|
-
|
82
|
-
|
92
|
+
super(
|
93
|
+
"The serialized state of your component is not valid.\n" \
|
94
|
+
"\n" \
|
95
|
+
"Hint: Ensure that you have not tampered with the contents of data " \
|
96
|
+
"attributes added by Motion in the DOM or changed the value of " \
|
97
|
+
"`Motion.config.secret`."
|
98
|
+
)
|
83
99
|
end
|
84
100
|
end
|
85
101
|
|
86
|
-
class
|
87
|
-
attr_reader :
|
88
|
-
:
|
89
|
-
|
90
|
-
def initialize(
|
91
|
-
super(
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
class UpgradeNotImplementedError < ComponentError
|
103
|
+
attr_reader :previous_revision,
|
104
|
+
:current_revision
|
105
|
+
|
106
|
+
def initialize(component, previous_revision, current_revision)
|
107
|
+
super(
|
108
|
+
component,
|
109
|
+
"Cannot upgrade `#{component.class}` from a previous revision of the " \
|
110
|
+
"application (#{previous_revision}) to the current revision of the " \
|
111
|
+
"application (#{current_revision})\n" \
|
112
|
+
"\n" \
|
113
|
+
"By default, Motion does not allow components from other revisions " \
|
114
|
+
"of the application to be mounted because new code with old state " \
|
115
|
+
"can lead to unpredictable and unsafe behavior.\n" \
|
116
|
+
"\n" \
|
117
|
+
"Hint: If you would like to allow this component to surive " \
|
118
|
+
"deployments, consider providing an alternative implimentation for " \
|
119
|
+
"`#{component.class}.upgrade_from`."
|
120
|
+
)
|
121
|
+
|
122
|
+
@previous_revision = previous_revision
|
123
|
+
@current_revision = current_revision
|
106
124
|
end
|
107
125
|
end
|
108
126
|
|
109
127
|
class AlreadyConfiguredError < Error
|
110
128
|
def initialize
|
111
|
-
super(
|
112
|
-
Motion is already configured
|
113
|
-
|
114
|
-
|
115
|
-
|
129
|
+
super(
|
130
|
+
"Motion is already configured.\n" \
|
131
|
+
"\n" \
|
132
|
+
"Hint: Move all Motion config to `config/initializers/motion.rb`."
|
133
|
+
)
|
116
134
|
end
|
117
135
|
end
|
118
136
|
|
@@ -120,12 +138,12 @@ module Motion
|
|
120
138
|
attr_reader :server_version, :client_version
|
121
139
|
|
122
140
|
def initialize(server_version, client_version)
|
123
|
-
super(
|
124
|
-
The client version (#{client_version}) is newer than the server
|
125
|
-
(#{server_version}). Please upgrade the Motion gem
|
126
|
-
|
127
|
-
|
128
|
-
|
141
|
+
super(
|
142
|
+
"The client version (#{client_version}) is newer than the server " \
|
143
|
+
"version (#{server_version}). Please upgrade the Motion gem.\n" \
|
144
|
+
"\n" \
|
145
|
+
"Hint: Run `bundle add motion --version \">= #{client_version}\"`."
|
146
|
+
)
|
129
147
|
|
130
148
|
@server_version = server_version
|
131
149
|
@client_version = client_version
|
@@ -136,16 +154,26 @@ module Motion
|
|
136
154
|
attr_reader :minimum_bytes
|
137
155
|
|
138
156
|
def initialize(minimum_bytes)
|
139
|
-
super(
|
140
|
-
The secret that you provided is not long enough. It must
|
141
|
-
#{minimum_bytes} bytes.
|
142
|
-
|
157
|
+
super(
|
158
|
+
"The secret that you provided is not long enough. It must be at " \
|
159
|
+
"least #{minimum_bytes} bytes long."
|
160
|
+
)
|
143
161
|
end
|
144
162
|
end
|
145
163
|
|
146
164
|
class BadRevisionError < Error
|
147
165
|
def initialize
|
148
|
-
super("The revision cannot contain a NULL byte")
|
166
|
+
super("The revision cannot contain a NULL byte.")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class BadRevisionPathsError < Error
|
171
|
+
def initialize
|
172
|
+
super(
|
173
|
+
"Revision paths must be a `Rails::Paths::Root` object or an object " \
|
174
|
+
"that responds to `all_paths.flat_map(&:existent)` and returns an " \
|
175
|
+
"Array of strings representing full paths."
|
176
|
+
)
|
149
177
|
end
|
150
178
|
end
|
151
179
|
end
|
data/lib/motion/event.rb
CHANGED
@@ -34,8 +34,16 @@ module Motion
|
|
34
34
|
@target = Motion::Element.from_raw(raw["target"])
|
35
35
|
end
|
36
36
|
|
37
|
+
def current_target
|
38
|
+
return @current_target if defined?(@current_target)
|
39
|
+
|
40
|
+
@current_target = Motion::Element.from_raw(raw["currentTarget"])
|
41
|
+
end
|
42
|
+
|
43
|
+
alias element current_target
|
44
|
+
|
37
45
|
def form_data
|
38
|
-
|
46
|
+
element&.form_data
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
data/lib/motion/log_helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "nokogiri"
|
4
|
+
require "active_support/core_ext/object/blank"
|
4
5
|
|
5
6
|
require "motion"
|
6
7
|
|
@@ -21,6 +22,8 @@ module Motion
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def add_state_to_html(component, html)
|
25
|
+
return if html.blank?
|
26
|
+
|
24
27
|
key, state = serializer.serialize(component)
|
25
28
|
|
26
29
|
transform_root(component, html) do |root|
|
@@ -35,7 +38,9 @@ module Motion
|
|
35
38
|
fragment = Nokogiri::HTML::DocumentFragment.parse(html)
|
36
39
|
root, *unexpected_others = fragment.children
|
37
40
|
|
38
|
-
|
41
|
+
if !root || unexpected_others.any?(&:present?)
|
42
|
+
raise MultipleRootsError, component
|
43
|
+
end
|
39
44
|
|
40
45
|
yield root
|
41
46
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
require "motion"
|
5
|
+
|
6
|
+
module Motion
|
7
|
+
class RevisionCalculator
|
8
|
+
attr_reader :revision_paths
|
9
|
+
|
10
|
+
def initialize(revision_paths:)
|
11
|
+
@revision_paths = revision_paths
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
derive_file_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def derive_file_hash
|
21
|
+
digest = Digest::MD5.new
|
22
|
+
|
23
|
+
files.each do |file|
|
24
|
+
digest << file # include filename as well as contents
|
25
|
+
digest << File.read(file)
|
26
|
+
end
|
27
|
+
|
28
|
+
digest.hexdigest
|
29
|
+
end
|
30
|
+
|
31
|
+
def existent_paths
|
32
|
+
@existent_paths ||=
|
33
|
+
begin
|
34
|
+
revision_paths.all_paths.flat_map(&:existent)
|
35
|
+
rescue
|
36
|
+
raise BadRevisionPathsError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def existent_files(path)
|
41
|
+
Dir["#{path}/**/*", path].reject { |f| File.directory?(f) }.uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def files
|
45
|
+
@files ||= existent_paths.flat_map { |path| existent_files(path) }.sort
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/motion/serializer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "digest"
|
4
|
+
require "lz4-ruby"
|
4
5
|
require "active_support/message_encryptor"
|
5
6
|
|
6
7
|
require "motion"
|
@@ -32,8 +33,12 @@ module Motion
|
|
32
33
|
@revision = revision
|
33
34
|
end
|
34
35
|
|
36
|
+
def weak_digest(component)
|
37
|
+
dump(component).hash
|
38
|
+
end
|
39
|
+
|
35
40
|
def serialize(component)
|
36
|
-
state = dump(component)
|
41
|
+
state = deflate(dump(component))
|
37
42
|
state_with_revision = "#{revision}#{NULL_BYTE}#{state}"
|
38
43
|
|
39
44
|
[
|
@@ -45,7 +50,7 @@ module Motion
|
|
45
50
|
def deserialize(serialized_component)
|
46
51
|
state_with_revision = decrypt_and_verify(serialized_component)
|
47
52
|
serialized_revision, state = state_with_revision.split(NULL_BYTE, 2)
|
48
|
-
component = load(state)
|
53
|
+
component = load(inflate(state))
|
49
54
|
|
50
55
|
if revision == serialized_revision
|
51
56
|
component
|
@@ -66,6 +71,14 @@ module Motion
|
|
66
71
|
Marshal.load(state)
|
67
72
|
end
|
68
73
|
|
74
|
+
def deflate(dumped_component)
|
75
|
+
LZ4.compress(dumped_component)
|
76
|
+
end
|
77
|
+
|
78
|
+
def inflate(deflated_state)
|
79
|
+
LZ4.uncompress(deflated_state)
|
80
|
+
end
|
81
|
+
|
69
82
|
def encrypt_and_sign(cleartext)
|
70
83
|
encryptor.encrypt_and_sign(cleartext)
|
71
84
|
end
|
data/lib/motion/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alec Larsen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-08-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -39,6 +39,20 @@ dependencies:
|
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '5.2'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: lz4-ruby
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 0.3.3
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.3.3
|
42
56
|
description: |
|
43
57
|
Motion extends Github's `view_component` to allow you to build reactive,
|
44
58
|
real-time frontend UI components in your Rails application using pure Ruby.
|
@@ -55,13 +69,18 @@ files:
|
|
55
69
|
- lib/generators/motion/templates/motion.rb
|
56
70
|
- lib/motion.rb
|
57
71
|
- lib/motion/action_cable_extentions.rb
|
72
|
+
- lib/motion/action_cable_extentions/declarative_notifications.rb
|
58
73
|
- lib/motion/action_cable_extentions/declarative_streams.rb
|
59
74
|
- lib/motion/action_cable_extentions/log_suppression.rb
|
75
|
+
- lib/motion/action_cable_extentions/synchronization.rb
|
76
|
+
- lib/motion/callback.rb
|
60
77
|
- lib/motion/channel.rb
|
61
78
|
- lib/motion/component.rb
|
62
79
|
- lib/motion/component/broadcasts.rb
|
80
|
+
- lib/motion/component/callbacks.rb
|
63
81
|
- lib/motion/component/lifecycle.rb
|
64
82
|
- lib/motion/component/motions.rb
|
83
|
+
- lib/motion/component/periodic_timers.rb
|
65
84
|
- lib/motion/component/rendering.rb
|
66
85
|
- lib/motion/component_connection.rb
|
67
86
|
- lib/motion/configuration.rb
|
@@ -71,6 +90,7 @@ files:
|
|
71
90
|
- lib/motion/log_helper.rb
|
72
91
|
- lib/motion/markup_transformer.rb
|
73
92
|
- lib/motion/railtie.rb
|
93
|
+
- lib/motion/revision_calculator.rb
|
74
94
|
- lib/motion/serializer.rb
|
75
95
|
- lib/motion/test_helpers.rb
|
76
96
|
- lib/motion/version.rb
|
@@ -82,7 +102,7 @@ metadata:
|
|
82
102
|
source_code_uri: https://github.com/unabridged/motion
|
83
103
|
post_install_message: |
|
84
104
|
Friendly reminder: When updating the motion gem, don't forget to update the
|
85
|
-
NPM package as well (`bin/yarn add '@unabridged/motion@0.
|
105
|
+
NPM package as well (`bin/yarn add '@unabridged/motion@0.4.1'`).
|
86
106
|
rdoc_options: []
|
87
107
|
require_paths:
|
88
108
|
- lib
|