svelte-on-rails 5.2.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 +60 -11
- data/lib/generators/svelte_on_rails/install/install_generator.rb +3 -3
- data/lib/svelte-on-rails.rb +2 -0
- data/lib/svelte_on_rails/action_cable.rb +43 -8
- 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 +42 -2
- data/lib/tasks/svelte_on_rails_tasks.rake +2 -2
- data/templates/{rails_vite_hello_world → all_features_test}/app/controllers/svelte_on_rails_hello_world_controller.rb +27 -6
- data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/ReceiveFromChannel.svelte +25 -8
- data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_nav.html.erb +1 -1
- metadata +24 -23
- /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/initializers/actionCable.js +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`
|
@@ -375,7 +418,7 @@ turbo_stream:
|
|
375
418
|
over streams or have different channels for each user privileges
|
376
419
|
- Has [Compatibility issue with UJS](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#compatibility-with-rails-ujs)
|
377
420
|
|
378
|
-
## SvelteOnRails::ActionCable
|
421
|
+
## SvelteOnRails::ActionCable
|
379
422
|
|
380
423
|
ActionCable is the more basic library behind `TurboStream` and it is a second Option to call Javascript Actions from the server.
|
381
424
|
|
@@ -453,7 +496,7 @@ The «wrapping Element» is the Element from the view helper `svelte_component`
|
|
453
496
|
Now you can dispatch events on the component by:
|
454
497
|
|
455
498
|
```ruby
|
456
|
-
SvelteOnRails::ActionCable.
|
499
|
+
SvelteOnRails::ActionCable.dispatch(
|
457
500
|
'folder/MyComponent',
|
458
501
|
{ message: "greetings from Server: äöü🤣🌴🌍漢字" }
|
459
502
|
)
|
@@ -461,9 +504,12 @@ SvelteOnRails::ActionCable.send(
|
|
461
504
|
|
462
505
|
And you will find the object, with the message key on the browser logs.
|
463
506
|
|
464
|
-
Without any arguments, just by `SvelteOnRails::ActionCable.
|
507
|
+
Without any arguments, just by `SvelteOnRails::ActionCable.dispatch` it would fire the `stream-action` event on all components.
|
465
508
|
|
466
|
-
|
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
|
467
513
|
|
468
514
|
Turbo Stream makes more sense when you think of sending confidential data to the components
|
469
515
|
or you want to separate to channels based on user groups, for example.
|
@@ -501,7 +547,7 @@ When this works you are good to go.
|
|
501
547
|
And call this by:
|
502
548
|
|
503
549
|
```ruby
|
504
|
-
SvelteOnRails::TurboStream.
|
550
|
+
SvelteOnRails::TurboStream.dispatch
|
505
551
|
```
|
506
552
|
**What it does**
|
507
553
|
|
@@ -527,7 +573,7 @@ within the svelte component.
|
|
527
573
|
Then, call it by:
|
528
574
|
|
529
575
|
```ruby
|
530
|
-
SvelteOnRails::TurboStream.
|
576
|
+
SvelteOnRails::TurboStream.dispatch(
|
531
577
|
channel: 'my-custom-stream',
|
532
578
|
component: '/javascript/components/TurboStreamsChannel',
|
533
579
|
selector: '.counter-button',
|
@@ -536,6 +582,9 @@ Then, call it by:
|
|
536
582
|
)
|
537
583
|
```
|
538
584
|
|
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.
|
587
|
+
|
539
588
|
## More rake tasks
|
540
589
|
|
541
590
|
This tasks are more for testing/playground purposes
|
@@ -129,15 +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
137
|
js_i = SvelteOnRails::Installer::Javascript
|
138
138
|
init_stat = '../initializers/actionCable.js'
|
139
139
|
js_i.append_import_statement(application_js_path, init_stat, "import '#{init_stat}';")
|
140
|
-
@hello_world_path = hw_i.install_hello_world(['
|
140
|
+
@hello_world_path = hw_i.install_hello_world(['all_features_test'], app_root: nil, force: true, silent: true)
|
141
141
|
end
|
142
142
|
|
143
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,11 +1,16 @@
|
|
1
1
|
module SvelteOnRails
|
2
2
|
class ActionCable
|
3
|
-
def self.
|
3
|
+
def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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)
|
9
14
|
|
10
15
|
if event != 'stream-action' && !selector
|
11
16
|
raise "Another event name than the default one is only possible together with a selector"
|
@@ -13,7 +18,7 @@ module SvelteOnRails
|
|
13
18
|
|
14
19
|
args = {
|
15
20
|
eventDetail: event_detail,
|
16
|
-
component:
|
21
|
+
component: _comp,
|
17
22
|
event: event,
|
18
23
|
selector: selector
|
19
24
|
}
|
@@ -23,13 +28,43 @@ module SvelteOnRails
|
|
23
28
|
args
|
24
29
|
)
|
25
30
|
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
|
34
|
+
|
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
|
+
)
|
26
52
|
|
27
53
|
end
|
28
54
|
|
29
55
|
private
|
30
56
|
|
31
|
-
def self.
|
32
|
-
SvelteOnRails::Configuration.instance.configs[:action_cable]
|
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
|
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
|
33
68
|
end
|
34
69
|
|
35
70
|
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 self.
|
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,6 +32,30 @@ 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
61
|
def self.configs
|
@@ -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
|
@@ -3,43 +3,64 @@ class SvelteOnRailsHelloWorldController < ApplicationController
|
|
3
3
|
def index
|
4
4
|
end
|
5
5
|
|
6
|
-
def
|
6
|
+
def web_socket_action
|
7
7
|
|
8
|
-
|
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'
|
9
13
|
|
10
14
|
case params['stream']
|
11
15
|
|
12
16
|
when 'action-cable-to-component'
|
13
|
-
SvelteOnRails::ActionCable.
|
17
|
+
SvelteOnRails::ActionCable.dispatch(
|
14
18
|
comp,
|
15
19
|
{ message: "#{SecureRandom.hex(2)} Sent by <span class='transfer'>ActionCable</span>: äöü🤣🌴🌍漢字", class: 'action-cable-to-component' },
|
16
20
|
event: 'stream-action'
|
17
21
|
)
|
18
22
|
|
19
23
|
when 'action-cable-to-element'
|
20
|
-
SvelteOnRails::ActionCable.
|
24
|
+
SvelteOnRails::ActionCable.dispatch(
|
21
25
|
comp,
|
22
26
|
{ message: "#{SecureRandom.hex(2)} <span class='transfer'>ActionCable to .my-custom-class / my-custom-event</span>", class: 'action-cable-to-element' },
|
23
27
|
selector: '.my-custom-class',
|
24
28
|
event: 'my-custom-event'
|
25
29
|
)
|
26
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
|
+
|
27
37
|
when 'turbo-stream-to-all-components'
|
28
|
-
SvelteOnRails::TurboStream.
|
38
|
+
SvelteOnRails::TurboStream.dispatch(
|
29
39
|
nil,
|
30
40
|
{ message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-all-components' },
|
31
41
|
)
|
32
42
|
|
33
43
|
when 'turbo-stream-to-element'
|
34
|
-
SvelteOnRails::TurboStream.
|
44
|
+
SvelteOnRails::TurboStream.dispatch(
|
35
45
|
nil,
|
36
46
|
{ message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-element' },
|
37
47
|
selector: '.my-custom-class',
|
38
48
|
event: 'my-custom-event'
|
39
49
|
)
|
40
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
|
+
|
41
60
|
end
|
42
61
|
|
62
|
+
render plain: "dispatched: #{params['stream']}"
|
63
|
+
|
43
64
|
end
|
44
65
|
|
45
66
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
function callChannelAction(action) {
|
8
8
|
results = []
|
9
|
-
axios.get(`/svelte_on_rails_hello_world/
|
9
|
+
axios.get(`/svelte_on_rails_hello_world/web_socket_action?stream=${action}`)
|
10
10
|
.then(function (response) {
|
11
11
|
console.log(`server action called, status: ${response.status}`)
|
12
12
|
})
|
@@ -30,21 +30,38 @@
|
|
30
30
|
)
|
31
31
|
};
|
32
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
|
+
|
33
42
|
</script>
|
34
43
|
|
35
|
-
<h1 use:addComponentStreamListener={handleCableEvent}>
|
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>
|
36
47
|
|
37
|
-
<
|
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>
|
38
53
|
|
39
|
-
<
|
40
|
-
<
|
41
|
-
<button class="turbo-stream-to-all-components" onclick="{() => callChannelAction('turbo-stream-to-all-components')}">
|
42
|
-
<button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-element')}">
|
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>
|
43
59
|
|
44
60
|
<span class="my-custom-class" onmy-custom-event="{handleElementEvent}"></span>
|
61
|
+
<span class="receive-by-selector" onstream-action="{handleStreamSelectorEvent}"></span>
|
45
62
|
|
63
|
+
<h3>Results</h3>
|
46
64
|
<div class="results-box">
|
47
|
-
<p>Result:</p>
|
48
65
|
<ul class="results">
|
49
66
|
{#each results as result}
|
50
67
|
<li class="{result.class}">{@html result.text}</li>
|
@@ -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'] %>
|
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,31 +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/initializers/actionCable.js
|
63
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/JavascriptImport.svelte
|
64
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/JpgImport.svelte
|
65
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/ParentWithChild.svelte
|
66
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
|
67
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
|
68
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
|
69
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
|
70
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
|
71
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascript.js
|
72
|
-
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascriptToggled.js
|
73
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_nav.html.erb
|
74
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_styles.html.erb
|
75
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
|
76
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
|
77
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
|
78
|
-
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
|
79
80
|
homepage: https://gitlab.com/sedl/svelte-on-rails
|
80
81
|
licenses:
|
81
82
|
- MIT
|
/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
|
File without changes
|