turbo_live 0.1.1 → 0.1.2

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: be283fc00ae2733bc0459e7c394e48b96359614de335b4d15cefec53a2134763
4
- data.tar.gz: 7cc650231d0bc1537af64d27ebd748422b722a94be71108809d18024640472a8
3
+ metadata.gz: b117e175ea920117e11f0cd6c3bab5cafc0ca7c1860fb6b890c632a37efe1509
4
+ data.tar.gz: ca56b1155834dbeb85a29112a2f85b0901dbf14d61909587f698f45cfc25bb34
5
5
  SHA512:
6
- metadata.gz: de85e08541c5fb65b2ecace8785f3688fc3b121f01a4a73991e7f25a21f935f7f4584913ca5c9a54ce99700da247ec1850d5898722fb44bd926a878d4eb84e33
7
- data.tar.gz: e0d1b93943354bbde2cb5e0654966bb0be5f27bcc68d54b2772440e67ff70697742554b9dd116146c4146dcb52b657f092856dd4512fd0d2bd6a1fe5f2f1e644
6
+ metadata.gz: fce48eb8c3ae06f14fb6bb365004cf399c4f85d4f147776a7b05d6d7ec4b2833182fe6e4fd5d99781ef555daaad2d0bcd4b7b757378c645a7486138c2d356809
7
+ data.tar.gz: f0b1875a9143f5ca1ca218c05b1dbd3439ff7ebb13b8aabe9d72fcd58746fe41e8cde508f3a41f138ae3acfb25c66247594c1856397eaa89d2b9f6aaf170bea4
data/README.md CHANGED
@@ -1,39 +1,230 @@
1
1
  # TurboLive
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/turbo_live`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ TurboLive is a Ruby gem that enables the creation of async, progressively enhanced, live components for Ruby applications. It works seamlessly over both WebSockets and HTTPS, providing real-time interactivity with graceful degradation.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Setup](#setup)
9
+ - [Usage](#usage)
10
+ - [Creating a Component](#creating-a-component)
11
+ - [Model State](#model-state)
12
+ - [View](#view)
13
+ - [Update](#update)
14
+ - [Events](#events)
15
+ - [Manual Events](#manual-events)
16
+ - [Timed Events](#timed-events)
17
+ - [Examples](#examples)
18
+ - [Performance Considerations](#performance-considerations)
19
+ - [Testing](#testing)
20
+ - [Troubleshooting](#troubleshooting)
21
+ - [Contributing](#contributing)
22
+ - [Changelog](#changelog)
23
+ - [License](#license)
6
24
 
7
25
  ## Installation
8
26
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
27
+ Add it to your project with:
28
+
29
+ ```console
30
+ bundle add 'turbo_live'
31
+ ```
10
32
 
11
- Install the gem and add to the application's Gemfile by executing:
33
+ Or install it yourself using:
12
34
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
35
+ ```console
36
+ gem install turbo_live
15
37
  ```
16
38
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
39
+ ### JavaScript
40
+
41
+ TurboLive ships a JavaScript component that comes as an npm package. You can pin it with importmaps or install it as an npm package depending on your asset pipeline:
42
+
43
+ For importmaps:
18
44
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
45
+ ```console
46
+ bin/importmap pin @radioactive-labs/turbo-live
47
+ ```
48
+
49
+ For npm:
50
+
51
+ ```console
52
+ npm install @radioactive-labs/turbo-live
53
+ ```
54
+
55
+ ## Setup
56
+
57
+ ### Stimulus Controller
58
+
59
+ TurboLive uses a Stimulus controller to manage interactions. In your `app/javascript/controllers/index.js`:
60
+
61
+ ```diff
62
+ import { application } from "controllers/application"
63
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
64
+ +import * as turboLive from "@radioactive-labs/turbo-live"
65
+
66
+ eagerLoadControllersFrom("controllers", application)
67
+ +turboLive.registerControllers(application)
68
+ ```
69
+
70
+ ### ActionCable (Optional)
71
+
72
+ TurboLive supports WebSockets using ActionCable with automatic failover to HTTPS. If you have ActionCable set up and would like to benefit from better performance, you can set up the integration.
73
+
74
+ In `app/javascript/channels/index.js`:
75
+
76
+ ```diff
77
+ +import consumer from "./consumer"
78
+ +import * as turboLive from "@radioactive-labs/turbo-live"
79
+ +
80
+ +turboLive.registerChannels(consumer)
81
+ ```
82
+
83
+ Then in your `app/javascript/application.js`:
84
+
85
+ ```diff
86
+ import "@hotwired/turbo-rails"
87
+ import "controllers"
88
+ +import "channels"
21
89
  ```
22
90
 
23
91
  ## Usage
24
92
 
25
- TODO: Write usage instructions here
93
+ A TurboLive component is a self-contained, interactive unit of a web application that can update in real-time without full page reloads. Components follow [The Elm Architecture](https://guide.elm-lang.org/architecture/) pattern.
94
+
95
+ ### Creating a Component
96
+
97
+ To create a TurboLive component, inherit from `TurboLive::Component`:
98
+
99
+ ```ruby
100
+ class MyComponent < TurboLive::Component
101
+ # Component logic goes here
102
+ end
103
+ ```
104
+
105
+ ### Model State
106
+
107
+ Define state variables using the `state` method:
108
+
109
+ ```ruby
110
+ class MyComponent < TurboLive::Component
111
+ state :count, Integer do |value|
112
+ value || 0
113
+ end
114
+ end
115
+ ```
116
+
117
+ > Note: State variables can only be primitive objects and basic collections.
118
+
119
+ ### View
120
+
121
+ Define the component's HTML structure in the `view` method:
26
122
 
27
- ## Development
123
+ ```ruby
124
+ def view
125
+ div do
126
+ button(**on(click: :increment)) { "+" }
127
+ span { count }
128
+ button(**on(click: :decrement)) { "-" }
129
+ end
130
+ end
131
+ ```
132
+
133
+ Components are [phlex](https://www.phlex.fun/) views, allowing you to write HTML in Ruby.
134
+
135
+ ### Update
28
136
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137
+ Handle events in the `update` method:
138
+
139
+ ```ruby
140
+ def update(input)
141
+ case input
142
+ in [:increment]
143
+ self.count += 1
144
+ in [:decrement]
145
+ self.count -= 1
146
+ end
147
+ end
148
+ ```
30
149
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
150
+ ## Events
151
+
152
+ Events are transmitted to the server using the currently active transport (HTTP or WebSockets).
153
+
154
+ ### Manual Events
155
+
156
+ Use the `on` method to set up manually triggered events:
157
+
158
+ ```ruby
159
+ button(**on(click: :decrement)) { "-" }
160
+ ```
161
+
162
+ You can also emit compound events that carry extra data:
163
+
164
+ ```ruby
165
+ button(**on(click: [:change_value, 1])) { "+" }
166
+ ```
167
+
168
+ > Note: Currently, only `:click` and `:change` events are supported.
169
+
170
+ ### Timed Events
171
+
172
+ Use the `every` method to set up recurring events:
173
+
174
+ ```ruby
175
+ def view
176
+ div do
177
+ h1 { countdown }
178
+ every(1000, :tick) if countdown > 0
179
+ end
180
+ end
181
+ ```
182
+
183
+ ## Examples
184
+
185
+ See the [/examples](/examples) folder in for detailed component examples including Counter, Countdown, Showcase and Tic-Tac-Toe components.
186
+
187
+ ## Performance Considerations
188
+
189
+ - Use fine-grained components to minimize the amount of data transferred and rendered.
190
+ - Implement debouncing for frequently triggered events.
191
+ - Consider using background jobs for heavy computations to keep the UI responsive.
192
+
193
+ ## Testing
194
+
195
+ TurboLive components can be tested using standard Rails testing tools. Here's a basic example:
196
+
197
+ ```ruby
198
+ require "test_helper"
199
+
200
+ class CounterComponentTest < ActiveSupport::TestCase
201
+ test "increments count" do
202
+ component = CounterComponent.new
203
+ assert_equal 0, component.count
204
+ component.update([:increment])
205
+ assert_equal 1, component.count
206
+ end
207
+ end
208
+ ```
209
+
210
+ ## Troubleshooting
211
+
212
+ Common issues and their solutions:
213
+
214
+ 1. **Component not updating**: Ensure that your `update` method is correctly handling the event and modifying the state.
215
+ 2. **WebSocket connection failing**: Check your ActionCable configuration and ensure that your server supports WebSocket connections.
216
+ 3. **JavaScript errors**: Make sure you've correctly set up the TurboLive JavaScript integration in your application.
217
+
218
+ For more issues, please check our [FAQ](https://github.com/radioactive-labs/turbo_live/wiki/FAQ) or open an issue on GitHub.
32
219
 
33
220
  ## Contributing
34
221
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/turbo_live.
222
+ We welcome contributions to TurboLive! Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get started.
223
+
224
+ ## Changelog
225
+
226
+ See the [CHANGELOG.md](CHANGELOG.md) file for details on each release.
36
227
 
37
228
  ## License
38
229
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
230
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,23 @@
1
+ class CountdownComponent < TurboLive::Component
2
+ state :countdown, Integer
3
+
4
+ def view
5
+ div do
6
+ if countdown.nil?
7
+ button(**on(click: :start)) { "Start!" }
8
+ else
9
+ h1 { countdown }
10
+ every(1000, :countdown) if countdown >= 1
11
+ end
12
+ end
13
+ end
14
+
15
+ def update(input)
16
+ case input
17
+ in [:countdown]
18
+ self.countdown -= 1
19
+ in [:start]
20
+ self.countdown = 1000
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ class CounterComponent < TurboLive::Component
2
+ state :count, Integer do |value|
3
+ value || 0
4
+ end
5
+
6
+ def view
7
+ div do
8
+ button(**on(click: :increment)) { "+" }
9
+ span { " Clicked: #{count} " }
10
+ button(**on(click: :decrement)) { "-" }
11
+ plain " "
12
+ button(**on(click: :reset)) { "Reset" }
13
+ end
14
+ end
15
+
16
+ def update(input)
17
+ case input
18
+ in [:decrement]
19
+ self.count -= 1
20
+ in [:increment]
21
+ self.count += 1
22
+ in [:reset]
23
+ self.count = 0
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ class ShowcaseComponent < TurboLive::Component
2
+ state :component, Symbol
3
+
4
+ def view
5
+ div class: "container" do
6
+ div class: "left-column" do
7
+ h2 { "Components" }
8
+ ul do
9
+ li { button(**on(click: [:change_component, :counter])) { "Counter" } }
10
+ li { button(**on(click: [:change_component, :countdown])) { "Countdown" } }
11
+ li { button(**on(click: [:change_component, :tic_tac_toe])) { "TicTacToe" } }
12
+ end
13
+ end
14
+ div class: "right-column" do
15
+ div class: "card" do
16
+ render selected_component.new
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def update(input)
23
+ case input
24
+ in [[:change_component, component]]
25
+ self.component = component
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def selected_component
32
+ case component
33
+ when :countdown
34
+ CountdownComponent
35
+ when :tic_tac_toe
36
+ TicTacToeComponent
37
+ else
38
+ CounterComponent
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,93 @@
1
+ class TicTacToeComponent < TurboLive::Component
2
+ state :board, Array do |value|
3
+ value || Array.new(9, nil)
4
+ end
5
+
6
+ state :current_player, String do |value|
7
+ value || "X"
8
+ end
9
+
10
+ state :winner, String do |value|
11
+ value || nil
12
+ end
13
+
14
+ def view
15
+ div(class: "tic-tac-toe-container") do
16
+ render_board
17
+ render_status
18
+ render_reset_button
19
+ end
20
+ end
21
+
22
+ def render_board
23
+ div(class: "board") do
24
+ board.each_with_index do |cell, index|
25
+ button(
26
+ class: "cell",
27
+ **on(click: [:make_move, index]),
28
+ disabled: cell || winner,
29
+ "data-value": cell
30
+ ) { cell || "&nbsp;".html_safe }
31
+ end
32
+ end
33
+ end
34
+
35
+ def render_status
36
+ div(class: "status") do
37
+ if winner
38
+ "Winner: #{winner}"
39
+ elsif board.compact.length == 9
40
+ "It's a draw!"
41
+ else
42
+ "Current player: #{current_player}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def render_reset_button
48
+ button(**on(click: :reset_game)) { "Reset Game" }
49
+ end
50
+
51
+ def update(input)
52
+ case input
53
+ in [[:make_move, index]]
54
+ make_move(index)
55
+ in [:reset_game]
56
+ reset_game
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def make_move(index)
63
+ return if board[index] || winner
64
+
65
+ new_board = board.dup
66
+ new_board[index] = current_player
67
+ self.board = new_board
68
+ self.winner = check_winner
69
+ self.current_player = (current_player == "X") ? "O" : "X" unless winner
70
+ end
71
+
72
+ def check_winner
73
+ winning_combinations = [
74
+ [0, 1, 2], [3, 4, 5], [6, 7, 8], # Rows
75
+ [0, 3, 6], [1, 4, 7], [2, 5, 8], # Columns
76
+ [0, 4, 8], [2, 4, 6] # Diagonals
77
+ ]
78
+
79
+ winning_combinations.each do |combo|
80
+ if board[combo[0]] && board[combo[0]] == board[combo[1]] && board[combo[0]] == board[combo[2]]
81
+ return board[combo[0]]
82
+ end
83
+ end
84
+
85
+ nil
86
+ end
87
+
88
+ def reset_game
89
+ self.board = Array.new(9, nil)
90
+ self.current_player = "X"
91
+ self.winner = nil
92
+ end
93
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurboLive
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/turbo-live",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/turbo-live",
9
- "version": "0.1.1",
9
+ "version": "0.1.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@hotwired/stimulus": "^3.2.2",
data/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@radioactive-labs/turbo-live",
3
- "version": "0.1.1",
4
- "description": "Stateless live components for Ruby applications",
3
+ "version": "0.1.2",
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",
7
7
  "files": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_live
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheDumbTechGuy
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: Stateless live components for Ruby applications that work over Websockets
42
- and HTTP.
41
+ description: Async, progressively enhanced, live components for Ruby applications
42
+ that work over Websockets and HTTP.
43
43
  email:
44
44
  - sfroelich01@gmail.com
45
45
  executables: []
@@ -55,6 +55,10 @@ files:
55
55
  - app/channels/components_channel.rb
56
56
  - app/controllers/turbo_live/components_controller.rb
57
57
  - config/routes.rb
58
+ - examples/countdown_component.rb
59
+ - examples/counter_component.rb
60
+ - examples/showcase_component.rb
61
+ - examples/tic_tac_toe_component.rb
58
62
  - lib/turbo_live.rb
59
63
  - lib/turbo_live/component.rb
60
64
  - lib/turbo_live/engine.rb
@@ -95,5 +99,5 @@ requirements: []
95
99
  rubygems_version: 3.5.16
96
100
  signing_key:
97
101
  specification_version: 4
98
- summary: Stateless live components for Ruby applications
102
+ summary: Async, progressively enhanced, live components for Ruby applications
99
103
  test_files: []