glib-web 7.2.0 → 7.3.0
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/app/controllers/glib/home_controller.rb +18 -0
- data/app/helpers/glib/json_ui/abstract_builder.rb +29 -0
- data/app/helpers/glib/json_ui/action_builder/popovers.rb +5 -0
- data/app/helpers/glib/json_ui/page_helper.rb +11 -0
- data/app/helpers/glib/json_ui/view_builder/fields.rb +62 -3
- data/app/views/json_ui/garage/content/web_panel_a.html.erb +17 -0
- data/app/views/json_ui/garage/content/web_panel_b.html.erb +17 -0
- data/app/views/json_ui/garage/test_page/components.json.jbuilder +13 -0
- data/app/views/json_ui/garage/test_page/fields_sign.json.jbuilder +108 -0
- data/app/views/json_ui/garage/test_page/popovers.json.jbuilder +19 -0
- data/app/views/json_ui/garage/test_page/web.json.jbuilder +8 -7
- data/app/views/json_ui/garage/views/map_cluster_data.json.jbuilder +4 -1
- data/app/views/json_ui/garage/views/map_data.json.jbuilder +6 -2
- data/config/routes.rb +2 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c46931ac3a3686ca63eb928be8cc23f77f6df86bcb2d3f6ad28c25a57f564fd3
|
|
4
|
+
data.tar.gz: 2b782f018a4220a56c0512f0603d2a712e4b97d57ffa15a64be1601035fda93f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d089424b8a53f6913c60c3cd895434aa168799243536b77ac73c75975ba766202382c52d86701beb6562fe38be03ff869336b353d168f3e4c92a89418ff85d1
|
|
7
|
+
data.tar.gz: 1cb4c4c951bdde00b63495407ee19e2c5738e2152107cbb8898bf19ce26ed91c3c82f6de9d922c7118d1754fe31d28def156b964d33edb3bc004980e019cd524
|
|
@@ -50,6 +50,24 @@ module Glib
|
|
|
50
50
|
render path
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# The content pages are plain HTML a web panel embeds — serve them
|
|
54
|
+
# unwrapped, outside the JSON-UI vue shell (spec 204).
|
|
55
|
+
json_libs_skip_json_ui only: :json_ui_garage_content
|
|
56
|
+
|
|
57
|
+
# The garage's engine-served content fixtures (spec 204): plain HTML the
|
|
58
|
+
# wire's web panel embeds, carried by the engine itself so every host
|
|
59
|
+
# that mounts it serves the same bytes — no host's public/ is involved.
|
|
60
|
+
# The name is allowlisted before it reaches a template path, and
|
|
61
|
+
# anything else is a 404. The format is pinned to HTML: the garage
|
|
62
|
+
# pipeline around this controller otherwise resolves every request as
|
|
63
|
+
# JSON (the json_ui_garage pages above).
|
|
64
|
+
def json_ui_garage_content
|
|
65
|
+
name = params[:name]
|
|
66
|
+
return head :not_found unless %w[web_panel_a web_panel_b].include?(name)
|
|
67
|
+
|
|
68
|
+
render "json_ui/garage/content/#{name}", formats: :html, layout: false
|
|
69
|
+
end
|
|
70
|
+
|
|
53
71
|
def chat
|
|
54
72
|
chat_params = params[:chat]
|
|
55
73
|
|
|
@@ -313,6 +313,35 @@ module Glib
|
|
|
313
313
|
end
|
|
314
314
|
end
|
|
315
315
|
|
|
316
|
+
# Sub-hash validation, one level deep: each `nested` key names a
|
|
317
|
+
# sub-hash whose own required/optional lists are checked the same
|
|
318
|
+
# way the top level is checked, so a typo inside a composite hash
|
|
319
|
+
# (e.g. a field's `draw` options) fails loudly at render time
|
|
320
|
+
# instead of passing silently. A nested spec that declares any
|
|
321
|
+
# keys (required or optional) CLOSES its sub-hash: keys outside
|
|
322
|
+
# required+optional raise. No recursion: sub-hashes of sub-hashes
|
|
323
|
+
# are the app's own data.
|
|
324
|
+
(options[:nested] || {}).each do |key, sub_options|
|
|
325
|
+
sub_value = value[key]
|
|
326
|
+
next if sub_value.nil?
|
|
327
|
+
raise ArgumentError, "Hash property: '#{key}' must be a hash" if !sub_value.is_a?(Hash)
|
|
328
|
+
|
|
329
|
+
sub_value = ActiveSupport::HashWithIndifferentAccess.new(sub_value)
|
|
330
|
+
sub_required = sub_options[:required] || []
|
|
331
|
+
sub_optional = sub_options[:optional] || []
|
|
332
|
+
|
|
333
|
+
sub_required.each do |req|
|
|
334
|
+
raise ArgumentError, "Hash property: '#{key}.#{req}' is required" if sub_value[req.to_s].nil?
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
allowed = sub_required + sub_optional
|
|
338
|
+
if allowed.present?
|
|
339
|
+
sub_value.each_key do |k|
|
|
340
|
+
raise ArgumentError, "Unknown hash property: '#{key}.#{k}'" if !allowed.include?(k.to_sym)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
316
345
|
json.set! propName, value
|
|
317
346
|
end
|
|
318
347
|
end
|
|
@@ -8,6 +8,11 @@ class Glib::JsonUi::ActionBuilder
|
|
|
8
8
|
string :key
|
|
9
9
|
# views :childViews
|
|
10
10
|
string :placement
|
|
11
|
+
# 'element' (default: the component that ran the action, or `targetId`)
|
|
12
|
+
# or 'pointer': the popover anchors to the click/tap point that started
|
|
13
|
+
# the action, for a menu opened from a large control where the control's
|
|
14
|
+
# corner may be far from the finger (a full-width signature box).
|
|
15
|
+
string :anchor
|
|
11
16
|
int :offset
|
|
12
17
|
int :width
|
|
13
18
|
bool :persistent
|
|
@@ -10,6 +10,17 @@ module Glib
|
|
|
10
10
|
)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# The garage's engine-served content pages (spec 204). Unlike
|
|
14
|
+
# json_ui_garage_url, no _render/format params ride along: the content
|
|
15
|
+
# GET is a plain fetch whose recorded path must stay query-less.
|
|
16
|
+
def json_ui_garage_content_url(options = {})
|
|
17
|
+
Glib::Web::Engine.routes.url_helpers.json_ui_garage_content_url(options.merge(
|
|
18
|
+
protocol: request.protocol,
|
|
19
|
+
host: request.host,
|
|
20
|
+
port: request.port)
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
13
24
|
def glib_chat_url(options = {})
|
|
14
25
|
Glib::Web::Engine.routes.url_helpers.chat_url(options.merge(
|
|
15
26
|
protocol: request.protocol,
|
|
@@ -916,9 +916,12 @@ class Glib::JsonUi::ViewBuilder
|
|
|
916
916
|
# a dialog with the drawing canvas, and Confirm uploads the drawing as a
|
|
917
917
|
# PNG to `directUploadUrl` and submits the resulting signed id under
|
|
918
918
|
# `name`. The placeholder then shows the signature, and reopening the
|
|
919
|
-
# dialog
|
|
920
|
-
# the
|
|
921
|
-
#
|
|
919
|
+
# dialog paints it back onto the canvas read-only -- a drawing shown on
|
|
920
|
+
# the canvas is never editable; clear it to draw again (the
|
|
921
|
+
# `draw.confirmedHint` cue says so under the canvas). Because the canvas
|
|
922
|
+
# is not in the form, the placeholder can be given the same height as a
|
|
923
|
+
# neighbouring input (e.g. a typed-name alternative) without losing
|
|
924
|
+
# drawing space.
|
|
922
925
|
#
|
|
923
926
|
# `label` titles the dialog; `placeholder` replaces the "Tap to sign"
|
|
924
927
|
# prompt.
|
|
@@ -956,6 +959,36 @@ class Glib::JsonUi::ViewBuilder
|
|
|
956
959
|
# width: 220,
|
|
957
960
|
# height: 100
|
|
958
961
|
#
|
|
962
|
+
# @example Offered saved signature
|
|
963
|
+
# # The app composes the menu on the empty box from generic pieces:
|
|
964
|
+
# # `onClick` runs while the field is blank, `popovers_show
|
|
965
|
+
# # anchor: 'pointer'` opens it at the tap point, `logics_set` fills the
|
|
966
|
+
# # field with the kept signature's signed id (which `savedSignature`
|
|
967
|
+
# # turns into a preview), and `components_invoke action_open` opens the
|
|
968
|
+
# # dialog. See the garage page for the full two-row composition.
|
|
969
|
+
# form.fields_sign \
|
|
970
|
+
# id: 'signature_offered',
|
|
971
|
+
# name: 'user[signature_offered]',
|
|
972
|
+
# directUploadUrl: glib_direct_uploads_url,
|
|
973
|
+
# width: 320, height: 160,
|
|
974
|
+
# onClick: ->(action) do
|
|
975
|
+
# action.popovers_show key: 'menu', anchor: 'pointer', placement: 'bottom-start',
|
|
976
|
+
# content: ->(popover) do
|
|
977
|
+
# popover.body styleClasses: ['popover', 'popover-menu'], childViews: ->(body) do
|
|
978
|
+
# body.panels_horizontal styleClasses: ['popover-menu-item'], align: 'middle',
|
|
979
|
+
# childViews: ->(row) { row.label text: 'Use my saved signature' },
|
|
980
|
+
# onClick: ->(sub) do
|
|
981
|
+
# sub.logics_set targetId: 'signature_offered', data: { value: 'saved-signed-id' }
|
|
982
|
+
# end
|
|
983
|
+
# end
|
|
984
|
+
# end
|
|
985
|
+
# end,
|
|
986
|
+
# draw: {
|
|
987
|
+
# hint: 'Draw it once. We keep it for next time.',
|
|
988
|
+
# savedSignature: { value: 'saved-signed-id', fileUrl: signature_url },
|
|
989
|
+
# saveOption: { name: 'user[save_signature_offered]', label: 'Save this signature for next time' }
|
|
990
|
+
# }
|
|
991
|
+
#
|
|
959
992
|
# @note A saved signature is a signed id that cannot be redrawn from the
|
|
960
993
|
# canvas: when a record is re-edited the field pre-populates `value`
|
|
961
994
|
# with the existing attachment's signed id (the component renders a
|
|
@@ -982,6 +1015,32 @@ class Glib::JsonUi::ViewBuilder
|
|
|
982
1015
|
# server routes it to the name attribute.
|
|
983
1016
|
hash :typedName, optional: [:name, :value, :label, :placeholder]
|
|
984
1017
|
|
|
1018
|
+
# Run instead of opening the dialog while the field is BLANK (a filled box
|
|
1019
|
+
# always opens the dialog on what it holds). The app composes whatever the
|
|
1020
|
+
# tap should do first, e.g. a popovers_show menu offering a kept signature
|
|
1021
|
+
# (logics_set of its signed id) or the dialog (components_invoke
|
|
1022
|
+
# name: 'action_open').
|
|
1023
|
+
action :onClick
|
|
1024
|
+
|
|
1025
|
+
# The Draw tab's hash, the parallel of `typedName` for the Type tab. `hint`
|
|
1026
|
+
# is a line under the canvas, shown only while the canvas is blank;
|
|
1027
|
+
# `placeholder` replaces the in-canvas "Draw your signature here" cue;
|
|
1028
|
+
# `confirmedHint` is the line under a painted (read-only) drawing on reopen;
|
|
1029
|
+
# `savedSignature` is plain data: when the field's value is set to `value`
|
|
1030
|
+
# (by logics_set, typically from the onClick menu) the box previews
|
|
1031
|
+
# `fileUrl`; `saveOption` is a checkbox shown under fresh ink, in the
|
|
1032
|
+
# fields_check shape, whose tick is submitted under `saveOption[:name]` as a
|
|
1033
|
+
# SECOND hidden input -- the one exception to the field's single-param
|
|
1034
|
+
# contract, because a boolean cannot share the input that carries the signed
|
|
1035
|
+
# id or the typed name. Distinct from `fileUrl` above: that is the box's OWN
|
|
1036
|
+
# value on re-edit, pre-filled and, like any painted drawing, read-only on
|
|
1037
|
+
# the canvas until cleared.
|
|
1038
|
+
hash :draw, optional: [:hint, :placeholder, :confirmedHint, :savedSignature, :saveOption],
|
|
1039
|
+
nested: {
|
|
1040
|
+
savedSignature: { required: [:value, :fileUrl] },
|
|
1041
|
+
saveOption: { required: [:name], optional: [:label, :value, :tooltip] }
|
|
1042
|
+
}
|
|
1043
|
+
|
|
985
1044
|
# Override
|
|
986
1045
|
# A stored signature is an attachment -- submit its signed id (mirrors
|
|
987
1046
|
# File#determine_value) so re-editing a signed submission pre-fills the
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<!-- The empty data-uri icon suppresses the WebView's automatic
|
|
7
|
+
/favicon.ico fetch, a byproduct request no cassette entry covers
|
|
8
|
+
(the served-through assertion would fail it). -->
|
|
9
|
+
<link rel="icon" href="data:,">
|
|
10
|
+
<title>Web Panel Content A</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body style="margin:0; font-family: sans-serif; background:#e8f5e9;">
|
|
13
|
+
<h1 style="margin:0; padding:12px; font-size:20px; color:#1b5e20;">Web Panel A</h1>
|
|
14
|
+
<div style="margin:12px; height:80px; background:#4caf50; border-radius:4px;"></div>
|
|
15
|
+
<p style="margin:12px; color:#1b5e20;">Content page A for the renderer contract suite.</p>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<!-- The empty data-uri icon suppresses the WebView's automatic
|
|
7
|
+
/favicon.ico fetch, a byproduct request no cassette entry covers
|
|
8
|
+
(the served-through assertion would fail it). -->
|
|
9
|
+
<link rel="icon" href="data:,">
|
|
10
|
+
<title>Web Panel Content B</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body style="margin:0; font-family: sans-serif; background:#e3f2fd;">
|
|
13
|
+
<h1 style="margin:0; padding:12px; font-size:24px; color:#0d47a1;">Web Panel B</h1>
|
|
14
|
+
<div style="margin:12px; height:120px; background:#2196f3; border-radius:8px;"></div>
|
|
15
|
+
<p style="margin:12px; color:#0d47a1;">Alternate content page B for the swap state.</p>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -84,6 +84,19 @@ page.body(
|
|
|
84
84
|
end
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
+
res.spacer height: 8
|
|
88
|
+
res.label id: 'chip_keyboard_status', text: 'Chip: idle'
|
|
89
|
+
res.spacer height: 8
|
|
90
|
+
# Chips are keyboard-activatable: Enter/Space run onClick through the
|
|
91
|
+
# same $onClick path a mouse click does.
|
|
92
|
+
res.chip(
|
|
93
|
+
id: 'chip_keyboard',
|
|
94
|
+
text: 'Chip with onClick',
|
|
95
|
+
onClick: ->(action) do
|
|
96
|
+
action.logics_set targetId: 'chip_keyboard_status', data: { text: 'Chip: activated' }
|
|
97
|
+
end
|
|
98
|
+
)
|
|
99
|
+
|
|
87
100
|
res.spacer height: 16
|
|
88
101
|
res.hr width: 'matchParent'
|
|
89
102
|
res.spacer height: 16
|
|
@@ -180,6 +180,114 @@ page.body(
|
|
|
180
180
|
form.hr width: 'matchParent'
|
|
181
181
|
form.spacer height: 12
|
|
182
182
|
|
|
183
|
+
form.h2 text: 'Offered saved signature'
|
|
184
|
+
form.spacer height: 8
|
|
185
|
+
form.label text: 'onClick composes a menu on the empty box (popovers_show at the pointer): use the kept signature (logics_set) or open the dialog (components_invoke action_open); draw.saveOption asks whether a fresh drawing should be saved.'
|
|
186
|
+
form.spacer height: 8
|
|
187
|
+
form.fields_sign(
|
|
188
|
+
id: 'signature_offered',
|
|
189
|
+
name: 'user[signature_offered]',
|
|
190
|
+
typedName: { label: 'Type your full name', placeholder: 'Full name' },
|
|
191
|
+
directUploadUrl: glib_direct_uploads_url,
|
|
192
|
+
width: 320,
|
|
193
|
+
height: 160,
|
|
194
|
+
onClick: ->(action) do
|
|
195
|
+
action.popovers_show key: 'menu', anchor: 'pointer', placement: 'bottom-start', content: ->(popover) do
|
|
196
|
+
popover.body styleClasses: ['popover', 'popover-menu'], childViews: ->(pbody) do
|
|
197
|
+
pbody.panels_horizontal styleClasses: ['popover-menu-item'], align: 'middle', childViews: ->(row) do
|
|
198
|
+
row.icon name: 'folder_open'
|
|
199
|
+
row.spacer width: 8
|
|
200
|
+
row.label text: 'Use my saved signature'
|
|
201
|
+
end, onClick: ->(sub) do
|
|
202
|
+
sub.runMultiple childActions: ->(m) do
|
|
203
|
+
m.logics_set targetId: 'signature_offered', data: { value: 'saved-signed-id' }
|
|
204
|
+
m.popovers_close key: 'menu'
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
pbody.panels_horizontal styleClasses: ['popover-menu-item'], align: 'middle', childViews: ->(row) do
|
|
208
|
+
row.icon name: 'draw'
|
|
209
|
+
row.spacer width: 8
|
|
210
|
+
row.label text: 'Draw or type a new signature'
|
|
211
|
+
end, onClick: ->(sub) do
|
|
212
|
+
sub.runMultiple childActions: ->(m) do
|
|
213
|
+
m.components_invoke targetId: 'signature_offered', name: 'action_open'
|
|
214
|
+
m.popovers_close key: 'menu'
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end,
|
|
220
|
+
draw: {
|
|
221
|
+
hint: 'Draw it once. We keep it for next time.',
|
|
222
|
+
placeholder: 'Sign inside the box',
|
|
223
|
+
confirmedHint: 'Keep it, or clear it and sign again.',
|
|
224
|
+
savedSignature: { value: 'saved-signed-id', fileUrl: preview_data_uri },
|
|
225
|
+
saveOption: {
|
|
226
|
+
name: 'user[save_signature_offered]',
|
|
227
|
+
label: 'Save this signature for next time',
|
|
228
|
+
tooltip: { text: 'This will replace the signature currently saved.' }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
form.spacer height: 8
|
|
233
|
+
form.panels_flow(
|
|
234
|
+
innerPadding: { bottom: 0 },
|
|
235
|
+
width: 'matchParent',
|
|
236
|
+
childViews: ->(flow) do
|
|
237
|
+
flow.button(
|
|
238
|
+
text: 'Reset offered',
|
|
239
|
+
onClick: ->(action) do
|
|
240
|
+
action.fields_reset targetId: 'signature_offered'
|
|
241
|
+
end
|
|
242
|
+
)
|
|
243
|
+
flow.spacer width: 8
|
|
244
|
+
flow.button(
|
|
245
|
+
text: 'Open offered field',
|
|
246
|
+
onClick: ->(action) do
|
|
247
|
+
action.components_invoke targetId: 'signature_offered', name: 'action_open'
|
|
248
|
+
end
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
form.spacer height: 12
|
|
254
|
+
form.hr width: 'matchParent'
|
|
255
|
+
form.spacer height: 12
|
|
256
|
+
|
|
257
|
+
form.h2 text: 'Hints on a blank canvas'
|
|
258
|
+
form.spacer height: 8
|
|
259
|
+
form.fields_sign(
|
|
260
|
+
id: 'signature_hinted',
|
|
261
|
+
name: 'user[signature_hinted]',
|
|
262
|
+
directUploadUrl: glib_direct_uploads_url,
|
|
263
|
+
width: 320,
|
|
264
|
+
height: 160,
|
|
265
|
+
draw: { hint: 'Draw it once. We keep it for next time.', placeholder: 'Sign inside the box',
|
|
266
|
+
confirmedHint: 'Keep it, or clear it and sign again.' }
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
form.spacer height: 12
|
|
270
|
+
form.hr width: 'matchParent'
|
|
271
|
+
form.spacer height: 12
|
|
272
|
+
|
|
273
|
+
form.h2 text: 'Failed image load'
|
|
274
|
+
form.spacer height: 8
|
|
275
|
+
form.label text: 'A dead fileUrl: the dialog must fall back to a drawable blank canvas, not a locked one.'
|
|
276
|
+
form.spacer height: 8
|
|
277
|
+
form.fields_sign(
|
|
278
|
+
id: 'signature_deadfile',
|
|
279
|
+
name: 'user[signature_deadfile]',
|
|
280
|
+
value: 'deadfile-signed-id',
|
|
281
|
+
fileUrl: '/signatures/missing.png',
|
|
282
|
+
directUploadUrl: glib_direct_uploads_url,
|
|
283
|
+
width: 320,
|
|
284
|
+
height: 160
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
form.spacer height: 12
|
|
288
|
+
form.hr width: 'matchParent'
|
|
289
|
+
form.spacer height: 12
|
|
290
|
+
|
|
183
291
|
form.h2 text: 'Read-only (pre-filled)'
|
|
184
292
|
form.spacer height: 8
|
|
185
293
|
form.label text: 'A saved signature on a read-only field: the preview shows, the edit overlay does not.'
|
|
@@ -186,6 +186,25 @@ page.body(
|
|
|
186
186
|
)
|
|
187
187
|
end
|
|
188
188
|
)
|
|
189
|
+
res.spacer height: 8
|
|
190
|
+
res.button(
|
|
191
|
+
text: 'popovers/show (pointer)',
|
|
192
|
+
onClick: ->(action) do
|
|
193
|
+
action.popovers_show(
|
|
194
|
+
key: 'popover_pointer',
|
|
195
|
+
anchor: 'pointer',
|
|
196
|
+
placement: 'bottom-start',
|
|
197
|
+
content: ->(dialog) do
|
|
198
|
+
dialog.body(
|
|
199
|
+
padding: glib_json_padding_body,
|
|
200
|
+
childViews: ->(menu) do
|
|
201
|
+
menu.label text: 'Opened at the click point'
|
|
202
|
+
end
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
)
|
|
189
208
|
end
|
|
190
209
|
)
|
|
191
210
|
end
|
|
@@ -10,13 +10,14 @@ page.body childViews: ->(body) do
|
|
|
10
10
|
res.h2 text: 'Overview'
|
|
11
11
|
res.p text: 'Web panels embed external web content within a panel container.'
|
|
12
12
|
|
|
13
|
-
# Hermetic content
|
|
14
|
-
# the
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# and
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
# Hermetic content served by the engine itself (spec 204): the two
|
|
14
|
+
# deterministic pages ride the engine's json_ui_garage/content route,
|
|
15
|
+
# so every host that mounts the engine serves them identically — no
|
|
16
|
+
# host's public/ is involved. The emitted URLs bake an origin the
|
|
17
|
+
# cassette replay can rewrite and serve; an external URL would leave
|
|
18
|
+
# the emulator for the real network and no golden could depend on it.
|
|
19
|
+
url_content = json_ui_garage_content_url(name: 'web_panel_a')
|
|
20
|
+
url_alt = json_ui_garage_content_url(name: 'web_panel_b')
|
|
20
21
|
|
|
21
22
|
res.spacer height: 12
|
|
22
23
|
res.hr width: 'matchParent'
|
|
@@ -34,7 +34,10 @@ json.pins do
|
|
|
34
34
|
json.longitude location[:lng]
|
|
35
35
|
json.clusterSize location[:size]
|
|
36
36
|
json.infoWindow do
|
|
37
|
-
|
|
37
|
+
# Pinned for byte-stable re-records (spec 203, Decision 14): was
|
|
38
|
+
# "TEST #{rand(1000)}" — every re-record renamed every cluster pin
|
|
39
|
+
# and the cassette diff was noise.
|
|
40
|
+
json.title 'TEST 500'
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
end
|
|
@@ -24,8 +24,12 @@ json.pins do
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
json.child! do
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
# Pinned for byte-stable re-records (spec 203, Decision 14): was
|
|
28
|
+
# 13.375903 + rand(0..0.1) / 144.749791 + rand(0..0.1) — the recorder
|
|
29
|
+
# hits this live fixture, so the jitter moved every pin on every
|
|
30
|
+
# re-record and the cassette diff was noise.
|
|
31
|
+
json.latitude 13.425903
|
|
32
|
+
json.longitude 144.799791
|
|
29
33
|
json.infoWindow do
|
|
30
34
|
json.title 'Place 3'
|
|
31
35
|
json.subtitle 'Random location'
|
data/config/routes.rb
CHANGED
|
@@ -4,10 +4,11 @@ Glib::Web::Engine.routes.draw do
|
|
|
4
4
|
post 'json_ui_garage', to: 'home#json_ui_garage'
|
|
5
5
|
patch 'json_ui_garage', to: 'home#json_ui_garage'
|
|
6
6
|
delete 'json_ui_garage', to: 'home#json_ui_garage'
|
|
7
|
+
get 'json_ui_garage/content/:name', to: 'home#json_ui_garage_content', as: :json_ui_garage_content
|
|
7
8
|
post 'chat', to: 'home#chat'
|
|
8
9
|
|
|
9
10
|
resources :glib_direct_uploads, only: [:create]
|
|
10
11
|
resources :blob_url_generators, only: [:create]
|
|
11
12
|
resources :errors, only: [:create]
|
|
12
13
|
end
|
|
13
|
-
end
|
|
14
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|
|
@@ -312,6 +312,8 @@ files:
|
|
|
312
312
|
- app/views/json_ui/garage/actions/index.json.jbuilder
|
|
313
313
|
- app/views/json_ui/garage/actions/partial_update.json.jbuilder
|
|
314
314
|
- app/views/json_ui/garage/actions/sheet_content.json.jbuilder
|
|
315
|
+
- app/views/json_ui/garage/content/web_panel_a.html.erb
|
|
316
|
+
- app/views/json_ui/garage/content/web_panel_b.html.erb
|
|
315
317
|
- app/views/json_ui/garage/empty.json.jbuilder
|
|
316
318
|
- app/views/json_ui/garage/forms/_alert_post_all_data.json.jbuilder
|
|
317
319
|
- app/views/json_ui/garage/forms/_alert_post_data.json.jbuilder
|