turbo_live 0.1.3 → 0.2.1

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: 5c9a249ff75e9eed862b6d2ab3402d5d867368b4d43e5e1f6679ac9bcabfb3eb
4
- data.tar.gz: 9d77ef656d61442697be5fb9f060145193636f439e971dd62af36b019354dc22
3
+ metadata.gz: f8bb20b23ebd77936e9d016fc914bad406118d259fc7e21b814ab21bd162dd48
4
+ data.tar.gz: b34976b5f6b8ba0e48a51f26d7fee4f13c2870631c5fdab88860e16db49d9bb0
5
5
  SHA512:
6
- metadata.gz: 6efe19e261f10de079be70b72404fc441d02e34cb795a8a6a632a3390d5afaeb603c2bf43754ade0fafda03013cf947d9bc284fd0b2945e25306e1a73cb66890
7
- data.tar.gz: 4abda82e8d71fbcc3275cfec205d0ea4197c814710b7f12e5cae69a5e359455996e6eb207b0035be6e6ad8156aafc6797c854f33d4f25a93e22f9222e8913011
6
+ metadata.gz: f23e8d630a733dad9c92b6ad79458213aa97b0f7f1c527a1e30b4b65914e62675ffeda07e3a272cdd8e8d924500c8fe36ae16c712922788b50b4939bcedad464
7
+ data.tar.gz: 266d2bcc3e8b82977ee5474dee219b88ce3e0d82b6de7258f231ec302807e87b8479422b4c124a8b3c05d1792d4293e01d81460b66809b1dfa7d4ea969b7923b
data/README.md CHANGED
@@ -46,7 +46,13 @@ For importmaps:
46
46
  bin/importmap pin @radioactive-labs/turbo-live
47
47
  ```
48
48
 
49
- For npm:
49
+ For yarn:
50
+
51
+ ```console
52
+ yarn add @radioactive-labs/turbo-live
53
+ ```
54
+
55
+ Or YAR:
50
56
 
51
57
  ```console
52
58
  npm install @radioactive-labs/turbo-live
@@ -54,6 +60,21 @@ npm install @radioactive-labs/turbo-live
54
60
 
55
61
  ## Setup
56
62
 
63
+ ### Rails Routes
64
+
65
+ In your rails routes, mount the engine:
66
+
67
+ ```diff
68
+ Rails.application.routes.draw do
69
+ # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
70
+
71
+ + mount TurboLive::Engine => "/turbo_live"
72
+
73
+ # Defines the root path route ("/")
74
+ root "index#index"
75
+ end
76
+ ```
77
+
57
78
  ### Stimulus Controller
58
79
 
59
80
  TurboLive uses a Stimulus controller to manage interactions. In your `app/javascript/controllers/index.js`:
@@ -61,9 +82,10 @@ TurboLive uses a Stimulus controller to manage interactions. In your `app/javasc
61
82
  ```diff
62
83
  import { application } from "controllers/application"
63
84
  import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
64
- +import * as turboLive from "@radioactive-labs/turbo-live"
65
85
 
66
86
  eagerLoadControllersFrom("controllers", application)
87
+
88
+ +import * as turboLive from "@radioactive-labs/turbo-live"
67
89
  +turboLive.registerControllers(application)
68
90
  ```
69
91
 
@@ -139,9 +161,9 @@ Handle events in the `update` method:
139
161
  ```ruby
140
162
  def update(input)
141
163
  case input
142
- in [:increment]
164
+ in :increment
143
165
  self.count += 1
144
- in [:decrement]
166
+ in :decrement
145
167
  self.count -= 1
146
168
  end
147
169
  end
@@ -165,7 +187,21 @@ You can also emit compound events that carry extra data:
165
187
  button(**on(click: [:change_value, 1])) { "+" }
166
188
  ```
167
189
 
168
- > Note: Currently, only `:click` and `:change` events are supported.
190
+ Certain events carry extra data as well, such as `input` and `change` events.
191
+
192
+ ```ruby
193
+ input(value:, input_value, **on(input: :input_changed))
194
+ ```
195
+
196
+ ```ruby
197
+ def update(input)
198
+ case input
199
+ in [:input_changed, value]
200
+ self.input_value = value
201
+ end
202
+ end
203
+
204
+ > Note: Currently, only `:click`, `:input` and `:change` events are supported.
169
205
 
