glib-web 0.5.18 → 0.5.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/concerns/glib/analytics/funnel.rb +61 -0
- data/app/controllers/concerns/glib/json/libs.rb +4 -4
- data/app/controllers/concerns/glib/json/traversal.rb +1 -0
- data/app/controllers/concerns/glib/json/ui.rb +16 -8
- data/app/controllers/glib/home_controller.rb +0 -0
- data/app/helpers/glib/json_ui/action_builder.rb +6 -1
- data/app/helpers/glib/json_ui/analytics_helper.rb +17 -0
- data/app/helpers/glib/json_ui/menu_builder.rb +48 -6
- data/app/helpers/glib/json_ui/page_helper.rb +7 -0
- data/app/helpers/glib/json_ui/response_helper.rb +2 -0
- data/app/helpers/glib/json_ui/view_builder.rb +36 -19
- data/app/helpers/glib/json_ui/view_builder/fields.rb +1 -0
- data/app/helpers/glib/json_ui/view_builder/panels.rb +1 -1
- data/app/views/json_ui/garage/forms/basic.json.jbuilder +2 -14
- data/app/views/json_ui/garage/forms/pickers.json.jbuilder +0 -0
- data/app/views/json_ui/garage/home/index.json.jbuilder +0 -0
- data/app/views/json_ui/garage/lists/chat_ui.json.jbuilder +23 -7
- data/app/views/json_ui/garage/notifications/android_post.json.jbuilder +38 -46
- data/app/views/json_ui/garage/notifications/index.json.jbuilder +9 -6
- data/app/views/json_ui/garage/pages/nav_buttons.json.jbuilder +4 -16
- data/app/views/json_ui/garage/tables/index.json.jbuilder +19 -20
- data/app/views/json_ui/garage/tables/layout.json.jbuilder +26 -28
- data/app/views/json_ui/garage/views/banners.json.jbuilder +15 -3
- metadata +3 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 96a374e3289f6a60740734297d8916d0d812d179522a6f8bb90b081fc250384b
         | 
| 4 | 
            +
              data.tar.gz: 5a7cd0858950a75f72c4a0746345c11f4229a3caede190fe0b038385307fceee
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 93a2a7ec3a7200cffe50217bbf485ef5544dcd8ea5df41c72edd6dacc6d245cd375150cf22c8da2c0ce0d1cd33ba02985cad24c32b9ff97fbf62f030635e4c10
         | 
| 7 | 
            +
              data.tar.gz: 864b0889dfb2213830fc8e4fab34baecb3a9d896406f2bb12c3d8adf31e315fb06ff5edcdca03f90bafada56f57dd81ed0dc5220a57d3de8b6b3cecb520a073a
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            module Glib::Analytics
         | 
| 2 | 
            +
              module Funnel
         | 
| 3 | 
            +
                extend ActiveSupport::Concern
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                included do
         | 
| 6 | 
            +
                  # include Overrides
         | 
| 7 | 
            +
                  extend ClassMethods
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  helper_method :glib_analytics_feature_segment
         | 
| 10 | 
            +
                  helper_method :glib_analytics_feature_referer
         | 
| 11 | 
            +
                  helper_method :glib_analytics_included?
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # module Overrides
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                #   # Expose protected method
         | 
| 17 | 
            +
                #   public  # Override
         | 
| 18 | 
            +
                #   def policy_scope(*args)
         | 
| 19 | 
            +
                #     super
         | 
| 20 | 
            +
                #   end
         | 
| 21 | 
            +
                # end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def glib_analytics_included?
         | 
| 24 | 
            +
                  true
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def glib_analytics_feature_segment
         | 
| 28 | 
            +
                  # To be overridden
         | 
| 29 | 
            +
                  nil
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def glib_analytics_feature_referer
         | 
| 33 | 
            +
                  group = request.headers['GApp-Analytics-Referer-Group']
         | 
| 34 | 
            +
                  action = request.headers['GApp-Analytics-Referer-Action']
         | 
| 35 | 
            +
                  segment = request.headers['GApp-Analytics-Referer-Segment']
         | 
| 36 | 
            +
                  placement = request.headers['GApp-Analytics-Referer-Placement']
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  if group && action
         | 
| 39 | 
            +
                    return { group: group, action: action, segment: segment, placement: placement }
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  nil
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
             | 
| 46 | 
            +
             | 
| 47 | 
            +
                module ClassMethods
         | 
| 48 | 
            +
                  # def glib_auth_inited?
         | 
| 49 | 
            +
                  #   @@__glib_auth_init ||= false
         | 
| 50 | 
            +
                  # end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # def glib_analytics_init
         | 
| 53 | 
            +
                  #   @@__glib_analytics_init = true
         | 
| 54 | 
            +
                  # end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # def glib_analytics_inited?
         | 
| 57 | 
            +
                  #   @@__glib_analytics_init ||= false
         | 
| 58 | 
            +
                  # end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -9,17 +9,17 @@ module Glib::Json::Libs | |
| 9 9 | 
             
              end
         | 
| 10 10 |  | 
| 11 11 | 
             
              def json_ui_app_bundle_id
         | 
| 12 | 
            -
                @json_ui_app_bundle_id ||= request.headers['JsonUiApp-Bundle-Id']
         | 
| 12 | 
            +
                @json_ui_app_bundle_id ||= request.headers['GApp-Bundle-Id'] || request.headers['JsonUiApp-Bundle-Id']
         | 
