motion 0.3.0 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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: []