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