turbo_live 0.1.2 → 0.1.3
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/README.md +1 -0
- data/app/channels/components_channel.rb +2 -2
- data/app/controllers/turbo_live/components_controller.rb +1 -1
- data/lib/turbo_live/component.rb +18 -11
- data/lib/turbo_live/renderer.rb +6 -3
- data/lib/turbo_live/version.rb +1 -1
- data/lib/turbo_live.rb +4 -0
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/src/js/controllers/turbo_live_controller.js +87 -67
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c9a249ff75e9eed862b6d2ab3402d5d867368b4d43e5e1f6679ac9bcabfb3eb
|
4
|
+
data.tar.gz: 9d77ef656d61442697be5fb9f060145193636f439e971dd62af36b019354dc22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/turbo_live/component.rb
CHANGED
@@ -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
|
56
|
-
|
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
|
62
|
-
#
|
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
|
-
|
66
|
-
|
67
|
-
|
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,
|
82
|
+
[prop.name, public_send(prop.name)]
|
76
83
|
end.to_h
|
77
84
|
|
78
85
|
{klass: self.class.to_s, state: state}
|
data/lib/turbo_live/renderer.rb
CHANGED
@@ -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
|
data/lib/turbo_live/version.rb
CHANGED
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.
|
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.
|
9
|
+
"version": "0.1.3",
|
10
10
|
"license": "MIT",
|
11
11
|
"dependencies": {
|
12
12
|
"@hotwired/stimulus": "^3.2.2",
|
data/package.json
CHANGED
@@ -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
|
21
|
+
console.log("TurboLiveController connected", this.element.id, this.component)
|
22
|
+
}
|
15
23
|
|
16
|
-
|
24
|
+
metaTargetConnected(target) {
|
25
|
+
console.log("TurboLiveController metaTargetConnected", this.element.id, this.#metaTargetId(target))
|
26
|
+
this.#readMetadata(target)
|
27
|
+
}
|
17
28
|
|
18
|
-
|
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.#
|
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
|
28
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
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
|
-
|
67
|
-
console.log("TurboLiveController onChange")
|
58
|
+
console.log("TurboLiveController onChange", this.element.id)
|
68
59
|
this.#dispatchValueEvent("change", event)
|
69
60
|
}
|
70
61
|
|
71
|
-
|
72
|
-
|
73
|
-
|
62
|
+
onInput(event) {
|
63
|
+
console.log("TurboLiveController onInput", this.element.id)
|
64
|
+
this.#dispatchValueEvent("input", event)
|
65
|
+
}
|
74
66
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
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
|
-
#
|
101
|
-
this.intervals
|
102
|
-
clearInterval(
|
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
|
-
|
108
|
-
this.dispatch(name, [
|
104
|
+
const liveEvent = params[name]
|
105
|
+
this.dispatch(name, [liveEvent])
|
109
106
|
}
|
110
107
|
|
111
108
|
#dispatchValueEvent(name, { params, target }) {
|
112
|
-
|
113
|
-
|
114
|
-
this.dispatch(name, [
|
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.
|
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-
|
11
|
+
date: 2024-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex-rails
|