lively 0.16.1 → 0.17.0
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/flappy-bird-tutorial.md +11 -11
- data/context/game-audio-tutorial.md +4 -4
- data/context/worms-tutorial.md +29 -29
- data/lib/lively/application.rb +48 -34
- data/lib/lively/pages/index.rb +1 -1
- data/lib/lively/resolver.rb +34 -0
- data/lib/lively/version.rb +2 -2
- data/license.md +1 -1
- data/public/_components/@socketry/live/Live.js +20 -5
- data/public/_components/@socketry/live/package.json +4 -1
- data/public/_components/@socketry/live-audio/package.json +5 -1
- data/readme.md +68 -10
- data/releases.md +116 -0
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d41e94774f23353b426f53e23eb343783826c244d9bbad793759e5eea9fcd534
|
|
4
|
+
data.tar.gz: c2c5b21df4f25117d12e8c8af2e8c17113489346087cdd80a2d5b6b0dbd920d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0445eecaf41965ab7aea620f3d2886da5b2e76a5850678fcb30b60e0410aafe6d5f7d4180348564f43833b07476f2bacac00dc447a38229528ff8884ad67374e
|
|
7
|
+
data.tar.gz: f75a5afa75d4d98ad71c4a014c00ffec9b62368d1995eafe67265c05ee72e0cfd43afcc9a43374dda74b2261753348c0b34adfe672bc9d09f3566899f661e69f
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -48,7 +48,7 @@ First, let's create the basic structure for our game. We'll start with a simple
|
|
|
48
48
|
Create a new file called `flappy_basic.rb`:
|
|
49
49
|
|
|
50
50
|
```ruby
|
|
51
|
-
require
|
|
51
|
+
require "live"
|
|
52
52
|
|
|
53
53
|
class FlappyBasicView < Live::View
|
|
54
54
|
WIDTH = 420
|
|
@@ -172,7 +172,7 @@ class Bird < BoundingBox
|
|
|
172
172
|
|
|
173
173
|
def initialize(x = 30, y = HEIGHT / 2, width: 34, height: 24, skin: nil)
|
|
174
174
|
super(x, y, width, height)
|
|
175
|
-
@skin = skin ||
|
|
175
|
+
@skin = skin || "bird"
|
|
176
176
|
@velocity = 0.0
|
|
177
177
|
@jumping = false
|
|
178
178
|
@dying = false
|
|
@@ -253,7 +253,7 @@ class Bird < BoundingBox
|
|
|
253
253
|
|
|
254
254
|
builder.inline_tag(:div,
|
|
255
255
|
id: id,
|
|
256
|
-
class:
|
|
256
|
+
class: "particle jump",
|
|
257
257
|
style: "left: #{center[0]}px; bottom: #{center[1]}px; --rotation-angle: #{angle}deg;"
|
|
258
258
|
)
|
|
259
259
|
end
|
|
@@ -387,12 +387,12 @@ class Pipe
|
|
|
387
387
|
|
|
388
388
|
# Render lower pipe
|
|
389
389
|
builder.inline_tag(:div,
|
|
390
|
-
class:
|
|
390
|
+
class: "pipe",
|
|
391
391
|
style: "left: #{@x}px; bottom: #{self.bottom}px; width: #{@width}px; height: #{@height}px; #{display}"
|
|
392
392
|
)
|
|
393
393
|
# Render upper pipe
|
|
394
394
|
builder.inline_tag(:div,
|
|
395
|
-
class:
|
|
395
|
+
class: "pipe",
|
|
396
396
|
style: "left: #{@x}px; bottom: #{self.top}px; width: #{@width}px; height: #{@height}px; #{display}"
|
|
397
397
|
)
|
|
398
398
|
end
|
|
@@ -461,7 +461,7 @@ class Gemstone < BoundingBox
|
|
|
461
461
|
end
|
|
462
462
|
|
|
463
463
|
builder.inline_tag(:div,
|
|
464
|
-
class:
|
|
464
|
+
class: "gemstone",
|
|
465
465
|
style: "left: #{@x}px; bottom: #{@y}px; width: #{@width}px; height: #{@height}px; opacity: #{opacity};"
|
|
466
466
|
)
|
|
467
467
|
|
|
@@ -475,7 +475,7 @@ class Gemstone < BoundingBox
|
|
|
475
475
|
|
|
476
476
|
builder.inline_tag(:div,
|
|
477
477
|
id: id,
|
|
478
|
-
class:
|
|
478
|
+
class: "particle bonus",
|
|
479
479
|
style: "left: #{center[0]}px; bottom: #{center[1]}px; --rotation-angle: #{angle}deg;"
|
|
480
480
|
)
|
|
481
481
|
end
|
|
@@ -540,7 +540,7 @@ Let's add a way for players to choose different bird skins:
|
|
|
540
540
|
|
|
541
541
|
```ruby
|
|
542
542
|
class SkinSelectionView < Live::View
|
|
543
|
-
SKINS = [
|
|
543
|
+
SKINS = ["bird", "gull", "kiwi", "owl"]
|
|
544
544
|
|
|
545
545
|
def handle(event)
|
|
546
546
|
skin = event.dig(:detail, :skin) or return
|
|
@@ -627,7 +627,7 @@ class FlappyView < Live::View
|
|
|
627
627
|
@pipes = nil
|
|
628
628
|
@bonus = nil
|
|
629
629
|
|
|
630
|
-
@skin_selection = SkinSelectionView.mount(self,
|
|
630
|
+
@skin_selection = SkinSelectionView.mount(self, "skin-selection")
|
|
631
631
|
|
|
632
632
|
# Game state
|
|
633
633
|
@score = 0
|
|
@@ -784,7 +784,7 @@ def step(dt)
|
|
|
784
784
|
play_music
|
|
785
785
|
end
|
|
786
786
|
end
|
|
787
|
-
|
|
787
|
+
|
|
788
788
|
# Check for collision
|
|
789
789
|
if pipe.intersect?(@bird)
|
|
790
790
|
Console.info(self, "Player has died.")
|
|
@@ -943,7 +943,7 @@ Here's how all the pieces fit together in a complete game file:
|
|
|
943
943
|
#!/usr/bin/env lively
|
|
944
944
|
# frozen_string_literal: true
|
|
945
945
|
|
|
946
|
-
require
|
|
946
|
+
require "live"
|
|
947
947
|
|
|
948
948
|
# [All the classes we've built: BoundingBox, Bird, Pipe, Gemstone, SkinSelectionView, FlappyView]
|
|
949
949
|
# Put them all together in a single file for easy execution
|
|
@@ -116,7 +116,7 @@ To play sounds from your Ruby application, you need to connect your custom eleme
|
|
|
116
116
|
**Ruby Application (`application.rb`):**
|
|
117
117
|
|
|
118
118
|
```ruby
|
|
119
|
-
require
|
|
119
|
+
require "lively"
|
|
120
120
|
|
|
121
121
|
class GameView < Live::View
|
|
122
122
|
def tag_name
|
|
@@ -131,17 +131,17 @@ class GameView < Live::View
|
|
|
131
131
|
|
|
132
132
|
def player_jump
|
|
133
133
|
@player.jump
|
|
134
|
-
play_sound(
|
|
134
|
+
play_sound("jump") # Play jump sound
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def collect_coin
|
|
138
138
|
@score += 10
|
|
139
|
-
play_sound(
|
|
139
|
+
play_sound("coin") # Play coin sound
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def player_dies
|
|
143
143
|
@lives -= 1
|
|
144
|
-
play_sound(
|
|
144
|
+
play_sound("death") # Play death sound
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
```
|
data/context/worms-tutorial.md
CHANGED
|
@@ -72,7 +72,7 @@ class StaticBoard
|
|
|
72
72
|
@height = height
|
|
73
73
|
|
|
74
74
|
# Use an Array of Arrays to store a grid:
|
|
75
|
-
@grid = Array.new(@height)
|
|
75
|
+
@grid = Array.new(@height){Array.new(@width)}
|
|
76
76
|
|
|
77
77
|
# Place a fruit:
|
|
78
78
|
@grid[1][1] = "🍎"
|
|
@@ -94,7 +94,7 @@ class StaticView < Live::View
|
|
|
94
94
|
|
|
95
95
|
# Render the HTML grid:
|
|
96
96
|
def render(builder)
|
|
97
|
-
builder.tag("h1")
|
|
97
|
+
builder.tag("h1"){builder.text("My First Lively Game!")}
|
|
98
98
|
builder.tag("table") do
|
|
99
99
|
@board.grid.each do |row|
|
|
100
100
|
builder.tag("tr") do
|
|
@@ -102,7 +102,7 @@ class StaticView < Live::View
|
|
|
102
102
|
if cell.is_a?(Hash)
|
|
103
103
|
builder.tag("td", style: "background-color: #{cell[:color]};")
|
|
104
104
|
elsif cell.is_a?(String)
|
|
105
|
-
builder.tag("td")
|
|
105
|
+
builder.tag("td"){builder.text(cell)}
|
|
106
106
|
else
|
|
107
107
|
builder.tag("td")
|
|
108
108
|
end
|
|
@@ -165,7 +165,7 @@ class InteractiveBoard
|
|
|
165
165
|
def initialize(width = 5, height = 5)
|
|
166
166
|
@width = width
|
|
167
167
|
@height = height
|
|
168
|
-
@grid = Array.new(@height)
|
|
168
|
+
@grid = Array.new(@height){Array.new(@width)}
|
|
169
169
|
# Put a fruit in the center:
|
|
170
170
|
@grid[2][2] = "🍎"
|
|
171
171
|
end
|
|
@@ -226,7 +226,7 @@ class InteractiveView < Live::View
|
|
|
226
226
|
|
|
227
227
|
# Render the HTML grid, including event forwarding.
|
|
228
228
|
def render(builder)
|
|
229
|
-
builder.tag("h1")
|
|
229
|
+
builder.tag("h1"){builder.text("Interactive Board - Click the cells!")}
|
|
230
230
|
builder.tag("table") do
|
|
231
231
|
@board.grid.each_with_index do |row, y|
|
|
232
232
|
builder.tag("tr") do
|
|
@@ -235,7 +235,7 @@ class InteractiveView < Live::View
|
|
|
235
235
|
# lively.forwardEvent sends the event from the browser to the server, invoking the handle method above. Note that we include the x and y coordinates as extra details.
|
|
236
236
|
builder.tag("td", onclick: "live.forwardEvent('#{@id}', event, {y: #{y}, x: #{x}});", style: "cursor: pointer; background-color: #{cell[:color]};")
|
|
237
237
|
elsif cell.is_a?(String)
|
|
238
|
-
builder.tag("td", onclick: "live.forwardEvent('#{@id}', event, {y: #{y}, x: #{x}});", style: "cursor: pointer;")
|
|
238
|
+
builder.tag("td", onclick: "live.forwardEvent('#{@id}', event, {y: #{y}, x: #{x}});", style: "cursor: pointer;"){builder.text(cell)}
|
|
239
239
|
else
|
|
240
240
|
builder.tag("td", onclick: "live.forwardEvent('#{@id}', event, {y: #{y}, x: #{x}});", style: "cursor: pointer;")
|
|
241
241
|
end
|
|
@@ -243,7 +243,7 @@ class InteractiveView < Live::View
|
|
|
243
243
|
end
|
|
244
244
|
end
|
|
245
245
|
end
|
|
246
|
-
builder.tag("p")
|
|
246
|
+
builder.tag("p"){builder.text("Click any cell to cycle: empty → fruit → worm segment → empty.")}
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
|
|
@@ -275,7 +275,7 @@ class Board
|
|
|
275
275
|
def initialize(width = 8, height = 8)
|
|
276
276
|
@width = width
|
|
277
277
|
@height = height
|
|
278
|
-
@grid = Array.new(@height)
|
|
278
|
+
@grid = Array.new(@height){Array.new(@width)}
|
|
279
279
|
end
|
|
280
280
|
|
|
281
281
|
attr_reader :grid, :width, :height
|
|
@@ -348,7 +348,7 @@ class SimpleWorm
|
|
|
348
348
|
# Don't move this tick
|
|
349
349
|
return
|
|
350
350
|
end
|
|
351
|
-
|
|
351
|
+
|
|
352
352
|
# Otherwise, update the worm's position:
|
|
353
353
|
@y, @x = new_y, new_x
|
|
354
354
|
@board.set_segment(@y, @x, @color, @length)
|
|
@@ -394,7 +394,7 @@ class MovingWormView < Live::View
|
|
|
394
394
|
|
|
395
395
|
# Render the HTML grid, including event forwarding.
|
|
396
396
|
def render(builder)
|
|
397
|
-
builder.tag("h1")
|
|
397
|
+
builder.tag("h1"){builder.text("Automatic Moving Worm")}
|
|
398
398
|
builder.tag("table") do
|
|
399
399
|
@board.grid.each_with_index do |row, y|
|
|
400
400
|
builder.tag("tr") do
|
|
@@ -408,8 +408,8 @@ class MovingWormView < Live::View
|
|
|
408
408
|
end
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
|
-
builder.tag("p")
|
|
412
|
-
builder.tag("p")
|
|
411
|
+
builder.tag("p"){builder.text("Watch the colored worm bounce around and leave a trail!")}
|
|
412
|
+
builder.tag("p"){builder.text("Current position: (#{@worm.y}, #{@worm.x})")}
|
|
413
413
|
end
|
|
414
414
|
end
|
|
415
415
|
|
|
@@ -434,7 +434,7 @@ class Board
|
|
|
434
434
|
def initialize(width = 10, height = 10)
|
|
435
435
|
@width = width
|
|
436
436
|
@height = height
|
|
437
|
-
@grid = Array.new(@height)
|
|
437
|
+
@grid = Array.new(@height){Array.new(@width)}
|
|
438
438
|
end
|
|
439
439
|
|
|
440
440
|
attr_reader :grid, :width, :height
|
|
@@ -508,7 +508,7 @@ class ControllableView < Live::View
|
|
|
508
508
|
@worm = ControllableWorm.new(@board, 5, 5)
|
|
509
509
|
@movement = nil
|
|
510
510
|
end
|
|
511
|
-
|
|
511
|
+
|
|
512
512
|
def bind(page)
|
|
513
513
|
super
|
|
514
514
|
|
|
@@ -521,7 +521,7 @@ class ControllableView < Live::View
|
|
|
521
521
|
end
|
|
522
522
|
end
|
|
523
523
|
end
|
|
524
|
-
|
|
524
|
+
|
|
525
525
|
def close
|
|
526
526
|
if movement = @movement
|
|
527
527
|
@movement = nil
|
|
@@ -530,7 +530,7 @@ class ControllableView < Live::View
|
|
|
530
530
|
|
|
531
531
|
super
|
|
532
532
|
end
|
|
533
|
-
|
|
533
|
+
|
|
534
534
|
def handle(event)
|
|
535
535
|
Console.info(self, "Event:", event)
|
|
536
536
|
|
|
@@ -548,9 +548,9 @@ class ControllableView < Live::View
|
|
|
548
548
|
end
|
|
549
549
|
end
|
|
550
550
|
end
|
|
551
|
-
|
|
551
|
+
|
|
552
552
|
def render(builder)
|
|
553
|
-
builder.tag("h1")
|
|
553
|
+
builder.tag("h1"){builder.text("Controllable Worm - Use WASD!")}
|
|
554
554
|
builder.tag("table", tabindex: 0, autofocus: true, onkeypress: "live.forwardEvent('#{@id}', event, {key: event.key});") do
|
|
555
555
|
@board.grid.each_with_index do |row, y|
|
|
556
556
|
builder.tag("tr") do
|
|
@@ -567,10 +567,10 @@ class ControllableView < Live::View
|
|
|
567
567
|
|
|
568
568
|
# Log extra information about the game:
|
|
569
569
|
builder.tag("div") do
|
|
570
|
-
builder.tag("p")
|
|
571
|
-
builder.tag("p")
|
|
572
|
-
builder.tag("p")
|
|
573
|
-
builder.tag("p")
|
|
570
|
+
builder.tag("p"){builder.text("Controls: W (up), A (left), S (down), D (right)")}
|
|
571
|
+
builder.tag("p"){builder.text("Current direction: #{@worm.direction}")}
|
|
572
|
+
builder.tag("p"){builder.text("Position: (#{@worm.y}, #{@worm.x})")}
|
|
573
|
+
builder.tag("p"){builder.text("Click on the game board first, then use WASD keys!")}
|
|
574
574
|
end
|
|
575
575
|
end
|
|
576
576
|
end
|
|
@@ -612,7 +612,7 @@ class Board
|
|
|
612
612
|
def initialize(width = 15, height = 15)
|
|
613
613
|
@width = width
|
|
614
614
|
@height = height
|
|
615
|
-
@grid = Array.new(@height)
|
|
615
|
+
@grid = Array.new(@height){Array.new(@width)}
|
|
616
616
|
@fruit_count = 0
|
|
617
617
|
reset!
|
|
618
618
|
end
|
|
@@ -640,7 +640,7 @@ class Board
|
|
|
640
640
|
end
|
|
641
641
|
|
|
642
642
|
def reset!
|
|
643
|
-
@grid.each
|
|
643
|
+
@grid.each{|row| row.fill(nil)}
|
|
644
644
|
@fruit_count = 0
|
|
645
645
|
add_fruit!
|
|
646
646
|
end
|
|
@@ -779,11 +779,11 @@ class WormsGameView < Live::View
|
|
|
779
779
|
end
|
|
780
780
|
|
|
781
781
|
def render(builder)
|
|
782
|
-
builder.tag("h1")
|
|
782
|
+
builder.tag("h1"){builder.text("Worms Game (Reference-style)")}
|
|
783
783
|
builder.tag("div") do
|
|
784
|
-
builder.tag("p")
|
|
785
|
-
builder.tag("p")
|
|
786
|
-
builder.tag("p")
|
|
784
|
+
builder.tag("p"){builder.text("Score: #{@player.score}")}
|
|
785
|
+
builder.tag("p"){builder.text("Length: #{@player.count}")}
|
|
786
|
+
builder.tag("p"){builder.text("Direction: #{@player.direction}")}
|
|
787
787
|
end
|
|
788
788
|
builder.tag("table", tabindex: 0, autofocus: true, onkeypress: "live.forwardEvent('#{@id}', event, {key: event.key});") do
|
|
789
789
|
@board.grid.each do |row|
|
|
@@ -792,7 +792,7 @@ class WormsGameView < Live::View
|
|
|
792
792
|
if cell.is_a?(Hash)
|
|
793
793
|
builder.tag("td", style: "background-color: #{cell[:color]};")
|
|
794
794
|
elsif cell.is_a?(String)
|
|
795
|
-
builder.tag("td")
|
|
795
|
+
builder.tag("td"){builder.text(cell)}
|
|
796
796
|
else
|
|
797
797
|
builder.tag("td")
|
|
798
798
|
end
|
data/lib/lively/application.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2021-
|
|
4
|
+
# Copyright, 2021-2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "live"
|
|
7
7
|
require "protocol/http/middleware"
|
|
8
8
|
require "async/websocket/adapters/http"
|
|
9
9
|
|
|
10
|
+
require_relative "resolver"
|
|
10
11
|
require_relative "pages/index"
|
|
11
12
|
require_relative "hello_world"
|
|
12
13
|
|
|
@@ -18,49 +19,65 @@ module Lively
|
|
|
18
19
|
# standard HTTP requests for the initial page load and WebSocket connections
|
|
19
20
|
# for live updates. It integrates with the Live framework to provide real-time
|
|
20
21
|
# interactive web applications.
|
|
22
|
+
#
|
|
23
|
+
# Use {.[]} to create a simple application class for a single view, optionally
|
|
24
|
+
# with shared state. For more complex applications, subclass and override
|
|
25
|
+
# {#allowed_views}, {#state}, and {#body}.
|
|
21
26
|
class Application < Protocol::HTTP::Middleware
|
|
22
|
-
|
|
27
|
+
VIEWS = [HelloWorld].freeze
|
|
28
|
+
STATE = {}.freeze
|
|
29
|
+
|
|
30
|
+
# Create a new application class configured for a specific Live view tag,
|
|
31
|
+
# optionally with shared state that is passed to all views.
|
|
32
|
+
#
|
|
23
33
|
# @parameter tag [Class] The Live view class to use as the application body.
|
|
34
|
+
# @parameter state [Hash] Shared state to pass to all views as keyword arguments.
|
|
24
35
|
# @returns [Class] A new application class configured for the specified tag.
|
|
25
|
-
def self.[](
|
|
36
|
+
def self.[](*tags, **state)
|
|
26
37
|
klass = Class.new(self)
|
|
27
38
|
|
|
28
|
-
klass.
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
klass.define_method(:body) do
|
|
33
|
-
tag.new
|
|
34
|
-
end
|
|
39
|
+
klass.const_set(:VIEWS, tags)
|
|
40
|
+
klass.const_set(:STATE, state)
|
|
35
41
|
|
|
36
42
|
return klass
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
# Get the default resolver for this application.
|
|
40
|
-
# @returns [Live::Resolver] A resolver configured to allow HelloWorld components.
|
|
41
|
-
def self.resolver
|
|
42
|
-
Live::Resolver.allow(HelloWorld)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
45
|
# Initialize a new Lively application.
|
|
46
46
|
# @parameter delegate [Protocol::HTTP::Middleware] The next middleware in the chain.
|
|
47
|
-
|
|
48
|
-
def initialize(delegate, resolver: self.class.resolver)
|
|
47
|
+
def initialize(delegate)
|
|
49
48
|
super(delegate)
|
|
50
|
-
|
|
51
|
-
@resolver = resolver
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
# @attribute [Live::Resolver] The resolver for live components.
|
|
55
|
-
attr :resolver
|
|
56
|
-
|
|
57
51
|
# @attribute [Protocol::HTTP::Middleware] The delegate middleware for request handling.
|
|
58
52
|
attr :delegate
|
|
59
53
|
|
|
54
|
+
# The shared state for this application, passed to all views via the resolver.
|
|
55
|
+
# Override this in subclasses to provide custom state.
|
|
56
|
+
# @returns [Hash] Key-value pairs passed as keyword arguments to view constructors.
|
|
57
|
+
def state
|
|
58
|
+
self.class::STATE
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The view classes that this application allows.
|
|
62
|
+
# Override this in subclasses to specify which views can be resolved.
|
|
63
|
+
# @returns [Array(Class)] The allowed view classes.
|
|
64
|
+
def allowed_views
|
|
65
|
+
self.class::VIEWS
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The resolver for live components.
|
|
69
|
+
# Built from {#allowed_views} and {#state}.
|
|
70
|
+
# @returns [Lively::Resolver] The resolver instance.
|
|
71
|
+
def resolver
|
|
72
|
+
@resolver ||= Resolver.new(self.state).tap do |resolver|
|
|
73
|
+
resolver.allow(*self.allowed_views)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
60
77
|
# Handle a WebSocket connection for live updates.
|
|
61
78
|
# @parameter connection [Async::WebSocket::Connection] The WebSocket connection.
|
|
62
79
|
def live(connection)
|
|
63
|
-
Live::Page.new(
|
|
80
|
+
Live::Page.new(self.resolver).run(connection)
|
|
64
81
|
end
|
|
65
82
|
|
|
66
83
|
# Get the title for this application.
|
|
@@ -70,25 +87,22 @@ module Lively
|
|
|
70
87
|
end
|
|
71
88
|
|
|
72
89
|
# Create the body content for this application.
|
|
73
|
-
# @
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
HelloWorld.new(...)
|
|
90
|
+
# @returns [Live::View] A new view instance.
|
|
91
|
+
def body
|
|
92
|
+
self.allowed_views.first.new(**self.state)
|
|
77
93
|
end
|
|
78
94
|
|
|
79
95
|
# Create the index page for this application.
|
|
80
|
-
# @parameter **options [Hash] Additional options to pass to the index constructor.
|
|
81
96
|
# @returns [Pages::Index] A new index page instance.
|
|
82
|
-
def index
|
|
83
|
-
Pages::Index.new(title: self.title, body: self.body
|
|
97
|
+
def index
|
|
98
|
+
Pages::Index.new(title: self.title, body: self.body)
|
|
84
99
|
end
|
|
85
100
|
|
|
86
101
|
# Handle a standard HTTP request.
|
|
87
102
|
# @parameter request [Protocol::HTTP::Request] The incoming HTTP request.
|
|
88
|
-
# @parameter **options [Hash] Additional options.
|
|
89
103
|
# @returns [Protocol::HTTP::Response] The HTTP response with the rendered page.
|
|
90
|
-
def handle(request
|
|
91
|
-
return Protocol::HTTP::Response[200, [], [self.index
|
|
104
|
+
def handle(request)
|
|
105
|
+
return Protocol::HTTP::Response[200, [], [self.index.call]]
|
|
92
106
|
end
|
|
93
107
|
|
|
94
108
|
# Process an incoming HTTP request.
|
data/lib/lively/pages/index.rb
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "live/resolver"
|
|
7
|
+
|
|
8
|
+
module Lively
|
|
9
|
+
# Extends {Live::Resolver} to pass shared application state to views on construction.
|
|
10
|
+
#
|
|
11
|
+
# When the browser reconnects via WebSocket, the resolver creates new view
|
|
12
|
+
# instances with the shared state (e.g. a controller) so all clients stay in sync.
|
|
13
|
+
class Resolver < Live::Resolver
|
|
14
|
+
# Initialize a new resolver with shared state.
|
|
15
|
+
# @parameter state [Hash] Key-value pairs to pass to view constructors as keyword arguments.
|
|
16
|
+
def initialize(state = nil)
|
|
17
|
+
super()
|
|
18
|
+
@state = state
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @attribute [Hash] The shared state passed to view constructors.
|
|
22
|
+
attr :state
|
|
23
|
+
|
|
24
|
+
# Resolve a client-side element to a server-side instance with shared state.
|
|
25
|
+
# @parameter id [String] The unique element identifier.
|
|
26
|
+
# @parameter data [Hash] The element data attributes.
|
|
27
|
+
# @returns [Live::Element | Nil] The resolved element, or `nil`.
|
|
28
|
+
def call(id, data)
|
|
29
|
+
if klass = @allowed[data[:class]]
|
|
30
|
+
return klass.new(id, data, **@state)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/lively/version.rb
CHANGED
data/license.md
CHANGED
|
@@ -87,8 +87,9 @@ export class Live {
|
|
|
87
87
|
|
|
88
88
|
server.onopen = () => {
|
|
89
89
|
this.#failures = 0;
|
|
90
|
-
|
|
90
|
+
// Attach elements before flushing any pending events:
|
|
91
91
|
this.#attach();
|
|
92
|
+
this.#flush();
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
server.onmessage = (message) => {
|
|
@@ -165,16 +166,30 @@ export class Live {
|
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
connected() {
|
|
170
|
+
return this.#server && this.#server.readyState === this.#window.WebSocket.OPEN;
|
|
171
|
+
}
|
|
172
|
+
|
|
168
173
|
bind(element) {
|
|
169
|
-
this
|
|
174
|
+
if (this.connected()) {
|
|
175
|
+
try {
|
|
176
|
+
return this.#server.send(JSON.stringify(['bind', element.id, element.dataset]));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
// The server must be in a bad state, we will rebind in attach on connect, later.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
170
181
|
}
|
|
171
182
|
|
|
172
183
|
unbind(element) {
|
|
173
|
-
if (this
|
|
174
|
-
|
|
184
|
+
if (this.connected()) {
|
|
185
|
+
try {
|
|
186
|
+
return this.#server.send(JSON.stringify(['unbind', element.id]));
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// The server must be in a bad state, unbind is irrelelvant.
|
|
189
|
+
}
|
|
175
190
|
}
|
|
176
191
|
}
|
|
177
|
-
|
|
192
|
+
|
|
178
193
|
#attach() {
|
|
179
194
|
for (let element of ViewElement.connectedElements) {
|
|
180
195
|
this.bind(element);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketry/live-audio",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"description": "Web Audio API-based game audio synthesis and background music library for Ruby Live applications.",
|
|
6
6
|
"main": "Live/Audio.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./Live/Audio.js",
|
|
9
|
+
"./Library": "./Live/Audio/Library.js"
|
|
10
|
+
},
|
|
7
11
|
"files": [
|
|
8
12
|
"Live/"
|
|
9
13
|
],
|
data/readme.md
CHANGED
|
@@ -16,6 +16,59 @@ Please see the [project documentation](https://socketry.github.io/lively/) for m
|
|
|
16
16
|
|
|
17
17
|
- [Game Audio Tutorial](https://socketry.github.io/lively/guides/game-audio-tutorial/index) - This guide shows you how to add audio to your Live.js games and applications using the `live-audio` library. You'll learn how to play sound effects, background music, and create dynamic audio experiences.
|
|
18
18
|
|
|
19
|
+
## Releases
|
|
20
|
+
|
|
21
|
+
Please see the [project releases](https://socketry.github.io/lively/releases/index) for all releases.
|
|
22
|
+
|
|
23
|
+
### v0.17.0
|
|
24
|
+
|
|
25
|
+
- Expose shared application state via `Application[..., controller: Controller.new]`.
|
|
26
|
+
|
|
27
|
+
### v0.16.2
|
|
28
|
+
|
|
29
|
+
- Modernize internals and update dependencies.
|
|
30
|
+
|
|
31
|
+
### v0.16.1
|
|
32
|
+
|
|
33
|
+
- Updated game audio tutorial content.
|
|
34
|
+
|
|
35
|
+
### v0.16.0
|
|
36
|
+
|
|
37
|
+
- Updated `live-audio` component dependencies.
|
|
38
|
+
|
|
39
|
+
### v0.15.1
|
|
40
|
+
|
|
41
|
+
- Fixed handling of spaces in asset names.
|
|
42
|
+
|
|
43
|
+
### v0.15.0
|
|
44
|
+
|
|
45
|
+
- Added platformer-style game example.
|
|
46
|
+
- Improved game audio tutorial.
|
|
47
|
+
|
|
48
|
+
### v0.14.1
|
|
49
|
+
|
|
50
|
+
- Added `bake` tasks for release management.
|
|
51
|
+
|
|
52
|
+
### v0.14.0
|
|
53
|
+
|
|
54
|
+
- Fixed guide rendering and test suite.
|
|
55
|
+
|
|
56
|
+
### v0.13.1
|
|
57
|
+
|
|
58
|
+
- Tidied up gem dependencies.
|
|
59
|
+
|
|
60
|
+
### v0.13.0
|
|
61
|
+
|
|
62
|
+
- Added `live-audio` support for background and positional audio in applications.
|
|
63
|
+
- Added game audio example demonstrating audio playback.
|
|
64
|
+
- Fixed serving non-existent asset paths gracefully.
|
|
65
|
+
- Achieved 100% test and documentation coverage.
|
|
66
|
+
|
|
67
|
+
## See Also
|
|
68
|
+
|
|
69
|
+
- [live](https://github.com/socketry/live) — Provides client-server communication using websockets.
|
|
70
|
+
- [mayu](https://github.com/mayu-live/framework) — A live streaming server-side component-based VirtualDOM rendering framework.
|
|
71
|
+
|
|
19
72
|
## Contributing
|
|
20
73
|
|
|
21
74
|
We welcome contributions to this project.
|
|
@@ -26,21 +79,26 @@ We welcome contributions to this project.
|
|
|
26
79
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
27
80
|
5. Create new Pull Request.
|
|
28
81
|
|
|
29
|
-
###
|
|
82
|
+
### Running Tests
|
|
30
83
|
|
|
31
|
-
|
|
84
|
+
To run the test suite:
|
|
32
85
|
|
|
33
|
-
|
|
86
|
+
``` shell
|
|
87
|
+
bundle exec sus
|
|
88
|
+
```
|
|
34
89
|
|
|
35
|
-
|
|
90
|
+
### Making Releases
|
|
36
91
|
|
|
37
|
-
|
|
92
|
+
To make a new release:
|
|
38
93
|
|
|
39
|
-
|
|
94
|
+
``` shell
|
|
95
|
+
bundle exec bake gem:release:patch # or minor or major
|
|
96
|
+
```
|
|
40
97
|
|
|
41
|
-
###
|
|
98
|
+
### Developer Certificate of Origin
|
|
42
99
|
|
|
43
|
-
|
|
100
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
|
44
101
|
|
|
45
|
-
|
|
46
|
-
|
|
102
|
+
### Community Guidelines
|
|
103
|
+
|
|
104
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data/releases.md
CHANGED
|
@@ -1,3 +1,119 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.17.0
|
|
4
|
+
|
|
5
|
+
- Expose shared application state via `Application[..., controller: Controller.new]`.
|
|
6
|
+
|
|
7
|
+
## v0.16.2
|
|
8
|
+
|
|
9
|
+
- Modernize internals and update dependencies.
|
|
10
|
+
|
|
11
|
+
## v0.16.1
|
|
12
|
+
|
|
13
|
+
- Updated game audio tutorial content.
|
|
14
|
+
|
|
15
|
+
## v0.16.0
|
|
16
|
+
|
|
17
|
+
- Updated `live-audio` component dependencies.
|
|
18
|
+
|
|
19
|
+
## v0.15.1
|
|
20
|
+
|
|
21
|
+
- Fixed handling of spaces in asset names.
|
|
22
|
+
|
|
23
|
+
## v0.15.0
|
|
24
|
+
|
|
25
|
+
- Added platformer-style game example.
|
|
26
|
+
- Improved game audio tutorial.
|
|
27
|
+
|
|
3
28
|
## v0.14.1
|
|
29
|
+
|
|
30
|
+
- Added `bake` tasks for release management.
|
|
31
|
+
|
|
32
|
+
## v0.14.0
|
|
33
|
+
|
|
34
|
+
- Fixed guide rendering and test suite.
|
|
35
|
+
|
|
36
|
+
## v0.13.1
|
|
37
|
+
|
|
38
|
+
- Tidied up gem dependencies.
|
|
39
|
+
|
|
40
|
+
## v0.13.0
|
|
41
|
+
|
|
42
|
+
- Added `live-audio` support for background and positional audio in applications.
|
|
43
|
+
- Added game audio example demonstrating audio playback.
|
|
44
|
+
- Fixed serving non-existent asset paths gracefully.
|
|
45
|
+
- Achieved 100% test and documentation coverage.
|
|
46
|
+
|
|
47
|
+
## v0.12.0
|
|
48
|
+
|
|
49
|
+
- Added bundled guides for getting started and tutorials.
|
|
50
|
+
- Added Pacman example application.
|
|
51
|
+
- Improved multiplayer support in flappy bird via `falcon.rb` binding.
|
|
52
|
+
- Fixed chatbot example.
|
|
53
|
+
|
|
54
|
+
## v0.11.0
|
|
55
|
+
|
|
56
|
+
- Added `.wav` audio file support.
|
|
57
|
+
- Added worms multiplayer presentation example.
|
|
58
|
+
- Added tool-assisted AI integration example.
|
|
59
|
+
- Dropped support for Ruby v3.1.
|
|
60
|
+
|
|
61
|
+
## v0.10.1
|
|
62
|
+
|
|
63
|
+
- Fixed missing `require` statements.
|
|
64
|
+
|
|
65
|
+
## v0.10.0
|
|
66
|
+
|
|
67
|
+
- Added touch input support for mobile devices.
|
|
68
|
+
- Added multiplayer support to the flappy bird example.
|
|
69
|
+
- Default asset root is now the local `public/` directory.
|
|
70
|
+
|
|
71
|
+
## v0.9.0
|
|
72
|
+
|
|
73
|
+
- Added `.mp3` audio file support.
|
|
74
|
+
- Added sound effects to the math quest example.
|
|
75
|
+
|
|
76
|
+
## v0.8.0
|
|
77
|
+
|
|
78
|
+
- Server now binds to `localhost` by default for security.
|
|
79
|
+
- Disabled asset caching during development.
|
|
80
|
+
- Added worms example application.
|
|
81
|
+
|
|
82
|
+
## v0.7.0
|
|
83
|
+
|
|
84
|
+
- Added hello-world example application.
|
|
85
|
+
- Updated `@socketry/live` to v0.14.0.
|
|
86
|
+
- Renamed `example/` directory to `examples/`.
|
|
87
|
+
|
|
88
|
+
## v0.6.0
|
|
89
|
+
|
|
90
|
+
- Added `lively` command-line executable.
|
|
91
|
+
- Added several example applications (chatbot, flappy bird, game of life, waves).
|
|
92
|
+
|
|
93
|
+
## v0.5.0
|
|
94
|
+
|
|
95
|
+
- Updated bundled `Live.js` client library.
|
|
96
|
+
|
|
97
|
+
## v0.4.0
|
|
98
|
+
|
|
99
|
+
- Updated dependencies and `live.js`.
|
|
100
|
+
|
|
101
|
+
## v0.3.0
|
|
102
|
+
|
|
103
|
+
- Modernized gem structure.
|
|
104
|
+
- Fixed service environment configuration issues.
|
|
105
|
+
|
|
106
|
+
## v0.2.1
|
|
107
|
+
|
|
108
|
+
- Updated dependencies.
|
|
109
|
+
- Switched to `bake-gem` for release management.
|
|
110
|
+
|
|
111
|
+
## v0.2.0
|
|
112
|
+
|
|
113
|
+
- Public assets are now included in the released gem.
|
|
114
|
+
- Moved example app to a separate repository.
|
|
115
|
+
- Improved event handling and UI styling.
|
|
116
|
+
|
|
117
|
+
## v0.1.0
|
|
118
|
+
|
|
119
|
+
- Initial proof of concept implementation.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lively
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -126,6 +126,7 @@ files:
|
|
|
126
126
|
- lib/lively/hello_world.rb
|
|
127
127
|
- lib/lively/pages/index.rb
|
|
128
128
|
- lib/lively/pages/index.xrb
|
|
129
|
+
- lib/lively/resolver.rb
|
|
129
130
|
- lib/lively/version.rb
|
|
130
131
|
- license.md
|
|
131
132
|
- public/_components/@socketry/live-audio/Live/Audio.js
|
|
@@ -164,14 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
164
165
|
requirements:
|
|
165
166
|
- - ">="
|
|
166
167
|
- !ruby/object:Gem::Version
|
|
167
|
-
version: '3.
|
|
168
|
+
version: '3.3'
|
|
168
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
170
|
requirements:
|
|
170
171
|
- - ">="
|
|
171
172
|
- !ruby/object:Gem::Version
|
|
172
173
|
version: '0'
|
|
173
174
|
requirements: []
|
|
174
|
-
rubygems_version:
|
|
175
|
+
rubygems_version: 4.0.6
|
|
175
176
|
specification_version: 4
|
|
176
177
|
summary: A simple client-server SPA framework.
|
|
177
178
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|