ruflet_core 0.0.14 → 0.0.15
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_protocol/ruflet/protocol.rb +8 -1
- data/lib/ruflet_ui/ruflet/control.rb +31 -0
- data/lib/ruflet_ui/ruflet/page.rb +60 -14
- data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +114 -1
- data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +28 -4
- data/lib/ruflet_ui/ruflet/ui/services/ruflet/audio_recorder_control.rb +4 -2
- data/lib/ruflet_ui.rb +6 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1823b100c3e97ee941849763509b25179554464310eb640d7954bb48a30d821
|
|
4
|
+
data.tar.gz: 80d3f6891f3418bf0b2b3aa2007e08e6578ca336b72bb48b9d84c54cac2397b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e37e04ea218fbc50da5d3a456ecdfe376f12d00fc802c0869da7dff977bafb6d0abab8eecff92e223b42002f94652e40f7d91d2eb7a62f04fbec72bf1c7dd600
|
|
7
|
+
data.tar.gz: 42458a85edccff3f66fd88cdc71724a7a94dbf1853fbf080d0751da7b57e783694e0ba65091c908bb4b1f203d6a8e8725e9e594df762f7deb1996e29e1bc88ed
|
data/lib/ruflet/version.rb
CHANGED
|
@@ -39,6 +39,12 @@ module Ruflet
|
|
|
39
39
|
"height" => page["height"],
|
|
40
40
|
"platform" => page["platform"],
|
|
41
41
|
"platform_brightness" => page["platform_brightness"],
|
|
42
|
+
# The Flutter client reports the host OS in "platform" even inside a
|
|
43
|
+
# browser, so "web" is the only reliable way to tell a web client from a
|
|
44
|
+
# native one. (pwa/wasm passed through for completeness.)
|
|
45
|
+
"web" => page["web"],
|
|
46
|
+
"pwa" => page["pwa"],
|
|
47
|
+
"wasm" => page["wasm"],
|
|
42
48
|
"media" => page["media"] || {}
|
|
43
49
|
}
|
|
44
50
|
end
|
|
@@ -97,7 +103,8 @@ module Ruflet
|
|
|
97
103
|
}
|
|
98
104
|
end
|
|
99
105
|
|
|
100
|
-
def register_response(session_id:, page_patch:
|
|
106
|
+
def register_response(session_id:, page_patch: nil, error: nil)
|
|
107
|
+
page_patch ||= {}
|
|
101
108
|
{
|
|
102
109
|
"session_id" => session_id,
|
|
103
110
|
"page_patch" => page_patch,
|
|
@@ -47,6 +47,37 @@ module Ruflet
|
|
|
47
47
|
@handlers.key?(normalized_event_name(event_name))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Read a prop by name: control["value"] or control[:value].
|
|
51
|
+
def [](key)
|
|
52
|
+
@props[key.to_s]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Write a prop by name: control["value"] = "x".
|
|
56
|
+
def []=(key, value)
|
|
57
|
+
@props[key.to_s] = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Convenience dot access to props, mirroring Flet-style controls:
|
|
61
|
+
# control.value # => @props["value"] (reads an existing prop)
|
|
62
|
+
# control.value = "hello" # => sets @props["value"]
|
|
63
|
+
# Reads only resolve props that exist, so typos still raise NoMethodError
|
|
64
|
+
# instead of silently returning nil. Defined methods (type, id, props,
|
|
65
|
+
# children, on, emit, to_patch, …) are never shadowed.
|
|
66
|
+
def method_missing(name, *args, &block)
|
|
67
|
+
key = name.to_s
|
|
68
|
+
if key.end_with?("=")
|
|
69
|
+
return @props[key[0..-2]] = args.first
|
|
70
|
+
end
|
|
71
|
+
return @props[key] if args.empty? && @props.key?(key)
|
|
72
|
+
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def respond_to_missing?(name, include_private = false)
|
|
77
|
+
key = name.to_s
|
|
78
|
+
key.end_with?("=") || @props.key?(key) || super
|
|
79
|
+
end
|
|
80
|
+
|
|
50
81
|
def to_patch
|
|
51
82
|
wire_type = schema_wire_type_for_class
|
|
52
83
|
if wire_type.nil?
|
|
@@ -533,7 +533,7 @@ module Ruflet
|
|
|
533
533
|
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
534
534
|
if on_result.respond_to?(:call)
|
|
535
535
|
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
536
|
-
|
|
536
|
+
if embedded_async_timeout_available? && !timeout.nil?
|
|
537
537
|
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
538
538
|
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
539
539
|
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
@@ -1157,12 +1157,15 @@ module Ruflet
|
|
|
1157
1157
|
def close_dialog(dialog_control)
|
|
1158
1158
|
return self unless dialog_control
|
|
1159
1159
|
|
|
1160
|
-
before_dialog_count = @dialogs_container.props["controls"].length
|
|
1161
1160
|
dialog_control.props["open"] = false
|
|
1162
1161
|
@dialog = nil if @dialog.equal?(dialog_control)
|
|
1163
1162
|
remove_dialog_tracking(dialog_control)
|
|
1164
1163
|
refresh_dialogs_container!
|
|
1165
|
-
|
|
1164
|
+
# Patch the dialogs container in place. Forcing a full view re-render
|
|
1165
|
+
# here would remount the whole overlay — fatal while another dialog
|
|
1166
|
+
# (e.g. the form behind a nested picker) is still open. The empty case
|
|
1167
|
+
# is handled inside push_dialogs_update!.
|
|
1168
|
+
push_dialogs_update!
|
|
1166
1169
|
self
|
|
1167
1170
|
end
|
|
1168
1171
|
|
|
@@ -1231,6 +1234,10 @@ module Ruflet
|
|
|
1231
1234
|
if page_control_target?(target)
|
|
1232
1235
|
if name.to_s == "route_change"
|
|
1233
1236
|
route_from_event = extract_route(data)
|
|
1237
|
+
# Dialogs (including pickers) belong to the view that opened them.
|
|
1238
|
+
# Navigating away must dismiss them, or they ghost onto the next
|
|
1239
|
+
# view — the picker that "reappears after going home".
|
|
1240
|
+
dismiss_tracked_dialogs! if route_from_event && route_from_event != @page_props["route"]
|
|
1234
1241
|
@page_props["route"] = route_from_event if route_from_event
|
|
1235
1242
|
end
|
|
1236
1243
|
dispatch_page_event(name: name, data: data)
|
|
@@ -1240,11 +1247,16 @@ module Ruflet
|
|
|
1240
1247
|
control = @wire_index[target.to_i] || @control_index[target.to_s]
|
|
1241
1248
|
return unless control
|
|
1242
1249
|
|
|
1243
|
-
event = Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
1250
|
+
event = Ruflet::Event.new(name: name, target: target, raw_data: data, page: self, control: control)
|
|
1244
1251
|
apply_event_value_to_control(control, event) if %w[change select select_change].include?(name.to_s)
|
|
1245
|
-
|
|
1252
|
+
# Material/Cupertino pickers dismiss themselves on the client once a
|
|
1253
|
+
# value is confirmed, but only send a value event — never a close. Mark
|
|
1254
|
+
# the dialog closed here so show_dialog can reopen it next time.
|
|
1255
|
+
mark_picker_dialog_closed(control, name)
|
|
1246
1256
|
if dialog_close_event?(control, name) && remove_dialog_tracking(control)
|
|
1247
|
-
|
|
1257
|
+
# Patch the container in place; never force a full view re-render that
|
|
1258
|
+
# would remount a still-open parent dialog (the nested-picker case).
|
|
1259
|
+
push_dialogs_update!
|
|
1248
1260
|
end
|
|
1249
1261
|
|
|
1250
1262
|
control.emit(name, event)
|
|
@@ -1289,6 +1301,10 @@ module Ruflet
|
|
|
1289
1301
|
|
|
1290
1302
|
private
|
|
1291
1303
|
|
|
1304
|
+
def embedded_async_timeout_available?
|
|
1305
|
+
!Object.const_defined?(:RUFLET_EMBEDDED_FAKE_THREAD)
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1292
1308
|
def invoke_and_wait(control_or_id, method_name, args: nil, timeout: 10)
|
|
1293
1309
|
control_id =
|
|
1294
1310
|
if page_control_target?(control_or_id)
|
|
@@ -1634,15 +1650,15 @@ module Ruflet
|
|
|
1634
1650
|
end
|
|
1635
1651
|
end
|
|
1636
1652
|
|
|
1637
|
-
def push_dialogs_update!
|
|
1653
|
+
def push_dialogs_update!
|
|
1638
1654
|
refresh_control_indexes!
|
|
1639
1655
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1656
|
+
# Once the dialogs container is mounted, every change — opening, closing,
|
|
1657
|
+
# even down to no dialogs at all — is an in-place patch of its controls
|
|
1658
|
+
# list. Re-sending the view (or the container as a whole object) would
|
|
1659
|
+
# replace the live container instance on the Flutter side, detaching its
|
|
1660
|
+
# listeners and breaking any other dialog still open. Only the very first
|
|
1661
|
+
# dialog, before the container has a wire id, needs a view patch to mount.
|
|
1646
1662
|
if @dialogs_container.wire_id
|
|
1647
1663
|
send_message(Protocol::ACTIONS[:patch_control], {
|
|
1648
1664
|
"id" => @dialogs_container.wire_id,
|
|
@@ -1670,6 +1686,36 @@ module Ruflet
|
|
|
1670
1686
|
name == "dismiss" || (%w[change select select_change].include?(name) && @dialogs.include?(control) && control.props["open"] == false)
|
|
1671
1687
|
end
|
|
1672
1688
|
|
|
1689
|
+
# Picker dialogs that auto-dismiss on the client after a selection. Their
|
|
1690
|
+
# confirm sends a value event (change/select), not a close, so the server
|
|
1691
|
+
# must flip `open` to false or show_dialog's open-guard blocks reopening.
|
|
1692
|
+
PICKER_DIALOG_TYPES = %w[
|
|
1693
|
+
datepicker daterangepicker timepicker
|
|
1694
|
+
cupertinodatepicker cupertinotimerpicker
|
|
1695
|
+
].freeze
|
|
1696
|
+
|
|
1697
|
+
def picker_dialog?(control)
|
|
1698
|
+
PICKER_DIALOG_TYPES.include?(control.type.to_s.tr("_", "").downcase)
|
|
1699
|
+
end
|
|
1700
|
+
|
|
1701
|
+
def mark_picker_dialog_closed(control, name)
|
|
1702
|
+
return unless picker_dialog?(control)
|
|
1703
|
+
return unless %w[change select select_change dismiss].include?(name.to_s)
|
|
1704
|
+
|
|
1705
|
+
control.props["open"] = false
|
|
1706
|
+
end
|
|
1707
|
+
|
|
1708
|
+
# Close and untrack every dialog currently shown. Called on navigation so
|
|
1709
|
+
# a dialog opened in one view does not linger as an overlay on the next.
|
|
1710
|
+
def dismiss_tracked_dialogs!
|
|
1711
|
+
return if @dialogs.empty?
|
|
1712
|
+
|
|
1713
|
+
@dialogs.each { |dialog| dialog.props["open"] = false }
|
|
1714
|
+
@dialogs.clear
|
|
1715
|
+
refresh_dialogs_container!
|
|
1716
|
+
push_dialogs_update! if @dialogs_container_mounted
|
|
1717
|
+
end
|
|
1718
|
+
|
|
1673
1719
|
def remove_dialog_tracking(control)
|
|
1674
1720
|
return false unless @dialogs.include?(control)
|
|
1675
1721
|
|
|
@@ -1821,7 +1867,7 @@ module Ruflet
|
|
|
1821
1867
|
call_id = "call_#{Ruflet::Control.generate_id}"
|
|
1822
1868
|
if on_result.respond_to?(:call)
|
|
1823
1869
|
@invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
|
|
1824
|
-
|
|
1870
|
+
if embedded_async_timeout_available? && !timeout.nil?
|
|
1825
1871
|
Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
|
|
1826
1872
|
sleep([invoke_timeout, 0.0].max + 0.1)
|
|
1827
1873
|
callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
|
|
@@ -4,11 +4,33 @@ module Ruflet
|
|
|
4
4
|
module UI
|
|
5
5
|
module Controls
|
|
6
6
|
module RufletComponents
|
|
7
|
+
# WebView control — parity with Flet's WebView
|
|
8
|
+
# (https://flet.dev/docs/controls/webview/).
|
|
9
|
+
#
|
|
10
|
+
# Properties: url, bgcolor, prevent_links, plus the usual layout props.
|
|
11
|
+
# Events: on_page_started, on_page_ended, on_web_resource_error,
|
|
12
|
+
# on_progress, on_url_change, on_scroll, on_console_message,
|
|
13
|
+
# on_javascript_alert_dialog.
|
|
14
|
+
# Methods (invoked over the wire on a mounted control): reload, go_back,
|
|
15
|
+
# go_forward, can_go_back, can_go_forward, run_javascript, load_html,
|
|
16
|
+
# load_request, load_file, scroll_to, scroll_by, clear_cache,
|
|
17
|
+
# clear_local_storage, enable_zoom, disable_zoom, set_javascript_mode,
|
|
18
|
+
# get_current_url, get_title, get_user_agent.
|
|
19
|
+
#
|
|
20
|
+
# Platform note: the native webview (and therefore run_javascript and the
|
|
21
|
+
# events/methods) runs on iOS, Android and macOS. On web it falls back to
|
|
22
|
+
# an <iframe>, which cannot run the methods and which most external sites
|
|
23
|
+
# block via X-Frame-Options/CSP — embed your own same-origin pages there.
|
|
7
24
|
class WebViewControl < Ruflet::Control
|
|
8
25
|
TYPE = "WebView".freeze
|
|
9
26
|
WIRE = "WebView".freeze
|
|
10
27
|
|
|
11
|
-
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
28
|
+
def initialize(id: nil, bgcolor: nil, data: nil, enable_javascript: nil, expand: nil,
|
|
29
|
+
height: nil, key: nil, method: nil, opacity: nil, prevent_links: nil,
|
|
30
|
+
rtl: nil, tooltip: nil, url: nil, visible: nil, width: nil,
|
|
31
|
+
on_page_ended: nil, on_page_started: nil, on_web_resource_error: nil,
|
|
32
|
+
on_progress: nil, on_url_change: nil, on_scroll: nil,
|
|
33
|
+
on_console_message: nil, on_javascript_alert_dialog: nil)
|
|
12
34
|
props = {}
|
|
13
35
|
props[:bgcolor] = bgcolor unless bgcolor.nil?
|
|
14
36
|
props[:data] = data unless data.nil?
|
|
@@ -18,6 +40,7 @@ module Ruflet
|
|
|
18
40
|
props[:key] = key unless key.nil?
|
|
19
41
|
props[:method] = method unless method.nil?
|
|
20
42
|
props[:opacity] = opacity unless opacity.nil?
|
|
43
|
+
props[:prevent_links] = prevent_links unless prevent_links.nil?
|
|
21
44
|
props[:rtl] = rtl unless rtl.nil?
|
|
22
45
|
props[:tooltip] = tooltip unless tooltip.nil?
|
|
23
46
|
props[:url] = url unless url.nil?
|
|
@@ -26,8 +49,98 @@ module Ruflet
|
|
|
26
49
|
props[:on_page_ended] = on_page_ended unless on_page_ended.nil?
|
|
27
50
|
props[:on_page_started] = on_page_started unless on_page_started.nil?
|
|
28
51
|
props[:on_web_resource_error] = on_web_resource_error unless on_web_resource_error.nil?
|
|
52
|
+
props[:on_progress] = on_progress unless on_progress.nil?
|
|
53
|
+
props[:on_url_change] = on_url_change unless on_url_change.nil?
|
|
54
|
+
props[:on_scroll] = on_scroll unless on_scroll.nil?
|
|
55
|
+
props[:on_console_message] = on_console_message unless on_console_message.nil?
|
|
56
|
+
props[:on_javascript_alert_dialog] = on_javascript_alert_dialog unless on_javascript_alert_dialog.nil?
|
|
29
57
|
super(type: TYPE, id: id, **props)
|
|
30
58
|
end
|
|
59
|
+
|
|
60
|
+
# --- Navigation --------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def reload = invoke_webview_method("reload")
|
|
63
|
+
def go_back = invoke_webview_method("go_back")
|
|
64
|
+
def go_forward = invoke_webview_method("go_forward")
|
|
65
|
+
|
|
66
|
+
def can_go_back(timeout: 10, &on_result)
|
|
67
|
+
invoke_webview_method("can_go_back", timeout: timeout, on_result: on_result)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def can_go_forward(timeout: 10, &on_result)
|
|
71
|
+
invoke_webview_method("can_go_forward", timeout: timeout, on_result: on_result)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- Loading content ---------------------------------------------
|
|
75
|
+
|
|
76
|
+
def load_request(url, method: "get")
|
|
77
|
+
invoke_webview_method("load_request", { "url" => url.to_s, "method" => method.to_s })
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def load_html(value, base_url: nil)
|
|
81
|
+
args = { "value" => value.to_s }
|
|
82
|
+
args["base_url"] = base_url.to_s unless base_url.nil?
|
|
83
|
+
invoke_webview_method("load_html", args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def load_file(path)
|
|
87
|
+
invoke_webview_method("load_file", { "path" => path.to_s })
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# --- JavaScript injection ---------------------------------------
|
|
91
|
+
|
|
92
|
+
# Run arbitrary JS inside the page — e.g. hide a node so a native
|
|
93
|
+
# control can take its place:
|
|
94
|
+
# webview.run_javascript("document.getElementById('banner').remove()")
|
|
95
|
+
def run_javascript(value)
|
|
96
|
+
invoke_webview_method("run_javascript", { "value" => value.to_s })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def set_javascript_mode(mode)
|
|
100
|
+
invoke_webview_method("set_javascript_mode", { "mode" => mode.to_s })
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# --- Scrolling ---------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def scroll_to(x, y)
|
|
106
|
+
invoke_webview_method("scroll_to", { "x" => x.to_i, "y" => y.to_i })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def scroll_by(x, y)
|
|
110
|
+
invoke_webview_method("scroll_by", { "x" => x.to_i, "y" => y.to_i })
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# --- Storage / zoom ----------------------------------------------
|
|
114
|
+
|
|
115
|
+
def clear_cache = invoke_webview_method("clear_cache")
|
|
116
|
+
def clear_local_storage = invoke_webview_method("clear_local_storage")
|
|
117
|
+
def enable_zoom = invoke_webview_method("enable_zoom")
|
|
118
|
+
def disable_zoom = invoke_webview_method("disable_zoom")
|
|
119
|
+
|
|
120
|
+
# --- Introspection (result delivered to the block) ---------------
|
|
121
|
+
|
|
122
|
+
def get_current_url(timeout: 10, &on_result)
|
|
123
|
+
invoke_webview_method("get_current_url", timeout: timeout, on_result: on_result)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_title(timeout: 10, &on_result)
|
|
127
|
+
invoke_webview_method("get_title", timeout: timeout, on_result: on_result)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get_user_agent(timeout: 10, &on_result)
|
|
131
|
+
invoke_webview_method("get_user_agent", timeout: timeout, on_result: on_result)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def invoke_webview_method(name, args = nil, timeout: 10, on_result: nil)
|
|
137
|
+
page = runtime_page
|
|
138
|
+
unless page && wire_id
|
|
139
|
+
raise "WebView ##{id} is not mounted yet — add it to the page before calling #{name}."
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
page.invoke(self, name, args: args, timeout: timeout, on_result: on_result)
|
|
143
|
+
end
|
|
31
144
|
end
|
|
32
145
|
end
|
|
33
146
|
end
|
|
@@ -50,10 +50,28 @@ module Ruflet
|
|
|
50
50
|
super(type: TYPE, id: id, **props)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
def wait_until_ready_to_show(timeout: 10, on_result: nil)
|
|
54
|
+
invoke_window_method("wait_until_ready_to_show", timeout: timeout, on_result: on_result)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_front(timeout: 10, on_result: nil)
|
|
58
|
+
invoke_window_method("to_front", timeout: timeout, on_result: on_result)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def center(timeout: 10, on_result: nil)
|
|
62
|
+
invoke_window_method("center", timeout: timeout, on_result: on_result)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close(timeout: 10, on_result: nil)
|
|
66
|
+
invoke_window_method("close", timeout: timeout, on_result: on_result)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def destroy(timeout: 10, on_result: nil)
|
|
70
|
+
invoke_window_method("destroy", timeout: timeout, on_result: on_result)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def start_dragging(timeout: 10, on_result: nil)
|
|
74
|
+
invoke_window_method("start_dragging", timeout: timeout, on_result: on_result)
|
|
57
75
|
end
|
|
58
76
|
|
|
59
77
|
def start_resizing(edge, timeout: 10, on_result: nil)
|
|
@@ -65,6 +83,12 @@ module Ruflet
|
|
|
65
83
|
on_result: on_result
|
|
66
84
|
)
|
|
67
85
|
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def invoke_window_method(method_name, timeout:, on_result:)
|
|
90
|
+
runtime_page&.invoke(self, method_name, timeout: timeout, on_result: on_result)
|
|
91
|
+
end
|
|
68
92
|
end
|
|
69
93
|
end
|
|
70
94
|
end
|
|
@@ -8,7 +8,8 @@ module Ruflet
|
|
|
8
8
|
TYPE = "audiorecorder".freeze
|
|
9
9
|
WIRE = "AudioRecorder".freeze
|
|
10
10
|
|
|
11
|
-
def initialize(id: nil, configuration:
|
|
11
|
+
def initialize(id: nil, configuration: nil, data: nil, key: nil, on_state_change: nil, on_stream: nil, on_upload: nil)
|
|
12
|
+
configuration ||= {}
|
|
12
13
|
props = {}
|
|
13
14
|
props[:configuration] = configuration unless configuration.nil?
|
|
14
15
|
props[:data] = data unless data.nil?
|
|
@@ -43,7 +44,8 @@ module Ruflet
|
|
|
43
44
|
)
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def start_recording(output_path: nil, configuration:
|
|
47
|
+
def start_recording(output_path: nil, configuration: nil, upload: nil, timeout: 10, on_result: nil)
|
|
48
|
+
configuration ||= {}
|
|
47
49
|
invoke_audio_recorder(
|
|
48
50
|
"start_recording",
|
|
49
51
|
args: compact_args(
|
data/lib/ruflet_ui.rb
CHANGED
|
@@ -140,7 +140,7 @@ module Ruflet
|
|
|
140
140
|
private
|
|
141
141
|
|
|
142
142
|
def control_delegate
|
|
143
|
-
Ruflet::
|
|
143
|
+
Ruflet::WidgetBuilder.new
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
end
|
|
@@ -154,8 +154,12 @@ module Kernel
|
|
|
154
154
|
def app(**opts, &block) = Ruflet::DSL.app(**opts, &block)
|
|
155
155
|
def page(**props, &block) = Ruflet::DSL.page(**props, &block)
|
|
156
156
|
|
|
157
|
+
# Bare widget helpers (view, text, container, …) build real controls anywhere
|
|
158
|
+
# — top-level scripts and Ruflet.run blocks — with no builder to instantiate.
|
|
159
|
+
# The framework owns this plumbing so dev code stays free of it; a fresh
|
|
160
|
+
# builder per call keeps the helpers stateless.
|
|
157
161
|
def control_delegate
|
|
158
|
-
Ruflet::
|
|
162
|
+
Ruflet::WidgetBuilder.new
|
|
159
163
|
end
|
|
160
164
|
|
|
161
165
|
if Ruflet::UI::SharedControlForwarders.respond_to?(:instance_methods)
|