| 13 13 | 
             
              end
         | 
| 14 14 |  | 
| 15 15 | 
             
              def json_ui_app_build_version
         | 
| 16 | 
            -
                @json_ui_app_build_version ||= request.headers['JsonUiApp-Build-Version']
         | 
| 16 | 
            +
                @json_ui_app_build_version ||= request.headers['GApp-Build-Version'] || request.headers['JsonUiApp-Build-Version']
         | 
| 17 17 | 
             
                @json_ui_app_build_version = params[:build_version] if @json_ui_app_build_version.nil? && Rails.env.development?  # For easy testing
         | 
| 18 18 | 
             
                @json_ui_app_build_version
         | 
| 19 19 | 
             
              end
         | 
| 20 20 |  | 
| 21 21 | 
             
              def json_ui_app_device_os
         | 
| 22 | 
            -
                @json_ui_app_device_os ||= request.headers['JsonUiApp-Device-Os']
         | 
| 22 | 
            +
                @json_ui_app_device_os ||= request.headers['GApp-Device-Os'] || request.headers['JsonUiApp-Device-Os']
         | 
| 23 23 | 
             
                @json_ui_app_device_os = params[:device_os] if @json_ui_app_device_os.nil? && Rails.env.development?  # For easy testing
         | 
| 24 24 | 
             
                @json_ui_app_device_os || 'web'
         | 
| 25 25 | 
             
              end
         | 
| @@ -59,7 +59,7 @@ module Glib::Json::Libs | |
| 59 59 | 
             
                #   raise exception
         | 
| 60 60 | 
             
                # end
         | 
| 61 61 | 
             
              end
         | 
| 62 | 
            -
             | 
| 62 | 
            +
             | 
| 63 63 | 
             
              def glib_json_handle_500(exception)
         | 
| 64 64 | 
             
                if json_ui_activated? && Rails.env.production?
         | 
| 65 65 | 
             
                  Rollbar.error(exception) if defined?(Rollbar)
         | 
| @@ -38,6 +38,17 @@ module Glib::Json::Ui | |
| 38 38 | 
             
                end
         | 
| 39 39 | 
             
              end
         | 
| 40 40 |  | 
| 41 | 
            +
              def json_ui_response_lifecycle_prop
         | 
| 42 | 
            +
                prop_name = 'onResponse'
         | 
| 43 | 
            +
                if (hash = json_transformation_start).is_a?(Hash) && hash[prop_name]  # A valid response
         | 
| 44 | 
            +
                  if hash[prop_name]['action'] != 'runMultiple'
         | 
| 45 | 
            +
                    childActions = [hash[prop_name]]
         | 
| 46 | 
            +
                    hash[prop_name] = { 'action' => 'runMultiple', 'childActions' => childActions }
         | 
| 47 | 
            +
                    childActions
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 41 52 | 
             
              def __json_ui_start(options)
         | 
| 42 53 | 
             
                @__json_ui_activated = false
         | 
| 43 54 | 
             
                @__json_ui_rendering = nil
         | 
| @@ -66,12 +77,9 @@ module Glib::Json::Ui | |
| 66 77 | 
             
              end
         | 
| 67 78 |  | 
| 68 79 | 
             
              private
         | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
                 | 
| 74 | 
            -
             | 
| 75 | 
            -
                # response.body = render_to_string(template: renderer_path, layout: false, content_type: 'text/html', locals: { page: hash, options: options })
         | 
| 76 | 
            -
              end
         | 
| 80 | 
            +
                def __json_ui_vue(hash, options)
         | 
| 81 | 
            +
                  renderer_path = options[:renderer_path]
         | 
| 82 | 
            +
                  @__json_ui_orig_page = response.body
         | 
| 83 | 
            +
                  response.body = render_to_string(template: renderer_path, layout: 'json_ui/renderer', content_type: 'text/html', locals: { page: hash, options: options })
         | 
| 84 | 
            +
                end
         | 
| 77 85 | 
             
            end
         | 
| 
            File without changes
         | 
| @@ -16,6 +16,7 @@ module Glib | |
| 16 16 | 
             
                  end
         | 
| 17 17 |  | 
| 18 18 | 
             
                  class Action < JsonUiElement
         | 
| 19 | 
            +
                    # TODO: Deprecated
         | 
| 19 20 | 
             
                    def analytics(value)
         | 
| 20 21 | 
             
                      if value.is_a?(String)
         | 
| 21 22 | 
             
                        @analyticsEnabled = true
         | 
| @@ -27,6 +28,7 @@ module Glib | |
| 27 28 |  | 
| 28 29 | 
             
                    # Override
         | 
| 29 30 | 
             
                    def created
         | 
| 31 | 
            +
                      # TODO: Deprecated
         | 
| 30 32 | 
             
                      if @analyticsEnabled
         | 
| 31 33 | 
             
                        suffix = @analyticsId
         | 
| 32 34 | 
             
                        # TODO: recognize_path() needs `method` as a second argument for POST request
         | 
| @@ -73,8 +75,11 @@ module Glib | |
| 73 75 |  | 
| 74 76 | 
             
                  module Devices
         | 
| 75 77 | 
             
                    class GetPushToken < Action
         | 
| 76 | 
            -
                       | 
| 78 | 
            +
                      string :postUrl
         | 
