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 +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
|