ruflet 0.0.2 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9723af806084c6004d763be0e114e70f7f6960282bf19c6f67e368a03cab840f
4
- data.tar.gz: ea6d88fc55552338f1e546c3ea03ba2013a9f7d43c11c91e60b56cef8efaa9f6
3
+ metadata.gz: 01d8a3779e874187cf24c4373efa47dbb8541c9495e8b54534d1c4028bf0b90c
4
+ data.tar.gz: 81d1d6959b1d6e144f24e3c6586f35b6c32fc5f971f039b702a607fe3ef1ea15
5
5
  SHA512:
6
- metadata.gz: 26d508da43b458e28a52837f79620aa0e9cb1790a3d71457a0f64ea695b5addd0ac055e895335c95776c5bbf4dd78bec4bba81deea3aecfc697cfebdf62ca5dd
7
- data.tar.gz: 69d7b02958213cc960ddd02b2f645a2694cb5be991a44af468d9f22335c1386916b054f918376fa3787d0e3a5267d7c75af5c5d8f2f6c8f1f22fec340eae9ec3
6
+ metadata.gz: 303761d68b2adda5d36edf77e6270c6668ca166defbc71035df969e012c7539bb6c39b9cd69a20df3ab2c849f1fa20cdb857b663db59eb274bcb36cae206661e
7
+ data.tar.gz: b0892bfbb584db2187782958b4d67c86ffbf2ed2899974389ac79b89d770d9898f099eef8ec42477d19b8104a4b5496ed7655b2882f4b7e149cd7d5f0a4b2428
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+
6
+ module Ruflet
7
+ module ManifestCompiler
8
+ module_function
9
+
10
+ def compile_app(app, route: "/")
11
+ messages = []
12
+ sender = lambda do |action, payload|
13
+ messages << { "action" => action, "payload" => payload }
14
+ end
15
+
16
+ page = Ruflet::Page.new(
17
+ session_id: "manifest",
18
+ client_details: { "route" => route.to_s.empty? ? "/" : route.to_s },
19
+ sender: sender
20
+ )
21
+
22
+ app.view(page)
23
+ page.update
24
+
25
+ compacted = compact_messages(messages)
26
+
27
+ {
28
+ "schema" => "ruflet_manifest/v1",
29
+ "generated_at" => Time.now.utc.iso8601,
30
+ "route" => page.route || "/",
31
+ "messages" => compacted
32
+ }
33
+ end
34
+
35
+ def write_file(path, manifest)
36
+ File.write(path, JSON.pretty_generate(manifest))
37
+ path
38
+ end
39
+
40
+ def read_file(path)
41
+ JSON.parse(File.read(path.to_s))
42
+ end
43
+
44
+ def compact_messages(messages)
45
+ full_patch_index = nil
46
+ messages.each_with_index do |message, idx|
47
+ next unless message["action"] == Ruflet::Protocol::ACTIONS[:patch_control]
48
+
49
+ payload = message["payload"] || {}
50
+ next unless payload["id"] == 1
51
+
52
+ patch = payload["patch"]
53
+ next unless patch.is_a?(Array) && patch.any? { |op| op.is_a?(Array) && op[2] == "views" }
54
+
55
+ full_patch_index = idx
56
+ end
57
+ return messages if full_patch_index.nil?
58
+
59
+ [messages[full_patch_index]]
60
+ end
61
+ end
62
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.2" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.4" unless const_defined?(:VERSION)
5
5
  end
data/lib/ruflet.rb CHANGED
@@ -11,12 +11,6 @@ module Ruflet
11
11
  module_function
12
12
 
13
13
  def run(entrypoint = nil, host: "0.0.0.0", port: 8550, &block)
14
- begin
15
- require "ruflet_server"
16
- rescue LoadError => e
17
- raise LoadError, "Ruflet.run requires the 'ruflet_server' gem. Add it to your Gemfile.", e.backtrace
18
- end
19
-
20
14
  callback = entrypoint || block
21
15
  raise ArgumentError, "Ruflet.run requires a callable entrypoint or block" unless callback.respond_to?(:call)