| 77 79 | 
             
                      string :paramNameForToken
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      # Use postUrl instead
         | 
| 82 | 
            +
                      # action :onGet
         | 
| 78 83 | 
             
                    end
         | 
| 79 84 | 
             
                  end
         | 
| 80 85 |  | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Glib
         | 
| 2 | 
            +
              module JsonUi
         | 
| 3 | 
            +
                module AnalyticsHelper
         | 
| 4 | 
            +
                  def __json_ui_analytics(json)
         | 
| 5 | 
            +
                    if try(:glib_analytics_included?)
         | 
| 6 | 
            +
                      json.analytics do
         | 
| 7 | 
            +
                        json.feature do
         | 
| 8 | 
            +
                          json.group controller_name
         | 
| 9 | 
            +
                          json.action action_name
         | 
| 10 | 
            +
                          json.segment glib_analytics_feature_segment
         | 
| 11 | 
            +
                        end
         | 
| 12 | 
            +
                      end
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -6,6 +6,36 @@ module Glib | |
| 6 6 | 
             
                  end
         | 
| 7 7 |  | 
| 8 8 | 
             
                  class MenuItem < JsonUiElement
         | 
| 9 | 
            +
                    hash :analytics
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    # def initialize(json, page)
         | 
| 12 | 
            +
                    #   super(json, page)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    #   # Can be improved by making it configurable site-wide.
         | 
| 15 | 
            +
                    #   @analyticsEnabled = true
         | 
| 16 | 
            +
                    # end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    # def analytics(value)
         | 
| 19 | 
            +
                    #   if value.is_a?(Hash)
         | 
| 20 | 
            +
                    #     @analyticsEnabled = true
         | 
| 21 | 
            +
                    #     @analyticsId = value
         | 
| 22 | 
            +
                    #   else
         | 
| 23 | 
            +
                    #     @analyticsEnabled = value == true
         | 
| 24 | 
            +
                    #   end
         | 
| 25 | 
            +
                    # end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    # # Override
         | 
| 28 | 
            +
                    # def created
         | 
| 29 | 
            +
                    #   json.analytics do
         | 
| 30 | 
            +
                    #     if @analyticsEnabled
         | 
| 31 | 
            +
                    #       if @analyticsId
         | 
| 32 | 
            +
                    #         json.placement @analyticsId
         | 
| 33 | 
            +
                    #       end
         | 
| 34 | 
            +
                    #     else
         | 
| 35 | 
            +
                    #       json.disabled true
         | 
| 36 | 
            +
                    #     end
         | 
| 37 | 
            +
                    #   end
         | 
| 38 | 
            +
                    # end
         | 
| 9 39 | 
             
                  end
         | 
| 10 40 |  | 
| 11 41 | 
             
                  class Button < MenuItem
         | 
| @@ -15,13 +45,16 @@ module Glib | |
| 15 45 | 
             
                    bool :disabled
         | 
| 16 46 | 
             
                    singleton_array :styleClass, :styleClasses
         | 
| 17 47 |  | 
| 18 | 
            -
                    def  | 
| 19 | 
            -
                      json. | 
| 48 | 
            +
                    def childButtons(block)
         | 
| 49 | 
            +
                      json.childButtons do
         | 
| 20 50 | 
             
                        block.call page.menu_builder
         | 
| 21 51 | 
             
                      end
         | 
| 22 52 | 
             
                    end
         | 
| 23 53 |  | 
| 54 | 
            +
                    # Override
         | 
| 24 55 | 
             
                    def created
         | 
| 56 | 
            +
                      super
         | 
| 57 | 
            +
             | 
| 25 58 | 
             
                      json.type 'button'
         | 
| 26 59 | 
             
                    end
         | 
| 27 60 | 
             
                  end
         | 
| @@ -29,6 +62,8 @@ module Glib | |
| 29 62 | 
             
                  class Divider < MenuItem
         | 
| 30 63 | 
             
                    # Override
         | 
| 31 64 | 
             
                    def created
         | 
| 65 | 
            +
                      super
         | 
| 66 | 
            +
             | 
| 32 67 | 
             
                      json.type 'divider'
         | 
| 33 68 | 
             
                    end
         | 
| 34 69 | 
             
                  end
         | 
| @@ -39,14 +74,21 @@ module Glib | |
| 39 74 |  | 
| 40 75 | 
             
                    # Override
         | 
| 41 76 | 
             
                    def created
         | 
| 77 | 
            +
                      super
         | 
| 78 | 
            +
             | 
| 42 79 | 
             
                      json.type 'label'
         | 
| 43 80 | 
             
                    end
         | 
| 44 81 | 
             
                  end
         | 
| 45 82 |  | 
| 46 | 
            -
                  class MenuLeftBottom < Button
         | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
                  end
         | 
| 83 | 
            +
                  # class MenuLeftBottom < Button
         | 
| 84 | 
            +
                  #   icon :icon
         | 
| 85 | 
            +
                  #   array :buttons
         | 
| 86 | 
            +
                  # end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  # class Select < Button
         | 
| 89 | 
            +
                  #   icon :icon
         | 
| 90 | 
            +
                  #   array :buttons
         | 
| 91 | 
            +
                  # end
         | 
| 50 92 | 
             
                end
         | 
| 51 93 | 
             
              end
         | 
| 52 94 | 
             
            end
         | 
