optics_view_components 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6288af3ac32f24e288467c22bea1cac3ced752a18d7cd7ff817cc927b57f3c06
4
- data.tar.gz: 9cb97927ebe42807ae5990d3bc5eda34ace45842569d744994cdab0a726e8ee6
3
+ metadata.gz: 1a951c750d000275cbaab0ba1647db5d7cd22d45b4fd727af9ce8a85ebebfbae
4
+ data.tar.gz: f26dcc64414812ba6ec93bae0be942b803f2da601876506e23782eaed9084334
5
5
  SHA512:
6
- metadata.gz: 82352039a648222ccb8721c529569cd7b1adbdc416b6e86d34c1ee611f724a9b6170f4b70843edbc3d4677af59509f1d1dfa870fc2e2b58bfc2af0991aef4661
7
- data.tar.gz: 9295d7c6b41b3148bb14bd1ce9569686c90fc9d246c48ac083952feb4b43064b7e73aad9ad8eb003fbc5add4808035c8ff278bccc40b33bddcab90f83f877dc1
6
+ metadata.gz: 9cc9ad65a1cbdb94c2553d35db1c52f1c11176c75af7ff8064fbd144dc67b80a3d6e90f56615e57c2566b06f90202a16545d313ee6924d7207217171521ff1c8
7
+ data.tar.gz: 14d71daf38ad04f2cd667ca17aa7e3810e91233c88103d082e4fa7acb0369734fca03b131218367f577bfac54dc10c2844eddd5fa56b5667fa5f07c4aed91e84
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- optics_view_components (0.1.8)
4
+ optics_view_components (0.1.9)
5
5
  view_component (> 2.0, < 4.0)
6
6
 
7
7
  GEM
@@ -14,7 +14,8 @@ module Optics
14
14
  def call
15
15
  content_tag(
16
16
  :nav,
17
- class: classes
17
+ class: classes,
18
+ **@attributes.except(:class)
18
19
  ) do
19
20
  capture do
20
21
  concat brand
@@ -45,7 +46,7 @@ module Optics
45
46
  accepts :name
46
47
 
47
48
  def call
48
- link_to(url, class: 'sidebar__brand') do
49
+ link_to(url, class: 'sidebar__brand', **@attributes) do
49
50
  name || image_tag(image_source, alt: image_label)
50
51
  end
51
52
  end
@@ -55,7 +56,7 @@ module Optics
55
56
  accepts :position, default: 'center'
56
57
 
57
58
  def call
58
- content_tag(:div, class: classes) do
59
+ content_tag(:div, class: classes, **@attributes) do
59
60
  content
60
61
  end
61
62
  end
@@ -63,7 +64,8 @@ module Optics
63
64
  def classes
64
65
  class_names(
65
66
  'sidebar__content',
66
- position_class
67
+ position_class,
68
+ @attributes[:class]
67
69
  ).join(' ')
68
70
  end
69
71
 
@@ -77,7 +79,7 @@ module Optics
77
79
  accepts :url
78
80
 
79
81
  def call
80
- link_to(url, class: 'sidebar__brand') do
82
+ link_to(url, class: 'sidebar__brand', **@attributes) do
81
83
  image_tag(img_src)
82
84
  end
83
85
  end
data/demo/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- optics_view_components (0.1.8)
4
+ optics_view_components (0.1.9)
5
5
  view_component (> 2.0, < 4.0)
6
6
 
7
7
  GEM