170
206
  ### Timed Events
171
207
 
@@ -201,7 +237,7 @@ class CounterComponentTest < ActiveSupport::TestCase
201
237
  test "increments count" do
202
238
  component = CounterComponent.new
203
239
  assert_equal 0, component.count
204
- component.update([:increment])
240
+ component.update(:increment)
205
241
  assert_equal 1, component.count
206
242
  end
207
243
  end
@@ -14,9 +14,9 @@ class CountdownComponent < TurboLive::Component
14
14
 
15
15
  def update(input)
16
16
  case input
17
- in [:countdown]
17
+ in :countdown
18
18
  self.countdown -= 1
19
- in [:start]
19
+ in :start
20
20
  self.countdown = 1000
21
21
  end
22
22
  end
@@ -15,11 +15,11 @@ class CounterComponent < TurboLive::Component
15
15
 
16
16
  def update(input)
17
17
  case input
18
- in [:decrement]
18
+ in :decrement
19
19
  self.count -= 1
20
- in [:increment]
20
+ in :increment
21
21
  self.count += 1
22
- in [:reset]
22
+ in :reset
23
23
  self.count = 0
24
24
  end
25
25
  end
@@ -0,0 +1,143 @@
1
+ class FlappyBirdComponent < TurboLive::Component
2
+ GRAVITY = 0.5
3
+ JUMP_STRENGTH = -10.0
4
+ GAME_HEIGHT = 400
5
+ GAME_WIDTH = 300
6
+ BIRD_SIZE = 20
7
+ OBSTACLE_WIDTH = 50
8
+ GAP_HEIGHT = 100
9
+ OBSTACLE_SPEED = 2
10
+
11
+ state :bird_y, Float do |value|
12
+ value || 150.0
13
+ end
14
+
15
+ state :bird_velocity, Float do |value|
16
+ value || 0.0
17
+ end
18
+
19
+ state :obstacles, Array do |value|
20
+ value || []
21
+ end
22
+
23
+ state :score, Integer do |value|
24
+ value || 0
25
+ end
26
+
27
+ state :game_over, _Boolean
28
+
29
+ state :nonce, Integer do |value|
30
+ value || 0
31
+ end
32
+
33
+ NONCES = {}
34
+
35
+ def initialize(...)
36
+ super
37
+ NONCES[live_id] ||= 0
38
+ end
39
+
40
+ def view
41
+ div(class: "flappy-bird-game") do
42
+ render_game
43
+ render_controls
44
+ end
45
+ end
46
+
47
+ def render_game
48
+ svg(viewBox: "0 0 #{GAME_WIDTH} #{GAME_HEIGHT}", width: GAME_WIDTH, height: GAME_HEIGHT) do |svg|
49
+ # Render bird
50
+ svg.circle(cx: 50, cy: bird_y, r: BIRD_SIZE / 2, fill: "yellow")
51
+
52
+ # Render obstacles
53
+ obstacles.each do |obstacle|
54
+ svg.rect(x: obstacle[:x], y: 0, width: OBSTACLE_WIDTH, height: obstacle[:top], fill: "green")
55
+ svg.rect(x: obstacle[:x], y: obstacle[:bottom], width: OBSTACLE_WIDTH, height: GAME_HEIGHT - obstacle[:bottom], fill: "green")
56
+ end
57
+
58
+ # Render score
59
+ svg.text(x: 10, y: 30, fill: "white", "font-size": "20px") { score.to_s }
60
+
61
+ # Render game over message
62
+ if game_over
63
+ svg.text(x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2, fill: "red", "font-size": "30px", "text-anchor": "middle") { "Game Over" }
64
+ else
65
+ every(1000 / 30.0, :tick)
66
+ end
67
+ end
68
+ end
69
+
70
+ def render_controls
71
+ div(class: "controls") do
72
+ button(**on(click: :jump)) { "Jump / Restart" }
73
+ end
74
+ end
75
+
76
+ def update(input)
77
+ case input
78
+ in :jump
79
+ if game_over
80
+ reset_game
81
+ else
82
+ self.nonce = NONCES[live_id] = NONCES[live_id] + 1
83
+ self.bird_velocity = JUMP_STRENGTH
84
+ end
85
+ in :tick
86
+ norender! if nonce < NONCES[live_id]
87
+ update_game_state
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def update_game_state
94
+ return if game_over
95
+
96
+ # Update bird position
97
+ self.bird_velocity += GRAVITY
98
+ self.bird_y += bird_velocity
99
+
100
+ # Add new obstacles
101
+ if obstacles.empty? || obstacles.last[:x] < GAME_WIDTH - 200
102
+ add_obstacle
103
+ end
104
+
105
+ # Update obstacle positions
106
+ self.obstacles = obstacles.map do |obstacle|
107
+ obstacle[:x] -= OBSTACLE_SPEED
108
+ obstacle
109
+ end.reject { |obstacle| obstacle[:x] < -OBSTACLE_WIDTH }
110
+
111
+ # Check collisions
112
+ check_collisions
113
+
114
+ # Update score
115
+ self.score += 1 if obstacles.any? { |obstacle| obstacle[:x] == 48 } # Bird's x position is 50, width is 20
116
+ end
117
+
118
+ def add_obstacle
119
+ gap_start = rand(50..(GAME_HEIGHT - GAP_HEIGHT - 50))
120
+ obstacles << {x: GAME_WIDTH, top: gap_start, bottom: gap_start + GAP_HEIGHT}
121
+ end
122
+
123
+ def check_collisions
124
+ if bird_y < 0 || bird_y > GAME_HEIGHT
125
+ self.game_over = true
126
+ end
127
+
128
+ obstacles.each do |obstacle|
129
+ if (obstacle[:x] < 70 && obstacle[:x] > 30) &&
130
+ (bird_y < obstacle[:top] + BIRD_SIZE / 2 || bird_y > obstacle[:bottom] - BIRD_SIZE / 2)
131
+ self.game_over = true
132
+ end
133
+ end
134
+ end
135
+
136
+ def reset_game
137
+ self.bird_y = 150.0
138
+ self.bird_velocity = 0.0
139
+ self.obstacles = []
140
+ self.score = 0
141
+ self.game_over = false
142
+ end
143
+ end
@@ -1,5 +1,7 @@
1
1
  class ShowcaseComponent < TurboLive::Component
