motion 0.3.0 → 0.4.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7645381b6b2749a4a9ba90a15c93916636e1b9edfd7b3a6a1bf7d47429fedb3f
4
- data.tar.gz: 46c929b8aa2d1de8c714eb8b8db1db8524aec892576a13ad1582b7fa858565f7
3
+ metadata.gz: 68e4222065f95cc117a8fc9233e8b421f05cdc8bbb58b966c618b82487723be7
4
+ data.tar.gz: c9c1927a3ae081faeb6d346246a05e8dbfdc8f2da91185073fdf18d8abf7ab83
5
5
  SHA512:
6
- metadata.gz: b8c99a480f51e9d818f5d22de3efbf8923155943d1815188e19332c54b8f24b909bc371abc7282d3a9caac754cf1b0aee4bb3193dad75b7f1d86a9112ed13983
7
- data.tar.gz: a12e9213ba0ae38cbc5d839ae1408a5aab064be055896300f19a2296ebcd74b49922b96636bc52bd7b7aef62be9868ea47437544938f20facaad45534744bc4e
6
+ metadata.gz: 218b161a0c04029315fcda296545156ddda990cd6ab98b664c3896ee1d15f93a1092bb1c0f4b50a550b848d5242d929b178fecde05b34b723c5354c016dd00ad
7
+ data.tar.gz: ed56e7e3f78851fa7e8ee6643a334016d3013ac00c813e766a2f4190d5698f6c976dce5dfef4465e882bcbed3df33aff63b8952687830e30ea61769bc9e9f89c
@@ -1,5 +1,5 @@
1
- import { createClient } from '@unabridged/motion';
2
- import consumer from './channels/consumer';
1
+ import { createClient } from '@unabridged/motion'
2
+ import consumer from './channels/consumer'
3
3
 
