turbo_live 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b117e175ea920117e11f0cd6c3bab5cafc0ca7c1860fb6b890c632a37efe1509
4
- data.tar.gz: ca56b1155834dbeb85a29112a2f85b0901dbf14d61909587f698f45cfc25bb34
3
+ metadata.gz: 5c9a249ff75e9eed862b6d2ab3402d5d867368b4d43e5e1f6679ac9bcabfb3eb
4
+ data.tar.gz: 9d77ef656d61442697be5fb9f060145193636f439e971dd62af36b019354dc22
5
5
  SHA512:
6
- metadata.gz: fce48eb8c3ae06f14fb6bb365004cf399c4f85d4f147776a7b05d6d7ec4b2833182fe6e4fd5d99781ef555daaad2d0bcd4b7b757378c645a7486138c2d356809
7
- data.tar.gz: f0b1875a9143f5ca1ca218c05b1dbd3439ff7ebb13b8aabe9d72fcd58746fe41e8cde508f3a41f138ae3acfb25c66247594c1856397eaa89d2b9f6aaf170bea4
6
+ metadata.gz: 6efe19e261f10de079be70b72404fc441d02e34cb795a8a6a632a3390d5afaeb603c2bf43754ade0fafda03013cf947d9bc284fd0b2945e25306e1a73cb66890
7
+ data.tar.gz: 4abda82e8d71fbcc3275cfec205d0ea4197c814710b7f12e5cae69a5e359455996e6eb207b0035be6e6ad8156aafc6797c854f33d4f25a93e22f9222e8913011
data/README.md CHANGED
@@ -214,6 +214,7 @@ Common issues and their solutions:
214
214
  1. **Component not updating**: Ensure that your `update` method is correctly handling the event and modifying the state.
215
215
  2. **WebSocket connection failing**: Check your ActionCable configuration and ensure that your server supports WebSocket connections.
216
216
  3. **JavaScript errors**: Make sure you've correctly set up the TurboLive JavaScript integration in your application.
217
+ 3. **My timed events won't go away**: Due to the use of morphing, there might be instances where your some meta attributes are not removed.
217
218
 
218
219
  For more issues, please check our [FAQ](https://github.com/radioactive-labs/turbo_live/wiki/FAQ) or open an issue on GitHub.
219
220
 
@@ -7,8 +7,8 @@ module TurboLive
7
7
  end
8
8
 
9
9
  def receive(params)
10
- stream = Renderer.render params
11
- ActionCable.server.broadcast(stream_name, stream)
10
+ stream = Renderer.render params.symbolize_keys
11
+ ActionCable.server.broadcast(stream_name, stream) if stream
12
12
  end
13
13
 
14
14
  protected
@@ -4,7 +4,7 @@ module TurboLive
4
4
  class ComponentsController < ActionController::API
5
5
  def update
6
6
  stream = Renderer.render params.to_unsafe_hash
7
- render plain: stream
7
+ render plain: stream if stream
8
8
  end
9
9
  end
10
10
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "phlex"
4
+ require "literal"
4
5
 
5
6
  module TurboLive
6
7
  class Component < Phlex::HTML
7
8
  extend Literal::Properties
8
9
 
9
- SUPPORTED_EVENTS = %i[click change].freeze
10
+ SUPPORTED_EVENTS = %i[click change input].freeze
10
11
 
11
12
  def self.state(name, type, **options, &block)
12
13
  options = {reader: :public, writer: :protected}.merge(**options).compact
@@ -52,27 +53,33 @@ module TurboLive
52
53
  end
53
54
 
54
55
  def every(milliseconds, event)
55
- data = {milliseconds => to_verifiable(event)}.to_json
56
- add_data :every, data
56
+ data = {interval: milliseconds, event: to_verifiable(event)}.to_json
57
+ add_meta :interval, data
58
+ end
59
+
60
+ def norender
61
+ SKIP_RENDER
62
+ end
63
+
64
+ def norender!
65
+ raise SkipRender
57
66
  end
58
67
 
59
68
  private
60
69
 
61
- def add_data(type, value)
62
- # Temporary hack to embed data.
63
- # Switch to HTML templates
70
+ def add_meta(type, value)
71
+ # TODO: turbo morph does some wonky things issues here since it doesn't force a replacement everytime
64
72
  div(
65
- class: "turbo-live-data",
66
- data_turbo_live_id: verifiable_live_id,
67
- data_turbo_live_data_type: type,
68
- data_turbo_live_data_value: value,
73
+ data_turbo_live_meta_type: type,
74
+ data_turbo_live_meta_value: value,
75
+ data_turbo_live_target: "meta",
69
76
  style: "display: none;", display: :none
70
77
  ) {}
71
78
  end
72
79
 
73
80
  def serialize
74
81
  state = self.class.literal_properties.map do |prop|
75
- [prop.name, instance_variable_get(:"@#{prop.name}")]
82
+ [prop.name, public_send(prop.name)]
76
83
  end.to_h
77
84
 
78
85
  {klass: self.class.to_s, state: state}
@@ -4,21 +4,24 @@ module TurboLive
4
4
  class Renderer
5
5
  class << self
6
6
  def render(data)
7
- data = data.symbolize_keys
8
7
  # build the payload
9
8
  payload = extract_payload(data)
10
9
  # create the component
11
10
  component = build_component(data)
12
11
  # run the update function
13
- component.update payload
12
+ result = component.update payload
13
+ return if result == TurboLive::SKIP_RENDER
14
+
14
15
  # render the replace stream
15
16
  <<~STREAM
16
- <turbo-stream action="replace" target="#{data[:id]}">
17
+ <turbo-stream action="replace" method="morph" target="#{data[:id]}">
17
18
  <template>
18
19
  #{component.call}
19
20
  </template>
20
21
  </turbo-stream>
21
22
  STREAM
23
+ rescue SkipRender
24
+ nil
22
25
  end
23
26
 
24
27
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurboLive
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/turbo_live.rb CHANGED
@@ -10,6 +10,10 @@ require_relative "../app/channels/components_channel" if defined?(ActionCable)
10
10
  module TurboLive
11
11
  class Error < StandardError; end
12
12
 
13
+ class SkipRender < StandardError; end
14
+
15
+ SKIP_RENDER = :skip_render
16
+
13
17
  class << self
14
18
  attr_writer :verifier_key
15
19
 
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/turbo-live",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/turbo-live",
9
- "version": "0.1.2",
9
+ "version": "0.1.3",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@hotwired/stimulus": "^3.2.2",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/turbo-live",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Async, progressively enhanced, live components for Ruby applications that work over Websockets and HTTP.",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -5,112 +5,132 @@ export default class extends Controller {
5
5
  id: String,
6
6
  component: String,
7
7
  }