@@ -0,0 +1 @@
1
+ {"files":{"manifest-dad05bf766af0fe3d79dd746db3c1361c0583026cdf35d6a2921bccaea835331.js":{"logical_path":"manifest.js","mtime":"2023-07-21T16:26:54-04:00","size":3,"digest":"6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167","integrity":"sha256-ajz1GSNU9xYVrFEDSz6Xwg7amWQ/yvW75tQa1ZvRIWc="},"application-0637d2874099d8ba663ba7c4b7ae404ed5d14dce524e1199c89ad93f13956807.css":{"logical_path":"application.css","mtime":"2023-07-21T16:26:54-04:00","size":191384,"digest":"804e47b010668b842d5c04f570a7a3a8346cb83928c31ebb7d08b3c9c0b4417b","integrity":"sha256-gE5HsBBmi4QtXAT1cKejqDRsuDkowx67fQizycC0QXs="},"actioncable-5433453f9b6619a9de91aaab2d7fc7ff183e5260c0107cbc9a1aa0c838d9a74e.js":{"logical_path":"actioncable.js","mtime":"2023-07-21T16:26:54-04:00","size":15699,"digest":"6b2fe44d9e4546fcc6b92f0a37b61d6b555020041ede1dd603c007b1c0904ec1","integrity":"sha256-ay/kTZ5FRvzGuS8KN7Yda1VQIAQe3h3WA8AHscCQTsE="},"actioncable.esm-e01089c3ec4fe7817fa9abcad06cab6bdc387f95f0ca6aab4bf7ba7537f70690.js":{"logical_path":"actioncable.esm.js","mtime":"2023-07-21T16:26:54-04:00","size":14080,"digest":"a9e53dfa810276aa0aee8791a910c4878d48b75093398f3d6d822db41b428b7b","integrity":"sha256-qeU9+oECdqoK7oeRqRDEh41It1CTOY89bYIttBtCi3s="}},"assets":{"manifest.js":"manifest-dad05bf766af0fe3d79dd746db3c1361c0583026cdf35d6a2921bccaea835331.js","application.css":"application-0637d2874099d8ba663ba7c4b7ae404ed5d14dce524e1199c89ad93f13956807.css","actioncable.js":"actioncable-5433453f9b6619a9de91aaab2d7fc7ff183e5260c0107cbc9a1aa0c838d9a74e.js","actioncable.esm.js":"actioncable.esm-e01089c3ec4fe7817fa9abcad06cab6bdc387f95f0ca6aab4bf7ba7537f70690.js"}}
@@ -0,0 +1,489 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
3
+ factory(global.ActionCable = {}));
4
+ })(this, (function(exports) {
5
+ "use strict";
6
+ var adapters = {
7
+ logger: self.console,
8
+ WebSocket: self.WebSocket
9
+ };
10
+ var logger = {
11
+ log(...messages) {
12
+ if (this.enabled) {
13
+ messages.push(Date.now());
14
+ adapters.logger.log("[ActionCable]", ...messages);
15
+ }
16
+ }
17
+ };
18
+ const now = () => (new Date).getTime();
19
+ const secondsSince = time => (now() - time) / 1e3;
20
+ class ConnectionMonitor {
21
+ constructor(connection) {
22
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
23
+ this.connection = connection;
24
+ this.reconnectAttempts = 0;
25
+ }
26
+ start() {
27
+ if (!this.isRunning()) {
28
+ this.startedAt = now();
29
+ delete this.stoppedAt;
30
+ this.startPolling();
31
+ addEventListener("visibilitychange", this.visibilityDidChange);
32
+ logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`);
33
+ }
34
+ }
35
+ stop() {
36
+ if (this.isRunning()) {
37
+ this.stoppedAt = now();
38
+ this.stopPolling();
39
+ removeEventListener("visibilitychange", this.visibilityDidChange);
40
+ logger.log("ConnectionMonitor stopped");
41
+ }
42
+ }
43
+ isRunning() {
44
+ return this.startedAt && !this.stoppedAt;
45
+ }
46
+ recordPing() {
47
+ this.pingedAt = now();
48
+ }
49
+ recordConnect() {
50
+ this.reconnectAttempts = 0;
51
+ this.recordPing();
52
+ delete this.disconnectedAt;
53
+ logger.log("ConnectionMonitor recorded connect");
54
+ }
55
+ recordDisconnect() {
56
+ this.disconnectedAt = now();
57
+ logger.log("ConnectionMonitor recorded disconnect");
58
+ }
59
+ startPolling() {
60
+ this.stopPolling();
61
+ this.poll();
62
+ }
63
+ stopPolling() {
64
+ clearTimeout(this.pollTimeout);
65
+ }
66
+ poll() {
67
+ this.pollTimeout = setTimeout((() => {
68
+ this.reconnectIfStale();
69
+ this.poll();
70
+ }), this.getPollInterval());
71
+ }
72
+ getPollInterval() {
73
+ const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor;
74
+ const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10));
75
+ const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate;
76
+ const jitter = jitterMax * Math.random();
77
+ return staleThreshold * 1e3 * backoff * (1 + jitter);
78
+ }
79
+ reconnectIfStale() {
80
+ if (this.connectionIsStale()) {
81
+ logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`);
82
+ this.reconnectAttempts++;
83
+ if (this.disconnectedRecently()) {
84
+ logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`);
85
+ } else {
86
+ logger.log("ConnectionMonitor reopening");
87
+ this.connection.reopen();
88
+ }
89
+ }
90
+ }
91
+ get refreshedAt() {
92
+ return this.pingedAt ? this.pingedAt : this.startedAt;
93
+ }
94
+ connectionIsStale() {
95
+ return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
96
+ }
97
+ disconnectedRecently() {
98
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
99
+ }
100
+ visibilityDidChange() {
101
+ if (document.visibilityState === "visible") {
102
+ setTimeout((() => {
103
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
104
+ logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`);
105
+ this.connection.reopen();
106
+ }
107
+ }), 200);
108
+ }
109
+ }
110
+ }
111
+ ConnectionMonitor.staleThreshold = 6;
112
+ ConnectionMonitor.reconnectionBackoffRate = .15;
113
+ var INTERNAL = {
114
+ message_types: {
115
+ welcome: "welcome",
116
+ disconnect: "disconnect",
117
+ ping: "ping",
118
+ confirmation: "confirm_subscription",
119
+ rejection: "reject_subscription"
120
+ },
121
+ disconnect_reasons: {
122
+ unauthorized: "unauthorized",
123
+ invalid_request: "invalid_request",
124
+ server_restart: "server_restart"
125
+ },
126
+ default_mount_path: "/cable",
127
+ protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
128
+ };
129
+ const {message_types: message_types, protocols: protocols} = INTERNAL;
130
+ const supportedProtocols = protocols.slice(0, protocols.length - 1);
131
+ const indexOf = [].indexOf;
132
+ class Connection {
133
+ constructor(consumer) {
134
+ this.open = this.open.bind(this);
135
+ this.consumer = consumer;
136
+ this.subscriptions = this.consumer.subscriptions;
137
+ this.monitor = new ConnectionMonitor(this);
138
+ this.disconnected = true;
139
+ }
140
+ send(data) {
141
+ if (this.isOpen()) {
142
+ this.webSocket.send(JSON.stringify(data));
143
+ return true;
144
+ } else {
145
+ return false;
146
+ }
147
+ }
148
+ open() {
149
+ if (this.isActive()) {
150
+ logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`);
151
+ return false;
152
+ } else {
153
+ logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`);
154
+ if (this.webSocket) {
155
+ this.uninstallEventHandlers();
156
+ }
157
+ this.webSocket = new adapters.WebSocket(this.consumer.url, protocols);
158
+ this.installEventHandlers();
159
+ this.monitor.start();
160
+ return true;
161
+ }
162
+ }
163
+ close({allowReconnect: allowReconnect} = {
164
+ allowReconnect: true
165
+ }) {
166
+ if (!allowReconnect) {
167
+ this.monitor.stop();
168
+ }
169
+ if (this.isOpen()) {
170
+ return this.webSocket.close();
171
+ }
172
+ }
173
+ reopen() {
174
+ logger.log(`Reopening WebSocket, current state is ${this.getState()}`);
175
+ if (this.isActive()) {
176
+ try {
177
+ return this.close();
178
+ } catch (error) {
179
+ logger.log("Failed to reopen WebSocket", error);
180
+ } finally {
181
+ logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`);
182
+ setTimeout(this.open, this.constructor.reopenDelay);
183
+ }
184
+ } else {
185
+ return this.open();
186
+ }
187
+ }
188
+ getProtocol() {
189
+ if (this.webSocket) {
190
+ return this.webSocket.protocol;
191
+ }
192
+ }
193
+ isOpen() {
194
+ return this.isState("open");
195
+ }
196
+ isActive() {
197
+ return this.isState("open", "connecting");
198
+ }
199
+ isProtocolSupported() {
200
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
201
+ }
202
+ isState(...states) {
203
+ return indexOf.call(states, this.getState()) >= 0;
204
+ }
205
+ getState() {
206
+ if (this.webSocket) {
207
+ for (let state in adapters.WebSocket) {
208
+ if (adapters.WebSocket[state] === this.webSocket.readyState) {
209
+ return state.toLowerCase();
210
+ }
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ installEventHandlers() {
216
+ for (let eventName in this.events) {
217
+ const handler = this.events[eventName].bind(this);
218
+ this.webSocket[`on${eventName}`] = handler;
219
+ }
220
+ }
221
+ uninstallEventHandlers() {
222
+ for (let eventName in this.events) {
223
+ this.webSocket[`on${eventName}`] = function() {};
224
+ }
225
+ }
226
+ }
227
+ Connection.reopenDelay = 500;
228
+ Connection.prototype.events = {
229
+ message(event) {
230
+ if (!this.isProtocolSupported()) {
231
+ return;
232
+ }
233
+ const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data);
234
+ switch (type) {
235
+ case message_types.welcome:
236
+ this.monitor.recordConnect();
237
+ return this.subscriptions.reload();
238
+
239
+ case message_types.disconnect:
240
+ logger.log(`Disconnecting. Reason: ${reason}`);
241
+ return this.close({
242
+ allowReconnect: reconnect
243
+ });
244
+
245
+ case message_types.ping:
246
+ return this.monitor.recordPing();
247
+
248
+ case message_types.confirmation:
249
+ this.subscriptions.confirmSubscription(identifier);
250
+ return this.subscriptions.notify(identifier, "connected");
251
+
252
+ case message_types.rejection:
253
+ return this.subscriptions.reject(identifier);
254
+
255
+ default:
256
+ return this.subscriptions.notify(identifier, "received", message);
257
+ }
258
+ },
259
+ open() {
260
+ logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`);
261
+ this.disconnected = false;
262
+ if (!this.isProtocolSupported()) {
263
+ logger.log("Protocol is unsupported. Stopping monitor and disconnecting.");
264
+ return this.close({
265
+ allowReconnect: false
266
+ });
267
+ }
268
+ },
269
+ close(event) {
270
+ logger.log("WebSocket onclose event");
271
+ if (this.disconnected) {
272
+ return;
273
+ }
274
+ this.disconnected = true;
275
+ this.monitor.recordDisconnect();
276
+ return this.subscriptions.notifyAll("disconnected", {
277
+ willAttemptReconnect: this.monitor.isRunning()
278
+ });
279
+ },
280
+ error() {
281
+ logger.log("WebSocket onerror event");
282
+ }
283
+ };
284
+ const extend = function(object, properties) {
285
+ if (properties != null) {
286
+ for (let key in properties) {
287
+ const value = properties[key];
288
+ object[key] = value;
289
+ }
290
+ }
291
+ return object;
292
+ };
293
+ class Subscription {
294
+ constructor(consumer, params = {}, mixin) {
295
+ this.consumer = consumer;
296
+ this.identifier = JSON.stringify(params);
297
+ extend(this, mixin);
298
+ }
299
+ perform(action, data = {}) {
300
+ data.action = action;
301
+ return this.send(data);
302
+ }
303
+ send(data) {
304
+ return this.consumer.send({
305
+ command: "message",
306
+ identifier: this.identifier,
307
+ data: JSON.stringify(data)
308
+ });
309
+ }
310
+ unsubscribe() {
311
+ return this.consumer.subscriptions.remove(this);
312
+ }
313
+ }
314
+ class SubscriptionGuarantor {
315
+ constructor(subscriptions) {
316
+ this.subscriptions = subscriptions;
317
+ this.pendingSubscriptions = [];
318
+ }
319
+ guarantee(subscription) {
320
+ if (this.pendingSubscriptions.indexOf(subscription) == -1) {
321
+ logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`);
322
+ this.pendingSubscriptions.push(subscription);
323
+ } else {
324
+ logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`);
325
+ }
326
+ this.startGuaranteeing();
327
+ }
328
+ forget(subscription) {
329
+ logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`);
330
+ this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription));
331
+ }
332
+ startGuaranteeing() {
333
+ this.stopGuaranteeing();
334
+ this.retrySubscribing();
335
+ }
336
+ stopGuaranteeing() {
337
+ clearTimeout(this.retryTimeout);
338
+ }
339
+ retrySubscribing() {
340
+ this.retryTimeout = setTimeout((() => {
341
+ if (this.subscriptions && typeof this.subscriptions.subscribe === "function") {
342
+ this.pendingSubscriptions.map((subscription => {
343
+ logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`);
344
+ this.subscriptions.subscribe(subscription);
345
+ }));
346
+ }
347
+ }), 500);
348
+ }
349
+ }
350
+ class Subscriptions {
351
+ constructor(consumer) {
352
+ this.consumer = consumer;
353
+ this.guarantor = new SubscriptionGuarantor(this);
354
+ this.subscriptions = [];
355
+ }
356
+ create(channelName, mixin) {
357
+ const channel = channelName;
358
+ const params = typeof channel === "object" ? channel : {
359
+ channel: channel
360
+ };
361
+ const subscription = new Subscription(this.consumer, params, mixin);
362
+ return this.add(subscription);
363
+ }
364
+ add(subscription) {
365
+ this.subscriptions.push(subscription);
366
+ this.consumer.ensureActiveConnection();
367
+ this.notify(subscription, "initialized");
368
+ this.subscribe(subscription);
369
+ return subscription;
370
+ }
371
+ remove(subscription) {
372
+ this.forget(subscription);
373
+ if (!this.findAll(subscription.identifier).length) {
374
+ this.sendCommand(subscription, "unsubscribe");
375
+ }
376
+ return subscription;
377
+ }
378
+ reject(identifier) {
379
+ return this.findAll(identifier).map((subscription => {
380
+ this.forget(subscription);
381
+ this.notify(subscription, "rejected");
382
+ return subscription;
383
+ }));
384
+ }
385
+ forget(subscription) {
386
+ this.guarantor.forget(subscription);
387
+ this.subscriptions = this.subscriptions.filter((s => s !== subscription));
388
+ return subscription;
389
+ }
390
+ findAll(identifier) {
391
+ return this.subscriptions.filter((s => s.identifier === identifier));
392
+ }
393
+ reload() {
394
+ return this.subscriptions.map((subscription => this.subscribe(subscription)));
395
+ }
396
+ notifyAll(callbackName, ...args) {
397
+ return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args)));
398
+ }
399
+ notify(subscription, callbackName, ...args) {
400
+ let subscriptions;
401
+ if (typeof subscription === "string") {
402
+ subscriptions = this.findAll(subscription);
403
+ } else {
404
+ subscriptions = [ subscription ];
405
+ }
406
+ return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined));
407
+ }
408
+ subscribe(subscription) {
409
+ if (this.sendCommand(subscription, "subscribe")) {
410
+ this.guarantor.guarantee(subscription);
411
+ }
412
+ }
413
+ confirmSubscription(identifier) {
414
+ logger.log(`Subscription confirmed ${identifier}`);
415
+ this.findAll(identifier).map((subscription => this.guarantor.forget(subscription)));
416
+ }
417
+ sendCommand(subscription, command) {
418
+ const {identifier: identifier} = subscription;
419
+ return this.consumer.send({
420
+ command: command,
421
+ identifier: identifier
422
+ });
423
+ }
424
+ }
425
+ class Consumer {
426
+ constructor(url) {
427
+ this._url = url;
428
+ this.subscriptions = new Subscriptions(this);
429
+ this.connection = new Connection(this);
430
+ }
431
+ get url() {
432
+ return createWebSocketURL(this._url);
433
+ }
434
+ send(data) {
435
+ return this.connection.send(data);
436
+ }
437
+ connect() {
438
+ return this.connection.open();
439
+ }
440
+ disconnect() {
441
+ return this.connection.close({
442
+ allowReconnect: false
443
+ });
444
+ }
445
+ ensureActiveConnection() {
446
+ if (!this.connection.isActive()) {
447
+ return this.connection.open();
448
+ }
449
+ }
450
+ }
451
+ function createWebSocketURL(url) {
452
+ if (typeof url === "function") {
453
+ url = url();
454
+ }
455
+ if (url && !/^wss?:/i.test(url)) {
456
+ const a = document.createElement("a");
457
+ a.href = url;
458
+ a.href = a.href;
459
+ a.protocol = a.protocol.replace("http", "ws");
460
+ return a.href;
461
+ } else {
462
+ return url;
463
+ }
464
+ }
465
+ function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) {
466
+ return new Consumer(url);
467
+ }
468
+ function getConfig(name) {
469
+ const element = document.head.querySelector(`meta[name='action-cable-${name}']`);
470
+ if (element) {
471
+ return element.getAttribute("content");
472
+ }
473
+ }
474
+ exports.Connection = Connection;
475
+ exports.ConnectionMonitor = ConnectionMonitor;
476
+ exports.Consumer = Consumer;
477
+ exports.INTERNAL = INTERNAL;
478
+ exports.Subscription = Subscription;
479
+ exports.SubscriptionGuarantor = SubscriptionGuarantor;
480
+ exports.Subscriptions = Subscriptions;
481
+ exports.adapters = adapters;
482
+ exports.createConsumer = createConsumer;
483
+ exports.createWebSocketURL = createWebSocketURL;
484
+ exports.getConfig = getConfig;
485
+ exports.logger = logger;
486
+ Object.defineProperty(exports, "__esModule", {
487
+ value: true
488
+ });
489
+ }));