ruflet_rails 0.0.13 → 0.0.14
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 +165 -62
- data/lib/generators/ruflet/install/install_generator.rb +3 -3
- data/lib/ruflet/rails/configuration.rb +13 -7
- data/lib/ruflet/rails/install_support.rb +19 -637
- data/lib/ruflet/rails/native_app/actions.rb +167 -0
- data/lib/ruflet/rails/native_app/customization.rb +118 -0
- data/lib/ruflet/rails/native_app/html_adapter.rb +248 -0
- data/lib/ruflet/rails/native_app/navigation.rb +645 -0
- data/lib/ruflet/rails/native_app/overlays.rb +222 -0
- data/lib/ruflet/rails/native_app/shell.rb +390 -0
- data/lib/ruflet/rails/native_app.rb +25 -222
- data/lib/ruflet/rails/protocol/web_app.rb +1 -1
- data/lib/ruflet/rails/railtie.rb +3 -20
- data/lib/ruflet/rails/view_helpers.rb +205 -0
- data/lib/ruflet/rails.rb +22 -55
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_rails.rb +0 -4
- metadata +14 -15
- data/lib/generators/ruflet/form/form_generator.rb +0 -55
- data/lib/generators/ruflet/scaffold/scaffold_generator.rb +0 -54
- data/lib/ruflet/rails/form_helpers.rb +0 -161
- data/lib/ruflet/rails/generator_hooks.rb +0 -25
- data/lib/ruflet/rails/resource_component.rb +0 -370
- data/lib/ruflet/rails/route_stack.rb +0 -90
- data/lib/ruflet/rails/webview_app.rb +0 -54
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "date"
|
|
4
|
-
require "time"
|
|
5
|
-
require "active_model"
|
|
6
|
-
require "active_support/core_ext/string/inflections"
|
|
7
|
-
|
|
8
|
-
module Ruflet
|
|
9
|
-
module Rails
|
|
10
|
-
# Base class for a Ruflet CRUD resource.
|
|
11
|
-
#
|
|
12
|
-
# The generated subclass owns the explicit CRUD UI (render, show, the
|
|
13
|
-
# create/edit form) AND the database calls (record.update, record.destroy!,
|
|
14
|
-
# model_class.new) — so a developer can read and change anything. This base
|
|
15
|
-
# provides reusable helpers: model resolution, record loading,
|
|
16
|
-
# field inference (resource_fields/display_fields/display_value), navigation
|
|
17
|
-
# (render_index/render_show/refresh), dialog management, snackbars, and the
|
|
18
|
-
# date/time picker value helpers.
|
|
19
|
-
#
|
|
20
|
-
# The same subclass renders on web and on mobile/desktop. It is mounted from
|
|
21
|
-
# config/routes.rb (the route path is declared there, never in the
|
|
22
|
-
# component):
|
|
23
|
-
#
|
|
24
|
-
# mount Ruflet::Rails.web_app(view: "ProductComponent"), at: "/products"
|
|
25
|
-
#
|
|
26
|
-
# web_app(view:) calls `.render(page)` on the class, which is why the class
|
|
27
|
-
# method below is the entrypoint. The model is inferred from the class name
|
|
28
|
-
# (ProductComponent -> Product); override `model_class` to customize.
|
|
29
|
-
class ResourceComponent
|
|
30
|
-
include Ruflet::UI::SharedControlForwarders
|
|
31
|
-
|
|
32
|
-
attr_reader :page, :controller
|
|
33
|
-
|
|
34
|
-
class << self
|
|
35
|
-
# Entrypoint used by web_app(view: "...") on each new session.
|
|
36
|
-
def render(page, *_args)
|
|
37
|
-
new(page).render_index
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Declare or read the managed model: `model Product` or inferred from
|
|
41
|
-
# the component class name.
|
|
42
|
-
def model(value = nil)
|
|
43
|
-
@model_class = value if value
|
|
44
|
-
@model_class || inferred_model_class
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Declare or read the plural resource title shown on the index screen.
|
|
48
|
-
def title(value = nil)
|
|
49
|
-
@resource_title = value if value
|
|
50
|
-
@resource_title || (model_name ? model_name.plural.humanize.titleize : "Resources")
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def singular_title
|
|
54
|
-
model_name ? model_name.human.titleize : "Record"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def inferred_model_class
|
|
60
|
-
base = name.to_s.split("::").last.to_s.sub(/Component\z/, "")
|
|
61
|
-
base.empty? ? nil : base.safe_constantize
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def model_name
|
|
65
|
-
klass = model
|
|
66
|
-
klass.respond_to?(:model_name) ? klass.model_name : nil
|
|
67
|
-
rescue StandardError
|
|
68
|
-
nil
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def initialize(page, controller: nil)
|
|
73
|
-
@page = page
|
|
74
|
-
@controller = controller
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# --- Resource configuration (override in the generated subclass) --------
|
|
78
|
-
|
|
79
|
-
def model_class
|
|
80
|
-
self.class.model
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def resource_title
|
|
84
|
-
self.class.title
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def singular_title
|
|
88
|
-
model_class.respond_to?(:model_name) ? model_class.model_name.human.titleize : self.class.singular_title
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Fields rendered on the detail (show) screen. The default uses the
|
|
92
|
-
# model's own attribute names; subclasses can override it.
|
|
93
|
-
def resource_fields
|
|
94
|
-
default_resource_fields
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Columns rendered in the index table / list tiles.
|
|
98
|
-
def display_fields
|
|
99
|
-
resource_fields
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Formats a single field for display. Override for custom rendering.
|
|
103
|
-
def display_value(record, field)
|
|
104
|
-
record.public_send(field).to_s
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# --- Record loading & navigation ---------------------------------------
|
|
108
|
-
|
|
109
|
-
def records
|
|
110
|
-
scope = model_class.respond_to?(:limit) ? model_class.limit(50) : model_class.all
|
|
111
|
-
scope.respond_to?(:limit) ? scope.limit(50) : scope.to_a.first(50)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def render_index
|
|
115
|
-
page.title = resource_title
|
|
116
|
-
page.views = []
|
|
117
|
-
page.add(render)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def render_show(record)
|
|
121
|
-
page.views = []
|
|
122
|
-
page.add(show(record))
|
|
123
|
-
page.update
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def show_record(record)
|
|
127
|
-
render_show(record)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Re-render the index after a create/update/destroy. The generated
|
|
131
|
-
# component calls this from its own (explicit) save/destroy code.
|
|
132
|
-
def refresh
|
|
133
|
-
render_index
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# --- Index labels -------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
def primary_label(record)
|
|
139
|
-
field = display_fields.first
|
|
140
|
-
field ? display_value(record, field) : "##{record_id(record)}"
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def secondary_label(record)
|
|
144
|
-
field = display_fields[1]
|
|
145
|
-
field ? display_value(record, field) : nil
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
private
|
|
149
|
-
|
|
150
|
-
def default_resource_fields
|
|
151
|
-
return [] unless model_class.respond_to?(:attribute_names)
|
|
152
|
-
|
|
153
|
-
ignored = %w[id created_at updated_at]
|
|
154
|
-
model_class.attribute_names.map(&:to_s) - ignored
|
|
155
|
-
rescue StandardError
|
|
156
|
-
[]
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def control_delegate
|
|
160
|
-
Ruflet::DSL
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# --- Layout helpers shared by every resource UI ------------------------
|
|
164
|
-
|
|
165
|
-
def compact?
|
|
166
|
-
width = page.client_details["width"].to_f
|
|
167
|
-
width > 0 && width < 600
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def dialog_width
|
|
171
|
-
width = page.client_details["width"].to_f
|
|
172
|
-
return 520 if width <= 0
|
|
173
|
-
|
|
174
|
-
[[width - 64, 280].max, 520].min
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def record_id(record)
|
|
178
|
-
record.respond_to?(:id) ? record.id : nil
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def show_errors(record)
|
|
182
|
-
show_snackbar(error_message(record))
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def show_snackbar(message)
|
|
186
|
-
page.snackbar = snackbar(text(message), open: true)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def error_message(record)
|
|
190
|
-
messages = record.errors.full_messages
|
|
191
|
-
messages.respond_to?(:to_sentence) ? messages.to_sentence : messages.join(", ")
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# --- Dialog management --------------------------------------------------
|
|
195
|
-
|
|
196
|
-
def open_dialog(dialog)
|
|
197
|
-
prune_closed_dialogs
|
|
198
|
-
return open_primary_dialog(dialog) if primary_dialog?(dialog) && !latest_stack_dialog
|
|
199
|
-
|
|
200
|
-
preserve_rendered_open_dialog_state
|
|
201
|
-
page.show_dialog(dialog)
|
|
202
|
-
dialog
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def close_dialog(dialog)
|
|
206
|
-
close_open_dialogs_above(dialog)
|
|
207
|
-
close_tracked_dialog(dialog)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def close_dialogs(*dialogs)
|
|
211
|
-
dialogs.flatten.compact.each do |dialog|
|
|
212
|
-
close_tracked_dialog(dialog)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def date_picker_value(value, fallback: Date.today)
|
|
217
|
-
return value.to_date.iso8601 if value.respond_to?(:to_date)
|
|
218
|
-
return fallback.iso8601 if value.to_s.empty?
|
|
219
|
-
|
|
220
|
-
Date.parse(value.to_s).iso8601
|
|
221
|
-
rescue ArgumentError
|
|
222
|
-
fallback.iso8601
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def datetime_picker_value(value, fallback: Time.now)
|
|
226
|
-
return value.iso8601 if value.respond_to?(:iso8601)
|
|
227
|
-
return fallback.iso8601 if value.to_s.empty?
|
|
228
|
-
|
|
229
|
-
Time.parse(value.to_s).iso8601
|
|
230
|
-
rescue ArgumentError
|
|
231
|
-
fallback.iso8601
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def time_picker_value(value)
|
|
235
|
-
return value.strftime("%H:%M") if value.respond_to?(:strftime)
|
|
236
|
-
|
|
237
|
-
value.to_s
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
def date_range_picker_values(value)
|
|
241
|
-
if value.respond_to?(:begin) && value.respond_to?(:end)
|
|
242
|
-
return [date_picker_value(value.begin), date_picker_value(value.end)]
|
|
243
|
-
end
|
|
244
|
-
if value.respond_to?(:to_a) && value.to_a.length >= 2
|
|
245
|
-
first, last = value.to_a.first(2)
|
|
246
|
-
return [date_picker_value(first), date_picker_value(last)]
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
raw = value.to_s.strip.gsub(/\A[\[(]|[\])]\z/, "")
|
|
250
|
-
start_value, end_value =
|
|
251
|
-
if raw.include?("...")
|
|
252
|
-
raw.split("...", 2)
|
|
253
|
-
elsif raw.include?("..")
|
|
254
|
-
raw.split("..", 2)
|
|
255
|
-
else
|
|
256
|
-
raw.split(",", 2)
|
|
257
|
-
end
|
|
258
|
-
[date_picker_value(start_value), date_picker_value(end_value || start_value)]
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def date_display_value(value)
|
|
262
|
-
visible = value.to_s.split("T", 2).first
|
|
263
|
-
visible.empty? ? "Not selected" : visible
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def date_range_display_value(start_value, end_value)
|
|
267
|
-
"#{date_display_value(start_value)} - #{date_display_value(end_value)}"
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def time_display_value(value)
|
|
271
|
-
visible = value.to_s
|
|
272
|
-
visible.empty? ? "Not selected" : visible
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def close_open_dialogs_above(dialog)
|
|
276
|
-
return false unless page.respond_to?(:latest_open_dialog, true)
|
|
277
|
-
|
|
278
|
-
while (latest = page.__send__(:latest_open_dialog)) && !latest.equal?(dialog)
|
|
279
|
-
close_tracked_dialog(latest)
|
|
280
|
-
end
|
|
281
|
-
rescue StandardError
|
|
282
|
-
false
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
def close_tracked_dialog(dialog)
|
|
286
|
-
return unless dialog
|
|
287
|
-
|
|
288
|
-
close_dialog_stack_entry(dialog)
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def close_dialog_stack_entry(dialog)
|
|
292
|
-
return close_primary_dialog(dialog) if primary_dialog_slot?(dialog)
|
|
293
|
-
|
|
294
|
-
unless page.respond_to?(:remove_dialog_tracking, true)
|
|
295
|
-
page.close_dialog(dialog)
|
|
296
|
-
return
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
page.update(dialog, open: false) if dialog.wire_id
|
|
300
|
-
page.__send__(:remove_dialog_tracking, dialog)
|
|
301
|
-
sync_dialogs_container
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def sync_dialogs_container
|
|
305
|
-
dialogs_container = page.instance_variable_get(:@dialogs_container) if page.instance_variable_defined?(:@dialogs_container)
|
|
306
|
-
return page.update unless dialogs_container&.wire_id
|
|
307
|
-
|
|
308
|
-
page.update(dialogs_container, controls: dialogs_container.props["controls"])
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def open_primary_dialog(dialog)
|
|
312
|
-
dialog.props["open"] = true
|
|
313
|
-
page.dialog = dialog
|
|
314
|
-
dialog
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def close_primary_dialog(dialog)
|
|
318
|
-
page.update(dialog, open: false) if dialog.wire_id
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
def primary_dialog_slot?(dialog)
|
|
322
|
-
page.instance_variable_defined?(:@dialog) && page.instance_variable_get(:@dialog).equal?(dialog)
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def latest_stack_dialog
|
|
326
|
-
page.__send__(:latest_open_dialog) if page.respond_to?(:latest_open_dialog, true)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
def primary_dialog?(dialog)
|
|
330
|
-
dialog.type.to_s.tr("_", "").casecmp("alertdialog").zero?
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
def preserve_rendered_open_dialog_state
|
|
334
|
-
tracked_dialogs.each do |dialog|
|
|
335
|
-
next if dialog.props["open"] == false
|
|
336
|
-
|
|
337
|
-
dialog.props["_open"] = true
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def tracked_dialogs
|
|
342
|
-
dialogs = []
|
|
343
|
-
dialogs << page.instance_variable_get(:@dialog) if page.instance_variable_defined?(:@dialog)
|
|
344
|
-
dialogs.concat(Array(page.instance_variable_get(:@dialogs))) if page.instance_variable_defined?(:@dialogs)
|
|
345
|
-
dialogs.compact.uniq
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
def prune_closed_dialogs
|
|
349
|
-
return unless page.respond_to?(:remove_dialog_tracking, true)
|
|
350
|
-
|
|
351
|
-
dialogs = page.instance_variable_get(:@dialogs) if page.instance_variable_defined?(:@dialogs)
|
|
352
|
-
Array(dialogs).select { |dialog| dialog.props["open"] == false }.each do |dialog|
|
|
353
|
-
page.__send__(:remove_dialog_tracking, dialog)
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
# Methods not found on the component fall back to an optional controller,
|
|
358
|
-
# kept for apps that still pair a component with a separate host object.
|
|
359
|
-
def method_missing(name, *args, **kwargs, &block)
|
|
360
|
-
return controller.__send__(name, *args, **kwargs, &block) if controller&.respond_to?(name, true)
|
|
361
|
-
|
|
362
|
-
super
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
def respond_to_missing?(name, include_private = false)
|
|
366
|
-
(controller&.respond_to?(name, true) || false) || super
|
|
367
|
-
end
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ruflet
|
|
4
|
-
module Rails
|
|
5
|
-
# Flet-style routed navigation stack for complex, multi-screen apps.
|
|
6
|
-
#
|
|
7
|
-
# It wires up the boilerplate that Flet's routing examples spell out by hand
|
|
8
|
-
# — on_route_change, on_view_pop and the initial page.go — so an app only has
|
|
9
|
-
# to describe, per route, how to build that screen's View stack. The stack is
|
|
10
|
-
# rebuilt from the current route on every navigation, and the client back
|
|
11
|
-
# button (AppBar arrow / system back) pops it automatically.
|
|
12
|
-
#
|
|
13
|
-
# Block form — one builder receives the current route and the stack:
|
|
14
|
-
#
|
|
15
|
-
# Ruflet::Rails.routed(page) do |route, nav|
|
|
16
|
-
# nav.push(home_view)
|
|
17
|
-
# nav.push(store_view) if route == "/store"
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
|
-
# Map form — declare a builder per route:
|
|
21
|
-
#
|
|
22
|
-
# Ruflet::Rails::RouteStack.new(page)
|
|
23
|
-
# .on("/") { |nav| nav.push(home_view) }
|
|
24
|
-
# .on("/store") { |nav| nav.push(home_view); nav.push(store_view) }
|
|
25
|
-
# .start
|
|
26
|
-
#
|
|
27
|
-
# Navigate with page.go("/store"); pushing onto the stack happens by
|
|
28
|
-
# rebuilding for the new route, exactly like Flet.
|
|
29
|
-
class RouteStack
|
|
30
|
-
def initialize(page, &builder)
|
|
31
|
-
@page = page
|
|
32
|
-
@builder = builder
|
|
33
|
-
@routes = {}
|
|
34
|
-
@stack = []
|
|
35
|
-
wire_page_handlers!
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Register a per-route stack builder. The block receives this RouteStack so
|
|
39
|
-
# it can #push one or more views for that route.
|
|
40
|
-
def on(route, &block)
|
|
41
|
-
@routes[normalize(route)] = block
|
|
42
|
-
self
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Append a View (or anything responding to #route) onto the current stack.
|
|
46
|
-
def push(view)
|
|
47
|
-
@stack << view
|
|
48
|
-
view
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Render the current route's stack and begin listening for navigation.
|
|
52
|
-
def start
|
|
53
|
-
@page.go(@page.route.to_s.empty? ? "/" : @page.route)
|
|
54
|
-
self
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def wire_page_handlers!
|
|
60
|
-
@page.on_route_change = ->(_event) { rebuild }
|
|
61
|
-
@page.on_view_pop = lambda do |_event|
|
|
62
|
-
@page.views.pop
|
|
63
|
-
top = @page.views.last
|
|
64
|
-
@page.go(top.respond_to?(:route) ? top.route : "/")
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def rebuild
|
|
69
|
-
@stack = []
|
|
70
|
-
route = normalize(@page.route.to_s.split("?", 2).first)
|
|
71
|
-
|
|
72
|
-
if @builder
|
|
73
|
-
@builder.call(route, self)
|
|
74
|
-
elsif (block = @routes[route])
|
|
75
|
-
block.call(self)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
@page.views = @stack
|
|
79
|
-
@page.update
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def normalize(route)
|
|
83
|
-
value = route.to_s.strip
|
|
84
|
-
return "/" if value.empty? || value == "/"
|
|
85
|
-
|
|
86
|
-
"/#{value.gsub(%r{\A/+|/+\z}, "")}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ruflet
|
|
4
|
-
module Rails
|
|
5
|
-
# Build a "webview app" shell: a native screen whose body is a WebView, with
|
|
6
|
-
# a native AppBar on top and a native NavigationBar (or bottom AppBar) below.
|
|
7
|
-
# The chrome stays native while the body shows your website.
|
|
8
|
-
#
|
|
9
|
-
# Ruflet.run do |page|
|
|
10
|
-
# Ruflet::Rails.routed(page) do |route, nav|
|
|
11
|
-
# if route == "/"
|
|
12
|
-
# nav.push(
|
|
13
|
-
# Ruflet::Rails.webview_app(
|
|
14
|
-
# url: "https://myapp.com",
|
|
15
|
-
# appbar: app_bar(title: text("My App")),
|
|
16
|
-
# navigation_bar: navigation_bar(destinations: [...]),
|
|
17
|
-
# # Following a link inside the page opens a NATIVE view instead:
|
|
18
|
-
# on_navigate: ->(url) { page.go("/details") if url.include?("/product/") }
|
|
19
|
-
# )
|
|
20
|
-
# )
|
|
21
|
-
# elsif route == "/details"
|
|
22
|
-
# nav.push(detail_view) # native screen, pushed on top; back returns to the shell
|
|
23
|
-
# end
|
|
24
|
-
# end
|
|
25
|
-
# end
|
|
26
|
-
#
|
|
27
|
-
# on_navigate fires from the webview's url change as the user navigates, with
|
|
28
|
-
# the target URL — map it to a native route with page.go. (prevent_links can
|
|
29
|
-
# additionally stop the webview from ever loading matching URLs.) Pass a block
|
|
30
|
-
# to capture the WebView control itself (e.g. to run_javascript on it later).
|
|
31
|
-
module_function
|
|
32
|
-
|
|
33
|
-
def webview_app(url:, appbar: nil, navigation_bar: nil, bottom_appbar: nil,
|
|
34
|
-
route: "/", prevent_links: nil, on_navigate: nil,
|
|
35
|
-
on_page_started: nil, on_page_ended: nil, **webview_props)
|
|
36
|
-
webview_args = { url: url, method: "get", expand: true }
|
|
37
|
-
webview_args[:prevent_links] = prevent_links unless prevent_links.nil?
|
|
38
|
-
webview_args[:on_url_change] = ->(event) { on_navigate.call(event.data) } if on_navigate
|
|
39
|
-
webview_args[:on_page_started] = on_page_started if on_page_started
|
|
40
|
-
webview_args[:on_page_ended] = on_page_ended if on_page_ended
|
|
41
|
-
webview_args.merge!(webview_props)
|
|
42
|
-
|
|
43
|
-
body = Ruflet::UI::ControlFactory.build(:webview, **webview_args)
|
|
44
|
-
yield body if block_given?
|
|
45
|
-
|
|
46
|
-
view_args = { route: route, controls: [body] }
|
|
47
|
-
view_args[:appbar] = appbar unless appbar.nil?
|
|
48
|
-
view_args[:navigation_bar] = navigation_bar unless navigation_bar.nil?
|
|
49
|
-
view_args[:bottom_appbar] = bottom_appbar unless bottom_appbar.nil?
|
|
50
|
-
|
|
51
|
-
Ruflet::UI::ControlFactory.build(:view, **view_args)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|