22
16
 
@@ -26,6 +20,12 @@ module Ruflet
26
20
  return result unless result == :pass
27
21
  end
28
22
 
23
+ begin
24
+ require "ruflet_server"
25
+ rescue LoadError => e
26
+ raise LoadError, "Ruflet.run requires the 'ruflet_server' gem unless a run interceptor handles execution.", e.backtrace
27
+ end
28
+
29
29
  Server.new(host: host, port: port) do |page|
30
30
  callback.call(page)
31
31
  end.start
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require_relative "../../ruflet/manifest_compiler"
2
3
 
3
4
  module Ruflet
4
5
  class App
@@ -8,6 +9,16 @@ module Ruflet
8
9
  end
9
10
 
10
11
  def run
12
+ manifest_out = ENV["RUFLET_MANIFEST_OUT"].to_s
13
+ unless manifest_out.empty?
14
+ route = ENV["RUFLET_MANIFEST_ROUTE"].to_s
15
+ route = "/" if route.empty?
16
+ manifest = Ruflet::ManifestCompiler.compile_app(self, route: route)
17
+ Ruflet::ManifestCompiler.write_file(manifest_out, manifest)
18
+ puts manifest_out
19
+ return manifest_out
20
+ end
21
+
11
22
  Ruflet.run(host: @host, port: @port) do |page|
12
23
  view(page)
13
24
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Ruflet
4
4
  module Colors
5
- module_function
6
-
7
5
  SEMANTIC_COLORS = {
8
6
  PRIMARY: "primary",
9
7
  ON_PRIMARY: "onprimary",
@@ -127,14 +125,32 @@ module Ruflet
127
125
  end
128
126
 
129
127
  def all_values
130
- @all_values ||= constants(false)
131
- .map { |c| const_get(c) }
132
- .select { |v| v.is_a?(String) }
133
- .uniq
134
- .freeze
128
+ return @all_values if @all_values
129
+
130
+ values = []
131
+ SEMANTIC_COLORS.each_value { |v| values << v }
132
+ FIXED_COLORS.each_value { |v| values << v }
133
+
134
+ BASE_PRIMARY.each do |base|
135
+ values << base
136
+ PRIMARY_SHADES.each do |shade|
137
+ values << "#{base}#{shade}"
138
+ end
139
+ end
140
+
141
+ BASE_ACCENT.each do |base|
142
+ values << "#{base}accent"
143
+ ACCENT_SHADES.each do |shade|
144
+ values << "#{base}accent#{shade}"
145
+ end
146
+ end
147
+
148
+ uniq_map = {}
149
+ values.each { |v| uniq_map[v] = true }
150
+ @all_values = uniq_map.keys.freeze
135
151
  end
136
152
 
137
- def normalize_color(color)
153
+ def self.normalize_color(color)
138
154
  return color.to_s if color.is_a?(Symbol)
139
155
  return color if color.is_a?(String)
140
156
  return color.to_s unless color.respond_to?(:to_s)
@@ -164,15 +180,17 @@ module Ruflet
164
180
  "yellow" => "YELLOW"
165
181
  }.freeze
166
182
 
167
- def constant_prefix_for(base_name)
168
- BASE_PREFIX.fetch(base_name) { base_name.upcase }
183
+ def self.constant_prefix_for(base_name)
184
+ key = base_name.to_s
185
+ return BASE_PREFIX[key] if BASE_PREFIX.key?(key)
186
+ key.upcase
169
187
  end
170
188
 
171
189
  SEMANTIC_COLORS.each { |k, v| const_set(k, v) }
172
190
  FIXED_COLORS.each { |k, v| const_set(k, v) }
173
191
 
174
192
  BASE_PRIMARY.each do |base|
175
- prefix = constant_prefix_for(base)
193
+ prefix = self.constant_prefix_for(base)
176
194
  const_set(prefix, base)
177
195
  PRIMARY_SHADES.each do |shade|
178
196
  const_set("#{prefix}_#{shade}", "#{base}#{shade}")
