lively 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/lively/application.rb +38 -0
- data/lib/lively/assets.rb +66 -14
- data/lib/lively/environment/application.rb +17 -2
- data/lib/lively/hello_world.rb +16 -0
- data/lib/lively/pages/index.rb +19 -1
- data/lib/lively/pages/index.xrb +2 -4
- data/lib/lively/version.rb +3 -2
- data/public/_components/@socketry/live/Live.js +42 -48
- data/public/_components/@socketry/live/package.json +4 -1
- data/public/_components/@socketry/live/readme.md +147 -31
- data/public/_components/@socketry/live-audio/Live/Audio/Controller.js +168 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Library.js +748 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Output.js +87 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Sound.js +34 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Visualizer.js +265 -0
- data/public/_components/@socketry/live-audio/Live/Audio.js +24 -0
- data/public/_components/@socketry/live-audio/package.json +35 -0
- data/public/_components/@socketry/live-audio/readme.md +250 -0
- data/public/application.js +4 -0
- data.tar.gz.sig +0 -0
- metadata +12 -4
- metadata.gz.sig +0 -0
- data/public/_components/@socketry/live/test/Live.js +0 -357
@@ -1,58 +1,174 @@
|
|
1
|
-
# Live
|
1
|
+
# Live.js
|
2
2
|
|
3
|
-
|
3
|
+
A JavaScript client library for building interactive web applications with Ruby Live framework.
|
4
4
|
|
5
|
-
|
5
|
+
[](https://github.com/socketry/live-js/actions?workflow=Test)
|
6
6
|
|
7
|
-
|
7
|
+
## Features
|
8
8
|
|
9
|
-
|
9
|
+
- **Real-time Communication**: WebSocket-based client-server communication.
|
10
|
+
- **DOM Manipulation**: Efficient updating, replacing, and modifying HTML elements.
|
11
|
+
- **Event Forwarding**: Forward client events to server for processing.
|
12
|
+
- **Controller Loading**: Declarative JavaScript controller loading with `data-live-controller`.
|
13
|
+
- **Automatic Cleanup**: Proper lifecycle management and memory cleanup.
|
14
|
+
- **Live Elements**: Automatic binding and unbinding of live elements.
|
10
15
|
|
11
|
-
|
16
|
+
## Usage
|
12
17
|
|
13
|
-
###
|
18
|
+
### Installation
|
14
19
|
|
15
|
-
|
20
|
+
```bash
|
21
|
+
npm install @socketry/live
|
22
|
+
```
|
16
23
|
|
17
|
-
|
24
|
+
### Basic Setup
|
18
25
|
|
19
|
-
|
26
|
+
```javascript
|
27
|
+
import { Live } from '@socketry/live';
|
20
28
|
|
21
|
-
|
29
|
+
// Start the live connection
|
30
|
+
const live = Live.start({
|
31
|
+
path: 'live', // WebSocket endpoint
|
32
|
+
base: window.location.href
|
33
|
+
});
|
34
|
+
```
|
22
35
|
|
23
|
-
|
36
|
+
### Controller Loading
|
24
37
|
|
25
|
-
|
38
|
+
Live.js supports declarative controller loading using the `data-live-controller` attribute:
|
26
39
|
|
27
|
-
|
40
|
+
```html
|
41
|
+
<div class="live" id="game" data-live-controller="/static/game_controller.mjs">
|
42
|
+
<!-- Game content -->
|
43
|
+
</div>
|
44
|
+
```
|
28
45
|
|
29
|
-
|
46
|
+
```javascript
|
47
|
+
// game_controller.mjs
|
48
|
+
export default function(element) {
|
49
|
+
console.log('Controller loaded for:', element);
|
50
|
+
|
51
|
+
// Setup your controller logic
|
52
|
+
element.addEventListener('click', handleClick);
|
53
|
+
|
54
|
+
// Return a controller object with cleanup
|
55
|
+
return {
|
56
|
+
dispose() {
|
57
|
+
element.removeEventListener('click', handleClick);
|
58
|
+
}
|
59
|
+
};
|
60
|
+
}
|
61
|
+
```
|
30
62
|
|
31
|
-
|
63
|
+
## API Reference
|
32
64
|
|
33
|
-
|
65
|
+
### Live Class
|
34
66
|
|
35
|
-
|
67
|
+
#### Static Methods
|
36
68
|
|
37
|
-
|
69
|
+
- `Live.start(options)` - Create and start a new Live instance
|
70
|
+
- `options.window` - Window object (defaults to globalThis)
|
71
|
+
- `options.path` - WebSocket path (defaults to 'live')
|
72
|
+
- `options.base` - Base URL (defaults to window.location.href)
|
38
73
|
|
39
|
-
|
74
|
+
#### Instance Methods
|
40
75
|
|
41
|
-
|
42
|
-
- `
|
43
|
-
- `
|
44
|
-
- `options.composed` - A boolean indicating whether the event will trigger listeners outside of a shadow root.
|
76
|
+
##### Connection Management
|
77
|
+
- `connect()` - Establish WebSocket connection
|
78
|
+
- `disconnect()` - Close WebSocket connection
|
45
79
|
|
46
|
-
|
80
|
+
##### DOM Manipulation
|
81
|
+
- `update(id, html, options)` - Update element content
|
82
|
+
- `replace(selector, html, options)` - Replace elements
|
83
|
+
- `prepend(selector, html, options)` - Prepend content
|
84
|
+
- `append(selector, html, options)` - Append content
|
85
|
+
- `remove(selector, options)` - Remove elements
|
47
86
|
|
48
|
-
|
87
|
+
##### Event Handling
|
88
|
+
- `forward(id, event)` - Forward event to server
|
89
|
+
- `forwardEvent(id, event, detail, preventDefault)` - Forward DOM event
|
90
|
+
- `forwardFormEvent(id, event, detail, preventDefault)` - Forward form event
|
49
91
|
|
50
|
-
|
92
|
+
##### Script Execution
|
93
|
+
- `script(id, code, options)` - Execute JavaScript code
|
94
|
+
- `loadController(id, path, options)` - Load JavaScript controller
|
51
95
|
|
52
|
-
|
96
|
+
##### Event Dispatching
|
97
|
+
- `dispatchEvent(selector, type, options)` - Dispatch custom events
|
53
98
|
|
54
|
-
|
99
|
+
### Options Parameter
|
55
100
|
|
56
|
-
|
101
|
+
Most methods accept an `options` parameter with:
|
102
|
+
- `options.reply` - If truthy, server will reply with `{reply: options.reply}`
|
57
103
|
|
58
|
-
|
104
|
+
### Controller Pattern
|
105
|
+
|
106
|
+
Controllers are JavaScript modules that manage view-specific behavior:
|
107
|
+
|
108
|
+
```javascript
|
109
|
+
// Simple controller
|
110
|
+
export default function(element) {
|
111
|
+
// Setup code
|
112
|
+
return {
|
113
|
+
dispose() {
|
114
|
+
// Cleanup code
|
115
|
+
}
|
116
|
+
};
|
117
|
+
}
|
118
|
+
|
119
|
+
// With options
|
120
|
+
export default function(element, options) {
|
121
|
+
const config = options.config || {};
|
122
|
+
// Use config...
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
126
|
+
## Live Elements
|
127
|
+
|
128
|
+
Elements with the `live` CSS class are automatically managed:
|
129
|
+
|
130
|
+
```html
|
131
|
+
<div class="live" id="my-element">
|
132
|
+
Content that can be updated
|
133
|
+
</div>
|
134
|
+
```
|
135
|
+
|
136
|
+
## Event Examples
|
137
|
+
|
138
|
+
### Basic Event Forwarding
|
139
|
+
|
140
|
+
```javascript
|
141
|
+
// Forward click events
|
142
|
+
element.addEventListener('click', (event) => {
|
143
|
+
live.forwardEvent('my-element', event, { button: 'clicked' });
|
144
|
+
});
|
145
|
+
|
146
|
+
// Forward form submissions
|
147
|
+
form.addEventListener('submit', (event) => {
|
148
|
+
live.forwardFormEvent('my-form', event, { action: 'submit' });
|
149
|
+
});
|
150
|
+
```
|
151
|
+
|
152
|
+
## Contributing
|
153
|
+
|
154
|
+
We welcome contributions to this project.
|
155
|
+
|
156
|
+
1. Fork it.
|
157
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
158
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
159
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
160
|
+
5. Create new Pull Request.
|
161
|
+
|
162
|
+
### Developer Certificate of Origin
|
163
|
+
|
164
|
+
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.
|
165
|
+
|
166
|
+
### Community Guidelines
|
167
|
+
|
168
|
+
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.
|
169
|
+
|
170
|
+
## See Also
|
171
|
+
|
172
|
+
- [lively](https://github.com/socketry/lively) — Ruby framework for building interactive web applications.
|
173
|
+
- [live](https://github.com/socketry/live) — Provides client-server communication using websockets.
|
174
|
+
- [live-audio-js](https://github.com/socketry/live-audio-js) — Web Audio API-based game audio synthesis library.
|
@@ -0,0 +1,168 @@
|
|
1
|
+
// Audio Controller - manages sound instances and provides unified API
|
2
|
+
import { Output } from './Output.js';
|
3
|
+
import { Sound } from './Sound.js';
|
4
|
+
|
5
|
+
// Get or create shared AudioContext (keyed by window)
|
6
|
+
async function getSharedAudioContext(window = globalThis) {
|
7
|
+
const contextKey = '_liveAudioContext';
|
8
|
+
|
9
|
+
let audioContext = window[contextKey];
|
10
|
+
|
11
|
+
if (!audioContext || audioContext.state === 'closed') {
|
12
|
+
audioContext = new (window.AudioContext || window.webkitAudioContext)({
|
13
|
+
latencyHint: 'interactive',
|
14
|
+
});
|
15
|
+
|
16
|
+
if (audioContext.state === 'suspended') {
|
17
|
+
await audioContext.resume();
|
18
|
+
}
|
19
|
+
|
20
|
+
window[contextKey] = audioContext;
|
21
|
+
|
22
|
+
console.log(`Live Audio Context created - Sample rate: ${audioContext.sampleRate}Hz, State: ${audioContext.state}`);
|
23
|
+
}
|
24
|
+
|
25
|
+
return audioContext;
|
26
|
+
}
|
27
|
+
|
28
|
+
export class Controller {
|
29
|
+
#window = null;
|
30
|
+
#audioContext = null;
|
31
|
+
#output = null;
|
32
|
+
#sounds = {};
|
33
|
+
#volume = 1.0;
|
34
|
+
|
35
|
+
// Callbacks:
|
36
|
+
#onOutputCreated = null;
|
37
|
+
#onOutputDisposed = null;
|
38
|
+
|
39
|
+
constructor(window = globalThis, options = {}) {
|
40
|
+
this.#window = window;
|
41
|
+
this.#onOutputCreated = options.onOutputCreated || null;
|
42
|
+
this.#onOutputDisposed = options.onOutputDisposed || null;
|
43
|
+
}
|
44
|
+
|
45
|
+
// Acquire output with AudioContext ready - returns null if not available.
|
46
|
+
async acquireOutput() {
|
47
|
+
let output = this.#output;
|
48
|
+
|
49
|
+
if (!output) {
|
50
|
+
// First get the AudioContext at Controller level
|
51
|
+
const audioContext = await getSharedAudioContext(this.#window);
|
52
|
+
if (!audioContext) return null;
|
53
|
+
|
54
|
+
// Then create Output instance with AudioContext:
|
55
|
+
this.#output = output = new Output(audioContext);
|
56
|
+
|
57
|
+
// Apply the controller's volume to the new output
|
58
|
+
output.setVolume(this.#volume);
|
59
|
+
|
60
|
+
// Call the output created callback if provided
|
61
|
+
if (this.#onOutputCreated) {
|
62
|
+
this.#onOutputCreated(this, output);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
return output;
|
67
|
+
}
|
68
|
+
|
69
|
+
// Add a sound to this controller instance
|
70
|
+
addSound(name, value) {
|
71
|
+
this.#sounds[name] = value;
|
72
|
+
return value;
|
73
|
+
}
|
74
|
+
|
75
|
+
// Play a sound by name
|
76
|
+
async playSound(name) {
|
77
|
+
// Return early if volume is zero (muted)
|
78
|
+
if (this.#volume <= 0) return;
|
79
|
+
|
80
|
+
const sound = this.#sounds[name];
|
81
|
+
if (sound) {
|
82
|
+
const output = await this.acquireOutput();
|
83
|
+
if (!output) return;
|
84
|
+
sound.play(output);
|
85
|
+
} else {
|
86
|
+
console.warn(`Sound '${name}' not found`);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Stop a sound by name
|
91
|
+
stopSound(name) {
|
92
|
+
const sound = this.#sounds[name];
|
93
|
+
if (sound) {
|
94
|
+
sound.stop();
|
95
|
+
} else {
|
96
|
+
console.warn(`Sound '${name}' not found`);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
// Stop all sounds
|
101
|
+
stopAllSounds() {
|
102
|
+
if (this.#sounds) {
|
103
|
+
Object.values(this.#sounds).forEach(sound => sound.stop());
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
// Get a sound instance for direct access
|
108
|
+
getSound(name) {
|
109
|
+
return this.#sounds[name];
|
110
|
+
}
|
111
|
+
|
112
|
+
// List all available sound names
|
113
|
+
listSounds() {
|
114
|
+
return Object.keys(this.#sounds);
|
115
|
+
}
|
116
|
+
|
117
|
+
// Remove a sound
|
118
|
+
removeSound(name) {
|
119
|
+
if (this.#sounds[name]) {
|
120
|
+
delete this.#sounds[name];
|
121
|
+
return true;
|
122
|
+
}
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
|
126
|
+
// Set master volume
|
127
|
+
async setVolume(volume) {
|
128
|
+
this.#volume = volume;
|
129
|
+
|
130
|
+
// Apply to output if it exists, or acquire it
|
131
|
+
const output = await this.acquireOutput();
|
132
|
+
if (output) {
|
133
|
+
output.setVolume(volume);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
// Get current volume
|
138
|
+
get volume() {
|
139
|
+
return this.#volume;
|
140
|
+
}
|
141
|
+
|
142
|
+
// Get sounds object (for testing)
|
143
|
+
get sounds() {
|
144
|
+
return this.#sounds;
|
145
|
+
}
|
146
|
+
|
147
|
+
// Get window object (for testing)
|
148
|
+
get window() {
|
149
|
+
return this.#window;
|
150
|
+
}
|
151
|
+
|
152
|
+
// Dispose of the controller and clean up resources
|
153
|
+
dispose() {
|
154
|
+
if (this.#output) {
|
155
|
+
const output = this.#output;
|
156
|
+
|
157
|
+
// Call the disposal callback if provided
|
158
|
+
if (this.#onOutputDisposed) {
|
159
|
+
this.#onOutputDisposed(this, output);
|
160
|
+
}
|
161
|
+
|
162
|
+
output.dispose();
|
163
|
+
this.#output = null;
|
164
|
+
}
|
165
|
+
|
166
|
+
this.#sounds = {};
|
167
|
+
}
|
168
|
+
}
|