8
+ static targets = ["meta"]
8
9
 
9
10
  get component() {
10
11
  return this.componentValue
11
12
  }
12
13
 
14
+ initialize() {
15
+ this.metaTargetsMap = new WeakMap()
16
+ this.metaTargetsCount = 0
17
+ this.intervals = {}
18
+ }
19
+
13
20
  connect() {
14
- console.log("TurboLiveController connected:", this.element.id, this.component)
21
+ console.log("TurboLiveController connected", this.element.id, this.component)
22
+ }
15
23
 
16
- this.intervals = []
24
+ metaTargetConnected(target) {
25
+ console.log("TurboLiveController metaTargetConnected", this.element.id, this.#metaTargetId(target))
26
+ this.#readMetadata(target)
27
+ }
17
28
 
18
- this.#readEmbeddedData()
29
+ metaTargetDisconnected(target) {
30
+ console.log("TurboLiveController metaTargetDisconnected", this.element.id, this.#metaTargetId(target))
31
+ this.#teardownInterval(this.#metaTargetId(target))
19
32
  }
20
33
 
21
34
  disconnect() {
22
- console.log("TurboLiveController disconnected")
23
- this.#clearIntervals()
35
+ console.log("TurboLiveController disconnected", this.element.id)
36
+ this.#cleanup()
24
37
  }
25
38
 
26
39
  dispatch(event, payload) {
27
- console.log("TurboLiveController dispatch:", this.element.id, event, payload)
28
- let data = { id: this.element.id, event: event, payload: payload, component: this.component }
40
+ console.log("TurboLiveController dispatch", this.element.id, event, payload)
41
+ const data = { id: this.element.id, event, payload, component: this.component }
42
+
29
43
  if (window.turboLive) {
30
- console.log("TurboLiveController dispatching via websockets")
44
+ console.log("TurboLiveController dispatching via websockets", this.element.id)
31
45
  window.turboLive.send(data)
32
- }
33
- else {
34
- console.log("TurboLiveController dispatching via HTTP")
35
-
36
- fetch('/turbo_live', {
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- },
41
- body: JSON.stringify(data)
42
- })
43
- .then(response => {
44
- if (!response.ok) {
45
- throw new Error(`Network response was not OK`);
46
- }
47
- return response.text();
48
- })
49
- .then(turbo_stream => {
50
- console.log('TurboLiveController dispatch success:', turbo_stream);
51
- Turbo.renderStreamMessage(turbo_stream);
52
- })
53
- .catch((error) => {
54
- console.error('TurboLiveController dispatch error:', error);
55
- });
46
+ } else {
47
+ console.log("TurboLiveController dispatching via HTTP", this.element.id)
48
+ this.#dispatchHTTP(data)
56
49
  }
57
50
  }
58
51
 
59
52
  onClick(event) {
60
- // event.preventDefault();
61
- console.log("TurboLiveController onClick")
53
+ console.log("TurboLiveController onClick", this.element.id)
62
54
  this.#dispatchSimpleEvent("click", event)
63
55
  }
64
56
 
65
57
  onChange(event) {
66
- // event.preventDefault();
67
- console.log("TurboLiveController onChange")
58
+ console.log("TurboLiveController onChange", this.element.id)
68
59
  this.#dispatchValueEvent("change", event)
69
60
  }
70
61
 
71
- #readEmbeddedData() {
72
- this.element.querySelectorAll(".turbo-live-data").forEach((element) => {
73
- if (this.element.id != element.dataset.turboLiveId) return;
62
+ onInput(event) {
63
+ console.log("TurboLiveController onInput", this.element.id)
64
+ this.#dispatchValueEvent("input", event)
65
+ }
74
66
 
75
- let type = element.dataset.turboLiveDataType
76
- let value = JSON.parse(element.dataset.turboLiveDataValue)
77
- switch (type) {
78
- case "every":
79
- this.#setupInterval(value)
80
- break;
81
- }
82
- })
67
+ #metaTargetId(target) {
68
+ if (!this.metaTargetsMap.has(target)) {
69
+ this.metaTargetsMap.set(target, ++this.metaTargetsCount)
70
+ }
71
+ return this.metaTargetsMap.get(target)
83
72
  }