@@ -180,7 +198,7 @@ module Ruflet
180
198
  end
181
199
 
182
200
  BASE_ACCENT.each do |base|
183
- prefix = "#{constant_prefix_for(base)}_ACCENT"
201
+ prefix = "#{self.constant_prefix_for(base)}_ACCENT"
184
202
  const_set(prefix, "#{base}accent")
185
203
  ACCENT_SHADES.each do |shade|
186
204
  const_set("#{prefix}_#{shade}", "#{base}accent#{shade}")
@@ -191,10 +209,26 @@ module Ruflet
191
209
  const_set(alias_name, const_get(target))
192
210
  end
193
211
 
194
- constants(false).each do |name|
195
- next if respond_to?(name)
196
-
197
- define_singleton_method(name) { const_get(name) }
212
+ constant_names = []
213
+ SEMANTIC_COLORS.each_key { |k| constant_names << k }
214
+ FIXED_COLORS.each_key { |k| constant_names << k }
215
+ BASE_PRIMARY.each do |base|
216
+ constant_names << constant_prefix_for(base).to_sym
217
+ PRIMARY_SHADES.each { |shade| constant_names << "#{constant_prefix_for(base)}_#{shade}".to_sym }
218
+ end
219
+ BASE_ACCENT.each do |base|
220
+ constant_names << "#{constant_prefix_for(base)}_ACCENT".to_sym
221
+ ACCENT_SHADES.each { |shade| constant_names << "#{constant_prefix_for(base)}_ACCENT_#{shade}".to_sym }
222
+ end
223
+ DEPRECATED_ALIASES.each_key { |k| constant_names << k }
224
+
225
+ uniq_constants = {}
226
+ constant_names.each { |n| uniq_constants[n] = true }
227
+ if respond_to?(:define_singleton_method)
228
+ uniq_constants.keys.each do |name|
229
+ next if respond_to?(name)
230
+ define_singleton_method(name) { const_get(name) }
231
+ end
198
232
  end
199
233
  end
200
234
  end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securerandom"
3
+ begin
4
+ require "securerandom"
5
+ rescue LoadError
6
+ nil
7
+ end
4
8
  require_relative "ui/control_registry"
5
9
  require_relative "icon_data"
6
10
  require_relative "icons/material_icon_lookup"
@@ -17,7 +21,7 @@ module Ruflet
17
21
 
18
22
  def initialize(type:, id: nil, **props)
19
23
  @type = type.to_s.downcase
20
- @id = (id || props.delete(:id) || "ctrl_#{SecureRandom.hex(4)}").to_s
24
+ @id = (id || props.delete(:id) || "ctrl_#{self.class.generate_id}").to_s
21
25
  @children = []
22
26
  @handlers = {}
23
27
  @wire_id = nil
@@ -62,6 +66,16 @@ module Ruflet
62
66
 
63
67
  private
64
68
 
69
+ class << self
70
+ def generate_id
71
+ if defined?(SecureRandom) && SecureRandom.respond_to?(:hex)
72
+ SecureRandom.hex(4)
73
+ else
74
+ format("%08x", rand(0..0xffff_ffff))
75
+ end
76
+ end
77
+ end
78
+
65
79
  def serialize_value(value)
66
80
  case value
67
81
  when Control
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "icons/material_icon_lookup"
4
- require_relative "icons/cupertino_icon_lookup"
3
+ begin
4
+ require_relative "icons/material_icon_lookup"
5
+ require_relative "icons/cupertino_icon_lookup"
6
+ rescue StandardError
7
+ nil
8
+ end
5
9
 
6
10
  module Ruflet
7
11
  class IconData
@@ -44,7 +48,8 @@ module Ruflet
44
48
  return codepoint
45
49
  end
46
50
 
47
- raw = input.to_s.strip
51
+ raw = input.to_s
52
+ raw = raw.strip if raw.respond_to?(:strip)
48
53
  return raw if raw.empty?
49
54
 
50
55
  codepoint = Ruflet::MaterialIconLookup.codepoint_for(raw)
