glib-web 0.5.5

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.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/app/controllers/concerns/glib/auth/policy.rb +148 -0
  3. data/app/controllers/concerns/glib/json/dynamic_text.rb +126 -0
  4. data/app/controllers/concerns/glib/json/libs.rb +144 -0
  5. data/app/controllers/concerns/glib/json/new_dynamic_text.rb +122 -0
  6. data/app/controllers/concerns/glib/json/transformation.rb +11 -0
  7. data/app/controllers/concerns/glib/json/traversal.rb +85 -0
  8. data/app/controllers/concerns/glib/json/ui.rb +70 -0
  9. data/app/controllers/concerns/glib/json/validation.rb +13 -0
  10. data/app/controllers/glib/home_controller.rb +16 -0
  11. data/app/helpers/glib/app_feature_support_helper.rb +16 -0
  12. data/app/helpers/glib/dynamic_images_helper.rb +52 -0
  13. data/app/helpers/glib/dynamic_texts_helper.rb +42 -0
  14. data/app/helpers/glib/forms_helper.rb +15 -0
  15. data/app/helpers/glib/json_ui/abstract_builder.rb +281 -0
  16. data/app/helpers/glib/json_ui/action_builder.rb +81 -0
  17. data/app/helpers/glib/json_ui/action_builder/dialogs.rb +58 -0
  18. data/app/helpers/glib/json_ui/action_builder/http.rb +19 -0
  19. data/app/helpers/glib/json_ui/action_builder/sheets.rb +15 -0
  20. data/app/helpers/glib/json_ui/action_builder/snackbars.rb +41 -0
  21. data/app/helpers/glib/json_ui/action_builder/windows.rb +25 -0
  22. data/app/helpers/glib/json_ui/dynamic_field_builders.rb +25 -0
  23. data/app/helpers/glib/json_ui/generic_builders.rb +28 -0
  24. data/app/helpers/glib/json_ui/list_builders.rb +87 -0
  25. data/app/helpers/glib/json_ui/menu_builder.rb +52 -0
  26. data/app/helpers/glib/json_ui/page_helper.rb +187 -0
  27. data/app/helpers/glib/json_ui/response_helper.rb +23 -0
  28. data/app/helpers/glib/json_ui/split_builders.rb +32 -0
  29. data/app/helpers/glib/json_ui/styling_helper.rb +25 -0
  30. data/app/helpers/glib/json_ui/table_builders.rb +74 -0
  31. data/app/helpers/glib/json_ui/view_builder.rb +185 -0
  32. data/app/helpers/glib/json_ui/view_builder/banners.rb +24 -0
  33. data/app/helpers/glib/json_ui/view_builder/charts.rb +33 -0
  34. data/app/helpers/glib/json_ui/view_builder/fields.rb +213 -0
  35. data/app/helpers/glib/json_ui/view_builder/panels.rb +219 -0
  36. data/app/models/glib/active_storage/attachment.rb +9 -0
  37. data/app/models/glib/active_storage/blob.rb +9 -0
  38. data/app/models/glib/dynamic_text_record.rb +9 -0
  39. data/app/models/glib/text.rb +96 -0
  40. data/app/policies/glib/application_policy.rb +164 -0
  41. data/app/validators/email_typo_validator.rb +38 -0
  42. data/app/validators/email_validator.rb +7 -0
  43. data/app/validators/url_validator.rb +20 -0
  44. data/app/views/app/views/json_ui/vue/renderer.html.erb +35 -0
  45. data/app/views/json_ui/garage/_nav_menu.json.jbuilder +71 -0
  46. data/app/views/json_ui/garage/actions/_dialogs.json.jbuilder +104 -0
  47. data/app/views/json_ui/garage/actions/_http.json.jbuilder +18 -0
  48. data/app/views/json_ui/garage/actions/_reload.json.jbuilder +17 -0
  49. data/app/views/json_ui/garage/actions/_sheets.json.jbuilder +19 -0
  50. data/app/views/json_ui/garage/actions/_snackbars.json.jbuilder +33 -0
  51. data/app/views/json_ui/garage/actions/_timeouts.json.jbuilder +18 -0
  52. data/app/views/json_ui/garage/actions/_windows.json.jbuilder +24 -0
  53. data/app/views/json_ui/garage/actions/dialogs_oauth_post.json.jbuilder +6 -0
  54. data/app/views/json_ui/garage/actions/index.json.jbuilder +23 -0
  55. data/app/views/json_ui/garage/forms/_alert_post_data.json.jbuilder +7 -0
  56. data/app/views/json_ui/garage/forms/basic.json.jbuilder +34 -0
  57. data/app/views/json_ui/garage/forms/basic_post.json.jbuilder +8 -0
  58. data/app/views/json_ui/garage/forms/checkboxes.json.jbuilder +44 -0
  59. data/app/views/json_ui/garage/forms/dynamic_group.json.jbuilder +41 -0
  60. data/app/views/json_ui/garage/forms/dynamic_select.json.jbuilder +25 -0
  61. data/app/views/json_ui/garage/forms/dynamic_select_data.json.jbuilder +38 -0
  62. data/app/views/json_ui/garage/forms/file_upload.json.jbuilder +58 -0
  63. data/app/views/json_ui/garage/forms/floating_submit.json.jbuilder +31 -0
  64. data/app/views/json_ui/garage/forms/generic_post.json.jbuilder +3 -0
  65. data/app/views/json_ui/garage/forms/get_request.json.jbuilder +28 -0
  66. data/app/views/json_ui/garage/forms/index.json.jbuilder +101 -0
  67. data/app/views/json_ui/garage/forms/pickers.json.jbuilder +46 -0
  68. data/app/views/json_ui/garage/forms/rich_text.json.jbuilder +40 -0
  69. data/app/views/json_ui/garage/forms/selects.json.jbuilder +70 -0
  70. data/app/views/json_ui/garage/forms/show_hide.json.jbuilder +88 -0
  71. data/app/views/json_ui/garage/forms/styled_boxes.json.jbuilder +32 -0
  72. data/app/views/json_ui/garage/forms/submission_flow.json.jbuilder +17 -0
  73. data/app/views/json_ui/garage/forms/submission_flow_post.json.jbuilder +24 -0
  74. data/app/views/json_ui/garage/forms/submission_indicator.json.jbuilder +63 -0
  75. data/app/views/json_ui/garage/forms/submission_indicator_post.json.jbuilder +25 -0
  76. data/app/views/json_ui/garage/forms/text_validation.json.jbuilder +22 -0
  77. data/app/views/json_ui/garage/home/blank.json.jbuilder +11 -0
  78. data/app/views/json_ui/garage/home/index.json.jbuilder +32 -0
  79. data/app/views/json_ui/garage/home/slow.json.jbuilder +11 -0
  80. data/app/views/json_ui/garage/lists/_infinite_scroll_section.json.jbuilder +20 -0
  81. data/app/views/json_ui/garage/lists/edit_actions.json.jbuilder +19 -0
  82. data/app/views/json_ui/garage/lists/fab.json.jbuilder +14 -0
  83. data/app/views/json_ui/garage/lists/index.json.jbuilder +23 -0
  84. data/app/views/json_ui/garage/lists/infinite_scroll.json.jbuilder +38 -0
  85. data/app/views/json_ui/garage/lists/templating.json.jbuilder +35 -0
  86. data/app/views/json_ui/garage/notifications/index.json.jbuilder +18 -0
  87. data/app/views/json_ui/garage/notifications/web_socket.json.jbuilder +60 -0
  88. data/app/views/json_ui/garage/pages/flat_centered.json.jbuilder +29 -0
  89. data/app/views/json_ui/garage/pages/full_width.json.jbuilder +29 -0
  90. data/app/views/json_ui/garage/pages/full_width_height.json.jbuilder +16 -0
  91. data/app/views/json_ui/garage/pages/index.json.jbuilder +47 -0
  92. data/app/views/json_ui/garage/pages/layout.json.jbuilder +19 -0
  93. data/app/views/json_ui/garage/pages/loading_indicator.json.jbuilder +10 -0
  94. data/app/views/json_ui/garage/pages/nav_buttons.json.jbuilder +21 -0
  95. data/app/views/json_ui/garage/pages/tab_bar.json.jbuilder +27 -0
  96. data/app/views/json_ui/garage/panels/_styled.json.jbuilder +78 -0
  97. data/app/views/json_ui/garage/panels/card.json.jbuilder +4 -0
  98. data/app/views/json_ui/garage/panels/carousel.json.jbuilder +16 -0
  99. data/app/views/json_ui/garage/panels/custom.json.jbuilder +17 -0
  100. data/app/views/json_ui/garage/panels/flow.json.jbuilder +49 -0
  101. data/app/views/json_ui/garage/panels/horizontal.json.jbuilder +91 -0
  102. data/app/views/json_ui/garage/panels/index.json.jbuilder +132 -0
  103. data/app/views/json_ui/garage/panels/outlined.json.jbuilder +4 -0
  104. data/app/views/json_ui/garage/panels/responsive.json.jbuilder +88 -0
  105. data/app/views/json_ui/garage/panels/split.json.jbuilder +183 -0
  106. data/app/views/json_ui/garage/panels/vertical.json.jbuilder +50 -0
  107. data/app/views/json_ui/garage/services/dynamic_text.json.jbuilder +13 -0
  108. data/app/views/json_ui/garage/services/image.json.jbuilder +47 -0
  109. data/app/views/json_ui/garage/services/index.json.jbuilder +17 -0
  110. data/app/views/json_ui/garage/tables/_autoload_section.json.jbuilder +13 -0
  111. data/app/views/json_ui/garage/tables/autoload_all.json.jbuilder +38 -0
  112. data/app/views/json_ui/garage/tables/autoload_as_needed.json.jbuilder +39 -0
  113. data/app/views/json_ui/garage/tables/export_import.json.jbuilder +29 -0
  114. data/app/views/json_ui/garage/tables/horizontal_scroll.json.jbuilder +26 -0
  115. data/app/views/json_ui/garage/tables/index.json.jbuilder +26 -0
  116. data/app/views/json_ui/garage/tables/layout.json.jbuilder +38 -0
  117. data/app/views/json_ui/garage/views/_chart_data.json.jbuilder +17 -0
  118. data/app/views/json_ui/garage/views/banners.json.jbuilder +51 -0
  119. data/app/views/json_ui/garage/views/calendar_data.json.jbuilder +30 -0
  120. data/app/views/json_ui/garage/views/carousels.json.jbuilder +37 -0
  121. data/app/views/json_ui/garage/views/charts.json.jbuilder +115 -0
  122. data/app/views/json_ui/garage/views/images.json.jbuilder +89 -0
  123. data/app/views/json_ui/garage/views/index.json.jbuilder +48 -0
  124. data/app/views/json_ui/garage/views/links.json.jbuilder +70 -0
  125. data/app/views/json_ui/garage/views/map_data.json.jbuilder +43 -0
  126. data/app/views/json_ui/garage/views/markdowns.json.jbuilder +41 -0
  127. data/app/views/json_ui/garage/views/misc.json.jbuilder +34 -0
  128. data/app/views/json_ui/garage/views/texts.json.jbuilder +41 -0
  129. data/app/views/layouts/json_ui/renderer.html.erb +32 -0
  130. data/config/routes.rb +8 -0
  131. data/lib/generators/glib/install_generator.rb +24 -0
  132. data/lib/generators/templates/20191017062519_create_texts.rb +12 -0
  133. data/lib/generators/templates/20191024063257_add_scope_to_texts.rb +7 -0
  134. data/lib/generators/templates/20191112095018_add_lang_to_texts.rb +7 -0
  135. data/lib/generators/templates/20191126071051_create_active_storage_tables.active_storage.rb +27 -0
  136. data/lib/generators/templates/database.yml +107 -0
  137. data/lib/generators/templates/dynamic_text.rb +2 -0
  138. data/lib/glib-web.rb +8 -0
  139. data/lib/glib/crypt.rb +1 -0
  140. data/lib/glib/crypt/utils.rb +26 -0
  141. data/lib/glib/dynamic_text.rb +1 -0
  142. data/lib/glib/dynamic_text/config.rb +21 -0
  143. data/lib/glib/engine.rb +7 -0
  144. data/lib/glib/json_crawler.rb +10 -0
  145. data/lib/glib/json_crawler/action_crawler.rb +20 -0
  146. data/lib/glib/json_crawler/action_crawlers/action_http.rb +14 -0
  147. data/lib/glib/json_crawler/action_crawlers/forms_submit.rb +48 -0
  148. data/lib/glib/json_crawler/action_crawlers/menu.rb +12 -0
  149. data/lib/glib/json_crawler/action_crawlers/nav_initiate.rb +15 -0
  150. data/lib/glib/json_crawler/action_crawlers/windows_open.rb +28 -0
  151. data/lib/glib/json_crawler/coverage.rb +20 -0
  152. data/lib/glib/json_crawler/http.rb +120 -0
  153. data/lib/glib/json_crawler/router.rb +86 -0
  154. data/lib/glib/test_helpers.rb +40 -0
  155. data/lib/glib/value.rb +7 -0
  156. data/lib/glib/version.rb +5 -0
  157. data/lib/tasks/db.rake +95 -0
  158. metadata +246 -0