2
- state :component, Symbol
2
+ state :component, Symbol do |value|
3
+ value || :counter
4
+ end
3
5
 
4
6
  def view
5
7
  div class: "container" do
@@ -9,6 +11,8 @@ class ShowcaseComponent < TurboLive::Component
9
11
  li { button(**on(click: [:change_component, :counter])) { "Counter" } }
10
12
  li { button(**on(click: [:change_component, :countdown])) { "Countdown" } }
11
13
  li { button(**on(click: [:change_component, :tic_tac_toe])) { "TicTacToe" } }
14
+ li { button(**on(click: [:change_component, :flappy_bird])) { "Flappy Bird" } }
15
+ li { button(**on(click: [:change_component, :form])) { "Form" } }
12
16
  end
13
17
  end
14
18
  div class: "right-column" do
@@ -21,7 +25,7 @@ class ShowcaseComponent < TurboLive::Component
21
25
 
22
26
  def update(input)
23
27
  case input
24
- in [[:change_component, component]]
28
+ in [:change_component, component]
25
29
  self.component = component
26
30
  end
27
31
  end
@@ -30,12 +34,16 @@ class ShowcaseComponent < TurboLive::Component
30
34
 
31
35
  def selected_component
32
36
  case component
37
+ when :counter
38
+ CounterComponent
33
39
  when :countdown
34
40
  CountdownComponent
35
41
  when :tic_tac_toe
36
42
  TicTacToeComponent
37
- else
38
- CounterComponent
43
+ when :flappy_bird
44
+ FlappyBirdComponent
45
+ when :form
46
+ FormComponent
39
47
  end
40
48
  end
41
49
  end
@@ -50,9 +50,9 @@ class TicTacToeComponent < TurboLive::Component
50
50
 
51
51
  def update(input)
52
52
  case input
53
- in [[:make_move, index]]
53
+ in [:make_move, index]
54
54
  make_move(index)