| @@ -10,9 +10,16 @@ module Glib | |
| 10 10 | 
             
                    )
         | 
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 | 
            +
                  def json_ui_garage_current_url(options = {})
         | 
| 14 | 
            +
                    json_ui_garage_url(options.merge(path: params[:path]))
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 13 17 | 
             
                  # TODO: Remove the block
         | 
| 14 18 | 
             
                  def json_ui_page(json, &block)
         | 
| 15 19 | 
             
                    @__json_ui_page ||= Page.new(json, self)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    __json_ui_analytics(json)
         | 
| 22 | 
            +
             | 
| 16 23 | 
             
                    block&.call @__json_ui_page
         | 
| 17 24 | 
             
                    @__json_ui_page
         | 
| 18 25 | 
             
                  end
         | 
| @@ -28,29 +28,46 @@ module Glib | |
| 28 28 | 
             
                    length :height
         | 
| 29 29 | 
             
                    color :backgroundColor
         | 
| 30 30 | 
             
                    hash :padding
         | 
| 31 | 
            -
                    # array :styleClasses
         | 
| 32 31 | 
             
                    singleton_array :styleClass, :styleClasses
         | 
| 33 32 | 
             
                    hash :showIf
         | 
| 33 | 
            +
                    hash :analytics
         | 
| 34 34 |  | 
| 35 | 
            -
                    def  | 
| 36 | 
            -
             | 
| 37 | 
            -
                        @analyticsEnabled = true
         | 
| 38 | 
            -
                        @analyticsId = value
         | 
| 39 | 
            -
                      else
         | 
| 40 | 
            -
                        @analyticsEnabled = value == true
         | 
| 41 | 
            -
                      end
         | 
| 42 | 
            -
                    end
         | 
| 35 | 
            +
                    # def initialize(json, page)
         | 
| 36 | 
            +
                    #   super(json, page)
         | 
| 43 37 |  | 
| 44 | 
            -
                    #  | 
| 45 | 
            -
                     | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                     | 
| 38 | 
            +
                    #   # Can be improved by making it configurable site-wide.
         | 
| 39 | 
            +
                    #   @analyticsEnabled = true
         | 
| 40 | 
            +
                    # end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    # def analytics(value)
         | 
| 43 | 
            +
                    #   if value.is_a?(String)
         | 
| 44 | 
            +
                    #     @analyticsEnabled = true
         | 
| 45 | 
            +
                    #     @analyticsId = value
         | 
| 46 | 
            +
                    #   else
         | 
| 47 | 
            +
                    #     @analyticsEnabled = value == true
         | 
| 48 | 
            +
                    #   end
         | 
| 49 | 
            +
                    # end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    # # Override
         | 
| 52 | 
            +
                    # def created
         | 
| 53 | 
            +
                    #   # if @analyticsEnabled
         | 
| 54 | 
            +
                    #   #   context = page.context
         | 
| 55 | 
            +
                    #   #   json.analytics do
         | 
| 56 | 
            +
                    #   #     content = @analyticsId || (@text || @icon)&.gsub(/\s+/, '_')&.downcase || 'nil'
         | 
| 57 | 
            +
                    #   #     json.namePrefix [context.controller_name, context.action_name, content].join('_')
         | 
| 58 | 
            +
                    #   #   end
         | 
| 59 | 
            +
                    #   # end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    #   json.analytics do
         | 
| 62 | 
            +
                    #     if @analyticsEnabled
         | 
| 63 | 
            +
                    #       if @analyticsId
         | 
| 64 | 
            +
                    #         json.placement @analyticsId
         | 
| 65 | 
            +
                    #       end
         | 
| 66 | 
            +
                    #     else
         | 
| 67 | 
            +
                    #       json.disabled true
         | 
| 68 | 
            +
                    #     end
         | 
| 69 | 
            +
                    #   end
         | 
| 70 | 
            +
                    # end
         | 
| 54 71 |  | 
| 55 72 | 
             
                  end
         | 
| 56 73 |  | 
| @@ -89,7 +89,6 @@ class Glib::JsonUi::ViewBuilder | |
| 89 89 | 
             
                      @childViewsBlock.call(page.view_builder)
         | 
| 90 90 | 
             
                      page.current_form = nil
         | 
| 91 91 | 
             
                    end
         | 
| 92 | 
            -
             | 
| 93 92 | 
             
                  end
         | 
| 94 93 |  | 
| 95 94 | 
             
                  def childViews(block)
         | 
| @@ -98,6 +97,7 @@ class Glib::JsonUi::ViewBuilder | |
| 98 97 | 
             
                end
         | 
| 99 98 |  | 
| 100 99 | 
             
                class List < View
         | 
| 100 | 
            +
                  hash :ws
         | 
| 101 101 | 
             
                  hash :nextPage
         | 
| 102 102 | 
             
                  action :onScrollToTop
         | 
| 103 103 | 
             
                  action :onScrollToBottom
         | 
| @@ -7,14 +7,6 @@ json_ui_page json do |page| | |
| 7 7 | 
             
                form.fields_text name: 'user[name]', width: 'matchParent', label: 'Name'
         | 
| 8 8 | 
             
                form.fields_password name: 'user[password]', width: 'matchParent', label: 'Password'
         | 
| 9 9 |  | 
| 10 | 
            -
                # form.panels_split width: 'matchParent', leftViews: ->(split) do
         | 
