turbo_live 0.1.3 → 0.2.0

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: 973ebc91558a780fcdfc2e8dea4c6b1bc3d090678ee30535768e3b9d7ad1f9aa
4
+ data.tar.gz: 63e48c4f2b207557d9b111609a3dad42a10f70ea3719ea96a1477ed8f4e952fe
5
5
  SHA512:
6
- metadata.gz: 6efe19e261f10de079be70b72404fc441d02e34cb795a8a6a632a3390d5afaeb603c2bf43754ade0fafda03013cf947d9bc284fd0b2945e25306e1a73cb66890
7
- data.tar.gz: 4abda82e8d71fbcc3275cfec205d0ea4197c814710b7f12e5cae69a5e359455996e6eb207b0035be6e6ad8156aafc6797c854f33d4f25a93e22f9222e8913011
6
+ metadata.gz: f1118112757bfde64bbdd6417827ad56ea031ba950f2006a8151436f55101ca14543b991317d3825ecd143844417b2fafe84c2d6cccf7d408c9c14d213e18c1f
7
+ data.tar.gz: 7c1269811dd43b3a80188f9cafefc571c198c0eb9bd5adf9683353b7a67ef4ef915832a29bdf3fc1db710eeeb10a8ebca4b1829d4b05dacb7e2d05468891c01d
data/README.md CHANGED
@@ -139,9 +139,9 @@ Handle events in the `update` method:
139
139
  ```ruby
140
140
  def update(input)
141
141
  case input
142
- in [:increment]
142
+ in :increment
143
143
  self.count += 1
144
- in [:decrement]
144
+ in :decrement
145
145
  self.count -= 1
146
146
  end
147
147
  end
@@ -165,7 +165,21 @@ You can also emit compound events that carry extra data:
165
165
  button(**on(click: [:change_value, 1])) { "+" }
166
166
  ```
167
167
 
168
- > Note: Currently, only `:click` and `:change` events are supported.
168
+ Certain events carry extra data as well, such as `input` and `change` events.
169
+
170
+ ```ruby
171
+ input(value:, input_value, **on(input: :input_changed))
172
+ ```
173
+
174
+ ```ruby
175
+ def update(input)
176
+ case input
177
+ in [:input_changed, value]
178
+ self.input_value = value
179
+ end
180
+ end
181
+
182
+ > Note: Currently, only `:click`, `:input` and `:change` events are supported.
169
183
 
170
184
  ### Timed Events
171
185
 
@@ -201,7 +215,7 @@ class CounterComponentTest < ActiveSupport::TestCase
201
215
  test "increments count" do
202
216
  component = CounterComponent.new
203
217
  assert_equal 0, component.count
204
- component.update([:increment])
218
+ component.update(:increment)
205
219
  assert_equal 1, component.count
206
220
  end
207
221
  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,7 @@ 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" } }
12
15
  end
13
16
  end
14
17
  div class: "right-column" do
@@ -30,12 +33,14 @@ class ShowcaseComponent < TurboLive::Component
30
33
 
31
34
  def selected_component
32
35
  case component
36
+ when :counter
37
+ CounterComponent
33
38
  when :countdown
34
39
  CountdownComponent
35
40
  when :tic_tac_toe
36
41
  TicTacToeComponent
37
- else
38
- CounterComponent
42
+ when :flappy_bird
43
+ FlappyBirdComponent
39
44
  end
40
45
  end
41
46
  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.0"
5
5
  end
data/lib/turbo_live.rb CHANGED
@@ -12,6 +12,7 @@ module TurboLive
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.0
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-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex-rails
@@ -57,6 +57,7 @@ files:
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