nitro_rails 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -0
  3. data/app/assets/audio/mousetap.mp3 +0 -0
  4. data/app/assets/config/nitro_rails_manifest.js +2 -0
  5. data/app/assets/javascript/nitro_rails/application.js +4 -0
  6. data/app/assets/javascript/nitro_rails/controllers/application.js +9 -0
  7. data/app/assets/javascript/nitro_rails/controllers/audio_controller.js +16 -0
  8. data/app/assets/javascript/nitro_rails/controllers/clock_controller.js +21 -0
  9. data/app/assets/javascript/nitro_rails/controllers/editable_text_controller.js +61 -0
  10. data/app/assets/javascript/nitro_rails/controllers/index.js +21 -0
  11. data/app/assets/javascript/nitro_rails/controllers/nitro_collection_controller.js +127 -0
  12. data/app/assets/javascript/nitro_rails/controllers/nitro_controller.js +35 -0
  13. data/app/assets/javascript/nitro_rails/controllers/nitro_form_controller.js +81 -0
  14. data/app/assets/javascript/nitro_rails/controllers/panel_controller.js +70 -0
  15. data/app/assets/javascript/nitro_rails/controllers/panel_manager_controller.js +89 -0
  16. data/app/assets/javascript/nitro_rails/controllers/tabs_controller.js +81 -0
  17. data/app/assets/javascript/nitro_rails/controllers/time_zone_controller.js +47 -0
  18. data/app/assets/javascript/nitro_rails/controllers/togglable_checkbox_controller.js +9 -0
  19. data/app/assets/javascript/nitro_rails/helpers/device_info_helper.js +99 -0
  20. data/app/assets/javascript/nitro_rails/helpers/screen_info_helper.js +20 -0
  21. data/app/assets/javascript/nitro_rails/turbo_streams/clear_errors.js +22 -0
  22. data/app/assets/javascript/nitro_rails/turbo_streams/focus.js +8 -0
  23. data/app/assets/javascript/nitro_rails/turbo_streams/index.js +8 -0
  24. data/app/assets/javascript/nitro_rails/turbo_streams/insert.js +10 -0
  25. data/app/assets/javascript/nitro_rails/turbo_streams/redirect.js +10 -0
  26. data/app/assets/javascript/nitro_rails/turbo_streams/refresh_frame.js +9 -0
  27. data/app/assets/javascript/nitro_rails/turbo_streams/render_errors.js +26 -0
  28. data/app/assets/javascript/nitro_rails/turbo_streams/toggle_checkbox.js +9 -0
  29. data/app/assets/javascript/nitro_rails/turbo_streams/toggle_class.js +14 -0
  30. data/app/assets/stylesheets/nitro_rails/__reset.css +25 -0
  31. data/app/assets/stylesheets/nitro_rails/_base/_color.css +24 -0
  32. data/app/assets/stylesheets/nitro_rails/_base/_font.css +35 -0
  33. data/app/assets/stylesheets/nitro_rails/_base/_format.css +38 -0
  34. data/app/assets/stylesheets/nitro_rails/_base/_grayscale.css +79 -0
  35. data/app/assets/stylesheets/nitro_rails/_base/_utility.css +34 -0
  36. data/app/assets/stylesheets/nitro_rails/application.css +23 -0
  37. data/app/assets/stylesheets/nitro_rails/modules/action-navigation.css +24 -0
  38. data/app/assets/stylesheets/nitro_rails/modules/alert.css +31 -0
  39. data/app/assets/stylesheets/nitro_rails/modules/btn.css +57 -0
  40. data/app/assets/stylesheets/nitro_rails/modules/checkbox.css +42 -0
  41. data/app/assets/stylesheets/nitro_rails/modules/clickable.css +9 -0
  42. data/app/assets/stylesheets/nitro_rails/modules/clock.css +0 -0
  43. data/app/assets/stylesheets/nitro_rails/modules/editable-text.css +45 -0
  44. data/app/assets/stylesheets/nitro_rails/modules/forms.css +108 -0
  45. data/app/assets/stylesheets/nitro_rails/modules/hover-reveal.css +11 -0
  46. data/app/assets/stylesheets/nitro_rails/modules/inline-svg.css +14 -0
  47. data/app/assets/stylesheets/nitro_rails/modules/list.css +29 -0
  48. data/app/assets/stylesheets/nitro_rails/modules/main-navigation.css +20 -0
  49. data/app/assets/stylesheets/nitro_rails/modules/nitro-checkbox.css +42 -0
  50. data/app/assets/stylesheets/nitro_rails/modules/resource-collection.css +12 -0
  51. data/app/assets/stylesheets/nitro_rails/modules/search-bar.css +36 -0
  52. data/app/assets/stylesheets/nitro_rails/modules/settings.css +33 -0
  53. data/app/assets/stylesheets/nitro_rails/modules/tabs.css +38 -0
  54. data/app/assets/stylesheets/nitro_rails/modules/toggle-switch.css +60 -0
  55. data/app/assets/stylesheets/nitro_rails/panels/bound-card-dropdown-panel.css +0 -0
  56. data/app/assets/stylesheets/nitro_rails/panels/card-panel.css +0 -0
  57. data/app/assets/stylesheets/nitro_rails/panels/embedded-panel.css +31 -0
  58. data/app/assets/stylesheets/nitro_rails/panels/panel-display.css +111 -0
  59. data/app/assets/stylesheets/nitro_rails/panels/popup-panel.css +123 -0
  60. data/app/assets/stylesheets/nitro_rails/panels/side-panel.css +0 -0
  61. data/app/controllers/nitro_rails/sequence_controller.rb +64 -0
  62. data/app/helpers/nitro_rails/application_helper.rb +42 -1
  63. data/app/helpers/nitro_rails/color_helper.rb +23 -0
  64. data/app/helpers/nitro_rails/components_helper.rb +49 -0
  65. data/app/helpers/nitro_rails/inline_edit_helper.rb +48 -0
  66. data/app/helpers/nitro_rails/nitro_action_helper.rb +178 -0
  67. data/app/helpers/nitro_rails/nitro_collection_helper.rb +87 -0
  68. data/app/helpers/nitro_rails/nitro_edit_helper.rb +15 -0
  69. data/app/helpers/nitro_rails/nitro_form_helper.rb +104 -0
  70. data/app/helpers/nitro_rails/nitro_options_helper.rb +78 -0
  71. data/app/helpers/nitro_rails/nitro_tag_helper.rb +183 -0
  72. data/app/helpers/nitro_rails/panel_helper.rb +65 -0
  73. data/app/helpers/nitro_rails/resource_collection_helper.rb +66 -0
  74. data/app/helpers/nitro_rails/tabs_helper.rb +51 -0
  75. data/app/helpers/nitro_rails/turbo_stream_helper.rb +46 -0
  76. data/app/lib/nitro_rails/local_time.rb +51 -0
  77. data/app/lib/nitro_rails/performable_routes.rb +24 -0
  78. data/app/lib/nitro_rails/resource_collection.rb +183 -0
  79. data/app/lib/nitro_rails/sequential_routes.rb +36 -0
  80. data/app/lib/nitro_rails/time_interval.rb +88 -0
  81. data/app/lib/nitro_rails/time_string.rb +22 -0
  82. data/app/models/nitro_rails/broadcast.rb +76 -0
  83. data/app/models/nitro_rails/broadcastable_model.rb +91 -0
  84. data/app/models/nitro_rails/broadcastable_record.rb +118 -0
  85. data/app/models/nitro_rails/default_broadcasts.rb +149 -0
  86. data/app/models/nitro_rails/record.rb +50 -0
  87. data/app/pseudocode/nitro.html.erb +18 -0
  88. data/app/view_components/nitro_rails/nitro_collection/batch_selectable.rb +24 -0
  89. data/app/view_components/nitro_rails/nitro_collection.rb +66 -0
  90. data/app/view_components/nitro_rails/view_component.rb +9 -0
  91. data/app/views/layouts/nitro_rails/application.html.erb +3 -5
  92. data/app/views/nitro_rails/_editable_text.html.erb +32 -0
  93. data/app/views/nitro_rails/_nitro_checkbox.html.erb +5 -0
  94. data/app/views/nitro_rails/_nitro_form.html.erb +10 -0
  95. data/app/views/nitro_rails/_panel_frame_tag.html.erb +8 -0
  96. data/app/views/nitro_rails/_panel_template.html.erb +40 -0
  97. data/app/views/nitro_rails/_togglable_checkbox.html.erb +16 -0
  98. data/config/importmap.rb +1 -0
  99. data/config/initializers/date_formatters.rb +9 -0
  100. data/lib/nitro_rails/engine.rb +25 -0
  101. data/lib/nitro_rails/version.rb +1 -1
  102. metadata +165 -3
  103. data/app/models/nitro_rails/application_record.rb +0 -5
