nitro_rails 0.1.0 → 0.1.2
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 +18 -0
- data/app/assets/audio/mousetap.mp3 +0 -0
- data/app/assets/config/nitro_rails_manifest.js +2 -0
- data/app/assets/images/nitro_rails/Aros2.png +0 -0
- data/app/assets/javascript/nitro_rails/application.js +4 -0
- data/app/assets/javascript/nitro_rails/controllers/application.js +9 -0
- data/app/assets/javascript/nitro_rails/controllers/audio_controller.js +16 -0
- data/app/assets/javascript/nitro_rails/controllers/clock_controller.js +21 -0
- data/app/assets/javascript/nitro_rails/controllers/editable_text_controller.js +61 -0
- data/app/assets/javascript/nitro_rails/controllers/index.js +21 -0
- data/app/assets/javascript/nitro_rails/controllers/nitro_collection_controller.js +127 -0
- data/app/assets/javascript/nitro_rails/controllers/nitro_controller.js +35 -0
- data/app/assets/javascript/nitro_rails/controllers/nitro_form_controller.js +81 -0
- data/app/assets/javascript/nitro_rails/controllers/panel_controller.js +70 -0
- data/app/assets/javascript/nitro_rails/controllers/panel_manager_controller.js +89 -0
- data/app/assets/javascript/nitro_rails/controllers/tabs_controller.js +81 -0
- data/app/assets/javascript/nitro_rails/controllers/time_zone_controller.js +47 -0
- data/app/assets/javascript/nitro_rails/controllers/togglable_checkbox_controller.js +9 -0
- data/app/assets/javascript/nitro_rails/helpers/device_info_helper.js +99 -0
- data/app/assets/javascript/nitro_rails/helpers/screen_info_helper.js +20 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/clear_errors.js +22 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/focus.js +8 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/index.js +8 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/insert.js +10 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/redirect.js +10 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/refresh_frame.js +9 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/render_errors.js +26 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/toggle_checkbox.js +9 -0
- data/app/assets/javascript/nitro_rails/turbo_streams/toggle_class.js +14 -0
- data/app/assets/stylesheets/nitro_rails/__reset.css +25 -0
- data/app/assets/stylesheets/nitro_rails/_base/_color.css +24 -0
- data/app/assets/stylesheets/nitro_rails/_base/_font.css +35 -0
- data/app/assets/stylesheets/nitro_rails/_base/_format.css +38 -0
- data/app/assets/stylesheets/nitro_rails/_base/_grayscale.css +79 -0
- data/app/assets/stylesheets/nitro_rails/_base/_utility.css +34 -0
- data/app/assets/stylesheets/nitro_rails/application.css +23 -0
- data/app/assets/stylesheets/nitro_rails/modules/action-navigation.css +24 -0
- data/app/assets/stylesheets/nitro_rails/modules/alert.css +31 -0
- data/app/assets/stylesheets/nitro_rails/modules/btn.css +57 -0
- data/app/assets/stylesheets/nitro_rails/modules/checkbox.css +42 -0
- data/app/assets/stylesheets/nitro_rails/modules/clickable.css +9 -0
- data/app/assets/stylesheets/nitro_rails/modules/clock.css +0 -0
- data/app/assets/stylesheets/nitro_rails/modules/editable-text.css +45 -0
- data/app/assets/stylesheets/nitro_rails/modules/forms.css +108 -0
- data/app/assets/stylesheets/nitro_rails/modules/hover-reveal.css +11 -0
- data/app/assets/stylesheets/nitro_rails/modules/inline-svg.css +14 -0
- data/app/assets/stylesheets/nitro_rails/modules/list.css +29 -0
- data/app/assets/stylesheets/nitro_rails/modules/main-navigation.css +20 -0
- data/app/assets/stylesheets/nitro_rails/modules/nitro-checkbox.css +42 -0
- data/app/assets/stylesheets/nitro_rails/modules/resource-collection.css +12 -0
- data/app/assets/stylesheets/nitro_rails/modules/search-bar.css +36 -0
- data/app/assets/stylesheets/nitro_rails/modules/settings.css +33 -0
- data/app/assets/stylesheets/nitro_rails/modules/tabs.css +38 -0
- data/app/assets/stylesheets/nitro_rails/modules/toggle-switch.css +60 -0
- data/app/assets/stylesheets/nitro_rails/panels/bound-card-dropdown-panel.css +0 -0
- data/app/assets/stylesheets/nitro_rails/panels/card-panel.css +0 -0
- data/app/assets/stylesheets/nitro_rails/panels/embedded-panel.css +31 -0
- data/app/assets/stylesheets/nitro_rails/panels/panel-display.css +111 -0
- data/app/assets/stylesheets/nitro_rails/panels/popup-panel.css +123 -0
- data/app/assets/stylesheets/nitro_rails/panels/side-panel.css +0 -0
- data/app/controllers/nitro_rails/sequence_controller.rb +64 -0
- data/app/helpers/nitro_rails/application_helper.rb +42 -1
- data/app/helpers/nitro_rails/color_helper.rb +23 -0
- data/app/helpers/nitro_rails/components_helper.rb +49 -0
- data/app/helpers/nitro_rails/inline_edit_helper.rb +48 -0
- data/app/helpers/nitro_rails/nitro_action_helper.rb +178 -0
- data/app/helpers/nitro_rails/nitro_collection_helper.rb +87 -0
- data/app/helpers/nitro_rails/nitro_edit_helper.rb +15 -0
- data/app/helpers/nitro_rails/nitro_form_helper.rb +104 -0
- data/app/helpers/nitro_rails/nitro_options_helper.rb +78 -0
- data/app/helpers/nitro_rails/nitro_tag_helper.rb +183 -0
- data/app/helpers/nitro_rails/panel_helper.rb +65 -0
- data/app/helpers/nitro_rails/resource_collection_helper.rb +66 -0
- data/app/helpers/nitro_rails/tabs_helper.rb +51 -0
- data/app/helpers/nitro_rails/turbo_stream_helper.rb +46 -0
- data/app/lib/nitro_rails/local_time.rb +51 -0
- data/app/lib/nitro_rails/performable_routes.rb +24 -0
- data/app/lib/nitro_rails/resource_collection.rb +183 -0
- data/app/lib/nitro_rails/sequential_routes.rb +36 -0
- data/app/lib/nitro_rails/time_interval.rb +88 -0
- data/app/lib/nitro_rails/time_string.rb +22 -0
- data/app/models/nitro_rails/broadcast.rb +76 -0
- data/app/models/nitro_rails/broadcastable_model.rb +91 -0
- data/app/models/nitro_rails/broadcastable_record.rb +118 -0
- data/app/models/nitro_rails/default_broadcasts.rb +149 -0
- data/app/models/nitro_rails/record.rb +50 -0
- data/app/pseudocode/nitro.html.erb +18 -0
- data/app/view_components/nitro_rails/nitro_collection/batch_selectable.rb +24 -0
- data/app/view_components/nitro_rails/nitro_collection.rb +66 -0
- data/app/view_components/nitro_rails/view_component.rb +9 -0
- data/app/views/layouts/nitro_rails/application.html.erb +3 -5
- data/app/views/nitro_rails/_editable_text.html.erb +32 -0
- data/app/views/nitro_rails/_nitro_checkbox.html.erb +5 -0
- data/app/views/nitro_rails/_nitro_form.html.erb +10 -0
- data/app/views/nitro_rails/_panel_frame_tag.html.erb +8 -0
- data/app/views/nitro_rails/_panel_template.html.erb +40 -0
- data/app/views/nitro_rails/_togglable_checkbox.html.erb +16 -0
- data/config/importmap.rb +1 -0
- data/config/initializers/date_formatters.rb +9 -0
- data/lib/nitro_rails/engine.rb +25 -0
- data/lib/nitro_rails/version.rb +1 -1
- metadata +166 -3
- 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
|