4
4
  export default createClient({
5
5
 
@@ -11,7 +11,7 @@ export default createClient({
11
11
  // Motion can log information about the lifecycle of components to the
12
12
  // browser's console. It is recommended to turn this feature off outside of
13
13
  // development.
14
- logging: process.env["RAILS_ENV"] === "development",
14
+ logging: process.env.RAILS_ENV === 'development'
15
15
 
16
16
  // This function will be called for every motion, and the return value will be
17
17
  // made available at `Motion::Event#extra_data`:
@@ -30,8 +30,8 @@ export default createClient({
30
30
  // The data attributes used by Motion can be customized, but these values must
31
31
  // also be updated in the Ruby initializer:
32
32
  //
33
- // keyAttribute: "data-motion-key",
34
- // stateAttribute: "data-motion-state",
35
- // motionAttribute: "data-motion",
33
+ // keyAttribute: 'data-motion-key',
34
+ // stateAttribute: 'data-motion-state',
35
+ // motionAttribute: 'data-motion',
36
36
 
37
- });
37
+ })
@@ -5,6 +5,7 @@ require "motion/errors"
5
5
 
6
6
  module Motion
7
7
  autoload :ActionCableExtentions, "motion/action_cable_extentions"
8
+ autoload :Callback, "motion/callback"
8
9
  autoload :Channel, "motion/channel"
9
10
  autoload :Component, "motion/component"
10
11
  autoload :ComponentConnection, "motion/component_connection"
@@ -18,41 +19,43 @@ module Motion
18
19
  autoload :Serializer, "motion/serializer"
19
20
  autoload :TestHelpers, "motion/test_helpers"
20
21
 
21
- def self.configure(&block)
22
- raise AlreadyConfiguredError if @config
22
+ class << self
23
+ def configure(&block)
24
+ raise AlreadyConfiguredError if @config
23
25
 
24
- @config = Configuration.new(&block)
25
- end
26
+ @config = Configuration.new(&block)
27
+ end
26
28
 
27
- def self.config
28
- @config ||= Configuration.default
29
- end
29
+ def config
30
+ @config ||= Configuration.default
31
+ end
30
32
 
31
- singleton_class.alias_method :configuration, :config
33
+ alias_method :configuration, :config
32
34
 
33
- def self.serializer
34
- @serializer ||= Serializer.new
35
- end
35
+ def serializer
36
+ @serializer ||= Serializer.new
37
+ end
36
38
 
37
- def self.markup_transformer
38
- @markup_transformer ||= MarkupTransformer.new
39
- end
39
+ def markup_transformer
40
+ @markup_transformer ||= MarkupTransformer.new
41
+ end
40
42
 
41
- def self.build_renderer_for(websocket_connection)
42
- config.renderer_for_connection_proc.call(websocket_connection)
43
- end
43
+ def build_renderer_for(websocket_connection)
44
+ config.renderer_for_connection_proc.call(websocket_connection)
45
+ end
44
46
 
45
- def self.notify_error(error, message)
46
- config.error_notification_proc&.call(error, message)
47
- end
47
+ def notify_error(error, message)
48
+ config.error_notification_proc&.call(error, message)
49
+ end
48
50
 
49
- # This method only exists for testing. Changing configuration while Motion is
50
- # in use is not supported. It is only safe to call this method when no
51
- # components are currently mounted.
52
- def self.reset_internal_state_for_testing!(new_configuration = nil)
53
- @config = new_configuration
54
- @serializer = nil
55
- @markup_transformer = nil
51
+ # This method only exists for testing. Changing configuration while Motion
52
+ # is in use is not supported. It is only safe to call this method when no
53
+ # components are currently mounted.
54
+ def reset_internal_state_for_testing!(new_configuration = nil)
55
+ @config = new_configuration
56
+ @serializer = nil
57
+ @markup_transformer = nil
58
+ end
56
59
  end
57
60
  end
58
61
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "motion"
4
+
5
+ module Motion
6
+ class Callback
7
+ attr_reader :broadcast
8
+
9
+ NAMESPACE = "motion:callback"
10
+ private_constant :NAMESPACE
11
+
12
+ def self.broadcast_for(component, method)
13
+ [
14
+ NAMESPACE,
15
+ component.stable_instance_identifier_for_callbacks,
16
+ method
17
+ ].join(":")
18
+ end
19
+
20
+ def initialize(component, method)
21
+ @broadcast = self.class.broadcast_for(component, method)
22
+
23
+ component.stream_from(broadcast, method)
24
+ end
25
+
26
+ def ==(other)
27
+ other.is_a?(Callback) &&
28
+ other.broadcast == broadcast
29
+ end
30
+
31
+ def call(message = nil)
32
+ ActionCable.server.broadcast(broadcast, message)
33
+ end
34
+ end
35
+ end
@@ -65,15 +65,15 @@ module Motion
65
65
  private
66
66
 
67
67
  def synchronize
68
+ component_connection.if_render_required do |component|
69
+ transmit(renderer.render(component))
70
+ end
71
+
68
72
  streaming_from component_connection.broadcasts,
69
73
  to: :process_broadcast
70
74
 
71
75
  periodically_notify component_connection.periodic_timers,
72
76
  via: :process_periodic_timer
73
-
74
- component_connection.if_render_required do |component|
75
- transmit(renderer.render(component))
76
- end
77
77
  end
78
78
 
79
79
  def handle_error(error, context)
@@ -5,6 +5,7 @@ require "active_support/concern"
5
5
  require "motion"
6
6
 
7
7
  require "motion/component/broadcasts"
8
+ require "motion/component/callbacks"
8
9
  require "motion/component/lifecycle"
9
10
  require "motion/component/motions"
10
11
  require "motion/component/periodic_timers"
@@ -15,6 +16,7 @@ module Motion
15
16
  extend ActiveSupport::Concern
16
17
 
17
18
  include Broadcasts
19
+ include Callbacks
18
20
  include Lifecycle
19
21
  include Motions
20
22
  include PeriodicTimers
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
- require "active_support/core_ext/class/attribute"
5
4
  require "active_support/core_ext/object/to_param"
6
5
  require "active_support/core_ext/hash/except"
7
6
 
@@ -12,6 +11,9 @@ module Motion
12
11
  module Broadcasts
13
12
  extend ActiveSupport::Concern
14
13
 
14
+ DEFAULT = {}.freeze
15
+ private_constant :DEFAULT
16
+
15
17
  # Analogous to `module_function` (available on both class and instance)
16
18
  module ModuleFunctions
17
19
  def stream_from(broadcast, handler)
@@ -37,14 +39,6 @@ module Motion
37
39
  end
38
40
  end
39
41
 
40
- included do
41
- class_attribute :_broadcast_handlers,
42
- instance_reader: false,
43
- instance_writer: false,
44
- instance_predicate: false,
45
- default: {}.freeze
46
- end
47
-
48
42
  class_methods do
49
43
  include ModuleFunctions
50
44
 
@@ -56,6 +50,15 @@ module Motion
56
50
  serialize_broadcasting([name, model])
57
51
  end
58
52
 
53
+ attr_writer :_broadcast_handlers
54
+
55
+ def _broadcast_handlers
56
+ return @_broadcast_handlers if defined?(@_broadcast_handlers)
57
+ return superclass._broadcast_handlers if superclass.respond_to?(:_broadcast_handlers)
58
+
59
+ DEFAULT
60
+ end
61
+
59
62
  private
60
63
 
61
64
  # This definition is copied from ActionCable::Channel::Broadcasting
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ require "motion"
6
+
7
+ module Motion
8
+ module Component
9
+ module Callbacks
10
+ def bind(method)
11
+ Callback.new(self, method)
12
+ end
13
+
14
+ def stable_instance_identifier_for_callbacks
15
+ @_stable_instance_identifier_for_callbacks ||= SecureRandom.uuid
16
+ end
17
+ end
18
+ end
19
+ end
@@ -105,8 +105,7 @@ module Motion
105
105
 
106
106
  run_callbacks(:action, &block)
107
107
  ensure
108
- # `@_action_callback_context = nil` would still appear in the state
109
- remove_instance_variable(:@_action_callback_context)
108
+ @_action_callback_context = nil
110
109
  end
111
110
  end
112
111
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
- require "active_support/core_ext/class/attribute"
5
4
  require "active_support/core_ext/hash/except"
6
5
 
7
6
  require "motion"
@@ -11,6 +10,9 @@ module Motion
11
10
  module Motions
12
11
  extend ActiveSupport::Concern
13
12
 
13
+ DEFAULT = {}.freeze
14
+ private_constant :DEFAULT
15
+
14
16
  # Analogous to `module_function` (available on both class and instance)
15
17
  module ModuleFunctions
16
18
  def map_motion(motion, handler = motion)
@@ -28,16 +30,17 @@ module Motion
28
30
  end
29
31
  end
30
32
 
31
- included do
32
- class_attribute :_motion_handlers,
33
- instance_reader: false,
34
- instance_writer: false,
35
- instance_predicate: false,
36
- default: {}.freeze
37
- end
38
-
39
33
  class_methods do
40
34
  include ModuleFunctions
35
+
36
+ attr_writer :_motion_handlers
37
+
38
+ def _motion_handlers
39
+ return @_motion_handlers if defined?(@_motion_handlers)
40
+ return superclass._motion_handlers if superclass.respond_to?(:_motion_handlers)
41
+
42
+ DEFAULT
43
+ end
41
44
  end
42
45
 
43
46
  include ModuleFunctions
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
- require "active_support/core_ext/class/attribute"
5
4
  require "active_support/core_ext/hash/except"
6
5
 
7
6
  require "motion"
@@ -11,6 +10,9 @@ module Motion
11
10
  module PeriodicTimers
12
11
  extend ActiveSupport::Concern
13
12
 
13
+ DEFAULT = {}.freeze
14
+ private_constant :DEFAULT
15
+
14
16
  # Analogous to `module_function` (available on both class and instance)
15
17
  module ModuleFunctions
16
18
  def every(interval, handler, name: handler)
@@ -32,16 +34,17 @@ module Motion
32
34
  end
33
35
  end
34
36
 
35
- included do
36
- class_attribute :_periodic_timers,
37
- instance_reader: false,
38
- instance_writer: false,
39
- instance_predicate: false,
40
- default: {}.freeze
41
- end
42
-
43
37
  class_methods do
44
38
  include ModuleFunctions
39
+
40
+ attr_writer :_periodic_timers
41
+
42
+ def _periodic_timers
43
+ return @_periodic_timers if defined?(@_periodic_timers)
44
+ return superclass._periodic_timers if superclass.respond_to?(:_periodic_timers)
45
+
46
+ DEFAULT
47
+ end
45
48
  end
46
49
 
47
50
  include ModuleFunctions
@@ -5,18 +5,37 @@ require "motion"
5
5
  module Motion
6
6
  module Component
7
7
  module Rendering
8
- # Use the presence/absence of the ivar instead of true/false to avoid
9
- # extra serialized state (Note that in this scheme, the presence of
10
- # the ivar will never be serialized).
11
- RERENDER_MARKER_IVAR = :@__awaiting_forced_rerender__
12
- private_constant :RERENDER_MARKER_IVAR
8
+ STATE_EXCLUDED_IVARS = %i[
9
+ @_action_callback_context
10
+ @_awaiting_forced_rerender
11
+ @_routes
12
+
13
+ @view_context
14
+ @lookup_context
15
+ @view_renderer
16
+ @view_flow
17
+ @virtual_path
18
+ @variant
19
+ @current_template
20
+ @output_buffer
21
+
22
+ @helpers
23
+ @controller
24
+ @request
25
+ @tag_builder
26
+
27
+ @asset_resolver_strategies
28
+ @assets_environment
29
+ ].freeze
30
+
31
+ private_constant :STATE_EXCLUDED_IVARS
13
32
 
14
33
  def rerender!
15
- instance_variable_set(RERENDER_MARKER_IVAR, true)
34
+ @_awaiting_forced_rerender = true
16
35
  end
17
36
 
18
37
  def awaiting_forced_rerender?
19
- instance_variable_defined?(RERENDER_MARKER_IVAR)
38
+ @_awaiting_forced_rerender
20
39
  end
21
40
 
22
41
  # * This can be overwritten.
@@ -34,7 +53,7 @@ module Motion
34
53
  _run_action_callbacks(context: :render) {
35
54
  _clear_awaiting_forced_rerender!
36
55
 
37
- view_context.capture { _without_new_instance_variables { super } }
56
+ view_context.capture { super }
38
57
  }
39
58
 
40
59
  raise RenderAborted, self if html == false
@@ -45,18 +64,19 @@ module Motion
45
64
  private
46
65
 
47
66
  def _clear_awaiting_forced_rerender!
48
- return unless awaiting_forced_rerender?
49
-
50
- remove_instance_variable(RERENDER_MARKER_IVAR)
67
+ @_awaiting_forced_rerender = false
51
68
  end
52
69
 
53
- def _without_new_instance_variables
54
- existing_instance_variables = instance_variables
70
+ def marshal_dump
71
+ (instance_variables - STATE_EXCLUDED_IVARS)
72
+ .map { |ivar| [ivar, instance_variable_get(ivar)] }
73
+ .to_h
74
+ end
55
75
 
56
- yield
57
- ensure
58
- (instance_variables - existing_instance_variables)
59
- .each(&method(:remove_instance_variable))
76
+ def marshal_load(instance_variables)
77
+ instance_variables.each do |ivar, value|
78
+ instance_variable_set(ivar, value)
79
+ end
60
80
  end
61
81
  end
62
82
  end
@@ -41,7 +41,7 @@ module Motion
41
41
  end
42
42
 
43
43
  def process_motion(motion, event = nil)
44
- timing("Proccessed #{motion}") do
44
+ timing("Processed #{motion}") do
45
45
  component.process_motion(motion, event)
46
46
  end
47
47
 
@@ -53,7 +53,7 @@ module Motion
53
53
  end
54
54
 
55
55
  def process_broadcast(broadcast, message)
56
- timing("Proccessed broadcast to #{broadcast}") do
56
+ timing("Processed broadcast to #{broadcast}") do
57
57
  component.process_broadcast broadcast, message
58
58
  end
59
59
 
@@ -65,7 +65,7 @@ module Motion
65
65
  end
66
66
 
67
67
  def process_periodic_timer(timer)
68
- timing("Proccessed periodic timer #{timer}") do
68
+ timing("Processed periodic timer #{timer}") do
69
69
  component.process_periodic_timer timer
70
70
  end
71
71
 
@@ -18,7 +18,7 @@ module Motion
18
18
  raw["type"]
19
19
  end
20
20
 
21
- alias name type
21
+ alias_method :name, :type
22
22
 
23
23
  def details
24
24
  raw.fetch("details", {})
@@ -34,14 +34,12 @@ 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)
37
+ def element
38
+ return @element if defined?(@element)
39
39
 
40
- @current_target = Motion::Element.from_raw(raw["currentTarget"])
40
+ @element = Motion::Element.from_raw(raw["element"])
41
41
  end
42
42
 
43
- alias element current_target
44
-
45
43
  def form_data
46
44
  element&.form_data
47
45
  end
@@ -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"
@@ -37,7 +38,7 @@ module Motion
37
38
  end
38
39
 
39
40
  def serialize(component)
40
- state = dump(component)
41
+ state = deflate(dump(component))
41
42
  state_with_revision = "#{revision}#{NULL_BYTE}#{state}"
42
43
 
43
44
  [
@@ -49,7 +50,7 @@ module Motion
49
50
  def deserialize(serialized_component)
50
51
  state_with_revision = decrypt_and_verify(serialized_component)
51
52
  serialized_revision, state = state_with_revision.split(NULL_BYTE, 2)
52
- component = load(state)
53
+ component = load(inflate(state))
53
54
 
54
55
  if revision == serialized_revision
55
56
  component
@@ -70,6 +71,14 @@ module Motion
70
71
  Marshal.load(state)
71
72
  end
72
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
+
73
82
  def encrypt_and_sign(cleartext)
74
83
  encryptor.encrypt_and_sign(cleartext)
75
84
  end
@@ -16,14 +16,18 @@ module Motion
16
16
  component.motions.include?(motion_name.to_s)
17
17
  end
18
18
 
19
- def run_motion(component, motion_name)
19
+ def run_motion(component, motion_name, event = motion_event)
20
20
  if block_given?
21
21
  c = component.dup
22
- c.process_motion(motion_name.to_s)
22
+ c.process_motion(motion_name.to_s, event)
23
23
  yield c
24
24
  else
25
- component.process_motion(motion_name.to_s)
25
+ component.process_motion(motion_name.to_s, event)
26
26
  end
27
27
  end
28
+
29
+ def motion_event(attributes = {})
30
+ Motion::Event.new(ActiveSupport::JSON.decode(attributes.to_json))
31
+ end
28
32
  end
29
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motion
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.4"
5
5
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alec Larsen
8
8
  - Drew Ulmer
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-07-12 00:00:00.000000000 Z
12
+ date: 2020-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -31,14 +31,28 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '5.2'
34
+ version: '5.1'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '5.2'
41
+ version: '5.1'
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.
@@ -59,9 +73,11 @@ files:
59
73
  - lib/motion/action_cable_extentions/declarative_streams.rb
60
74
  - lib/motion/action_cable_extentions/log_suppression.rb
61
75
  - lib/motion/action_cable_extentions/synchronization.rb
76
+ - lib/motion/callback.rb
62
77
  - lib/motion/channel.rb
63
78
  - lib/motion/component.rb
64
79
  - lib/motion/component/broadcasts.rb
80
+ - lib/motion/component/callbacks.rb
65
81
  - lib/motion/component/lifecycle.rb
66
82
  - lib/motion/component/motions.rb
67
83
  - lib/motion/component/periodic_timers.rb
@@ -86,7 +102,7 @@ metadata:
86
102
  source_code_uri: https://github.com/unabridged/motion
87
103
  post_install_message: |
88
104
  Friendly reminder: When updating the motion gem, don't forget to update the
89
- NPM package as well (`bin/yarn add '@unabridged/motion@0.3.0'`).
105
+ NPM package as well (`bin/yarn add '@unabridged/motion@0.4.4'`).
90
106
  rdoc_options: []
91
107
  require_paths:
92
108
  - lib
@@ -94,15 +110,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
110
  requirements:
95
111
  - - ">="
96
112
  - !ruby/object:Gem::Version
97
- version: 2.3.0
113
+ version: 2.5.0
98
114
  required_rubygems_version: !ruby/object:Gem::Requirement
99
115
  requirements:
100
116
  - - ">="
101
117
  - !ruby/object:Gem::Version
102
118
  version: '0'
103
119
  requirements: []
104
- rubygems_version: 3.0.3
105
- signing_key:
120
+ rubygems_version: 3.1.2
121
+ signing_key:
106
122
  specification_version: 4
107
123
  summary: Reactive frontend UI components for Rails in pure Ruby.
108
124
  test_files: []