glib-web 5.0.1 → 5.0.3
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/json_ui/abstract_builder.rb +7 -1
- data/app/helpers/glib/json_ui/action_builder.rb +35 -0
- data/app/helpers/glib/json_ui/page_helper.rb +13 -8
- data/app/helpers/glib/json_ui/view_builder/fields.rb +3 -0
- data/app/helpers/glib/json_ui/view_builder.rb +8 -15
- data/config/routes.rb +0 -4
- data/lib/glib/test_helpers.rb +127 -0
- metadata +7 -7
- 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: 4f8b29b4f537ee6bf84d38fb8e0e9731b66a6f7dd39cc7498d2dc38680b47832
|
|
4
|
+
data.tar.gz: e6b063e4098ef285461c961c166ce3bb18f54749b20e83a27ccea15480f37c43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68b869bf225f42361ee6e2aa10004101469ed51298961dd5acd86a33305c6a475a864788071e44d2d1fc6fa6253a0d198279936052ccb127a4d74b4bbf6cf164
|
|
7
|
+
data.tar.gz: c7204c91732b75735519b7242ee04d481ef11a938f0194963f3eaeb21b2c8e3d65c45e29c1b7a45398440eb4475126dfcf51397ff09e5a725d12b7714c28ea4f
|
|
@@ -286,7 +286,13 @@ module Glib
|
|
|
286
286
|
def self.action(propName)
|
|
287
287
|
define_method(propName) do |value|
|
|
288
288
|
if value # Allow nil value
|
|
289
|
-
|
|
289
|
+
# Isolate nested action callbacks (e.g. onClick) from the outer context's
|
|
290
|
+
# singleton-tracking state, and ensure the outer state is restored even if
|
|
291
|
+
# the callback raises.
|
|
292
|
+
json.set!(propName) do
|
|
293
|
+
action_builder = page.action_builder
|
|
294
|
+
action_builder.with_isolated_singleton_tracking { value.call(action_builder) }
|
|
295
|
+
end
|
|
290
296
|
end
|
|
291
297
|
end
|
|
292
298
|
end
|
|
@@ -7,10 +7,45 @@ 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
|
+
|
|
22
|
+
# Runs block with fresh singleton-tracking state, then restores whatever
|
|
23
|
+
# was there before — even if the block raises. Used around user-provided
|
|
24
|
+
# callbacks (onLoad, onUnload, etc.) so that their action state doesn't
|
|
25
|
+
# leak out and trip the singleton guard on later actions.
|
|
26
|
+
def with_isolated_singleton_tracking
|
|
27
|
+
saved_set = @singleton_action_set
|
|
28
|
+
saved_name = @singleton_action_name
|
|
29
|
+
reset_singleton_tracking!
|
|
30
|
+
begin
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
restore_singleton_tracking!(saved_set, saved_name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
10
37
|
def method_missing(m, *args)
|
|
11
38
|
if @multiple
|
|
12
39
|
add_element_to_array_v1 'action', m, *args
|
|
13
40
|
else
|
|
41
|
+
if !Rails.env.production? && @singleton_action_set
|
|
42
|
+
raise "Multiple actions called on a singleton ActionBuilder. " \
|
|
43
|
+
"Only one action is allowed. Previous action: '#{@singleton_action_name}', " \
|
|
44
|
+
"current action: '#{m}'. Use `run_multiple` with `childActions` or " \
|
|
45
|
+
"chain actions using `onClose` callbacks."
|
|
46
|
+
end
|
|
47
|
+
@singleton_action_set = true
|
|
48
|
+
@singleton_action_name = m
|
|
14
49
|
add_singleton_element_v1 'action', m, *args
|
|
15
50
|
end
|
|
16
51
|
end
|
|
@@ -76,6 +76,11 @@ module Glib
|
|
|
76
76
|
|
|
77
77
|
def json_ui_action(json)
|
|
78
78
|
@__json_ui_action ||= Page.new(json, self)
|
|
79
|
+
# The Page is memoized across calls within the same view context (e.g. sequential
|
|
80
|
+
# injection templates sharing one render). Reset the builder's singleton-tracking
|
|
81
|
+
# state so each caller gets a fresh state, otherwise a prior caller's action
|
|
82
|
+
# would trip the singleton guard.
|
|
83
|
+
@__json_ui_action.action_builder.reset_singleton_tracking!
|
|
79
84
|
@__json_ui_action.action_builder
|
|
80
85
|
end
|
|
81
86
|
|
|
@@ -135,19 +140,19 @@ module Glib
|
|
|
135
140
|
json.actionCable do
|
|
136
141
|
if (on_error = options[:onError])
|
|
137
142
|
json.onError do
|
|
138
|
-
on_error.call(@action_builder)
|
|
143
|
+
@action_builder.with_isolated_singleton_tracking { on_error.call(@action_builder) }
|
|
139
144
|
end
|
|
140
145
|
end
|
|
141
146
|
|
|
142
147
|
if (on_open = options[:onOpen])
|
|
143
148
|
json.onOpen do
|
|
144
|
-
on_open.call(@action_builder)
|
|
149
|
+
@action_builder.with_isolated_singleton_tracking { on_open.call(@action_builder) }
|
|
145
150
|
end
|
|
146
151
|
end
|
|
147
152
|
|
|
148
153
|
if (on_close = options[:onClose])
|
|
149
154
|
json.onClose do
|
|
150
|
-
on_close.call(@action_builder)
|
|
155
|
+
@action_builder.with_isolated_singleton_tracking { on_close.call(@action_builder) }
|
|
151
156
|
end
|
|
152
157
|
end
|
|
153
158
|
|
|
@@ -298,31 +303,31 @@ module Glib
|
|
|
298
303
|
def on(options = {})
|
|
299
304
|
if (on_load = options[:load])
|
|
300
305
|
json.onLoad do
|
|
301
|
-
on_load.call @action_builder
|
|
306
|
+
@action_builder.with_isolated_singleton_tracking { on_load.call @action_builder }
|
|
302
307
|
end
|
|
303
308
|
end
|
|
304
309
|
|
|
305
310
|
if (on_rerender = options[:reRender])
|
|
306
311
|
json.onRerender do
|
|
307
|
-
on_rerender.call @action_builder
|
|
312
|
+
@action_builder.with_isolated_singleton_tracking { on_rerender.call @action_builder }
|
|
308
313
|
end
|
|
309
314
|
end
|
|
310
315
|
|
|
311
316
|
if (on_unload = options[:unload])
|
|
312
317
|
json.onUnload do
|
|
313
|
-
on_unload.call @action_builder
|
|
318
|
+
@action_builder.with_isolated_singleton_tracking { on_unload.call @action_builder }
|
|
314
319
|
end
|
|
315
320
|
end
|
|
316
321
|
|
|
317
322
|
if (on_refocus = options[:refocus])
|
|
318
323
|
json.onRefocus do
|
|
319
|
-
on_refocus.call @action_builder
|
|
324
|
+
@action_builder.with_isolated_singleton_tracking { on_refocus.call @action_builder }
|
|
320
325
|
end
|
|
321
326
|
end
|
|
322
327
|
|
|
323
328
|
if (on_foreground = options[:foreground])
|
|
324
329
|
json.onForeground do
|
|
325
|
-
on_foreground.call @action_builder
|
|
330
|
+
@action_builder.with_isolated_singleton_tracking { on_foreground.call @action_builder }
|
|
326
331
|
end
|
|
327
332
|
end
|
|
328
333
|
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
|
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
|
metadata
CHANGED
|
@@ -1,10 +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.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 2019-10-04 00:00:00.000000000 Z
|
|
@@ -149,6 +150,7 @@ dependencies:
|
|
|
149
150
|
- - ">="
|
|
150
151
|
- !ruby/object:Gem::Version
|
|
151
152
|
version: '0'
|
|
153
|
+
description:
|
|
152
154
|
email: ''
|
|
153
155
|
executables: []
|
|
154
156
|
extensions: []
|
|
@@ -168,7 +170,6 @@ files:
|
|
|
168
170
|
- app/controllers/concerns/glib/json/traversal.rb
|
|
169
171
|
- app/controllers/concerns/glib/json/ui.rb
|
|
170
172
|
- app/controllers/concerns/glib/json/validation.rb
|
|
171
|
-
- app/controllers/glib/api_docs_controller.rb
|
|
172
173
|
- app/controllers/glib/blob_url_generators_controller.rb
|
|
173
174
|
- app/controllers/glib/errors_controller.rb
|
|
174
175
|
- app/controllers/glib/glib_direct_uploads_controller.rb
|
|
@@ -233,9 +234,6 @@ files:
|
|
|
233
234
|
- app/validators/mutually_exclusive_with_validator.rb
|
|
234
235
|
- app/validators/presence_only_if_validator.rb
|
|
235
236
|
- app/validators/url_validator.rb
|
|
236
|
-
- app/views/glib/api_docs/component.json.jbuilder
|
|
237
|
-
- app/views/glib/api_docs/index.json.jbuilder
|
|
238
|
-
- app/views/glib/api_docs/show.json.jbuilder
|
|
239
237
|
- app/views/json_ui/garage/_nav_menu.json.jbuilder
|
|
240
238
|
- app/views/json_ui/garage/actions/_commands.json.jbuilder
|
|
241
239
|
- app/views/json_ui/garage/actions/_components.json.jbuilder
|
|
@@ -527,9 +525,10 @@ files:
|
|
|
527
525
|
- lib/glib/value.rb
|
|
528
526
|
- lib/glib/version.rb
|
|
529
527
|
- lib/tasks/db.rake
|
|
530
|
-
|
|
528
|
+
homepage:
|
|
531
529
|
licenses: []
|
|
532
530
|
metadata: {}
|
|
531
|
+
post_install_message:
|
|
533
532
|
rdoc_options: []
|
|
534
533
|
require_paths:
|
|
535
534
|
- lib
|
|
@@ -544,7 +543,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
544
543
|
- !ruby/object:Gem::Version
|
|
545
544
|
version: '0'
|
|
546
545
|
requirements: []
|
|
547
|
-
rubygems_version: 4.
|
|
546
|
+
rubygems_version: 3.4.6
|
|
547
|
+
signing_key:
|
|
548
548
|
specification_version: 4
|
|
549
549
|
summary: ''
|
|
550
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
|