@@ -7,14 +7,43 @@ require_relative "ui/control_methods"
7
7
  require_relative "ui/widget_builder"
8
8
  require_relative "icons/material_icon_lookup"
9
9
  require_relative "icons/cupertino_icon_lookup"
10
- require "set"
11
- require "cgi"
10
+ begin
11
+ require "set"
12
+ rescue LoadError
13
+ class Set
14
+ def initialize
15
+ @index = {}
16
+ end
17
+
18
+ def include?(value)
19
+ @index.key?(value)
20
+ end
21
+
22
+ def <<(value)
23
+ @index[value] = true
24
+ self
25
+ end
26
+ end
27
+ end
28
+
29
+ begin
30
+ require "cgi"
31
+ rescue LoadError
32
+ module CGI
33
+ module_function
34
+
35
+ def escape(text)
36
+ value = text.to_s
37
+ value.gsub(/[^a-zA-Z0-9_.~-]/) { |ch| "%%%02X" % ch.ord }
38
+ end
39
+ end
40
+ end
12
41
 
13
42
  module Ruflet
14
43
  class Page
15
44
  include UI::ControlMethods
16
45
 
17
- PAGE_PROP_KEYS = %w[route title vertical_alignment horizontal_alignment].freeze
46
+ PAGE_PROP_KEYS = %w[route title vertical_alignment horizontal_alignment scroll].freeze
18
47
  DIALOG_PROP_KEYS = %w[dialog snack_bar bottom_sheet].freeze
19
48
  BUTTON_TEXT_TYPES = %w[button elevatedbutton textbutton filledbutton].freeze
20
49
 
@@ -39,12 +68,18 @@ module Ruflet
39
68
  id: "_overlay",
40
69
  controls: []
41
70
  )
71
+ @services_container = Ruflet::Control.new(
72
+ type: "service_registry",
73
+ id: "_services",
74
+ "_services": []
75
+ )
42
76
  @dialogs_container = Ruflet::Control.new(
43
77
  type: "dialogs",
44
78
  id: "_dialogs",
45
79
  controls: []
46
80
  )
47
81
  refresh_overlay_container!
82
+ refresh_services_container!
48
83
  refresh_dialogs_container!
49
84
  end
50
85
 
@@ -69,6 +104,16 @@ module Ruflet
69
104
  @page_props["route"] = value
70
105
  end
71
106
 
107
+ def scroll
108
+ @page_props["scroll"]
109
+ end
110
+
111
+ def scroll=(value)
112
+ v = normalize_value("scroll", value)
113
+ @page_props["scroll"] = v
114
+ @view_props["scroll"] = v
115
+ end
116
+
72
117
  def vertical_alignment
73
118
  @page_props["vertical_alignment"] || @view_props["vertical_alignment"]
74
119
  end
@@ -118,6 +163,24 @@ module Ruflet
118
163
  self
119
164
  end
120
165
 
166
+ def services
167
+ @services_container.props["_services"] ||= []
168
+ end
169
+
170
+ def services=(value)
171
+ @services_container.props["_services"] = Array(value).compact
172
+ refresh_services_container!
173
+ push_services_update!
174
+ self
175
+ end
176
+
177
+ def add_service(*value)
178
+ @services_container.props["_services"] = services + value.flatten.compact
179
+ refresh_services_container!
180
+ push_services_update!
181
+ self
182
+ end
183
+
121
184
  def views=(value)
122
185
  @views = Array(value).compact
123
186
  self
@@ -232,6 +295,7 @@ module Ruflet
232
295
  return nil unless dialog_control
233
296
 
234
297
  dialog_control.props["open"] = false
298
+ @dialogs.delete(dialog_control)
235
299
  refresh_dialogs_container!
236
300
  push_dialogs_update!
237
301
  dialog_control
@@ -252,7 +316,21 @@ module Ruflet
252
316
  control = resolve_control(control_or_id)
253
317
  return self unless control
254
318
 
