stimulus_reflex 3.3.0.pre0 → 3.3.0.pre5
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.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +107 -4
- data/Gemfile.lock +72 -70
- data/README.md +26 -20
- data/lib/generators/templates/app/javascript/controllers/%file_name%_controller.js.tt +53 -21
- data/lib/generators/templates/app/javascript/controllers/application_controller.js.tt +11 -8
- data/lib/stimulus_reflex.rb +67 -5
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +56 -0
- data/lib/stimulus_reflex/{morph_mode/nothing_morph_mode.rb → broadcasters/nothing_broadcaster.rb} +8 -6
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +36 -0
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +51 -0
- data/lib/stimulus_reflex/channel.rb +11 -8
- data/lib/stimulus_reflex/reflex.rb +11 -26
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/tasks/stimulus_reflex/install.rake +13 -1
- metadata +25 -18
- data/lib/stimulus_reflex/broadcaster.rb +0 -53
- data/lib/stimulus_reflex/morph_mode.rb +0 -19
- data/lib/stimulus_reflex/morph_mode/page_morph_mode.rb +0 -15
- data/lib/stimulus_reflex/morph_mode/selector_morph_mode.rb +0 -15
- data/stimulus_reflex.gemspec +0 -39
- data/tags +0 -42
- data/test/tmp/app/reflexes/application_reflex.rb +0 -12
- data/test/tmp/app/reflexes/demo_reflex.rb +0 -25
@@ -43,12 +43,13 @@ class StimulusReflex::Reflex
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
attr_reader :channel, :url, :element, :selectors, :method_name, :
|
46
|
+
attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :permanent_attribute_name
|
47
47
|
|
48
|
-
delegate :connection, to: :channel
|
48
|
+
delegate :connection, :stream_name, to: :channel
|
49
49
|
delegate :session, to: :request
|
50
|
+
delegate :broadcast, :broadcast_message, to: :broadcaster
|
50
51
|
|
51
|
-
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil,
|
52
|
+
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, permanent_attribute_name: nil, params: {})
|
52
53
|
@channel = channel
|
53
54
|
@url = url
|
54
55
|
@element = element
|
@@ -56,7 +57,7 @@ class StimulusReflex::Reflex
|
|
56
57
|
@method_name = method_name
|
57
58
|
@params = params
|
58
59
|
@permanent_attribute_name = permanent_attribute_name
|
59
|
-
@
|
60
|
+
@broadcaster = StimulusReflex::PageBroadcaster.new(self)
|
60
61
|
self.params
|
61
62
|
end
|
62
63
|
|
@@ -89,33 +90,17 @@ class StimulusReflex::Reflex
|
|
89
90
|
def morph(selectors, html = "")
|
90
91
|
case selectors
|
91
92
|
when :page
|
92
|
-
raise StandardError.new("Cannot call :page morph after :#{
|
93
|
+
raise StandardError.new("Cannot call :page morph after :#{broadcaster.to_sym} morph") unless broadcaster.page?
|
93
94
|
when :nothing
|
94
|
-
raise StandardError.new("Cannot call :nothing morph after :selector morph") if
|
95
|
-
@
|
95
|
+
raise StandardError.new("Cannot call :nothing morph after :selector morph") if broadcaster.selector?
|
96
|
+
@broadcaster = StimulusReflex::NothingBroadcaster.new(self) unless broadcaster.nothing?
|
96
97
|
else
|
97
|
-
raise StandardError.new("Cannot call :selector morph after :nothing morph") if
|
98
|
-
@
|
99
|
-
|
100
|
-
selectors.each do |selector, html|
|
101
|
-
enqueue_selector_broadcast selector, html
|
102
|
-
end
|
103
|
-
else
|
104
|
-
enqueue_selector_broadcast selectors, html
|
105
|
-
end
|
106
|
-
cable_ready.broadcast
|
98
|
+
raise StandardError.new("Cannot call :selector morph after :nothing morph") if broadcaster.nothing?
|
99
|
+
@broadcaster = StimulusReflex::SelectorBroadcaster.new(self) unless broadcaster.selector?
|
100
|
+
broadcaster.morphs << [selectors, html]
|
107
101
|
end
|
108
102
|
end
|
109
103
|
|
110
|
-
def enqueue_selector_broadcast(selector, html)
|
111
|
-
cable_ready[channel.stream_name].morph(
|
112
|
-
selector: selector,
|
113
|
-
html: html,
|
114
|
-
children_only: true,
|
115
|
-
permanent_attribute_name: permanent_attribute_name
|
116
|
-
)
|
117
|
-
end
|
118
|
-
|
119
104
|
def controller
|
120
105
|
@controller ||= begin
|
121
106
|
request.controller_class.new.tap do |c|
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "fileutils"
|
4
|
+
require "stimulus_reflex/version"
|
4
5
|
|
5
6
|
namespace :stimulus_reflex do
|
6
7
|
desc "Install StimulusReflex in this application"
|
7
8
|
task install: :environment do
|
8
9
|
system "bundle exec rails webpacker:install:stimulus"
|
9
|
-
|
10
|
+
gem_version = StimulusReflex::VERSION.gsub(".pre", "-pre")
|
11
|
+
system "yarn add stimulus_reflex@#{gem_version}"
|
10
12
|
|
11
13
|
FileUtils.mkdir_p Rails.root.join("app/javascript/controllers"), verbose: true
|
12
14
|
FileUtils.mkdir_p Rails.root.join("app/reflexes"), verbose: true
|
@@ -50,6 +52,16 @@ namespace :stimulus_reflex do
|
|
50
52
|
File.open(filepath, "w") { |f| f.write lines.join }
|
51
53
|
end
|
52
54
|
|
55
|
+
filepath = Rails.root.join("config/cable.yml")
|
56
|
+
lines = File.open(filepath, "r") { |f| f.readlines }
|
57
|
+
if lines[1].include?("adapter: async")
|
58
|
+
lines.delete_at 1
|
59
|
+
lines.insert 1, " adapter: redis\n"
|
60
|
+
lines.insert 2, " url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n"
|
61
|
+
lines.insert 3, " channel_prefix: " + Rails.application.class.module_parent.to_s.underscore + "_development\n"
|
62
|
+
File.open(filepath, "w") { |f| f.write lines.join }
|
63
|
+
end
|
64
|
+
|
53
65
|
system "bundle exec rails generate stimulus_reflex example"
|
54
66
|
system "rails dev:cache" unless Rails.root.join("tmp", "caching-dev.txt").exist?
|
55
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stimulus_reflex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.3.0.
|
4
|
+
version: 3.3.0.pre5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Hopkins
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 4.1.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redis
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: bundler
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,7 +150,7 @@ dependencies:
|
|
136
150
|
- - ">="
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
139
|
-
description:
|
153
|
+
description:
|
140
154
|
email:
|
141
155
|
- natehop@gmail.com
|
142
156
|
executables: []
|
@@ -162,22 +176,17 @@ files:
|
|
162
176
|
- lib/generators/templates/app/reflexes/%file_name%_reflex.rb.tt
|
163
177
|
- lib/generators/templates/app/reflexes/application_reflex.rb.tt
|
164
178
|
- lib/stimulus_reflex.rb
|
165
|
-
- lib/stimulus_reflex/broadcaster.rb
|
179
|
+
- lib/stimulus_reflex/broadcasters/broadcaster.rb
|
180
|
+
- lib/stimulus_reflex/broadcasters/nothing_broadcaster.rb
|
181
|
+
- lib/stimulus_reflex/broadcasters/page_broadcaster.rb
|
182
|
+
- lib/stimulus_reflex/broadcasters/selector_broadcaster.rb
|
166
183
|
- lib/stimulus_reflex/channel.rb
|
167
184
|
- lib/stimulus_reflex/element.rb
|
168
|
-
- lib/stimulus_reflex/morph_mode.rb
|
169
|
-
- lib/stimulus_reflex/morph_mode/nothing_morph_mode.rb
|
170
|
-
- lib/stimulus_reflex/morph_mode/page_morph_mode.rb
|
171
|
-
- lib/stimulus_reflex/morph_mode/selector_morph_mode.rb
|
172
185
|
- lib/stimulus_reflex/reflex.rb
|
173
186
|
- lib/stimulus_reflex/version.rb
|
174
187
|
- lib/tasks/stimulus_reflex/install.rake
|
175
|
-
- stimulus_reflex.gemspec
|
176
|
-
- tags
|
177
188
|
- test/generators/stimulus_reflex_generator_test.rb
|
178
189
|
- test/test_helper.rb
|
179
|
-
- test/tmp/app/reflexes/application_reflex.rb
|
180
|
-
- test/tmp/app/reflexes/demo_reflex.rb
|
181
190
|
homepage: https://github.com/hopsoft/stimulus_reflex
|
182
191
|
licenses:
|
183
192
|
- MIT
|
@@ -206,12 +215,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
215
|
- !ruby/object:Gem::Version
|
207
216
|
version: 1.3.1
|
208
217
|
requirements: []
|
209
|
-
rubygems_version: 3.0.
|
210
|
-
signing_key:
|
218
|
+
rubygems_version: 3.0.6
|
219
|
+
signing_key:
|
211
220
|
specification_version: 4
|
212
221
|
summary: Build reactive applications with the Rails tooling you already know and love.
|
213
222
|
test_files:
|
214
|
-
- test/test_helper.rb
|
215
223
|
- test/generators/stimulus_reflex_generator_test.rb
|
216
|
-
- test/
|
217
|
-
- test/tmp/app/reflexes/application_reflex.rb
|
224
|
+
- test/test_helper.rb
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module StimulusReflex
|
2
|
-
module Broadcaster
|
3
|
-
include CableReady::Broadcaster
|
4
|
-
|
5
|
-
def render_page_and_broadcast_morph(reflex, selectors, data = {})
|
6
|
-
html = render_page(reflex)
|
7
|
-
broadcast_morphs selectors, data, html if html.present?
|
8
|
-
end
|
9
|
-
|
10
|
-
def render_page(reflex)
|
11
|
-
reflex.controller.process reflex.url_params[:action]
|
12
|
-
reflex.controller.response.body
|
13
|
-
end
|
14
|
-
|
15
|
-
def broadcast_morphs(selectors, data, html)
|
16
|
-
document = Nokogiri::HTML(html)
|
17
|
-
selectors = selectors.select { |s| document.css(s).present? }
|
18
|
-
selectors.each do |selector|
|
19
|
-
cable_ready[stream_name].morph(
|
20
|
-
selector: selector,
|
21
|
-
html: document.css(selector).inner_html,
|
22
|
-
children_only: true,
|
23
|
-
permanent_attribute_name: data["permanent_attribute_name"],
|
24
|
-
stimulus_reflex: data.merge({
|
25
|
-
last: selector == selectors.last,
|
26
|
-
morph_mode: "page"
|
27
|
-
})
|
28
|
-
)
|
29
|
-
end
|
30
|
-
cable_ready.broadcast
|
31
|
-
end
|
32
|
-
|
33
|
-
def broadcast_message(subject:, body: nil, data: {})
|
34
|
-
message = {
|
35
|
-
subject: subject,
|
36
|
-
body: body
|
37
|
-
}
|
38
|
-
|
39
|
-
logger.error "\e[31m#{body}\e[0m" if subject == "error"
|
40
|
-
|
41
|
-
data[:morph_mode] = "page"
|
42
|
-
data[:server_message] = message
|
43
|
-
data[:morph_mode] = "selector" if subject == "selector"
|
44
|
-
data[:morph_mode] = "nothing" if subject == "nothing"
|
45
|
-
|
46
|
-
cable_ready[stream_name].dispatch_event(
|
47
|
-
name: "stimulus-reflex:server-message",
|
48
|
-
detail: {stimulus_reflex: data}
|
49
|
-
)
|
50
|
-
cable_ready.broadcast
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
data/stimulus_reflex.gemspec
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require File.expand_path("../lib/stimulus_reflex/version", __FILE__)
|
2
|
-
|
3
|
-
Gem::Specification.new do |gem|
|
4
|
-
gem.name = "stimulus_reflex"
|
5
|
-
gem.license = "MIT"
|
6
|
-
gem.version = StimulusReflex::VERSION
|
7
|
-
gem.authors = ["Nathan Hopkins"]
|
8
|
-
gem.email = ["natehop@gmail.com"]
|
9
|
-
gem.homepage = "https://github.com/hopsoft/stimulus_reflex"
|
10
|
-
gem.summary = "Build reactive applications with the Rails tooling you already know and love."
|
11
|
-
gem.post_install_message = <<~MESSAGE
|
12
|
-
Friendly reminder: When updating the stimulus_reflex gem,
|
13
|
-
don't forget to update your npm package as well.
|
14
|
-
|
15
|
-
See https://www.npmjs.com/package/stimulus_reflex
|
16
|
-
MESSAGE
|
17
|
-
|
18
|
-
gem.metadata = {
|
19
|
-
"bug_tracker_uri" => "https://github.com/hopsoft/stimulus_reflex/issues",
|
20
|
-
"changelog_uri" => "https://github.com/hopsoft/stimulus_reflex/CHANGELOG.md",
|
21
|
-
"documentation_uri" => "https://docs.stimulusreflex.com",
|
22
|
-
"homepage_uri" => gem.homepage,
|
23
|
-
"source_code_uri" => gem.homepage
|
24
|
-
}
|
25
|
-
|
26
|
-
gem.files = Dir["lib/**/*", "bin/*", "[A-Z]*"]
|
27
|
-
gem.test_files = Dir["test/**/*.rb"]
|
28
|
-
|
29
|
-
gem.add_dependency "rack"
|
30
|
-
gem.add_dependency "nokogiri"
|
31
|
-
gem.add_dependency "rails", ">= 5.2"
|
32
|
-
gem.add_dependency "cable_ready", ">= 4.1.2"
|
33
|
-
|
34
|
-
gem.add_development_dependency "bundler", "~> 2.0"
|
35
|
-
gem.add_development_dependency "pry-nav"
|
36
|
-
gem.add_development_dependency "pry"
|
37
|
-
gem.add_development_dependency "rake"
|
38
|
-
gem.add_development_dependency "standardrb"
|
39
|
-
end
|
data/tags
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
2
|
-
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
3
|
-
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
|
4
|
-
!_TAG_PROGRAM_NAME Exuberant Ctags //
|
5
|
-
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
|
6
|
-
!_TAG_PROGRAM_VERSION 5.8 //
|
7
|
-
ApplicationReflex lib/generators/templates/application_reflex.rb /^class ApplicationReflex < StimulusReflex::Reflex$/;" c
|
8
|
-
ApplicationReflex test/tmp/app/reflexes/application_reflex.rb /^class ApplicationReflex < StimulusReflex::Reflex$/;" c
|
9
|
-
DemoReflex test/tmp/app/reflexes/demo_reflex.rb /^class DemoReflex < ApplicationReflex$/;" c
|
10
|
-
Engine lib/stimulus_reflex.rb /^ class Engine < Rails::Engine$/;" c class:StimulusReflex
|
11
|
-
StimulusReflex lib/stimulus_reflex.rb /^module StimulusReflex$/;" m
|
12
|
-
StimulusReflex lib/stimulus_reflex/channel.rb /^class StimulusReflex::Channel < ActionCable::Channel::Base$/;" c
|
13
|
-
StimulusReflex lib/stimulus_reflex/element.rb /^class StimulusReflex::Element$/;" c
|
14
|
-
StimulusReflex lib/stimulus_reflex/reflex.rb /^class StimulusReflex::Reflex$/;" c
|
15
|
-
StimulusReflex lib/stimulus_reflex/version.rb /^module StimulusReflex$/;" m
|
16
|
-
StimulusReflexGenerator lib/generators/stimulus_reflex_generator.rb /^class StimulusReflexGenerator < Rails::Generators::NamedBase$/;" c
|
17
|
-
StimulusReflexGeneratorTest test/generators/stimulus_reflex_generator_test.rb /^class StimulusReflexGeneratorTest < Rails::Generators::TestCase$/;" c
|
18
|
-
broadcast_error lib/stimulus_reflex/channel.rb /^ def broadcast_error(message, data = {})$/;" f class:StimulusReflex
|
19
|
-
broadcast_morphs lib/stimulus_reflex/channel.rb /^ def broadcast_morphs(selectors, data, html)$/;" f class:StimulusReflex
|
20
|
-
build_request lib/stimulus_reflex/channel.rb /^ def build_request$/;" f class:StimulusReflex
|
21
|
-
commit_session lib/stimulus_reflex/channel.rb /^ def commit_session(request, response)$/;" f class:StimulusReflex
|
22
|
-
copy_controller_files lib/generators/stimulus_reflex_generator.rb /^ def copy_controller_files$/;" f class:StimulusReflexGenerator
|
23
|
-
copy_reflex_files lib/generators/stimulus_reflex_generator.rb /^ def copy_reflex_files$/;" f class:StimulusReflexGenerator
|
24
|
-
dataset lib/stimulus_reflex/element.rb /^ def dataset$/;" f class:StimulusReflex
|
25
|
-
delegate_call_to_reflex lib/stimulus_reflex/channel.rb /^ def delegate_call_to_reflex(reflex, method_name, arguments = [])$/;" f class:StimulusReflex
|
26
|
-
exception_message_with_backtrace lib/stimulus_reflex/channel.rb /^ def exception_message_with_backtrace(exception)$/;" f class:StimulusReflex
|
27
|
-
export.reflexAttribute javascript/schema.js /^ reflexAttribute: 'data-reflex',$/;" p
|
28
|
-
export.reflexPermanentAttribute javascript/schema.js /^ reflexPermanentAttribute: 'data-reflex-permanent',$/;" p
|
29
|
-
export.reflexRootAttribute javascript/schema.js /^ reflexRootAttribute: 'data-reflex-root'$/;" p
|
30
|
-
findConsumer javascript/consumer.js /^function findConsumer (object, depth = 0) {$/;" f
|
31
|
-
initialize lib/stimulus_reflex/element.rb /^ def initialize(attrs = {})$/;" f class:StimulusReflex
|
32
|
-
initialize lib/stimulus_reflex/reflex.rb /^ def initialize(channel, url: nil, element: nil, selectors: [])$/;" f class:StimulusReflex
|
33
|
-
initialize_controllers lib/generators/stimulus_reflex_generator.rb /^ def initialize_controllers$/;" f class:StimulusReflexGenerator
|
34
|
-
initialize_reflexes lib/generators/stimulus_reflex_generator.rb /^ def initialize_reflexes$/;" f class:StimulusReflexGenerator
|
35
|
-
is_reflex? lib/stimulus_reflex/channel.rb /^ def is_reflex?(reflex_class)$/;" f class:StimulusReflex
|
36
|
-
receive lib/stimulus_reflex/channel.rb /^ def receive(data)$/;" f class:StimulusReflex
|
37
|
-
render_page lib/stimulus_reflex/channel.rb /^ def render_page(reflex)$/;" f class:StimulusReflex
|
38
|
-
render_page_and_broadcast_morph lib/stimulus_reflex/channel.rb /^ def render_page_and_broadcast_morph(reflex, selectors, data = {})$/;" f class:StimulusReflex
|
39
|
-
request lib/stimulus_reflex/reflex.rb /^ def request$/;" f class:StimulusReflex
|
40
|
-
stream_name lib/stimulus_reflex/channel.rb /^ def stream_name$/;" f class:StimulusReflex
|
41
|
-
subscribed lib/stimulus_reflex/channel.rb /^ def subscribed$/;" f class:StimulusReflex
|
42
|
-
url_params lib/stimulus_reflex/reflex.rb /^ def url_params$/;" f class:StimulusReflex
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ApplicationReflex < StimulusReflex::Reflex
|
4
|
-
# Put application wide Reflex behavior in this file.
|
5
|
-
#
|
6
|
-
# Example:
|
7
|
-
#
|
8
|
-
# # If your ActionCable connection is: `identified_by :current_user`
|
9
|
-
# delegate :current_user, to: :connection
|
10
|
-
#
|
11
|
-
# Learn more at: https://docs.stimulusreflex.com
|
12
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class DemoReflex < ApplicationReflex
|
4
|
-
# Add Reflex methods in this file.
|
5
|
-
#
|
6
|
-
# All Reflex instances expose the following properties:
|
7
|
-
#
|
8
|
-
# - connection - the ActionCable connection
|
9
|
-
# - channel - the ActionCable channel
|
10
|
-
# - request - an ActionDispatch::Request proxy for the socket connection
|
11
|
-
# - session - the ActionDispatch::Session store for the current visitor
|
12
|
-
# - url - the URL of the page that triggered the reflex
|
13
|
-
# - element - a Hash like object that represents the HTML element that triggered the reflex
|
14
|
-
# - params - parameters from the element's closest form (if any)
|
15
|
-
#
|
16
|
-
# Example:
|
17
|
-
#
|
18
|
-
# def example(argument=true)
|
19
|
-
# # Your logic here...
|
20
|
-
# # Any declared instance variables will be made available to the Rails controller and view.
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# Learn more at: https://docs.stimulusreflex.com
|
24
|
-
|
25
|
-
end
|