| 11 | 
            -
                #   if params[:mode] == 'dialog'
         | 
| 12 | 
            -
                #     split.button styleClass: 'link', text: 'cancel', onClick: ->(action) { action.dialogs_close }
         | 
| 13 | 
            -
                #   end
         | 
| 14 | 
            -
                # end, rightViews: ->(split) do
         | 
| 15 | 
            -
                #   split.button text: 'Submit', onClick: ->(action) { action.forms_submit }
         | 
| 16 | 
            -
                # end
         | 
| 17 | 
            -
             | 
| 18 10 | 
             
                form.panels_split width: 'matchParent', content: ->(split) do
         | 
| 19 11 | 
             
                  split.left childViews: ->(left) do
         | 
| 20 12 | 
             
                    if params[:mode] == 'dialog'
         | 
| @@ -22,13 +14,9 @@ json_ui_page json do |page| | |
| 22 14 | 
             
                    end
         | 
| 23 15 | 
             
                  end
         | 
| 24 16 | 
             
                  split.right childViews: ->(right) do
         | 
| 25 | 
            -
                    right.button text: 'Submit', onClick: ->(action) { action.forms_submit }
         | 
| 17 | 
            +
                    # right.button text: 'Submit', onClick: ->(action) { action.forms_submit }
         | 
| 18 | 
            +
                    right.fields_submit text: 'Submit'
         | 
| 26 19 | 
             
                  end
         | 
| 27 20 | 
             
                end
         | 
| 28 | 
            -
             | 
| 29 21 | 
             
              end
         | 
| 30 | 
            -
              # , paramNameForFormData: 'formData', onSubmit: ->(action) do
         | 
| 31 | 
            -
              #   action.http_post url: json_ui_garage_url(path: 'forms/generic_post')
         | 
| 32 | 
            -
              # end
         | 
| 33 | 
            -
             | 
| 34 22 | 
             
            end
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| @@ -5,7 +5,26 @@ liked = params[:liked] == 'true' | |
| 5 5 | 
             
            page = json_ui_page json
         | 
| 6 6 | 
             
            render "#{@path_prefix}/nav_menu", json: json, page: page
         | 
| 7 7 |  | 
| 8 | 
            -
             | 
| 8 | 
            +
            json.ws({
         | 
| 9 | 
            +
              "socket" => {
         | 
| 10 | 
            +
                "endpoint" => "/socket/websocket",
         | 
| 11 | 
            +
                "params" => {
         | 
| 12 | 
            +
                  vsn: '2.0.0',
         | 
| 13 | 
            +
                  token: 'TOKEN'
         | 
| 14 | 
            +
                }
         | 
| 15 | 
            +
              },
         | 
| 16 | 
            +
              "topic" => "rooms",
         | 
| 17 | 
            +
              "events" => [],
         | 
| 18 | 
            +
              # "events" => ["new_link_added"],
         | 
| 19 | 
            +
              # "header" => {
         | 
| 20 | 
            +
              #   "user_id" => 2,
         | 
| 21 | 
            +
              #   "prev_item_id" => nil,
         | 
| 22 | 
            +
              #   "last_item_id" => nil
         | 
| 23 | 
            +
              # }
         | 
| 24 | 
            +
            })
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            list_ws = { topic: 'rooms', events: ['comments_updated'] }
         | 
| 27 | 
            +
            page.list ws: list_ws, firstSection: ->(section) do
         | 
| 9 28 | 
             
              section.header padding: { top: 12, left: 16, right: 16, bottom: 12 }, childViews: ->(header) do
         | 
| 10 29 | 
             
                header.h3 text: 'Chat with John Doe'
         | 
| 11 30 | 
             
              end
         | 
| @@ -73,18 +92,15 @@ page.footer padding: { top: 12, left: 16, right: 16, bottom: 12 }, childViews: - | |
| 73 92 |  | 
| 74 93 | 
             
              footer.panels_form width: 'matchParent', url: json_ui_garage_url(path: 'forms/basic_post'), method: 'post', padding: glib_json_padding_body, paramNameForFormData: 'formData', onSubmit: ->(action) do
         | 
| 75 94 | 
             
                json.action "ws/push"
         | 
| 76 | 
            -
                json.topic " | 
| 77 | 
            -
                json.event " | 
| 95 | 
            +
                json.topic "rooms"
         | 
| 96 | 
            +
                json.event "create_comment"
         | 
| 78 97 | 
             
                json.payload({
         | 
| 79 | 
            -
                  "club_id": "2",
         | 
| 80 98 | 
             
                  "room_id": "30",
         | 
| 81 99 | 
             
                  "user_id": "2"
         | 
| 82 | 
            -
                    #  title: "TITLE",
         | 
| 83 | 
            -
                    #  url: "URL"
         | 
| 84 100 | 
             
                })
         | 
| 85 101 |  | 
| 86 102 | 
             
              end, childViews: ->(form) do
         | 
| 87 | 
            -
                form.fields_text name: 'message | 
| 103 | 
            +
                form.fields_text name: 'user[message]', width: 'matchParent', label: 'Message'
         | 
| 88 104 |  | 
| 89 105 | 
             
                form.panels_split width: 'matchParent', content: ->(split) do
         | 
| 90 106 | 
             
                  split.right childViews: ->(right) do
         | 