55
- in [:reset_game]
55
+ in :reset_game
56
56
  reset_game
57
57
  end
58
58
  end
@@ -23,7 +23,8 @@ module TurboLive
23
23
  id: verifiable_live_id,
24
24
  style: "display: contents;",
25
25
  data_controller: "turbo-live",
26
- data_turbo_live_component_value: to_verifiable(serialize)
26
+ data_turbo_live_component_value: to_verifiable(serialize),
27
+ data_turbo_live_protocol_version_value: TurboLive::PROTOCOL_VERSION
27
28
  ) do
28
29
  view
29
30
  end
@@ -67,12 +68,13 @@ module TurboLive
67
68
 
68
69
  private
69
70
 
70
- def add_meta(type, value)
71
- # TODO: turbo morph does some wonky things issues here since it doesn't force a replacement everytime
71
+ def add_meta(type, data)
72
72
  div(
73
- data_turbo_live_meta_type: type,
74
- data_turbo_live_meta_value: value,
73
+ data_controller: "turbo-live-meta-data",
75
74
  data_turbo_live_target: "meta",
75
+ data_turbo_live_meta_data_type_value: type,
76
+ data_turbo_live_meta_data_data_value: data,
77
+ data_action: "turbo:before-morph-element->turbo-live-meta-data#beforeMorph turbo:morph-element->turbo-live-meta-data#afterMorph",
76
78
  style: "display: none;", display: :none
77
79
  ) {}
78
80
  end
@@ -31,7 +31,7 @@ module TurboLive
31
31
  if data[:payload].size == 2
32
32
  [payload_event, data[:payload][1]]
33
33
  else
34
- [payload_event]
34
+ payload_event
35
35
  end
36
36
  end
37
37
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurboLive
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/turbo_live.rb CHANGED
@@ -5,13 +5,14 @@ require_relative "turbo_live/component"
5
5
  require_relative "turbo_live/renderer"
6
6
 
7
7
  require_relative "turbo_live/engine" if defined?(Rails)
8
- require_relative "../app/channels/components_channel" if defined?(ActionCable)
8
+ require_relative "../app/channels/turbo_live/components_channel" if defined?(ActionCable::Channel::Base)
9
9
 
10
10
  module TurboLive
11
11
  class Error < StandardError; end
12
12
 
13
13
  class SkipRender < StandardError; end
14
14
 
15
+ PROTOCOL_VERSION = "0.2.0"
15
16
  SKIP_RENDER = :skip_render
16
17
 
