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 +4 -4
- data/README.md +12 -2
- data/app/helpers/cubism_helper.rb +21 -4
- data/app/helpers/cubism_helper.rb~ +34 -0
- data/lib/cubism/broadcaster.rb +5 -3
- data/lib/cubism/broadcaster.rb~ +26 -13
- data/lib/cubism/cubicle_block_store.rb +28 -7
- data/lib/cubism/cubicle_block_store.rb~ +17 -1
- data/lib/cubism/engine.rb +3 -0
- data/lib/cubism/engine.rb~ +4 -0
- data/lib/cubism/parser.rb~ +0 -0
- data/lib/cubism/preprocessor.rb +30 -0
- data/lib/cubism/preprocessor.rb~ +26 -0
- data/lib/cubism/version.rb +1 -1
- data/lib/cubism/version.rb~ +1 -1
- data/lib/cubism.rb +3 -1
- data/lib/cubism.rb~ +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 976cb8cfb5c2fe6edb6dd6e541c74361ae17dd12ce717bafadcb6ff6bdd1b9f1
|
4
|
+
data.tar.gz: a3e777efd1df0423ab4754ed30fe119b6497197ec2bc8ee564304385b907849d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](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
|
-
|
6
|
-
|
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-#{
|
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
|
data/lib/cubism/broadcaster.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
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)}']",
|
data/lib/cubism/broadcaster.rb~
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
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:
|
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
|
-
|
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(:
|
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(:
|
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
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
|
data/lib/cubism/version.rb
CHANGED
data/lib/cubism/version.rb~
CHANGED
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
|
-
|
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.
|
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-
|
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
|