glib-web 5.0.0 → 5.0.2
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/helpers/glib/format_helper.rb +1 -1
- data/app/helpers/glib/json_ui/abstract_builder.rb +10 -1
- data/app/helpers/glib/json_ui/action_builder.rb +20 -0
- data/app/helpers/glib/json_ui/view_builder/fields.rb +3 -0
- data/app/helpers/glib/json_ui/view_builder.rb +8 -15
- data/app/views/json_ui/garage/test_page/custom.json.jbuilder +58 -50
- data/app/views/json_ui/garage/test_page/popovers.json.jbuilder +91 -16
- data/config/routes.rb +0 -4
- data/lib/glib/test_helpers.rb +127 -0
- data/lib/tasks/db.rake +1 -0
- metadata +24 -15
- data/app/controllers/glib/api_docs_controller.rb +0 -145
- data/app/views/glib/api_docs/component.json.jbuilder +0 -215
- data/app/views/glib/api_docs/index.json.jbuilder +0 -103
- data/app/views/glib/api_docs/show.json.jbuilder +0 -111
- data/lib/tasks/docs.rake +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e50fd58d6fa3b0ee4d505c72f4bc49adf0809e208486eeb5c0aaeada10d39dfa
|
|
4
|
+
data.tar.gz: f32fb5c6985560a68a6240586e2a6411ffaec55dab2a30c12eecc177a002efaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ddfb285130294449bbb45b56458dedfaa5cdc7ad791877b8e7e722e4da26bf0cfa2c11b5b24a56031d2da80bcdac40a6065bee6c453add16891f91956f81cca
|
|
7
|
+
data.tar.gz: 3f5ed1fc1745800c005db0356d123b6e334d6333262e99943933ee528861227cb31174618bd9d400c73eb8b849906a534bc47018bbe0eeb5472cc5efc323325f
|
|
@@ -286,7 +286,16 @@ module Glib
|
|
|
286
286
|
def self.action(propName)
|
|
287
287
|
define_method(propName) do |value|
|
|
288
288
|
if value # Allow nil value
|
|
289
|
-
json.set!(propName)
|
|
289
|
+
json.set!(propName) do
|
|
290
|
+
# Save/restore singleton tracking around nested action callbacks (e.g. onClick)
|
|
291
|
+
# to prevent the inner callback from polluting the outer context's state.
|
|
292
|
+
builder = page.action_builder
|
|
293
|
+
saved_singleton_set = builder.singleton_action_set
|
|
294
|
+
saved_singleton_name = builder.singleton_action_name
|
|
295
|
+
builder.reset_singleton_tracking!
|
|
296
|
+
value.call(builder)
|
|
297
|
+
builder.restore_singleton_tracking!(saved_singleton_set, saved_singleton_name)
|
|
298
|
+
end
|
|
290
299
|
end
|
|
291
300
|
end
|
|
292
301
|
end
|
|
@@ -7,10 +7,30 @@ module Glib
|
|
|
7
7
|
@multiple = multiple
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
attr_reader :singleton_action_set, :singleton_action_name
|
|
11
|
+
|
|
12
|
+
def reset_singleton_tracking!
|
|
13
|
+
@singleton_action_set = false
|
|
14
|
+
@singleton_action_name = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def restore_singleton_tracking!(action_set, action_name)
|
|
18
|
+
@singleton_action_set = action_set
|
|
19
|
+
@singleton_action_name = action_name
|
|
20
|
+
end
|
|
21
|
+
|
|
10
22
|
def method_missing(m, *args)
|
|
11
23
|
if @multiple
|
|
12
24
|
add_element_to_array_v1 'action', m, *args
|
|
13
25
|
else
|
|
26
|
+
if !Rails.env.production? && @singleton_action_set
|
|
27
|
+
raise "Multiple actions called on a singleton ActionBuilder. " \
|
|
28
|
+
"Only one action is allowed. Previous action: '#{@singleton_action_name}', " \
|
|
29
|
+
"current action: '#{m}'. Use `run_multiple` with `childActions` or " \
|
|
30
|
+
"chain actions using `onClose` callbacks."
|
|
31
|
+
end
|
|
32
|
+
@singleton_action_set = true
|
|
33
|
+
@singleton_action_name = m
|
|
14
34
|
add_singleton_element_v1 'action', m, *args
|
|
15
35
|
end
|
|
16
36
|
end
|
|
@@ -527,6 +527,9 @@ class Glib::JsonUi::ViewBuilder
|
|
|
527
527
|
#
|
|
528
528
|
# @see app/views/json_ui/garage/forms/text_validation.json.jbuilder Garage example
|
|
529
529
|
class Textarea < Text
|
|
530
|
+
# Number of visible text lines.
|
|
531
|
+
# @example rows: 3
|
|
532
|
+
int :rows
|
|
530
533
|
end
|
|
531
534
|
|
|
532
535
|
# Rich text editor field with WYSIWYG editing.
|
|
@@ -59,17 +59,6 @@ module Glib
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
if @onMaxFileLengthError.blank?
|
|
63
|
-
@onMaxFileLengthError = ->(action) do
|
|
64
|
-
action.snackbars_alert \
|
|
65
|
-
message: I18n.t(
|
|
66
|
-
'glib.fields.upload.on_max_file_length_error',
|
|
67
|
-
max: @accepts['maxFileLength'],
|
|
68
|
-
default: "You can't upload more than the maximum number of files allowed. The limit is #{@accepts['maxFileLength']} files."),
|
|
69
|
-
styleClass: 'error'
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
62
|
if @onFileTypeError.blank?
|
|
74
63
|
@onFileTypeError = ->(action) do
|
|
75
64
|
action.snackbars_alert \
|
|
@@ -81,9 +70,12 @@ module Glib
|
|
|
81
70
|
end
|
|
82
71
|
|
|
83
72
|
json.accepts @accepts
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
# Each handler needs its own singleton ActionBuilder — sharing
|
|
74
|
+
# `page.action_builder` across all three trips the "only one action
|
|
75
|
+
# per singleton" guard because the builder accumulates state.
|
|
76
|
+
json.set!('onMaxFileSizeError') { @onMaxFileSizeError.call(ActionBuilder.new(json, page, false)) }
|
|
77
|
+
json.set!('onMaxFileLengthError') { @onMaxFileLengthError.call(ActionBuilder.new(json, page, false)) }
|
|
78
|
+
json.set!('onFileTypeError') { @onFileTypeError.call(ActionBuilder.new(json, page, false)) }
|
|
87
79
|
end
|
|
88
80
|
end
|
|
89
81
|
end
|
|
@@ -130,7 +122,7 @@ module Glib
|
|
|
130
122
|
hash :padding, optional: [:top, :right, :bottom, :left, :x, :y, :all]
|
|
131
123
|
singleton_array :styleClass, :styleClasses
|
|
132
124
|
|
|
133
|
-
hash :showIf
|
|
125
|
+
hash :showIf # Deprecated: use logics_set instead
|
|
134
126
|
hash :loadIf
|
|
135
127
|
action :onIfTrue
|
|
136
128
|
action :onIfFalse
|
|
@@ -247,6 +239,7 @@ module Glib
|
|
|
247
239
|
string :base64Data
|
|
248
240
|
action :onClick
|
|
249
241
|
string :fit
|
|
242
|
+
string :alt
|
|
250
243
|
end
|
|
251
244
|
|
|
252
245
|
class Avatar < View
|
|
@@ -2,55 +2,63 @@ json.title 'Test Page (Custom)'
|
|
|
2
2
|
|
|
3
3
|
page = json_ui_page json
|
|
4
4
|
|
|
5
|
-
page.body
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
5
|
+
page.body(
|
|
6
|
+
childViews: ->(body) do
|
|
7
|
+
render 'json_ui/garage/test_page/header', view: body
|
|
8
|
+
body.panels_responsive(
|
|
9
|
+
padding: glib_json_padding_body,
|
|
10
|
+
childViews: ->(res) do
|
|
11
|
+
res.h2 text: 'Overview'
|
|
12
|
+
res.p text: 'Custom panels render a predefined template with custom data payloads.'
|
|
13
|
+
data_primary = { imageUrl: glib_json_image_standard_url, title: 'Primary', subtitle: 'Thumbnail template' }
|
|
14
|
+
data_alt = { imageUrl: glib_json_image_standard_url, title: 'Alternate', subtitle: 'Alternate data payload' }
|
|
15
|
+
res.spacer height: 12
|
|
16
|
+
res.hr width: 'matchParent'
|
|
17
|
+
res.h2 text: 'Basic'
|
|
18
|
+
res.spacer height: 8
|
|
19
|
+
res.panels_custom id: 'custom_main', template: 'thumbnail', width: 'matchParent', backgroundColor: '#fafafa', data: data_primary
|
|
20
|
+
res.spacer height: 12
|
|
21
|
+
res.hr width: 'matchParent'
|
|
22
|
+
res.h2 text: 'Variants and Props'
|
|
23
|
+
res.spacer height: 8
|
|
24
|
+
res.panels_flow(
|
|
25
|
+
innerPadding: { bottom: 0 },
|
|
26
|
+
childViews: ->(flow) do
|
|
27
|
+
flow.button(
|
|
28
|
+
text: 'Primary data',
|
|
29
|
+
onClick: ->(action) do
|
|
30
|
+
action.components_set targetId: 'custom_main', data: { data: data_primary }
|
|
31
|
+
end
|
|
32
|
+
)
|
|
33
|
+
flow.spacer width: 4
|
|
34
|
+
flow.button(
|
|
35
|
+
text: 'Alternate data',
|
|
36
|
+
onClick: ->(action) do
|
|
37
|
+
action.components_set targetId: 'custom_main', data: { data: data_alt }
|
|
38
|
+
end
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
)
|
|
42
|
+
res.spacer height: 12
|
|
43
|
+
res.hr width: 'matchParent'
|
|
44
|
+
res.h2 text: 'Actions and Events'
|
|
45
|
+
res.spacer height: 8
|
|
46
|
+
res.label id: 'custom_action_status', text: 'Action status: idle'
|
|
47
|
+
res.spacer height: 6
|
|
48
|
+
res.panels_custom(
|
|
49
|
+
template: 'thumbnail',
|
|
50
|
+
data: data_primary,
|
|
51
|
+
onClick: ->(action) do
|
|
52
|
+
action.components_set targetId: 'custom_action_status', data: { text: 'Action status: clicked' }
|
|
53
|
+
end
|
|
54
|
+
)
|
|
55
|
+
res.spacer height: 12
|
|
56
|
+
res.hr width: 'matchParent'
|
|
57
|
+
res.h2 text: 'Edge and Advanced'
|
|
58
|
+
res.spacer height: 8
|
|
59
|
+
res.label text: 'Non-existent template'
|
|
60
|
+
res.panels_custom template: 'nonExistentTemplate'
|
|
30
61
|
end
|
|
31
|
-
|
|
32
|
-
flow.button text: 'Alternate data', onClick: ->(action) do
|
|
33
|
-
action.components_set targetId: 'custom_main', data: { data: data_alt }
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
res.spacer height: 12
|
|
38
|
-
res.hr width: 'matchParent'
|
|
39
|
-
|
|
40
|
-
res.h2 text: 'Actions and Events'
|
|
41
|
-
res.spacer height: 8
|
|
42
|
-
res.label id: 'custom_action_status', text: 'Action status: idle'
|
|
43
|
-
res.spacer height: 6
|
|
44
|
-
res.panels_custom template: 'thumbnail', data: data_primary, onClick: ->(action) do
|
|
45
|
-
action.components_set targetId: 'custom_action_status', data: { text: 'Action status: clicked' }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
res.spacer height: 12
|
|
49
|
-
res.hr width: 'matchParent'
|
|
50
|
-
|
|
51
|
-
res.h2 text: 'Edge and Advanced'
|
|
52
|
-
res.spacer height: 8
|
|
53
|
-
res.label text: 'Non-existent template'
|
|
54
|
-
res.panels_custom template: 'nonExistentTemplate'
|
|
62
|
+
)
|
|
55
63
|
end
|
|
56
|
-
|
|
64
|
+
)
|
|
@@ -19,6 +19,59 @@ page.body(
|
|
|
19
19
|
res.spacer height: 12
|
|
20
20
|
res.hr width: 'matchParent'
|
|
21
21
|
res.spacer height: 12
|
|
22
|
+
res.h2 text: 'Inside dialog'
|
|
23
|
+
res.spacer height: 8
|
|
24
|
+
res.button(
|
|
25
|
+
text: 'open dialog with popover',
|
|
26
|
+
onClick: ->(action) do
|
|
27
|
+
action.dialogs_show(
|
|
28
|
+
content: ->(dialog) do
|
|
29
|
+
dialog.body(
|
|
30
|
+
padding: glib_json_padding_body,
|
|
31
|
+
childViews: ->(dbody) do
|
|
32
|
+
dbody.label id: 'dialog_popover_target', text: 'Dialog anchor'
|
|
33
|
+
dbody.spacer height: 8
|
|
34
|
+
dbody.button(
|
|
35
|
+
text: 'show popover',
|
|
36
|
+
onClick: ->(daction) do
|
|
37
|
+
daction.popovers_show(
|
|
38
|
+
key: 'popover_in_dialog',
|
|
39
|
+
placement: 'top-start',
|
|
40
|
+
targetId: 'dialog_popover_target',
|
|
41
|
+
content: ->(pdialog) do
|
|
42
|
+
pdialog.body(
|
|
43
|
+
padding: glib_json_padding_body,
|
|
44
|
+
childViews: ->(pbody) do
|
|
45
|
+
pbody.label text: 'Popover inside dialog'
|
|
46
|
+
pbody.spacer height: 8
|
|
47
|
+
pbody.button(
|
|
48
|
+
text: 'close popover',
|
|
49
|
+
onClick: ->(paction) do
|
|
50
|
+
paction.popovers_close key: 'popover_in_dialog'
|
|
51
|
+
end
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
)
|
|
59
|
+
dbody.spacer height: 8
|
|
60
|
+
dbody.button(
|
|
61
|
+
text: 'close dialog',
|
|
62
|
+
onClick: ->(daction) do
|
|
63
|
+
daction.dialogs_close
|
|
64
|
+
end
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
)
|
|
72
|
+
res.spacer height: 16
|
|
73
|
+
res.hr width: 'matchParent'
|
|
74
|
+
res.spacer height: 16
|
|
22
75
|
|
|
23
76
|
res.h2 text: 'Basic example'
|
|
24
77
|
res.spacer height: 8
|
|
@@ -27,23 +80,40 @@ page.body(
|
|
|
27
80
|
res.button(
|
|
28
81
|
text: 'popovers/show menu',
|
|
29
82
|
onClick: ->(action) do
|
|
30
|
-
action.popovers_show
|
|
83
|
+
action.popovers_show(
|
|
31
84
|
key: 'popover_menu',
|
|
32
85
|
placement: 'bottom-start',
|
|
33
86
|
targetId: 'popover_target',
|
|
34
87
|
content: ->(dialog) do
|
|
35
|
-
dialog.body
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
88
|
+
dialog.body(
|
|
89
|
+
styleClass: 'popover-menu',
|
|
90
|
+
padding: glib_json_padding_body,
|
|
91
|
+
childViews: ->(menu) do
|
|
92
|
+
menu.label(
|
|
93
|
+
text: 'Item 1',
|
|
94
|
+
styleClass: 'popover-menu-item',
|
|
95
|
+
onClick: ->(subaction) do
|
|
96
|
+
subaction.popovers_close key: 'popover_menu'
|
|
97
|
+
end
|
|
98
|
+
)
|
|
99
|
+
menu.label(
|
|
100
|
+
text: 'Item 2',
|
|
101
|
+
styleClass: 'popover-menu-item',
|
|
102
|
+
onClick: ->(subaction) do
|
|
103
|
+
subaction.popovers_close key: 'popover_menu'
|
|
104
|
+
end
|
|
105
|
+
)
|
|
106
|
+
menu.label(
|
|
107
|
+
text: 'Item 3',
|
|
108
|
+
styleClass: 'popover-menu-item',
|
|
109
|
+
onClick: ->(subaction) do
|
|
110
|
+
subaction.popovers_close key: 'popover_menu'
|
|
111
|
+
end
|
|
112
|
+
)
|
|
44
113
|
end
|
|
45
|
-
|
|
114
|
+
)
|
|
46
115
|
end
|
|
116
|
+
)
|
|
47
117
|
end
|
|
48
118
|
)
|
|
49
119
|
|
|
@@ -63,10 +133,11 @@ page.body(
|
|
|
63
133
|
res.button(
|
|
64
134
|
text: 'popovers/open (updateExisting)',
|
|
65
135
|
onClick: ->(action) do
|
|
66
|
-
action.popovers_open
|
|
136
|
+
action.popovers_open(
|
|
67
137
|
url: json_ui_garage_url(path: 'views/popovers_open', key: 'server_popover'),
|
|
68
138
|
key: 'server_popover',
|
|
69
139
|
updateExisting: true
|
|
140
|
+
)
|
|
70
141
|
end
|
|
71
142
|
)
|
|
72
143
|
|
|
@@ -99,15 +170,19 @@ page.body(
|
|
|
99
170
|
res.button(
|
|
100
171
|
text: 'popovers/show (top-end)',
|
|
101
172
|
onClick: ->(action) do
|
|
102
|
-
action.popovers_show
|
|
173
|
+
action.popovers_show(
|
|
103
174
|
key: 'popover_note',
|
|
104
175
|
placement: 'top-end',
|
|
105
176
|
targetId: 'popover_target',
|
|
106
177
|
content: ->(dialog) do
|
|
107
|
-
dialog.body
|
|
108
|
-
|
|
109
|
-
|
|
178
|
+
dialog.body(
|
|
179
|
+
padding: glib_json_padding_body,
|
|
180
|
+
childViews: ->(menu) do
|
|
181
|
+
menu.label text: 'Pinned note'
|
|
182
|
+
end
|
|
183
|
+
)
|
|
110
184
|
end
|
|
185
|
+
)
|
|
111
186
|
end
|
|
112
187
|
)
|
|
113
188
|
end
|
data/config/routes.rb
CHANGED
|
@@ -6,10 +6,6 @@ Glib::Web::Engine.routes.draw do
|
|
|
6
6
|
delete 'json_ui_garage', to: 'home#json_ui_garage'
|
|
7
7
|
post 'chat', to: 'home#chat'
|
|
8
8
|
|
|
9
|
-
get 'json_ui_api', to: 'api_docs#index'
|
|
10
|
-
get 'json_ui_api/:category', to: 'api_docs#show', as: :json_ui_api_category
|
|
11
|
-
get 'json_ui_api/:category/:component', to: 'api_docs#component', as: :json_ui_api_component
|
|
12
|
-
|
|
13
9
|
resources :glib_direct_uploads, only: [:create]
|
|
14
10
|
resources :blob_url_generators, only: [:create]
|
|
15
11
|
resources :errors, only: [:create]
|
data/lib/glib/test_helpers.rb
CHANGED
|
@@ -83,6 +83,58 @@ module Glib
|
|
|
83
83
|
"http://#{HOST}/users/sign_out.json"
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
# Submits a form by parsing the page payload and extracting form data.
|
|
87
|
+
# This ensures the view renders successfully before submitting.
|
|
88
|
+
#
|
|
89
|
+
# @param page_payload [String] The JSON response body containing the form
|
|
90
|
+
# @param data [Hash] The form data to submit (e.g., { chat: { message_body: 'hello' } })
|
|
91
|
+
# @param url [String, nil] Optional URL override (if form URL is nil)
|
|
92
|
+
# @param method [Symbol, nil] Optional HTTP method override
|
|
93
|
+
#
|
|
94
|
+
# @example
|
|
95
|
+
# get new_chat_url(checklist_id: @checklist.id), params: json_params
|
|
96
|
+
# submit_form(response.body, { chat: { message_body: 'Hello' } })
|
|
97
|
+
#
|
|
98
|
+
def submit_form(page_payload, data, url: nil, method: nil)
|
|
99
|
+
json = JSON.parse(page_payload)
|
|
100
|
+
form = __find_form(json)
|
|
101
|
+
|
|
102
|
+
raise 'No form found in page. Ensure the form is rendered properly.' unless form
|
|
103
|
+
|
|
104
|
+
# Get URL from form, or use provided URL
|
|
105
|
+
form_url = form['url']
|
|
106
|
+
if form_url.blank? && form['onSubmit']
|
|
107
|
+
on_submit = form['onSubmit']
|
|
108
|
+
form_url = on_submit['httpPost']&.[]('url') || on_submit['http']&.[]('url')
|
|
109
|
+
end
|
|
110
|
+
url ||= form_url
|
|
111
|
+
|
|
112
|
+
raise "Form missing 'url'. Please provide url: parameter. Form keys: #{form.keys.join(', ')}" if url.blank?
|
|
113
|
+
|
|
114
|
+
# Get method from parameter, form, or default to post
|
|
115
|
+
method ||= form['method']
|
|
116
|
+
method = 'post' if method.blank?
|
|
117
|
+
|
|
118
|
+
# Build params: hidden fields from form + provided data
|
|
119
|
+
params = __build_form_params(form, data)
|
|
120
|
+
|
|
121
|
+
# Submit using the appropriate HTTP method
|
|
122
|
+
case method.to_sym
|
|
123
|
+
when :get
|
|
124
|
+
get url, params: params
|
|
125
|
+
when :post
|
|
126
|
+
post url, params: params
|
|
127
|
+
when :patch
|
|
128
|
+
patch url, params: params
|
|
129
|
+
when :put
|
|
130
|
+
put url, params: params
|
|
131
|
+
else
|
|
132
|
+
send(method, url, params: params)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
assert_response :success
|
|
136
|
+
end
|
|
137
|
+
|
|
86
138
|
def glib_travel(*args, &block)
|
|
87
139
|
Timecop.travel(*args, &block)
|
|
88
140
|
end
|
|
@@ -96,6 +148,81 @@ module Glib
|
|
|
96
148
|
end
|
|
97
149
|
|
|
98
150
|
private
|
|
151
|
+
# --- submit_form helpers ---
|
|
152
|
+
|
|
153
|
+
def __find_form(json)
|
|
154
|
+
forms = []
|
|
155
|
+
__traverse_form_view(json) do |view|
|
|
156
|
+
forms << view if __view_is_form?(view)
|
|
157
|
+
end
|
|
158
|
+
forms.max_by { |f| f['childViews']&.length || 0 }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def __view_is_form?(view)
|
|
162
|
+
view.is_a?(Hash) && (
|
|
163
|
+
view['view']&.start_with?('panels/form') ||
|
|
164
|
+
view['view']&.start_with?('panels/fullPageForm') ||
|
|
165
|
+
view['view'] == 'panels/bulkEdit2-v1' ||
|
|
166
|
+
view['view'] == 'panels/splitForm'
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def __build_form_params(form, data)
|
|
171
|
+
params = {}
|
|
172
|
+
data_prefixes = data.keys.map { |k| "#{k}[" }
|
|
173
|
+
|
|
174
|
+
__extract_form_fields(form).each do |field|
|
|
175
|
+
next unless field[:view] == 'fields/hidden' || field[:view] == 'fields/hidden-v1'
|
|
176
|
+
next if data_prefixes.any? { |prefix| field[:name]&.start_with?(prefix) }
|
|
177
|
+
|
|
178
|
+
params[field[:name]] = field[:value]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
data.each do |key, value|
|
|
182
|
+
if value.is_a?(Hash)
|
|
183
|
+
params[key] ||= {}
|
|
184
|
+
params[key].merge!(value)
|
|
185
|
+
else
|
|
186
|
+
params[key] = value
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
params
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def __extract_form_fields(form)
|
|
194
|
+
fields = []
|
|
195
|
+
__traverse_form_views(form['childViews'] || []) do |view|
|
|
196
|
+
if view.is_a?(Hash) && view['view']&.start_with?('fields/')
|
|
197
|
+
fields << { name: view['name'], value: view['value'], view: view['view'] }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
fields
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def __traverse_form_views(views, &block)
|
|
204
|
+
return unless views.is_a?(Array)
|
|
205
|
+
|
|
206
|
+
views.each { |view| __traverse_form_view(view, &block) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def __traverse_form_view(view, &block)
|
|
210
|
+
return unless view.is_a?(Hash)
|
|
211
|
+
|
|
212
|
+
block.call(view)
|
|
213
|
+
|
|
214
|
+
['childViews', 'header', 'body', 'footer', 'left', 'right', 'panels'].each do |key|
|
|
215
|
+
child = view[key]
|
|
216
|
+
if child.is_a?(Array)
|
|
217
|
+
__traverse_form_views(child, &block)
|
|
218
|
+
elsif child.is_a?(Hash)
|
|
219
|
+
__traverse_form_view(child, &block)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# --- Crawler helpers ---
|
|
225
|
+
|
|
99
226
|
def __execute_crawler(user, inspect_http:, log_file: nil, &block)
|
|
100
227
|
auth_token = login user
|
|
101
228
|
user[:token] = auth_token
|
data/lib/tasks/db.rake
CHANGED
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.0.
|
|
4
|
+
version: 5.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2019-10-04 00:00:00.000000000 Z
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 8.0
|
|
19
|
+
version: '8.0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 8.0
|
|
26
|
+
version: '8.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: pundit
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +58,14 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 8.0
|
|
61
|
+
version: '8.0'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 8.0
|
|
68
|
+
version: '8.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: js_regex
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,6 +108,20 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: redcarpet
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
111
125
|
- !ruby/object:Gem::Dependency
|
|
112
126
|
name: rubocop
|
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -136,7 +150,7 @@ dependencies:
|
|
|
136
150
|
- - ">="
|
|
137
151
|
- !ruby/object:Gem::Version
|
|
138
152
|
version: '0'
|
|
139
|
-
description:
|
|
153
|
+
description:
|
|
140
154
|
email: ''
|
|
141
155
|
executables: []
|
|
142
156
|
extensions: []
|
|
@@ -156,7 +170,6 @@ files:
|
|
|
156
170
|
- app/controllers/concerns/glib/json/traversal.rb
|
|
157
171
|
- app/controllers/concerns/glib/json/ui.rb
|
|
158
172
|
- app/controllers/concerns/glib/json/validation.rb
|
|
159
|
-
- app/controllers/glib/api_docs_controller.rb
|
|
160
173
|
- app/controllers/glib/blob_url_generators_controller.rb
|
|
161
174
|
- app/controllers/glib/errors_controller.rb
|
|
162
175
|
- app/controllers/glib/glib_direct_uploads_controller.rb
|
|
@@ -221,9 +234,6 @@ files:
|
|
|
221
234
|
- app/validators/mutually_exclusive_with_validator.rb
|
|
222
235
|
- app/validators/presence_only_if_validator.rb
|
|
223
236
|
- app/validators/url_validator.rb
|
|
224
|
-
- app/views/glib/api_docs/component.json.jbuilder
|
|
225
|
-
- app/views/glib/api_docs/index.json.jbuilder
|
|
226
|
-
- app/views/glib/api_docs/show.json.jbuilder
|
|
227
237
|
- app/views/json_ui/garage/_nav_menu.json.jbuilder
|
|
228
238
|
- app/views/json_ui/garage/actions/_commands.json.jbuilder
|
|
229
239
|
- app/views/json_ui/garage/actions/_components.json.jbuilder
|
|
@@ -515,11 +525,10 @@ files:
|
|
|
515
525
|
- lib/glib/value.rb
|
|
516
526
|
- lib/glib/version.rb
|
|
517
527
|
- lib/tasks/db.rake
|
|
518
|
-
|
|
519
|
-
homepage:
|
|
528
|
+
homepage:
|
|
520
529
|
licenses: []
|
|
521
530
|
metadata: {}
|
|
522
|
-
post_install_message:
|
|
531
|
+
post_install_message:
|
|
523
532
|
rdoc_options: []
|
|
524
533
|
require_paths:
|
|
525
534
|
- lib
|
|
@@ -535,7 +544,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
535
544
|
version: '0'
|
|
536
545
|
requirements: []
|
|
537
546
|
rubygems_version: 3.4.6
|
|
538
|
-
signing_key:
|
|
547
|
+
signing_key:
|
|
539
548
|
specification_version: 4
|
|
540
549
|
summary: ''
|
|
541
550
|
test_files: []
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
module Glib
|
|
2
|
-
class ApiDocsController < ApplicationController
|
|
3
|
-
prepend_before_action do
|
|
4
|
-
params[:_skip_custom_render] = 'true' if params[:custom_render] != 'true'
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
if try(:glib_auth_inited?)
|
|
8
|
-
skip_before_action :glib_load_resource
|
|
9
|
-
skip_before_action :glib_authorize_resource
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Skip JSON injections from the host app
|
|
13
|
-
skip_after_action :json_inject_left_menu, raise: false
|
|
14
|
-
skip_after_action :json_inject_feature_usage_analytics, raise: false
|
|
15
|
-
skip_after_action :json_inject_flash_actions, raise: false
|
|
16
|
-
|
|
17
|
-
before_action :set_path_prefix
|
|
18
|
-
layout false
|
|
19
|
-
|
|
20
|
-
# Override injection methods to do nothing
|
|
21
|
-
def json_inject_left_menu; end
|
|
22
|
-
def json_inject_feature_usage_analytics; end
|
|
23
|
-
def json_inject_flash_actions; end
|
|
24
|
-
|
|
25
|
-
def set_path_prefix
|
|
26
|
-
@path_prefix = 'glib/api_docs'
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# GET /glib/json_ui_api
|
|
30
|
-
def index
|
|
31
|
-
@categories = load_all_categories
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# GET /glib/json_ui_api/:category
|
|
35
|
-
def show
|
|
36
|
-
@category = params[:category]
|
|
37
|
-
@doc = load_documentation(@category)
|
|
38
|
-
|
|
39
|
-
unless @doc
|
|
40
|
-
render plain: "Category '#{@category}' not found", status: :not_found
|
|
41
|
-
return
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
@components = @doc['components'] || {}
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# GET /glib/json_ui_api/:category/:component
|
|
48
|
-
def component
|
|
49
|
-
@category = params[:category]
|
|
50
|
-
@component_key = params[:component]
|
|
51
|
-
@doc = load_documentation(@category)
|
|
52
|
-
|
|
53
|
-
unless @doc
|
|
54
|
-
render plain: "Category '#{@category}' not found", status: :not_found
|
|
55
|
-
return
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
@component = @doc['components'][@component_key]
|
|
59
|
-
|
|
60
|
-
unless @component
|
|
61
|
-
render plain: "Component '#{@component_key}' not found in category '#{@category}'", status: :not_found
|
|
62
|
-
return
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def load_all_categories
|
|
69
|
-
# Try to find the doc/components directory
|
|
70
|
-
# First try the gem's path
|
|
71
|
-
doc_path = Glib::Web::Engine.root.join('doc', 'components')
|
|
72
|
-
|
|
73
|
-
# If not found, try relative to Rails root (for development in host app)
|
|
74
|
-
unless Dir.exist?(doc_path)
|
|
75
|
-
doc_path = Rails.root.join('..', 'glib-web', 'doc', 'components')
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
return [] unless Dir.exist?(doc_path)
|
|
79
|
-
|
|
80
|
-
categories = []
|
|
81
|
-
Dir.glob(doc_path.join('*.yml')).each do |file|
|
|
82
|
-
next if File.basename(file) == 'README.md'
|
|
83
|
-
|
|
84
|
-
basename = File.basename(file, '.yml')
|
|
85
|
-
doc = YAML.load_file(file)
|
|
86
|
-
|
|
87
|
-
categories << {
|
|
88
|
-
'key' => basename,
|
|
89
|
-
'name' => basename.titleize,
|
|
90
|
-
'file' => file,
|
|
91
|
-
'component_count' => doc['components']&.size || 0,
|
|
92
|
-
'source_file' => doc.dig('meta', 'source_file')
|
|
93
|
-
}
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
categories.sort_by { |c| c['name'] }
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def load_documentation(category)
|
|
100
|
-
# Try to find the YAML file
|
|
101
|
-
# First try the gem's path
|
|
102
|
-
doc_path = Glib::Web::Engine.root.join('doc', 'components', "#{category}.yml")
|
|
103
|
-
|
|
104
|
-
# If not found, try relative to Rails root (for development in host app)
|
|
105
|
-
unless File.exist?(doc_path)
|
|
106
|
-
doc_path = Rails.root.join('..', 'glib-web', 'doc', 'components', "#{category}.yml")
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
return nil unless File.exist?(doc_path)
|
|
110
|
-
|
|
111
|
-
YAML.load_file(doc_path)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
helper_method :json_ui_api_url
|
|
115
|
-
def json_ui_api_url(category: nil, component: nil)
|
|
116
|
-
if component && category
|
|
117
|
-
"/glib/json_ui_api/#{category}/#{component}"
|
|
118
|
-
elsif category
|
|
119
|
-
"/glib/json_ui_api/#{category}"
|
|
120
|
-
else
|
|
121
|
-
"/glib/json_ui_api"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
helper_method :property_type_badge_class
|
|
126
|
-
def property_type_badge_class(type)
|
|
127
|
-
case type
|
|
128
|
-
when 'string', 'text'
|
|
129
|
-
'badge-primary'
|
|
130
|
-
when 'int', 'float'
|
|
131
|
-
'badge-success'
|
|
132
|
-
when 'bool'
|
|
133
|
-
'badge-info'
|
|
134
|
-
when 'action'
|
|
135
|
-
'badge-warning'
|
|
136
|
-
when 'views', 'panels_builder'
|
|
137
|
-
'badge-secondary'
|
|
138
|
-
when 'hash', 'array'
|
|
139
|
-
'badge-dark'
|
|
140
|
-
else
|
|
141
|
-
'badge-light'
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
json.title @component['class_name']
|
|
2
|
-
|
|
3
|
-
page = json_ui_page json
|
|
4
|
-
|
|
5
|
-
page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
|
6
|
-
# Back button
|
|
7
|
-
scroll.button \
|
|
8
|
-
text: "← Back to #{@category.titleize}",
|
|
9
|
-
styleClasses: ['btn-outline-secondary', 'btn-sm'],
|
|
10
|
-
onClick: ->(action) do
|
|
11
|
-
action.windows_open url: json_ui_api_url(category: @category)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
scroll.spacer height: 16
|
|
15
|
-
|
|
16
|
-
# Title
|
|
17
|
-
scroll.panels_horizontal align: 'middle', childViews: ->(title_row) do
|
|
18
|
-
title_row.h1 text: @component['class_name']
|
|
19
|
-
|
|
20
|
-
if @component['extends']
|
|
21
|
-
title_row.spacer width: 12
|
|
22
|
-
title_row.h5 text: "extends #{@component['extends']}", styleClass: 'text-muted'
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Deprecated warning
|
|
27
|
-
if @component['deprecated']
|
|
28
|
-
scroll.spacer height: 16
|
|
29
|
-
scroll.panels_vertical \
|
|
30
|
-
padding: { all: 16 },
|
|
31
|
-
backgroundColor: '#fff3cd',
|
|
32
|
-
styleClasses: ['rounded'],
|
|
33
|
-
childViews: ->(warning) do
|
|
34
|
-
warning.h5 text: '⚠️ Deprecated', color: '#856404'
|
|
35
|
-
deprecation_text = @component['deprecated'] == true ? 'This component is deprecated and should not be used in new code.' : @component['deprecated']
|
|
36
|
-
warning.p text: deprecation_text, color: '#856404'
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
scroll.spacer height: 24
|
|
41
|
-
|
|
42
|
-
# Description
|
|
43
|
-
if @component['description']
|
|
44
|
-
scroll.h3 text: 'Description'
|
|
45
|
-
@component['description'].split("\n\n").each do |paragraph|
|
|
46
|
-
scroll.p text: paragraph.strip, styleClass: 'lead'
|
|
47
|
-
end
|
|
48
|
-
scroll.spacer height: 32
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Properties
|
|
52
|
-
if @component['properties'] && @component['properties'].any?
|
|
53
|
-
scroll.h2 text: 'Properties'
|
|
54
|
-
scroll.spacer height: 16
|
|
55
|
-
|
|
56
|
-
@component['properties'].each do |prop_name, prop|
|
|
57
|
-
scroll.panels_vertical \
|
|
58
|
-
padding: { all: 16 },
|
|
59
|
-
styleClasses: ['card', 'rounded'],
|
|
60
|
-
childViews: ->(prop_card) do
|
|
61
|
-
# Property name and badges
|
|
62
|
-
prop_card.panels_horizontal align: 'middle', childViews: ->(header) do
|
|
63
|
-
header.h5 text: prop_name, styleClass: 'font-monospace'
|
|
64
|
-
header.spacer width: 8
|
|
65
|
-
|
|
66
|
-
# Type badge
|
|
67
|
-
type_color = case prop['type']
|
|
68
|
-
when 'string', 'text' then '#0d6efd'
|
|
69
|
-
when 'int', 'float' then '#198754'
|
|
70
|
-
when 'bool' then '#0dcaf0'
|
|
71
|
-
when 'action' then '#ffc107'
|
|
72
|
-
when 'views', 'panels_builder' then '#6c757d'
|
|
73
|
-
when 'hash', 'array' then '#212529'
|
|
74
|
-
else '#f8f9fa'
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
header.label text: prop['type'], backgroundColor: type_color, styleClasses: ['badge'], color: '#ffffff'
|
|
78
|
-
|
|
79
|
-
if prop['required']
|
|
80
|
-
header.spacer width: 4
|
|
81
|
-
header.label text: 'required', backgroundColor: '#dc3545', styleClasses: ['badge'], color: '#ffffff'
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
if prop['deprecated']
|
|
85
|
-
header.spacer width: 4
|
|
86
|
-
header.label text: 'deprecated', backgroundColor: '#ffc107', styleClasses: ['badge']
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Description
|
|
91
|
-
if prop['description']
|
|
92
|
-
prop_card.spacer height: 8
|
|
93
|
-
prop['description'].split("\n").each do |line|
|
|
94
|
-
prop_card.p text: line.strip
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Hash options
|
|
99
|
-
if prop['options'] && prop['options']['optional']
|
|
100
|
-
prop_card.spacer height: 8
|
|
101
|
-
prop_card.label text: 'Optional hash keys:', styleClass: 'text-muted'
|
|
102
|
-
prop_card.label text: prop['options']['optional'].join(', '), styleClass: 'font-monospace'
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Examples
|
|
106
|
-
if prop['examples'] && prop['examples'].any?
|
|
107
|
-
prop_card.spacer height: 8
|
|
108
|
-
prop_card.label text: 'Example:', styleClasses: ['text-muted', 'font-weight-bold']
|
|
109
|
-
prop['examples'].each do |example|
|
|
110
|
-
prop_card.panels_vertical \
|
|
111
|
-
padding: { all: 12 },
|
|
112
|
-
backgroundColor: '#f6f8fa',
|
|
113
|
-
styleClasses: ['rounded'],
|
|
114
|
-
childViews: ->(code_block) do
|
|
115
|
-
code_block.label text: example, styleClass: 'font-monospace'
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Notes
|
|
121
|
-
if prop['notes'] && prop['notes'].any?
|
|
122
|
-
prop_card.spacer height: 8
|
|
123
|
-
prop_card.panels_vertical \
|
|
124
|
-
padding: { all: 12 },
|
|
125
|
-
backgroundColor: '#cfe2ff',
|
|
126
|
-
styleClasses: ['rounded'],
|
|
127
|
-
childViews: ->(note_block) do
|
|
128
|
-
note_block.label text: 'Note:', styleClasses: ['font-weight-bold'], color: '#084298'
|
|
129
|
-
prop['notes'].each do |note|
|
|
130
|
-
note_block.label text: note, color: '#084298'
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Deprecation
|
|
136
|
-
if prop['deprecated']
|
|
137
|
-
prop_card.spacer height: 8
|
|
138
|
-
prop_card.panels_vertical \
|
|
139
|
-
padding: { all: 12 },
|
|
140
|
-
backgroundColor: '#fff3cd',
|
|
141
|
-
styleClasses: ['rounded'],
|
|
142
|
-
childViews: ->(dep_block) do
|
|
143
|
-
dep_block.label text: 'Deprecated:', styleClasses: ['font-weight-bold'], color: '#856404'
|
|
144
|
-
dep_block.label text: prop['deprecated'], color: '#856404'
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
scroll.spacer height: 12
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
scroll.spacer height: 32
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Examples
|
|
156
|
-
if @component['examples'] && @component['examples'].any?
|
|
157
|
-
scroll.h2 text: 'Examples'
|
|
158
|
-
scroll.spacer height: 16
|
|
159
|
-
|
|
160
|
-
@component['examples'].each_with_index do |example, index|
|
|
161
|
-
scroll.panels_vertical \
|
|
162
|
-
styleClasses: ['card'],
|
|
163
|
-
childViews: ->(example_card) do
|
|
164
|
-
# Header
|
|
165
|
-
example_card.panels_vertical \
|
|
166
|
-
padding: { all: 12 },
|
|
167
|
-
backgroundColor: '#f8f9fa',
|
|
168
|
-
childViews: ->(header) do
|
|
169
|
-
header.h5 text: example['label'] || "Example #{index + 1}"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Code
|
|
173
|
-
example_card.panels_vertical \
|
|
174
|
-
padding: { all: 16 },
|
|
175
|
-
backgroundColor: '#f6f8fa',
|
|
176
|
-
childViews: ->(code_block) do
|
|
177
|
-
example['code'].split("\n").each do |line|
|
|
178
|
-
code_block.label text: line, styleClass: 'font-monospace'
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
scroll.spacer height: 12
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
scroll.spacer height: 32
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# References
|
|
190
|
-
if @component['references'] && @component['references'].any?
|
|
191
|
-
scroll.h2 text: 'References'
|
|
192
|
-
scroll.spacer height: 16
|
|
193
|
-
|
|
194
|
-
scroll.panels_vertical styleClasses: ['card'], childViews: ->(refs) do
|
|
195
|
-
@component['references'].each do |ref|
|
|
196
|
-
refs.panels_vertical padding: { all: 12 }, childViews: ->(ref_item) do
|
|
197
|
-
if ref['url']
|
|
198
|
-
ref_item.button \
|
|
199
|
-
text: ref['url'],
|
|
200
|
-
styleClasses: ['btn-link', 'text-start'],
|
|
201
|
-
onClick: ->(action) do
|
|
202
|
-
action.windows_open url: ref['url']
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
if ref['description']
|
|
206
|
-
ref_item.label text: ref['description'], styleClasses: ['text-muted', 'text-small']
|
|
207
|
-
end
|
|
208
|
-
else
|
|
209
|
-
ref_item.label text: ref['text'], styleClass: 'font-monospace'
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
json.title 'Glib Component API Documentation'
|
|
2
|
-
|
|
3
|
-
page = json_ui_page json
|
|
4
|
-
|
|
5
|
-
page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
|
6
|
-
# Header
|
|
7
|
-
scroll.panels_vertical \
|
|
8
|
-
padding: { top: 32, bottom: 32 },
|
|
9
|
-
backgroundColor: '#667eea',
|
|
10
|
-
align: 'center',
|
|
11
|
-
childViews: ->(header) do
|
|
12
|
-
header.h1 text: 'Glib Component API', color: '#ffffff'
|
|
13
|
-
header.p text: 'Interactive documentation for all Glib UI components', color: '#ffffff'
|
|
14
|
-
header.label text: 'Auto-generated from YARD comments in source code', color: '#e0e0e0', styleClass: 'text-small'
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
scroll.spacer height: 32
|
|
18
|
-
|
|
19
|
-
# Title
|
|
20
|
-
scroll.h2 text: 'Component Categories'
|
|
21
|
-
scroll.p text: 'Browse components by category', styleClass: 'text-muted'
|
|
22
|
-
|
|
23
|
-
scroll.spacer height: 16
|
|
24
|
-
|
|
25
|
-
if @categories.empty?
|
|
26
|
-
# Warning message
|
|
27
|
-
scroll.panels_vertical \
|
|
28
|
-
padding: { all: 16 },
|
|
29
|
-
backgroundColor: '#fff3cd',
|
|
30
|
-
styleClasses: ['rounded'],
|
|
31
|
-
childViews: ->(warning) do
|
|
32
|
-
warning.h4 text: 'No Documentation Found', color: '#856404'
|
|
33
|
-
warning.p text: 'Component documentation has not been generated yet.'
|
|
34
|
-
warning.p text: 'Run bin/generate_component_docs to generate documentation.', styleClass: 'code'
|
|
35
|
-
end
|
|
36
|
-
else
|
|
37
|
-
# Categories grid
|
|
38
|
-
scroll.panels_responsive width: 'matchParent', childViews: ->(responsive) do
|
|
39
|
-
@categories.each do |category|
|
|
40
|
-
responsive.panels_column \
|
|
41
|
-
lg: { cols: 4 },
|
|
42
|
-
md: { cols: 6 },
|
|
43
|
-
xs: { cols: 12 },
|
|
44
|
-
childViews: ->(col) do
|
|
45
|
-
col.panels_vertical \
|
|
46
|
-
padding: { all: 16 },
|
|
47
|
-
styleClasses: ['card', 'rounded'],
|
|
48
|
-
onClick: ->(action) do
|
|
49
|
-
action.windows_open url: json_ui_api_url(category: category['key'])
|
|
50
|
-
end,
|
|
51
|
-
childViews: ->(card) do
|
|
52
|
-
# Title with badge
|
|
53
|
-
card.panels_horizontal \
|
|
54
|
-
align: 'middle',
|
|
55
|
-
childViews: ->(title_row) do
|
|
56
|
-
title_row.h5 text: category['name']
|
|
57
|
-
title_row.spacer width: 8
|
|
58
|
-
title_row.label \
|
|
59
|
-
text: "#{category['component_count']} components",
|
|
60
|
-
styleClasses: ['badge', 'bg-primary']
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
card.spacer height: 8
|
|
64
|
-
|
|
65
|
-
# Source file
|
|
66
|
-
if category['source_file']
|
|
67
|
-
card.label \
|
|
68
|
-
text: File.basename(category['source_file']),
|
|
69
|
-
styleClasses: ['text-muted', 'text-small', 'font-monospace']
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
card.spacer height: 12
|
|
73
|
-
|
|
74
|
-
# Button
|
|
75
|
-
card.button \
|
|
76
|
-
text: 'View Components →',
|
|
77
|
-
styleClasses: ['btn-outline-primary', 'btn-sm']
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
scroll.spacer height: 48
|
|
85
|
-
|
|
86
|
-
# About section
|
|
87
|
-
scroll.h3 text: 'About This Documentation'
|
|
88
|
-
scroll.panels_vertical styleClasses: ['card'], padding: { all: 16 }, childViews: ->(about) do
|
|
89
|
-
about.p text: 'This documentation is automatically generated from YARD comments in the Glib source code. Each component includes:'
|
|
90
|
-
|
|
91
|
-
about.panels_ul childViews: ->(ul) do
|
|
92
|
-
ul.label text: 'Description - What the component does and when to use it'
|
|
93
|
-
ul.label text: 'Properties - All available properties with types and descriptions'
|
|
94
|
-
ul.label text: 'Examples - Real code examples showing how to use the component'
|
|
95
|
-
ul.label text: 'References - Links to external documentation and garage examples'
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
about.spacer height: 8
|
|
99
|
-
about.label \
|
|
100
|
-
text: 'For more information, see doc/components/README.md and doc/common/component_documentation_guidelines.md',
|
|
101
|
-
styleClasses: ['text-muted', 'text-small']
|
|
102
|
-
end
|
|
103
|
-
end
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
json.title "#{@category.titleize} Components"
|
|
2
|
-
|
|
3
|
-
page = json_ui_page json
|
|
4
|
-
|
|
5
|
-
page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
|
|
6
|
-
# Back button
|
|
7
|
-
scroll.button \
|
|
8
|
-
text: '← Back to Categories',
|
|
9
|
-
styleClasses: ['btn-outline-secondary', 'btn-sm'],
|
|
10
|
-
onClick: ->(action) do
|
|
11
|
-
action.windows_open url: json_ui_api_url
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
scroll.spacer height: 16
|
|
15
|
-
|
|
16
|
-
# Title
|
|
17
|
-
scroll.h2 text: "#{@category.titleize} Components"
|
|
18
|
-
|
|
19
|
-
# Metadata
|
|
20
|
-
if @doc['meta']
|
|
21
|
-
scroll.panels_vertical \
|
|
22
|
-
padding: { all: 12 },
|
|
23
|
-
backgroundColor: '#f8f9fa',
|
|
24
|
-
styleClasses: ['rounded'],
|
|
25
|
-
childViews: ->(meta) do
|
|
26
|
-
meta.label \
|
|
27
|
-
text: "Source: #{@doc['meta']['source_file']}",
|
|
28
|
-
styleClasses: ['text-muted', 'text-small', 'font-monospace']
|
|
29
|
-
|
|
30
|
-
if @doc['meta']['generated_at']
|
|
31
|
-
generated_time = Time.parse(@doc['meta']['generated_at']).strftime('%Y-%m-%d %H:%M') rescue @doc['meta']['generated_at']
|
|
32
|
-
meta.label \
|
|
33
|
-
text: "Generated: #{generated_time}",
|
|
34
|
-
styleClasses: ['text-muted', 'text-small']
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
scroll.spacer height: 24
|
|
40
|
-
|
|
41
|
-
# Component cards
|
|
42
|
-
@components.sort.each do |key, component|
|
|
43
|
-
scroll.panels_vertical \
|
|
44
|
-
id: key,
|
|
45
|
-
padding: { all: 16 },
|
|
46
|
-
styleClasses: ['card', 'rounded'],
|
|
47
|
-
childViews: ->(card) do
|
|
48
|
-
# Title
|
|
49
|
-
card.panels_horizontal align: 'middle', childViews: ->(title_row) do
|
|
50
|
-
title_row.h4 text: component['class_name']
|
|
51
|
-
|
|
52
|
-
if component['extends']
|
|
53
|
-
title_row.spacer width: 8
|
|
54
|
-
title_row.label \
|
|
55
|
-
text: "extends #{component['extends']}",
|
|
56
|
-
styleClasses: ['text-muted']
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Deprecated warning
|
|
61
|
-
if component['deprecated']
|
|
62
|
-
card.spacer height: 8
|
|
63
|
-
card.panels_vertical \
|
|
64
|
-
padding: { all: 12 },
|
|
65
|
-
backgroundColor: '#fff3cd',
|
|
66
|
-
styleClasses: ['rounded'],
|
|
67
|
-
childViews: ->(warning) do
|
|
68
|
-
warning.label text: '⚠️ Deprecated', color: '#856404', styleClass: 'font-weight-bold'
|
|
69
|
-
deprecation_text = component['deprecated'] == true ? 'This component is deprecated' : component['deprecated']
|
|
70
|
-
warning.label text: deprecation_text, color: '#856404'
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Description
|
|
75
|
-
if component['description']
|
|
76
|
-
card.spacer height: 12
|
|
77
|
-
component['description'].split("\n\n").each do |paragraph|
|
|
78
|
-
card.p text: paragraph.strip
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
card.spacer height: 12
|
|
83
|
-
|
|
84
|
-
# Metadata and View Details button
|
|
85
|
-
card.panels_horizontal align: 'middle', childViews: ->(actions) do
|
|
86
|
-
actions.button \
|
|
87
|
-
text: 'View Details →',
|
|
88
|
-
styleClasses: ['btn-outline-primary', 'btn-sm'],
|
|
89
|
-
onClick: ->(action) do
|
|
90
|
-
action.windows_open url: json_ui_api_url(category: @category, component: key)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if component['properties']
|
|
94
|
-
actions.spacer width: 12
|
|
95
|
-
actions.label \
|
|
96
|
-
text: "#{component['properties'].size} #{'property'.pluralize(component['properties'].size)}",
|
|
97
|
-
styleClasses: ['text-muted']
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
if component['examples']
|
|
101
|
-
actions.spacer width: 12
|
|
102
|
-
actions.label \
|
|
103
|
-
text: "#{component['examples'].size} #{'example'.pluralize(component['examples'].size)}",
|
|
104
|
-
styleClasses: ['text-muted']
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
scroll.spacer height: 16
|
|
110
|
-
end
|
|
111
|
-
end
|
data/lib/tasks/docs.rake
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
namespace :glib do
|
|
2
|
-
namespace :docs do
|
|
3
|
-
desc 'Generate component documentation in YAML format'
|
|
4
|
-
task :generate do
|
|
5
|
-
require_relative '../glib/doc_generator'
|
|
6
|
-
|
|
7
|
-
generator = Glib::DocGenerator.new
|
|
8
|
-
|
|
9
|
-
# Generate documentation for different component categories
|
|
10
|
-
categories = [
|
|
11
|
-
{
|
|
12
|
-
name: 'panels',
|
|
13
|
-
file: 'app/helpers/glib/json_ui/view_builder/panels.rb',
|
|
14
|
-
output: 'doc/components/panels.yml'
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
name: 'fields',
|
|
18
|
-
file: 'app/helpers/glib/json_ui/view_builder/fields.rb',
|
|
19
|
-
output: 'doc/components/fields.yml'
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
name: 'views',
|
|
23
|
-
file: 'app/helpers/glib/json_ui/view_builder.rb',
|
|
24
|
-
output: 'doc/components/views.yml'
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'actions',
|
|
28
|
-
file: 'app/helpers/glib/json_ui/action_builder.rb',
|
|
29
|
-
output: 'doc/components/actions.yml'
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
categories.each do |category|
|
|
34
|
-
puts "Generating documentation for #{category[:name]}..."
|
|
35
|
-
generator.generate_for_file(category[:file], category[:output])
|
|
36
|
-
puts " -> #{category[:output]}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Also scan action subdirectories
|
|
40
|
-
Dir.glob('app/helpers/glib/json_ui/action_builder/*.rb').each do |file|
|
|
41
|
-
basename = File.basename(file, '.rb')
|
|
42
|
-
output = "doc/components/actions_#{basename}.yml"
|
|
43
|
-
puts "Generating documentation for actions/#{basename}..."
|
|
44
|
-
generator.generate_for_file(file, output)
|
|
45
|
-
puts " -> #{output}"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
puts "\nDocumentation generation complete!"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
desc 'Validate YARD documentation'
|
|
52
|
-
task :validate do
|
|
53
|
-
puts "Validating YARD documentation..."
|
|
54
|
-
system('yard stats --list-undoc')
|
|
55
|
-
system('yard doc --fail-on-warning --no-output')
|
|
56
|
-
puts "\nValidation complete!"
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|