17
18
  class << self
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/turbo-live",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/turbo-live",
9
- "version": "0.1.3",
9
+ "version": "0.2.0",
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.3",
3
+ "version": "0.2.0",
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",
@@ -1,30 +1,32 @@
1
+ import { logger } from "../logger.js"
2
+
1
3
  export default function (consumer) {
2
4
  consumer.subscriptions.create({ channel: "TurboLive::ComponentsChannel" }, {
3
5
  // Called once when the subscription is created.
4
6
  initialized() {
5
- console.log("TurboLiveChannel initialized")
7
+ logger.debug("TurboLiveChannel initialized")
6
8
  },
7
9
 
8
10
  // Called when the subscription is ready for use on the server.
9
11
  connected() {
10
- console.log("TurboLiveChannel connected")
12
+ logger.debug("TurboLiveChannel connected")
11
13
  window.turboLive = this;
12
14
  },
13
15
 
14
16
  received(turbo_stream) {
15
- console.log("TurboLiveChannel received", turbo_stream)
17
+ logger.info("TurboLiveChannel received", turbo_stream)
16
18
  Turbo.renderStreamMessage(turbo_stream);
17
19
  },
18
20
 
19
21
  // Called when the WebSocket connection is closed.
20
22
  disconnected() {
21
- console.log("TurboLiveChannel disconnected")
23
+ logger.debug("TurboLiveChannel disconnected")
22
24
  window.turboLive = null;
23
25
  },
24
26
 
25
27
  // Called when the subscription is rejected by the server.
26
28
  rejected() {
27
- console.log("TurboLiveChannel rejected")
29
+ logger.debug("TurboLiveChannel rejected")
28
30
  window.turboLive = null;
29
31
  },
30
32
  })
@@ -1,7 +1,9 @@
1
1
  // Import controllers here
2
2
  import TurboLiveController from "./turbo_live_controller.js"
3
+ import TurboLiveMetaDataController from "./turbo_live_meta_data_controller.js"
3
4
 
4
5
  export default function (application) {
5
6
  // Register controllers here
6
7
  application.register("turbo-live", TurboLiveController)
8
+ application.register("turbo-live-meta-data", TurboLiveMetaDataController)
7
9
  }
@@ -1,9 +1,13 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
+ import { logger } from "../logger.js"
3
+
4
+ const SupportedProtocolVersion = "0.2.0"
2
5
 
3
6
  export default class extends Controller {
4
7
  static values = {
5
8
  id: String,
6
9
  component: String,
10
+ protocolVersion: String,
7
11
  }
8
12
  static targets = ["meta"]
9
13
 
@@ -11,101 +15,59 @@ export default class extends Controller {
11
15
  return this.componentValue
12
16
  }
13
17
 
14
- initialize() {
15
- this.metaTargetsMap = new WeakMap()
16
- this.metaTargetsCount = 0
17
- this.intervals = {}
18
- }
19
-
20
18
  connect() {
21
- console.log("TurboLiveController connected", this.element.id, this.component)
19
+ logger.log("TurboLiveController connect", this.element.id, this.protocolVersionValue)
20
+ if (SupportedProtocolVersion != this.protocolVersionValue) {
21
+ throw Error(`
22
+ Protocol version ${this.protocolVersionValue} is not supported
23
+ (supported version: ${SupportedProtocolVersion})
24
+ `)
25
+ }
22
26
  }
23
27
 
24
28
  metaTargetConnected(target) {
25
- console.log("TurboLiveController metaTargetConnected", this.element.id, this.#metaTargetId(target))
26
- this.#readMetadata(target)
27
- }
28
-
29
- metaTargetDisconnected(target) {
30
- console.log("TurboLiveController metaTargetDisconnected", this.element.id, this.#metaTargetId(target))
31
- this.#teardownInterval(this.#metaTargetId(target))
32
- }
33
-
34
- disconnect() {
35
- console.log("TurboLiveController disconnected", this.element.id)
36
- this.#cleanup()
29
+ logger.debug("TurboLiveController metaTargetConnected", this.element.id, target)
30
+ this.application
31
+ .getControllerForElementAndIdentifier(target, "turbo-live-meta-data")
32
+ .setComponent(this)
37
33
  }
38
34
 
39
35
  dispatch(event, payload) {
40
- console.log("TurboLiveController dispatch", this.element.id, event, payload)
41
36
  const data = { id: this.element.id, event, payload, component: this.component }
37
+ logger.info("TurboLiveController dispatching", this.element.id, data)
42
38
 
43
39
  if (window.turboLive) {
44
- console.log("TurboLiveController dispatching via websockets", this.element.id)
40
+ logger.debug("TurboLiveController dispatching via", this.element.id, "websockets")
45
41
  window.turboLive.send(data)
46
42
  } else {
47
- console.log("TurboLiveController dispatching via HTTP", this.element.id)
43
+ logger.debug("TurboLiveController dispatching via", this.element.id, "http")
48
44
  this.#dispatchHTTP(data)
49
45
  }
50
46
  }
51
47
 
52
48
  onClick(event) {
53
- console.log("TurboLiveController onClick", this.element.id)
49
+ logger.debug("TurboLiveController onClick", this.element.id, event)
54
50
  this.#dispatchSimpleEvent("click", event)
55
51
  }
56
52
 
57
53
  onChange(event) {
58
- console.log("TurboLiveController onChange", this.element.id)
54
+ logger.debug("TurboLiveController onChange", this.element.id, event)
59
55
  this.#dispatchValueEvent("change", event)
60
56
  }
61
57
 
62
58
  onInput(event) {
63
- console.log("TurboLiveController onInput", this.element.id)
59
+ logger.debug("TurboLiveController onInput", this.element.id, event)
64
60
  this.#dispatchValueEvent("input", event)
65
61
  }
66
62
 
67
- #metaTargetId(target) {
68
- if (!this.metaTargetsMap.has(target)) {
69
- this.metaTargetsMap.set(target, ++this.metaTargetsCount)
70
- }
71
- return this.metaTargetsMap.get(target)
72
- }
73
-
74
- #readMetadata(element) {
75
- const type = element.dataset.turboLiveMetaType
76
- if (type === "interval") {
77
- this.#setupInterval(element)
78
- }
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) {
88
- console.error(e)
89
- }
90
- }
91
-
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))
101
- }
102
-
103
63
  #dispatchSimpleEvent(name, { params }) {