84
73
 
85
- #setupInterval(intervalConfig) {
86
- try {
87
- for (let interval in intervalConfig) {
88
- this.intervals.push(
89
- setInterval(() => {
90
- this.dispatch("every", [intervalConfig[interval]])
91
- }, interval)
92
- )
93
- }
74
+ #readMetadata(element) {
75
+ const type = element.dataset.turboLiveMetaType
76
+ if (type === "interval") {
77
+ this.#setupInterval(element)
94
78
  }
95
- catch (e) {
79
+ }
80
+
81
+ #setupInterval(element) {
82
+ try {
83
+ const config = JSON.parse(element.dataset.turboLiveMetaValue)
84
+ this.intervals[this.#metaTargetId(element)] = setInterval(() => {
85
+ this.dispatch("interval", [config.event])
86
+ }, config.interval)
87
+ } catch (e) {
96
88
  console.error(e)
97
89
  }
98
90
  }
99
91
 
100
- #clearIntervals() {
101
- this.intervals.forEach((interval) => {
102
- clearInterval(interval)
103
- })
92
+ #teardownInterval(id) {
93
+ if (this.intervals[id]) {
94
+ clearInterval(this.intervals[id])
95
+ delete this.intervals[id]
96
+ }
97
+ }
98
+
99
+ #cleanup() {
100
+ Object.keys(this.intervals).forEach(this.#teardownInterval.bind(this))
104
101
  }
105
102
 
106
103
  #dispatchSimpleEvent(name, { params }) {
107
- let live_event = params[name]
108
- this.dispatch(name, [live_event])
104
+ const liveEvent = params[name]
105
+ this.dispatch(name, [liveEvent])
109
106
  }
110
107
 
111
108
  #dispatchValueEvent(name, { params, target }) {
112
- let value = target.value
113
- let live_event = params[name]
114
- this.dispatch(name, [live_event, value])
109
+ const value = target.value
110
+ const liveEvent = params[name]
111
+ this.dispatch(name, [liveEvent, value])
112
+ }
113
+
114
+ #dispatchHTTP(data) {
115
+ fetch('/turbo_live', {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ },
120
+ body: JSON.stringify(data)
121
+ })
122
+ .then(response => {
123
+ if (!response.ok) {
124
+ throw new Error(`Network response was not OK`)
125
+ }
126
+ return response.text()
127
+ })
128
+ .then(turboStream => {
129
+ console.log('TurboLiveController dispatch success', this.element.id, turboStream)
130
+ if (turboStream) Turbo.renderStreamMessage(turboStream)
131
+ })
132
+ .catch((error) => {
133
+ console.error('TurboLiveController dispatch error', this.element.id, error)
134
+ })
115
135
  }
116
- }
136
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_live
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheDumbTechGuy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-13 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex-rails