255
- patch = normalize_props(props)
319
+ visited = Set.new
320
+ props.each_value { |value| register_embedded_value(value, visited) }
321
+
322
+ raw_props = props.dup
323
+ if BUTTON_TEXT_TYPES.include?(control.type)
324
+ if raw_props.key?(:text) || raw_props.key?("text")
325
+ text_value = raw_props.key?(:text) ? raw_props.delete(:text) : raw_props.delete("text")
326
+ raw_props[:content] = text_value unless raw_props.key?(:content) || raw_props.key?("content")
327
+ end
328
+ end
329
+
330
+ normalized_control_props = control.send(:normalize_props, raw_props)
331
+ normalized_control_props.each { |k, v| control.props[k] = v }
332
+
333
+ patch = normalize_props(raw_props)
256
334
  if BUTTON_TEXT_TYPES.include?(control.type) && patch.key?("text")
257
335
  patch["content"] = patch.delete("text")
258
336
  end
@@ -267,6 +345,22 @@ module Ruflet
267
345
  self
268
346
  end
269
347
 
348
+ def invoke(control_or_id, method_name, args: nil, timeout: 10)
349
+ control = resolve_control(control_or_id)
350
+ return nil unless control
351
+
352
+ call_id = "call_#{Ruflet::Control.generate_id}"
353
+ send_message(Protocol::ACTIONS[:invoke_control_method], {
354
+ "control_id" => control.wire_id,
355
+ "call_id" => call_id,
356
+ "name" => method_name.to_s,
357
+ "args" => args,
358
+ "timeout" => timeout
359
+ })
360
+
361
+ call_id
362
+ end
363
+
270
364
  def patch_page(control_id, **props)
271
365
  update(control_id, **props)
272
366
  end
@@ -395,7 +489,7 @@ module Ruflet
395
489
  def resolve_control(control_or_id)
396
490
  if control_or_id.respond_to?(:wire_id)
397
491
  control_or_id
398
- elsif control_or_id.to_s.match?(/^\d+$/)
492
+ elsif numeric_string?(control_or_id.to_s)
399
493
  @wire_index[control_or_id.to_i]
400
494
  else
401
495
  @control_index[control_or_id.to_s]
@@ -415,10 +509,32 @@ module Ruflet
415
509
  return codepoint unless codepoint.nil?
416
510
  end
417
511
 
512
+ if value.is_a?(Ruflet::Control)
513
+ register_control_tree(value, Set.new)
514
+ return value.to_patch
515
+ end
516
+ return serialize_value(value) if value.is_a?(Array) || value.is_a?(Hash)
517
+
418
518
  return value.value if value.is_a?(Ruflet::IconData)
419
519
  value.is_a?(Symbol) ? value.to_s : value
420
520
  end
421
521
 
522
+ def serialize_value(value)
523
+ case value
524
+ when Ruflet::Control
525
+ register_control_tree(value, Set.new)
526
+ value.to_patch
527
+ when Ruflet::IconData
528
+ value.value
529
+ when Array
530
+ value.map { |v| serialize_value(v) }
531
+ when Hash
532
+ value.transform_values { |v| serialize_value(v) }
533
+ else
534
+ value
535
+ end
536
+ end
537
+
422
538
  def build_route(route, query_params = {})
423
539
  base = route.to_s
424
540
  return base if query_params.nil? || query_params.empty?
@@ -470,6 +586,12 @@ module Ruflet
470
586
  key == "icon" || key.end_with?("_icon")
471
587
  end
472
588
 
589
+ def numeric_string?(value)
590
+ return false if value.empty?
591
+ value.each_byte { |b| return false unless b >= 0x30 && b <= 0x39 }
592
+ true
593
+ end
594
+
473
595
  def refresh_dialogs_container!
474
596
  dialog_controls = (@dialogs + dialog_slots).uniq
475
597
  @dialogs_container.props["controls"] = dialog_controls
@@ -480,6 +602,23 @@ module Ruflet
480
602
  @page_props["_overlay"] = @overlay_container
481
603
  end
482
604
 