64
+ logger.debug("TurboLiveController dispatchSimpleEvent", this.element.id, name, params)
104
65
  const liveEvent = params[name]
105
66
  this.dispatch(name, [liveEvent])
106
67
  }
107
68
 
108
69
  #dispatchValueEvent(name, { params, target }) {
70
+ logger.debug("TurboLiveController dispatchValueEvent", this.element.id, name, params)
109
71
  const value = target.value
110
72
  const liveEvent = params[name]
111
73
  this.dispatch(name, [liveEvent, value])
@@ -126,11 +88,11 @@ export default class extends Controller {
126
88
  return response.text()
127
89
  })
128
90
  .then(turboStream => {
129
- console.log('TurboLiveController dispatch success', this.element.id, turboStream)
91
+ logger.info('TurboLiveController dispatch success', this.element.id, turboStream)
130
92
  if (turboStream) Turbo.renderStreamMessage(turboStream)
131
93
  })
132
94
  .catch((error) => {
133
- console.error('TurboLiveController dispatch error', this.element.id, error)
95
+ logger.error('TurboLiveController dispatch error', this.element.id, error)
134
96
  })
135
97
  }
136
98
  }
@@ -0,0 +1,69 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { logger } from "../logger.js"
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ type: String,
7
+ data: String,
8
+ }
9
+
10
+ connect() {
11
+ logger.debug("TurboLiveMetaDataController connected", this.typeValue, this.dataValue)
12
+ this.#setup(this.typeValue, this.dataValue)
13
+ }
14
+
15
+ disconnect() {
16
+ logger.debug("TurboLiveMetaDataController disconnected", this.typeValue, this.dataValue)
17
+ this.#teardown()
18
+ this.component = null
19
+ }
20
+
21
+ beforeMorph({ detail: { currentElement, newElement } }) {
22
+ logger.debug("TurboLiveMetaDataController beforeMorph", this.typeValue, this.dataValue)
23
+ if (currentElement.dataset.turboLiveMetaType != newElement.dataset.turboLiveMetaType ||
24
+ currentElement.dataset.turboLiveMetaValue != newElement.dataset.turboLiveMetaValue) {
25
+ logger.info("TurboLiveMetaDataController changed",
26
+ this.typeValue, this.dataValue,
27
+ newElement.dataset.turboLiveMetaDataTypeValue, newElement.dataset.turboLiveMetaDataDataValue)
28
+
29
+ this.changedDuringMorph = true
30
+ // teardown here since we still have our current state
31
+ this.#teardown()
32
+ }
33
+ }
34
+
35
+ afterMorph() {
36
+ logger.debug("TurboLiveMetaDataController afterMorph", this.typeValue, this.dataValue)
37
+ if (this.changedDuringMorph) {
38
+ this.#setup(this.typeValue, this.dataValue)
39
+ this.changedDuringMorph = false
40
+ }
41
+ }
42
+
43
+ setComponent(component) {
44
+ logger.debug("TurboLiveMetaDataController setComponent", this.typeValue, this.dataValue, component)
45
+ this.component = component
46
+ }
47
+
48
+ #setup(type, data) {
49
+ logger.debug("TurboLiveMetaDataController #setup", this.typeValue, this.dataValue, type, data)
50
+ if (type === "interval") {
51
+ this.#setupInterval(JSON.parse(data))
52
+ }
53
+ }
54
+
55
+ #teardown() {
56
+ logger.debug("TurboLiveMetaDataController #teardown", this.typeValue, this.dataValue)
57
+ if (this.interval) {
58
+ clearInterval(this.interval)
59
+ this.interval = null
60
+ }
61
+ }
62
+
63
+ #setupInterval(config) {
64
+ logger.debug("TurboLiveMetaDataController #setupInterval", this.typeValue, this.dataValue, config)
65
+ this.interval = setInterval(() => {
66
+ this.component.dispatch("interval", [config.event])
67
+ }, config.interval)
68
+ }
69
+ }
data/src/js/core.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import registerControllers from "./controllers/register_controllers.js"
2
2
  import registerChannels from "./channels/register_channels.js"
