ruflet 0.0.7 → 0.0.9
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/bin/ruflet +6 -0
- data/lib/ruflet/cli/build_command.rb +372 -0
- data/lib/ruflet/cli/extra_command.rb +146 -0
- data/lib/ruflet/cli/flutter_sdk.rb +359 -0
- data/lib/ruflet/cli/new_command.rb +221 -0
- data/lib/ruflet/cli/run_command.rb +699 -0
- data/lib/ruflet/cli/templates.rb +68 -0
- data/lib/ruflet/cli/update_command.rb +111 -0
- data/lib/ruflet/cli.rb +85 -0
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_cli.rb +3 -0
- metadata +59 -75
- data/lib/ruflet/manifest_compiler.rb +0 -62
- data/lib/ruflet.rb +0 -40
- data/lib/ruflet_protocol/ruflet/protocol.rb +0 -62
- data/lib/ruflet_protocol.rb +0 -4
- data/lib/ruflet_ui/ruflet/app.rb +0 -31
- data/lib/ruflet_ui/ruflet/colors.rb +0 -234
- data/lib/ruflet_ui/ruflet/control.rb +0 -168
- data/lib/ruflet_ui/ruflet/dsl.rb +0 -227
- data/lib/ruflet_ui/ruflet/event.rb +0 -28
- data/lib/ruflet_ui/ruflet/icon_data.rb +0 -62
- data/lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb +0 -54
- data/lib/ruflet_ui/ruflet/icons/cupertino_icon_lookup.rb +0 -112
- data/lib/ruflet_ui/ruflet/icons/material_icon_lookup.rb +0 -112
- data/lib/ruflet_ui/ruflet/icons/material_icons.rb +0 -55
- data/lib/ruflet_ui/ruflet/page.rb +0 -741
- data/lib/ruflet_ui/ruflet/ui/control_factory.rb +0 -23
- data/lib/ruflet_ui/ruflet/ui/control_methods.rb +0 -16
- data/lib/ruflet_ui/ruflet/ui/control_registry.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_action_sheet_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_alert_dialog_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_dialog_action_control.rb +0 -24
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_filled_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_navigation_bar_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_slider_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_switch_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_text_field_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/alert_dialog_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/app_bar_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/bottom_sheet_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/button_control.rb +0 -24
- data/lib/ruflet_ui/ruflet/ui/controls/material/checkbox_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/column_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/container_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/drag_target_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/draggable_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/elevated_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/filled_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/floating_action_button_control.rb +0 -28
- data/lib/ruflet_ui/ruflet/ui/controls/material/gesture_detector_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/grid_view_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/icon_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/icon_control.rb +0 -24
- data/lib/ruflet_ui/ruflet/ui/controls/material/image_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/markdown_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_destination_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/radio_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/radio_group_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/row_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/snack_bar_control.rb +0 -68
- data/lib/ruflet_ui/ruflet/ui/controls/material/stack_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_view_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/tabs_control.rb +0 -63
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_button_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_field_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/controls/material/view_control.rb +0 -13
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_factory.rb +0 -40
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_methods.rb +0 -26
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_registry.rb +0 -49
- data/lib/ruflet_ui/ruflet/ui/material_control_factory.rb +0 -97
- data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +0 -128
- data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +0 -154
- data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +0 -89
- data/lib/ruflet_ui/ruflet/ui/widget_builder.rb +0 -55
- data/lib/ruflet_ui.rb +0 -111
|
@@ -1,741 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "event"
|
|
4
|
-
require "ruflet_protocol"
|
|
5
|
-
require_relative "control"
|
|
6
|
-
require_relative "ui/widget_builder"
|
|
7
|
-
require_relative "icons/material_icon_lookup"
|
|
8
|
-
require_relative "icons/cupertino_icon_lookup"
|
|
9
|
-
require "set"
|
|
10
|
-
require "cgi"
|
|
11
|
-
require "thread"
|
|
12
|
-
require "timeout"
|
|
13
|
-
|
|
14
|
-
module Ruflet
|
|
15
|
-
class Page
|
|
16
|
-
PAGE_PROP_KEYS = %w[route title vertical_alignment horizontal_alignment scroll].freeze
|
|
17
|
-
DIALOG_PROP_KEYS = %w[dialog snack_bar bottom_sheet].freeze
|
|
18
|
-
BUTTON_TEXT_TYPES = %w[button elevatedbutton textbutton filledbutton].freeze
|
|
19
|
-
DEPRECATED_PAGE_WIDGET_METHODS = %i[
|
|
20
|
-
control widget view column center row stack container gesture_detector gesturedetector draggable
|
|
21
|
-
drag_target dragtarget text button elevated_button elevatedbutton text_button textbutton filled_button
|
|
22
|
-
filledbutton icon_button iconbutton text_field textfield checkbox radio radio_group radiogroup
|
|
23
|
-
alert_dialog alertdialog markdown icon image app_bar appbar floating_action_button snack_bar snackbar
|
|
24
|
-
bottom_sheet bottomsheet tabs tab tab_bar tabbar tab_bar_view tabbarview navigation_bar navigationbar
|
|
25
|
-
navigation_bar_destination navigationbardestination fab cupertino_button
|
|
26
|
-
cupertinobutton cupertino_filled_button cupertinofilledbutton cupertino_text_field cupertinotextfield
|
|
27
|
-
cupertino_switch cupertinoswitch cupertino_slider cupertinoslider cupertino_alert_dialog
|
|
28
|
-
cupertinoalertdialog cupertino_action_sheet cupertinoactionsheet cupertino_dialog_action
|
|
29
|
-
cupertinodialogaction cupertino_navigation_bar cupertinonavigationbar
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
|
-
attr_reader :session_id, :client_details, :views
|
|
33
|
-
|
|
34
|
-
def initialize(session_id:, client_details:, sender:)
|
|
35
|
-
@session_id = session_id
|
|
36
|
-
@client_details = client_details
|
|
37
|
-
@sender = sender
|
|
38
|
-
@control_index = {}
|
|
39
|
-
@wire_index = {}
|
|
40
|
-
@next_wire_id = 100
|
|
41
|
-
@view_id = 20
|
|
42
|
-
@root_controls = []
|
|
43
|
-
@views = []
|
|
44
|
-
@dialogs = []
|
|
45
|
-
@page_event_handlers = {}
|
|
46
|
-
@view_props = {}
|
|
47
|
-
@page_props = { "route" => (client_details["route"] || "/") }
|
|
48
|
-
@overlay_container = Ruflet::Control.new(
|
|
49
|
-
type: "overlay",
|
|
50
|
-
id: "_overlay",
|
|
51
|
-
controls: []
|
|
52
|
-
)
|
|
53
|
-
@services_container = Ruflet::Control.new(
|
|
54
|
-
type: "service_registry",
|
|
55
|
-
id: "_services",
|
|
56
|
-
"_services": [],
|
|
57
|
-
"_internals": { "uid" => Ruflet::Control.generate_id }
|
|
58
|
-
)
|
|
59
|
-
@dialogs_container = Ruflet::Control.new(
|
|
60
|
-
type: "dialogs",
|
|
61
|
-
id: "_dialogs",
|
|
62
|
-
controls: []
|
|
63
|
-
)
|
|
64
|
-
@invoke_waiters = {}
|
|
65
|
-
@invoke_waiters_mutex = Mutex.new
|
|
66
|
-
refresh_overlay_container!
|
|
67
|
-
refresh_services_container!
|
|
68
|
-
refresh_dialogs_container!
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def set_view_props(props)
|
|
72
|
-
split_props(normalize_props(props || {}))
|
|
73
|
-
self
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def title
|
|
77
|
-
@page_props["title"]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def title=(value)
|
|
81
|
-
@page_props["title"] = value
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def route
|
|
85
|
-
@page_props["route"]
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def route=(value)
|
|
89
|
-
@page_props["route"] = value
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def vertical_alignment
|
|
93
|
-
@page_props["vertical_alignment"] || @view_props["vertical_alignment"]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def vertical_alignment=(value)
|
|
97
|
-
v = normalize_value("vertical_alignment", value)
|
|
98
|
-
@page_props["vertical_alignment"] = v
|
|
99
|
-
@view_props["vertical_alignment"] = v
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def horizontal_alignment
|
|
103
|
-
@page_props["horizontal_alignment"] || @view_props["horizontal_alignment"]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def horizontal_alignment=(value)
|
|
107
|
-
v = normalize_value("horizontal_alignment", value)
|
|
108
|
-
@page_props["horizontal_alignment"] = v
|
|
109
|
-
@view_props["horizontal_alignment"] = v
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def bgcolor
|
|
113
|
-
@view_props["bgcolor"]
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def bgcolor=(value)
|
|
117
|
-
@view_props["bgcolor"] = normalize_value("bgcolor", value)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def add(*controls, appbar: nil, floating_action_button: nil, navigation_bar: nil, dialog: nil, snack_bar: nil, bottom_sheet: nil)
|
|
121
|
-
controls = controls.flatten
|
|
122
|
-
visited = Set.new
|
|
123
|
-
controls.each { |c| register_control_tree(c, visited) }
|
|
124
|
-
@root_controls = controls
|
|
125
|
-
|
|
126
|
-
@view_props["appbar"] = appbar if appbar
|
|
127
|
-
@view_props["floating_action_button"] = floating_action_button if floating_action_button
|
|
128
|
-
@view_props["navigation_bar"] = navigation_bar if navigation_bar
|
|
129
|
-
@dialog = dialog if dialog
|
|
130
|
-
@snack_bar = snack_bar if snack_bar
|
|
131
|
-
@bottom_sheet = bottom_sheet if bottom_sheet
|
|
132
|
-
|
|
133
|
-
refresh_dialogs_container!
|
|
134
|
-
@view_props.each_value { |value| register_embedded_value(value, visited) }
|
|
135
|
-
|
|
136
|
-
send_view_patch
|
|
137
|
-
|
|
138
|
-
self
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def views=(value)
|
|
142
|
-
@views = Array(value).compact
|
|
143
|
-
self
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def services
|
|
147
|
-
@services_container.props["_services"] ||= []
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def services=(value)
|
|
151
|
-
@services_container.props["_services"] = Array(value).compact
|
|
152
|
-
refresh_services_container!
|
|
153
|
-
push_services_update!
|
|
154
|
-
self
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def add_service(*value)
|
|
158
|
-
@services_container.props["_services"] = services + value.flatten.compact
|
|
159
|
-
refresh_services_container!
|
|
160
|
-
push_services_update!
|
|
161
|
-
self
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def go(route, **query_params)
|
|
165
|
-
@page_props["route"] = build_route(route, query_params)
|
|
166
|
-
dispatch_page_event(name: "route_change", data: @page_props["route"])
|
|
167
|
-
send_view_patch
|
|
168
|
-
self
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def on_route_change=(handler)
|
|
172
|
-
@page_event_handlers["route_change"] = handler
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def on_view_pop=(handler)
|
|
176
|
-
@page_event_handlers["view_pop"] = handler
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def on(event_name, &block)
|
|
180
|
-
@page_event_handlers[event_name.to_s.sub(/\Aon_/, "")] = block
|
|
181
|
-
self
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def mount(&block)
|
|
185
|
-
builder = WidgetBuilder.new
|
|
186
|
-
builder.instance_eval(&block)
|
|
187
|
-
add(*builder.children)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def appbar=(value)
|
|
191
|
-
@view_props["appbar"] = value
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def floating_action_button=(value)
|
|
195
|
-
@view_props["floating_action_button"] = value
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def dialog = @dialog
|
|
199
|
-
|
|
200
|
-
def dialog=(value)
|
|
201
|
-
@dialog = value
|
|
202
|
-
refresh_dialogs_container!
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def snack_bar=(value)
|
|
206
|
-
@snack_bar = value
|
|
207
|
-
refresh_dialogs_container!
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def snackbar=(value)
|
|
211
|
-
self.snack_bar = value
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def bottom_sheet=(value)
|
|
215
|
-
@bottom_sheet = value
|
|
216
|
-
refresh_dialogs_container!
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def bottomsheet=(value)
|
|
220
|
-
self.bottom_sheet = value
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def show_dialog(dialog_control)
|
|
224
|
-
return self unless dialog_control
|
|
225
|
-
|
|
226
|
-
return self if dialog_open?(dialog_control)
|
|
227
|
-
|
|
228
|
-
dialog_control.props["open"] = true
|
|
229
|
-
@dialogs << dialog_control unless @dialogs.include?(dialog_control)
|
|
230
|
-
refresh_dialogs_container!
|
|
231
|
-
send_view_patch unless @dialogs_container.wire_id
|
|
232
|
-
push_dialogs_update!
|
|
233
|
-
self
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def invoke(control_or_id, method_name, args: nil, timeout: 10)
|
|
237
|
-
control = resolve_control(control_or_id)
|
|
238
|
-
return nil unless control
|
|
239
|
-
|
|
240
|
-
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
241
|
-
send_message(Protocol::ACTIONS[:invoke_control_method], {
|
|
242
|
-
"control_id" => control.wire_id,
|
|
243
|
-
"call_id" => call_id,
|
|
244
|
-
"name" => method_name.to_s,
|
|
245
|
-
"args" => args,
|
|
246
|
-
"timeout" => timeout
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
call_id
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def launch_url(url, mode: "external_application", web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10)
|
|
253
|
-
launcher = ensure_url_launcher_service
|
|
254
|
-
invoke(
|
|
255
|
-
launcher,
|
|
256
|
-
"launch_url",
|
|
257
|
-
args: {
|
|
258
|
-
"url" => url,
|
|
259
|
-
"mode" => mode,
|
|
260
|
-
"web_view_configuration" => web_view_configuration,
|
|
261
|
-
"browser_configuration" => browser_configuration,
|
|
262
|
-
"web_only_window_name" => web_only_window_name
|
|
263
|
-
}.compact,
|
|
264
|
-
timeout: timeout
|
|
265
|
-
)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def can_launch_url(url, timeout: 10)
|
|
269
|
-
launcher = ensure_url_launcher_service
|
|
270
|
-
invoke(launcher, "can_launch_url", args: { "url" => url }, timeout: timeout)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def set_clipboard(value, timeout: 10)
|
|
274
|
-
clipboard = ensure_clipboard_service
|
|
275
|
-
invoke(clipboard, "set", args: { "data" => value.to_s }, timeout: timeout)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def get_clipboard(timeout: 10)
|
|
279
|
-
clipboard = ensure_clipboard_service
|
|
280
|
-
invoke(clipboard, "get", timeout: timeout)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
def set_clipboard_files(files, timeout: 10)
|
|
284
|
-
clipboard = ensure_clipboard_service
|
|
285
|
-
invoke(clipboard, "set_files", args: { "files" => Array(files).map(&:to_s) }, timeout: timeout)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def get_clipboard_files(timeout: 10)
|
|
289
|
-
clipboard = ensure_clipboard_service
|
|
290
|
-
invoke(clipboard, "get_files", timeout: timeout)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def set_clipboard_image(value, timeout: 10)
|
|
294
|
-
clipboard = ensure_clipboard_service
|
|
295
|
-
invoke(clipboard, "set_image", args: { "data" => value }, timeout: timeout)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
def get_clipboard_image(timeout: 10)
|
|
299
|
-
clipboard = ensure_clipboard_service
|
|
300
|
-
invoke(clipboard, "get_image", timeout: timeout)
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def handle_invoke_method_result(payload)
|
|
304
|
-
call_id = payload["call_id"].to_s
|
|
305
|
-
waiter = @invoke_waiters_mutex.synchronize { @invoke_waiters[call_id] }
|
|
306
|
-
return false unless waiter
|
|
307
|
-
|
|
308
|
-
waiter << payload
|
|
309
|
-
true
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def pop_dialog
|
|
313
|
-
dialog_control = latest_open_dialog
|
|
314
|
-
return nil unless dialog_control
|
|
315
|
-
|
|
316
|
-
dialog_control.props["open"] = false
|
|
317
|
-
refresh_dialogs_container!
|
|
318
|
-
push_dialogs_update!
|
|
319
|
-
dialog_control
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
def update(control_or_id = nil, **props)
|
|
323
|
-
if control_or_id.nil? && props.empty?
|
|
324
|
-
send_view_patch
|
|
325
|
-
return self
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
if page_control_target?(control_or_id)
|
|
329
|
-
split_props(normalize_props(props))
|
|
330
|
-
send_view_patch
|
|
331
|
-
return self
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
control = resolve_control(control_or_id)
|
|
335
|
-
return self unless control
|
|
336
|
-
|
|
337
|
-
patch = normalize_props(props)
|
|
338
|
-
if BUTTON_TEXT_TYPES.include?(control.type) && patch.key?("text")
|
|
339
|
-
patch["content"] = patch.delete("text")
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
# Keep runtime control tree aligned with incremental patches.
|
|
343
|
-
if patch.key?("controls")
|
|
344
|
-
control.children.clear
|
|
345
|
-
Array(patch["controls"]).each { |child| control.children << child if child.is_a?(Control) }
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
visited = Set.new
|
|
349
|
-
patch.each_value { |value| register_embedded_value(value, visited) }
|
|
350
|
-
|
|
351
|
-
patch_ops = patch.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
|
|
352
|
-
|
|
353
|
-
send_message(Protocol::ACTIONS[:patch_control], {
|
|
354
|
-
"id" => control.wire_id,
|
|
355
|
-
"patch" => [[0], *patch_ops]
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
self
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
def patch_page(control_id, **props)
|
|
362
|
-
update(control_id, **props)
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
def apply_client_update(control_or_id, props)
|
|
366
|
-
control = resolve_control(control_or_id)
|
|
367
|
-
return self unless control
|
|
368
|
-
|
|
369
|
-
patch = normalize_props(props || {})
|
|
370
|
-
patch.each { |k, v| control.props[k] = v }
|
|
371
|
-
|
|
372
|
-
remove_dialog_tracking(control) if patch.key?("open") && patch["open"] == false
|
|
373
|
-
|
|
374
|
-
self
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
def dispatch_event(target:, name:, data:)
|
|
378
|
-
if page_control_target?(target)
|
|
379
|
-
if name.to_s == "route_change"
|
|
380
|
-
route_from_event = extract_route(data)
|
|
381
|
-
@page_props["route"] = route_from_event if route_from_event
|
|
382
|
-
end
|
|
383
|
-
dispatch_page_event(name: name, data: data)
|
|
384
|
-
return
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
control = @wire_index[target.to_i] || @control_index[target.to_s]
|
|
388
|
-
return unless control
|
|
389
|
-
|
|
390
|
-
event = Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
391
|
-
control.emit(name, event)
|
|
392
|
-
|
|
393
|
-
if name.to_s == "dismiss" && remove_dialog_tracking(control)
|
|
394
|
-
push_dialogs_update!
|
|
395
|
-
end
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
def method_missing(name, *args, &block)
|
|
399
|
-
method_name = name.to_s
|
|
400
|
-
prop_name = method_name.delete_suffix("=")
|
|
401
|
-
|
|
402
|
-
if method_name.end_with?("=")
|
|
403
|
-
if DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym)
|
|
404
|
-
Kernel.warn("[DEPRECATION] `page.#{prop_name}(...)` is no longer supported.")
|
|
405
|
-
raise NoMethodError, "Use `#{prop_name}(...)` as a free widget helper, then attach with `page.add(...)`."
|
|
406
|
-
end
|
|
407
|
-
assign_split_prop(prop_name, normalize_value(prop_name, args.first))
|
|
408
|
-
return args.first
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
if args.empty? && !block
|
|
412
|
-
return @page_props[method_name] if @page_props.key?(method_name)
|
|
413
|
-
return @view_props[method_name] if @view_props.key?(method_name)
|
|
414
|
-
return instance_variable_get("@#{method_name}") if DIALOG_PROP_KEYS.include?(method_name)
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
if DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym)
|
|
418
|
-
Kernel.warn("[DEPRECATION] `page.#{name}(...)` is no longer supported.")
|
|
419
|
-
raise NoMethodError, "Use `#{name}(...)` as a free widget helper, then attach with `page.add(...)`."
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
super
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
def respond_to_missing?(name, include_private = false)
|
|
426
|
-
method_name = name.to_s
|
|
427
|
-
prop_name = method_name.delete_suffix("=")
|
|
428
|
-
DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym) ||
|
|
429
|
-
DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym) ||
|
|
430
|
-
method_name.end_with?("=") ||
|
|
431
|
-
@page_props.key?(method_name) ||
|
|
432
|
-
@view_props.key?(method_name) ||
|
|
433
|
-
DIALOG_PROP_KEYS.include?(method_name) ||
|
|
434
|
-
super
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
private
|
|
438
|
-
|
|
439
|
-
def invoke_and_wait(control_or_id, method_name, args: nil, timeout: 10)
|
|
440
|
-
control = resolve_control(control_or_id)
|
|
441
|
-
return nil unless control
|
|
442
|
-
|
|
443
|
-
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
444
|
-
waiter = Queue.new
|
|
445
|
-
@invoke_waiters_mutex.synchronize { @invoke_waiters[call_id] = waiter }
|
|
446
|
-
|
|
447
|
-
send_message(Protocol::ACTIONS[:invoke_control_method], {
|
|
448
|
-
"control_id" => control.wire_id,
|
|
449
|
-
"call_id" => call_id,
|
|
450
|
-
"name" => method_name.to_s,
|
|
451
|
-
"args" => args,
|
|
452
|
-
"timeout" => timeout
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
response = Timeout.timeout(timeout.to_f) { waiter.pop }
|
|
456
|
-
error = response["error"]
|
|
457
|
-
raise RuntimeError, error if error && !error.to_s.empty?
|
|
458
|
-
|
|
459
|
-
response["result"]
|
|
460
|
-
ensure
|
|
461
|
-
@invoke_waiters_mutex.synchronize { @invoke_waiters.delete(call_id) } if call_id
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
def build_widget(type, **props, &block) = WidgetBuilder.new.control(type, **props, &block)
|
|
465
|
-
|
|
466
|
-
def split_props(props)
|
|
467
|
-
props.each do |k, v|
|
|
468
|
-
assign_split_prop(k, v)
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
|
-
|
|
472
|
-
def send_message(action, payload)
|
|
473
|
-
@sender.call(action, payload)
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def send_view_patch
|
|
477
|
-
refresh_control_indexes!
|
|
478
|
-
view_patches = build_view_patches
|
|
479
|
-
page_patch_ops = build_page_patch_ops
|
|
480
|
-
|
|
481
|
-
send_message(Protocol::ACTIONS[:patch_control], {
|
|
482
|
-
"id" => 1,
|
|
483
|
-
"patch" => [
|
|
484
|
-
[0],
|
|
485
|
-
[0, 0, "views", view_patches],
|
|
486
|
-
*page_patch_ops
|
|
487
|
-
]
|
|
488
|
-
})
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
def register_control_tree(control, visited = Set.new)
|
|
492
|
-
return unless control
|
|
493
|
-
return if visited.include?(control.object_id)
|
|
494
|
-
|
|
495
|
-
visited << control.object_id
|
|
496
|
-
assign_wire_id(control)
|
|
497
|
-
control.runtime_page = self if control.respond_to?(:runtime_page=)
|
|
498
|
-
@control_index[control.id.to_s] = control
|
|
499
|
-
@wire_index[control.wire_id] = control
|
|
500
|
-
control.children.each { |child| register_control_tree(child, visited) }
|
|
501
|
-
control.props.each_value { |value| register_embedded_value(value, visited) }
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
def implicit_view_patch
|
|
505
|
-
view_patch = {
|
|
506
|
-
"_c" => "View",
|
|
507
|
-
"_i" => @view_id,
|
|
508
|
-
"route" => (@page_props["route"] || @client_details["route"] || "/"),
|
|
509
|
-
# Required by Flet layout engine so children with `expand` inside View
|
|
510
|
-
# are wrapped with Expanded/Flexible on the Flutter side.
|
|
511
|
-
"_internals" => { "host_expanded" => true }
|
|
512
|
-
}
|
|
513
|
-
@view_props.each { |k, v| view_patch[k] = serialize_patch_value(v) }
|
|
514
|
-
view_patch["controls"] = @root_controls.map(&:to_patch)
|
|
515
|
-
view_patch
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
def refresh_control_indexes!
|
|
519
|
-
@control_index.clear
|
|
520
|
-
@wire_index.clear
|
|
521
|
-
visited = Set.new
|
|
522
|
-
|
|
523
|
-
if @views.any?
|
|
524
|
-
@views.each { |view| register_control_tree(view, visited) }
|
|
525
|
-
else
|
|
526
|
-
@root_controls.each { |control| register_control_tree(control, visited) }
|
|
527
|
-
@view_props.each_value { |value| register_embedded_value(value, visited) }
|
|
528
|
-
end
|
|
529
|
-
@page_props.each_value { |value| register_embedded_value(value, visited) }
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
def register_embedded_value(value, visited)
|
|
533
|
-
case value
|
|
534
|
-
when Control
|
|
535
|
-
register_control_tree(value, visited)
|
|
536
|
-
when Array
|
|
537
|
-
value.each { |v| register_embedded_value(v, visited) }
|
|
538
|
-
when Hash
|
|
539
|
-
value.each_value { |v| register_embedded_value(v, visited) }
|
|
540
|
-
end
|
|
541
|
-
end
|
|
542
|
-
|
|
543
|
-
def assign_wire_id(control)
|
|
544
|
-
return if control.wire_id
|
|
545
|
-
|
|
546
|
-
control.wire_id = @next_wire_id
|
|
547
|
-
@next_wire_id += 1
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
def resolve_control(control_or_id)
|
|
551
|
-
if control_or_id.respond_to?(:wire_id)
|
|
552
|
-
control_or_id
|
|
553
|
-
elsif control_or_id.to_s.match?(/^\d+$/)
|
|
554
|
-
@wire_index[control_or_id.to_i]
|
|
555
|
-
else
|
|
556
|
-
@control_index[control_or_id.to_s]
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
def normalize_props(hash)
|
|
561
|
-
hash.each_with_object({}) do |(k, v), result|
|
|
562
|
-
key = k.to_s
|
|
563
|
-
key = "controls" if key == "children"
|
|
564
|
-
result[key] = normalize_value(key, v)
|
|
565
|
-
end
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
def normalize_value(key, value)
|
|
569
|
-
if icon_prop_key?(key) && (value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Integer))
|
|
570
|
-
codepoint = resolve_icon_codepoint(value)
|
|
571
|
-
return codepoint unless codepoint.nil?
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
return value.value if value.is_a?(Ruflet::IconData)
|
|
575
|
-
value.is_a?(Symbol) ? value.to_s : value
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
def build_route(route, query_params = {})
|
|
579
|
-
base = route.to_s
|
|
580
|
-
return base if query_params.nil? || query_params.empty?
|
|
581
|
-
|
|
582
|
-
query = query_params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join("&")
|
|
583
|
-
separator = base.include?("?") ? "&" : "?"
|
|
584
|
-
"#{base}#{separator}#{query}"
|
|
585
|
-
end
|
|
586
|
-
|
|
587
|
-
def extract_route(data)
|
|
588
|
-
case data
|
|
589
|
-
when String
|
|
590
|
-
data
|
|
591
|
-
when Hash
|
|
592
|
-
data["route"] || data[:route]
|
|
593
|
-
else
|
|
594
|
-
nil
|
|
595
|
-
end
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
def dispatch_page_event(name:, data:)
|
|
599
|
-
handler = @page_event_handlers[name.to_s.sub(/\Aon_/, "")]
|
|
600
|
-
return unless handler.respond_to?(:call)
|
|
601
|
-
|
|
602
|
-
event = Event.new(name: name.to_s, target: 1, raw_data: data, page: self, control: nil)
|
|
603
|
-
handler.call(event)
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
def page_control_target?(control_or_id)
|
|
607
|
-
control_or_id == 1 || control_or_id.to_s == "1" || control_or_id.to_s == "page"
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
def serialize_patch_value(value)
|
|
611
|
-
case value
|
|
612
|
-
when Control
|
|
613
|
-
value.to_patch
|
|
614
|
-
when Ruflet::IconData
|
|
615
|
-
value.value
|
|
616
|
-
when Array
|
|
617
|
-
value.map { |v| serialize_patch_value(v) }
|
|
618
|
-
when Hash
|
|
619
|
-
value.transform_values { |v| serialize_patch_value(v) }
|
|
620
|
-
else
|
|
621
|
-
value
|
|
622
|
-
end
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
def icon_prop_key?(key)
|
|
626
|
-
key == "icon" || key.end_with?("_icon")
|
|
627
|
-
end
|
|
628
|
-
|
|
629
|
-
def refresh_dialogs_container!
|
|
630
|
-
dialog_controls = (@dialogs + dialog_slots).uniq
|
|
631
|
-
@dialogs_container.props["controls"] = dialog_controls
|
|
632
|
-
@page_props["_dialogs"] = @dialogs_container
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
def refresh_overlay_container!
|
|
636
|
-
@page_props["_overlay"] = @overlay_container
|
|
637
|
-
end
|
|
638
|
-
|
|
639
|
-
def refresh_services_container!
|
|
640
|
-
@page_props["_services"] = @services_container
|
|
641
|
-
end
|
|
642
|
-
|
|
643
|
-
def push_services_update!
|
|
644
|
-
refresh_control_indexes!
|
|
645
|
-
|
|
646
|
-
if @services_container.wire_id
|
|
647
|
-
send_message(Protocol::ACTIONS[:patch_control], {
|
|
648
|
-
"id" => @services_container.wire_id,
|
|
649
|
-
"patch" => [[0], [0, 0, "_services", serialize_patch_value(@services_container.props["_services"])]]
|
|
650
|
-
})
|
|
651
|
-
else
|
|
652
|
-
send_view_patch
|
|
653
|
-
end
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
def push_dialogs_update!
|
|
657
|
-
refresh_control_indexes!
|
|
658
|
-
|
|
659
|
-
if @dialogs_container.wire_id
|
|
660
|
-
send_message(Protocol::ACTIONS[:patch_control], {
|
|
661
|
-
"id" => @dialogs_container.wire_id,
|
|
662
|
-
"patch" => [[0], [0, 0, "controls", serialize_patch_value(@dialogs_container.props["controls"])]]
|
|
663
|
-
})
|
|
664
|
-
else
|
|
665
|
-
send_view_patch
|
|
666
|
-
end
|
|
667
|
-
end
|
|
668
|
-
|
|
669
|
-
def dialog_slots
|
|
670
|
-
[@dialog, @snack_bar, @bottom_sheet].compact
|
|
671
|
-
end
|
|
672
|
-
|
|
673
|
-
def latest_open_dialog
|
|
674
|
-
@dialogs.reverse.find { |d| d.props["open"] != false }
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
def dialog_open?(dialog_control)
|
|
678
|
-
@dialogs.include?(dialog_control) && dialog_control.props["open"] == true
|
|
679
|
-
end
|
|
680
|
-
|
|
681
|
-
def remove_dialog_tracking(control)
|
|
682
|
-
return false unless @dialogs.include?(control)
|
|
683
|
-
|
|
684
|
-
@dialogs.delete(control)
|
|
685
|
-
refresh_dialogs_container!
|
|
686
|
-
true
|
|
687
|
-
end
|
|
688
|
-
|
|
689
|
-
def assign_split_prop(key, value)
|
|
690
|
-
if key == "vertical_alignment" || key == "horizontal_alignment"
|
|
691
|
-
@page_props[key] = value
|
|
692
|
-
@view_props[key] = value
|
|
693
|
-
elsif DIALOG_PROP_KEYS.include?(key)
|
|
694
|
-
instance_variable_set("@#{key}", value)
|
|
695
|
-
refresh_dialogs_container!
|
|
696
|
-
elsif PAGE_PROP_KEYS.include?(key)
|
|
697
|
-
@page_props[key] = value
|
|
698
|
-
else
|
|
699
|
-
@view_props[key] = value
|
|
700
|
-
end
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
def build_view_patches
|
|
704
|
-
if @views.any?
|
|
705
|
-
@views.map(&:to_patch)
|
|
706
|
-
else
|
|
707
|
-
[implicit_view_patch]
|
|
708
|
-
end
|
|
709
|
-
end
|
|
710
|
-
|
|
711
|
-
def build_page_patch_ops
|
|
712
|
-
@page_props.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
|
|
713
|
-
end
|
|
714
|
-
|
|
715
|
-
def resolve_icon_codepoint(value)
|
|
716
|
-
codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
|
|
717
|
-
if codepoint.nil? || codepoint == value
|
|
718
|
-
codepoint = Ruflet::CupertinoIconLookup.codepoint_for(value)
|
|
719
|
-
end
|
|
720
|
-
codepoint
|
|
721
|
-
end
|
|
722
|
-
|
|
723
|
-
def ensure_url_launcher_service
|
|
724
|
-
launcher = services.find { |service| service.is_a?(Control) && service.type == "url_launcher" }
|
|
725
|
-
return launcher if launcher
|
|
726
|
-
|
|
727
|
-
launcher = build_widget(:url_launcher)
|
|
728
|
-
add_service(launcher)
|
|
729
|
-
launcher
|
|
730
|
-
end
|
|
731
|
-
|
|
732
|
-
def ensure_clipboard_service
|
|
733
|
-
clipboard = services.find { |service| service.is_a?(Control) && service.type == "clipboard" }
|
|
734
|
-
return clipboard if clipboard
|
|
735
|
-
|
|
736
|
-
clipboard = build_widget(:clipboard)
|
|
737
|
-
add_service(clipboard)
|
|
738
|
-
clipboard
|
|
739
|
-
end
|
|
740
|
-
end
|
|
741
|
-
end
|