| @@ -1,54 +1,46 @@ | |
| 1 | 
            -
            if (server_key = ENV['GCM_SERVER_KEY'])
         | 
| 2 | 
            -
              fcm = FCM.new(server_key)
         | 
| 3 1 |  | 
| 4 | 
            -
             | 
| 5 | 
            -
               | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
            #  | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
            #  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
                notification: {
         | 
| 33 | 
            -
                  title: params[:title],
         | 
| 34 | 
            -
                  body: "Message from server"
         | 
| 35 | 
            -
                },
         | 
| 36 | 
            -
                data: {
         | 
| 37 | 
            -
                  channelId: 'default',
         | 
| 38 | 
            -
                  notificationId: 1,
         | 
| 39 | 
            -
                  onClick: payload
         | 
| 2 | 
            +
            if (server_key = ENV['FCM_SERVER_KEY'])
         | 
| 3 | 
            +
              if defined? FCM
         | 
| 4 | 
            +
                # Introduce delay so that we have time to place the app to background
         | 
| 5 | 
            +
                # before receiving push notification.
         | 
| 6 | 
            +
                sleep 2
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                fcm = FCM.new(server_key)
         | 
| 9 | 
            +
                registration_ids = [params[:token]] # An array of one or more client registration tokens
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                payload = json_ui_action_payload do |action|
         | 
| 12 | 
            +
                  action.dialogs_alert message: 'Opened'
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # See https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages for all available options.
         | 
| 16 | 
            +
                options = {
         | 
| 17 | 
            +
                  notification: {
         | 
| 18 | 
            +
                    title: params[:title],
         | 
| 19 | 
            +
                    body: 'Message from server'
         | 
| 20 | 
            +
                    # title: 'Offer from Ajisen Ramen',
         | 
| 21 | 
            +
                    # body: 'Free side order of Gyoza'
         | 
| 22 | 
            +
                  },
         | 
| 23 | 
            +
                  data: {
         | 
| 24 | 
            +
                    channelId: 'default',
         | 
| 25 | 
            +
                    notificationId: 1,
         | 
| 26 | 
            +
                    openUrl: json_ui_garage_url(path: 'home/blank'),
         | 
| 27 | 
            +
                    onOpen: payload
         | 
| 28 | 
            +
                    # openUrl: 'http://10.0.2.2:3000/offers.json?_render=v1',
         | 
| 29 | 
            +
                  }
         | 
| 40 30 | 
             
                }
         | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
                 | 
| 31 | 
            +
                response = fcm.send(registration_ids, options)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                body = JSON.parse(response[:body])
         | 
| 34 | 
            +
                if (errors = body['results'].map { |i| i['error'] }.compact).any?
         | 
| 35 | 
            +
                  message = errors.join(', ')
         | 
| 36 | 
            +
                else
         | 
| 37 | 
            +
                  message = 'Sent'
         | 
| 38 | 
            +
                end
         | 
| 47 39 | 
             
              else
         | 
| 48 | 
            -
                message = ' | 
| 40 | 
            +
                message = 'FCM gem needed'
         | 
| 49 41 | 
             
              end
         | 
| 50 42 | 
             
            else
         | 
| 51 | 
            -
              message = ' | 
| 43 | 
            +
              message = 'Env var needed: GCM_SERVER_KEY'
         | 
| 52 44 | 
             
            end
         | 
| 53 45 |  | 
| 54 46 | 
             
            json_ui_response json do |action|
         | 
| @@ -13,12 +13,15 @@ page.list firstSection: ->(section) do | |
| 13 13 |  | 
| 14 14 | 
             
                template.thumbnail title: 'Send Android Notification', onClick: ->(action) do
         | 
| 15 15 | 
             
                  action.auth_saveCsrfToken token: form_authenticity_token, onSave: ->(subaction) do
         | 
| 16 | 
            -
                    subaction.devices_getPushToken paramNameForToken: 'formData[token]', onGet: ->(subsubaction) do
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                    end
         | 
| 16 | 
            +
                    # subaction.devices_getPushToken paramNameForToken: 'formData[token]', onGet: ->(subsubaction) do
         | 
| 17 | 
            +
                    #   formData = {
         | 
| 18 | 
            +
                    #     title: 'Title from formData'
         | 
| 19 | 
            +
                    #   }
         | 
| 20 | 
            +
                    #   subsubaction.http_post url: json_ui_garage_url(path: 'notifications/android_post'), formData: formData
         | 
| 21 | 
            +
                    # end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    post_url = json_ui_garage_url(path: 'notifications/android_post', title: 'Title from formData')
         | 
| 24 | 
            +
                    subaction.devices_getPushToken postUrl: post_url, paramNameForToken: 'token'
         | 
| 22 25 | 
             
                  end
         | 
| 23 26 | 
             
                end
         | 
| 24 27 |  | 
| @@ -4,30 +4,18 @@ page = json_ui_page json | |
| 4 4 |  | 
| 5 5 | 
             
            render "#{@path_prefix}/nav_menu", json: json, page: page
         | 
| 6 6 |  | 
| 7 | 
            -
            page.navBar backgroundColor: '#a8c4e3', rightButtons: ->(menu) do
         | 
| 8 | 
            -
              menu.button icon: 'search', onClick: ->(action) do | 
| 7 | 
            +
            page.navBar backgroundColor: '#a8c4e3', color: '#ffffff', rightButtons: ->(menu) do
         | 
| 8 | 
            +
              menu.button icon: 'search', onClick: ->(action) do
         | 
| 9 9 | 
             
                action.dialogs_alert message: 'Perform some action'
         | 
| 10 10 | 
             
              end
         | 
| 11 | 
            -
              menu.button icon: { name: 'star', badge: { text: '1', backgroundColor: '#ff0000' } }, onClick: ->(action) do | 
| 11 | 
            +
              menu.button icon: { name: 'star', badge: { text: '1', backgroundColor: '#ff0000' } }, onClick: ->(action) do
         | 
| 12 12 | 
             
                action.dialogs_alert message: 'Perform some action'
         | 
| 13 13 | 
             
              end
         | 
| 14 | 
            -
              menu.button icon: { name: 'map', badge: '2' }, onClick: ->(action) do | 
| 14 | 
            +
              menu.button icon: { name: 'map', badge: '2' }, onClick: ->(action) do
         | 
| 15 15 | 
             
                action.dialogs_alert message: 'Perform some action'
         | 
| 16 16 | 
             
              end
         | 
| 17 17 | 
             
            end
         | 
| 18 18 |  | 
| 19 | 
            -
            # page.rightNavButtons do |menu|
         | 
| 20 | 
            -
            #   menu.button icon: 'search', onClick: ->(action) do 
         | 
| 21 | 
            -
            #     action.dialogs_alert message: 'Perform some action'
         | 
| 22 | 
            -
            #   end
         | 
| 23 | 
            -
            #   menu.button icon: { name: 'star', badge: { text: '1', backgroundColor: '#ff0000' } }, onClick: ->(action) do 
         | 
| 24 | 
            -
            #     action.dialogs_alert message: 'Perform some action'
         | 
| 25 | 
            -
            #   end
         | 
| 26 | 
            -
            #   menu.button icon: { name: 'map', badge: '2' }, onClick: ->(action) do 
         | 
| 27 | 
            -
            #     action.dialogs_alert message: 'Perform some action'
         | 
| 28 | 
            -
            #   end
         | 
| 29 | 
            -
            # end
         | 
| 30 | 
            -
             | 
| 31 19 | 
             
            page.scroll padding: glib_json_padding_body, childViews: ->(scroll) do
         | 
| 32 20 | 
             
              scroll.label text: 'See the right menu buttons on the nav bar'
         | 
| 33 21 | 
             
            end
         | 
| @@ -1,26 +1,25 @@ | |
| 1 1 | 
             
            json.title 'Tables'
         | 
| 2 2 |  | 
| 3 | 
            -
            json_ui_page json | 
| 4 | 
            -
             | 
| 3 | 
            +
            page = json_ui_page json
         | 
| 4 | 
            +
            render "#{@path_prefix}/nav_menu", json: json, page: page, top_nav: true
         | 
| 5 5 |  | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                  end
         | 
| 6 | 
            +
            page.list firstSection: ->(section) do
         | 
| 7 | 
            +
              section.rows builder: ->(template) do
         | 
| 8 | 
            +
                template.thumbnail title: 'Layout', onClick: ->(action) do
         | 
| 9 | 
            +
                  action.windows_open url: json_ui_garage_url(path: 'tables/layout')
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                template.thumbnail title: 'Horizontal Scroll', onClick: ->(action) do
         | 
| 12 | 
            +
                  action.windows_open url: json_ui_garage_url(path: 'tables/horizontal_scroll')
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                template.thumbnail title: 'Export/Import', onClick: ->(action) do
         | 
| 15 | 
            +
                  action.windows_open url: json_ui_garage_url(path: 'tables/export_import')
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
                template.thumbnail title: 'Autoload as Needed', onClick: ->(action) do
         | 
| 18 | 
            +
                  action.windows_open url: json_ui_garage_url(path: 'tables/autoload_as_needed')
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                template.thumbnail title: 'Autoload All', onClick: ->(action) do
         | 
| 21 | 
            +
                  action.windows_open url: json_ui_garage_url(path: 'tables/autoload_all')
         | 
| 23 22 | 
             
                end
         | 
| 24 | 
            -
             | 
| 25 23 | 
             
              end
         | 
| 24 | 
            +
             | 
| 26 25 | 
             
            end
         | 
| @@ -1,38 +1,36 @@ | |
| 1 1 | 
             
            json.title 'Tables'
         | 
| 2 2 |  | 
| 3 | 
            -
            json_ui_page json | 
| 4 | 
            -
             | 
| 3 | 
            +
            page = json_ui_page json
         | 
| 4 | 
            +
            render "#{@path_prefix}/nav_menu", json: json, page: page
         | 
| 5 5 |  | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                      end
         | 
| 6 | 
            +
            column_indexes = (1..5)
         | 
| 7 | 
            +
            page.table sections: [
         | 
| 8 | 
            +
              ->(section) do
         | 
| 9 | 
            +
                section.rows builder: ->(row) do
         | 
| 10 | 
            +
                  row.default colSpans: [3, 2], cellViews: ->(cell) do
         | 
| 11 | 
            +
                    cell.label text: 'Spans 3 columns'
         | 
| 12 | 
            +
                    cell.panels_horizontal width: 'matchParent', backgroundColor: '#dddddd', padding: { top: 10, right: 10, bottom: 10, left: 10 }, childViews: ->(horizontal) do
         | 
| 13 | 
            +
                      horizontal.label text: 'Spans 2 columns'
         | 
| 15 14 | 
             
                    end
         | 
| 16 15 | 
             
                  end
         | 
| 17 | 
            -
                end | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                     | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end,
         | 
| 18 | 
            +
              ->(section) do
         | 
| 19 | 
            +
                section.header cellViews: ->(header) do
         | 
| 20 | 
            +
                  column_indexes.each do |i|
         | 
| 21 | 
            +
                    header.label text: "Heading#{i}"
         | 
| 23 22 | 
             
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 24 |  | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                      end
         | 
| 31 | 
            -
                    end, onClick: ->(action) do
         | 
| 32 | 
            -
                      action.windows_open url: json_ui_garage_url(path: 'home/blank')
         | 
| 25 | 
            +
                items = [1, 2, 3]
         | 
| 26 | 
            +
                section.rows objects: items, builder: ->(row, item, index) do
         | 
| 27 | 
            +
                  row.default colStyles: [{ width: 200, backgroundColor: '#eeeeee' }], cellViews: ->(cell) do
         | 
| 28 | 
            +
                    column_indexes.each do |i|
         | 
| 29 | 
            +
                      cell.label text: "Data #{item}"
         | 
| 33 30 | 
             
                    end
         | 
| 31 | 
            +
                  end, onClick: ->(action) do
         | 
| 32 | 
            +
                    action.windows_open url: json_ui_garage_url(path: 'home/blank')
         | 
| 34 33 | 
             
                  end
         | 
| 35 34 | 
             
                end
         | 
| 36 | 
            -
               | 
| 37 | 
            -
             | 
| 38 | 
            -
            end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            ]
         | 
| @@ -19,15 +19,27 @@ json_ui_page json do |page| | |
| 19 19 | 
             
                scroll.h2 text: 'With select options'
         | 
| 20 20 | 
             
                scroll.spacer height: 6
         | 
| 21 21 | 
             
                scroll.banners_select width: 'matchParent', icon: 'info', message: 'This is a select banner', buttons: ->(menu) do
         | 
| 22 | 
            -
                  menu.button text: ' | 
| 22 | 
            +
                  menu.button icon: 'add', text: 'Add', onClick: ->(action) do
         | 
| 23 23 | 
             
                    action.windows_open url: json_ui_garage_url(path: 'home/blank')
         | 
| 24 24 | 
             
                  end
         | 
| 25 | 
            -
                  menu.button text: ' | 
| 25 | 
            +
                  menu.button icon: 'edit', text: 'Edit', onClick: ->(action) do
         | 
| 26 26 | 
             
                    action.windows_open url: json_ui_garage_url(path: 'home/slow')
         | 
| 27 27 | 
             
                  end
         | 
| 28 | 
            -
                  menu.button text: ' | 
| 28 | 
            +
                  menu.button icon: 'delete', text: 'Delete', onClick: ->(action) do
         | 
| 29 29 | 
             
                    action.dialogs_alert message: 'Alert'
         | 
| 30 30 | 
             
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  menu.button icon: 'more_vert', childButtons: ->(submenu) do
         | 
| 33 | 
            +
                    submenu.button icon: 'add', text: 'Option1', onClick: ->(action) do
         | 
| 34 | 
            +
                      action.windows_open url: json_ui_garage_url(path: 'home/blank')
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    submenu.button icon: 'edit', text: 'Option2', onClick: ->(action) do
         | 
| 37 | 
            +
                      action.windows_open url: json_ui_garage_url(path: 'home/slow')
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                    submenu.button icon: 'delete', text: 'Option3', onClick: ->(action) do
         | 
| 40 | 
            +
                      action.dialogs_alert message: 'Alert'
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 31 43 | 
             
                end
         | 
| 32 44 |  | 
| 33 45 | 
             
                scroll.spacer height: 20
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: glib-web
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.5. | 
| 4 | 
            +
              version: 0.5.23
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - ''
         | 
| @@ -64,6 +64,7 @@ executables: [] | |
| 64 64 | 
             
            extensions: []
         | 
| 65 65 | 
             
            extra_rdoc_files: []
         | 
| 66 66 | 
             
            files:
         | 
| 67 | 
            +
            - app/controllers/concerns/glib/analytics/funnel.rb
         | 
| 67 68 | 
             
            - app/controllers/concerns/glib/auth/policy.rb
         | 
| 68 69 | 
             
            - app/controllers/concerns/glib/json/dynamic_text.rb
         | 
| 69 70 | 
             
            - app/controllers/concerns/glib/json/libs.rb
         | 
| @@ -84,6 +85,7 @@ files: | |
| 84 85 | 
             
            - app/helpers/glib/json_ui/action_builder/sheets.rb
         | 
| 85 86 | 
             
            - app/helpers/glib/json_ui/action_builder/snackbars.rb
         | 
| 86 87 | 
             
            - app/helpers/glib/json_ui/action_builder/windows.rb
         | 
| 88 | 
            +
            - app/helpers/glib/json_ui/analytics_helper.rb
         | 
| 87 89 | 
             
            - app/helpers/glib/json_ui/dynamic_field_builders.rb
         | 
| 88 90 | 
             
            - app/helpers/glib/json_ui/generic_builders.rb
         | 
| 89 91 | 
             
            - app/helpers/glib/json_ui/list_builders.rb
         |