ruflet 0.0.1
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 +7 -0
- data/README.md +3 -0
- data/lib/ruflet/version.rb +5 -0
- data/lib/ruflet.rb +40 -0
- data/lib/ruflet_protocol/ruflet/protocol.rb +62 -0
- data/lib/ruflet_protocol.rb +4 -0
- data/lib/ruflet_ui/ruflet/app.rb +20 -0
- data/lib/ruflet_ui/ruflet/colors.rb +200 -0
- data/lib/ruflet_ui/ruflet/control.rb +154 -0
- data/lib/ruflet_ui/ruflet/dsl.rb +182 -0
- data/lib/ruflet_ui/ruflet/event.rb +28 -0
- data/lib/ruflet_ui/ruflet/icon_data.rb +57 -0
- data/lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb +54 -0
- data/lib/ruflet_ui/ruflet/icons/cupertino_icon_lookup.rb +112 -0
- data/lib/ruflet_ui/ruflet/icons/material_icon_lookup.rb +112 -0
- data/lib/ruflet_ui/ruflet/icons/material_icons.rb +55 -0
- data/lib/ruflet_ui/ruflet/page.rb +550 -0
- data/lib/ruflet_ui/ruflet/ui/control_factory.rb +22 -0
- data/lib/ruflet_ui/ruflet/ui/control_methods.rb +16 -0
- data/lib/ruflet_ui/ruflet/ui/control_registry.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_action_sheet_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_alert_dialog_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_dialog_action_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_filled_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_navigation_bar_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_slider_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_switch_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_text_field_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/alert_dialog_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/app_bar_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/bottom_sheet_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/button_control.rb +24 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/checkbox_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/column_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/container_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/drag_target_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/draggable_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/elevated_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/filled_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/floating_action_button_control.rb +28 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/gesture_detector_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/icon_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/icon_control.rb +24 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/image_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/markdown_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_destination_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/radio_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/radio_group_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/row_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/snack_bar_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/stack_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_view_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/tab_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/tabs_control.rb +63 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_button_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/text_field_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/controls/material/view_control.rb +13 -0
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_factory.rb +40 -0
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_methods.rb +26 -0
- data/lib/ruflet_ui/ruflet/ui/cupertino_control_registry.rb +35 -0
- data/lib/ruflet_ui/ruflet/ui/material_control_factory.rb +94 -0
- data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +116 -0
- data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +88 -0
- data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +85 -0
- data/lib/ruflet_ui/ruflet/ui/widget_builder.rb +48 -0
- data/lib/ruflet_ui.rb +112 -0
- metadata +109 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a46d9a04d401502b437f5f1f4bd16825d70fd83a0742f2222be7b3f44779806e
|
|
4
|
+
data.tar.gz: 6cd952e7b2cfe71fb900a9e6bfcab4459e0f5a573013ea45ef61c5fc5557374d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b2357c3392cc63a15cc83efd9cab123bccc80eba1de9a7363e969b608268fc24a1741dae5f90ea1a2a15db6452c17a7ba5d97a2e45457e82f97222e221c62892
|
|
7
|
+
data.tar.gz: cdeea6994f53e37c3a622f9b0997a9b8f5bed7c4b49af60cfd0bfe0b37e9e1ea569bc242cdc95cadbc908b81aa0315e27eb957f34cfb00ea5f18846f2f77e57f
|
data/README.md
ADDED
data/lib/ruflet.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thread"
|
|
4
|
+
require "ruflet_protocol"
|
|
5
|
+
require "ruflet_ui"
|
|
6
|
+
|
|
7
|
+
module Ruflet
|
|
8
|
+
@run_interceptors = []
|
|
9
|
+
@run_interceptors_mutex = Mutex.new
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
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
|
+
callback = entrypoint || block
|
|
21
|
+
raise ArgumentError, "Ruflet.run requires a callable entrypoint or block" unless callback.respond_to?(:call)
|
|
22
|
+
|
|
23
|
+
interceptor = @run_interceptors_mutex.synchronize { @run_interceptors.last }
|
|
24
|
+
if interceptor
|
|
25
|
+
result = interceptor.call(entrypoint: callback, host: host, port: port)
|
|
26
|
+
return result unless result == :pass
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Server.new(host: host, port: port) do |page|
|
|
30
|
+
callback.call(page)
|
|
31
|
+
end.start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with_run_interceptor(interceptor)
|
|
35
|
+
@run_interceptors_mutex.synchronize { @run_interceptors << interceptor }
|
|
36
|
+
yield
|
|
37
|
+
ensure
|
|
38
|
+
@run_interceptors_mutex.synchronize { @run_interceptors.delete(interceptor) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Protocol
|
|
5
|
+
ACTIONS = {
|
|
6
|
+
register_client: 1,
|
|
7
|
+
patch_control: 2,
|
|
8
|
+
control_event: 3,
|
|
9
|
+
update_control: 4,
|
|
10
|
+
invoke_control_method: 5,
|
|
11
|
+
session_crashed: 6,
|
|
12
|
+
|
|
13
|
+
# Legacy JSON protocol aliases kept for compatibility.
|
|
14
|
+
register_web_client: "registerWebClient",
|
|
15
|
+
page_event_from_web: "pageEventFromWeb",
|
|
16
|
+
update_control_props: "updateControlProps"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def pack_message(action:, payload:)
|
|
22
|
+
[action, payload]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalize_register_payload(payload)
|
|
26
|
+
page = payload["page"] || {}
|
|
27
|
+
{
|
|
28
|
+
"session_id" => payload["session_id"],
|
|
29
|
+
"page_name" => payload["page_name"] || "",
|
|
30
|
+
"route" => page["route"] || "/",
|
|
31
|
+
"width" => page["width"],
|
|
32
|
+
"height" => page["height"],
|
|
33
|
+
"platform" => page["platform"],
|
|
34
|
+
"platform_brightness" => page["platform_brightness"],
|
|
35
|
+
"media" => page["media"] || {}
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def normalize_control_event_payload(payload)
|
|
40
|
+
{
|
|
41
|
+
"target" => payload["target"] || payload["eventTarget"],
|
|
42
|
+
"name" => payload["name"] || payload["eventName"],
|
|
43
|
+
"data" => payload["data"] || payload["eventData"]
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def normalize_update_control_payload(payload)
|
|
48
|
+
{
|
|
49
|
+
"id" => payload["id"] || payload["target"] || payload["eventTarget"],
|
|
50
|
+
"props" => payload["props"].is_a?(Hash) ? payload["props"] : {}
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def register_response(session_id:)
|
|
55
|
+
{
|
|
56
|
+
"session_id" => session_id,
|
|
57
|
+
"page_patch" => {},
|
|
58
|
+
"error" => nil
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
class App
|
|
5
|
+
def initialize(host: "0.0.0.0", port: 8550)
|
|
6
|
+
@host = host
|
|
7
|
+
@port = port
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
Ruflet.run(host: @host, port: @port) do |page|
|
|
12
|
+
view(page)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view(_page)
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #view(page)"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Colors
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
SEMANTIC_COLORS = {
|
|
8
|
+
PRIMARY: "primary",
|
|
9
|
+
ON_PRIMARY: "onprimary",
|
|
10
|
+
PRIMARY_CONTAINER: "primarycontainer",
|
|
11
|
+
ON_PRIMARY_CONTAINER: "onprimarycontainer",
|
|
12
|
+
PRIMARY_FIXED: "primaryfixed",
|
|
13
|
+
PRIMARY_FIXED_DIM: "primaryfixeddim",
|
|
14
|
+
ON_PRIMARY_FIXED: "onprimaryfixed",
|
|
15
|
+
ON_PRIMARY_FIXED_VARIANT: "onprimaryfixedvariant",
|
|
16
|
+
SECONDARY: "secondary",
|
|
17
|
+
ON_SECONDARY: "onsecondary",
|
|
18
|
+
SECONDARY_CONTAINER: "secondarycontainer",
|
|
19
|
+
ON_SECONDARY_CONTAINER: "onsecondarycontainer",
|
|
20
|
+
SECONDARY_FIXED: "secondaryfixed",
|
|
21
|
+
SECONDARY_FIXED_DIM: "secondaryfixeddim",
|
|
22
|
+
ON_SECONDARY_FIXED: "onsecondaryfixed",
|
|
23
|
+
ON_SECONDARY_FIXED_VARIANT: "onsecondaryfixedvariant",
|
|
24
|
+
TERTIARY: "tertiary",
|
|
25
|
+
ON_TERTIARY: "ontertiary",
|
|
26
|
+
TERTIARY_CONTAINER: "tertiarycontainer",
|
|
27
|
+
ON_TERTIARY_CONTAINER: "ontertiarycontainer",
|
|
28
|
+
TERTIARY_FIXED: "tertiaryfixed",
|
|
29
|
+
TERTIARY_FIXED_DIM: "tertiaryfixeddim",
|
|
30
|
+
ON_TERTIARY_FIXED: "ontertiaryfixed",
|
|
31
|
+
ON_TERTIARY_FIXED_VARIANT: "ontertiaryfixedvariant",
|
|
32
|
+
ERROR: "error",
|
|
33
|
+
ON_ERROR: "onerror",
|
|
34
|
+
ERROR_CONTAINER: "errorcontainer",
|
|
35
|
+
ON_ERROR_CONTAINER: "onerrorcontainer",
|
|
36
|
+
SURFACE: "surface",
|
|
37
|
+
ON_SURFACE: "onsurface",
|
|
38
|
+
ON_SURFACE_VARIANT: "onsurfacevariant",
|
|
39
|
+
SURFACE_TINT: "surfacetint",
|
|
40
|
+
SURFACE_DIM: "surfacedim",
|
|
41
|
+
SURFACE_BRIGHT: "surfacebright",
|
|
42
|
+
SURFACE_CONTAINER: "surfacecontainer",
|
|
43
|
+
SURFACE_CONTAINER_LOW: "surfacecontainerlow",
|
|
44
|
+
SURFACE_CONTAINER_LOWEST: "surfacecontainerlowest",
|
|
45
|
+
SURFACE_CONTAINER_HIGH: "surfacecontainerhigh",
|
|
46
|
+
SURFACE_CONTAINER_HIGHEST: "surfacecontainerhighest",
|
|
47
|
+
OUTLINE: "outline",
|
|
48
|
+
OUTLINE_VARIANT: "outlinevariant",
|
|
49
|
+
SHADOW: "shadow",
|
|
50
|
+
SCRIM: "scrim",
|
|
51
|
+
INVERSE_SURFACE: "inversesurface",
|
|
52
|
+
ON_INVERSE_SURFACE: "oninversesurface",
|
|
53
|
+
INVERSE_PRIMARY: "inverseprimary"
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
BASE_PRIMARY = %w[
|
|
57
|
+
amber blue bluegrey brown cyan deeporange deeppurple green grey indigo lightblue
|
|
58
|
+
lightgreen lime orange pink purple red teal yellow
|
|
59
|
+
].freeze
|
|
60
|
+
BASE_ACCENT = %w[
|
|
61
|
+
amber blue cyan deeporange deeppurple green indigo lightblue lightgreen lime
|
|
62
|
+
orange pink purple red teal yellow
|
|
63
|
+
].freeze
|
|
64
|
+
PRIMARY_SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900].freeze
|
|
65
|
+
ACCENT_SHADES = [100, 200, 400, 700].freeze
|
|
66
|
+
|
|
67
|
+
FIXED_COLORS = {
|
|
68
|
+
BLACK: "black",
|
|
69
|
+
BLACK_12: "black12",
|
|
70
|
+
BLACK_26: "black26",
|
|
71
|
+
BLACK_38: "black38",
|
|
72
|
+
BLACK_45: "black45",
|
|
73
|
+
BLACK_54: "black54",
|
|
74
|
+
BLACK_87: "black87",
|
|
75
|
+
WHITE: "white",
|
|
76
|
+
WHITE_10: "white10",
|
|
77
|
+
WHITE_12: "white12",
|
|
78
|
+
WHITE_24: "white24",
|
|
79
|
+
WHITE_30: "white30",
|
|
80
|
+
WHITE_38: "white38",
|
|
81
|
+
WHITE_54: "white54",
|
|
82
|
+
WHITE_60: "white60",
|
|
83
|
+
WHITE_70: "white70",
|
|
84
|
+
TRANSPARENT: "transparent"
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
DEPRECATED_ALIASES = {
|
|
88
|
+
BLACK12: :BLACK_12,
|
|
89
|
+
BLACK26: :BLACK_26,
|
|
90
|
+
BLACK38: :BLACK_38,
|
|
91
|
+
BLACK45: :BLACK_45,
|
|
92
|
+
BLACK54: :BLACK_54,
|
|
93
|
+
BLACK87: :BLACK_87,
|
|
94
|
+
WHITE10: :WHITE_10,
|
|
95
|
+
WHITE12: :WHITE_12,
|
|
96
|
+
WHITE24: :WHITE_24,
|
|
97
|
+
WHITE30: :WHITE_30,
|
|
98
|
+
WHITE38: :WHITE_38,
|
|
99
|
+
WHITE54: :WHITE_54,
|
|
100
|
+
WHITE60: :WHITE_60,
|
|
101
|
+
WHITE70: :WHITE_70
|
|
102
|
+
}.freeze
|
|
103
|
+
|
|
104
|
+
def with_opacity(opacity, color)
|
|
105
|
+
value = Float(opacity)
|
|
106
|
+
raise ArgumentError, "opacity must be between 0.0 and 1.0 inclusive, got #{opacity}" unless value.between?(0.0, 1.0)
|
|
107
|
+
|
|
108
|
+
"#{normalize_color(color)},#{value}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def random(exclude: nil, weights: nil)
|
|
112
|
+
choices = all_values.dup
|
|
113
|
+
excluded = Array(exclude).map { |c| normalize_color(c) }
|
|
114
|
+
choices.reject! { |c| excluded.include?(c) }
|
|
115
|
+
return nil if choices.empty?
|
|
116
|
+
|
|
117
|
+
if weights && !weights.empty?
|
|
118
|
+
expanded = choices.flat_map do |color|
|
|
119
|
+
weight = weights.fetch(color, weights.fetch(color.to_sym, 1)).to_i rescue 1
|
|
120
|
+
weight = 1 if weight <= 0
|
|
121
|
+
[color] * weight
|
|
122
|
+
end
|
|
123
|
+
return expanded.sample
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
choices.sample
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
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
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def normalize_color(color)
|
|
138
|
+
return color.to_s if color.is_a?(Symbol)
|
|
139
|
+
return color if color.is_a?(String)
|
|
140
|
+
return color.to_s unless color.respond_to?(:to_s)
|
|
141
|
+
|
|
142
|
+
color.to_s
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
BASE_PREFIX = {
|
|
146
|
+
"amber" => "AMBER",
|
|
147
|
+
"blue" => "BLUE",
|
|
148
|
+
"bluegrey" => "BLUE_GREY",
|
|
149
|
+
"brown" => "BROWN",
|
|
150
|
+
"cyan" => "CYAN",
|
|
151
|
+
"deeporange" => "DEEP_ORANGE",
|
|
152
|
+
"deeppurple" => "DEEP_PURPLE",
|
|
153
|
+
"green" => "GREEN",
|
|
154
|
+
"grey" => "GREY",
|
|
155
|
+
"indigo" => "INDIGO",
|
|
156
|
+
"lightblue" => "LIGHT_BLUE",
|
|
157
|
+
"lightgreen" => "LIGHT_GREEN",
|
|
158
|
+
"lime" => "LIME",
|
|
159
|
+
"orange" => "ORANGE",
|
|
160
|
+
"pink" => "PINK",
|
|
161
|
+
"purple" => "PURPLE",
|
|
162
|
+
"red" => "RED",
|
|
163
|
+
"teal" => "TEAL",
|
|
164
|
+
"yellow" => "YELLOW"
|
|
165
|
+
}.freeze
|
|
166
|
+
|
|
167
|
+
def constant_prefix_for(base_name)
|
|
168
|
+
BASE_PREFIX.fetch(base_name) { base_name.upcase }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
SEMANTIC_COLORS.each { |k, v| const_set(k, v) }
|
|
172
|
+
FIXED_COLORS.each { |k, v| const_set(k, v) }
|
|
173
|
+
|
|
174
|
+
BASE_PRIMARY.each do |base|
|
|
175
|
+
prefix = constant_prefix_for(base)
|
|
176
|
+
const_set(prefix, base)
|
|
177
|
+
PRIMARY_SHADES.each do |shade|
|
|
178
|
+
const_set("#{prefix}_#{shade}", "#{base}#{shade}")
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
BASE_ACCENT.each do |base|
|
|
183
|
+
prefix = "#{constant_prefix_for(base)}_ACCENT"
|
|
184
|
+
const_set(prefix, "#{base}accent")
|
|
185
|
+
ACCENT_SHADES.each do |shade|
|
|
186
|
+
const_set("#{prefix}_#{shade}", "#{base}accent#{shade}")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
DEPRECATED_ALIASES.each do |alias_name, target|
|
|
191
|
+
const_set(alias_name, const_get(target))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
constants(false).each do |name|
|
|
195
|
+
next if respond_to?(name)
|
|
196
|
+
|
|
197
|
+
define_singleton_method(name) { const_get(name) }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "ui/control_registry"
|
|
5
|
+
require_relative "icon_data"
|
|
6
|
+
require_relative "icons/material_icon_lookup"
|
|
7
|
+
require_relative "icons/cupertino_icon_lookup"
|
|
8
|
+
|
|
9
|
+
module Ruflet
|
|
10
|
+
class Control
|
|
11
|
+
TYPE_MAP = UI::ControlRegistry::TYPE_MAP
|
|
12
|
+
EVENT_PROPS = UI::ControlRegistry::EVENT_PROPS
|
|
13
|
+
HOST_EXPANDED_TYPES = %w[view row column].freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :type, :id, :props, :children
|
|
16
|
+
attr_accessor :wire_id, :runtime_page
|
|
17
|
+
|
|
18
|
+
def initialize(type:, id: nil, **props)
|
|
19
|
+
@type = type.to_s.downcase
|
|
20
|
+
@id = (id || props.delete(:id) || "ctrl_#{SecureRandom.hex(4)}").to_s
|
|
21
|
+
@children = []
|
|
22
|
+
@handlers = {}
|
|
23
|
+
@wire_id = nil
|
|
24
|
+
@props = normalize_props(extract_handlers(preprocess_props(props)))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on(event_name, &block)
|
|
28
|
+
name = normalized_event_name(event_name)
|
|
29
|
+
@handlers[name] = block
|
|
30
|
+
@props["on_#{name}"] = true
|
|
31
|
+
runtime_page&.update(self, "on_#{name}": true) if wire_id
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def emit(event_name, event)
|
|
36
|
+
handler = @handlers[normalized_event_name(event_name)]
|
|
37
|
+
return false unless handler
|
|
38
|
+
|
|
39
|
+
handler.call(event)
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def has_handler?(event_name)
|
|
44
|
+
@handlers.key?(normalized_event_name(event_name))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_patch
|
|
48
|
+
patch = {
|
|
49
|
+
"_c" => TYPE_MAP.fetch(type, type.split("_").map(&:capitalize).join),
|
|
50
|
+
"_i" => wire_id
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
internals = {}
|
|
54
|
+
internals["host_positioned"] = true if type == "stack"
|
|
55
|
+
internals["host_expanded"] = true if HOST_EXPANDED_TYPES.include?(type)
|
|
56
|
+
patch["_internals"] = internals unless internals.empty?
|
|
57
|
+
|
|
58
|
+
props.each { |k, v| patch[k] = serialize_value(v) }
|
|
59
|
+
patch["controls"] = children.map(&:to_patch) unless children.empty?
|
|
60
|
+
patch
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def serialize_value(value)
|
|
66
|
+
case value
|
|
67
|
+
when Control
|
|
68
|
+
value.to_patch
|
|
69
|
+
when Ruflet::IconData
|
|
70
|
+
value.value
|
|
71
|
+
when Array
|
|
72
|
+
value.map { |v| serialize_value(v) }
|
|
73
|
+
when Hash
|
|
74
|
+
value.transform_values { |v| serialize_value(v) }
|
|
75
|
+
else
|
|
76
|
+
value
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def extract_handlers(input)
|
|
81
|
+
output = input.dup
|
|
82
|
+
|
|
83
|
+
EVENT_PROPS.each do |prop, event_name|
|
|
84
|
+
string_prop = prop.to_s
|
|
85
|
+
next unless output.key?(prop) || output.key?(string_prop)
|
|
86
|
+
|
|
87
|
+
handler = output.key?(prop) ? output.delete(prop) : output.delete(string_prop)
|
|
88
|
+
@handlers[event_name] = handler if handler.respond_to?(:call)
|
|
89
|
+
output["on_#{event_name}"] = true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
output
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def normalize_props(hash)
|
|
96
|
+
hash.each_with_object({}) do |(k, v), result|
|
|
97
|
+
key = k.to_s
|
|
98
|
+
mapped_key = key
|
|
99
|
+
value =
|
|
100
|
+
if v.is_a?(Symbol)
|
|
101
|
+
v.to_s
|
|
102
|
+
elsif v.is_a?(Ruflet::IconData)
|
|
103
|
+
v.value
|
|
104
|
+
else
|
|
105
|
+
v
|
|
106
|
+
end
|
|
107
|
+
value = normalize_icon_prop(mapped_key, value)
|
|
108
|
+
value = normalize_color_prop(mapped_key, value)
|
|
109
|
+
|
|
110
|
+
result[mapped_key] = value
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def preprocess_props(props)
|
|
115
|
+
props
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def normalize_color_prop(key, value)
|
|
119
|
+
return value unless value.is_a?(String)
|
|
120
|
+
return value.downcase if color_prop_key?(key)
|
|
121
|
+
|
|
122
|
+
value
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def color_prop_key?(key)
|
|
126
|
+
key == "color" || key == "bgcolor" || key.end_with?("_color")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def normalize_icon_prop(key, value)
|
|
130
|
+
return value unless icon_prop_key?(key)
|
|
131
|
+
codepoint = resolve_icon_codepoint(value)
|
|
132
|
+
codepoint.nil? ? value : codepoint
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def icon_prop_key?(key)
|
|
136
|
+
key == "icon" || key.end_with?("_icon")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def resolve_icon_codepoint(value)
|
|
140
|
+
return nil unless value.is_a?(Integer) || value.is_a?(Symbol) || value.is_a?(String)
|
|
141
|
+
|
|
142
|
+
codepoint = Ruflet::MaterialIconLookup.codepoint_for(value)
|
|
143
|
+
if codepoint.nil? || (value.is_a?(Integer) && codepoint == value)
|
|
144
|
+
cupertino = Ruflet::CupertinoIconLookup.codepoint_for(value)
|
|
145
|
+
codepoint = cupertino unless cupertino.nil?
|
|
146
|
+
end
|
|
147
|
+
codepoint
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def normalized_event_name(event_name)
|
|
151
|
+
event_name.to_s.sub(/\Aon_/, "")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ui/control_methods"
|
|
4
|
+
require_relative "ui/control_factory"
|
|
5
|
+
|
|
6
|
+
module Ruflet
|
|
7
|
+
module DSL
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def _pending_app
|
|
11
|
+
@_pending_app ||= App.new(host: "0.0.0.0", port: 8550)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def _reset_pending_app!
|
|
15
|
+
@_pending_app = App.new(host: "0.0.0.0", port: 8550)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def app(host: "0.0.0.0", port: 8550, &block)
|
|
19
|
+
return App.new(host: host, port: port).tap { |a| a.instance_eval(&block) } if block
|
|
20
|
+
|
|
21
|
+
pending = _pending_app
|
|
22
|
+
pending.set_endpoint!(host: host, port: port)
|
|
23
|
+
_reset_pending_app!
|
|
24
|
+
pending
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def page(**props, &block) = _pending_app.page(**props, &block)
|
|
28
|
+
def control(type, **props, &block) = _pending_app.control(type, **props, &block)
|
|
29
|
+
def widget(type, **props, &block) = _pending_app.widget(type, **props, &block)
|
|
30
|
+
def column(**props, &block) = _pending_app.column(**props, &block)
|
|
31
|
+
def center(**props, &block) = _pending_app.center(**props, &block)
|
|
32
|
+
def row(**props, &block) = _pending_app.row(**props, &block)
|
|
33
|
+
def stack(**props, &block) = _pending_app.stack(**props, &block)
|
|
34
|
+
def container(**props, &block) = _pending_app.container(**props, &block)
|
|
35
|
+
def gesture_detector(**props, &block) = _pending_app.gesture_detector(**props, &block)
|
|
36
|
+
def gesturedetector(**props, &block) = _pending_app.gesturedetector(**props, &block)
|
|
37
|
+
def draggable(**props, &block) = _pending_app.draggable(**props, &block)
|
|
38
|
+
def drag_target(**props, &block) = _pending_app.drag_target(**props, &block)
|
|
39
|
+
def dragtarget(**props, &block) = _pending_app.dragtarget(**props, &block)
|
|
40
|
+
def text(value = nil, **props) = _pending_app.text(value, **props)
|
|
41
|
+
def button(**props) = _pending_app.button(**props)
|
|
42
|
+
def elevated_button(**props) = _pending_app.elevated_button(**props)
|
|
43
|
+
def elevatedbutton(**props) = _pending_app.elevatedbutton(**props)
|
|
44
|
+
def text_field(**props) = _pending_app.text_field(**props)
|
|
45
|
+
def textfield(**props) = _pending_app.textfield(**props)
|
|
46
|
+
def icon(**props) = _pending_app.icon(**props)
|
|
47
|
+
def image(src = nil, **props) = _pending_app.image(src, **props)
|
|
48
|
+
def icon_button(**props) = _pending_app.icon_button(**props)
|
|
49
|
+
def iconbutton(**props) = _pending_app.iconbutton(**props)
|
|
50
|
+
def app_bar(**props) = _pending_app.app_bar(**props)
|
|
51
|
+
def appbar(**props) = _pending_app.appbar(**props)
|
|
52
|
+
def text_button(**props) = _pending_app.text_button(**props)
|
|
53
|
+
def textbutton(**props) = _pending_app.textbutton(**props)
|
|
54
|
+
def filled_button(**props) = _pending_app.filled_button(**props)
|
|
55
|
+
def filledbutton(**props) = _pending_app.filledbutton(**props)
|
|
56
|
+
def checkbox(**props) = _pending_app.checkbox(**props)
|
|
57
|
+
def radio(**props) = _pending_app.radio(**props)
|
|
58
|
+
def radio_group(**props) = _pending_app.radio_group(**props)
|
|
59
|
+
def radiogroup(**props) = _pending_app.radiogroup(**props)
|
|
60
|
+
def alert_dialog(**props) = _pending_app.alert_dialog(**props)
|
|
61
|
+
def alertdialog(**props) = _pending_app.alertdialog(**props)
|
|
62
|
+
def snack_bar(**props) = _pending_app.snack_bar(**props)
|
|
63
|
+
def snackbar(**props) = _pending_app.snackbar(**props)
|
|
64
|
+
def bottom_sheet(**props) = _pending_app.bottom_sheet(**props)
|
|
65
|
+
def bottomsheet(**props) = _pending_app.bottomsheet(**props)
|
|
66
|
+
def markdown(value = nil, **props) = _pending_app.markdown(value, **props)
|
|
67
|
+
def floating_action_button(**props) = _pending_app.floating_action_button(**props)
|
|
68
|
+
def floatingactionbutton(**props) = _pending_app.floatingactionbutton(**props)
|
|
69
|
+
def tabs(**props, &block) = _pending_app.tabs(**props, &block)
|
|
70
|
+
def tab(**props, &block) = _pending_app.tab(**props, &block)
|
|
71
|
+
def tab_bar(**props, &block) = _pending_app.tab_bar(**props, &block)
|
|
72
|
+
def tabbar(**props, &block) = _pending_app.tabbar(**props, &block)
|
|
73
|
+
def tab_bar_view(**props, &block) = _pending_app.tab_bar_view(**props, &block)
|
|
74
|
+
def tabbarview(**props, &block) = _pending_app.tabbarview(**props, &block)
|
|
75
|
+
def navigation_bar(**props, &block) = _pending_app.navigation_bar(**props, &block)
|
|
76
|
+
def navigationbar(**props, &block) = _pending_app.navigationbar(**props, &block)
|
|
77
|
+
def navigation_bar_destination(**props, &block) = _pending_app.navigation_bar_destination(**props, &block)
|
|
78
|
+
def navigationbardestination(**props, &block) = _pending_app.navigationbardestination(**props, &block)
|
|
79
|
+
def fab(content = nil, **props) = _pending_app.fab(content, **props)
|
|
80
|
+
def cupertino_button(**props) = _pending_app.cupertino_button(**props)
|
|
81
|
+
def cupertinobutton(**props) = _pending_app.cupertinobutton(**props)
|
|
82
|
+
def cupertino_filled_button(**props) = _pending_app.cupertino_filled_button(**props)
|
|
83
|
+
def cupertinofilledbutton(**props) = _pending_app.cupertinofilledbutton(**props)
|
|
84
|
+
def cupertino_text_field(**props) = _pending_app.cupertino_text_field(**props)
|
|
85
|
+
def cupertinotextfield(**props) = _pending_app.cupertinotextfield(**props)
|
|
86
|
+
def cupertino_switch(**props) = _pending_app.cupertino_switch(**props)
|
|
87
|
+
def cupertinoswitch(**props) = _pending_app.cupertinoswitch(**props)
|
|
88
|
+
def cupertino_slider(**props) = _pending_app.cupertino_slider(**props)
|
|
89
|
+
def cupertinoslider(**props) = _pending_app.cupertinoslider(**props)
|
|
90
|
+
def cupertino_alert_dialog(**props) = _pending_app.cupertino_alert_dialog(**props)
|
|
91
|
+
def cupertinoalertdialog(**props) = _pending_app.cupertinoalertdialog(**props)
|
|
92
|
+
def cupertino_action_sheet(**props) = _pending_app.cupertino_action_sheet(**props)
|
|
93
|
+
def cupertinoactionsheet(**props) = _pending_app.cupertinoactionsheet(**props)
|
|
94
|
+
def cupertino_dialog_action(**props) = _pending_app.cupertino_dialog_action(**props)
|
|
95
|
+
def cupertinodialogaction(**props) = _pending_app.cupertinodialogaction(**props)
|
|
96
|
+
def cupertino_navigation_bar(**props) = _pending_app.cupertino_navigation_bar(**props)
|
|
97
|
+
def cupertinonavigationbar(**props) = _pending_app.cupertinonavigationbar(**props)
|
|
98
|
+
|
|
99
|
+
class App
|
|
100
|
+
include UI::ControlMethods
|
|
101
|
+
|
|
102
|
+
attr_reader :page_props, :host, :port
|
|
103
|
+
|
|
104
|
+
def initialize(host:, port:)
|
|
105
|
+
@host = host
|
|
106
|
+
@port = port
|
|
107
|
+
@roots = []
|
|
108
|
+
@stack = []
|
|
109
|
+
@page_props = { "route" => "/" }
|
|
110
|
+
@seq = 0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def set_endpoint!(host:, port:)
|
|
114
|
+
@host = host
|
|
115
|
+
@port = port
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def page(**props, &block)
|
|
119
|
+
@page_props.merge!(normalize_props(props))
|
|
120
|
+
instance_eval(&block) if block
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def widget(type, **props, &block)
|
|
125
|
+
control(type.to_s, **props, &block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def control(type, **props, &block)
|
|
129
|
+
mapped_props = props.dup
|
|
130
|
+
prop_children = mapped_props.delete(:controls) || mapped_props.delete("controls")
|
|
131
|
+
|
|
132
|
+
id = mapped_props.delete(:id)&.to_s || next_id(type)
|
|
133
|
+
c = Ruflet::UI::ControlFactory.build(type.to_s, id: id, **normalize_props(mapped_props))
|
|
134
|
+
attach(c)
|
|
135
|
+
|
|
136
|
+
if block
|
|
137
|
+
@stack.push(c)
|
|
138
|
+
instance_eval(&block)
|
|
139
|
+
@stack.pop
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
if prop_children
|
|
143
|
+
Array(prop_children).each { |child| c.children << child if child.is_a?(Ruflet::Control) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
c
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def run
|
|
150
|
+
app_roots = @roots
|
|
151
|
+
page_props = @page_props.dup
|
|
152
|
+
|
|
153
|
+
Ruflet::Server.new(host: host, port: port) do |runtime_page|
|
|
154
|
+
runtime_page.set_view_props(page_props)
|
|
155
|
+
runtime_page.add(*app_roots)
|
|
156
|
+
end.start
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def build_widget(type, **props, &block) = control(type.to_s, **props, &block)
|
|
162
|
+
|
|
163
|
+
def attach(control)
|
|
164
|
+
if @stack.empty?
|
|
165
|
+
@roots << control
|
|
166
|
+
else
|
|
167
|
+
@stack.last.children << control
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def normalize_props(hash)
|
|
172
|
+
hash.transform_keys(&:to_s).transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def next_id(type)
|
|
176
|
+
@seq += 1
|
|
177
|
+
"#{type}_#{@seq}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|