svelte-on-rails 5.1.0 → 5.3.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
- data/README.md +106 -60
- data/lib/generators/svelte_on_rails/install/install_generator.rb +7 -3
- data/lib/svelte-on-rails.rb +2 -0
- data/lib/svelte_on_rails/action_cable.rb +49 -20
- data/lib/svelte_on_rails/active_record_extensions.rb +19 -0
- data/lib/svelte_on_rails/installer/hello_world.rb +1 -1
- data/lib/svelte_on_rails/lib/utils.rb +147 -0
- data/lib/svelte_on_rails/lib/view_helper_support.rb +3 -30
- data/lib/svelte_on_rails/railtie.rb +6 -0
- data/lib/svelte_on_rails/turbo_stream.rb +43 -3
- data/lib/tasks/svelte_on_rails_tasks.rake +2 -2
- data/templates/all_features_test/app/controllers/svelte_on_rails_hello_world_controller.rb +66 -0
- data/templates/all_features_test/app/frontend/initializers/actionCable.js +17 -0
- data/templates/all_features_test/app/frontend/javascript/components/ReceiveFromChannel.svelte +70 -0
- data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_nav.html.erb +1 -1
- data/templates/config_base/config/svelte_on_rails.yml +4 -0
- metadata +24 -22
- data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb +0 -35
- data/templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte +0 -57
- /data/templates/{rails_vite_hello_world → all_features_test}/app/channels/svelte_on_rails_channel.rb +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-england.png +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JavascriptImport.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JpgImport.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/ParentWithChild.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/PngImport.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvgRawImport.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/sub/NestedComponent.svelte +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascript.js +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascriptToggled.js +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_styles.html.erb +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/index.html.erb +0 -0
- /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb +0 -0
- /data/templates/{rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb → all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f91cbdd2375be3978749859e8bb36b16fbba13fa1e9ac494b9b5e731bc06f91
|
4
|
+
data.tar.gz: f0164b5a93bfb62b2abeedbaebc85064253ad6e4500d9d94a0cfda570416e794
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f490b501d07cfe64636b73e45ba2b42d30cfc2fc33168488635565fee4a1d3def9e31b5097cbba82aa7ebfad92e08b6a6be30afa53385dbb206e616d6a72352e
|
7
|
+
data.tar.gz: 94e88d6876d9d434e504899f47f5d71ba2d3129a3a55a94992ce6a573d9e78ab305e306f7c3c2576c0135b3aeb03c5578fec74175eb2fc18d89151bac059d664
|
data/README.md
CHANGED
@@ -34,11 +34,14 @@ vision while aligning seamlessly with Rails’ full-stack philosophy.
|
|
34
34
|
- Svelte eliminates redundant HTML state logic
|
35
35
|
- Consolidates component logic into a single file
|
36
36
|
- Offloads rendering of certain HTML components to frontend JavaScript, reducing server load
|
37
|
-
- **Compared to React**
|
38
|
-
-
|
39
|
-
|
40
|
-
-
|
41
|
-
-
|
37
|
+
- **Compared to React/Vue**
|
38
|
+
- Svelte eliminates the virtual DOM, resulting in leaner, more compact packages and faster performance
|
39
|
+
- Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison with react
|
40
|
+
- Easier to learn
|
41
|
+
- React and Vue having a vaster Community, bigger Ecosystem
|
42
|
+
- But, for integration, like here, this is not importand
|
43
|
+
- Svelte is sufficiently established
|
44
|
+
- Svelte is younger and growing
|
42
45
|
|
43
46
|
Svelte empowers Rails’ full-stack vision with modern, streamlined frontend integration.
|
44
47
|
|
@@ -307,6 +310,46 @@ Consider setting `ssr: false` for testing only for performance reasons.
|
|
307
310
|
Yo can override this on a attribute on the view-helper for specific components.
|
308
311
|
But, in normal cases it should not be neccessary testing ssr explicitly.
|
309
312
|
|
313
|
+
## ActiveRecord helpers
|
314
|
+
|
315
|
+
Adds the `#svelte_attributes` helper to your models, example:
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
@book.svelte_attributes(
|
319
|
+
:name,
|
320
|
+
:calculation_method,
|
321
|
+
author: [:name]
|
322
|
+
)
|
323
|
+
```
|
324
|
+
|
325
|
+
would result in something like this:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
{
|
329
|
+
"values" => {
|
330
|
+
"name" => "Learning Ruby",
|
331
|
+
"calculation_method": "any-result",
|
332
|
+
"author" => {
|
333
|
+
"name" => "Michael Hartl"
|
334
|
+
},
|
335
|
+
},
|
336
|
+
"book_labels" => {
|
337
|
+
"name" => "Name", # translated by human_attribute_name..
|
338
|
+
"calculation_method" => "Calculation method",
|
339
|
+
"author" => "Author"
|
340
|
+
},
|
341
|
+
"author_labels": {
|
342
|
+
"name" => "Name"
|
343
|
+
}
|
344
|
+
}
|
345
|
+
```
|
346
|
+
|
347
|
+
This should ease transferring data you need within the component mostly.
|
348
|
+
|
349
|
+
If an optional `belongs_to` association is empty, the labels are still calculated.
|
350
|
+
|
351
|
+
**Caching:** The component's attributes are used to generate a checksum, which serves as the cache key for efficient storage and retrieval.
|
352
|
+
|
310
353
|
## Caching
|
311
354
|
|
312
355
|
Caching only is relevant for `ssr`
|
@@ -365,7 +408,17 @@ turbo_stream:
|
|
365
408
|
channel: 'public'
|
366
409
|
```
|
367
410
|
|
368
|
-
## ActionCable
|
411
|
+
## ActionCable vs TurboStream
|
412
|
+
|
413
|
+
- **ActionCable**
|
414
|
+
- Cleaner setup no html needed
|
415
|
+
- Seams to initialize securer, especially for testings (not: production)
|
416
|
+
- **TurboStream**
|
417
|
+
- Secured Streams by html-tag `signed-stream-name` if you want to send confidential data
|
418
|
+
over streams or have different channels for each user privileges
|
419
|
+
- Has [Compatibility issue with UJS](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#compatibility-with-rails-ujs)
|
420
|
+
|
421
|
+
## SvelteOnRails::ActionCable
|
369
422
|
|
370
423
|
ActionCable is the more basic library behind `TurboStream` and it is a second Option to call Javascript Actions from the server.
|
371
424
|
|
@@ -395,7 +448,8 @@ Add to `application.js`
|
|
395
448
|
|
396
449
|
```javascript
|
397
450
|
import { createConsumer } from "@rails/actioncable"
|
398
|
-
import {dispatchSvelteStreamEvent, actionCableDebugLog} from '@csedl/svelte-on-rails'
|
451
|
+
import { SvelteOnRails, dispatchSvelteStreamEvent, actionCableDebugLog } from '@csedl/svelte-on-rails'
|
452
|
+
SvelteOnRails.debug = true
|
399
453
|
|
400
454
|
const consumer = createConsumer()
|
401
455
|
|
@@ -413,9 +467,7 @@ consumer.subscriptions.create("SvelteOnRailsChannel", {
|
|
413
467
|
})
|
414
468
|
```
|
415
469
|
|
416
|
-
|
417
|
-
|
418
|
-
**WhatdispatchSvelteStreamEvent does**
|
470
|
+
**What dispatchSvelteStreamEvent does**
|
419
471
|
|
420
472
|
- Without the attribute `component` given,
|
421
473
|
it searches for all elements with the class `svelte-component` and fires the event `channel-action`
|
@@ -424,18 +476,43 @@ By setting `debug = true` [node package](https://www.npmjs.com/package/@csedl/sv
|
|
424
476
|
|
425
477
|
**Usage**
|
426
478
|
|
479
|
+
`app/frontend/javascript/components/folder/MyComponent.svelte`
|
480
|
+
|
481
|
+
```sveltehtml
|
482
|
+
<script>
|
483
|
+
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
484
|
+
|
485
|
+
function handleCableEvent(event) {
|
486
|
+
console.log('Event received by Turbo Stream', event.detail)
|
487
|
+
}
|
488
|
+
</script>
|
489
|
+
<!--on ANY element:-->
|
490
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
491
|
+
```
|
492
|
+
|
493
|
+
The `addComponentStreamListener` adds the eventListener `stream-action` on the wrapping Element.
|
494
|
+
The «wrapping Element» is the Element from the view helper `svelte_component` with the class `svelte-component`.
|
495
|
+
|
427
496
|
Now you can dispatch events on the component by:
|
428
497
|
|
429
498
|
```ruby
|
430
|
-
ActionCable.
|
431
|
-
|
432
|
-
|
433
|
-
component: '/javascript/components/ReceiveFromChannel',
|
434
|
-
}
|
499
|
+
SvelteOnRails::ActionCable.dispatch(
|
500
|
+
'folder/MyComponent',
|
501
|
+
{ message: "greetings from Server: äöü🤣🌴🌍漢字" }
|
435
502
|
)
|
436
503
|
```
|
437
504
|
|
438
|
-
|
505
|
+
And you will find the object, with the message key on the browser logs.
|
506
|
+
|
507
|
+
Without any arguments, just by `SvelteOnRails::ActionCable.dispatch` it would fire the `stream-action` event on all components.
|
508
|
+
|
509
|
+
The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
|
510
|
+
on the whole `document` and fires the given event there.
|
511
|
+
|
512
|
+
# SvelteOnRails::TurboStream
|
513
|
+
|
514
|
+
Turbo Stream makes more sense when you think of sending confidential data to the components
|
515
|
+
or you want to separate to channels based on user groups, for example.
|
439
516
|
|
440
517
|
Few setup is needed for that:
|
441
518
|
|
@@ -449,15 +526,15 @@ rails turbo:install
|
|
449
526
|
```
|
450
527
|
|
451
528
|
make sure that `import "@hotwired/turbo-rails"` is on your application.js and set the view helper
|
452
|
-
`<%= turbo_stream_from '
|
529
|
+
`<%= turbo_stream_from 'authenticated' if current_user %>` to your view.
|
453
530
|
|
454
|
-
If a channel (e.g.: `
|
531
|
+
If a channel (e.g.: `authenticated`) is active and you have an HTML element with a HTML-ID (e.g.: `svelte-on-rails-stream-actions-box`),
|
455
532
|
you can test it by:
|
456
533
|
|
457
534
|
```ruby
|
458
535
|
Turbo::StreamsChannel.send(
|
459
536
|
"broadcast_append_to",
|
460
|
-
'
|
537
|
+
'authenticated',
|
461
538
|
target: 'svelte-on-rails-stream-actions-box',
|
462
539
|
content: "<div>Turbo-Streams are working!</div>"
|
463
540
|
)
|
@@ -467,27 +544,13 @@ When this works you are good to go.
|
|
467
544
|
|
468
545
|
**Minimal Usage Example**
|
469
546
|
|
470
|
-
```sveltehtml
|
471
|
-
<script>
|
472
|
-
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
473
|
-
|
474
|
-
function handleCableEvent() {
|
475
|
-
console.log('Event received by Turbo Stream')
|
476
|
-
}
|
477
|
-
</script>
|
478
|
-
<!--on any element:-->
|
479
|
-
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
480
|
-
```
|
481
|
-
|
482
547
|
And call this by:
|
483
548
|
|
484
549
|
```ruby
|
485
|
-
SvelteOnRails::TurboStream.
|
550
|
+
SvelteOnRails::TurboStream.dispatch
|
486
551
|
```
|
487
552
|
**What it does**
|
488
553
|
|
489
|
-
- The `addComponentStreamListener` adds the eventListener `stream-action` on the wrapping Element
|
490
|
-
- The «wrapping Element» is the Element from the view helper `svelte_component` with the class `svelte-component`.
|
491
554
|
- A Stimulus controller is pushed to all subscribers of the configured channel.
|
492
555
|
- You can override the channel name by passing `channel` to the method.
|
493
556
|
|
@@ -510,7 +573,7 @@ within the svelte component.
|
|
510
573
|
Then, call it by:
|
511
574
|
|
512
575
|
```ruby
|
513
|
-
SvelteOnRails::TurboStream.
|
576
|
+
SvelteOnRails::TurboStream.dispatch(
|
514
577
|
channel: 'my-custom-stream',
|
515
578
|
component: '/javascript/components/TurboStreamsChannel',
|
516
579
|
selector: '.counter-button',
|
@@ -519,7 +582,8 @@ Then, call it by:
|
|
519
582
|
)
|
520
583
|
```
|
521
584
|
|
522
|
-
|
585
|
+
The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
|
586
|
+
on the whole `document` and fires the given event there.
|
523
587
|
|
524
588
|
## More rake tasks
|
525
589
|
|
@@ -575,30 +639,12 @@ This will cause the installer, to install the npm package from a local path inst
|
|
575
639
|
Then run the tests and start contributing.
|
576
640
|
|
577
641
|
**RUN THE FIRST TEST (!)**: Testing is complex here because of the design based on the installer test.
|
578
|
-
On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`.
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
test_helpers. But I have done my best to give all the code a clear structure.
|
585
|
-
I hope you like it, and improvements are welcome.
|
586
|
-
|
587
|
-
**Installer tests** starting with completely destroy the rails app within the `generated_test_app_path`,
|
588
|
-
generating a new rails app and running the installer and test by `playwright` if the components are working.
|
589
|
-
|
590
|
-
**component tests** only checking if a rails server is alive, and if not, install and run a rails app.
|
591
|
-
For this is the testing helper `start_rails_server_unless_ping`. This step may only be slow on the
|
592
|
-
first run, then it is fast. And on every repeating the test it always overwrites the components
|
593
|
-
with the components from the template by the testing helper `install_hello_world(
|
594
|
-
['rails_vite_hello_world'],
|
595
|
-
app_root: generated_rails_app_root,
|
596
|
-
force: true,
|
597
|
-
silent: true
|
598
|
-
)`. At the end of the test it leaves the rails server running.
|
599
|
-
|
600
|
-
On that way a developer can just edit the templates and run a test and see always the refreshed
|
601
|
-
content on the browser and on the app within the `generated_test_app_path`.
|
642
|
+
On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`.
|
643
|
+
If there are problems, open the generated app on a IDE and check errors there.
|
644
|
+
|
645
|
+
When this passes, all the others passing mostly.
|
646
|
+
|
647
|
+
At the end of the most tests it leaves the rails server running, so that you can see the result on `localhost:3000`.
|
602
648
|
|
603
649
|
NOTE: Theese tests are dependend on your environment, including the running ruby version!
|
604
650
|
I am working on rvm. If you work on a different environment, some (not many) changes may be necessary.
|
@@ -129,11 +129,15 @@ module SvelteOnRails
|
|
129
129
|
hw_i = SvelteOnRails::Installer::HelloWorld
|
130
130
|
|
131
131
|
utils_i = SvelteOnRails::Installer::Utils
|
132
|
-
utils_i.add_route(" get \"svelte_on_rails_hello_world/
|
133
|
-
utils_i.add_route(" get \"svelte_on_rails_hello_world/
|
132
|
+
utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket\"", app_root: Rails.root)
|
133
|
+
utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket_action\"", app_root: Rails.root)
|
134
134
|
npm_i = SvelteOnRails::Installer::Npm
|
135
135
|
npm_i.install_or_update_package('axios')
|
136
|
-
|
136
|
+
npm_i.install_or_update_package('@rails/actioncable')
|
137
|
+
js_i = SvelteOnRails::Installer::Javascript
|
138
|
+
init_stat = '../initializers/actionCable.js'
|
139
|
+
js_i.append_import_statement(application_js_path, init_stat, "import '#{init_stat}';")
|
140
|
+
@hello_world_path = hw_i.install_hello_world(['all_features_test'], app_root: nil, force: true, silent: true)
|
137
141
|
end
|
138
142
|
|
139
143
|
def haml_and_convert
|
data/lib/svelte-on-rails.rb
CHANGED
@@ -2,6 +2,8 @@ require "svelte_on_rails/configuration"
|
|
2
2
|
require "svelte_on_rails/view_helpers"
|
3
3
|
require "svelte_on_rails/turbo_stream"
|
4
4
|
require "svelte_on_rails/action_cable"
|
5
|
+
require "svelte_on_rails/active_record_extensions"
|
6
|
+
|
5
7
|
require "svelte_on_rails/railtie" if defined?(Rails)
|
6
8
|
|
7
9
|
require "svelte_on_rails/renderer/renderer"
|
@@ -1,6 +1,16 @@
|
|
1
1
|
module SvelteOnRails
|
2
2
|
class ActionCable
|
3
|
-
def
|
3
|
+
def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
|
4
|
+
|
5
|
+
utils = SvelteOnRails::Lib::Utils
|
6
|
+
conf = SvelteOnRails::Configuration.instance
|
7
|
+
|
8
|
+
_comp = if component
|
9
|
+
utils.validate_filename(component)
|
10
|
+
"/#{conf.components_folder + component}"
|
11
|
+
end
|
12
|
+
|
13
|
+
_channel = require_channel(svelte_on_rails_configs['channel'] || channel)
|
4
14
|
|
5
15
|
if event != 'stream-action' && !selector
|
6
16
|
raise "Another event name than the default one is only possible together with a selector"
|
@@ -8,35 +18,54 @@ module SvelteOnRails
|
|
8
18
|
|
9
19
|
args = {
|
10
20
|
eventDetail: event_detail,
|
11
|
-
component:
|
21
|
+
component: _comp,
|
12
22
|
event: event,
|
13
23
|
selector: selector
|
14
24
|
}
|
15
25
|
|
16
|
-
|
26
|
+
::ActionCable.server.broadcast(
|
27
|
+
_channel,
|
28
|
+
args
|
29
|
+
)
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
|
17
34
|
|
18
|
-
|
35
|
+
_channel = require_channel(svelte_on_rails_configs['channel'] || channel)
|
36
|
+
|
37
|
+
if event != 'stream-action' && !selector
|
38
|
+
raise "Another event name than the default one is only possible together with a selector"
|
39
|
+
end
|
40
|
+
|
41
|
+
args = {
|
42
|
+
eventDetail: event_detail,
|
43
|
+
component: '/false/',
|
44
|
+
event: event,
|
45
|
+
selector: selector
|
46
|
+
}
|
47
|
+
|
48
|
+
::ActionCable.server.broadcast(
|
49
|
+
_channel,
|
50
|
+
args
|
51
|
+
)
|
19
52
|
|
20
53
|
end
|
21
54
|
|
22
55
|
private
|
23
56
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
unless conf.configs[:turbo_stream]
|
29
|
-
raise '[svelte-on-rails] missing configuration: :turbo_stream'
|
30
|
-
end
|
31
|
-
unless conf.configs[:turbo_stream]['target_html_id']
|
32
|
-
raise '[svelte-on-rails] missing configuration: turbo_stream/target_html_id'
|
33
|
-
end
|
34
|
-
unless conf.configs[:turbo_stream]['channel']
|
35
|
-
raise '[svelte-on-rails] missing configuration: turbo_stream/channel'
|
36
|
-
end
|
37
|
-
|
38
|
-
conf.configs[:turbo_stream]
|
39
|
-
end
|
57
|
+
def self.svelte_on_rails_configs
|
58
|
+
cnf = SvelteOnRails::Configuration.instance.configs[:action_cable]
|
59
|
+
raise 'ActionCable not configured for SvelteOnRails' if cnf.nil?
|
60
|
+
cnf
|
40
61
|
end
|
62
|
+
|
63
|
+
def self.require_channel(channel)
|
64
|
+
unless channel.present?
|
65
|
+
raise 'Missing attribute or configuration: action_cable/channel'
|
66
|
+
end
|
67
|
+
channel
|
68
|
+
end
|
69
|
+
|
41
70
|
end
|
42
71
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# lib/svelte_on_rails/active_record_extensions.rb
|
2
|
+
module SvelteOnRails
|
3
|
+
module ActiveRecordExtensions
|
4
|
+
def self.included(base)
|
5
|
+
unless defined?(ActiveRecord::Base) && base.ancestors.include?(ActiveRecord::Base)
|
6
|
+
raise 'SvelteOnRails::ActiveRecordExtensions can only be included in ActiveRecord models'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a hash of attributes, methods, and associations formatted for Svelte components
|
11
|
+
def svelte_attributes(*attributes)
|
12
|
+
@svelte_attributes ||= begin
|
13
|
+
utils = SvelteOnRails::Lib::Utils
|
14
|
+
utils.svelte_attributes(self, attributes)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -38,7 +38,7 @@ module SvelteOnRails
|
|
38
38
|
end
|
39
39
|
|
40
40
|
|
41
|
-
def self.remove_hello_world(templates = ['
|
41
|
+
def self.remove_hello_world(templates = ['all_features_test'], app_root: nil, ask: true)
|
42
42
|
|
43
43
|
utils = SvelteOnRails::Installer::Utils
|
44
44
|
|
@@ -137,6 +137,153 @@ module SvelteOnRails
|
|
137
137
|
Rails.logger.warn(" => #{line}")
|
138
138
|
end
|
139
139
|
end
|
140
|
+
|
141
|
+
def self.validate_filename(filename)
|
142
|
+
|
143
|
+
conf = SvelteOnRails::Configuration.instance
|
144
|
+
|
145
|
+
file_path = conf.components_folder_full + "#{filename}.svelte"
|
146
|
+
|
147
|
+
unless File.exist?(file_path)
|
148
|
+
|
149
|
+
raise "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
|
150
|
+
"#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
|
151
|
+
"Your configurations are:\n\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
if conf.watch_changes? && !file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
|
156
|
+
|
157
|
+
raise "svelte-on-rails gem, view helper #svelte_component\n\n" +
|
158
|
+
"File found but Upper and lower case letters are incorrect:\n" +
|
159
|
+
"(This check is only on development environments when watch_changes is true)\n\n" +
|
160
|
+
"#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
|
161
|
+
"Your configurations are:\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.svelte_attributes(record, attributes, labels = {}, call_stack: 0, offset: nil, limit: nil)
|
167
|
+
|
168
|
+
if record.respond_to?(:each)
|
169
|
+
unless record.is_a?(ActiveRecord::Relation) || record.is_a?(ActiveRecord::Associations::CollectionProxy)
|
170
|
+
raise 'record set must be a Article::ActiveRecord_Associations_CollectionProxy'
|
171
|
+
end
|
172
|
+
|
173
|
+
recs = (limit ? record.limit(limit) : record)
|
174
|
+
recs2 = (offset ? recs.offset(limit) : recs)
|
175
|
+
|
176
|
+
values = recs2.map do |rec|
|
177
|
+
svelte_attributes(rec, attributes, labels, call_stack: call_stack + 1)
|
178
|
+
end
|
179
|
+
|
180
|
+
elsif record.is_a?(Class)
|
181
|
+
|
182
|
+
# this is the case if a belongs_to association is empty; In that case we pass the class itself and extract only the labels
|
183
|
+
raise 'limit and offset are supported only for iterable objects' if limit || offset
|
184
|
+
attributes.each do |attr|
|
185
|
+
unless attr.respond_to?(:each)
|
186
|
+
labels["#{record.to_s.underscore}_labels"] ||= {}
|
187
|
+
labels["#{record.to_s.underscore}_labels"][attr.to_s] ||= record.human_attribute_name(attr)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
values = {}
|
191
|
+
|
192
|
+
else
|
193
|
+
|
194
|
+
# we have a single record
|
195
|
+
|
196
|
+
raise 'limit and offset are supported only for iterable objects' if limit || offset
|
197
|
+
values = {}
|
198
|
+
|
199
|
+
table_name = record.class.to_s.underscore
|
200
|
+
|
201
|
+
attributes.each do |attr|
|
202
|
+
|
203
|
+
if attr.is_a? Hash
|
204
|
+
|
205
|
+
# we have associations
|
206
|
+
attr.each do |key, value|
|
207
|
+
_key = key.to_s
|
208
|
+
|
209
|
+
# skip and fetch offset and limit
|
210
|
+
if key.to_s.match(/_limit$/) && !record.respond_to?(key)
|
211
|
+
raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-7])
|
212
|
+
next
|
213
|
+
end
|
214
|
+
if key.to_s.match(/_offset$/) && !record.respond_to?(key)
|
215
|
+
raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-8])
|
216
|
+
next
|
217
|
+
end
|
218
|
+
offs, lim = svelte_attribute_extract_limit(record, attributes, _key)
|
219
|
+
|
220
|
+
labels["#{table_name}_labels"] ||= {}
|
221
|
+
labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
|
222
|
+
|
223
|
+
# values
|
224
|
+
|
225
|
+
content = record.send(_key)
|
226
|
+
reflect = record.class.reflect_on_association(_key)
|
227
|
+
if reflect
|
228
|
+
if content.respond_to?(:each)
|
229
|
+
values[_key] = svelte_attributes(
|
230
|
+
content, value, labels,
|
231
|
+
call_stack: call_stack + 1,
|
232
|
+
offset: offs,
|
233
|
+
limit: lim
|
234
|
+
)
|
235
|
+
else
|
236
|
+
values[_key] = svelte_attributes(
|
237
|
+
content || reflect.active_record, # if no record, we extract only the labels
|
238
|
+
value, labels,
|
239
|
+
call_stack: call_stack + 1,
|
240
|
+
offset: offs,
|
241
|
+
limit: lim
|
242
|
+
)
|
243
|
+
end
|
244
|
+
else
|
245
|
+
raise "invalid association: #{_key}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
else
|
250
|
+
# we have attributes
|
251
|
+
raise 'Invalid attribute' unless [Symbol, String].include?(attr.class)
|
252
|
+
_key = attr.to_s
|
253
|
+
|
254
|
+
labels["#{table_name}_labels"] ||= {}
|
255
|
+
labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
|
256
|
+
|
257
|
+
values[_key] = record.send(_key)
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
if call_stack >= 1
|
264
|
+
values
|
265
|
+
else
|
266
|
+
{ 'values' => values }.merge(labels)
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
def self.svelte_attribute_extract_limit(record, attributes, key)
|
274
|
+
|
275
|
+
hash_args = attributes.grep(Hash).first.with_indifferent_access # multiple arrays is not possible
|
276
|
+
|
277
|
+
offset = if hash_args["#{key}_offset"] && !record.respond_to?("#{key}_offset")
|
278
|
+
hash_args["#{key}_offset"]
|
279
|
+
end
|
280
|
+
|
281
|
+
limit = if hash_args["#{key}_limit"] && !record.respond_to?("#{key}_limit")
|
282
|
+
hash_args["#{key}_limit"]
|
283
|
+
end
|
284
|
+
|
285
|
+
[offset, limit]
|
286
|
+
end
|
140
287
|
end
|
141
288
|
end
|
142
289
|
end
|
@@ -8,9 +8,11 @@ module SvelteOnRails
|
|
8
8
|
def initialize(file, args, request, caching = false)
|
9
9
|
|
10
10
|
@start_time = Time.now
|
11
|
+
@conf = SvelteOnRails::Configuration.instance
|
12
|
+
utils = SvelteOnRails::Lib::Utils
|
13
|
+
utils.validate_filename(file) if @conf.watch_changes?
|
11
14
|
@filename = (file.match(/\.svelte$/) ? file[0..-8] : file)
|
12
15
|
@args_checksum = Zlib.crc32(args.to_json).to_s(36)
|
13
|
-
@conf = SvelteOnRails::Configuration.instance
|
14
16
|
@helper_options, @html_options, @svelte_props = split_props(
|
15
17
|
args,
|
16
18
|
%i[class id style],
|
@@ -18,7 +20,6 @@ module SvelteOnRails
|
|
18
20
|
)
|
19
21
|
@request = request
|
20
22
|
@ssr = determine_ssr
|
21
|
-
validate_file if @conf.watch_changes?
|
22
23
|
|
23
24
|
# precompile
|
24
25
|
|
@@ -64,24 +65,6 @@ module SvelteOnRails
|
|
64
65
|
@ssr
|
65
66
|
end
|
66
67
|
|
67
|
-
def file_not_found_message
|
68
|
-
ff = conf.frontend_folder
|
69
|
-
cf = conf.components_folder
|
70
|
-
"svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
|
71
|
-
"#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
|
72
|
-
"Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
|
73
|
-
end
|
74
|
-
|
75
|
-
def file_case_sensitive_message
|
76
|
-
ff = conf.frontend_folder
|
77
|
-
cf = conf.components_folder
|
78
|
-
"svelte-on-rails gem, view helper #svelte_component\n\n" +
|
79
|
-
"File found but Upper and lower case letters are incorrect:\n" +
|
80
|
-
"(This check is only on development environments when watch_changes is true)\n\n" +
|
81
|
-
"#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
|
82
|
-
"Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
|
83
|
-
end
|
84
|
-
|
85
68
|
def log_rendering(message)
|
86
69
|
|
87
70
|
Rails.logger.info " #{message} (#{elapsed_milliseconds}ms)"
|
@@ -119,16 +102,6 @@ module SvelteOnRails
|
|
119
102
|
|
120
103
|
private
|
121
104
|
|
122
|
-
def validate_file
|
123
|
-
file_path = conf.components_folder_full + "#{filename}.svelte"
|
124
|
-
unless File.exist?(file_path)
|
125
|
-
raise file_not_found_message
|
126
|
-
end
|
127
|
-
if conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
|
128
|
-
raise file_case_sensitive_message
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
105
|
def determine_ssr
|
133
106
|
_ssr = if @helper_options.key?(:ssr)
|
134
107
|
if @conf.watch_changes?
|
@@ -13,6 +13,12 @@ module SvelteOnRails
|
|
13
13
|
SvelteOnRails::Configuration.instance
|
14
14
|
end
|
15
15
|
|
16
|
+
initializer 'svelte_on_rails.active_record_extensions' do
|
17
|
+
ActiveSupport.on_load(:active_record) do
|
18
|
+
include SvelteOnRails::ActiveRecordExtensions
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
16
22
|
rake_tasks do
|
17
23
|
load File.expand_path("../../tasks/svelte_on_rails_tasks.rake", __FILE__)
|
18
24
|
end
|
@@ -1,6 +1,14 @@
|
|
1
1
|
module SvelteOnRails
|
2
2
|
class TurboStream
|
3
|
-
def
|
3
|
+
def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
|
4
|
+
|
5
|
+
utils = SvelteOnRails::Lib::Utils
|
6
|
+
conf = SvelteOnRails::Configuration.instance
|
7
|
+
|
8
|
+
_comp = if component
|
9
|
+
utils.validate_filename(component)
|
10
|
+
"/#{conf.components_folder + component}"
|
11
|
+
end
|
4
12
|
|
5
13
|
if event != 'stream-action' && !selector
|
6
14
|
raise "Another event name than the default one is only possible together with a selector"
|
@@ -8,7 +16,7 @@ module SvelteOnRails
|
|
8
16
|
|
9
17
|
args = {
|
10
18
|
eventDetail: event_detail,
|
11
|
-
component:
|
19
|
+
component: _comp,
|
12
20
|
event: event,
|
13
21
|
selector: selector
|
14
22
|
}
|
@@ -24,9 +32,33 @@ module SvelteOnRails
|
|
24
32
|
|
25
33
|
end
|
26
34
|
|
35
|
+
def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
|
36
|
+
|
37
|
+
if event != 'stream-action' && !selector
|
38
|
+
raise "Another event name than the default one is only possible together with a selector"
|
39
|
+
end
|
40
|
+
|
41
|
+
args = {
|
42
|
+
eventDetail: event_detail,
|
43
|
+
component: '/false/',
|
44
|
+
event: event,
|
45
|
+
selector: selector
|
46
|
+
}
|
47
|
+
|
48
|
+
args_enc = Base64.strict_encode64(args.to_json)
|
49
|
+
|
50
|
+
Turbo::StreamsChannel.send(
|
51
|
+
"broadcast_append_to",
|
52
|
+
require_channel(channel || configs['channel']),
|
53
|
+
target: configs['target_html_id'],
|
54
|
+
content: "<div style=\"display: none;\" data-controller=\"svelte-on-rails-turbo-stream\" data-args=\"#{args_enc}\"></div>"
|
55
|
+
)
|
56
|
+
|
57
|
+
end
|
58
|
+
|
27
59
|
private
|
28
60
|
|
29
|
-
def configs
|
61
|
+
def self.configs
|
30
62
|
@configs ||= begin
|
31
63
|
|
32
64
|
conf = SvelteOnRails::Configuration.instance
|
@@ -43,5 +75,13 @@ module SvelteOnRails
|
|
43
75
|
conf.configs[:turbo_stream]
|
44
76
|
end
|
45
77
|
end
|
78
|
+
|
79
|
+
def self.require_channel(channel)
|
80
|
+
unless channel.present?
|
81
|
+
raise 'Missing attribute or configuration: turbo_stream/channel'
|
82
|
+
end
|
83
|
+
channel
|
84
|
+
end
|
85
|
+
|
46
86
|
end
|
47
87
|
end
|
@@ -18,7 +18,7 @@ namespace :svelte_on_rails do
|
|
18
18
|
task :remove_hello_world do
|
19
19
|
|
20
20
|
hw_i = SvelteOnRails::Installer::HelloWorld
|
21
|
-
hw_i.remove_hello_world(['
|
21
|
+
hw_i.remove_hello_world(['all_features_test'])
|
22
22
|
|
23
23
|
end
|
24
24
|
|
@@ -27,7 +27,7 @@ namespace :svelte_on_rails do
|
|
27
27
|
|
28
28
|
puts '-' * 80
|
29
29
|
hw_i = SvelteOnRails::Installer::HelloWorld
|
30
|
-
hello_world_path = hw_i.install_hello_world(['
|
30
|
+
hello_world_path = hw_i.install_hello_world(['all_features_test'])
|
31
31
|
puts "You can now see the Hello World component on: #{hello_world_path}."
|
32
32
|
|
33
33
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class SvelteOnRailsHelloWorldController < ApplicationController
|
2
|
+
|
3
|
+
def index
|
4
|
+
end
|
5
|
+
|
6
|
+
def web_socket_action
|
7
|
+
|
8
|
+
# render plain: Article.first.svelte_attributes(:name) #(:name, :calc_method, children: [:name], children_limit: 2, parent: [:name]).to_json
|
9
|
+
#
|
10
|
+
# return
|
11
|
+
|
12
|
+
comp = 'ReceiveFromChannel'
|
13
|
+
|
14
|
+
case params['stream']
|
15
|
+
|
16
|
+
when 'action-cable-to-component'
|
17
|
+
SvelteOnRails::ActionCable.dispatch(
|
18
|
+
comp,
|
19
|
+
{ message: "#{SecureRandom.hex(2)} Sent by <span class='transfer'>ActionCable</span>: äöü🤣🌴🌍漢字", class: 'action-cable-to-component' },
|
20
|
+
event: 'stream-action'
|
21
|
+
)
|
22
|
+
|
23
|
+
when 'action-cable-to-element'
|
24
|
+
SvelteOnRails::ActionCable.dispatch(
|
25
|
+
comp,
|
26
|
+
{ message: "#{SecureRandom.hex(2)} <span class='transfer'>ActionCable to .my-custom-class / my-custom-event</span>", class: 'action-cable-to-element' },
|
27
|
+
selector: '.my-custom-class',
|
28
|
+
event: 'my-custom-event'
|
29
|
+
)
|
30
|
+
|
31
|
+
when 'action-cable-to-selector'
|
32
|
+
SvelteOnRails::ActionCable.dispatch_by_selector(
|
33
|
+
'.receive-by-selector',
|
34
|
+
{ message: "Sent by ActionCable/Selector: äöü🤣🌴🌍漢字", class: 'action-cable-to-selector' }
|
35
|
+
)
|
36
|
+
|
37
|
+
when 'turbo-stream-to-all-components'
|
38
|
+
SvelteOnRails::TurboStream.dispatch(
|
39
|
+
nil,
|
40
|
+
{ message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-all-components' },
|
41
|
+
)
|
42
|
+
|
43
|
+
when 'turbo-stream-to-element'
|
44
|
+
SvelteOnRails::TurboStream.dispatch(
|
45
|
+
nil,
|
46
|
+
{ message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-element' },
|
47
|
+
selector: '.my-custom-class',
|
48
|
+
event: 'my-custom-event'
|
49
|
+
)
|
50
|
+
|
51
|
+
when 'turbo-stream-to-selector'
|
52
|
+
SvelteOnRails::TurboStream.dispatch_by_selector(
|
53
|
+
'.receive-by-selector',
|
54
|
+
{ message: "Sent by TurboStream/Selector: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-selector' }
|
55
|
+
)
|
56
|
+
|
57
|
+
else
|
58
|
+
raise 'Unknown stream'
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
render plain: "dispatched: #{params['stream']}"
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { createConsumer } from "@rails/actioncable"
|
2
|
+
import {dispatchSvelteStreamEvent, actionCableDebugLog} from '@csedl/svelte-on-rails'
|
3
|
+
|
4
|
+
const consumer = createConsumer()
|
5
|
+
|
6
|
+
consumer.subscriptions.create("SvelteOnRailsChannel", {
|
7
|
+
connected() {
|
8
|
+
actionCableDebugLog("Connected to SvelteOnRailsChannel")
|
9
|
+
},
|
10
|
+
disconnected() {
|
11
|
+
actionCableDebugLog("Disconnected from SvelteOnRailsChannel")
|
12
|
+
},
|
13
|
+
received(data) {
|
14
|
+
actionCableDebugLog("Received:", data)
|
15
|
+
dispatchSvelteStreamEvent(data)
|
16
|
+
}
|
17
|
+
})
|
@@ -0,0 +1,70 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import axios from "axios";
|
3
|
+
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
4
|
+
|
5
|
+
let results = $state([])
|
6
|
+
|
7
|
+
function callChannelAction(action) {
|
8
|
+
results = []
|
9
|
+
axios.get(`/svelte_on_rails_hello_world/web_socket_action?stream=${action}`)
|
10
|
+
.then(function (response) {
|
11
|
+
console.log(`server action called, status: ${response.status}`)
|
12
|
+
})
|
13
|
+
}
|
14
|
+
|
15
|
+
const handleCableEvent = (ev) => {
|
16
|
+
results.push(
|
17
|
+
{
|
18
|
+
text: `Message received from Server: ${JSON.stringify(ev.detail.message)}`,
|
19
|
+
class: ev.detail.class
|
20
|
+
}
|
21
|
+
)
|
22
|
+
};
|
23
|
+
|
24
|
+
function handleElementEvent(ev) {
|
25
|
+
results.push(
|
26
|
+
{
|
27
|
+
text: `Element triggered from Server: ${JSON.stringify(ev.detail.message)}`,
|
28
|
+
class: ev.detail.class
|
29
|
+
}
|
30
|
+
)
|
31
|
+
};
|
32
|
+
|
33
|
+
function handleStreamSelectorEvent(ev) {
|
34
|
+
results.push(
|
35
|
+
{
|
36
|
+
text: `Element triggered from Server: ${JSON.stringify(ev.detail.message)}`,
|
37
|
+
class: ev.detail.class
|
38
|
+
}
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
</script>
|
43
|
+
|
44
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Dispatch Actions from Server</h1>
|
45
|
+
|
46
|
+
<p>Actions that are triggered from the server, by TurboStream or ActionCable</p>
|
47
|
+
|
48
|
+
<h3>ActionCable</h3>
|
49
|
+
<p>Easier Technic, more basic</p>
|
50
|
+
<button class="action-cable-to-component" onclick="{() => callChannelAction('action-cable-to-component')}">to component</button>
|
51
|
+
<button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-element')}">to element</button>
|
52
|
+
<button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-selector')}">to selector</button>
|
53
|
+
|
54
|
+
<h3>TurboStream</h3>
|
55
|
+
<p>Securer Channels, for example if sending confidential data from the server to privileged user groups</p>
|
56
|
+
<button class="turbo-stream-to-all-components" onclick="{() => callChannelAction('turbo-stream-to-all-components')}">to all components</button>
|
57
|
+
<button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-element')}">to element</button>
|
58
|
+
<button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-selector')}">to selector</button>
|
59
|
+
|
60
|
+
<span class="my-custom-class" onmy-custom-event="{handleElementEvent}"></span>
|
61
|
+
<span class="receive-by-selector" onstream-action="{handleStreamSelectorEvent}"></span>
|
62
|
+
|
63
|
+
<h3>Results</h3>
|
64
|
+
<div class="results-box">
|
65
|
+
<ul class="results">
|
66
|
+
{#each results as result}
|
67
|
+
<li class="{result.class}">{@html result.text}</li>
|
68
|
+
{/each}
|
69
|
+
</ul>
|
70
|
+
</div>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
|
6
6
|
<%= link_to 'SSR-Auto rendered (default)', '/svelte_on_rails_hello_world/ssr_auto_rendered' %>
|
7
7
|
|
|
8
|
-
<%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/
|
8
|
+
<%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/web_socket' %>
|
9
9
|
</div>
|
10
10
|
|
11
11
|
<% turbo_id = request.headers['X-Turbo-Request-ID'] %>
|
@@ -27,6 +27,10 @@ turbo_stream:
|
|
27
27
|
# html-id of any element that must exist for being able to receive actions (turbo streams can only work this way)
|
28
28
|
channel: 'public'
|
29
29
|
|
30
|
+
action_cable:
|
31
|
+
# if you want to use it
|
32
|
+
channel: "svelte_on_rails_channel"
|
33
|
+
|
30
34
|
development:
|
31
35
|
watch_changes: true
|
32
36
|
# Check on every request if any file within the svelte components folder have changed, for recompiling
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svelte-on-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Sedlmair
|
@@ -32,6 +32,7 @@ files:
|
|
32
32
|
- lib/generators/svelte_on_rails/install/install_generator.rb
|
33
33
|
- lib/svelte-on-rails.rb
|
34
34
|
- lib/svelte_on_rails/action_cable.rb
|
35
|
+
- lib/svelte_on_rails/active_record_extensions.rb
|
35
36
|
- lib/svelte_on_rails/configuration.rb
|
36
37
|
- lib/svelte_on_rails/installer/gem_utils.rb
|
37
38
|
- lib/svelte_on_rails/installer/haml.rb
|
@@ -51,30 +52,31 @@ files:
|
|
51
52
|
- lib/svelte_on_rails/turbo_stream.rb
|
52
53
|
- lib/svelte_on_rails/view_helpers.rb
|
53
54
|
- lib/tasks/svelte_on_rails_tasks.rake
|
55
|
+
- templates/all_features_test/app/channels/svelte_on_rails_channel.rb
|
56
|
+
- templates/all_features_test/app/controllers/svelte_on_rails_hello_world_controller.rb
|
57
|
+
- templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-england.png
|
58
|
+
- templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
|
59
|
+
- templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
|
60
|
+
- templates/all_features_test/app/frontend/initializers/actionCable.js
|
61
|
+
- templates/all_features_test/app/frontend/javascript/components/JavascriptImport.svelte
|
62
|
+
- templates/all_features_test/app/frontend/javascript/components/JpgImport.svelte
|
63
|
+
- templates/all_features_test/app/frontend/javascript/components/ParentWithChild.svelte
|
64
|
+
- templates/all_features_test/app/frontend/javascript/components/PngImport.svelte
|
65
|
+
- templates/all_features_test/app/frontend/javascript/components/ReceiveFromChannel.svelte
|
66
|
+
- templates/all_features_test/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
|
67
|
+
- templates/all_features_test/app/frontend/javascript/components/SvgRawImport.svelte
|
68
|
+
- templates/all_features_test/app/frontend/javascript/components/sub/NestedComponent.svelte
|
69
|
+
- templates/all_features_test/app/frontend/javascript/nestedJavascript.js
|
70
|
+
- templates/all_features_test/app/frontend/javascript/nestedJavascriptToggled.js
|
71
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/_nav.html.erb
|
72
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/_styles.html.erb
|
73
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
|
74
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/index.html.erb
|
75
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
|
76
|
+
- templates/all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb
|
54
77
|
- templates/config_base/app/frontend/ssr/ssr.js
|
55
78
|
- templates/config_base/config/svelte_on_rails.yml
|
56
79
|
- templates/config_base/vite-ssr.config.ts
|
57
|
-
- templates/rails_vite_hello_world/app/channels/svelte_on_rails_channel.rb
|
58
|
-
- templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
|
59
|
-
- templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-england.png
|
60
|
-
- templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
|
61
|
-
- templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
|
62
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/JavascriptImport.svelte
|
63
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/JpgImport.svelte
|
64
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/ParentWithChild.svelte
|
65
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
|
66
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
|
67
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
|
68
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
|
69
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
|
70
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascript.js
|
71
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascriptToggled.js
|
72
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_nav.html.erb
|
73
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_styles.html.erb
|
74
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
|
75
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
|
76
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
|
77
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
|
78
80
|
homepage: https://gitlab.com/sedl/svelte-on-rails
|
79
81
|
licenses:
|
80
82
|
- MIT
|
data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
class SvelteOnRailsHelloWorldController < ApplicationController
|
2
|
-
|
3
|
-
def index
|
4
|
-
end
|
5
|
-
|
6
|
-
def turbo_stream_action
|
7
|
-
|
8
|
-
if params['increase']
|
9
|
-
SvelteOnRails::TurboStream.new.dispatch_event(
|
10
|
-
event: 'click',
|
11
|
-
selector: '.counter-button',
|
12
|
-
component: '/javascript/components/ReceiveFromChannel'
|
13
|
-
)
|
14
|
-
render plain: 'increase-action streamed'
|
15
|
-
elsif params['action_cable']
|
16
|
-
|
17
|
-
ActionCable.server.broadcast("svelte_on_rails_channel",
|
18
|
-
{
|
19
|
-
eventDetail: { message: "Sent by ActionCable: äöü🤣🌴🌍漢字" },
|
20
|
-
component: '/javascript/components/ReceiveFromChannel',
|
21
|
-
}
|
22
|
-
)
|
23
|
-
#SvelteOnRails::ActionCable.new.dispatch_event(channel: "svelte_on_rails_channel", event_detail: { message: "Hello from SvelteChannel!" })
|
24
|
-
|
25
|
-
render plain: 'ActionCable streaming'
|
26
|
-
else
|
27
|
-
SvelteOnRails::TurboStream.new.dispatch_event(
|
28
|
-
event_detail: { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", user: "Müller" }
|
29
|
-
)
|
30
|
-
render plain: 'component-action streamed'
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
data/templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
<script lang="ts">
|
2
|
-
import axios from "axios";
|
3
|
-
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
4
|
-
import { onMount } from "svelte";
|
5
|
-
|
6
|
-
let counter = $state(0)
|
7
|
-
let results = $state([])
|
8
|
-
let incrBtn
|
9
|
-
|
10
|
-
onMount(() => {
|
11
|
-
incrBtn.addEventListener('click', increaseCounter)
|
12
|
-
});
|
13
|
-
|
14
|
-
function callChannelAction(params) {
|
15
|
-
results = []
|
16
|
-
axios.get(`/svelte_on_rails_hello_world/turbo_stream_action?${params}`)
|
17
|
-
.then(function (response) {
|
18
|
-
results.push(
|
19
|
-
{
|
20
|
-
text: `server action called, status: ${response.status}`,
|
21
|
-
class: 'called-for-action'
|
22
|
-
})
|
23
|
-
})
|
24
|
-
}
|
25
|
-
|
26
|
-
function increaseCounter() {
|
27
|
-
counter += 1
|
28
|
-
}
|
29
|
-
|
30
|
-
const handleCableEvent = (ev) => {
|
31
|
-
results.push(
|
32
|
-
{
|
33
|
-
text: `Message received from Server: ${JSON.stringify(ev.detail.message)}`,
|
34
|
-
class: 'stream-action-received'
|
35
|
-
}
|
36
|
-
)
|
37
|
-
};
|
38
|
-
|
39
|
-
</script>
|
40
|
-
|
41
|
-
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
42
|
-
|
43
|
-
<p>Actions that are triggered by SvelteOnRails::TurboStream channel from the server</p>
|
44
|
-
|
45
|
-
<button onstream-action="{() => callChannelAction('increase=true')}">Call increase action over Channel</button>
|
46
|
-
|
47
|
-
<button bind:this={incrBtn} class="counter-button" onclick="{increaseCounter}">increase {counter}</button>
|
48
|
-
|
49
|
-
<button class="call-channel-action" onclick="{callChannelAction}">Action</button>
|
50
|
-
|
51
|
-
<button onclick="{() => callChannelAction('action_cable=true')}">Call ActionCable Action</button>
|
52
|
-
|
53
|
-
<ul class="results">
|
54
|
-
{#each results as result}
|
55
|
-
<li class="{result.class}">{result.text}</li>
|
56
|
-
{/each}
|
57
|
-
</ul>
|
/data/templates/{rails_vite_hello_world → all_features_test}/app/channels/svelte_on_rails_channel.rb
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|