3
+ import { logger, Logger } from "./logger.js"
3
4
 
4
5
 
5
- export { registerControllers, registerChannels }
6
+ export { registerControllers, registerChannels, logger, Logger }
data/src/js/logger.js ADDED
@@ -0,0 +1,57 @@
1
+ export class Logger {
2
+ static LOG_LEVELS = {
3
+ OFF: -1,
4
+ ERROR: 0,
5
+ WARN: 1,
6
+ INFO: 2,
7
+ DEBUG: 3
8
+ };
9
+
10
+ constructor(level = Logger.LOG_LEVELS.OFF) {
11
+ this.setLevel(level)
12
+ }
13
+
14
+ setLevel(level) {
15
+ if (typeof level === 'string' && level in Logger.LOG_LEVELS) {
16
+ this.level = Logger.LOG_LEVELS[level];
17
+ } else if (typeof level === 'number' && (level === -1 || Object.values(Logger.LOG_LEVELS).includes(level))) {
18
+ this.level = level;
19
+ } else {
20
+ throw new Error('Invalid log level');
21
+ }
22
+ }
23
+
24
+ error(...args) {
25
+ if (this.level >= Logger.LOG_LEVELS.ERROR) console.error('[ERROR]', ...args);
26
+ }
27
+
28
+ warn(...args) {
29
+ if (this.level >= Logger.LOG_LEVELS.WARN) console.warn('[WARN]', ...args);
30
+ }
31
+
32
+ info(...args) {
33
+ if (this.level >= Logger.LOG_LEVELS.INFO) console.info('[INFO]', ...args);
34
+ }
35
+
36
+ debug(...args) {
37
+ if (this.level >= Logger.LOG_LEVELS.DEBUG) console.log('[DEBUG]', ...args);
38
+ }
39
+
40
+ log(...args) {
41
+ if (this.level > Logger.LOG_LEVELS.OFF) this.info(...args);
42
+ }
43
+ }
44
+
45
+ export const logger = new Logger();
46
+
47
+ // Usage examples:
48
+ // import { Logger, logger } from './logger.js';
49
+ //
50
+ // logger.setLevel('DEBUG');
51
+ // logger.debug('This is a debug message');
52
+ //
53
+ // logger.setLevel(-1);
54
+ // logger.error('This error will not be logged');
55
+ //
56
+ // const customLogger = new Logger(Logger.LOG_LEVELS.OFF);
57
+ // customLogger.error('This error will not be logged either');
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.3
4
+ version: 0.2.1
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-15 00:00:00.000000000 Z
11
+ date: 2024-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex-rails
@@ -52,11 +52,12 @@ files:
52
52
  - LICENSE.txt
53
53
  - README.md
54
54
  - Rakefile
55
- - app/channels/components_channel.rb
55
+ - app/channels/turbo_live/components_channel.rb
56
56
  - app/controllers/turbo_live/components_controller.rb
57
57
  - config/routes.rb
58
58
  - examples/countdown_component.rb
59
59
  - examples/counter_component.rb
60
+ - examples/flappy_bird_component.rb
60
61
  - examples/showcase_component.rb
61
62
  - examples/tic_tac_toe_component.rb
62
63
  - lib/turbo_live.rb
@@ -72,7 +73,9 @@ files:
72
73
  - src/js/channels/turbo_live_channel.js
73
74
  - src/js/controllers/register_controllers.js
74
75
  - src/js/controllers/turbo_live_controller.js
76
+ - src/js/controllers/turbo_live_meta_data_controller.js
75
77
  - src/js/core.js
78
+ - src/js/logger.js
76
79
  homepage: https://github.com/radioactive-labs/turbo_live
77
80
  licenses:
78
81
  - MIT