605
+ def refresh_services_container!
606
+ @page_props["_services"] = @services_container
607
+ end
608
+
609
+ def push_services_update!
610
+ refresh_control_indexes!
611
+
612
+ if @services_container.wire_id
613
+ send_message(Protocol::ACTIONS[:patch_control], {
614
+ "id" => @services_container.wire_id,
615
+ "patch" => [[0], [0, 0, "_services", serialize_patch_value(@services_container.props["_services"])]]
616
+ })
617
+ else
618
+ send_view_patch
619
+ end
620
+ end
621
+
483
622
  def push_dialogs_update!
484
623
  refresh_control_indexes!
485
624
 
@@ -540,10 +679,24 @@ module Ruflet
540
679
  end
541
680
 
542
681
  def resolve_icon_codepoint(value)
543
- codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
544
- if codepoint.nil? || codepoint == value
545
- codepoint = Ruflet::CupertinoIconLookup.codepoint_for(value)
682
+ return nil unless value.is_a?(Integer) || value.is_a?(Symbol) || value.is_a?(String)
683
+
684
+ codepoint = nil
685
+ begin
686
+ codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
687
+ rescue NameError
688
+ codepoint = nil
546
689
  end
690
+
691
+ if codepoint.nil? || (value.is_a?(Integer) && codepoint == value)
692
+ begin
693
+ cupertino = Ruflet::CupertinoIconLookup.codepoint_for(value)
694
+ codepoint = cupertino unless cupertino.nil?
695
+ rescue NameError
696
+ codepoint = nil
697
+ end
698
+ end
699
+
547
700
  codepoint
548
701
  end
549
702
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "material_control_factory"
4
4
  require_relative "cupertino_control_factory"
5
+ require_relative "../control"
5
6
 
6
7
  module Ruflet
7
8
  module UI
@@ -13,9 +14,9 @@ module Ruflet
13
14
  def build(type, id: nil, **props)
14
15
  normalized_type = type.to_s.downcase
15
16
  klass = CLASS_MAP[normalized_type]
16
- raise ArgumentError, "Unsupported control type: #{normalized_type}" unless klass
17
+ return klass.new(id: id, **props) if klass
17
18
 
18
- klass.new(id: id, **props)
19
+ Ruflet::Control.new(type: normalized_type, id: id, **props)
19
20
  end
20
21
  end
21
22
  end
@@ -7,6 +7,17 @@ module Ruflet
7
7
  def initialize(id: nil, **props)
8
8
  super(type: "cupertino_dialog_action", id: id, **props)
9
9
  end
10
+
11
+ private
12
+
13
+ def preprocess_props(props)
14
+ mapped = props.dup
15
+ if mapped.key?(:text) || mapped.key?("text")
16
+ value = mapped.key?(:text) ? mapped.delete(:text) : mapped.delete("text")
17
+ mapped[:content] = value unless mapped.key?(:content) || mapped.key?("content")
18
+ end
19
+ mapped
20
+ end
10
21
  end
11
22
  end
12
23
  end
@@ -8,18 +8,32 @@ module Ruflet
8
8
  "cupertinobutton" => "CupertinoButton",
9
9
  "cupertino_filled_button" => "CupertinoFilledButton",
10
10
  "cupertinofilledbutton" => "CupertinoFilledButton",
11
+ "cupertino_tinted_button" => "CupertinoTintedButton",
12
+ "cupertinotintedbutton" => "CupertinoTintedButton",
11
13
  "cupertino_text_field" => "CupertinoTextField",
12
14
  "cupertinotextfield" => "CupertinoTextField",
15
+ "cupertino_checkbox" => "CupertinoCheckbox",
16
+ "cupertinocheckbox" => "CupertinoCheckbox",
13
17
  "cupertino_switch" => "CupertinoSwitch",
14
18
  "cupertinoswitch" => "CupertinoSwitch",
15
19
  "cupertino_slider" => "CupertinoSlider",
16
20
  "cupertinoslider" => "CupertinoSlider",
21
+ "cupertino_radio" => "CupertinoRadio",
22
+ "cupertinoradio" => "CupertinoRadio",
17
23
  "cupertino_alert_dialog" => "CupertinoAlertDialog",
