cubism 0.1.0.pre3 → 0.1.0.pre4

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: 552b9be661b93fdddc9ded1d3b95316ef341fc750ea9950edf6d3d5e84f7ae46
4
- data.tar.gz: 58e4f47f505d94f1f23294699208746cb344ea36b1858aef4596a06396f71838
3
+ metadata.gz: af6a4e98fdf0f80e86235cd109e063af6535651ab112bb6b50329eda64eae2ef
4
+ data.tar.gz: 85dd646220a2d3b040ac409489e52462f969fbd935d66abe234f1eeebf51828c
5
5
  SHA512:
6
- metadata.gz: d1c8117ba4c9c31db45a293678ac38f8427c392cde8e7ac7cc2c46eb5a7801195917ac78db78e2e0a528d6059fcc15c6db5df415edb0d671a4d12b0bc3230935
7
- data.tar.gz: 682f88933c469912f8f36b3188942d4a88a4aa78229194c5936116edc285e846dc1a3ad3db829efb1b3654343f0ea9168751899c299d1d2773cd4efc7e3c01d3
6
+ metadata.gz: c8eb831972225222b251825d9accccbebdb09aaf9298c963a0355501be3a94ecd6d26be1af60dd78bc74ee279da83cf24aa612a73cb90a33b07ade5e64e00619
7
+ data.tar.gz: 18e35469a81269c4b23e17fb141543594fb44d0195c8e48650d7c895c82ed0c2dfa4ad65ddab6a06a194b2ddc8c252de64c4fdb5403b940acb925d72d044f758
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Cubism
2
+ <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3
+ [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
4
+ <!-- ALL-CONTRIBUTORS-BADGE:END -->
2
5
  [![Twitter follow](https://img.shields.io/twitter/follow/julian_rubisch?style=social)](https://twitter.com/julian_rubisch)
3
6
 
4
7
  Lightweight Resource-Based Presence Solution with CableReady
@@ -8,7 +11,8 @@ Lightweight Resource-Based Presence Solution with CableReady
8
11
  - [Table of Contents](#table-of-contents)
9
12
  - [Usage](#usage)
10
13
  - [Installation](#installation)
11
- - [Manual Installation](#manual-installation)
14
+ - [API](#api)
15
+ - [Gotchas](#gotchas)
12
16
  - [Contributing](#contributing)
13
17
  - [License](#license)
14
18
  - [Contributors](#contributors)
@@ -16,12 +20,11 @@ Lightweight Resource-Based Presence Solution with CableReady
16
20
  ## Usage
17
21
 
18
22
  ### Prepare your User Model
19
- In your app's `User` model, include `Cubism::User` and set up a safelist of exposed attributes:
23
+ In your app's `User` model, include `Cubism::User`:
20
24
 
21
25
  ```rb
22
26
  class User < ApplicationRecord
23
27
  include Cubism::User
24
- self.cubicle_attributes = %i[email id]
25
28
 
26
29
  # ...
27
30
  end
@@ -33,6 +36,8 @@ In the models you'd like to track presence for, include the `Cubism::Presence` c
33
36
  ```rb
34
37
  class Project < ApplicationRecord
35
38
  include Cubism::Presence
39
+
40
+ # ...
36
41
  end
37
42
  ```
38
43
 
@@ -41,18 +46,14 @@ end
41
46
  Using the `cubicle_for` helper, you can set up a presence indicator. It will
42
47
 
43
48
  1. subscribe to the respective resource, and
44
- 2. accept a "template" using the attributes safelisted above. Elements marked with `data-cubicle-attribute=` will have their `innerHTML` replaced by Cubism.
49
+ 2. render a block which is passed the list of present `users`:
45
50
 
46
51
  ```erb
47
- <%= cubicle_for @project, current_user do %>
48
- <span class="avatar">
49
- <span data-cubicle-attribute="email"></span>
50
- </span>
52
+ <%= cubicle_for @project, current_user do |users| %>
53
+ <%= users.map(&:username).join(", ")
51
54
  <% end %>
52
55
  ```
53
56
 
54
- Note that this template will simply be repeated for every user that's in the `@project`s `present_users` set.
55
-
56
57
  ## Installation
57
58
  Add this line to your application's Gemfile:
58
59
 
@@ -86,6 +87,39 @@ import "@minthesize/cubism";
86
87
  CableReady.initialize({ consumer });
87
88
  ```
88
89
 
90
+ ## API
91
+
92
+ The `cubicle_for` helper accepts the following options as keyword arguments:
93
+
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.
95
+ - `appear_trigger`: a JavaScript event name (e.g. `:focus`) to use. The default is `:connect`, i.e. register a user as "appeared"/"present" when the element connects to the DOM.
96
+ - `disappear_trigger`: a JavaScript event name (e.g. `:blur`) to use. The default is `:disconnect`, i.e. remove a user form the present users list when the element disconnects from the DOM.
97
+ - `trigger_root`: a CSS selector to attach the appear/disappear events to. Defaults to the `cubicle-element` itself.
98
+ - `html_options` are passed to the TagBuilder.
99
+
100
+ ## Gotchas
101
+
102
+ ### Usage with ViewComponent
103
+
104
+ Currently there's a bug in VC resulting in the `capture` helper not working correctly (https://github.com/github/view_component/pull/974). The current workaround is to assign a slot in your component and render the presence list from outside:
105
+
106
+ ```rb
107
+ class MyComponent < ViewComponent::Base
108
+ renders_one :presence_list
109
+
110
+ # ...
111
+ end
112
+ ```
113
+
114
+ ```erb
115
+ <%= render MyComponent.new do |c| %>
116
+ <% c.presence_list do %>
117
+ <%= cubicle_for @project, current_user do |users| %>
118
+ ...
119
+ <% end %>
120
+ <% end %>
121
+ <% end %>
122
+ ```
89
123
 
90
124
  ## Contributing
91
125
 
@@ -130,3 +164,17 @@ yarn install --force
130
164
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
131
165
 
132
166
  ## Contributors
167
+
168
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
169
+ <!-- prettier-ignore-start -->
170
+ <!-- markdownlint-disable -->
171
+ <table>
172
+ <tr>
173
+ <td align="center"><a href="http://www.minthesize.com"><img src="https://avatars.githubusercontent.com/u/4352208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Julian Rubisch</b></sub></a><br /><a href="https://github.com/julianrubisch/cubism/commits?author=julianrubisch" title="Code">💻</a></td>
174
+ </tr>
175
+ </table>
176
+
177
+ <!-- markdownlint-restore -->
178
+ <!-- prettier-ignore-end -->
179
+
180
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
@@ -4,7 +4,8 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
4
4
  def subscribed
5
5
  if resource.present?
6
6
  stream_for resource
7
- resource.present_users.add(user.id)
7
+ resource.cubicle_element_ids << element_id
8
+ resource.excluded_user_id_for_element_id[element_id] = user.id if exclude_current_user?
8
9
  else
9
10
  reject
10
11
  end
@@ -13,6 +14,16 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
13
14
  def unsubscribed
14
15
  return unless resource.present?
15
16
 
17
+ resource.cubicle_element_ids.remove(element_id)
18
+ resource.excluded_user_id_for_element_id.delete(element_id)
19
+ disappear
20
+ end
21
+
22
+ def appear
23
+ resource.present_users.add(user.id)
24
+ end
25
+
26
+ def disappear
16
27
  resource.present_users.remove(user.id)
17
28
  end
18
29
 
@@ -26,4 +37,16 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
26
37
  def user
27
38
  GlobalID::Locator.locate_signed(params[:user])
28
39
  end
40
+
41
+ def exclude_current_user?
42
+ params[:exclude_current_user]
43
+ end
44
+
45
+ def element_id
46
+ params[:element_id]
47
+ end
48
+
49
+ def url
50
+ params[:url]
51
+ end
29
52
  end
@@ -4,7 +4,7 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
4
4
  def subscribed
5
5
  if resource.present?
6
6
  stream_for resource
7
- resource.present_users.add(current_user.id)
7
+ resource.present_users.add(user.id)
8
8
  else
9
9
  reject
10
10
  end
@@ -13,7 +13,7 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
13
13
  def unsubscribed
14
14
  return unless resource.present?
15
15
 
16
- resource.present_users.remove(current_user.id)
16
+ resource.present_users.remove(user.id)
17
17
  end
18
18
 
19
19
  private
@@ -22,4 +22,8 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
22
22
  locator = verified_stream_identifier(params[:identifier])
23
23
  GlobalID::Locator.locate(locator)
24
24
  end
25
+
26
+ def user
27
+ GlobalID::Locator.locate_signed(params[:user])
28
+ end
25
29
  end
@@ -1,11 +1,20 @@
1
1
  module CubismHelper
2
2
  include CableReady::StreamIdentifier
3
3
 
4
- def cubicle_for(resource, user, html_options: {}, &block)
5
- template = capture(&block)
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)
6
7
 
7
- tag.cubicle_element({identifier: signed_stream_identifier(resource.to_global_id.to_s), user: user.to_sgid.to_s}) do
8
- content_tag(:template, template, {slot: "template"})
9
- end
8
+ Cubism.store[digested_id] = Cubism::BlockStoreItem.new(context: self, block: block.dup)
9
+ tag.cubicle_element(
10
+ identifier: signed_stream_identifier(resource.to_gid.to_s),
11
+ user: user.to_sgid.to_s,
12
+ "appear-trigger": appear_trigger,
13
+ "disappear-trigger": disappear_trigger,
14
+ "trigger-root": trigger_root,
15
+ id: "cubicle-#{digested_id}",
16
+ "exclude-current-user": exclude_current_user,
17
+ **html_options
18
+ )
10
19
  end
11
20
  end
@@ -1,10 +1,23 @@
1
1
  module CubismHelper
2
2
  include CableReady::StreamIdentifier
3
3
 
4
- def cubicle_for(resource, html_options: {}, &block)
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
+ verifiable_id = CableReady.signed_stream_verifier.generate(key)
7
+
8
+ Cubism.store[verifiable_id] = Cubism::BlockStoreItem.new(context: self, block: block.dup)
5
9
  template = capture(&block)
6
10
 
7
- tag.cubicle_element({identifier: signed_stream_identifier(resource.to_global_id.to_s)}) do
11
+ tag.cubicle_element(
12
+ identifier: signed_stream_identifier(resource.to_gid.to_s),
13
+ user: user.to_sgid.to_s,
14
+ "appear-trigger": appear_trigger,
15
+ "disappear-trigger": disappear_trigger,
16
+ "trigger-root": trigger_root,
17
+ id: "cubicle-#{verifiable_id}",
18
+ "exclude-current-user": exclude_current_user,
19
+ **html_options
20
+ ) do
8
21
  content_tag(:template, template, {slot: "template"})
9
22
  end
10
23
  end
@@ -2,10 +2,12 @@ module Cubism::Presence
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- kredis_set :present_users, after_change: :stream_presence_later
5
+ kredis_set :present_users, after_change: :stream_presence
6
+ kredis_set :cubicle_element_ids
7
+ kredis_hash :excluded_user_id_for_element_id
6
8
  end
7
9
 
8
- def stream_presence_later
9
- Cubism::StreamPresenceJob.perform_later(resource: self)
10
+ def stream_presence
11
+ Cubism::Broadcaster.new(resource: self).broadcast
10
12
  end
11
13
  end
@@ -3,9 +3,5 @@ module Cubism::User
3
3
 
4
4
  included do
5
5
  Cubism.user_class = self
6
-
7
- class_eval do
8
- cattr_accessor :cubicle_attributes
9
- end
10
6
  end
11
7
  end
@@ -0,0 +1,11 @@
1
+ module Cubism::User
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ Cubism.user_class = self
6
+
7
+ class_eval do
8
+ cattr_accessor :cubicle_attributes
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Cubism
2
+ class Current < ActiveSupport::CurrentAttributes
3
+ attribute :user
4
+ end
5
+ end
File without changes
@@ -0,0 +1,37 @@
1
+ require "cable_ready"
2
+
3
+ module Cubism
4
+ class Broadcaster
5
+ include CableReady::Broadcaster
6
+ include CableReady::StreamIdentifier
7
+
8
+ attr_reader :resource
9
+
10
+ def initialize(resource:)
11
+ @resource = resource
12
+ end
13
+
14
+ def broadcast
15
+ resource.cubicle_element_ids.to_a.each do |element_id|
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(users_for(resource, element_id), &block)
20
+
21
+ cable_ready[Cubism::PresenceChannel].inner_html(
22
+ selector: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
23
+ html: html
24
+ ).broadcast_to(resource)
25
+ end
26
+ 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
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ module Cubism
2
+ class Broadcaster
3
+ include CableReady::Broadcaster
4
+ include CableReady::StreamIdentifier
5
+
6
+ attr_reader :resource
7
+
8
+ def initialize(resource:)
9
+ @resource = resource
10
+ end
11
+
12
+ def broadcast
13
+ resource.cubicle_element_ids.to_a.each do |element_id|
14
+ /cubicle-(?<block_key>.+)/ =~ element_id
15
+ block = Cubism.store[block_key].block
16
+ view_context = Cubism.store[block_key].context
17
+ html = view_context.capture(users_for(resource, element_id), &block)
18
+
19
+ cable_ready[Cubism::PresenceChannel].inner_html(
20
+ selector: "cubicle-element##{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
21
+ html: html
22
+ ).broadcast_to(resource)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def users_for(resource, element_id)
29
+ users = Cubism.user_class.find(resource.present_users.members)
30
+ users.reject! { |user| user.id == resource.excluded_user_id_for_element_id[element_id].to_i }
31
+
32
+ users
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Cubism
2
+ class CubicleBlockStore
3
+ include Singleton
4
+
5
+ def initialize
6
+ @blocks = {}
7
+ end
8
+
9
+ def [](key)
10
+ @blocks[key]
11
+ end
12
+
13
+ def []=(key, value)
14
+ mutex.synchronize do
15
+ @blocks[key] = value
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def mutex
22
+ @mutex ||= Mutex.new
23
+ end
24
+ end
25
+
26
+ BlockStoreItem = Struct.new(:context, :block, keyword_init: true)
27
+ end
File without changes
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre3"
2
+ VERSION = "0.1.0.pre4"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module Cubism
2
- VERSION = "0.1.0.pre2"
2
+ VERSION = "0.1.0.pre3"
3
3
  end
data/lib/cubism.rb CHANGED
@@ -2,7 +2,15 @@ require "kredis"
2
2
 
3
3
  require "cubism/version"
4
4
  require "cubism/engine"
5
+ require "cubism/broadcaster"
6
+ require "cubism/cubicle_block_store"
5
7
 
6
8
  module Cubism
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :Broadcaster, "cubism/broadcaster"
12
+
7
13
  mattr_accessor :user_class, instance_writer: false, instance_reader: false
14
+
15
+ mattr_reader :store, instance_reader: false, default: Cubism::CubicleBlockStore.instance
8
16
  end
data/lib/cubism.rb~ CHANGED
@@ -1,3 +1,5 @@
1
+ require "kredis"
2
+
1
3
  require "cubism/version"
2
4
  require "cubism/engine"
3
5
 
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.pre3
4
+ version: 0.1.0.pre4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Rubisch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -44,14 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 5.0.0.pre6
47
+ version: 5.0.0.pre8
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 5.0.0.pre6
54
+ version: 5.0.0.pre8
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: standard
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -81,8 +95,6 @@ files:
81
95
  - app/channels/cubism/presence_channel.rb~
82
96
  - app/helpers/cubism_helper.rb
83
97
  - app/helpers/cubism_helper.rb~
84
- - app/jobs/cubism/stream_presence_job.rb
85
- - app/jobs/cubism/stream_presence_job.rb~
86
98
  - app/models/concerns/cubism/base.rb~
87
99
  - app/models/concerns/cubism/presence.rb
88
100
  - app/models/concerns/cubism/presence.rb~
@@ -90,9 +102,15 @@ files:
90
102
  - app/models/concerns/cubism/user.rb~
91
103
  - app/models/cubism/base.rb
92
104
  - app/models/cubism/base.rb~
105
+ - app/models/cubism/current.rb
106
+ - app/models/cubism/current.rb~
93
107
  - config/routes.rb
94
108
  - lib/cubism.rb
95
109
  - lib/cubism.rb~
110
+ - lib/cubism/broadcaster.rb
111
+ - lib/cubism/broadcaster.rb~
112
+ - lib/cubism/cubicle_block_store.rb
113
+ - lib/cubism/cubicle_block_store.rb~
96
114
  - lib/cubism/engine.rb
97
115
  - lib/cubism/engine.rb~
98
116
  - lib/cubism/railtie.rb~
@@ -1,15 +0,0 @@
1
- class Cubism::StreamPresenceJob < ApplicationJob
2
- include CableReady::Broadcaster
3
- include CableReady::StreamIdentifier
4
- queue_as :default
5
-
6
- def perform(resource:)
7
- cable_ready[Cubism::PresenceChannel].dispatch_event(
8
- name: "cubism:update",
9
- selector: "cubicle-element[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']",
10
- detail: {
11
- users: Cubism.user_class.find(resource.present_users.members).map { |user| user.slice(user.cubicle_attributes) }.as_json
12
- }
13
- ).broadcast_to(resource)
14
- end
15
- end
@@ -1,11 +0,0 @@
1
- class Cubism::StreamPresenceJob < ApplicationJob
2
- include CableReady::Broadcaster
3
- queue_as :default
4
-
5
- def perform(resource:)
6
- cable_ready[Cubism::PresenceChannel].outer_html(
7
- selector: dom_id(resource, "cubicle").to_s,
8
- html: ApplicationController.render(partial: "shared/presence_indicator", locals: {users: User.where(id: resource.present_users.members)})
9
- ).broadcast
10
- end
11
- end