cubism 0.1.0.pre10 → 0.1.0.pre13
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/Appraisals +16 -0
- data/Appraisals~ +16 -0
- data/CHANGELOG.md +87 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +220 -0
- data/Gemfile~ +9 -0
- data/README.md +35 -14
- data/README.md~ +191 -0
- data/app/assets/javascripts/cubism.js +141 -0
- data/app/assets/javascripts/cubism.min.js +2 -0
- data/app/assets/javascripts/cubism.min.js.map +1 -0
- data/app/assets/javascripts/cubism.umd.js +149 -0
- data/app/assets/javascripts/cubism.umd.min.js +2 -0
- data/app/assets/javascripts/cubism.umd.min.js.map +1 -0
- data/app/channels/cubism/presence_channel.rb +14 -5
- data/app/helpers/cubism_helper.rb +15 -17
- data/app/models/concerns/cubism/presence.rb +11 -3
- data/bin/rails +14 -0
- data/bin/standardize +4 -0
- data/bin/test +5 -0
- data/cubism.gemspec +36 -0
- data/cubism.gemspec~ +28 -0
- data/lib/cubism/broadcaster.rb +13 -6
- data/lib/cubism/cubicle_store.rb +141 -0
- data/lib/cubism/engine.rb +23 -2
- data/lib/cubism/importmap.rb +2 -0
- data/lib/cubism/preprocessor.rb +4 -1
- data/lib/cubism/version.rb +1 -1
- data/lib/cubism.rb +3 -2
- data/package.json +43 -0
- data/package.json~ +33 -0
- data/rollup.config.js +77 -0
- data/{lib/cubism/parser.rb~ → rollup.config.js~} +0 -0
- data/test/block_container_test.rb +105 -0
- data/test/block_source_test.rb +102 -0
- data/test/broadcaster_test.rb +66 -0
- data/test/channels/cubism/presence_channel_test.rb +72 -0
- data/test/cubicle_store_test.rb +34 -0
- data/test/cubism_test.rb +7 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +7 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/post.rb +3 -0
- data/test/dummy/app/models/user.rb +3 -0
- data/test/dummy/config/application.rb +22 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +76 -0
- data/test/dummy/config/environments/production.rb +120 -0
- data/test/dummy/config/environments/test.rb +59 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/assets.rb +12 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/test/dummy/config/initializers/content_security_policy.rb +28 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/permissions_policy.rb +11 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/puma.rb +43 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/migrate/20220102072929_create_posts.rb +10 -0
- data/test/dummy/db/migrate/20220102073003_create_users.rb +9 -0
- data/test/dummy/db/schema.rb +26 -0
- data/test/dummy/test/models/post_test.rb +7 -0
- data/test/dummy/test/models/user_test.rb +7 -0
- data/test/helpers/cubism_helper_test.rb +39 -0
- data/test/integration/navigation_test.rb +7 -0
- data/test/models/concerns/presence_test.rb +25 -0
- data/test/models/concerns/user_test.rb +9 -0
- data/test/rendering/preprocessor_test.rb +61 -0
- data/test/test_helper.rb +16 -0
- data/yarn.lock +2814 -0
- metadata +118 -18
- data/app/channels/cubism/presence_channel.rb~ +0 -52
- data/app/helpers/cubism_helper.rb~ +0 -34
- data/app/models/concerns/cubism/presence.rb~ +0 -13
- data/config/routes.rb +0 -2
- data/lib/cubism/broadcaster.rb~ +0 -50
- data/lib/cubism/cubicle_block_store.rb +0 -56
- data/lib/cubism/cubicle_block_store.rb~ +0 -43
- data/lib/cubism/engine.rb~ +0 -4
- data/lib/cubism/preprocessor.rb~ +0 -26
- data/lib/cubism/version.rb~ +0 -3
- data/lib/cubism.rb~ +0 -18
- data/lib/tasks/cubism_tasks.rake +0 -4
@@ -0,0 +1,141 @@
|
|
1
|
+
import CableReady, { SubscribingElement } from "cable_ready";
|
2
|
+
|
3
|
+
function debounce(func, timeout) {
|
4
|
+
let timer;
|
5
|
+
return (...args) => {
|
6
|
+
clearTimeout(timer);
|
7
|
+
timer = setTimeout((() => func.apply(this, args)), timeout);
|
8
|
+
};
|
9
|
+
}
|
10
|
+
|
11
|
+
class Cubicle extends SubscribingElement {
|
12
|
+
constructor() {
|
13
|
+
super();
|
14
|
+
const shadowRoot = this.attachShadow({
|
15
|
+
mode: "open"
|
16
|
+
});
|
17
|
+
shadowRoot.innerHTML = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`;
|
18
|
+
this.triggerRoot = this;
|
19
|
+
this.present = false;
|
20
|
+
}
|
21
|
+
async connectedCallback() {
|
22
|
+
if (this.preview) return;
|
23
|
+
this.appear = debounce(this.appear.bind(this), 50);
|
24
|
+
this.appearTriggers = this.getAttribute("appear-trigger") ? this.getAttribute("appear-trigger").split(",") : [];
|
25
|
+
this.disappearTriggers = this.getAttribute("disappear-trigger") ? this.getAttribute("disappear-trigger").split(",") : [];
|
26
|
+
this.triggerRootSelector = this.getAttribute("trigger-root");
|
27
|
+
this.consumer = await CableReady.consumer;
|
28
|
+
this.channel = this.createSubscription();
|
29
|
+
this.appearanceIntersectionObserver = new IntersectionObserver(((entries, observer) => {
|
30
|
+
entries.forEach((entry => {
|
31
|
+
if (entry.target !== this.triggerRoot) return;
|
32
|
+
if (entry.isIntersecting) {
|
33
|
+
if (!this.present) {
|
34
|
+
this.appear();
|
35
|
+
}
|
36
|
+
} else {
|
37
|
+
if (this.present) {
|
38
|
+
this.disappear();
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}));
|
42
|
+
}), {
|
43
|
+
threshold: 0
|
44
|
+
});
|
45
|
+
this.mutationObserver = new MutationObserver(((mutationsList, observer) => {
|
46
|
+
if (this.triggerRootSelector) {
|
47
|
+
for (const mutation of mutationsList) {
|
48
|
+
const root = document.querySelector(this.triggerRootSelector);
|
49
|
+
if (root) {
|
50
|
+
this.uninstall();
|
51
|
+
this.triggerRoot = root;
|
52
|
+
this.install();
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
this.mutationObserver.disconnect();
|
57
|
+
}));
|
58
|
+
this.mutationObserver.observe(document, {
|
59
|
+
subtree: true,
|
60
|
+
childList: true
|
61
|
+
});
|
62
|
+
}
|
63
|
+
disconnectedCallback() {
|
64
|
+
this.disappear();
|
65
|
+
super.disconnectedCallback();
|
66
|
+
}
|
67
|
+
install() {
|
68
|
+
if (this.appearTriggers.includes("connect")) {
|
69
|
+
this.appear();
|
70
|
+
}
|
71
|
+
this.appearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {
|
72
|
+
this.appearanceIntersectionObserver.observe(this.triggerRoot);
|
73
|
+
}));
|
74
|
+
this.appearTriggers.filter((eventName => eventName !== "connect" && eventName !== "intersect")).forEach((eventName => {
|
75
|
+
this.triggerRoot.addEventListener(eventName, this.appear.bind(this));
|
76
|
+
}));
|
77
|
+
this.disappearTriggers.filter((eventName => eventName !== "disconnect" && eventName !== "intersect")).forEach((eventName => {
|
78
|
+
this.triggerRoot.addEventListener(eventName, this.disappear.bind(this));
|
79
|
+
}));
|
80
|
+
this.disappearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {}));
|
81
|
+
}
|
82
|
+
uninstall() {
|
83
|
+
this.appearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {
|
84
|
+
this.appearanceIntersectionObserver.unobserve(this.triggerRoot);
|
85
|
+
}));
|
86
|
+
this.appearTriggers.filter((eventName => eventName !== "connect" && eventName !== "intersect")).forEach((eventName => {
|
87
|
+
this.triggerRoot.removeEventListener(eventName, this.appear.bind(this));
|
88
|
+
}));
|
89
|
+
this.disappearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {}));
|
90
|
+
this.disappearTriggers.filter((eventName => eventName !== "disconnect" && eventName !== "intersect")).forEach((eventName => {
|
91
|
+
this.triggerRoot.removeEventListener(eventName, this.disappear.bind(this));
|
92
|
+
}));
|
93
|
+
}
|
94
|
+
appear() {
|
95
|
+
if (this.channel) {
|
96
|
+
this.present = true;
|
97
|
+
this.channel.perform("appear");
|
98
|
+
}
|
99
|
+
}
|
100
|
+
disappear() {
|
101
|
+
if (this.channel) {
|
102
|
+
this.present = false;
|
103
|
+
this.channel.perform("disappear");
|
104
|
+
}
|
105
|
+
}
|
106
|
+
performOperations(data) {
|
107
|
+
if (data.cableReady) {
|
108
|
+
CableReady.perform(data.operations);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
createSubscription() {
|
112
|
+
if (!this.consumer) {
|
113
|
+
console.error("The `cubicle-element` helper cannot connect without an ActionCable consumer.");
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
return this.consumer.subscriptions.create({
|
117
|
+
channel: this.channelName,
|
118
|
+
identifier: this.getAttribute("identifier"),
|
119
|
+
element_id: this.id,
|
120
|
+
scope: this.getAttribute("scope"),
|
121
|
+
exclude_current_user: this.getAttribute("exclude-current-user") === "true"
|
122
|
+
}, {
|
123
|
+
connected: () => {
|
124
|
+
this.install();
|
125
|
+
},
|
126
|
+
disconnected: () => {
|
127
|
+
this.disappear();
|
128
|
+
this.uninstall();
|
129
|
+
},
|
130
|
+
rejected: () => {
|
131
|
+
this.uninstall();
|
132
|
+
},
|
133
|
+
received: this.performOperations.bind(this)
|
134
|
+
});
|
135
|
+
}
|
136
|
+
get channelName() {
|
137
|
+
return "Cubism::PresenceChannel";
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
customElements.define("cubicle-element", Cubicle);
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import e,{SubscribingElement as t}from"cable_ready";customElements.define("cubicle-element",class extends t{constructor(){super();this.attachShadow({mode:"open"}).innerHTML="\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n",this.triggerRoot=this,this.present=!1}async connectedCallback(){this.preview||(this.appear=function(e,t){let r;return(...i)=>{clearTimeout(r),r=setTimeout((()=>e.apply(this,i)),t)}}(this.appear.bind(this),50),this.appearTriggers=this.getAttribute("appear-trigger")?this.getAttribute("appear-trigger").split(","):[],this.disappearTriggers=this.getAttribute("disappear-trigger")?this.getAttribute("disappear-trigger").split(","):[],this.triggerRootSelector=this.getAttribute("trigger-root"),this.consumer=await e.consumer,this.channel=this.createSubscription(),this.appearanceIntersectionObserver=new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.target===this.triggerRoot&&(e.isIntersecting?this.present||this.appear():this.present&&this.disappear())}))}),{threshold:0}),this.mutationObserver=new MutationObserver(((e,t)=>{if(this.triggerRootSelector)for(const t of e){const e=document.querySelector(this.triggerRootSelector);e&&(this.uninstall(),this.triggerRoot=e,this.install())}this.mutationObserver.disconnect()})),this.mutationObserver.observe(document,{subtree:!0,childList:!0}))}disconnectedCallback(){this.disappear(),super.disconnectedCallback()}install(){this.appearTriggers.includes("connect")&&this.appear(),this.appearTriggers.filter((e=>"intersect"===e)).forEach((()=>{this.appearanceIntersectionObserver.observe(this.triggerRoot)})),this.appearTriggers.filter((e=>"connect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.addEventListener(e,this.appear.bind(this))})),this.disappearTriggers.filter((e=>"disconnect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.addEventListener(e,this.disappear.bind(this))})),this.disappearTriggers.filter((e=>"intersect"===e)).forEach((()=>{}))}uninstall(){this.appearTriggers.filter((e=>"intersect"===e)).forEach((()=>{this.appearanceIntersectionObserver.unobserve(this.triggerRoot)})),this.appearTriggers.filter((e=>"connect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.removeEventListener(e,this.appear.bind(this))})),this.disappearTriggers.filter((e=>"intersect"===e)).forEach((()=>{})),this.disappearTriggers.filter((e=>"disconnect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.removeEventListener(e,this.disappear.bind(this))}))}appear(){this.channel&&(this.present=!0,this.channel.perform("appear"))}disappear(){this.channel&&(this.present=!1,this.channel.perform("disappear"))}performOperations(t){t.cableReady&&e.perform(t.operations)}createSubscription(){if(this.consumer)return this.consumer.subscriptions.create({channel:this.channelName,identifier:this.getAttribute("identifier"),element_id:this.id,scope:this.getAttribute("scope"),exclude_current_user:"true"===this.getAttribute("exclude-current-user")},{connected:()=>{this.install()},disconnected:()=>{this.disappear(),this.uninstall()},rejected:()=>{this.uninstall()},received:this.performOperations.bind(this)});console.error("The `cubicle-element` helper cannot connect without an ActionCable consumer.")}get channelName(){return"Cubism::PresenceChannel"}});
|
2
|
+
//# sourceMappingURL=cubism.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"cubism.min.js","sources":["../../../javascript/elements/index.js","../../../javascript/elements/cubicle.js","../../../node_modules/cable_ready/javascript/utils.js"],"sourcesContent":["/* global customElements */\n\nimport { Cubicle } from './cubicle'\n\nexport * from './cubicle'\n\ncustomElements.define('cubicle-element', Cubicle)\n","/* eslint-disable no-undef */\nimport CableReady, { SubscribingElement } from 'cable_ready'\nimport { debounce } from 'cable_ready/javascript/utils'\n\nexport class Cubicle extends SubscribingElement {\n constructor () {\n super()\n const shadowRoot = this.attachShadow({ mode: 'open' })\n shadowRoot.innerHTML = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`\n\n this.triggerRoot = this\n this.present = false\n }\n\n async connectedCallback () {\n if (this.preview) return\n\n this.appear = debounce(this.appear.bind(this), 50)\n\n this.appearTriggers = this.getAttribute('appear-trigger')\n ? this.getAttribute('appear-trigger').split(',')\n : []\n this.disappearTriggers = this.getAttribute('disappear-trigger')\n ? this.getAttribute('disappear-trigger').split(',')\n : []\n this.triggerRootSelector = this.getAttribute('trigger-root')\n\n this.consumer = await CableReady.consumer\n\n this.channel = this.createSubscription()\n\n this.appearanceIntersectionObserver = new IntersectionObserver(\n (entries, observer) => {\n entries.forEach(entry => {\n if (entry.target !== this.triggerRoot) return\n if (entry.isIntersecting) {\n if (!this.present) {\n this.appear()\n }\n } else {\n if (this.present) {\n this.disappear()\n }\n }\n })\n },\n { threshold: 0 }\n )\n\n this.mutationObserver = new MutationObserver((mutationsList, observer) => {\n if (this.triggerRootSelector) {\n // eslint-disable-next-line no-unused-vars\n for (const mutation of mutationsList) {\n const root = document.querySelector(this.triggerRootSelector)\n if (root) {\n this.uninstall()\n this.triggerRoot = root\n this.install()\n }\n }\n }\n this.mutationObserver.disconnect()\n })\n\n this.mutationObserver.observe(document, {\n subtree: true,\n childList: true\n })\n }\n\n disconnectedCallback () {\n this.disappear()\n super.disconnectedCallback()\n }\n\n install () {\n if (this.appearTriggers.includes('connect')) {\n this.appear()\n }\n\n this.appearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {\n this.appearanceIntersectionObserver.observe(this.triggerRoot)\n })\n\n this.appearTriggers\n .filter(eventName => eventName !== 'connect' && eventName !== 'intersect')\n .forEach(eventName => {\n this.triggerRoot.addEventListener(eventName, this.appear.bind(this))\n })\n\n this.disappearTriggers\n .filter(\n eventName => eventName !== 'disconnect' && eventName !== 'intersect'\n )\n .forEach(eventName => {\n this.triggerRoot.addEventListener(eventName, this.disappear.bind(this))\n })\n\n this.disappearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {})\n }\n\n uninstall () {\n this.appearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {\n this.appearanceIntersectionObserver.unobserve(this.triggerRoot)\n })\n\n this.appearTriggers\n .filter(eventName => eventName !== 'connect' && eventName !== 'intersect')\n .forEach(eventName => {\n this.triggerRoot.removeEventListener(eventName, this.appear.bind(this))\n })\n\n this.disappearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {})\n\n this.disappearTriggers\n .filter(\n eventName => eventName !== 'disconnect' && eventName !== 'intersect'\n )\n .forEach(eventName => {\n this.triggerRoot.removeEventListener(\n eventName,\n this.disappear.bind(this)\n )\n })\n }\n\n appear () {\n if (this.channel) {\n this.present = true\n this.channel.perform('appear')\n }\n }\n\n disappear () {\n if (this.channel) {\n this.present = false\n this.channel.perform('disappear')\n }\n }\n\n performOperations (data) {\n if (data.cableReady) {\n CableReady.perform(data.operations)\n }\n }\n\n createSubscription () {\n if (!this.consumer) {\n console.error(\n 'The `cubicle-element` helper cannot connect without an ActionCable consumer.'\n )\n return\n }\n\n return this.consumer.subscriptions.create(\n {\n channel: this.channelName,\n identifier: this.getAttribute('identifier'),\n element_id: this.id,\n scope: this.getAttribute('scope'),\n exclude_current_user:\n this.getAttribute('exclude-current-user') === 'true'\n },\n {\n connected: () => {\n this.install()\n },\n disconnected: () => {\n this.disappear()\n this.uninstall()\n },\n rejected: () => {\n this.uninstall()\n },\n received: this.performOperations.bind(this)\n }\n )\n }\n\n get channelName () {\n return 'Cubism::PresenceChannel'\n }\n}\n","import { inputTags, textInputTypes } from './enums'\nimport activeElement from './active_element'\n\n// Indicates if the passed element is considered a text input.\n//\nconst isTextInput = element => {\n return inputTags[element.tagName] && textInputTypes[element.type]\n}\n\n// Assigns focus to the appropriate element... preferring the explicitly passed selector\n//\n// * selector - a CSS selector for the element that should have focus\n//\nconst assignFocus = selector => {\n const element =\n selector && selector.nodeType === Node.ELEMENT_NODE\n ? selector\n : document.querySelector(selector)\n const focusElement = element || activeElement.element\n if (focusElement && focusElement.focus) focusElement.focus()\n}\n\n// Dispatches an event on the passed element\n//\n// * element - the element\n// * name - the name of the event\n// * detail - the event detail\n//\nconst dispatch = (element, name, detail = {}) => {\n const init = { bubbles: true, cancelable: true, detail: detail }\n const evt = new CustomEvent(name, init)\n element.dispatchEvent(evt)\n if (window.jQuery) window.jQuery(element).trigger(name, detail)\n}\n\n// Accepts an xPath query and returns the element found at that position in the DOM\n//\nconst xpathToElement = xpath => {\n return document.evaluate(\n xpath,\n document,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue\n}\n\n// Return an array with the class names to be used\n//\n// * names - could be a string or an array of strings for multiple classes.\n//\nconst getClassNames = names => Array(names).flat()\n\n// Perform operation for either the first or all of the elements returned by CSS selector\n//\n// * operation - the instruction payload from perform\n// * callback - the operation function to run for each element\n//\nconst processElements = (operation, callback) => {\n Array.from(\n operation.selectAll ? operation.element : [operation.element]\n ).forEach(callback)\n}\n\n// camelCase to kebab-case\n//\nconst kebabize = str => {\n return str\n .split('')\n .map((letter, idx) => {\n return letter.toUpperCase() === letter\n ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`\n : letter\n })\n .join('')\n}\n\n// Provide a standardized pipeline of checks and modifications to all operations based on provided options\n// Currently skips execution if cancelled and implements an optional delay\n//\nconst operate = (operation, callback) => {\n if (!operation.cancel) {\n operation.delay ? setTimeout(callback, operation.delay) : callback()\n return true\n }\n return false\n}\n\n// Dispatch life-cycle events with standardized naming\nconst before = (target, operation) =>\n dispatch(\n target,\n `cable-ready:before-${kebabize(operation.operation)}`,\n operation\n )\n\nconst after = (target, operation) =>\n dispatch(\n target,\n `cable-ready:after-${kebabize(operation.operation)}`,\n operation\n )\n\nfunction debounce (func, timeout) {\n let timer\n return (...args) => {\n clearTimeout(timer)\n timer = setTimeout(() => func.apply(this, args), timeout)\n }\n}\n\nfunction handleErrors (response) {\n if (!response.ok) throw Error(response.statusText)\n return response\n}\n\n// A proxy method to wrap a fetch call in error handling\n//\n// * url - the URL to fetch\n// * additionalHeaders - an object of additional headers passed to fetch\n//\nasync function graciouslyFetch (url, additionalHeaders) {\n try {\n const response = await fetch(url, {\n headers: {\n 'X-REQUESTED-WITH': 'XmlHttpRequest',\n ...additionalHeaders\n }\n })\n if (response == undefined) return\n\n handleErrors(response)\n\n return response\n } catch (e) {\n console.error(`Could not fetch ${url}`)\n }\n}\n\nexport {\n isTextInput,\n assignFocus,\n dispatch,\n xpathToElement,\n getClassNames,\n processElements,\n operate,\n before,\n after,\n debounce,\n handleErrors,\n graciouslyFetch\n}\n"],"names":["customElements","define","SubscribingElement","constructor","super","this","attachShadow","mode","innerHTML","triggerRoot","present","async","preview","appear","func","timeout","timer","args","clearTimeout","setTimeout","apply","debounce","bind","appearTriggers","getAttribute","split","disappearTriggers","triggerRootSelector","consumer","CableReady","channel","createSubscription","appearanceIntersectionObserver","IntersectionObserver","entries","observer","forEach","entry","target","isIntersecting","disappear","threshold","mutationObserver","MutationObserver","mutationsList","mutation","root","document","querySelector","uninstall","install","disconnect","observe","subtree","childList","disconnectedCallback","includes","filter","eventName","addEventListener","unobserve","removeEventListener","perform","performOperations","data","cableReady","operations","subscriptions","create","channelName","identifier","element_id","id","scope","exclude_current_user","connected","disconnected","rejected","received","console","error"],"mappings":"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"}
|
@@ -0,0 +1,149 @@
|
|
1
|
+
(function(global, factory) {
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(require("cable_ready")) : typeof define === "function" && define.amd ? define([ "cable_ready" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
|
3
|
+
factory(global.CableReady));
|
4
|
+
})(this, (function(CableReady) {
|
5
|
+
"use strict";
|
6
|
+
function _interopDefaultLegacy(e) {
|
7
|
+
return e && typeof e === "object" && "default" in e ? e : {
|
8
|
+
default: e
|
9
|
+
};
|
10
|
+
}
|
11
|
+
var CableReady__default = _interopDefaultLegacy(CableReady);
|
12
|
+
function debounce(func, timeout) {
|
13
|
+
let timer;
|
14
|
+
return (...args) => {
|
15
|
+
clearTimeout(timer);
|
16
|
+
timer = setTimeout((() => func.apply(this, args)), timeout);
|
17
|
+
};
|
18
|
+
}
|
19
|
+
class Cubicle extends CableReady.SubscribingElement {
|
20
|
+
constructor() {
|
21
|
+
super();
|
22
|
+
const shadowRoot = this.attachShadow({
|
23
|
+
mode: "open"
|
24
|
+
});
|
25
|
+
shadowRoot.innerHTML = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`;
|
26
|
+
this.triggerRoot = this;
|
27
|
+
this.present = false;
|
28
|
+
}
|
29
|
+
async connectedCallback() {
|
30
|
+
if (this.preview) return;
|
31
|
+
this.appear = debounce(this.appear.bind(this), 50);
|
32
|
+
this.appearTriggers = this.getAttribute("appear-trigger") ? this.getAttribute("appear-trigger").split(",") : [];
|
33
|
+
this.disappearTriggers = this.getAttribute("disappear-trigger") ? this.getAttribute("disappear-trigger").split(",") : [];
|
34
|
+
this.triggerRootSelector = this.getAttribute("trigger-root");
|
35
|
+
this.consumer = await CableReady__default["default"].consumer;
|
36
|
+
this.channel = this.createSubscription();
|
37
|
+
this.appearanceIntersectionObserver = new IntersectionObserver(((entries, observer) => {
|
38
|
+
entries.forEach((entry => {
|
39
|
+
if (entry.target !== this.triggerRoot) return;
|
40
|
+
if (entry.isIntersecting) {
|
41
|
+
if (!this.present) {
|
42
|
+
this.appear();
|
43
|
+
}
|
44
|
+
} else {
|
45
|
+
if (this.present) {
|
46
|
+
this.disappear();
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}));
|
50
|
+
}), {
|
51
|
+
threshold: 0
|
52
|
+
});
|
53
|
+
this.mutationObserver = new MutationObserver(((mutationsList, observer) => {
|
54
|
+
if (this.triggerRootSelector) {
|
55
|
+
for (const mutation of mutationsList) {
|
56
|
+
const root = document.querySelector(this.triggerRootSelector);
|
57
|
+
if (root) {
|
58
|
+
this.uninstall();
|
59
|
+
this.triggerRoot = root;
|
60
|
+
this.install();
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
this.mutationObserver.disconnect();
|
65
|
+
}));
|
66
|
+
this.mutationObserver.observe(document, {
|
67
|
+
subtree: true,
|
68
|
+
childList: true
|
69
|
+
});
|
70
|
+
}
|
71
|
+
disconnectedCallback() {
|
72
|
+
this.disappear();
|
73
|
+
super.disconnectedCallback();
|
74
|
+
}
|
75
|
+
install() {
|
76
|
+
if (this.appearTriggers.includes("connect")) {
|
77
|
+
this.appear();
|
78
|
+
}
|
79
|
+
this.appearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {
|
80
|
+
this.appearanceIntersectionObserver.observe(this.triggerRoot);
|
81
|
+
}));
|
82
|
+
this.appearTriggers.filter((eventName => eventName !== "connect" && eventName !== "intersect")).forEach((eventName => {
|
83
|
+
this.triggerRoot.addEventListener(eventName, this.appear.bind(this));
|
84
|
+
}));
|
85
|
+
this.disappearTriggers.filter((eventName => eventName !== "disconnect" && eventName !== "intersect")).forEach((eventName => {
|
86
|
+
this.triggerRoot.addEventListener(eventName, this.disappear.bind(this));
|
87
|
+
}));
|
88
|
+
this.disappearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {}));
|
89
|
+
}
|
90
|
+
uninstall() {
|
91
|
+
this.appearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {
|
92
|
+
this.appearanceIntersectionObserver.unobserve(this.triggerRoot);
|
93
|
+
}));
|
94
|
+
this.appearTriggers.filter((eventName => eventName !== "connect" && eventName !== "intersect")).forEach((eventName => {
|
95
|
+
this.triggerRoot.removeEventListener(eventName, this.appear.bind(this));
|
96
|
+
}));
|
97
|
+
this.disappearTriggers.filter((eventName => eventName === "intersect")).forEach((() => {}));
|
98
|
+
this.disappearTriggers.filter((eventName => eventName !== "disconnect" && eventName !== "intersect")).forEach((eventName => {
|
99
|
+
this.triggerRoot.removeEventListener(eventName, this.disappear.bind(this));
|
100
|
+
}));
|
101
|
+
}
|
102
|
+
appear() {
|
103
|
+
if (this.channel) {
|
104
|
+
this.present = true;
|
105
|
+
this.channel.perform("appear");
|
106
|
+
}
|
107
|
+
}
|
108
|
+
disappear() {
|
109
|
+
if (this.channel) {
|
110
|
+
this.present = false;
|
111
|
+
this.channel.perform("disappear");
|
112
|
+
}
|
113
|
+
}
|
114
|
+
performOperations(data) {
|
115
|
+
if (data.cableReady) {
|
116
|
+
CableReady__default["default"].perform(data.operations);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
createSubscription() {
|
120
|
+
if (!this.consumer) {
|
121
|
+
console.error("The `cubicle-element` helper cannot connect without an ActionCable consumer.");
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
return this.consumer.subscriptions.create({
|
125
|
+
channel: this.channelName,
|
126
|
+
identifier: this.getAttribute("identifier"),
|
127
|
+
element_id: this.id,
|
128
|
+
scope: this.getAttribute("scope"),
|
129
|
+
exclude_current_user: this.getAttribute("exclude-current-user") === "true"
|
130
|
+
}, {
|
131
|
+
connected: () => {
|
132
|
+
this.install();
|
133
|
+
},
|
134
|
+
disconnected: () => {
|
135
|
+
this.disappear();
|
136
|
+
this.uninstall();
|
137
|
+
},
|
138
|
+
rejected: () => {
|
139
|
+
this.uninstall();
|
140
|
+
},
|
141
|
+
received: this.performOperations.bind(this)
|
142
|
+
});
|
143
|
+
}
|
144
|
+
get channelName() {
|
145
|
+
return "Cubism::PresenceChannel";
|
146
|
+
}
|
147
|
+
}
|
148
|
+
customElements.define("cubicle-element", Cubicle);
|
149
|
+
}));
|
@@ -0,0 +1,2 @@
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("cable_ready")):"function"==typeof define&&define.amd?define(["cable_ready"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CableReady)}(this,(function(e){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=t(e);class r extends e.SubscribingElement{constructor(){super();this.attachShadow({mode:"open"}).innerHTML="\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n",this.triggerRoot=this,this.present=!1}async connectedCallback(){this.preview||(this.appear=function(e,t){let i;return(...r)=>{clearTimeout(i),i=setTimeout((()=>e.apply(this,r)),t)}}(this.appear.bind(this),50),this.appearTriggers=this.getAttribute("appear-trigger")?this.getAttribute("appear-trigger").split(","):[],this.disappearTriggers=this.getAttribute("disappear-trigger")?this.getAttribute("disappear-trigger").split(","):[],this.triggerRootSelector=this.getAttribute("trigger-root"),this.consumer=await i.default.consumer,this.channel=this.createSubscription(),this.appearanceIntersectionObserver=new IntersectionObserver(((e,t)=>{e.forEach((e=>{e.target===this.triggerRoot&&(e.isIntersecting?this.present||this.appear():this.present&&this.disappear())}))}),{threshold:0}),this.mutationObserver=new MutationObserver(((e,t)=>{if(this.triggerRootSelector)for(const t of e){const e=document.querySelector(this.triggerRootSelector);e&&(this.uninstall(),this.triggerRoot=e,this.install())}this.mutationObserver.disconnect()})),this.mutationObserver.observe(document,{subtree:!0,childList:!0}))}disconnectedCallback(){this.disappear(),super.disconnectedCallback()}install(){this.appearTriggers.includes("connect")&&this.appear(),this.appearTriggers.filter((e=>"intersect"===e)).forEach((()=>{this.appearanceIntersectionObserver.observe(this.triggerRoot)})),this.appearTriggers.filter((e=>"connect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.addEventListener(e,this.appear.bind(this))})),this.disappearTriggers.filter((e=>"disconnect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.addEventListener(e,this.disappear.bind(this))})),this.disappearTriggers.filter((e=>"intersect"===e)).forEach((()=>{}))}uninstall(){this.appearTriggers.filter((e=>"intersect"===e)).forEach((()=>{this.appearanceIntersectionObserver.unobserve(this.triggerRoot)})),this.appearTriggers.filter((e=>"connect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.removeEventListener(e,this.appear.bind(this))})),this.disappearTriggers.filter((e=>"intersect"===e)).forEach((()=>{})),this.disappearTriggers.filter((e=>"disconnect"!==e&&"intersect"!==e)).forEach((e=>{this.triggerRoot.removeEventListener(e,this.disappear.bind(this))}))}appear(){this.channel&&(this.present=!0,this.channel.perform("appear"))}disappear(){this.channel&&(this.present=!1,this.channel.perform("disappear"))}performOperations(e){e.cableReady&&i.default.perform(e.operations)}createSubscription(){if(this.consumer)return this.consumer.subscriptions.create({channel:this.channelName,identifier:this.getAttribute("identifier"),element_id:this.id,scope:this.getAttribute("scope"),exclude_current_user:"true"===this.getAttribute("exclude-current-user")},{connected:()=>{this.install()},disconnected:()=>{this.disappear(),this.uninstall()},rejected:()=>{this.uninstall()},received:this.performOperations.bind(this)});console.error("The `cubicle-element` helper cannot connect without an ActionCable consumer.")}get channelName(){return"Cubism::PresenceChannel"}}customElements.define("cubicle-element",r)}));
|
2
|
+
//# sourceMappingURL=cubism.umd.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"cubism.umd.min.js","sources":["../../../javascript/elements/cubicle.js","../../../node_modules/cable_ready/javascript/utils.js","../../../javascript/elements/index.js"],"sourcesContent":["/* eslint-disable no-undef */\nimport CableReady, { SubscribingElement } from 'cable_ready'\nimport { debounce } from 'cable_ready/javascript/utils'\n\nexport class Cubicle extends SubscribingElement {\n constructor () {\n super()\n const shadowRoot = this.attachShadow({ mode: 'open' })\n shadowRoot.innerHTML = `\n<style>\n :host {\n display: block;\n }\n</style>\n<slot></slot>\n`\n\n this.triggerRoot = this\n this.present = false\n }\n\n async connectedCallback () {\n if (this.preview) return\n\n this.appear = debounce(this.appear.bind(this), 50)\n\n this.appearTriggers = this.getAttribute('appear-trigger')\n ? this.getAttribute('appear-trigger').split(',')\n : []\n this.disappearTriggers = this.getAttribute('disappear-trigger')\n ? this.getAttribute('disappear-trigger').split(',')\n : []\n this.triggerRootSelector = this.getAttribute('trigger-root')\n\n this.consumer = await CableReady.consumer\n\n this.channel = this.createSubscription()\n\n this.appearanceIntersectionObserver = new IntersectionObserver(\n (entries, observer) => {\n entries.forEach(entry => {\n if (entry.target !== this.triggerRoot) return\n if (entry.isIntersecting) {\n if (!this.present) {\n this.appear()\n }\n } else {\n if (this.present) {\n this.disappear()\n }\n }\n })\n },\n { threshold: 0 }\n )\n\n this.mutationObserver = new MutationObserver((mutationsList, observer) => {\n if (this.triggerRootSelector) {\n // eslint-disable-next-line no-unused-vars\n for (const mutation of mutationsList) {\n const root = document.querySelector(this.triggerRootSelector)\n if (root) {\n this.uninstall()\n this.triggerRoot = root\n this.install()\n }\n }\n }\n this.mutationObserver.disconnect()\n })\n\n this.mutationObserver.observe(document, {\n subtree: true,\n childList: true\n })\n }\n\n disconnectedCallback () {\n this.disappear()\n super.disconnectedCallback()\n }\n\n install () {\n if (this.appearTriggers.includes('connect')) {\n this.appear()\n }\n\n this.appearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {\n this.appearanceIntersectionObserver.observe(this.triggerRoot)\n })\n\n this.appearTriggers\n .filter(eventName => eventName !== 'connect' && eventName !== 'intersect')\n .forEach(eventName => {\n this.triggerRoot.addEventListener(eventName, this.appear.bind(this))\n })\n\n this.disappearTriggers\n .filter(\n eventName => eventName !== 'disconnect' && eventName !== 'intersect'\n )\n .forEach(eventName => {\n this.triggerRoot.addEventListener(eventName, this.disappear.bind(this))\n })\n\n this.disappearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {})\n }\n\n uninstall () {\n this.appearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {\n this.appearanceIntersectionObserver.unobserve(this.triggerRoot)\n })\n\n this.appearTriggers\n .filter(eventName => eventName !== 'connect' && eventName !== 'intersect')\n .forEach(eventName => {\n this.triggerRoot.removeEventListener(eventName, this.appear.bind(this))\n })\n\n this.disappearTriggers\n .filter(eventName => eventName === 'intersect')\n .forEach(() => {})\n\n this.disappearTriggers\n .filter(\n eventName => eventName !== 'disconnect' && eventName !== 'intersect'\n )\n .forEach(eventName => {\n this.triggerRoot.removeEventListener(\n eventName,\n this.disappear.bind(this)\n )\n })\n }\n\n appear () {\n if (this.channel) {\n this.present = true\n this.channel.perform('appear')\n }\n }\n\n disappear () {\n if (this.channel) {\n this.present = false\n this.channel.perform('disappear')\n }\n }\n\n performOperations (data) {\n if (data.cableReady) {\n CableReady.perform(data.operations)\n }\n }\n\n createSubscription () {\n if (!this.consumer) {\n console.error(\n 'The `cubicle-element` helper cannot connect without an ActionCable consumer.'\n )\n return\n }\n\n return this.consumer.subscriptions.create(\n {\n channel: this.channelName,\n identifier: this.getAttribute('identifier'),\n element_id: this.id,\n scope: this.getAttribute('scope'),\n exclude_current_user:\n this.getAttribute('exclude-current-user') === 'true'\n },\n {\n connected: () => {\n this.install()\n },\n disconnected: () => {\n this.disappear()\n this.uninstall()\n },\n rejected: () => {\n this.uninstall()\n },\n received: this.performOperations.bind(this)\n }\n )\n }\n\n get channelName () {\n return 'Cubism::PresenceChannel'\n }\n}\n","import { inputTags, textInputTypes } from './enums'\nimport activeElement from './active_element'\n\n// Indicates if the passed element is considered a text input.\n//\nconst isTextInput = element => {\n return inputTags[element.tagName] && textInputTypes[element.type]\n}\n\n// Assigns focus to the appropriate element... preferring the explicitly passed selector\n//\n// * selector - a CSS selector for the element that should have focus\n//\nconst assignFocus = selector => {\n const element =\n selector && selector.nodeType === Node.ELEMENT_NODE\n ? selector\n : document.querySelector(selector)\n const focusElement = element || activeElement.element\n if (focusElement && focusElement.focus) focusElement.focus()\n}\n\n// Dispatches an event on the passed element\n//\n// * element - the element\n// * name - the name of the event\n// * detail - the event detail\n//\nconst dispatch = (element, name, detail = {}) => {\n const init = { bubbles: true, cancelable: true, detail: detail }\n const evt = new CustomEvent(name, init)\n element.dispatchEvent(evt)\n if (window.jQuery) window.jQuery(element).trigger(name, detail)\n}\n\n// Accepts an xPath query and returns the element found at that position in the DOM\n//\nconst xpathToElement = xpath => {\n return document.evaluate(\n xpath,\n document,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue\n}\n\n// Return an array with the class names to be used\n//\n// * names - could be a string or an array of strings for multiple classes.\n//\nconst getClassNames = names => Array(names).flat()\n\n// Perform operation for either the first or all of the elements returned by CSS selector\n//\n// * operation - the instruction payload from perform\n// * callback - the operation function to run for each element\n//\nconst processElements = (operation, callback) => {\n Array.from(\n operation.selectAll ? operation.element : [operation.element]\n ).forEach(callback)\n}\n\n// camelCase to kebab-case\n//\nconst kebabize = str => {\n return str\n .split('')\n .map((letter, idx) => {\n return letter.toUpperCase() === letter\n ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`\n : letter\n })\n .join('')\n}\n\n// Provide a standardized pipeline of checks and modifications to all operations based on provided options\n// Currently skips execution if cancelled and implements an optional delay\n//\nconst operate = (operation, callback) => {\n if (!operation.cancel) {\n operation.delay ? setTimeout(callback, operation.delay) : callback()\n return true\n }\n return false\n}\n\n// Dispatch life-cycle events with standardized naming\nconst before = (target, operation) =>\n dispatch(\n target,\n `cable-ready:before-${kebabize(operation.operation)}`,\n operation\n )\n\nconst after = (target, operation) =>\n dispatch(\n target,\n `cable-ready:after-${kebabize(operation.operation)}`,\n operation\n )\n\nfunction debounce (func, timeout) {\n let timer\n return (...args) => {\n clearTimeout(timer)\n timer = setTimeout(() => func.apply(this, args), timeout)\n }\n}\n\nfunction handleErrors (response) {\n if (!response.ok) throw Error(response.statusText)\n return response\n}\n\n// A proxy method to wrap a fetch call in error handling\n//\n// * url - the URL to fetch\n// * additionalHeaders - an object of additional headers passed to fetch\n//\nasync function graciouslyFetch (url, additionalHeaders) {\n try {\n const response = await fetch(url, {\n headers: {\n 'X-REQUESTED-WITH': 'XmlHttpRequest',\n ...additionalHeaders\n }\n })\n if (response == undefined) return\n\n handleErrors(response)\n\n return response\n } catch (e) {\n console.error(`Could not fetch ${url}`)\n }\n}\n\nexport {\n isTextInput,\n assignFocus,\n dispatch,\n xpathToElement,\n getClassNames,\n processElements,\n operate,\n before,\n after,\n debounce,\n handleErrors,\n graciouslyFetch\n}\n","/* global customElements */\n\nimport { Cubicle } from './cubicle'\n\nexport * from './cubicle'\n\ncustomElements.define('cubicle-element', Cubicle)\n"],"names":["Cubicle","SubscribingElement","constructor","super","this","attachShadow","mode","innerHTML","triggerRoot","present","async","preview","appear","func","timeout","timer","args","clearTimeout","setTimeout","apply","debounce","bind","appearTriggers","getAttribute","split","disappearTriggers","triggerRootSelector","consumer","CableReady","channel","createSubscription","appearanceIntersectionObserver","IntersectionObserver","entries","observer","forEach","entry","target","isIntersecting","disappear","threshold","mutationObserver","MutationObserver","mutationsList","mutation","root","document","querySelector","uninstall","install","disconnect","observe","subtree","childList","disconnectedCallback","includes","filter","eventName","addEventListener","unobserve","removeEventListener","perform","performOperations","data","cableReady","operations","subscriptions","create","channelName","identifier","element_id","id","scope","exclude_current_user","connected","disconnected","rejected","received","console","error","customElements","define"],"mappings":"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"}
|
@@ -3,7 +3,7 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
3
3
|
|
4
4
|
def subscribed
|
5
5
|
if resource.present?
|
6
|
-
stream_from
|
6
|
+
stream_from element_id
|
7
7
|
resource.cubicle_element_ids << element_id
|
8
8
|
resource.excluded_user_id_for_element_id[element_id] = user.id if exclude_current_user?
|
9
9
|
else
|
@@ -20,11 +20,11 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def appear
|
23
|
-
resource.
|
23
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).add(user.id), scope) if scope
|
24
24
|
end
|
25
25
|
|
26
26
|
def disappear
|
27
|
-
resource.
|
27
|
+
resource.set_present_users_for_scope(resource.present_users_for_scope(scope).delete(user.id), scope) if scope
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -35,7 +35,15 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def user
|
38
|
-
|
38
|
+
block_container&.user
|
39
|
+
end
|
40
|
+
|
41
|
+
def scope
|
42
|
+
block_container&.scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def block_container
|
46
|
+
Cubism.block_store[element_id]
|
39
47
|
end
|
40
48
|
|
41
49
|
def exclude_current_user?
|
@@ -43,7 +51,8 @@ class Cubism::PresenceChannel < ActionCable::Channel::Base
|
|
43
51
|
end
|
44
52
|
|
45
53
|
def element_id
|
46
|
-
params[:element_id]
|
54
|
+
/cubicle-(?<element_id>.+)/ =~ params[:element_id]
|
55
|
+
element_id
|
47
56
|
end
|
48
57
|
|
49
58
|
def url
|
@@ -1,31 +1,29 @@
|
|
1
1
|
module CubismHelper
|
2
2
|
include CableReady::StreamIdentifier
|
3
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
|
4
|
+
def cubicle_for(resource, user, scope: "", html_options: {}, appear_trigger: :connect, disappear_trigger: nil, trigger_root: nil, exclude_current_user: true, &block)
|
6
5
|
block_location = block.source_location.join(":")
|
7
|
-
|
8
|
-
|
6
|
+
block_source = Cubism::BlockSource.find_or_create(
|
7
|
+
location: block_location,
|
8
|
+
view_context: self
|
9
|
+
)
|
10
|
+
|
11
|
+
resource_gid = resource.to_gid.to_s
|
9
12
|
|
10
|
-
|
11
|
-
store_item = Cubism.store[digested_block_key] || Cubism::BlockStoreItem.new(
|
13
|
+
block_container = Cubism::BlockContainer.new(
|
12
14
|
block_location: block_location,
|
13
|
-
|
14
|
-
|
15
|
+
block_source: block_source,
|
16
|
+
resource_gid: resource_gid,
|
17
|
+
user_gid: user.to_gid.to_s,
|
18
|
+
scope: scope
|
15
19
|
)
|
16
20
|
|
17
|
-
|
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
|
21
|
+
digested_block_key = block_container.digest
|
23
22
|
|
24
|
-
Cubism.
|
23
|
+
Cubism.block_store.fetch(digested_block_key, block_container)
|
25
24
|
|
26
25
|
tag.cubicle_element(
|
27
|
-
identifier: signed_stream_identifier(
|
28
|
-
user: user.to_sgid.to_s,
|
26
|
+
identifier: signed_stream_identifier(resource_gid),
|
29
27
|
"appear-trigger": Array(appear_trigger).join(","),
|
30
28
|
"disappear-trigger": disappear_trigger,
|
31
29
|
"trigger-root": trigger_root,
|
@@ -2,7 +2,7 @@ module Cubism::Presence
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
|
5
|
+
kredis_hash :present_users, after_change: :stream_presence
|
6
6
|
kredis_set :cubicle_element_ids
|
7
7
|
kredis_hash :excluded_user_id_for_element_id
|
8
8
|
end
|
@@ -11,8 +11,16 @@ module Cubism::Presence
|
|
11
11
|
Cubism::Broadcaster.new(resource: self).broadcast
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def present_users_for_scope(scope = "")
|
15
|
+
present_users[scope].present? ? Marshal.load(present_users[scope]) : Set.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_present_users_for_scope(user_ids, scope = "")
|
19
|
+
present_users[scope] = Marshal.dump(Set.new(user_ids))
|
20
|
+
end
|
21
|
+
|
22
|
+
def present_users_for_element_id_and_scope(element_id, scope = "")
|
23
|
+
users = Cubism.user_class.find(present_users_for_scope(scope).to_a)
|
16
24
|
users.reject! { |user| user.id == excluded_user_id_for_element_id[element_id].to_i }
|
17
25
|
|
18
26
|
users
|
data/bin/rails
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/cubism/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
|
12
|
+
|
13
|
+
require "rails/all"
|
14
|
+
require "rails/engine/commands"
|
data/bin/standardize
ADDED
data/bin/test
ADDED
data/cubism.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "lib/cubism/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "cubism"
|
5
|
+
spec.version = Cubism::VERSION
|
6
|
+
spec.authors = ["Julian Rubisch"]
|
7
|
+
spec.email = ["julian@julianrubisch.at"]
|
8
|
+
spec.homepage = "https://github.com/julianrubisch/cubism"
|
9
|
+
spec.summary = "Lightweight Resource-Based Presence Solution with CableReady"
|
10
|
+
spec.description = "Lightweight Resource-Based Presence Solution with CableReady"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
14
|
+
spec.metadata["source_code_uri"] = "https://github.com/julianrubisch/cubism.git"
|
15
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
16
|
+
|
17
|
+
spec.files = Dir[
|
18
|
+
"lib/**/*.rb",
|
19
|
+
"app/**/*.rb",
|
20
|
+
"app/assets/javascripts/*",
|
21
|
+
"bin/*",
|
22
|
+
"[A-Z]*"
|
23
|
+
]
|
24
|
+
|
25
|
+
spec.test_files = Dir["test/**/*.rb"]
|
26
|
+
|
27
|
+
spec.add_dependency "rails", ">= 6.0"
|
28
|
+
spec.add_dependency "kredis", ">= 0.4"
|
29
|
+
spec.add_dependency "cable_ready", "= 5.0.0.pre9"
|
30
|
+
|
31
|
+
spec.add_development_dependency "standard"
|
32
|
+
spec.add_development_dependency "nokogiri"
|
33
|
+
spec.add_development_dependency "mocha"
|
34
|
+
spec.add_development_dependency "appraisal"
|
35
|
+
spec.add_development_dependency "sqlite3"
|
36
|
+
end
|
data/cubism.gemspec~
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "lib/cubism/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "cubism"
|
5
|
+
spec.version = Cubism::VERSION
|
6
|
+
spec.authors = ["Julian Rubisch"]
|
7
|
+
spec.email = ["julian@julianrubisch.at"]
|
8
|
+
spec.homepage = "https://github.com/julianrubisch/cubism"
|
9
|
+
spec.summary = "Lightweight Resource-Based Presence Solution with CableReady"
|
10
|
+
spec.description = "Lightweight Resource-Based Presence Solution with CableReady"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
14
|
+
spec.metadata["source_code_uri"] = "https://github.com/julianrubisch/cubism.git"
|
15
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
16
|
+
|
17
|
+
spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
|
18
|
+
|
19
|
+
spec.add_dependency "rails", ">= 6.0"
|
20
|
+
spec.add_dependency "kredis", ">= 0.4"
|
21
|
+
spec.add_dependency "cable_ready", "= 5.0.0.pre8"
|
22
|
+
|
23
|
+
spec.add_development_dependency "standard"
|
24
|
+
spec.add_development_dependency "nokogiri"
|
25
|
+
spec.add_development_dependency "mocha"
|
26
|
+
spec.add_development_dependency "appraisal"
|
27
|
+
spec.add_development_dependency "sqlite3"
|
28
|
+
end
|
data/lib/cubism/broadcaster.rb
CHANGED
@@ -13,18 +13,25 @@ module Cubism
|
|
13
13
|
|
14
14
|
def broadcast
|
15
15
|
resource.cubicle_element_ids.to_a.each do |element_id|
|
16
|
-
|
17
|
-
store_item = Cubism.store[block_key]
|
16
|
+
block_container = Cubism.block_store[element_id]
|
18
17
|
|
19
|
-
next if
|
18
|
+
next if block_container.blank?
|
20
19
|
|
21
|
-
|
20
|
+
present_users = resource.present_users_for_element_id_and_scope(element_id, block_container.scope)
|
21
|
+
|
22
|
+
block_source = block_container.block_source
|
23
|
+
|
24
|
+
html = ApplicationController.render(inline: block_source.source, locals: {"#{block_source.variable_name}": present_users})
|
25
|
+
|
26
|
+
selector = "cubicle-element#cubicle-#{element_id}[identifier='#{signed_stream_identifier(resource.to_global_id.to_s)}']"
|
22
27
|
|
23
28
|
cable_ready[element_id].inner_html(
|
24
|
-
selector:
|
29
|
+
selector: selector,
|
25
30
|
html: html
|
26
|
-
)
|
31
|
+
)
|
27
32
|
end
|
33
|
+
|
34
|
+
cable_ready.broadcast
|
28
35
|
end
|
29
36
|
end
|
30
37
|
end
|