18
24
  "cupertinoalertdialog" => "CupertinoAlertDialog",
19
25
  "cupertino_action_sheet" => "CupertinoActionSheet",
20
26
  "cupertinoactionsheet" => "CupertinoActionSheet",
21
27
  "cupertino_dialog_action" => "CupertinoDialogAction",
22
28
  "cupertinodialogaction" => "CupertinoDialogAction",
29
+ "cupertino_bottom_sheet" => "CupertinoBottomSheet",
30
+ "cupertinobottomsheet" => "CupertinoBottomSheet",
31
+ "cupertino_picker" => "CupertinoPicker",
32
+ "cupertinopicker" => "CupertinoPicker",
33
+ "cupertino_date_picker" => "CupertinoDatePicker",
34
+ "cupertinodatepicker" => "CupertinoDatePicker",
35
+ "cupertino_timer_picker" => "CupertinoTimerPicker",
36
+ "cupertinotimerpicker" => "CupertinoTimerPicker",
23
37
  "cupertino_navigation_bar" => "CupertinoNavigationBar",
24
38
  "cupertinonavigationbar" => "CupertinoNavigationBar"
25
39
  }.freeze
@@ -71,14 +71,17 @@ module Ruflet
71
71
 
72
72
  def icon(**props) = build_widget(:icon, **props)
73
73
 
74
- def image(src = nil, **props)
74
+ def image(src = nil, src_base64: nil, placeholder_src: nil, **props)
75
75
  mapped = props.dup
76
- mapped[:src] = src unless src.nil?
76
+ mapped[:src] = normalize_image_source(src) unless src.nil?
77
+ mapped[:src] = normalize_image_source(src_base64) if mapped[:src].nil? && !src_base64.nil?
78
+ mapped[:placeholder_src] = normalize_image_source(placeholder_src) unless placeholder_src.nil?
77
79
  build_widget(:image, **mapped)
78
80
  end
79
81
 
80
82
  def app_bar(**props) = build_widget(:appbar, **props)
81
83
  def appbar(**props) = app_bar(**props)
84
+ def url_launcher(**props) = build_widget(:url_launcher, **props)
82
85
  def floating_action_button(**props) = build_widget(:floatingactionbutton, **props)
83
86
  def floatingactionbutton(**props) = floating_action_button(**props)
84
87
  def tabs(**props, &block) = build_widget(:tabs, **props, &block)
@@ -100,6 +103,12 @@ module Ruflet
100
103
 
101
104
  private
102
105
 
106
+ def normalize_image_source(value)
107
+ return value unless value.is_a?(Array)
108
+ return value.pack("C*") if value.all? { |v| v.is_a?(Integer) }
109
+ value
110
+ end
111
+
103
112
  # Flet container alignment expects a vector-like object ({x:, y:}),
104
113
  # not a plain string. Keep common shorthand compatible.
105
114
  def normalize_container_props(props)
@@ -52,7 +52,55 @@ module Ruflet
52
52
  "navigationbar" => "NavigationBar",
53
53
  "navigation_bar" => "NavigationBar",
54
54
  "navigationbardestination" => "NavigationBarDestination",
