turbo_live 0.1.3 → 0.2.1

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