@@ -0,0 +1,11 @@
1
+ module Glib::Json::Transformation
2
+ def json_transformation_start
3
+ if request.format == 'json'
4
+ @__transformed_json ||= JSON.parse(response.body) rescue nil
5
+ end
6
+ end
7
+
8
+ def __json_transformation_commit
9
+ response.body = @__transformed_json.to_json if @__transformed_json
10
+ end
11
+ end
@@ -0,0 +1,85 @@
1
+ module Glib::Json::Traversal
2
+ def __json_traversal_on_traverse(&block)
3
+ @__json_traversal_on_traverses ||= []
4
+ @__json_traversal_on_traverses << block
5
+ end
6
+
7
+ def __json_traversal_on_complete(&block)
8
+ @__json_traversal_on_completes ||= []
9
+ @__json_traversal_on_completes << block
10
+ end
11
+
12
+ def __json_traversal_perform
13
+ if (hash = json_transformation_start).is_a?(Hash)
14
+ traverse hash['header']&.[]('childViews')
15
+ traverse hash['body']&.[]('childViews')
16
+ traverse hash['footer']&.[]('childViews')
17
+
18
+ @__json_traversal_on_completes.each do |block|
19
+ block.call
20
+ end
21
+ end
22
+ end
23
+
24
+ def traverse(views)
25
+ Visitor.new.traverse_multiple views, ->(view) do
26
+ @__json_traversal_on_traverses.each do |block|
27
+ block.call(view)
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+
34
+ class Visitor
35
+ # Used in test crawler
36
+ attr_reader :forms
37
+
38
+ def initialize
39
+ @forms = []
40
+ end
41
+
42
+ def traverse_multiple(views, block)
43
+ if views.is_a? Array
44
+ views.each do |view|
45
+ case view['view']
46
+ when 'panels/form-v1'
47
+ @forms << view
48
+ traverse_single view, block
49
+ @forms.pop
50
+ else
51
+ traverse_single view, block
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def traverse_vertical_content(view, block)
58
+ traverse_multiple view['childViews'], block if view
59
+ end
60
+
61
+ def traverse_single(view, block)
62
+ block.call view
63
+
64
+ # Generic view children
65
+ traverse_multiple view['childViews'], block
66
+
67
+ # Generic menu buttons
68
+ traverse_multiple view['buttons'], block
69
+
70
+ # Split panel
71
+ traverse_vertical_content view['left'], block
72
+ traverse_vertical_content view['center'], block
73
+ traverse_vertical_content view['right'], block
74
+
75
+ # TODO: crawl header and footer
76
+ # Table/List
77
+ if (sections = view['sections']).is_a? Array
78
+ sections.each do |section|
79
+ # Table
80
+ traverse_multiple section['rows'], block
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,70 @@
1
+ module Glib::Json::Ui
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rescue_from ActionController::UnknownFormat do |exception|
6
+ if json_ui_activated?
7
+ # Tell `__json_ui_start()` to avoid rendering this page while still retaining the `_render` param
8
+ # so that the page remains linking to other json_ui pages.
9
+ redirect_to url_for(format: nil, _skip_render: true)
10
+ else
11
+ raise exception
12
+ end
13
+ end
14
+ end
15
+
16
+ # Override
17
+ def form_authenticity_token(*args)
18
+ Rails.env.test? ? 'test_token' : super
19
+ end
20
+
21
+ # NOTE: Override default_url_options and call this method
22
+ def json_ui_url_options
23
+ options = {}
24
+ options[:_render] = params[:_render]
25
+ options[:_locale] = params[:_locale]
26
+ options[:format] = :json if request.format == :json
27
+ options
28
+ end
29
+
30
+ def json_ui_activated?
31
+ @__json_ui_activated
32
+ end
33
+
34
+ def __json_ui_start(options)
35
+ @__json_ui_activated = false
36
+ @__json_ui_rendering = nil
37
+ if options[:when] == :always || params[:_render] == 'v1'
38
+ @__json_ui_activated = true
39
+ request.variant = :ui
40
+
41
+ if request.format.html? && params[:_skip_render] != 'true'
42
+ @__json_ui_rendering = :vue
43
+ request.format = 'json'
44
+ end
45
+ end
46
+ end
47
+
48
+ def __json_ui_commit(options)
49
+ return if response.status >= 300
50
+
51
+ case @__json_ui_rendering
52
+ when :vue
53
+ if (hash = json_transformation_start).is_a?(Hash)
54
+ __json_ui_vue(hash, options)
55
+ else
56
+ raise "Invalid JSON UI payload: #{hash}"
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def __json_ui_vue(hash, options)
64
+ renderer_path = options[:renderer_path]
65
+ @__json_ui_orig_page = response.body
66
+ response.body = render_to_string(template: renderer_path, layout: 'json_ui/renderer', content_type: 'text/html', locals: { page: hash, options: options })
67
+
68
+ # response.body = render_to_string(template: renderer_path, layout: false, content_type: 'text/html', locals: { page: hash, options: options })
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ module Glib::Json::Validation
2
+ def __json_validate_perform
3
+ if Rails.env.development? && params[:_validate] == 'true'
4
+ if (hash = json_transformation_start).is_a?(Hash)
5
+ json_validate = JSONValidate.new(hash)
6
+ response_message = json_validate.valid?
7
+ hash[:_json] = {
8
+ validationErrors: response_message
9
+ }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Glib
2
+ class HomeController < ApplicationController
3
+ if try(:glib_auth_inited?)
4
+ skip_before_action :glib_load_resource
5
+ skip_before_action :glib_authorize_resource
6
+ end
7
+
8
+ def json_ui_garage
9
+ @path_prefix = 'json_ui/garage'
10
+
11
+ # We can't use prepend_view_path because it affects the app, not the gem
12
+ path = "#{@path_prefix}/#{params[:path] || 'home/index'}"
13
+ render path
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Glib
2
+ module AppFeatureSupportHelper
3
+ def glib_app_feature_supported?(name, app_version, dictionary)
4
+ device = (app_device_os || :default).to_sym
5
+
6
+ version = dictionary[name.to_sym].try(:[], device)
7
+ except = dictionary[name.to_sym].try(:[], :except).try(:[], device)
8
+
9
+ !version.nil? && (version == :all || glib_app_version_gte(version, app_version)) && (except.nil? || except.to_s != app_version)
10
+ end
11
+
12
+ def glib_app_version_gte(feature_version, app_version)
13
+ app_version.blank? || Gem::Version.new(app_version) >= Gem::Version.new(feature_version)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ module Glib
2
+ module DynamicImagesHelper
3
+ # NOTE: The bucket should probably be set as a parameter for json_libs
4
+ def dynamic_image_url(blob_key, width: 100, height: 100, fit: 'clip', bucket: Rails.application.config.try(:aws_s3_bucket))
5
+ return unless blob_key.present?
6
+
7
+ full_params_hash = {
8
+ bucket_name: bucket,
9
+ blob_key: blob_key,
10
+ w: width,
11
+ h: height,
12
+ fit: fit,
13
+ expires: 1.hours.from_now.to_i
14
+ }
15
+ request_params_hash = full_params_hash.except(:bucket_name, :blob_key)
16
+
17
+ if (private_key = ENV['PRIVATE_API_KEY'])
18
+ encryption_service = EncryptionService.new(bucket, private_key)
19
+ signature = encryption_service.encrypt(full_params_hash)
20
+ request_params_hash = request_params_hash.merge(signature: signature)
21
+ end
22
+
23
+ # TODO: This should probably be set as a parameter for json_libs
24
+ uri = URI::HTTPS.build(
25
+ host: 'imageserver-demo.herokuapp.com',
26
+ path: "/image/#{bucket}/#{blob_key}",
27
+ query: request_params_hash.to_param
28
+ )
29
+
30
+ uri.to_s
31
+ end
32
+
33
+ EncryptionService = Struct.new(:salt, :private_key) do
34
+ def encrypt(value)
35
+ encryptor.encrypt_and_sign(value)
36
+ end
37
+
38
+ def decrypt(value)
39
+ encryptor.decrypt_and_verify(value)
40
+ end
41
+
42
+ private
43
+ def encryptor
44
+ @encryptor ||= begin
45
+ key_len = ActiveSupport::MessageEncryptor.key_len
46
+ key = ActiveSupport::KeyGenerator.new(private_key).generate_key(salt, key_len)
47
+ ActiveSupport::MessageEncryptor.new(key)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ module Glib
2
+ module DynamicTextsHelper
3
+ def dt(key, default_value = '', **args)
4
+ new_key = key
5
+ options = {
6
+ scope: args.fetch(:scope, 'itinerarybuilder'),
7
+ lang: args.fetch(:lang, 'en')
8
+ }
9
+
10
+ if key.starts_with?('.')
11
+ new_key = "#{controller_name}.#{action_name}#{key}"
12
+ end
13
+
14
+ content, text_object = Glib::Text.get_content(new_key, default_value, options: options)
15
+ content = content.gsub(/\{\{(\w+)\}\}/) { args.fetch($1.to_sym, "{{#{$1}}}") }
16
+
17
+ if text_object.images.attached?
18
+ content = content.gsub(/\{\{image(\d)\}\}/) {
19
+ if image = text_object.images[$1.to_i - 1]
20
+ dynamic_image_url(image.blob.key)
21
+ else
22
+ "{{image#{$1}}}"
23
+ end
24
+ }
25
+ end
26
+
27
+ content
28
+ end
29
+
30
+ def dt_json(key, default_value = '', **args)
31
+ new_key = key
32
+
33
+ if key.starts_with?('.')
34
+ new_key = "#{controller_name}.#{action_name}#{key}"
35
+ end
36
+
37
+ {
38
+ dt_key: new_key
39
+ }.merge(args)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ module Glib
2
+ module FormsHelper
3
+ def glib_form_field_label(model_name, prop, args = {})
4
+ I18n.t("dt_models.#{model_name}.#{prop}.label", args.merge(default: nil)) || I18n.t("activerecord.attributes.#{model_name}.#{prop}", args)
5
+ end
6
+
7
+ def glib_form_hint_label(model_name, prop, args = {})
8
+ I18n.t("dt_models.#{model_name}.#{prop}.hint", args.merge(default: nil))
9
+ end
10
+
11
+ def glib_form_placeholder_label(model_name, prop, args = {})
12
+ I18n.t("dt_models.#{model_name}.#{prop}.placeholder", args.merge(default: nil))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,281 @@
1
+ module Glib
2
+ module JsonUi
3
+ class AbstractBuilder
4
+ attr_reader :json, :page
5
+
6
+ def initialize(json, page)
7
+ @json = json
8
+ @page = page
9
+ end
10
+
11
+ private
12
+ def capitalize_first_letter_and_leave_others_alone(str)
13
+ str.slice(0,1).capitalize + str.slice(1..-1)
14
+ end
15
+
16
+ def add_singleton_element type, name, *args
17
+ name_components = name.to_s.split('_')
18
+ json.set! type, "#{name_components.join('/')}" if type
19
+ args = [yield] if block_given?
20
+
21
+ # TODO: consider caching these instances
22
+ "#{self.class.name}::#{name_components.map{ |str| capitalize_first_letter_and_leave_others_alone(str) }.join('::')}".constantize.new(json, @page).props(*args)
23
+ end
24
+
25
+ def add_element_to_array type, name, *args
26
+ json.child! do
27
+ add_singleton_element type, name, *args
28
+ end
29
+ end
30
+
31
+ # TODO: Consider removing this in favour of non 'v1' suffix
32
+ def add_singleton_element_v1 type, name, *args
33
+ name_components = name.to_s.split('_')
34
+ json.set! type, "#{name_components.join('/')}-v1" if type
35
+ args = [yield] if block_given?
36
+ "#{self.class.name}::#{name_components.map{ |str| capitalize_first_letter_and_leave_others_alone(str) }.join('::')}".constantize.new(json, @page).props(*args)
37
+ end
38
+
39
+ # TODO: Consider removing this in favour of non 'v1' suffix
40
+ def add_element_to_array_v1 type, name, *args
41
+ json.child! do
42
+ add_singleton_element_v1 type, name, *args
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class JsonUiElement
49
+ attr_reader :json, :page
50
+
51
+ def initialize(json, page)
52
+ @json = json
53
+ @page = page
54
+ end
55
+
56
+ def props(args = {})
57
+ @args = args
58
+
59
+ if args.is_a? Hash
60
+ args.each do |k, v|
61
+ send(k, v)
62
+ end
63
+ else
64
+ raise "Invalid properties: #{args}"
65
+ end
66
+
67
+ created
68
+ end
69
+
70
+ private
71
+
72
+ def self.date(propName)
73
+ define_method(propName) do |value|
74
+ json.set! propName, value&.to_date
75
+
76
+ # if (value = value&.to_s)
77
+ # if !Rails.env.production?
78
+ # raise "Invalid date for #{propName}: #{value}" unless value =~ /^\d{4}-\d{2}-\d{2}$/
79
+ # end
80
+
81
+ # json.set! propName, value
82
+ # end
83
+ end
84
+ end
85
+
86
+ def self.date_time(propName)
87
+ define_method(propName) do |value|
88
+ json.set! propName, value&.to_datetime
89
+
90
+ # if (value = value&.to_s)
91
+ # if !Rails.env.production?
92
+ # try Date.parse(value) rescue raise "Invalid date: #{value}"
93
+ # end
94
+
95
+ # json.set! propName, value
96
+ # end
97
+ end
98
+ end
99
+
100
+ def self.color propName
101
+ define_method(propName) do |value|
102
+ if (value = value&.to_s)
103
+ if !Rails.env.production?
104
+ if !value.match /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i
105
+ raise "Invalid color: #{value}"
106
+ end
107
+ end
108
+
109
+ json.set! propName, value
110
+ end
111
+ end
112
+ end
113
+
114
+ def self.length propName
115
+ define_method(propName) do |value|
116
+ if value
117
+ if !Rails.env.production?
118
+ case value
119
+ when 'matchParent'
120
+ when 'wrapContent'
121
+ else
122
+ if !value.is_a? Integer
123
+ raise "Invalid length: #{value}"
124
+ end
125
+ end
126
+ end
127
+
128
+ json.set! propName, value
129
+ end
130
+ end
131
+ end
132
+
133
+ def self.string propName, options = {}
134
+ define_method(propName) do |value|
135
+ str = value&.to_s
136
+ instance_variable_set("@#{propName}", str) if options[:cache]
137
+ json.set! propName, str
138
+ end
139
+ end
140
+
141
+ # Support either string or dynamic_text hash
142
+ def self.text propName, options = {}
143
+ define_method(propName) do |value|
144
+ if value.is_a?(Hash)
145
+ json.set! propName, value
146
+ else
147
+ json.set! propName, value&.to_s
148
+ end
149
+ end
150
+ end
151
+
152
+ def self.int propName
153
+ define_method(propName) do |value|
154
+ json.set! propName, value&.to_i
155
+ end
156
+ end
157
+
158
+ def self.float propName
159
+ define_method(propName) do |value|
160
+ json.set! propName, value&.to_f
161
+ end
162
+ end
163
+
164
+ def self.bool propName, options = {}
165
+ define_method(propName) do |value|
166
+ bool = value == true
167
+ instance_variable_set("@#{propName}", bool) if options[:cache]
168
+ json.set! propName, bool
169
+ end
170
+ end
171
+
172
+ def self.icon propName, options = {}
173
+ define_method(propName) do |value|
174
+
175
+ if value.is_a?(Hash)
176
+ data = value
177
+ name = data[:name]
178
+ else
179
+ data = {}
180
+ name = value
181
+ # data = { material: { name: value } }
182
+
183
+ # name = value
184
+
185
+ # TODO: deprecated
186
+ # json.set!(propName) do
187
+ # json.name name
188
+ # end
189
+ end
190
+
191
+ json.set!(propName) do
192
+ if name
193
+ data[:material] ||= {}
194
+ data[:material][:name] = name
195
+ end
196
+
197
+ if (material = data[:material])
198
+ json.material do
199
+ json.name material[:name]
200
+ json.size material[:size] if material[:size]
201
+ end
202
+ end
203
+
204
+ if (fa = data[:fa])
205
+ json.fa do
206
+ json.name fa[:name]
207
+ json.size fa[:size] if fa[:size]
208
+ end
209
+ end
210
+
211
+ if (custom = data[:custom])
212
+ json.custom do
213
+ json.name custom[:name]
214
+ json.size custom[:size] if custom[:size]
215
+ end
216
+ end
217
+
218
+ if (badge = data[:badge])
219
+ if badge.is_a?(Hash)
220
+ json.badge badge
221
+ else
222
+ json.badge do
223
+ json.text badge
224
+ end
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+ # raise "Invalid properties: #{options.keys}" if options.size > 0
231
+
232
+ # instance_variable_set("@#{propName}", name) if options[:cache]
233
+
234
+ end
235
+ end
236
+
237
+ def self.action propName
238
+ define_method(propName) do |value|
239
+ json.set!(propName) { value.call(page.action_builder) }
240
+ end
241
+ end
242
+
243
+ def self.views propName
244
+ define_method(propName) do |value|
245
+ json.set!(propName) { value.call(page.view_builder) }
246
+ end
247
+ end
248
+
249
+ def self.array propName
250
+ define_method(propName) do |value|
251
+ json.set! propName, value&.to_a
252
+ end
253
+ end
254
+
255
+ def self.hash propName
256
+ define_method(propName) do |value|
257
+ json.set! propName, value
258
+ end
259
+ end
260
+
261
+ def self.singleton_array singletonName, arrayName
262
+ array arrayName
263
+
264
+ define_method(singletonName) do |value|
265
+ json.set! arrayName, [value]
266
+ end
267
+ end
268
+
269
+ def self.panels_builder(propName, *panelNames)
270
+ define_method(propName) do |value|
271
+ value.call(page.content_builder(panelNames))
272
+ end
273
+ end
274
+
275
+ def created
276
+ # To be overridden
277
+ end
278
+
279
+ end
280
+ end
281
+ end