55
- "navigation_bar_destination" => "NavigationBarDestination"
55
+ "navigation_bar_destination" => "NavigationBarDestination",
56
+ "switch" => "Switch",
57
+ "slider" => "Slider",
58
+ "dropdown" => "DropdownM2",
59
+ "dropdownm2" => "DropdownM2",
60
+ "dropdown_m2" => "DropdownM2",
61
+ "option" => "Option",
62
+ "card" => "Card",
63
+ "banner" => "Banner",
64
+ "datepicker" => "DatePicker",
65
+ "date_picker" => "DatePicker",
66
+ "timepicker" => "TimePicker",
67
+ "time_picker" => "TimePicker",
68
+ "filledtonalbutton" => "FilledTonalButton",
69
+ "filled_tonal_button" => "FilledTonalButton",
70
+ "outlinedbutton" => "OutlinedButton",
71
+ "outlined_button" => "OutlinedButton",
72
+ "listtile" => "ListTile",
73
+ "list_tile" => "ListTile",
74
+ "progressbar" => "ProgressBar",
75
+ "progress_bar" => "ProgressBar",
76
+ "safearea" => "SafeArea",
77
+ "safe_area" => "SafeArea",
78
+ "canvas" => "Canvas",
79
+ "line" => "Line",
80
+ "service_registry" => "ServiceRegistry",
81
+ "url_launcher" => "UrlLauncher",
82
+ "audio" => "Audio",
83
+ "video" => "Video",
84
+ "flashlight" => "Flashlight",
85
+ "barchart" => "BarChart",
86
+ "barchartgroup" => "BarChartGroup",
87
+ "barchartrod" => "BarChartRod",
88
+ "barchartrodstackitem" => "BarChartRodStackItem",
89
+ "linechart" => "LineChart",
90
+ "linechartdata" => "LineChartData",
91
+ "linechartdatapoint" => "LineChartDataPoint",
92
+ "piechart" => "PieChart",
93
+ "piechartsection" => "PieChartSection",
94
+ "candlestickchart" => "CandlestickChart",
95
+ "candlestickchartspot" => "CandlestickChartSpot",
96
+ "radarchart" => "RadarChart",
97
+ "radarcharttitle" => "RadarChartTitle",
98
+ "radardataset" => "RadarDataSet",
99
+ "radardatasetentry" => "RadarDataSetEntry",
100
+ "scatterchart" => "ScatterChart",
101
+ "scatterchartspot" => "ScatterChartSpot",
102
+ "chartaxis" => "ChartAxis",
103
+ "chartaxislabel" => "ChartAxisLabel"
56
104
  }.freeze
57
105
 
58
106
  EVENT_PROPS = {
@@ -77,6 +125,21 @@ module Ruflet
77
125
  on_horizontal_drag_start: "horizontal_drag_start",
78
126
  on_horizontal_drag_update: "horizontal_drag_update",
79
127
  on_horizontal_drag_end: "horizontal_drag_end",
128
+ on_tap_down: "tap_down",
129
+ on_long_press_start: "long_press_start",
130
+ on_right_pan_start: "right_pan_start",
131
+ on_event: "event",
132
+ on_load: "load",
133
+ on_loaded: "loaded",
134
+ on_enter_fullscreen: "enter_fullscreen",
135
+ on_exit_fullscreen: "exit_fullscreen",
136
+ on_duration_change: "duration_change",
137
+ on_position_change: "position_change",
138
+ on_state_change: "state_change",
139
+ on_seek_complete: "seek_complete",
140
+ on_complete: "complete",
141
+ on_track_change: "track_change",
142
+ on_error: "error",
80
143
  on_accept: "accept",
81
144
  on_will_accept: "will_accept",
82
145
  on_accept_with_details: "accept_with_details",
data/lib/ruflet_ui.rb CHANGED
@@ -40,9 +40,6 @@ module Ruflet
40
40
  end
41
41
 
42
42
  module Icons
43
- REMOVE = MaterialIcons::REMOVE
44
- ADD = MaterialIcons::ADD
45
-
46
43
  class << self
47
44
  def const_missing(name)
48
45
  if Ruflet::MaterialIcons.const_defined?(name, false)
@@ -108,5 +105,7 @@ module Kernel
108
105
  Ruflet::DSL
109
106
  end
110
107
 
111
- private(*Ruflet::UI::SharedControlForwarders.instance_methods(false))
108
+ if Ruflet::UI::SharedControlForwarders.respond_to?(:instance_methods)
109
+ private(*Ruflet::UI::SharedControlForwarders.instance_methods(false))
110
+ end
112
111
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruflet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa
@@ -19,6 +19,7 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - README.md
21
21
  - lib/ruflet.rb
22
+ - lib/ruflet/manifest_compiler.rb
22
23
  - lib/ruflet/version.rb
23
24
  - lib/ruflet_protocol.rb
24
25
  - lib/ruflet_protocol/ruflet/protocol.rb