cubism 0.1.0.pre9 → 0.1.0.pre10

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: 579dec94521951d33b64eb0c211a210fe86cc89f927a79489cd05258593e3634
4
- data.tar.gz: 21d48a1e4c42ad33e27e11c1128930d22c2103120307a8bd0351d5321d520f9a
3
+ metadata.gz: 976cb8cfb5c2fe6edb6dd6e541c74361ae17dd12ce717bafadcb6ff6bdd1b9f1
4
+ data.tar.gz: a3e777efd1df0423ab4754ed30fe119b6497197ec2bc8ee564304385b907849d
5
5
  SHA512:
6
- metadata.gz: 57c779b3efd7deae25de93339ab19d4a673e2bfae83e3f0d02e62741780b8320d4f1e1dd1c2a4c07bfec3a42c417eb5c59e5fbdfddd43a586c28ed9f945e546a
7
- data.tar.gz: 999b2cae4e2d0c506fd08a02dad104c7e66f50f863fd9f5a58fa600e4fa2d0409ad81d88819905541423b68fccb9d80589a03dafc75c6c6a510aed2966f164fb
6
+ metadata.gz: 163db944110832cefb97406ebc7c4c98b231fdf98d9cce25abe164469eb64d9930d5aa1689c53426605dd11e42cbb721e314430a7b1ec1b7acc37449a676f702
7
+ data.tar.gz: b1c4052e437c7ebe723615e4b3316f1ac5d538aa2008b87957aca8762cd04bf61aed0f930aac42256aa07340d1c05652611fdcd47cad2f4b2e9d870393999e18
data/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
5
5
  [![Twitter follow](https://img.shields.io/twitter/follow/julian_rubisch?style=social)](https://twitter.com/julian_rubisch)
6
6
 
7
- Lightweight Resource-Based Presence Solution with CableReady
7
+ Lightweight Resource-Based Presence Solution with CableReady.
8
+
9
+ `Cubism` provides real-time updates of who is viewing or interacting with whatever resources you need. Whether you want Slack's "X is typing..." indicator or an e-commerce "5 other customers are viewing this item" notice, `Cubism` gives you everything you need "under the hood" so that you can focus on what really matters—end-user functionality.
8
10
 
9
11
  ## Table of Contents
10
12
 
@@ -12,6 +14,7 @@ Lightweight Resource-Based Presence Solution with CableReady
12
14
  - [Usage](#usage)
13
15
  - [Installation](#installation)
14
16
  - [API](#api)
17
+ - [Limitations](#limitations)
15
18
  - [Gotchas](#gotchas)
16
19
  - [Contributing](#contributing)
17
20
  - [License](#license)
@@ -54,6 +57,8 @@ Using the `cubicle_for` helper, you can set up a presence indicator. It will
54
57
  <% end %>
55
58
  ```
56
59
 
60
+ **Important!** due to technical limitations the cubism block does _not_ act as a closure, i.e. it has _only_ access to the `users` variable passed to it - think of it more as a self-contained component.
61
+
57
62
  ## Installation
58
63
  Add this line to your application's Gemfile:
59
64
 
@@ -91,12 +96,17 @@ CableReady.initialize({ consumer });
91
96
 
92
97
  The `cubicle_for` helper accepts the following options as keyword arguments:
93
98
 
94
- - `exclude_current_user (true|false)`: Whether or not to exclude the current user from the list of present users broadcasted to the view. Useful e.g. for "typing..." indicators.
99
+ - `exclude_current_user (true|false)`: Whether or not to exclude the current user from the list of present users broadcasted to the view. Useful e.g. for "typing..." indicators (default: `true`).
95
100
  - `appear_trigger`: JavaScript event names (e.g. `["focus", "debounced:input]`) to use. (Can also be a singular string, which will be converted to an array). The default is `:connect`, i.e. register a user as "appeared"/"present" when the element connects to the DOM.
96
101
  - `disappear_trigger`: a JavaScript event name (e.g. `:blur`) to use. (Can also be a singular string, which will be converted to an array). The default is `:disconnect`, i.e. remove a user form the present users list when the element disconnects from the DOM.
97
102
  - `trigger_root`: a CSS selector to attach the appear/disappear events to. Defaults to the `cubicle-element` itself.
98
103
  - `html_options` are passed to the TagBuilder.
99
104
 
105
+ ## Limitations
106
+
107
+ ### Supported Template Handlers
108
+ - ERB
109
+
100
110
  ## Gotchas
101
111
 
102
112
  ### Usage with ViewComponent
@@ -2,17 +2,34 @@ module CubismHelper
2
2
  include CableReady::StreamIdentifier
3
3
 
4
4
  def cubicle_for(resource, user, html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
5
- key = "#{block.source_location.join(":")}:#{resource.to_gid}:#{user.to_gid}"
6
- digested_id = ActiveSupport::Digest.hexdigest(key)
5
+ filename, lineno = block.source_location
6
+ block_location = block.source_location.join(":")
7
+ resource_user_key = "#{resource.to_gid}:#{user.to_gid}"
8
+ digested_block_key = ActiveSupport::Digest.hexdigest("#{block_location}:#{resource_user_key}")
9
+
10
+ # the store item (identified by block location, resource, and user) might already be present
11
+ store_item = Cubism.store[digested_block_key] || Cubism::BlockStoreItem.new(
12
+ block_location: block_location,
13
+ resource_gid: resource.to_gid.to_s,
14
+ user_gid: user.to_gid.to_s
15
+ )
16
+
17
+ if Cubism.store[digested_block_key]&.block_source.blank? && !block_location.start_with?("inline template")
18
+ lines = File.readlines(filename)[lineno - 1..]
19
+
20
+ preprocessor = Cubism::Preprocessor.new(source: lines.join.squish, view_context: self)
21
+ store_item.block_source = preprocessor.process
22
+ end
23
+
24
+ Cubism.store[digested_block_key] = store_item
7
25
 
8
- Cubism.store[digested_id] = Cubism::BlockStoreItem.new(context: self, block: block.dup)
9
26
  tag.cubicle_element(
10
27
  identifier: signed_stream_identifier(resource.to_gid.to_s),
11
28
  user: user.to_sgid.to_s,
12
29
  "appear-trigger": Array(appear_trigger).join(","),
13
30
  "disappear-trigger": disappear_trigger,
14
31
  "trigger-root": trigger_root,
15
- id: "cubicle-#{digested_id}",
32
+ id: "cubicle-#{digested_block_key}",
16
33
  "exclude-current-user": exclude_current_user,
17
34
  **html_options
18
35
  )
@@ -0,0 +1,34 @@
1
+ module CubismHelper
2
+ include CableReady::StreamIdentifier
3
+
4
+ def cubicle_for(resource, user, html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
5
+ filename, lineno = block.source_location
6
+ block_location = block.source_location.join(":")
7
+ resource_user_key = "#{resource.to_gid}:#{user.to_gid}"
8
+ digested_block_key = ActiveSupport::Digest.hexdigest("#{block_location}:#{resource_user_key}")
9
+
10
+ Cubism.store[digested_block_key] = Cubism::BlockStoreItem.new(
11
+ block_location: block_location,
12
+ resource_gid: resource.to_gid.to_s,
13
+ user_gid: user.to_gid.to_s
14
+ )
15
+
16
+ if Cubism.store[block_location].blank? && !block_location.start_with?("inline template")
17
+ lines = File.readlines(filename)[lineno - 1..]
18
+
19
+ preprocessor = Cubism::Preprocessor.new(source: lines.join.squish, view_context: self)
20
+ Cubism.store[block_location] = preprocessor.process
21
+ end
22
+
23
+ tag.cubicle_element(
24
+ identifier: signed_stream_identifier(resource.to_gid.to_s),
25
+ user: user.to_sgid.to_s,
26
+ "appear-trigger": Array(appear_trigger).join(","),
27
+ "disappear-trigger": disappear_trigger,
28
+ "trigger-root": trigger_root,
29
+ id: "cubicle-#{digested_block_key}",
30
+ "exclude-current-user": exclude_current_user,
31
+ **html_options
32
+ )
33
+ end
34
+ end
@@ -14,9 +14,11 @@ module Cubism
14
14
  def broadcast
15
15
  resource.cubicle_element_ids.to_a.each do |element_id|
16
16
  /cubicle-(?<block_key>.+)/ =~ element_id
17
- block = Cubism.store[block_key].block
18
- view_context = Cubism.store[block_key].context
19
- html = view_context.capture(resource.present_users_for_element_id(element_id), &block)
17
+ store_item = Cubism.store[block_key]
18
+
19
+ next if store_item.blank?
20
+
21
+ html = ApplicationController.render(inline: store_item.block_source, locals: {users: resource.present_users_for_element_id(element_id)})
20
22
 
21
23
  cable_ready[element_id].inner_html(
22
24
  selector: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
@@ -1,9 +1,11 @@
1
1
  require "cable_ready"
2
+ require "parser/current"
2
3
 
3
4
  module Cubism
4
5
  class Broadcaster
5
6
  include CableReady::Broadcaster
6
7
  include CableReady::StreamIdentifier
8
+ include ActionView::Helpers
7
9
 
8
10
  attr_reader :resource
9
11
 
@@ -14,24 +16,35 @@ module Cubism
14
16
  def broadcast
15
17
  resource.cubicle_element_ids.to_a.each do |element_id|
16
18
  /cubicle-(?<block_key>.+)/ =~ element_id
17
- block = Cubism.store[block_key].block
18
- view_context = Cubism.store[block_key].context
19
- html = view_context.capture(users_for(resource, element_id), &block)
19
+ block_store_item = Cubism.store[block_key]
20
+
21
+ next if block_store_item.blank?
22
+
23
+ block_source = Cubism.store[block_store_item.block_location]
24
+ erubi = ActionView::Template::Handlers::ERB::Erubi.new(block_source)
25
+ ast = Parser::CurrentRuby.parse erubi.src
26
+
27
+ # html = ApplicationController.render(inline: block_source, locals: {current_user: block_store_item.user, "@project": block_store_item.resource, users: resource.present_users_for_element_id(element_id)})
28
+
29
+ # binding.pry
30
+
31
+ # context = ActionView::Base.with_view_paths(ActionController::Base.view_paths)
32
+ # erubi = ActionView::Template::Handlers::ERB::Erubi.new(File.read(filename), filename: filename)
33
+ # context = ApplicationController.new.view_context
34
+
35
+ # filename, lineno = block_store_item.block_location.split
36
+
37
+ # binding.pry
38
+
39
+ # block = Cubism.store[block_key].block
40
+ # view_context = Cubism.store[block_key].context
41
+ # html = view_context.capture(resource.present_users_for_element_id(element_id), &block)
20
42
 
21
43
  cable_ready[element_id].inner_html(
22
44
  selector: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
23
- html: html
45
+ html: "#{resource.present_users_for_element_id(element_id).map(&:id)}"
24
46
  ).broadcast
25
47
  end
26
48
  end
27
-
28
- private
29
-
30
- def users_for(resource, element_id)
31
- users = Cubism.user_class.find(resource.present_users.members)
32
- users.reject! { |user| user.id == resource.excluded_user_id_for_element_id[element_id].to_i }
33
-
34
- users
35
- end
36
49
  end
37
50
  end
@@ -1,29 +1,32 @@
1
1
  module Cubism
2
2
  class CubicleBlockStore
3
- include Singleton
4
-
5
3
  delegate_missing_to :@blocks
6
4
 
7
5
  def initialize
8
- @blocks = {}
6
+ @blocks = Kredis.hash "cubism-blocks"
9
7
  end
10
8
 
11
9
  def [](key)
12
- @blocks[key]
10
+ Marshal.load(@blocks[key]) if @blocks[key]
13
11
  end
14
12
 
15
13
  def []=(key, value)
16
14
  mutex.synchronize do
17
- @blocks[key] = value
15
+ @blocks[key] = Marshal.dump value
18
16
  end
19
17
  end
20
18
 
21
19
  def clear
22
20
  mutex.synchronize do
23
- @blocks.clear
21
+ # kredis #remove
22
+ @blocks.remove
24
23
  end
25
24
  end
26
25
 
26
+ def size
27
+ @blocks.to_h.size
28
+ end
29
+
27
30
  private
28
31
 
29
32
  def mutex
@@ -31,5 +34,23 @@ module Cubism
31
34
  end
32
35
  end
33
36
 
34
- BlockStoreItem = Struct.new(:context, :block, keyword_init: true)
37
+ BlockStoreItem = Struct.new(:block_location, :block_source, :user_gid, :resource_gid, keyword_init: true) do
38
+ def user
39
+ GlobalID::Locator.locate self[:user_gid]
40
+ end
41
+
42
+ def resource
43
+ GlobalID::Locator.locate self[:resource_gid]
44
+ end
45
+
46
+ def marshal_dump
47
+ to_h
48
+ end
49
+
50
+ def marshal_load(serialized_item)
51
+ %i[block_location block_source user_gid resource_gid].each do |arg|
52
+ send("#{arg}=", serialized_item[arg])
53
+ end
54
+ end
55
+ end
35
56
  end
@@ -2,6 +2,8 @@ module Cubism
2
2
  class CubicleBlockStore
3
3
  include Singleton
4
4
 
5
+ delegate_missing_to :@blocks
6
+
5
7
  def initialize
6
8
  @blocks = {}
7
9
  end
@@ -16,6 +18,12 @@ module Cubism
16
18
  end
17
19
  end
18
20
 
21
+ def clear
22
+ mutex.synchronize do
23
+ @blocks.clear
24
+ end
25
+ end
26
+
19
27
  private
20
28
 
21
29
  def mutex
@@ -23,5 +31,13 @@ module Cubism
23
31
  end
24
32
  end
25
33
 
26
- BlockStoreItem = Struct.new(:context, :block, keyword_init: true)
34
+ BlockStoreItem = Struct.new(:block_location, :user_gid, :resource_gid, keyword_init: true) do
35
+ def user
36
+ GlobalID::Locator.locate self[:user_gid]
37
+ end
38
+
39
+ def resource
40
+ GlobalID::Locator.locate self[:resource_gid]
41
+ end
42
+ end
27
43
  end
data/lib/cubism/engine.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  module Cubism
2
2
  class Engine < ::Rails::Engine
3
+ initializer "cubism.store" do
4
+ Cubism.store = Cubism::CubicleBlockStore.new
5
+ end
3
6
  end
4
7
  end
@@ -0,0 +1,4 @@
1
+ module Cubism
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
File without changes
@@ -0,0 +1,30 @@
1
+ module Cubism
2
+ class Preprocessor
3
+ def initialize(source:, view_context:)
4
+ match_data = /<%=\s+cubicle_for.+\|.+\|\s+%>/.match(source)
5
+ start_pos = match_data&.end(0) || 0
6
+ @source = source[start_pos..]
7
+ @view_context = view_context
8
+ end
9
+
10
+ def process
11
+ begin
12
+ do_parse
13
+ rescue NameError
14
+ # we ignore any name errors from unset instance variables or local assigns here
15
+ end
16
+
17
+ @source
18
+ end
19
+
20
+ private
21
+
22
+ def do_parse
23
+ ActionView::Template::Handlers::ERB::Erubi.new(@source).evaluate(@view_context)
24
+ rescue SyntaxError
25
+ end_at_end = /(<%\s+end\s+%>)\z/.match(@source)
26
+ @source = end_at_end ? @source[..-(end_at_end[0].length + 1)] : @source[..-2]
27
+ do_parse
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module Cubism
2
+ class Preprocessor
3
+ def initialize(source:, view_context:)
4
+ start_pos = /<%= cubicle_for/ =~ source
5
+ @source = source[start_pos..]
6
+ @view_context = view_context
7
+ end
8
+
9
+ def process
10
+ begin
11
+ do_parse
12
+ rescue NameError
13
+ # we ignore any name errors from unset instance variables or local assigns here
14
+ end
15
+
16
+ @source
17
+ end
18
+
19
+ def do_parse
20
+ ActionView::Template::Handlers::ERB::Erubi.new(@source).evaluate(@view_context)
21
+ rescue SyntaxError
22
+ @source = @source[..-2]
23
+ do_parse
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre9"
2
+ VERSION = "0.1.0.pre10"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre8"
2
+ VERSION = "0.1.0.pre9"
3
3
  end
data/lib/cubism.rb CHANGED
@@ -4,13 +4,15 @@ require "cubism/version"
4
4
  require "cubism/engine"
5
5
  require "cubism/broadcaster"
6
6
  require "cubism/cubicle_block_store"
7
+ require "cubism/preprocessor"
7
8
 
8
9
  module Cubism
9
10
  extend ActiveSupport::Autoload
10
11
 
11
12
  autoload :Broadcaster, "cubism/broadcaster"
13
+ autoload :Preprocessor, "cubism/preprocessor"
12
14
 
13
15
  mattr_accessor :user_class, instance_writer: false, instance_reader: false
14
16
 
15
- mattr_reader :store, instance_reader: false, default: Cubism::CubicleBlockStore.instance
17
+ mattr_accessor :store, instance_reader: false
16
18
  end
data/lib/cubism.rb~ CHANGED
@@ -4,11 +4,13 @@ require "cubism/version"
4
4
  require "cubism/engine"
5
5
  require "cubism/broadcaster"
6
6
  require "cubism/cubicle_block_store"
7
+ require "cubism/preprocessor"
7
8
 
8
9
  module Cubism
9
10
  extend ActiveSupport::Autoload
10
11
 
11
12
  autoload :Broadcaster, "cubism/broadcaster"
13
+ autoload :Preprocessor, "cubism/preprocessor"
12
14
 
13
15
  mattr_accessor :user_class, instance_writer: false, instance_reader: false
14
16
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cubism
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre9
4
+ version: 0.1.0.pre10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-09 00:00:00.000000000 Z
11
+ date: 2022-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -135,6 +135,7 @@ files:
135
135
  - app/channels/cubism/presence_channel.rb
136
136
  - app/channels/cubism/presence_channel.rb~
137
137
  - app/helpers/cubism_helper.rb
138
+ - app/helpers/cubism_helper.rb~
138
139
  - app/models/concerns/cubism/presence.rb
139
140
  - app/models/concerns/cubism/presence.rb~
140
141
  - app/models/concerns/cubism/user.rb
@@ -147,6 +148,10 @@ files:
147
148
  - lib/cubism/cubicle_block_store.rb
148
149
  - lib/cubism/cubicle_block_store.rb~
149
150
  - lib/cubism/engine.rb
151
+ - lib/cubism/engine.rb~
152
+ - lib/cubism/parser.rb~
153
+ - lib/cubism/preprocessor.rb
154
+ - lib/cubism/preprocessor.rb~
150
155
  - lib/cubism/version.rb
151
156
  - lib/cubism/version.rb~
152
157
  - lib/tasks/cubism_tasks.rake