cubism 0.1.0.pre9 → 0.1.0.pre10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
-
|
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
|