glib-web 5.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf55f8e828ad48e18227bfeb9940e268090705f7dfeec2b180a1b7d858108f9b
4
- data.tar.gz: 6aa26bf19c388341ec451e523910818871df09673bb5f816f6021e09a26a0cbc
3
+ metadata.gz: e50fd58d6fa3b0ee4d505c72f4bc49adf0809e208486eeb5c0aaeada10d39dfa
4
+ data.tar.gz: f32fb5c6985560a68a6240586e2a6411ffaec55dab2a30c12eecc177a002efaa
5
5
  SHA512:
6
- metadata.gz: 29f3fb45d898e5855420695e3c6c564cfaee9631506faf3d9e05d71ee6d549aa9be842d58127e998333396026cc2e68f27788c2877a394197b8b763e6b7f42b4
7
- data.tar.gz: 390ab4fab4e75f3327dfab76e46b3f5868105d4ff0701873e9db9a91ef77fa8d1b92764652e45f806b0f3634438fff4541c34f4e827bad33c004479d47c73e7d
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) { value.call(page.action_builder) }
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
- json.set!('onMaxFileSizeError') { @onMaxFileSizeError.call(page.action_builder) }
85
- json.set!('onMaxFileLengthError') { @onMaxFileLengthError.call(page.action_builder) }
86
- json.set!('onFileTypeError') { @onFileTypeError.call(page.action_builder) }
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]
@@ -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
@@ -64,6 +64,7 @@ namespace :db do
64
64
  entries.each do |a|
65
65
  attrs = Hash[a.attributes.sort]
66
66
  attrs.delete_if { |k, v| v.nil? }
67
+ # attrs.delete_if { |k, v| v.nil? }
67
68
 
68
69
  id = if attrs.include?('id')
69
70
  [a.id]
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.1
4
+ version: 5.0.2
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
- - lib/tasks/docs.rake
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.0.6
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