@@ -0,0 +1,183 @@
1
+ class NitroRails::ResourceCollection
2
+ delegate :content_tag, :concat, :capture, :form_tag, :hidden_field_tag, :render, :submit_tag, :turbo_stream_from, :request, :link_to, :url_for, to: :@view
3
+
4
+ # Should completely refigure what's passed in here
5
+ def initialize(resources, view:, create_path:, parent: nil, resource_type: nil, id: nil, signature: nil)
6
+ @resources = resources
7
+ @resource_path = create_path
8
+ @view = view
9
+ @parent = parent
10
+ @resource_type = resource_type if resource_type
11
+ @collection_tag = resource_type.pluralize if resource_type
12
+ # Little weird to override create path here
13
+ @create_path = (defined?(User) && @parent.is_a?(User)) ? @collection_tag : create_path
14
+ @id = id
15
+ @params = @view.params
16
+
17
+ if resources.first
18
+ @collection_tag ||= resources.first.collection_tag # For list
19
+ @resource_type ||= resources.first.type # For list elements
20
+ end
21
+ end
22
+
23
+ # RENDERING METHODS
24
+
25
+ def render_content(&block)
26
+ # MAYBE - PASSED BLOCKS JUST DO A REFRESH OF THE COLLECTION, WHERE AS RENDERED PARTIALS DOES APPEND/REMOVE BROADCASTS (nitro_id: RESOURCES_ID_REFRESH, RESOURCE_ID_BROADCAST)
27
+ container_tag do
28
+ concat(capture(self, &block)) if block_given?
29
+ concat(render_items) unless @resources_rendered
30
+ end
31
+ end
32
+
33
+ def container_tag(&block)
34
+ content_tag(:div,
35
+ class: "resource-collection",
36
+ data: {
37
+ controller: "resource-collection",
38
+ action: "keydown->resource-collection#handleKeyDown"
39
+ }, &block)
40
+ end
41
+
42
+ def render_items(partial = nil, **opts, &block)
43
+ @resources_rendered = true
44
+ # set_order_info(opts[:order])
45
+
46
+ content_tag(:ul, id: @collection_tag, class: collection_classes(opts[:class]), data: { nitro_id: "#{nitro_id}" }) do
47
+ @resources.order(opts[:order]).each do |resource|
48
+ concat(render_list_item(resource) do
49
+ render_item(resource, partial, &block)
50
+ end)
51
+ end
52
+ end
53
+ end
54
+
55
+ # Do we need separate functions for item and list item?
56
+ def render_list_item(resource, &block)
57
+ content_tag(:li, class: "resource-collection-item list-item #{resource.type}", data: { id: resource.id, nitro_id: "#{resource.nitro_id}" }) do
58
+ concat(capture(resource, &block))
59
+ end
60
+ end
61
+
62
+ def render_item(resource, partial, &block)
63
+ if block_given?
64
+ # set_resource_collection_cache(&block)
65
+ capture(resource, &block)
66
+ else
67
+ if partial.present?
68
+ render(partial, "#{resource.type}": resource)
69
+ else
70
+ render resource
71
+ end
72
+ end
73
+ end
74
+
75
+ def new_resource_form(&block)
76
+ @form_rendered = true
77
+ form_tag(@create_path || "/#{@view.url_for(@collection_tag)}", method: :post, data: {
78
+ action: "ajax:success->resource-collection#handleSuccess" }) do # I hate the way we're setting the path here
79
+ concat(hidden_field_tag("#{@resource_type.singularize}[#{@parent.type.underscore}_id]", @parent.id)) # This definitely shouldn't be specific to projects
80
+ concat(capture(self, &block)) if block_given?
81
+ end
82
+ end
83
+
84
+ def create_resource_button(text = nil, **opts, &block)
85
+ new_resource_form() do
86
+ block_given? ? concat(content_tag(:button, class: "btn btn-create", type: "submit", data: { turbo: opts[:turbo] || false, "keyboard-target": "enter" }, &block)) : concat(submit_tag(text || "Create", class: "btn-new")) # This can be cleaned up
87
+ end
88
+ end
89
+
90
+ ###
91
+ # INFORMATION METHODS
92
+
93
+ # Actually maybe split this so list classes are separate from container classes
94
+ def collection_classes(classes)
95
+ if @parent
96
+ @collection_classes ||= "#{@parent.type}-#{@collection_tag} #{@parent.type}-#{@parent.id}-#{@collection_tag} #{@parent.type}-#{@collection_tag}-list #{@collection_tag} #{@collection_tag}-list list list--arrow-detail list--basic-link #{classes}"
97
+ else
98
+ @collection_classes ||= "#{@collection_tag} #{@collection_tag}-list list list--arrow-detail list--basic-link #{classes}"
99
+ end
100
+ end
101
+
102
+ def nitro_id
103
+ @nitro_id ||= @parent ? "#{@parent.resource_id}:#{@collection_tag}" : "#{@collection_tag}"
104
+ end
105
+
106
+ ###
107
+ # ORDERING AND FILTERING METHODS
108
+ # Probably need to split these out into a separate module
109
+
110
+ def sorter(text, active_text=text, **opts)
111
+ if active_organizer(**opts)
112
+ link_to(active_text, current_path_without_order_params(**opts), class: "active", data: { turbo_action: :replace })
113
+ else
114
+ link_to(text, current_path_with_new_order_params(**opts), class: "inactive", data: { turbo_action: :replace })
115
+ end
116
+ end
117
+
118
+ def active_organizer(**opts)
119
+ list_params && list_params[:order] && opts.all? do |key, val|
120
+ list_params[:order][key].to_s == val.to_s
121
+ end
122
+ end
123
+
124
+ def filter(text, **opts)
125
+ link_to(text, current_path_with_filter_params(**opts))
126
+ end
127
+
128
+ def current_path_with_new_order_params(**opts)
129
+ # current_params = request.query_parameters
130
+ # url_for(current_params.merge("#{@signature}" => { order: opts }))
131
+ end
132
+
133
+ def current_path_without_order_params(**opts)
134
+ # current_params = request.query_parameters.deep_dup
135
+ # opts.each { |key, val| current_params["#{@signature}"][:order].delete("#{key}") }
136
+ # url_for(current_params)
137
+ end
138
+
139
+ def current_path_with_filter_params(**opts)
140
+ url_for("#{@signature}" => { filters: opts })
141
+ end
142
+
143
+ # def set_order_info(order_info = :created_at)
144
+ # # Determine order direction and method from list_params if present
145
+ # if list_params[:order]
146
+ # @order_direction = list_params[:order].values.first == "asc" ? :asc : :desc
147
+ # @order_method = list_params[:order].keys.first
148
+ # @order = { @order_method => @order_direction }
149
+ # else
150
+ # # Set order information from the provided order_info
151
+ # @order = order_info || :created_at
152
+
153
+ # case @order
154
+ # when Symbol
155
+ # @order_method = @order
156
+ # @order_direction = :asc
157
+ # when Hash
158
+ # @order_method, @order_direction = @order.first
159
+ # when Array
160
+ # if @order[0].is_a?(Hash)
161
+ # @order_method, @order_direction = @order[0].first
162
+ # else
163
+ # @order_method, @order_direction = @order
164
+ # end
165
+ # end
166
+ # end
167
+ # end
168
+
169
+ private
170
+
171
+ def list_params
172
+ @list_params ||= @params.permit("#{@signature}": { order: @resources.sort_methods, filters: @resources.filter_methods})
173
+ @list_params["#{@signature}"] || {}
174
+ end
175
+ end
176
+
177
+
178
+ # def render_list_item(resource, &block)
179
+ # concat(render(layout: "aros/resource_collection_list_item", locals: { resource: resource }, &block))
180
+ # end
181
+ # <%# content_tag(:li, id: dom_id(resource), class: "#{resource.type} #{resource.type}-item resource-collection-item", data: { nitro_id: resource.nitro_id }) do %>
182
+
183
+
@@ -0,0 +1,36 @@
1
+ module NitroRails::SequentialRoutes
2
+ def sequence(domain, sequence_name, steps:)
3
+ controller = "#{domain}/#{sequence_name}_sequence"
4
+ collection_route = "#{domain}/#{sequence_name}"
5
+ individual_route = "#{domain}/:id/#{sequence_name}"
6
+ helper = "#{domain}_#{sequence_name}_sequence"
7
+
8
+ get collection_route, to: "#{controller}#start", as: helper
9
+ get "#{individual_route}/finish", to: "#{controller}#finish", as: "#{helper}_finish"
10
+ # get "#{individual_route}/:step", to: "#{controller}#step", as: "#{helper}_step"
11
+ #
12
+ steps.each do |step|
13
+ get "#{individual_route}/#{step}", to: "#{controller}##{step}", as: "#{helper}_#{step}"
14
+ get "#{collection_route}/#{step}", to: "#{controller}##{step}", as: "new_#{helper}_#{step}"
15
+ patch "#{individual_route}/#{step}", to: "#{controller}##{step}!", as: "post_#{helper}_#{step}"
16
+ post "#{collection_route}/#{step}", to: "#{controller}##{step}!", as: "create_#{helper}_#{step}"
17
+ post "#{individual_route}/#{step}", to: "#{controller}##{step}!", as: "create_items_#{helper}_#{step}"
18
+ end
19
+ end
20
+
21
+ def sequence_controller(domain, sequence_name)
22
+ "sequences/#{domain}_#{sequence_name}"
23
+ end
24
+
25
+ def sequence_collection_route(domain, sequence_name)
26
+ "#{domain}/#{sequence_name}"
27
+ end
28
+
29
+ def sequence_individual_route(domain, sequence_name)
30
+ "#{domain}/:id/#{sequence_name}"
31
+ end
32
+
33
+ def sequence_helper(domain, sequence_name)
34
+ "#{domain}_#{sequence_name}_sequence"
35
+ end
36
+ end
@@ -0,0 +1,88 @@
1
+ class NitroRails::TimeInterval
2
+ attr_accessor :started_at, :ended_at
3
+
4
+ def self.parse(string, date: Date.current, offset: 0)
5
+ if valid_time_interval_string?(string)
6
+ times = string.split("-").map do |time|
7
+ time.to_time(:utc).change(
8
+ offset: offset,
9
+ day: date.day,
10
+ month: date.month,
11
+ year: date.year
12
+ )
13
+ end
14
+
15
+ new(started_at: times.first, ended_at: times.first > times.last ? times.last + 1.day : times.last)
16
+ end
17
+ end
18
+
19
+ def self.valid_time_interval_string?(string)
20
+ string.match?(/\A\s*#{NitroRails::TimeString::TIME_INTERVAL_REGEX}\s*\z/)
21
+ end
22
+
23
+ def self.from(started_at, **options)
24
+ new(**options.merge(started_at: started_at))
25
+ end
26
+
27
+ def initialize(**attributes)
28
+ set(**attributes)
29
+ end
30
+
31
+ def change(**attributes)
32
+ set(**attributes)
33
+ reset_duration
34
+ return self
35
+ end
36
+
37
+ def from(time)
38
+ change(started_at: time)
39
+ end
40
+
41
+ def to(time)
42
+ change(ended_at: time)
43
+ end
44
+
45
+ def duration
46
+ @duration ||= ActiveSupport::Duration.build(ended_at - started_at)
47
+ end
48
+
49
+ def times
50
+ [started_at, ended_at]
51
+ end
52
+
53
+ alias_method :to_a, :times
54
+
55
+ def dates
56
+ [started_at.to_date, ended_at.to_date]
57
+ end
58
+
59
+ def localtime(offset)
60
+ NitroRails::TimeInterval.new(
61
+ started_at: started_at.localtime(offset),
62
+ ended_at: ended_at.localtime(offset)
63
+ )
64
+ end
65
+
66
+ def to_fs(opt = :time, join: " - ")
67
+ times.map { |time| time.to_fs(opt) }.join(join)
68
+ end
69
+
70
+ def inspect
71
+ to_fs(:db)
72
+ end
73
+
74
+ private
75
+
76
+ def set(**attributes)
77
+ started_at = attributes[:started_at] || attributes[:from]
78
+ ended_at = attributes[:ended_at] || attributes[:to]
79
+
80
+ @started_at = started_at if started_at
81
+ @ended_at = ended_at if ended_at
82
+ end
83
+
84
+ def reset_duration
85
+ @duration = ActiveSupport::Duration.build(ended_at - started_at)
86
+ end
87
+ end
88
+
@@ -0,0 +1,22 @@
1
+ module NitroRails::TimeString
2
+ # REGULAR EXPRESSIONS
3
+ MINUTE_REGEX = /[0-5][0-9]/ # Matches 00 - 59
4
+
5
+ ## MILITARY TIME REGEX
6
+ MILITARY_HOUR_REGEX = /[0-1]?[0-9]|[2][0-3]/ # Matches 01-23
7
+ MILITARY_TIME_REGEX = /^\s*(#{MILITARY_HOUR_REGEX}):(#{MINUTE_REGEX})\s*$/
8
+
9
+ ## MERIDIEM TIME REGEX
10
+ MERIDIEM_HOUR_REGEX = /[0]?[1-9]|[1][0-2]/ # Matches 1-12
11
+ MERIDIEM_SUFFIX_REGEX = /[pPaA][.]?[mM][.]?/ # Matches 'a.m.' or 'PM'
12
+ POST_MERIDIEM_SUFFIX_REGEX = /[pP][.]?[mM][.]?/ # Matches just PM
13
+ MERIDIEM_TIME_REGEX = /(#{MERIDIEM_HOUR_REGEX}):(#{MINUTE_REGEX})\s*(#{MERIDIEM_SUFFIX_REGEX})/
14
+
15
+ ## ANY TIME
16
+ TIME_REGEX = /(#{MERIDIEM_TIME_REGEX})|(#{MILITARY_TIME_REGEX})/
17
+
18
+ ## TIME INTERVAL STRING
19
+ MERIDIEM_TIME_INTERVAL_REGEX = /(#{MERIDIEM_TIME_REGEX})\s*\-\s*(#{MERIDIEM_TIME_REGEX})/
20
+ MILITARY_TIME_INTERVAL_REGEX = /(#{MERIDIEM_TIME_REGEX})\s*\-\s*(#{MERIDIEM_TIME_REGEX})/
21
+ TIME_INTERVAL_REGEX = /(#{MERIDIEM_TIME_INTERVAL_REGEX})|(#{MILITARY_TIME_INTERVAL_REGEX})/
22
+ end
@@ -0,0 +1,76 @@
1
+ module NitroRails
2
+ # Maybe call Stream instead of Broadcast
3
+ class Broadcast
4
+ include Turbo::Streams::Broadcasts
5
+
6
+ def self.build(*streamables, action, &block)
7
+ new(*streamables, action).build(&block)
8
+ end
9
+
10
+ def build
11
+ yield(self)
12
+ return self
13
+ end
14
+
15
+ def initialize(streamables, action = nil)
16
+ @streamables = streamables
17
+ @action = action
18
+ end
19
+
20
+ def action(action)
21
+ @action = action
22
+ end
23
+
24
+ def attributes(**attributes)
25
+ @attributes = attributes
26
+ end
27
+
28
+ def target(target)
29
+ @target = "#{target}:not([data-reject-broadcasts])"
30
+ end
31
+
32
+ def targets(targets)
33
+ @targets = "#{targets}:not([data-reject-broadcasts])"
34
+ end
35
+
36
+ def template(content = nil, **rendering)
37
+ @template = content || render_format(:html, **rendering)
38
+ end
39
+
40
+ def stream_attributes
41
+ (@attributes || {}).merge(targets: @targets, target: @target, template: @template)
42
+ end
43
+
44
+ def stream
45
+ turbo_stream_action_tag(@action, **stream_attributes)
46
+ end
47
+
48
+ def perform
49
+ broadcast_stream_to(@streamables, content: stream)
50
+ end
51
+
52
+ def perform_later
53
+ broadcast_stream_later_to(@streamables, content: stream)
54
+ end
55
+
56
+ private
57
+ def render_format(format, **rendering)
58
+ ::ApplicationController.render(formats: [ format ], **rendering)
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ module Turbo::Streams::Broadcasts
65
+ def broadcast_stream_later_to(*streamables, content:)
66
+ Turbo::Streams::StreamBroadcastJob.perform_later(*streamables, content: content)
67
+ end
68
+ end
69
+
70
+ class Turbo::Streams::StreamBroadcastJob < ActiveJob::Base
71
+ discard_on ActiveJob::DeserializationError
72
+
73
+ def perform(*streamables, content:)
74
+ Turbo::StreamsChannel.broadcast_stream_to streamables, content: content
75
+ end
76
+ end
@@ -0,0 +1,91 @@
1
+ module NitroRails
2
+ module BroadcastableModel
3
+
4
+ ## Default broadcasts
5
+
6
+ def use_default_broadcasts!
7
+ broadcast_on_create :refresh_page, :focus_on_new_resource
8
+ broadcast_on_update :update_associated_attributes
9
+ broadcast_on_delete :remove_everywhere, :redirect_to_index
10
+ end
11
+
12
+ def use_refresh_broadcasts!
13
+ broadcast_on_create :refresh_page
14
+ broadcast_on_update :refresh_page
15
+ broadcast_on_delete :refresh_page
16
+ end
17
+
18
+ ## Action Broadcasts
19
+
20
+ def action_broadcasts(methods = {})
21
+ @action_broadcasts ||= methods
22
+ end
23
+
24
+ def broadcast_on_create(*methods)
25
+ broadcast_on_action(:create, *methods)
26
+ end
27
+
28
+ def broadcast_on_update(*methods)
29
+ broadcast_on_action(:update, *methods)
30
+ end
31
+
32
+ def broadcast_on_delete(*methods)
33
+ broadcast_on_action(:delete, *methods)
34
+ end
35
+
36
+ def broadcast_on_action(action, *methods)
37
+ if action_broadcasts[action].present?
38
+ action_broadcasts[action].concat(methods)
39
+ else
40
+ action_broadcasts[action] = methods
41
+ end
42
+ end
43
+
44
+ def template
45
+ new.template
46
+ end
47
+
48
+
49
+ ## Affiliations
50
+
51
+ def broadcast_affiliations(*affiliations)
52
+ @affiliations ||= affiliations
53
+ end
54
+
55
+
56
+ ## Attribute Broadcasts
57
+
58
+ def broadcast_on_attribute(attribute, *methods)
59
+ attribute_broadcasts[attribute] = methods
60
+ end
61
+
62
+ def attribute_broadcasts(broadcasts = {})
63
+ @attribute_broadcasts ||= broadcasts
64
+ end
65
+
66
+ def skipped_attributes
67
+ @skipped_attributes ||= []
68
+ end
69
+
70
+ def skip_attribute_broadcasting(*attributes)
71
+ @skipped_attributes = attributes
72
+ end
73
+
74
+ ## Status Attributes
75
+
76
+ def status_attributes(*attributes)
77
+ @status_attributes ||= attributes
78
+ end
79
+
80
+
81
+ ## Broadcast Identification
82
+
83
+ def type
84
+ name.underscore
85
+ end
86
+
87
+ def collection_tag
88
+ type.pluralize
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,118 @@
1
+ module NitroRails
2
+ module BroadcastableRecord
3
+
4
+ include NitroRails::DefaultBroadcasts
5
+
6
+ ## Action Broadcasting
7
+
8
+ def broadcast_create
9
+ broadcast(:create)
10
+ end
11
+
12
+ def broadcast_update
13
+ broadcast(:update)
14
+ end
15
+
16
+ def broadcast_delete
17
+ broadcast(:delete)
18
+ end
19
+
20
+ def broadcast(action)
21
+ self.class.action_broadcasts[action].to_a.each do |broadcast|
22
+ send(broadcast)
23
+ end
24
+ end
25
+
26
+
27
+ ## Attribute Broadcasting
28
+
29
+ def attribute_broadcasts
30
+ self.class.attribute_broadcasts
31
+ end
32
+
33
+ def skipped_attributes
34
+ self.class.skipped_attributes
35
+ end
36
+
37
+ ## Status Attributes
38
+
39
+ def status_attributes
40
+ self.class.status_attributes
41
+ end
42
+
43
+ def status_attribute?(attribute)
44
+ status_attributes.include?(attribute)
45
+ end
46
+
47
+ def attribute_effects(attribute)
48
+ effects = ["replace_text", "replace_value", "replace_html"]
49
+ if status_attribute?(attribute)
50
+ if send(attribute).is_a?(TrueClass) || send(attribute).is_a?(FalseClass)
51
+ effects << "toggle_boolean_status_class"
52
+ else
53
+ effects << "update_status_class"
54
+ end
55
+ end
56
+
57
+ return effects
58
+ end
59
+
60
+ def attribute_effects_string(attribute)
61
+ attribute_effects(attribute).join(" ").camelize(:lower)
62
+ end
63
+
64
+
65
+ ## Affiliations
66
+
67
+ def affiliation_keys
68
+ self.class.broadcast_affiliations
69
+ end
70
+
71
+ def affiliations
72
+ affiliation_keys.map do |key|
73
+ send(key)
74
+ end.concat(belongs_to)
75
+ end
76
+
77
+
78
+ ## Broadcast Identification
79
+
80
+ def template
81
+ @template ||= true
82
+ return self
83
+ end
84
+
85
+ def template?
86
+ @template
87
+ end
88
+
89
+ def type
90
+ self.class.name.underscore
91
+ end
92
+
93
+ def collection_tag
94
+ type.pluralize
95
+ end
96
+
97
+ def resource_id
98
+ persisted? ? "#{type}_#{id}" : "#{type}_$#{temporary_id}"
99
+ end
100
+
101
+ def temporary_id
102
+ template? ? "template" : object_id
103
+ end
104
+
105
+ def nitro_id(*attributes)
106
+ [resource_id.dasherize, *attributes.compact].join(':')
107
+ end
108
+
109
+ def path_for_record
110
+ send("#{type}_path", self)
111
+ end
112
+
113
+ def belongs_to
114
+ self.class.reflect_on_all_associations(:belongs_to).map(&:name)
115
+ end
116
+
117
+ end
118
+ end