ruflet_core 0.0.17 → 0.0.19
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/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_core.rb +9 -7
- data/lib/ruflet_protocol/ruflet/protocol.rb +28 -54
- data/lib/ruflet_ui/ruflet/app.rb +1 -1
- data/lib/ruflet_ui/ruflet/colors.rb +4 -50
- data/lib/ruflet_ui/ruflet/control.rb +69 -64
- data/lib/ruflet_ui/ruflet/dsl.rb +10 -3
- data/lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb +2 -2
- data/lib/ruflet_ui/ruflet/icons/cupertino_icon_lookup.rb +1 -0
- data/lib/ruflet_ui/ruflet/icons/icon_constant_names.rb +29 -0
- data/lib/ruflet_ui/ruflet/icons/material/material_icons.rb +2 -2
- data/lib/ruflet_ui/ruflet/icons/material_icon_lookup.rb +1 -0
- data/lib/ruflet_ui/ruflet/page.rb +313 -523
- data/lib/ruflet_ui/ruflet/ui/control_factory.rb +68 -12
- data/lib/ruflet_ui/ruflet/ui/control_registry.rb +13 -6
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheet_control.rb +8 -45
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheetaction_control.rb +45 -2
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactivityindicator_control.rb +43 -2
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoalertdialog_control.rb +0 -5
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobutton_control.rb +59 -4
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocheckbox_control.rb +56 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenu_control.rb +1 -3
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenuaction_control.rb +1 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodatepicker_control.rb +53 -8
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodialogaction_control.rb +1 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinofilledbutton_control.rb +59 -4
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinolisttile_control.rb +54 -9
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinonavigationbar_control.rb +47 -9
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinopicker_control.rb +52 -10
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoradio_control.rb +52 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinosegmentedbutton_control.rb +49 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslider_control.rb +50 -5
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslidingsegmentedbutton_control.rb +46 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoswitch_control.rb +61 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotextfield_control.rb +145 -1
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotimerpicker_control.rb +47 -12
- data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotintedbutton_control.rb +59 -4
- data/lib/ruflet_ui/ruflet/ui/controls/materials/alertdialog_control.rb +8 -45
- data/lib/ruflet_ui/ruflet/ui/controls/materials/appbar_control.rb +8 -40
- data/lib/ruflet_ui/ruflet/ui/controls/materials/audio_control.rb +16 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/autocomplete_control.rb +44 -1
- data/lib/ruflet_ui/ruflet/ui/controls/materials/badge_control.rb +0 -7
- data/lib/ruflet_ui/ruflet/ui/controls/materials/banner_control.rb +0 -7
- data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomappbar_control.rb +8 -47
- data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomsheet_control.rb +0 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/button_control.rb +8 -54
- data/lib/ruflet_ui/ruflet/ui/controls/materials/card_control.rb +8 -50
- data/lib/ruflet_ui/ruflet/ui/controls/materials/chart_controls.rb +19 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb +9 -60
- data/lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb +7 -81
- data/lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb +7 -56
- data/lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb +9 -65
- data/lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb +50 -4
- data/lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb +1 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/datacolumn_control.rb +1 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/datatable_control.rb +7 -88
- data/lib/ruflet_ui/ruflet/ui/controls/materials/datepicker_control.rb +9 -55
- data/lib/ruflet_ui/ruflet/ui/controls/materials/daterangepicker_control.rb +8 -58
- data/lib/ruflet_ui/ruflet/ui/controls/materials/divider_control.rb +0 -9
- data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdown_control.rb +8 -93
- data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownm2_control.rb +9 -103
- data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownoption_control.rb +0 -2
- data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanel_control.rb +7 -51
- data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanellist_control.rb +46 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/expansiontile_control.rb +69 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/filledbutton_control.rb +54 -2
- data/lib/ruflet_ui/ruflet/ui/controls/materials/fillediconbutton_control.rb +67 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonalbutton_control.rb +54 -2
- data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonaliconbutton_control.rb +67 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/floatingactionbutton_control.rb +7 -69
- data/lib/ruflet_ui/ruflet/ui/controls/materials/iconbutton_control.rb +8 -68
- data/lib/ruflet_ui/ruflet/ui/controls/materials/listtile_control.rb +8 -74
- data/lib/ruflet_ui/ruflet/ui/controls/materials/map_control.rb +99 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/menuitembutton_control.rb +8 -56
- data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbar_control.rb +7 -59
- data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawer_control.rb +0 -7
- data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationrail_control.rb +7 -73
- data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinedbutton_control.rb +51 -2
- data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinediconbutton_control.rb +67 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenubutton_control.rb +8 -61
- data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenuitem_control.rb +0 -6
- data/lib/ruflet_ui/ruflet/ui/controls/materials/progressbar_control.rb +8 -56
- data/lib/ruflet_ui/ruflet/ui/controls/materials/progressring_control.rb +8 -56
- data/lib/ruflet_ui/ruflet/ui/controls/materials/radio_control.rb +9 -54
- data/lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb +7 -62
- data/lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb +9 -64
- data/lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb +5 -26
- data/lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb +80 -11
- data/lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb +0 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/segmentedbutton_control.rb +7 -64
- data/lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb +1 -3
- data/lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb +9 -60
- data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb +37 -4
- data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbaraction_control.rb +38 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_control.rb +25 -0
- data/lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb +52 -7
- data/lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb +9 -61
- data/lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb +0 -7
- data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbar_control.rb +8 -64
- data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbarview_control.rb +43 -5
- data/lib/ruflet_ui/ruflet/ui/controls/materials/tabs_control.rb +7 -47
- data/lib/ruflet_ui/ruflet/ui/controls/materials/textbutton_control.rb +51 -2
- data/lib/ruflet_ui/ruflet/ui/controls/materials/textfield_control.rb +9 -132
- data/lib/ruflet_ui/ruflet/ui/controls/materials/timepicker_control.rb +10 -34
- data/lib/ruflet_ui/ruflet/ui/controls/materials/verticaldivider_control.rb +0 -9
- data/lib/ruflet_ui/ruflet/ui/controls/materials/video_control.rb +9 -42
- data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +3 -4
- data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +13 -28
- data/lib/ruflet_ui/ruflet/ui/controls/shared/animatedswitcher_control.rb +46 -4
- data/lib/ruflet_ui/ruflet/ui/controls/shared/arc_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/browsercontextmenu_control.rb +1 -12
- data/lib/ruflet_ui/ruflet/ui/controls/shared/canvas_control.rb +8 -54
- data/lib/ruflet_ui/ruflet/ui/controls/shared/circle_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/column_control.rb +9 -51
- data/lib/ruflet_ui/ruflet/ui/controls/shared/dismissible_control.rb +52 -12
- data/lib/ruflet_ui/ruflet/ui/controls/shared/draggable_control.rb +1 -7
- data/lib/ruflet_ui/ruflet/ui/controls/shared/dragtarget_control.rb +1 -3
- data/lib/ruflet_ui/ruflet/ui/controls/shared/fletapp_control.rb +9 -46
- data/lib/ruflet_ui/ruflet/ui/controls/shared/gesturedetector_control.rb +9 -113
- data/lib/ruflet_ui/ruflet/ui/controls/shared/gridview_control.rb +57 -13
- data/lib/ruflet_ui/ruflet/ui/controls/shared/hero_control.rb +9 -40
- data/lib/ruflet_ui/ruflet/ui/controls/shared/icon_control.rb +9 -48
- data/lib/ruflet_ui/ruflet/ui/controls/shared/interactiveviewer_control.rb +9 -53
- data/lib/ruflet_ui/ruflet/ui/controls/shared/line_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/listview_control.rb +57 -11
- data/lib/ruflet_ui/ruflet/ui/controls/shared/markdown_control.rb +9 -54
- data/lib/ruflet_ui/ruflet/ui/controls/shared/oval_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/page_control.rb +9 -58
- data/lib/ruflet_ui/ruflet/ui/controls/shared/pagelet_control.rb +9 -48
- data/lib/ruflet_ui/ruflet/ui/controls/shared/pageview_control.rb +50 -4
- data/lib/ruflet_ui/ruflet/ui/controls/shared/path_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/placeholder_control.rb +44 -9
- data/lib/ruflet_ui/ruflet/ui/controls/shared/points_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/rect_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/reorderabledraghandle_control.rb +43 -4
- data/lib/ruflet_ui/ruflet/ui/controls/shared/responsiverow_control.rb +47 -15
- data/lib/ruflet_ui/ruflet/ui/controls/shared/rotatedbox_control.rb +97 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/row_control.rb +9 -51
- data/lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb +5 -0
- data/lib/ruflet_ui/ruflet/ui/controls/shared/safearea_control.rb +48 -4
- data/lib/ruflet_ui/ruflet/ui/{services/ruflet → controls/shared}/screenshot_control.rb +18 -2
- data/lib/ruflet_ui/ruflet/ui/controls/shared/semantics_control.rb +65 -1
- data/lib/ruflet_ui/ruflet/ui/controls/shared/shadermask_control.rb +9 -41
- data/lib/ruflet_ui/ruflet/ui/controls/shared/shimmer_control.rb +9 -44
- data/lib/ruflet_ui/ruflet/ui/controls/shared/stack_control.rb +44 -1
- data/lib/ruflet_ui/ruflet/ui/controls/shared/text_control.rb +9 -35
- data/lib/ruflet_ui/ruflet/ui/controls/shared/transparentpointer_control.rb +9 -38
- data/lib/ruflet_ui/ruflet/ui/controls/shared/view_control.rb +63 -1
- data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +40 -66
- data/lib/ruflet_ui/ruflet/ui/controls/shared/windowdragarea_control.rb +45 -4
- data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +62 -18
- data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +22 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/accelerometer_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/audio_recorder_control.rb +14 -36
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/barometer_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/battery_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/camera_control.rb +9 -42
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/clipboard_control.rb +31 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/connectivity_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/filepicker_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/flashlight_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/geolocator_control.rb +14 -30
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/gyroscope_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/hapticfeedback_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/magnetometer_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/permissionhandler_control.rb +5 -20
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenbrightness_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/securestorage_control.rb +22 -49
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/semanticsservice_control.rb +20 -15
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/shakedetector_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/share_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/sharedpreferences_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/storagepaths_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/urllauncher_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/useraccelerometer_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/wakelock_control.rb +1 -0
- data/lib/ruflet_ui/ruflet/ui/services/ruflet_services.rb +0 -7
- data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +25 -2
- data/lib/ruflet_ui.rb +12 -10
- metadata +7 -8
- data/lib/ruflet_ui/ruflet/types/animation.rb +0 -110
- data/lib/ruflet_ui/ruflet/ui/controls/materials/codeeditor_control.rb +0 -93
- data/lib/ruflet_ui/ruflet/ui/controls/materials/map_controls.rb +0 -311
- data/lib/ruflet_ui/ruflet/ui/controls/materials/rive_control.rb +0 -87
- data/lib/ruflet_ui/ruflet/ui/controls/materials/spinkit_controls.rb +0 -109
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/tester_control.rb +0 -126
|
@@ -9,11 +9,21 @@ require_relative "icons/material_icon_lookup"
|
|
|
9
9
|
require_relative "icons/cupertino_icon_lookup"
|
|
10
10
|
require "set"
|
|
11
11
|
require "cgi"
|
|
12
|
+
require "uri"
|
|
12
13
|
require "thread"
|
|
13
14
|
require "timeout"
|
|
14
15
|
|
|
15
16
|
module Ruflet
|
|
16
17
|
class Page
|
|
18
|
+
# Older and embedded clients may omit this capability from registration.
|
|
19
|
+
# Ruflet applications should still be able to branch on `page.web` just as
|
|
20
|
+
# they do with a full desktop client.
|
|
21
|
+
def web
|
|
22
|
+
!!(@client_details && @client_details["web"])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias web? web
|
|
26
|
+
|
|
17
27
|
class SharedPreferencesService
|
|
18
28
|
def initialize(page)
|
|
19
29
|
@page = page
|
|
@@ -139,15 +149,16 @@ module Ruflet
|
|
|
139
149
|
end
|
|
140
150
|
end
|
|
141
151
|
|
|
142
|
-
PAGE_PROP_KEYS = %w[dark_theme fonts route rtl show_semantics_debugger theme theme_mode title vertical_alignment horizontal_alignment scroll].freeze
|
|
152
|
+
PAGE_PROP_KEYS = %w[dark_theme fonts route rtl show_semantics_debugger theme theme_mode theme_animation_style title vertical_alignment horizontal_alignment scroll].freeze
|
|
143
153
|
DIALOG_PROP_KEYS = %w[dialog snack_bar bottom_sheet].freeze
|
|
154
|
+
PAGE_ADD_RESERVED_KEYS = %i[appbar bottom_appbar floating_action_button navigation_bar dialog snack_bar bottom_sheet].freeze
|
|
144
155
|
WIDGET_HELPER_METHODS = (
|
|
145
156
|
Ruflet::UI::MaterialControlMethods.instance_methods(false) +
|
|
146
157
|
Ruflet::UI::CupertinoControlMethods.instance_methods(false) +
|
|
147
158
|
%i[control widget]
|
|
148
|
-
).map(&:to_s).
|
|
159
|
+
).map(&:to_s).uniq.freeze
|
|
149
160
|
|
|
150
|
-
attr_reader :session_id, :client_details, :views
|
|
161
|
+
attr_reader :session_id, :client_details, :views, :window
|
|
151
162
|
|
|
152
163
|
def initialize(session_id:, client_details:, sender:)
|
|
153
164
|
@session_id = session_id
|
|
@@ -167,6 +178,8 @@ module Ruflet
|
|
|
167
178
|
@page_event_handlers = {}
|
|
168
179
|
@view_props = {}
|
|
169
180
|
@page_props = { "route" => (client_details["route"] || "/") }
|
|
181
|
+
@window = build_client_window(client_details["window"])
|
|
182
|
+
@page_props["window"] = @window
|
|
170
183
|
@overlay_container = Ruflet::Control.new(
|
|
171
184
|
type: "overlay",
|
|
172
185
|
id: "_overlay",
|
|
@@ -183,7 +196,6 @@ module Ruflet
|
|
|
183
196
|
id: "_dialogs",
|
|
184
197
|
controls: []
|
|
185
198
|
)
|
|
186
|
-
@mounted_services = []
|
|
187
199
|
@invoke_waiters = {}
|
|
188
200
|
@invoke_callbacks = {}
|
|
189
201
|
@invoke_waiters_mutex = Mutex.new
|
|
@@ -245,64 +257,64 @@ module Ruflet
|
|
|
245
257
|
@view_props["bgcolor"] = normalize_value("bgcolor", value)
|
|
246
258
|
end
|
|
247
259
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def width
|
|
253
|
-
client_reported_prop("width")
|
|
254
|
-
end
|
|
260
|
+
def add(control)
|
|
261
|
+
if control.is_a?(Hash) && (control.keys.map(&:to_sym) & PAGE_ADD_RESERVED_KEYS).any?
|
|
262
|
+
raise ArgumentError, "Page#add accepts only controls; assign page.appbar, page.floating_action_button, dialogs, or other page properties before calling add"
|
|
263
|
+
end
|
|
255
264
|
|
|
256
|
-
|
|
257
|
-
client_reported_prop("height")
|
|
258
|
-
end
|
|
265
|
+
raise ArgumentError, "Page#add accepts exactly one control" unless control.is_a?(Control)
|
|
259
266
|
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
replace_root_controls(@root_controls + [control])
|
|
268
|
+
self
|
|
262
269
|
end
|
|
263
270
|
|
|
264
|
-
def
|
|
265
|
-
|
|
271
|
+
def controls
|
|
272
|
+
@root_controls
|
|
266
273
|
end
|
|
267
274
|
|
|
268
|
-
def
|
|
269
|
-
|
|
275
|
+
def controls=(value)
|
|
276
|
+
replace_root_controls(Array(value).flatten.compact)
|
|
277
|
+
self
|
|
270
278
|
end
|
|
271
279
|
|
|
272
|
-
def
|
|
273
|
-
|
|
280
|
+
def insert(at, *controls)
|
|
281
|
+
@root_controls.insert(at.to_i, *controls.flatten.compact)
|
|
282
|
+
send_view_patch
|
|
283
|
+
self
|
|
274
284
|
end
|
|
275
285
|
|
|
276
|
-
def
|
|
277
|
-
|
|
286
|
+
def remove(*controls)
|
|
287
|
+
controls.flatten.each { |control| @root_controls.delete(control) }
|
|
288
|
+
send_view_patch
|
|
289
|
+
self
|
|
278
290
|
end
|
|
279
291
|
|
|
280
|
-
def
|
|
281
|
-
|
|
292
|
+
def remove_at(index)
|
|
293
|
+
@root_controls.delete_at(index.to_i)
|
|
294
|
+
send_view_patch
|
|
295
|
+
self
|
|
282
296
|
end
|
|
283
297
|
|
|
284
|
-
def
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@root_controls = controls
|
|
289
|
-
|
|
290
|
-
update_view_slot("appbar", appbar)
|
|
291
|
-
update_view_slot("bottom_appbar", bottom_appbar)
|
|
292
|
-
update_view_slot("floating_action_button", floating_action_button)
|
|
293
|
-
update_view_slot("navigation_bar", navigation_bar)
|
|
294
|
-
@dialog = dialog if dialog
|
|
295
|
-
@snack_bar = snack_bar if snack_bar
|
|
296
|
-
@bottom_sheet = bottom_sheet if bottom_sheet
|
|
297
|
-
|
|
298
|
-
refresh_dialogs_container!
|
|
299
|
-
@view_props.each_value { |value| register_embedded_value(value, visited) }
|
|
298
|
+
def clean
|
|
299
|
+
replace_root_controls([])
|
|
300
|
+
self
|
|
301
|
+
end
|
|
300
302
|
|
|
301
|
-
|
|
303
|
+
def overlay
|
|
304
|
+
@overlay_container.children
|
|
305
|
+
end
|
|
302
306
|
|
|
307
|
+
def overlay=(value)
|
|
308
|
+
@overlay_container.children.replace(Array(value).flatten.compact)
|
|
309
|
+
push_overlay_update!
|
|
303
310
|
self
|
|
304
311
|
end
|
|
305
312
|
|
|
313
|
+
def get_control(id)
|
|
314
|
+
refresh_control_indexes!
|
|
315
|
+
resolve_control(id)
|
|
316
|
+
end
|
|
317
|
+
|
|
306
318
|
def views=(value)
|
|
307
319
|
@views = Array(value).compact
|
|
308
320
|
self
|
|
@@ -351,18 +363,6 @@ module Ruflet
|
|
|
351
363
|
service(:audio_recorder, **props)
|
|
352
364
|
end
|
|
353
365
|
|
|
354
|
-
def browser_context_menu(**props)
|
|
355
|
-
service(:browser_context_menu, **props)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def window(**props)
|
|
359
|
-
service(:window, **props)
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def tester(**props)
|
|
363
|
-
service(:tester, **props)
|
|
364
|
-
end
|
|
365
|
-
|
|
366
366
|
def add_service(*value)
|
|
367
367
|
@services_container.props["_services"] = services + value.flatten.compact
|
|
368
368
|
refresh_services_container!
|
|
@@ -407,6 +407,10 @@ module Ruflet
|
|
|
407
407
|
return svc
|
|
408
408
|
end
|
|
409
409
|
|
|
410
|
+
unless service_control_type?(normalized_type)
|
|
411
|
+
raise ArgumentError, "#{type} is a visual control, not a page service"
|
|
412
|
+
end
|
|
413
|
+
|
|
410
414
|
existing =
|
|
411
415
|
if id
|
|
412
416
|
services.find { |s| s.is_a?(Control) && s.id.to_s == id.to_s }
|
|
@@ -415,7 +419,14 @@ module Ruflet
|
|
|
415
419
|
s.is_a?(Control) && s.type.to_s.downcase.delete("_") == compact_type
|
|
416
420
|
end
|
|
417
421
|
end
|
|
418
|
-
|
|
422
|
+
if existing
|
|
423
|
+
unless mapped_props.empty?
|
|
424
|
+
existing.merge_props(mapped_props)
|
|
425
|
+
refresh_services_container!
|
|
426
|
+
push_services_update!
|
|
427
|
+
end
|
|
428
|
+
return existing
|
|
429
|
+
end
|
|
419
430
|
|
|
420
431
|
svc = Ruflet::UI::ControlFactory.build(type.to_s, id: id&.to_s, **mapped_props)
|
|
421
432
|
add_service(svc) unless services.include?(svc)
|
|
@@ -449,25 +460,35 @@ module Ruflet
|
|
|
449
460
|
@page_event_handlers["view_pop"] = handler
|
|
450
461
|
end
|
|
451
462
|
|
|
452
|
-
def
|
|
453
|
-
@page_event_handlers["
|
|
463
|
+
def on_views_pop_until=(handler)
|
|
464
|
+
@page_event_handlers["views_pop_until"] = handler
|
|
454
465
|
end
|
|
455
466
|
|
|
456
467
|
def on(event_name, &block)
|
|
457
|
-
|
|
468
|
+
name = event_name.to_s
|
|
469
|
+
name = name[3..-1] if name.start_with?("on_")
|
|
470
|
+
@page_event_handlers[name] = block
|
|
458
471
|
self
|
|
459
472
|
end
|
|
460
473
|
|
|
461
474
|
def mount(&block)
|
|
462
475
|
builder = WidgetBuilder.new
|
|
463
476
|
builder.instance_eval(&block)
|
|
464
|
-
|
|
477
|
+
builder.children.each { |control| add(control) }
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def appbar
|
|
481
|
+
@view_props["appbar"]
|
|
465
482
|
end
|
|
466
483
|
|
|
467
484
|
def appbar=(value)
|
|
468
485
|
@view_props["appbar"] = value
|
|
469
486
|
end
|
|
470
487
|
|
|
488
|
+
def bottom_appbar
|
|
489
|
+
@view_props["bottom_appbar"]
|
|
490
|
+
end
|
|
491
|
+
|
|
471
492
|
def bottom_appbar=(value)
|
|
472
493
|
@view_props["bottom_appbar"] = value
|
|
473
494
|
end
|
|
@@ -476,48 +497,92 @@ module Ruflet
|
|
|
476
497
|
self.bottom_appbar = value
|
|
477
498
|
end
|
|
478
499
|
|
|
500
|
+
def floating_action_button
|
|
501
|
+
@view_props["floating_action_button"]
|
|
502
|
+
end
|
|
503
|
+
|
|
479
504
|
def floating_action_button=(value)
|
|
480
505
|
@view_props["floating_action_button"] = value
|
|
481
506
|
end
|
|
482
507
|
|
|
483
|
-
def
|
|
484
|
-
@view_props["
|
|
508
|
+
def navigation_bar
|
|
509
|
+
@view_props["navigation_bar"]
|
|
485
510
|
end
|
|
486
511
|
|
|
487
|
-
def
|
|
488
|
-
@view_props["
|
|
512
|
+
def navigation_bar=(value)
|
|
513
|
+
@view_props["navigation_bar"] = value
|
|
489
514
|
end
|
|
490
515
|
|
|
491
|
-
def
|
|
492
|
-
@view_props["
|
|
516
|
+
def auto_scroll
|
|
517
|
+
@view_props["auto_scroll"]
|
|
493
518
|
end
|
|
494
519
|
|
|
495
|
-
def
|
|
496
|
-
@view_props["
|
|
520
|
+
def auto_scroll=(value)
|
|
521
|
+
@view_props["auto_scroll"] = value
|
|
497
522
|
end
|
|
498
523
|
|
|
499
|
-
def
|
|
500
|
-
|
|
524
|
+
def browser_context_menu
|
|
525
|
+
@view_props["browser_context_menu"]
|
|
526
|
+
end
|
|
501
527
|
|
|
502
|
-
|
|
503
|
-
|
|
528
|
+
def browser_context_menu=(value)
|
|
529
|
+
@view_props["browser_context_menu"] = value
|
|
504
530
|
end
|
|
505
531
|
|
|
506
|
-
def
|
|
507
|
-
|
|
508
|
-
self
|
|
532
|
+
def decoration
|
|
533
|
+
@view_props["decoration"]
|
|
509
534
|
end
|
|
510
535
|
|
|
511
|
-
def
|
|
512
|
-
|
|
536
|
+
def decoration=(value)
|
|
537
|
+
@view_props["decoration"] = value
|
|
538
|
+
end
|
|
513
539
|
|
|
514
|
-
|
|
515
|
-
|
|
540
|
+
def floating_action_button_location
|
|
541
|
+
@view_props["floating_action_button_location"]
|
|
516
542
|
end
|
|
517
543
|
|
|
518
|
-
def
|
|
519
|
-
|
|
520
|
-
|
|
544
|
+
def floating_action_button_location=(value)
|
|
545
|
+
@view_props["floating_action_button_location"] = value
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def foreground_decoration
|
|
549
|
+
@view_props["foreground_decoration"]
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def foreground_decoration=(value)
|
|
553
|
+
@view_props["foreground_decoration"] = value
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def padding
|
|
557
|
+
@view_props["padding"]
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def padding=(value)
|
|
561
|
+
@view_props["padding"] = value
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def spacing
|
|
565
|
+
@view_props["spacing"]
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def spacing=(value)
|
|
569
|
+
@view_props["spacing"] = value
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def drawer
|
|
573
|
+
@view_props["drawer"]
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def drawer=(value)
|
|
577
|
+
@view_props["drawer"] = value
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def end_drawer
|
|
581
|
+
@view_props["end_drawer"]
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def end_drawer=(value)
|
|
585
|
+
@view_props["end_drawer"] = value
|
|
521
586
|
end
|
|
522
587
|
|
|
523
588
|
def dialog = @dialog
|
|
@@ -534,6 +599,10 @@ module Ruflet
|
|
|
534
599
|
push_dialogs_update! if @dialogs_container_mounted
|
|
535
600
|
end
|
|
536
601
|
|
|
602
|
+
def snack_bar
|
|
603
|
+
@snack_bar
|
|
604
|
+
end
|
|
605
|
+
|
|
537
606
|
def snackbar=(value)
|
|
538
607
|
self.snack_bar = value
|
|
539
608
|
end
|
|
@@ -541,14 +610,13 @@ module Ruflet
|
|
|
541
610
|
def bottom_sheet=(value)
|
|
542
611
|
@bottom_sheet = value
|
|
543
612
|
refresh_dialogs_container!
|
|
544
|
-
# Mirror dialog= / snack_bar=: once the dialogs container is mounted, push
|
|
545
|
-
# the change as an in-place patch. Without this, opening a bottom sheet
|
|
546
|
-
# after the container is already mounted (e.g. a prior dialog/snackbar)
|
|
547
|
-
# never reaches the client, since send_view_patch skips _dialogs once
|
|
548
|
-
# mounted. This is what made NativeApp's modal: sheets fail to open.
|
|
549
613
|
push_dialogs_update! if @dialogs_container_mounted
|
|
550
614
|
end
|
|
551
615
|
|
|
616
|
+
def bottom_sheet
|
|
617
|
+
@bottom_sheet
|
|
618
|
+
end
|
|
619
|
+
|
|
552
620
|
def bottomsheet=(value)
|
|
553
621
|
self.bottom_sheet = value
|
|
554
622
|
end
|
|
@@ -559,7 +627,6 @@ module Ruflet
|
|
|
559
627
|
return self if dialog_open?(dialog_control)
|
|
560
628
|
|
|
561
629
|
dialog_control.props["open"] = true
|
|
562
|
-
remove_existing_singleton_dialogs(dialog_control)
|
|
563
630
|
@dialogs << dialog_control unless @dialogs.include?(dialog_control)
|
|
564
631
|
refresh_dialogs_container!
|
|
565
632
|
send_view_patch unless @dialogs_container.wire_id
|
|
@@ -567,6 +634,75 @@ module Ruflet
|
|
|
567
634
|
self
|
|
568
635
|
end
|
|
569
636
|
|
|
637
|
+
def show_snack_bar(snack_bar_control)
|
|
638
|
+
show_dialog(snack_bar_control)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def show_snackbar(snack_bar_control)
|
|
642
|
+
show_dialog(snack_bar_control)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def show_bottom_sheet(bottom_sheet_control)
|
|
646
|
+
show_dialog(bottom_sheet_control)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def show_banner(banner_control)
|
|
650
|
+
show_dialog(banner_control)
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def close_banner(banner_control = nil)
|
|
654
|
+
close_dialog(banner_control)
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def close_dialog(dialog_control = nil)
|
|
658
|
+
target = dialog_control || latest_open_dialog
|
|
659
|
+
return nil unless target
|
|
660
|
+
|
|
661
|
+
target.props["open"] = false
|
|
662
|
+
@dialog = nil if @dialog.equal?(target)
|
|
663
|
+
@snack_bar = nil if @snack_bar.equal?(target)
|
|
664
|
+
@bottom_sheet = nil if @bottom_sheet.equal?(target)
|
|
665
|
+
refresh_dialogs_container!
|
|
666
|
+
push_dialogs_update!
|
|
667
|
+
target
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def show_drawer(timeout: 10, on_result: nil)
|
|
671
|
+
raise ArgumentError, "No drawer defined" unless drawer
|
|
672
|
+
|
|
673
|
+
invoke(:page, "show_drawer", timeout: timeout, on_result: on_result)
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def close_drawer(timeout: 10, on_result: nil)
|
|
677
|
+
invoke(:page, "close_drawer", timeout: timeout, on_result: on_result)
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
def show_end_drawer(timeout: 10, on_result: nil)
|
|
681
|
+
raise ArgumentError, "No end_drawer defined" unless end_drawer
|
|
682
|
+
|
|
683
|
+
invoke(:page, "show_end_drawer", timeout: timeout, on_result: on_result)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def close_end_drawer(timeout: 10, on_result: nil)
|
|
687
|
+
invoke(:page, "close_end_drawer", timeout: timeout, on_result: on_result)
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def scroll_to(offset: nil, delta: nil, scroll_key: nil, duration: nil, curve: nil, timeout: 10, on_result: nil)
|
|
691
|
+
invoke(
|
|
692
|
+
:page,
|
|
693
|
+
"scroll_to",
|
|
694
|
+
args: {
|
|
695
|
+
"offset" => offset,
|
|
696
|
+
"delta" => delta,
|
|
697
|
+
"scroll_key" => scroll_key,
|
|
698
|
+
"duration" => duration,
|
|
699
|
+
"curve" => curve
|
|
700
|
+
},
|
|
701
|
+
timeout: timeout,
|
|
702
|
+
on_result: on_result
|
|
703
|
+
)
|
|
704
|
+
end
|
|
705
|
+
|
|
570
706
|
def invoke(control_or_id, method_name, args: nil, timeout: 10, on_result: nil)
|
|
571
707
|
control_id =
|
|
572
708
|
if page_control_target?(control_or_id)
|
|
@@ -580,7 +716,7 @@ module Ruflet
|
|
|
580
716
|
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
581
717
|
if on_result.respond_to?(:call)
|
|
582
718
|
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
583
|
-
|
|
719
|
+
unless timeout.nil?
|
|
584
720
|
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
585
721
|
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
586
722
|
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
@@ -729,167 +865,6 @@ module Ruflet
|
|
|
729
865
|
upload(files, timeout: timeout, on_result: on_result)
|
|
730
866
|
end
|
|
731
867
|
|
|
732
|
-
def disable_browser_context_menu(timeout: 10, on_result: nil)
|
|
733
|
-
invoke_browser_context_menu("disable_menu", timeout: timeout, on_result: on_result)
|
|
734
|
-
end
|
|
735
|
-
|
|
736
|
-
def enable_browser_context_menu(timeout: 10, on_result: nil)
|
|
737
|
-
invoke_browser_context_menu("enable_menu", timeout: timeout, on_result: on_result)
|
|
738
|
-
end
|
|
739
|
-
|
|
740
|
-
def wait_until_ready_to_show(timeout: 10, on_result: nil)
|
|
741
|
-
invoke_window("wait_until_ready_to_show", timeout: timeout, on_result: on_result)
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
def window_to_front(timeout: 10, on_result: nil)
|
|
745
|
-
invoke_window("to_front", timeout: timeout, on_result: on_result)
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
def center_window(timeout: 10, on_result: nil)
|
|
749
|
-
invoke_window("center", timeout: timeout, on_result: on_result)
|
|
750
|
-
end
|
|
751
|
-
|
|
752
|
-
def close_window(timeout: 10, on_result: nil)
|
|
753
|
-
invoke_window("close", timeout: timeout, on_result: on_result)
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
def destroy_window(timeout: 10, on_result: nil)
|
|
757
|
-
invoke_window("destroy", timeout: timeout, on_result: on_result)
|
|
758
|
-
end
|
|
759
|
-
|
|
760
|
-
def start_window_dragging(timeout: 10, on_result: nil)
|
|
761
|
-
invoke_window("start_dragging", timeout: timeout, on_result: on_result)
|
|
762
|
-
end
|
|
763
|
-
|
|
764
|
-
def start_window_resizing(edge, timeout: 10, on_result: nil)
|
|
765
|
-
invoke_window(
|
|
766
|
-
"start_resizing",
|
|
767
|
-
args: { "edge" => normalize_service_value(edge) },
|
|
768
|
-
timeout: timeout,
|
|
769
|
-
on_result: on_result
|
|
770
|
-
)
|
|
771
|
-
end
|
|
772
|
-
|
|
773
|
-
def tester_pump(options = nil, duration: nil, timeout: 10, on_result: nil)
|
|
774
|
-
duration = options[:duration] || options["duration"] if options.is_a?(Hash) && duration.nil?
|
|
775
|
-
invoke_tester("pump", args: compact_service_args("duration" => duration), timeout: timeout, on_result: on_result)
|
|
776
|
-
end
|
|
777
|
-
|
|
778
|
-
def tester_pump_and_settle(options = nil, duration: nil, timeout: 10, on_result: nil)
|
|
779
|
-
duration = options[:duration] || options["duration"] if options.is_a?(Hash) && duration.nil?
|
|
780
|
-
invoke_tester("pump_and_settle", args: compact_service_args("duration" => duration), timeout: timeout, on_result: on_result)
|
|
781
|
-
end
|
|
782
|
-
|
|
783
|
-
def find_by_text(text, timeout: 10, on_result: nil)
|
|
784
|
-
invoke_tester("find_by_text", args: { "text" => text }, timeout: timeout, on_result: on_result)
|
|
785
|
-
end
|
|
786
|
-
|
|
787
|
-
def find_by_text_containing(pattern, timeout: 10, on_result: nil)
|
|
788
|
-
invoke_tester("find_by_text_containing", args: { "pattern" => pattern }, timeout: timeout, on_result: on_result)
|
|
789
|
-
end
|
|
790
|
-
|
|
791
|
-
def find_by_key(key, timeout: 10, on_result: nil)
|
|
792
|
-
invoke_tester("find_by_key", args: { "key" => key }, timeout: timeout, on_result: on_result)
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
def find_by_tooltip(value, timeout: 10, on_result: nil)
|
|
796
|
-
invoke_tester("find_by_tooltip", args: { "value" => value }, timeout: timeout, on_result: on_result)
|
|
797
|
-
end
|
|
798
|
-
|
|
799
|
-
def find_by_icon(icon, timeout: 10, on_result: nil)
|
|
800
|
-
invoke_tester("find_by_icon", args: { "icon" => normalize_service_value(icon) }, timeout: timeout, on_result: on_result)
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
def take_screenshot(name, timeout: 10, on_result: nil)
|
|
804
|
-
invoke_tester("take_screenshot", args: { "name" => name }, timeout: timeout, on_result: on_result)
|
|
805
|
-
end
|
|
806
|
-
|
|
807
|
-
def tap(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
808
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
809
|
-
invoke_tester_finder("tap", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
def mouse_click(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
813
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
814
|
-
invoke_tester_finder("mouse_click", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
815
|
-
end
|
|
816
|
-
|
|
817
|
-
def mouse_double_click(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
818
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
819
|
-
invoke_tester_finder("mouse_double_click", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
820
|
-
end
|
|
821
|
-
|
|
822
|
-
def right_mouse_click(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
823
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
824
|
-
invoke_tester_finder("right_mouse_click", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
825
|
-
end
|
|
826
|
-
|
|
827
|
-
def tap_at(offset = nil, timeout: 10, on_result: nil)
|
|
828
|
-
invoke_tester_at("tap_at", offset, timeout: timeout, on_result: on_result)
|
|
829
|
-
end
|
|
830
|
-
|
|
831
|
-
def mouse_click_at(offset = nil, timeout: 10, on_result: nil)
|
|
832
|
-
invoke_tester_at("mouse_click_at", offset, timeout: timeout, on_result: on_result)
|
|
833
|
-
end
|
|
834
|
-
|
|
835
|
-
def mouse_double_click_at(offset = nil, timeout: 10, on_result: nil)
|
|
836
|
-
invoke_tester_at("mouse_double_click_at", offset, timeout: timeout, on_result: on_result)
|
|
837
|
-
end
|
|
838
|
-
|
|
839
|
-
def right_mouse_click_at(offset = nil, timeout: 10, on_result: nil)
|
|
840
|
-
invoke_tester_at("right_mouse_click_at", offset, timeout: timeout, on_result: on_result)
|
|
841
|
-
end
|
|
842
|
-
|
|
843
|
-
def drag(finder_id, offset, finder_index: nil, timeout: 10, on_result: nil)
|
|
844
|
-
invoke_tester(
|
|
845
|
-
"drag",
|
|
846
|
-
args: compact_service_args(
|
|
847
|
-
"finder_id" => finder_id,
|
|
848
|
-
"finder_index" => finder_index,
|
|
849
|
-
"offset" => offset
|
|
850
|
-
),
|
|
851
|
-
timeout: timeout,
|
|
852
|
-
on_result: on_result
|
|
853
|
-
)
|
|
854
|
-
end
|
|
855
|
-
|
|
856
|
-
def drag_from(start, offset, timeout: 10, on_result: nil)
|
|
857
|
-
invoke_tester(
|
|
858
|
-
"drag_from",
|
|
859
|
-
args: compact_service_args("start" => start, "offset" => offset),
|
|
860
|
-
timeout: timeout,
|
|
861
|
-
on_result: on_result
|
|
862
|
-
)
|
|
863
|
-
end
|
|
864
|
-
|
|
865
|
-
def long_press(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
866
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
867
|
-
invoke_tester_finder("long_press", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
def enter_text(finder_id, text, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
871
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
872
|
-
invoke_tester(
|
|
873
|
-
"enter_text",
|
|
874
|
-
args: compact_service_args(
|
|
875
|
-
"finder_id" => finder_id,
|
|
876
|
-
"finder_index" => finder_index,
|
|
877
|
-
"text" => text
|
|
878
|
-
),
|
|
879
|
-
timeout: timeout,
|
|
880
|
-
on_result: on_result
|
|
881
|
-
)
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
def mouse_hover(finder_id = nil, options = nil, finder_index: nil, timeout: 10, on_result: nil)
|
|
885
|
-
finder_index = options[:finder_index] || options["finder_index"] if options.is_a?(Hash) && finder_index.nil?
|
|
886
|
-
invoke_tester_finder("mouse_hover", finder_id, finder_index: finder_index, timeout: timeout, on_result: on_result)
|
|
887
|
-
end
|
|
888
|
-
|
|
889
|
-
def tester_teardown(timeout: 10, on_result: nil)
|
|
890
|
-
invoke_tester("teardown", timeout: timeout, on_result: on_result)
|
|
891
|
-
end
|
|
892
|
-
|
|
893
868
|
def heavy_impact(timeout: 10, on_result: nil)
|
|
894
869
|
invoke_haptic_feedback("heavy_impact", timeout: timeout, on_result: on_result)
|
|
895
870
|
end
|
|
@@ -989,6 +964,18 @@ module Ruflet
|
|
|
989
964
|
service(:barometer, **props)
|
|
990
965
|
end
|
|
991
966
|
|
|
967
|
+
def geolocator(**props)
|
|
968
|
+
service(:geolocator, **props)
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def permission_handler(**props)
|
|
972
|
+
service(:permission_handler, **props)
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
def secure_storage(**props)
|
|
976
|
+
service(:secure_storage, **props)
|
|
977
|
+
end
|
|
978
|
+
|
|
992
979
|
def shake_detector(**props)
|
|
993
980
|
service(:shake_detector, **props)
|
|
994
981
|
end
|
|
@@ -998,7 +985,7 @@ module Ruflet
|
|
|
998
985
|
end
|
|
999
986
|
|
|
1000
987
|
def screenshot(**props)
|
|
1001
|
-
|
|
988
|
+
Ruflet::UI::ControlFactory.build(:screenshot, **props)
|
|
1002
989
|
end
|
|
1003
990
|
|
|
1004
991
|
def battery(**props)
|
|
@@ -1037,18 +1024,6 @@ module Ruflet
|
|
|
1037
1024
|
service(:haptic_feedback, **props)
|
|
1038
1025
|
end
|
|
1039
1026
|
|
|
1040
|
-
def geolocator(**props)
|
|
1041
|
-
service(:geolocator, **props)
|
|
1042
|
-
end
|
|
1043
|
-
|
|
1044
|
-
def permission_handler(**props)
|
|
1045
|
-
service(:permission_handler, **props)
|
|
1046
|
-
end
|
|
1047
|
-
|
|
1048
|
-
def secure_storage(**props)
|
|
1049
|
-
service(:secure_storage, **props)
|
|
1050
|
-
end
|
|
1051
|
-
|
|
1052
1027
|
def get_application_cache_directory(timeout: nil, on_result: nil)
|
|
1053
1028
|
invoke_storage_paths("get_application_cache_directory", timeout: timeout, on_result: on_result)
|
|
1054
1029
|
end
|
|
@@ -1193,27 +1168,7 @@ module Ruflet
|
|
|
1193
1168
|
end
|
|
1194
1169
|
|
|
1195
1170
|
def pop_dialog
|
|
1196
|
-
|
|
1197
|
-
return nil unless dialog_control
|
|
1198
|
-
|
|
1199
|
-
dialog_control.props["open"] = false
|
|
1200
|
-
update(dialog_control, open: false)
|
|
1201
|
-
dialog_control
|
|
1202
|
-
end
|
|
1203
|
-
|
|
1204
|
-
def close_dialog(dialog_control)
|
|
1205
|
-
return self unless dialog_control
|
|
1206
|
-
|
|
1207
|
-
dialog_control.props["open"] = false
|
|
1208
|
-
@dialog = nil if @dialog.equal?(dialog_control)
|
|
1209
|
-
remove_dialog_tracking(dialog_control)
|
|
1210
|
-
refresh_dialogs_container!
|
|
1211
|
-
# Patch the dialogs container in place. Forcing a full view re-render
|
|
1212
|
-
# here would remount the whole overlay — fatal while another dialog
|
|
1213
|
-
# (e.g. the form behind a nested picker) is still open. The empty case
|
|
1214
|
-
# is handled inside push_dialogs_update!.
|
|
1215
|
-
push_dialogs_update!
|
|
1216
|
-
self
|
|
1171
|
+
close_dialog
|
|
1217
1172
|
end
|
|
1218
1173
|
|
|
1219
1174
|
def update(control_or_id = nil, **props)
|
|
@@ -1243,6 +1198,8 @@ module Ruflet
|
|
|
1243
1198
|
patch["content"] = patch.delete("text")
|
|
1244
1199
|
end
|
|
1245
1200
|
|
|
1201
|
+
patch.each { |key, value| control.props[key] = value }
|
|
1202
|
+
|
|
1246
1203
|
# Keep runtime control tree aligned with incremental patches.
|
|
1247
1204
|
if patch.key?("controls")
|
|
1248
1205
|
control.children.clear
|
|
@@ -1251,7 +1208,6 @@ module Ruflet
|
|
|
1251
1208
|
|
|
1252
1209
|
visited = Set.new
|
|
1253
1210
|
patch.each_value { |value| register_embedded_value(value, visited) }
|
|
1254
|
-
patch.each { |k, v| control.props[k] = v }
|
|
1255
1211
|
|
|
1256
1212
|
patch_ops = patch.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
|
|
1257
1213
|
|
|
@@ -1263,6 +1219,10 @@ module Ruflet
|
|
|
1263
1219
|
self
|
|
1264
1220
|
end
|
|
1265
1221
|
|
|
1222
|
+
def schedule_update
|
|
1223
|
+
update
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1266
1226
|
def patch_page(control_id, **props)
|
|
1267
1227
|
update(control_id, **props)
|
|
1268
1228
|
end
|
|
@@ -1274,6 +1234,8 @@ module Ruflet
|
|
|
1274
1234
|
patch = normalize_props(props || {})
|
|
1275
1235
|
patch.each { |k, v| control.props[k] = v }
|
|
1276
1236
|
|
|
1237
|
+
remove_dialog_tracking(control) if patch.key?("open") && patch["open"] == false
|
|
1238
|
+
|
|
1277
1239
|
self
|
|
1278
1240
|
end
|
|
1279
1241
|
|
|
@@ -1281,17 +1243,8 @@ module Ruflet
|
|
|
1281
1243
|
if page_control_target?(target)
|
|
1282
1244
|
if name.to_s == "route_change"
|
|
1283
1245
|
route_from_event = extract_route(data)
|
|
1284
|
-
|
|
1285
|
-
# Navigating away must dismiss them, or they ghost onto the next
|
|
1286
|
-
# view — the picker that "reappears after going home".
|
|
1287
|
-
dismiss_tracked_dialogs! if route_from_event && route_from_event != @page_props["route"]
|
|
1246
|
+
return if route_from_event && route_from_event == @page_props["route"]
|
|
1288
1247
|
@page_props["route"] = route_from_event if route_from_event
|
|
1289
|
-
elsif name.to_s == "resize"
|
|
1290
|
-
# The client reports the live page size via the "resize" event. Store
|
|
1291
|
-
# it so `page.width`/`page.height` reflect the real viewport — without
|
|
1292
|
-
# this, responsive layouts collapse on clients (e.g. embedded/iOS)
|
|
1293
|
-
# that don't know their size at the initial handshake.
|
|
1294
|
-
store_reported_page_size(data)
|
|
1295
1248
|
end
|
|
1296
1249
|
dispatch_page_event(name: name, data: data)
|
|
1297
1250
|
return
|
|
@@ -1300,19 +1253,13 @@ module Ruflet
|
|
|
1300
1253
|
control = @wire_index[target.to_i] || @control_index[target.to_s]
|
|
1301
1254
|
return unless control
|
|
1302
1255
|
|
|
1303
|
-
event =
|
|
1256
|
+
event = Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
1304
1257
|
apply_event_value_to_control(control, event) if %w[change select select_change].include?(name.to_s)
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
mark_picker_dialog_closed(control, name)
|
|
1309
|
-
if dialog_close_event?(control, name) && remove_dialog_tracking(control)
|
|
1310
|
-
# Patch the container in place; never force a full view re-render that
|
|
1311
|
-
# would remount a still-open parent dialog (the nested-picker case).
|
|
1258
|
+
control.emit(name, event)
|
|
1259
|
+
|
|
1260
|
+
if name.to_s == "dismiss" && remove_dialog_tracking(control)
|
|
1312
1261
|
push_dialogs_update!
|
|
1313
1262
|
end
|
|
1314
|
-
|
|
1315
|
-
control.emit(name, event)
|
|
1316
1263
|
end
|
|
1317
1264
|
|
|
1318
1265
|
def method_missing(name, *args, &block)
|
|
@@ -1330,6 +1277,9 @@ module Ruflet
|
|
|
1330
1277
|
if args.empty? && !block
|
|
1331
1278
|
return @page_props[method_name] if @page_props.key?(method_name)
|
|
1332
1279
|
return @view_props[method_name] if @view_props.key?(method_name)
|
|
1280
|
+
# Client-reported page properties (width, height, platform,
|
|
1281
|
+
# platform_brightness, media) arrive in the register payload.
|
|
1282
|
+
return @client_details[method_name] if @client_details.respond_to?(:key?) && @client_details.key?(method_name)
|
|
1333
1283
|
return instance_variable_get("@#{method_name}") if DIALOG_PROP_KEYS.include?(method_name)
|
|
1334
1284
|
end
|
|
1335
1285
|
|
|
@@ -1354,14 +1304,20 @@ module Ruflet
|
|
|
1354
1304
|
|
|
1355
1305
|
private
|
|
1356
1306
|
|
|
1357
|
-
def
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1307
|
+
def build_client_window(snapshot)
|
|
1308
|
+
properties = snapshot.is_a?(Hash) ? snapshot : {}
|
|
1309
|
+
allowed = Ruflet::UI::Controls::RufletComponents::WindowControl::KEYWORDS
|
|
1310
|
+
props = properties.each_with_object({}) do |(key, value), result|
|
|
1311
|
+
name = key.to_s
|
|
1312
|
+
result[name.to_sym] = value if allowed.include?(name.to_sym)
|
|
1313
|
+
end
|
|
1314
|
+
control = Ruflet::UI::Controls::RufletComponents::WindowControl.new(
|
|
1315
|
+
id: "_window",
|
|
1316
|
+
**props
|
|
1317
|
+
)
|
|
1318
|
+
control.wire_id = (properties["_i"] || properties[:_i] || 2).to_i
|
|
1319
|
+
control.runtime_page = self
|
|
1320
|
+
control
|
|
1365
1321
|
end
|
|
1366
1322
|
|
|
1367
1323
|
def invoke_and_wait(control_or_id, method_name, args: nil, timeout: 10)
|
|
@@ -1447,6 +1403,14 @@ module Ruflet
|
|
|
1447
1403
|
type.to_s.delete("_") == "camera"
|
|
1448
1404
|
end
|
|
1449
1405
|
|
|
1406
|
+
def service_control_type?(type)
|
|
1407
|
+
normalized = type.to_s.downcase
|
|
1408
|
+
compact = normalized.delete("_")
|
|
1409
|
+
compact == "audio" ||
|
|
1410
|
+
Ruflet::UI::Services::RufletServices::CLASS_MAP.key?(normalized) ||
|
|
1411
|
+
Ruflet::UI::Services::RufletServices::CLASS_MAP.key?(compact)
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1450
1414
|
def text_maps_to_content?(control, patch)
|
|
1451
1415
|
patch.key?("text") && control.type.end_with?("button")
|
|
1452
1416
|
end
|
|
@@ -1461,12 +1425,14 @@ module Ruflet
|
|
|
1461
1425
|
@sender.call(action, payload)
|
|
1462
1426
|
end
|
|
1463
1427
|
|
|
1464
|
-
def
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1428
|
+
def replace_root_controls(controls)
|
|
1429
|
+
visited = Set.new
|
|
1430
|
+
controls.each { |control| register_control_tree(control, visited) }
|
|
1431
|
+
@root_controls = controls
|
|
1432
|
+
|
|
1433
|
+
refresh_dialogs_container!
|
|
1434
|
+
@view_props.each_value { |value| register_embedded_value(value, visited) }
|
|
1435
|
+
send_view_patch
|
|
1470
1436
|
end
|
|
1471
1437
|
|
|
1472
1438
|
def send_view_patch
|
|
@@ -1478,8 +1444,8 @@ module Ruflet
|
|
|
1478
1444
|
"id" => 1,
|
|
1479
1445
|
"patch" => [
|
|
1480
1446
|
[0],
|
|
1481
|
-
|
|
1482
|
-
|
|
1447
|
+
[0, 0, "views", view_patches],
|
|
1448
|
+
*page_patch_ops
|
|
1483
1449
|
]
|
|
1484
1450
|
})
|
|
1485
1451
|
@overlay_container_mounted = true if @overlay_container.wire_id
|
|
@@ -1574,7 +1540,6 @@ module Ruflet
|
|
|
1574
1540
|
raise ArgumentError, "page #{key} must use an icon name string, not #{value.inspect}"
|
|
1575
1541
|
end
|
|
1576
1542
|
|
|
1577
|
-
value = Ruflet::Colors.normalize_property(key, value)
|
|
1578
1543
|
return value.value if value.is_a?(Ruflet::IconData)
|
|
1579
1544
|
value.is_a?(Symbol) ? value.to_s : value
|
|
1580
1545
|
end
|
|
@@ -1600,14 +1565,9 @@ module Ruflet
|
|
|
1600
1565
|
query_string = route_value.to_s.split("?", 2)[1].to_s
|
|
1601
1566
|
return {} if query_string.empty?
|
|
1602
1567
|
|
|
1603
|
-
query_string.
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
key, value = pair.split("=", 2)
|
|
1607
|
-
key = CGI.unescape(key.to_s.tr("+", " "))
|
|
1608
|
-
value = CGI.unescape(value.to_s.tr("+", " "))
|
|
1609
|
-
previous = result[key]
|
|
1610
|
-
result[key] = previous.nil? ? value : Array(previous) << value
|
|
1568
|
+
URI.decode_www_form(query_string).group_by(&:first).each_with_object({}) do |(key, pairs), result|
|
|
1569
|
+
values = pairs.map(&:last)
|
|
1570
|
+
result[key] = values.size == 1 ? values.first : values
|
|
1611
1571
|
end
|
|
1612
1572
|
end
|
|
1613
1573
|
|
|
@@ -1622,20 +1582,13 @@ module Ruflet
|
|
|
1622
1582
|
end
|
|
1623
1583
|
end
|
|
1624
1584
|
|
|
1625
|
-
def store_reported_page_size(data)
|
|
1626
|
-
return unless data.is_a?(Hash)
|
|
1627
|
-
|
|
1628
|
-
width = data["width"] || data[:width]
|
|
1629
|
-
height = data["height"] || data[:height]
|
|
1630
|
-
@page_props["width"] = width unless width.nil?
|
|
1631
|
-
@page_props["height"] = height unless height.nil?
|
|
1632
|
-
end
|
|
1633
|
-
|
|
1634
1585
|
def dispatch_page_event(name:, data:)
|
|
1635
|
-
|
|
1586
|
+
event_name = name.to_s
|
|
1587
|
+
event_name = event_name[3..-1] if event_name.start_with?("on_")
|
|
1588
|
+
handler = @page_event_handlers[event_name]
|
|
1636
1589
|
return unless handler.respond_to?(:call)
|
|
1637
1590
|
|
|
1638
|
-
event =
|
|
1591
|
+
event = Event.new(name: name.to_s, target: 1, raw_data: data, page: self, control: nil)
|
|
1639
1592
|
handler.call(event)
|
|
1640
1593
|
end
|
|
1641
1594
|
|
|
@@ -1651,8 +1604,8 @@ module Ruflet
|
|
|
1651
1604
|
end_value = range_value["end_value"] || range_value[:end_value]
|
|
1652
1605
|
control.props["start_value"] = start_value unless start_value.nil?
|
|
1653
1606
|
control.props["end_value"] = end_value unless end_value.nil?
|
|
1607
|
+
return
|
|
1654
1608
|
end
|
|
1655
|
-
return
|
|
1656
1609
|
end
|
|
1657
1610
|
|
|
1658
1611
|
return if value.nil?
|
|
@@ -1699,7 +1652,7 @@ module Ruflet
|
|
|
1699
1652
|
end
|
|
1700
1653
|
|
|
1701
1654
|
def refresh_dialogs_container!
|
|
1702
|
-
dialog_controls = (
|
|
1655
|
+
dialog_controls = (@dialogs + dialog_slots).uniq
|
|
1703
1656
|
@dialogs_container.props["controls"] = dialog_controls
|
|
1704
1657
|
@page_props["_dialogs"] = @dialogs_container
|
|
1705
1658
|
end
|
|
@@ -1708,6 +1661,19 @@ module Ruflet
|
|
|
1708
1661
|
@page_props["_overlay"] = @overlay_container
|
|
1709
1662
|
end
|
|
1710
1663
|
|
|
1664
|
+
def push_overlay_update!
|
|
1665
|
+
refresh_control_indexes!
|
|
1666
|
+
|
|
1667
|
+
if @overlay_container.wire_id
|
|
1668
|
+
send_message(Protocol::ACTIONS[:patch_control], {
|
|
1669
|
+
"id" => @overlay_container.wire_id,
|
|
1670
|
+
"patch" => [[0], [0, 0, "controls", serialize_patch_value(@overlay_container.children)]]
|
|
1671
|
+
})
|
|
1672
|
+
else
|
|
1673
|
+
send_view_patch
|
|
1674
|
+
end
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1711
1677
|
def refresh_services_container!
|
|
1712
1678
|
@page_props["_services"] = @services_container
|
|
1713
1679
|
end
|
|
@@ -1715,72 +1681,19 @@ module Ruflet
|
|
|
1715
1681
|
def push_services_update!
|
|
1716
1682
|
refresh_control_indexes!
|
|
1717
1683
|
|
|
1718
|
-
|
|
1719
|
-
# First mount: the whole list ships inside the page/view patch.
|
|
1720
|
-
send_view_patch
|
|
1721
|
-
@mounted_services = Array(@services_container.props["_services"]).dup
|
|
1722
|
-
return
|
|
1723
|
-
end
|
|
1724
|
-
|
|
1725
|
-
current = Array(@services_container.props["_services"])
|
|
1726
|
-
ops = services_patch_ops(@mounted_services, current)
|
|
1727
|
-
unless ops.empty?
|
|
1728
|
-
# Incrementally add/remove individual services. Re-sending the whole
|
|
1729
|
-
# `_services` list as full objects (a single replace op) makes the
|
|
1730
|
-
# client run Control.fromMap for EVERY service, replacing each Control
|
|
1731
|
-
# instance while ServiceRegistry only binds ids it hasn't seen — so an
|
|
1732
|
-
# already-mounted service (share, clipboard, haptic) keeps its old
|
|
1733
|
-
# binding while controlsIndex points at the fresh, listener-less
|
|
1734
|
-
# instance, and its next invoke hangs. That was the "after Copy, Share
|
|
1735
|
-
# stops working" bug. Add/remove ops leave untouched services intact.
|
|
1684
|
+
if @services_container.wire_id
|
|
1736
1685
|
send_message(Protocol::ACTIONS[:patch_control], {
|
|
1737
1686
|
"id" => @services_container.wire_id,
|
|
1738
|
-
"patch" => [[0,
|
|
1687
|
+
"patch" => [[0], [0, 0, "_services", serialize_patch_value(@services_container.props["_services"])]]
|
|
1739
1688
|
})
|
|
1689
|
+
else
|
|
1690
|
+
send_view_patch
|
|
1740
1691
|
end
|
|
1741
|
-
@mounted_services = current.dup
|
|
1742
|
-
end
|
|
1743
|
-
|
|
1744
|
-
# Diff the previously-mounted services list against the current one and emit
|
|
1745
|
-
# add (1) / remove (2) ops against the `_services` list (patch node 1).
|
|
1746
|
-
# Services are normally append-only; a moved entry is expressed as
|
|
1747
|
-
# remove + add. Existing entries are never re-serialized, so their client
|
|
1748
|
-
# Control instances (and invoke listeners) survive.
|
|
1749
|
-
def services_patch_ops(previous, current)
|
|
1750
|
-
ops = []
|
|
1751
|
-
working = previous.dup
|
|
1752
|
-
|
|
1753
|
-
(working.length - 1).downto(0) do |index|
|
|
1754
|
-
next if current.any? { |service| service.equal?(working[index]) }
|
|
1755
|
-
|
|
1756
|
-
ops << [2, 1, index]
|
|
1757
|
-
working.delete_at(index)
|
|
1758
|
-
end
|
|
1759
|
-
|
|
1760
|
-
current.each_with_index do |service, index|
|
|
1761
|
-
next if working[index]&.equal?(service)
|
|
1762
|
-
|
|
1763
|
-
existing = working.index { |candidate| candidate.equal?(service) }
|
|
1764
|
-
if existing
|
|
1765
|
-
ops << [2, 1, existing]
|
|
1766
|
-
working.delete_at(existing)
|
|
1767
|
-
end
|
|
1768
|
-
ops << [1, 1, index, serialize_patch_value(service)]
|
|
1769
|
-
working.insert(index, service)
|
|
1770
|
-
end
|
|
1771
|
-
|
|
1772
|
-
ops
|
|
1773
1692
|
end
|
|
1774
1693
|
|
|
1775
1694
|
def push_dialogs_update!
|
|
1776
1695
|
refresh_control_indexes!
|
|
1777
1696
|
|
|
1778
|
-
# Once the dialogs container is mounted, every change — opening, closing,
|
|
1779
|
-
# even down to no dialogs at all — is an in-place patch of its controls
|
|
1780
|
-
# list. Re-sending the view (or the container as a whole object) would
|
|
1781
|
-
# replace the live container instance on the Flutter side, detaching its
|
|
1782
|
-
# listeners and breaking any other dialog still open. Only the very first
|
|
1783
|
-
# dialog, before the container has a wire id, needs a view patch to mount.
|
|
1784
1697
|
if @dialogs_container.wire_id
|
|
1785
1698
|
send_message(Protocol::ACTIONS[:patch_control], {
|
|
1786
1699
|
"id" => @dialogs_container.wire_id,
|
|
@@ -1803,41 +1716,6 @@ module Ruflet
|
|
|
1803
1716
|
@dialogs.include?(dialog_control) && dialog_control.props["open"] == true
|
|
1804
1717
|
end
|
|
1805
1718
|
|
|
1806
|
-
def dialog_close_event?(control, name)
|
|
1807
|
-
name = name.to_s
|
|
1808
|
-
name == "dismiss" || (%w[change select select_change].include?(name) && @dialogs.include?(control) && control.props["open"] == false)
|
|
1809
|
-
end
|
|
1810
|
-
|
|
1811
|
-
# Picker dialogs that auto-dismiss on the client after a selection. Their
|
|
1812
|
-
# confirm sends a value event (change/select), not a close, so the server
|
|
1813
|
-
# must flip `open` to false or show_dialog's open-guard blocks reopening.
|
|
1814
|
-
PICKER_DIALOG_TYPES = %w[
|
|
1815
|
-
datepicker daterangepicker timepicker
|
|
1816
|
-
cupertinodatepicker cupertinotimerpicker
|
|
1817
|
-
].freeze
|
|
1818
|
-
|
|
1819
|
-
def picker_dialog?(control)
|
|
1820
|
-
PICKER_DIALOG_TYPES.include?(control.type.to_s.tr("_", "").downcase)
|
|
1821
|
-
end
|
|
1822
|
-
|
|
1823
|
-
def mark_picker_dialog_closed(control, name)
|
|
1824
|
-
return unless picker_dialog?(control)
|
|
1825
|
-
return unless %w[change select select_change dismiss].include?(name.to_s)
|
|
1826
|
-
|
|
1827
|
-
control.props["open"] = false
|
|
1828
|
-
end
|
|
1829
|
-
|
|
1830
|
-
# Close and untrack every dialog currently shown. Called on navigation so
|
|
1831
|
-
# a dialog opened in one view does not linger as an overlay on the next.
|
|
1832
|
-
def dismiss_tracked_dialogs!
|
|
1833
|
-
return if @dialogs.empty?
|
|
1834
|
-
|
|
1835
|
-
@dialogs.each { |dialog| dialog.props["open"] = false }
|
|
1836
|
-
@dialogs.clear
|
|
1837
|
-
refresh_dialogs_container!
|
|
1838
|
-
push_dialogs_update! if @dialogs_container_mounted
|
|
1839
|
-
end
|
|
1840
|
-
|
|
1841
1719
|
def remove_dialog_tracking(control)
|
|
1842
1720
|
return false unless @dialogs.include?(control)
|
|
1843
1721
|
|
|
@@ -1846,16 +1724,6 @@ module Ruflet
|
|
|
1846
1724
|
true
|
|
1847
1725
|
end
|
|
1848
1726
|
|
|
1849
|
-
def remove_existing_singleton_dialogs(control)
|
|
1850
|
-
return unless singleton_dialog_control?(control)
|
|
1851
|
-
|
|
1852
|
-
@dialogs.delete_if { |dialog| dialog != control && singleton_dialog_control?(dialog) }
|
|
1853
|
-
end
|
|
1854
|
-
|
|
1855
|
-
def singleton_dialog_control?(control)
|
|
1856
|
-
control.type.to_s.tr("_", "").downcase == "snackbar"
|
|
1857
|
-
end
|
|
1858
|
-
|
|
1859
1727
|
def assign_split_prop(key, value)
|
|
1860
1728
|
if key == "vertical_alignment" || key == "horizontal_alignment"
|
|
1861
1729
|
@page_props[key] = value
|
|
@@ -1902,7 +1770,6 @@ module Ruflet
|
|
|
1902
1770
|
existing = service_by_type(type)
|
|
1903
1771
|
return [existing, false] if existing
|
|
1904
1772
|
|
|
1905
|
-
# `service` already syncs via add_service -> push_services_update!.
|
|
1906
1773
|
[service(type), true]
|
|
1907
1774
|
end
|
|
1908
1775
|
|
|
@@ -1927,51 +1794,6 @@ module Ruflet
|
|
|
1927
1794
|
service(:url_launcher)
|
|
1928
1795
|
end
|
|
1929
1796
|
|
|
1930
|
-
def ensure_browser_context_menu_service
|
|
1931
|
-
service(:browser_context_menu)
|
|
1932
|
-
end
|
|
1933
|
-
|
|
1934
|
-
def invoke_browser_context_menu(method_name, timeout:, on_result:)
|
|
1935
|
-
browser_context_menu = ensure_browser_context_menu_service
|
|
1936
|
-
invoke(browser_context_menu, method_name, timeout: timeout, on_result: on_result)
|
|
1937
|
-
end
|
|
1938
|
-
|
|
1939
|
-
def ensure_window_service
|
|
1940
|
-
service(:window)
|
|
1941
|
-
end
|
|
1942
|
-
|
|
1943
|
-
def invoke_window(method_name, args: nil, timeout:, on_result:)
|
|
1944
|
-
window = ensure_window_service
|
|
1945
|
-
invoke(window, method_name, args: args, timeout: timeout, on_result: on_result)
|
|
1946
|
-
end
|
|
1947
|
-
|
|
1948
|
-
def ensure_tester_service
|
|
1949
|
-
service(:tester)
|
|
1950
|
-
end
|
|
1951
|
-
|
|
1952
|
-
def invoke_tester(method_name, args: nil, timeout:, on_result:)
|
|
1953
|
-
tester = ensure_tester_service
|
|
1954
|
-
invoke(tester, method_name, args: args, timeout: timeout, on_result: on_result)
|
|
1955
|
-
end
|
|
1956
|
-
|
|
1957
|
-
def invoke_tester_finder(method_name, finder_id, finder_index:, timeout:, on_result:)
|
|
1958
|
-
invoke_tester(
|
|
1959
|
-
method_name,
|
|
1960
|
-
args: compact_service_args("finder_id" => finder_id, "finder_index" => finder_index),
|
|
1961
|
-
timeout: timeout,
|
|
1962
|
-
on_result: on_result
|
|
1963
|
-
)
|
|
1964
|
-
end
|
|
1965
|
-
|
|
1966
|
-
def invoke_tester_at(method_name, offset, timeout:, on_result:)
|
|
1967
|
-
invoke_tester(
|
|
1968
|
-
method_name,
|
|
1969
|
-
args: compact_service_args("offset" => offset),
|
|
1970
|
-
timeout: timeout,
|
|
1971
|
-
on_result: on_result
|
|
1972
|
-
)
|
|
1973
|
-
end
|
|
1974
|
-
|
|
1975
1797
|
def ensure_haptic_feedback_service
|
|
1976
1798
|
service(:haptic_feedback)
|
|
1977
1799
|
end
|
|
@@ -1981,38 +1803,6 @@ module Ruflet
|
|
|
1981
1803
|
invoke(haptic_feedback, method_name, timeout: timeout, on_result: on_result)
|
|
1982
1804
|
end
|
|
1983
1805
|
|
|
1984
|
-
def invoke_current_view(method_name, timeout:, on_result:)
|
|
1985
|
-
target_id = @views.last&.wire_id || @view_id
|
|
1986
|
-
invoke_control_id(target_id, method_name, timeout: timeout, on_result: on_result)
|
|
1987
|
-
end
|
|
1988
|
-
|
|
1989
|
-
def invoke_control_id(control_id, method_name, args: nil, timeout: 10, on_result: nil)
|
|
1990
|
-
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
1991
|
-
if on_result.respond_to?(:call)
|
|
1992
|
-
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
1993
|
-
if embedded_async_timeout_available? && !timeout.nil?
|
|
1994
|
-
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
1995
|
-
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
1996
|
-
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
1997
|
-
callback&.call(nil, "execution expired")
|
|
1998
|
-
rescue StandardError => e
|
|
1999
|
-
Kernel.warn("invoke timeout callback error: #{e.class}: #{e.message}")
|
|
2000
|
-
end
|
|
2001
|
-
end
|
|
2002
|
-
end
|
|
2003
|
-
|
|
2004
|
-
payload = {
|
|
2005
|
-
"control_id" => control_id,
|
|
2006
|
-
"call_id" => call_id,
|
|
2007
|
-
"name" => method_name.to_s,
|
|
2008
|
-
"args" => args
|
|
2009
|
-
}
|
|
2010
|
-
payload["timeout"] = timeout unless timeout.nil?
|
|
2011
|
-
send_message(Protocol::ACTIONS[:invoke_control_method], payload)
|
|
2012
|
-
|
|
2013
|
-
call_id
|
|
2014
|
-
end
|
|
2015
|
-
|
|
2016
1806
|
def ensure_connectivity_service
|
|
2017
1807
|
service_with_created(:connectivity)
|
|
2018
1808
|
end
|