bluesky 0.0.1
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 +7 -0
- data/bin/bluesky +133 -0
- data/lib/bluesky/application.rb +102 -0
- data/lib/bluesky/dom_helper.rb +37 -0
- data/lib/bluesky/navigation_controller.rb +79 -0
- data/lib/bluesky/pure_component.rb +226 -0
- data/lib/bluesky/try.rb +100 -0
- data/lib/bluesky/version.rb +3 -0
- data/lib/bluesky/view_controller.rb +153 -0
- data/lib/bluesky.rb +53 -0
- data/spec/bluesky_spec.rb +34 -0
- data/spec/spec_helper.rb +109 -0
- metadata +127 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 8026d38b693a30250518feaa44d015bc53d84f6c
         | 
| 4 | 
            +
              data.tar.gz: e176fc2a30755f2580ea12a2871abf415b561aca
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 2e833e11e26b668e146c76cffeb5df9404ee5a938e38d5bfa04148884b3ec9203a7da46e528b0dcf6bccff416b657581c826b1c2e8a884990f1c722e52a6bcac
         | 
| 7 | 
            +
              data.tar.gz: 8e4b9b75a7d66d8d934dcb5d30f6bcd3fb125b99680297f002bc75534746d8244ddc02df4f906f4da480ac7649f8263aaa88745fa2b9e33a037566e7a5f1f4e5
         | 
    
        data/bin/bluesky
    ADDED
    
    | @@ -0,0 +1,133 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'fileutils'
         | 
| 4 | 
            +
            require 'optparse'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class String
         | 
| 7 | 
            +
              def unindent
         | 
| 8 | 
            +
                gsub /^#{self[/\A[ \t]*/]}/, ''
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            options = {}
         | 
| 13 | 
            +
            opt_parser = OptionParser.new do |opts|
         | 
| 14 | 
            +
              opts.banner = "Usage: bluesky [command] [options]"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
         | 
| 17 | 
            +
                options[:verbose] = v
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            opt_parser.parse!
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            case ARGV[0]
         | 
| 24 | 
            +
            when "new"
         | 
| 25 | 
            +
              path = File.absolute_path(ARGV[1])
         | 
| 26 | 
            +
              abort "#{path} already exist! Aborting!" if File.exist?(path)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              title = ARGV[1].capitalize
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              FileUtils.mkdir_p([
         | 
| 31 | 
            +
                File.join(path, 'app/models'),
         | 
| 32 | 
            +
                File.join(path, 'app/views'),
         | 
| 33 | 
            +
                File.join(path, 'app/controllers'),
         | 
| 34 | 
            +
              ])
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              IO.write File.join(path, 'Gemfile'), <<-RUBY.unindent
         | 
| 37 | 
            +
                source 'https://rubygems.org'
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                gem 'opal', '~> 0.10'
         | 
| 40 | 
            +
                gem 'clearwater', '1.0.0.rc4'
         | 
| 41 | 
            +
                gem 'bluesky', path: '../'
         | 
| 42 | 
            +
              RUBY
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              IO.write File.join(path, 'app/application.rb'), <<-RUBY.unindent
         | 
| 45 | 
            +
                require 'bluesky'
         | 
| 46 | 
            +
                include Bluesky
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                require_tree './models'
         | 
| 49 | 
            +
                require_tree './views'
         | 
| 50 | 
            +
                require_tree './controllers'
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                $app = Class.new(Application) do
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def root_view_controller
         | 
| 55 | 
            +
                    @root_view_controller ||= NavigationController.new(RootController.new)
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                end.new
         | 
| 59 | 
            +
                $app.debug!
         | 
| 60 | 
            +
                $app.run
         | 
| 61 | 
            +
              RUBY
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              IO.write File.join(path, 'app/controllers/root_controller.rb'), <<-RUBY.unindent
         | 
| 64 | 
            +
                class RootController < ViewController
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  attribute :name, 'World'
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def view
         | 
| 69 | 
            +
                    HelloView(name: name)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def change_name(name)
         | 
| 73 | 
            +
                    self.name = name
         | 
| 74 | 
            +
                    notify(self, :name_changed, self.name)
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              RUBY
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              IO.write File.join(path, 'app/views/hello_view.rb'), <<-RUBY.unindent
         | 
| 81 | 
            +
                class HelloView < PureComponent
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  attribute :name
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def render
         | 
| 86 | 
            +
                    div [header, name_input]
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def header
         | 
| 90 | 
            +
                    h1("Hello \#{name}")
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def name_input
         | 
| 94 | 
            +
                    handler = -> (event) { dispatch(:change_name, event.target.value) }
         | 
| 95 | 
            +
                    label ['Change name: ',
         | 
| 96 | 
            +
                           input(type: :text, value: name, oninput: handler)]
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
              RUBY
         | 
| 101 | 
            +
             | 
| 102 | 
            +
             | 
| 103 | 
            +
              IO.write File.join(path, 'config.ru'), <<-RUBY.unindent
         | 
| 104 | 
            +
                require 'bundler'
         | 
| 105 | 
            +
                Bundler.require
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                run Opal::Server.new { |s|
         | 
| 108 | 
            +
                  s.main = 'application'
         | 
| 109 | 
            +
                  s.append_path 'app'
         | 
| 110 | 
            +
                  s.index_path = 'index.html.erb'
         | 
| 111 | 
            +
                }
         | 
| 112 | 
            +
              RUBY
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              IO.write File.join(path, 'index.html.erb'), <<-ERB.unindent
         | 
| 115 | 
            +
                <!DOCTYPE html>
         | 
| 116 | 
            +
                <html>
         | 
| 117 | 
            +
                  <head>
         | 
| 118 | 
            +
                    <meta charset="utf-8">
         | 
| 119 | 
            +
                    <title>#{title}</title>
         | 
| 120 | 
            +
                  </head>
         | 
| 121 | 
            +
                  <body>
         | 
| 122 | 
            +
                    <%= javascript_include_tag 'application' %>
         | 
| 123 | 
            +
                  </body>
         | 
| 124 | 
            +
                </html>
         | 
| 125 | 
            +
              ERB
         | 
| 126 | 
            +
             | 
| 127 | 
            +
              Dir.chdir(path) do
         | 
| 128 | 
            +
                system("bundle install")
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            else
         | 
| 132 | 
            +
              puts opt_parser
         | 
| 133 | 
            +
            end
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            module Bluesky
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # Bluesky::Application
         | 
| 4 | 
            +
              class Application
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                include DOMHelper
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                attr_accessor :root_view_controller, :debug, :delegate
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize
         | 
| 11 | 
            +
                  @dispatch_queue = []
         | 
| 12 | 
            +
                  @debug = false
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def debug?
         | 
| 16 | 
            +
                  @debug
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def debug!
         | 
| 20 | 
            +
                  @debug = true
         | 
| 21 | 
            +
                  @clearwater.debug! if @clearwater
         | 
| 22 | 
            +
                  @delegate = DebugDelegate.new(@delegate)
         | 
| 23 | 
            +
                  self
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def dispatch(target, action, *payload, &block)
         | 
| 27 | 
            +
                  promise = Promise.new
         | 
| 28 | 
            +
                  delegate.try(:dispatch, target, action, *payload)
         | 
| 29 | 
            +
                  @dispatch_queue << lambda do
         | 
| 30 | 
            +
                    begin
         | 
| 31 | 
            +
                      result = target.send(action, *payload, &block)
         | 
| 32 | 
            +
                      promise.resolve(result).then { refresh }
         | 
| 33 | 
            +
                      delegate.try(:dispatch_resolved, target, action, *payload, result)
         | 
| 34 | 
            +
                    rescue => err
         | 
| 35 | 
            +
                      promise.reject(err)
         | 
| 36 | 
            +
                      delegate.try(:dispatch_rejected, target, action, *payload, err)
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  defer { process_dispatch_queue }
         | 
| 40 | 
            +
                  promise
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def process_dispatch_queue
         | 
| 44 | 
            +
                  return if @dispatch_queue.empty?
         | 
| 45 | 
            +
                  @dispatch_queue.delete_if do |task|
         | 
| 46 | 
            +
                    task.call
         | 
| 47 | 
            +
                    true
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def notify(source, event, *payload)
         | 
| 52 | 
            +
                  @delegate.try(:notify, source, event, *payload)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def refresh(&block)
         | 
| 56 | 
            +
                  promise = Promise.new
         | 
| 57 | 
            +
                  @clearwater.call { promise.resolve }
         | 
| 58 | 
            +
                  block ? promise.then(&block) : promise
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def run
         | 
| 62 | 
            +
                  raise 'root_view_controller must be defined in Application' unless root_view_controller
         | 
| 63 | 
            +
                  PureComponent.install_hooks(debug?)
         | 
| 64 | 
            +
                  root_view_controller.parent = self
         | 
| 65 | 
            +
                  @clearwater = Clearwater::Application.new(component: root_view_controller)
         | 
| 66 | 
            +
                  @clearwater.debug! if debug?
         | 
| 67 | 
            +
                  root_view_controller.begin_appearance_transition(true)
         | 
| 68 | 
            +
                  refresh { root_view_controller.end_appearance_transition() }
         | 
| 69 | 
            +
                  self
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              class DebugDelegate
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def initialize(delegate = nil)
         | 
| 77 | 
            +
                  @delegate = delegate
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
                def dispatch(target, action, *payload)
         | 
| 80 | 
            +
                  @delegate.try(:dispatch, target, action, *payload)
         | 
| 81 | 
            +
                  puts "[DISPATCH] #{action} on #{target}"
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def dispatch_resolved(target, action, *payload, result)
         | 
| 85 | 
            +
                  @delegate.try(:dispatch_resolved, result, target, action, *payload)
         | 
| 86 | 
            +
                  puts "[RESOLVED] #{action} on #{target} yielding #{result}"
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def dispatch_rejected(target, action, *payload, error)
         | 
| 90 | 
            +
                  @delegate.try(:dispatch_rejected, target, action, *payload, error)
         | 
| 91 | 
            +
                  puts "[REJECTED] #{action} on #{target}"
         | 
| 92 | 
            +
                  warn error
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def notify(source, event, *payload)
         | 
| 96 | 
            +
                  puts "[NOTIFY] #{event} from #{source}"
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            end
         | 
| 102 | 
            +
             | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module Bluesky
         | 
| 2 | 
            +
              module DOMHelper
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                extend self
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                protected
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # Delays execution to the main event loop
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # == Parameters:
         | 
| 11 | 
            +
                # block
         | 
| 12 | 
            +
                #   A block to execute on the main event loop
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # == Returns:
         | 
| 15 | 
            +
                # A promise that resolves after block has completed
         | 
| 16 | 
            +
                def defer(&block)
         | 
| 17 | 
            +
                  timeout(0, &block)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def delay(hours: 0, minutes: 0, seconds: 0, milliseconds: 0, &block)
         | 
| 21 | 
            +
                  timeout(((hours * 60 + minutes) * 60 + seconds) * 1000 + milliseconds, &block)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def timeout(milliseconds, &block)
         | 
| 27 | 
            +
                  promise = Promise.new
         | 
| 28 | 
            +
                  $$[:setTimeout].call(-> { promise.resolve }, milliseconds)
         | 
| 29 | 
            +
                  block ? promise.then(&block) : promise
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def self.included(base)
         | 
| 33 | 
            +
                  base.extend(self)
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            module Bluesky
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # Bluesky::NavigationController
         | 
| 4 | 
            +
              class NavigationController < ViewController
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                attr_accessor :root_view_contrller
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(root_view_controller)
         | 
| 9 | 
            +
                  raise 'NavigationController requires a root_view_controller' unless root_view_controller
         | 
| 10 | 
            +
                  super
         | 
| 11 | 
            +
                  add_child_view_controller(root_view_controller)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def view
         | 
| 15 | 
            +
                  top_view_controller.view
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def view_will_appear
         | 
| 19 | 
            +
                  top_view_controller.begin_appearance_transition(true)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def view_did_appear
         | 
| 23 | 
            +
                  top_view_controller.end_appearance_transition
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def view_will_disappear
         | 
| 27 | 
            +
                  top_view_controller.end_appearance_transition(false)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def view_did_disappear
         | 
| 31 | 
            +
                  top_view_controller.end_appearance_transition
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def root_view_controller
         | 
| 35 | 
            +
                  @children.first
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def top_view_controller
         | 
| 39 | 
            +
                  @children.last
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def visible_view_controller
         | 
| 43 | 
            +
                  top_view_controller
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def push_view_controller(view_controller)
         | 
| 47 | 
            +
                  old_view_controller = top_view_controller
         | 
| 48 | 
            +
                  old_view_controller.begin_appearance_transition(false)
         | 
| 49 | 
            +
                  view_controller.begin_appearance_transition(true)
         | 
| 50 | 
            +
                  add_child_view_controller(view_controller)
         | 
| 51 | 
            +
                  force_update do
         | 
| 52 | 
            +
                    view_controller.end_appearance_transition
         | 
| 53 | 
            +
                    old_view_controller.end_appearance_transition
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def pop_view_controller
         | 
| 58 | 
            +
                  return nil if top_view_controller == root_view_controller
         | 
| 59 | 
            +
                  old_view_controller = top_view_controller
         | 
| 60 | 
            +
                  old_view_controller.begin_appearance_transition(false)
         | 
| 61 | 
            +
                  old_view_controller.remove_from_parent_view_controller
         | 
| 62 | 
            +
                  top_view_controller.begin_appearance_transition(true)
         | 
| 63 | 
            +
                  force_update do
         | 
| 64 | 
            +
                    top_view_controller.end_appearance_transition
         | 
| 65 | 
            +
                    old_view_controller.end_appearance_transition
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def pop_to_view_controller(view_controller)
         | 
| 70 | 
            +
                  result = []
         | 
| 71 | 
            +
                  result << pop_view_controller while top_view_controller != view_controller
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def pop_to_root_view_controller
         | 
| 75 | 
            +
                  pop_to_view_controller(root_view_controller)
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| @@ -0,0 +1,226 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module Bluesky
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              module DSL
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                module_function
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                Clearwater::Component::HTML_TAGS.each do |tag_name|
         | 
| 9 | 
            +
                  define_method(tag_name) do |attributes, content|
         | 
| 10 | 
            +
                    %x{
         | 
| 11 | 
            +
                      if(!(attributes === nil || attributes.$$is_hash)) {
         | 
| 12 | 
            +
                        content = attributes;
         | 
| 13 | 
            +
                        attributes = nil;
         | 
| 14 | 
            +
                      }
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    tag(tag_name, attributes, content)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def tag(tag_name, attributes=nil, content=nil, &block)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  if block
         | 
| 24 | 
            +
                    attributes ||= {}
         | 
| 25 | 
            +
                    content ||= []
         | 
| 26 | 
            +
                    block.call(NodeBuilder.new(tag_name, attributes, content))
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  Clearwater::VirtualDOM.node(
         | 
| 30 | 
            +
                    tag_name,
         | 
| 31 | 
            +
                    Clearwater::Component.sanitize_attributes(attributes),
         | 
| 32 | 
            +
                    Clearwater::Component.sanitize_content(content)
         | 
| 33 | 
            +
                  )
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
             | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              # A presentation `Component`
         | 
| 40 | 
            +
              class PureComponent
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # Example:
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                # class FooView < Bluesky::PureComponent
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                #   attribute :times
         | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
                #   def render
         | 
| 49 | 
            +
                #     div([
         | 
| 50 | 
            +
                #       div("Clicked #{times}!"),
         | 
| 51 | 
            +
                #       form([
         | 
| 52 | 
            +
                #         label('Button to click'),
         | 
| 53 | 
            +
                #         button({ onclick: -> { dispatch(:clicked) } }, [ 'click me!' ])
         | 
| 54 | 
            +
                #       ])
         | 
| 55 | 
            +
                #     ])
         | 
| 56 | 
            +
                #   end
         | 
| 57 | 
            +
                # end
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # class FooController < Bluesky::ViewController
         | 
| 60 | 
            +
                #
         | 
| 61 | 
            +
                #   def initialize
         | 
| 62 | 
            +
                #     @times = 0
         | 
| 63 | 
            +
                #   end
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                #   def view
         | 
| 66 | 
            +
                #     FormView.new({ times: @times }, self)
         | 
| 67 | 
            +
                #   end
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                #   def clicked
         | 
| 70 | 
            +
                #     @times += 1
         | 
| 71 | 
            +
                #   end
         | 
| 72 | 
            +
                # end
         | 
| 73 | 
            +
                if RUBY_ENGINE == 'opal'
         | 
| 74 | 
            +
                %x{
         | 
| 75 | 
            +
                  Opal.defn(self, 'hook', function(node, propertyName, previousValue) {
         | 
| 76 | 
            +
                    var self = this;
         | 
| 77 | 
            +
                    console.log('hook', node, propertyName, previousValue);
         | 
| 78 | 
            +
                    if (!previousValue) {
         | 
| 79 | 
            +
                      var component_did_mount = self.$component_did_mount;
         | 
| 80 | 
            +
                      if (component_did_mount && !component_did_mount.$$stub) {
         | 
| 81 | 
            +
                        self.$mount();
         | 
| 82 | 
            +
                        self.$component_did_mount();
         | 
| 83 | 
            +
                      }
         | 
| 84 | 
            +
                    }
         | 
| 85 | 
            +
                  });
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  Opal.defn(self, 'unhook', function(node, propertyName, previousValue) {
         | 
| 88 | 
            +
                    var self = this;
         | 
| 89 | 
            +
                    console.log('unhook', node, propertyName, previousValue);
         | 
| 90 | 
            +
                    if (!previousValue) {
         | 
| 91 | 
            +
                      var component_will_unmount = self.$component_will_unmount;
         | 
| 92 | 
            +
                      if (component_will_unmount && !component_will_unmount.$$stub) {
         | 
| 93 | 
            +
                        self.$unmount();
         | 
| 94 | 
            +
                        self.$component_will_unmount();
         | 
| 95 | 
            +
                      }
         | 
| 96 | 
            +
                    }
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  });
         | 
| 99 | 
            +
                }
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                include Clearwater::Component
         | 
| 103 | 
            +
                include Clearwater::CachedRender
         | 
| 104 | 
            +
                include DSL
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                @descendants = []
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def self.inherited(subclass)
         | 
| 109 | 
            +
                  DSL.send(:define_method, subclass.name) do |data = {}, delegate = nil, &block|
         | 
| 110 | 
            +
                    delegate ||= @delegate
         | 
| 111 | 
            +
                    component = subclass.new(data, delegate)
         | 
| 112 | 
            +
                    block.call(component) if block
         | 
| 113 | 
            +
                    component
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  @descendants << subclass
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def self.install_hooks(debug=false)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  @descendants.each do |subclass|
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    if subclass.instance_methods.include?(:component_did_mount) ||
         | 
| 124 | 
            +
                       subclass.instance_methods.include?(:component_will_unmount)
         | 
| 125 | 
            +
                      subclass.class_eval { `Opal.defn(self, '$$mountable', true);` }
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    if subclass.instance_methods.include?(:component_will_mount)
         | 
| 129 | 
            +
                      subclass.class_eval { `Opal.defn(self, '$$hook_will_mount', true);` }
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                    subclass.send(:alias_method, :do_render, :render) unless
         | 
| 132 | 
            +
                      subclass.instance_methods.include?(:do_render)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    subclass.send(:define_method, :render) do
         | 
| 135 | 
            +
                      begin
         | 
| 136 | 
            +
                        $$.console.time("#{subclass.name}:render") if debug
         | 
| 137 | 
            +
                        %x{
         | 
| 138 | 
            +
                          var contents = #{do_render};
         | 
| 139 | 
            +
                          if (self.$$mountable) contents.properties.ref = self;
         | 
| 140 | 
            +
                          return contents;
         | 
| 141 | 
            +
                        }
         | 
| 142 | 
            +
                      rescue Object => err
         | 
| 143 | 
            +
                        warn err
         | 
| 144 | 
            +
                        div({ class: 'broken', style: { display: :none } }, [err.message])
         | 
| 145 | 
            +
                      ensure
         | 
| 146 | 
            +
                        $$.console.timeEnd("#{subclass.name}:render") if debug
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                    end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def self.attribute(name, *args)
         | 
| 155 | 
            +
                  case args.length
         | 
| 156 | 
            +
                  when 0
         | 
| 157 | 
            +
                    define_method(name) do |&block|
         | 
| 158 | 
            +
                      if block
         | 
| 159 | 
            +
                        _data.store(name, block)
         | 
| 160 | 
            +
                        block
         | 
| 161 | 
            +
                      else
         | 
| 162 | 
            +
                        _data.fetch(name)
         | 
| 163 | 
            +
                      end
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
                  when 1
         | 
| 166 | 
            +
                    if args[0].respond_to?(:call)
         | 
| 167 | 
            +
                      define_method(name) { _data.fetch(name) { _data.store(name, args[0].call) } }
         | 
| 168 | 
            +
                    else
         | 
| 169 | 
            +
                      define_method(name) { _data.fetch(name, args[0]) }
         | 
| 170 | 
            +
                    end
         | 
| 171 | 
            +
                  else
         | 
| 172 | 
            +
                    raise ArgumentError, %{ wrong number of arguments
         | 
| 173 | 
            +
                                            (#{args.length} for 1..2) }
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
                  define_method("#{name}=") { |value| _data.store(name, value) }
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def initialize(data = {}, delegate = nil)
         | 
| 179 | 
            +
                  @data     = data
         | 
| 180 | 
            +
                  @delegate = delegate
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                def shallow_equal?(a, b)
         | 
| 184 | 
            +
                  a.equal?(b) || a.each_pair.all? do |k, v|
         | 
| 185 | 
            +
                    bk = b[k]
         | 
| 186 | 
            +
                    v.equal?(bk) || v.eql?(bk)
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
                rescue Object => _
         | 
| 189 | 
            +
                  false
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                def should_render?(previous)
         | 
| 193 | 
            +
                  # puts "#{self.class.name}:should_render? #{(@delegate && @delegate.force_update?)}, #{!shallow_equal?(_data, previous._data)}"
         | 
| 194 | 
            +
                  (@delegate && @delegate.force_update?) ||
         | 
| 195 | 
            +
                    !shallow_equal?(_data, previous._data)
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                def dispatch(action, *payload, &block)
         | 
| 199 | 
            +
                  warn 'Missing delegate' unless @delegate
         | 
| 200 | 
            +
                  root = @delegate
         | 
| 201 | 
            +
                  root = root.parent while root.respond_to?(:parent) && root.parent
         | 
| 202 | 
            +
                  root.dispatch(@delegate, action, *payload, &block)
         | 
| 203 | 
            +
                end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                def mount
         | 
| 206 | 
            +
                  puts 'mount'
         | 
| 207 | 
            +
                  @mounted = true
         | 
| 208 | 
            +
                end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                def unmount
         | 
| 211 | 
            +
                  puts 'unmount'
         | 
| 212 | 
            +
                  @mounted = false
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                def mounted?
         | 
| 216 | 
            +
                  !!@mounted
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                protected
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                def _data
         | 
| 222 | 
            +
                  @data
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
              end
         | 
| 226 | 
            +
            end
         | 
    
        data/lib/bluesky/try.rb
    ADDED
    
    | @@ -0,0 +1,100 @@ | |
| 1 | 
            +
            class Object
         | 
| 2 | 
            +
              # Invokes the public method whose name goes as first argument just like
         | 
| 3 | 
            +
              # +public_send+ does, except that if the receiver does not respond to it the
         | 
| 4 | 
            +
              # call returns +nil+ rather than raising an exception.
         | 
| 5 | 
            +
              #
         | 
| 6 | 
            +
              # This method is defined to be able to write
         | 
| 7 | 
            +
              #
         | 
| 8 | 
            +
              #   @person.try(:name)
         | 
| 9 | 
            +
              #
         | 
| 10 | 
            +
              # instead of
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              #   @person.name if @person
         | 
| 13 | 
            +
              #
         | 
| 14 | 
            +
              # +try+ calls can be chained:
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              #   @person.try(:spouse).try(:name)
         | 
| 17 | 
            +
              #
         | 
| 18 | 
            +
              # instead of
         | 
| 19 | 
            +
              #
         | 
| 20 | 
            +
              #   @person.spouse.name if @person && @person.spouse
         | 
| 21 | 
            +
              #
         | 
| 22 | 
            +
              # +try+ will also return +nil+ if the receiver does not respond to the method:
         | 
| 23 | 
            +
              #
         | 
| 24 | 
            +
              #   @person.try(:non_existing_method) #=> nil
         | 
| 25 | 
            +
              #
         | 
| 26 | 
            +
              # instead of
         | 
| 27 | 
            +
              #
         | 
| 28 | 
            +
              #   @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
         | 
| 29 | 
            +
              #
         | 
| 30 | 
            +
              # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
         | 
| 31 | 
            +
              # to the method:
         | 
| 32 | 
            +
              #
         | 
| 33 | 
            +
              #   nil.try(:to_i) # => nil, rather than 0
         | 
| 34 | 
            +
              #
         | 
| 35 | 
            +
              # Arguments and blocks are forwarded to the method if invoked:
         | 
| 36 | 
            +
              #
         | 
| 37 | 
            +
              #   @posts.try(:each_slice, 2) do |a, b|
         | 
| 38 | 
            +
              #     ...
         | 
| 39 | 
            +
              #   end
         | 
| 40 | 
            +
              #
         | 
| 41 | 
            +
              # The number of arguments in the signature must match. If the object responds
         | 
| 42 | 
            +
              # to the method the call is attempted and +ArgumentError+ is still raised
         | 
| 43 | 
            +
              # in case of argument mismatch.
         | 
| 44 | 
            +
              #
         | 
| 45 | 
            +
              # If +try+ is called without arguments it yields the receiver to a given
         | 
| 46 | 
            +
              # block unless it is +nil+:
         | 
| 47 | 
            +
              #
         | 
| 48 | 
            +
              #   @person.try do |p|
         | 
| 49 | 
            +
              #     ...
         | 
| 50 | 
            +
              #   end
         | 
| 51 | 
            +
              #
         | 
| 52 | 
            +
              # You can also call try with a block without accepting an argument, and the block
         | 
| 53 | 
            +
              # will be instance_eval'ed instead:
         | 
| 54 | 
            +
              #
         | 
| 55 | 
            +
              #   @person.try { upcase.truncate(50) }
         | 
| 56 | 
            +
              #
         | 
| 57 | 
            +
              # Please also note that +try+ is defined on +Object+. Therefore, it won't work
         | 
| 58 | 
            +
              # with instances of classes that do not have +Object+ among their ancestors,
         | 
| 59 | 
            +
              # like direct subclasses of +BasicObject+. For example, using +try+ with
         | 
| 60 | 
            +
              # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
         | 
| 61 | 
            +
              # the delegator itself.
         | 
| 62 | 
            +
              def try(*a, &b)
         | 
| 63 | 
            +
                try!(*a, &b) if a.empty? || respond_to?(a.first)
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
         | 
| 67 | 
            +
              # does not implement the tried method.
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              def try!(*a, &b)
         | 
| 70 | 
            +
                if a.empty? && block_given?
         | 
| 71 | 
            +
                  if b.arity.zero?
         | 
| 72 | 
            +
                    instance_eval(&b)
         | 
| 73 | 
            +
                  else
         | 
| 74 | 
            +
                    yield self
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                else
         | 
| 77 | 
            +
                  public_send(*a, &b)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            class NilClass
         | 
| 83 | 
            +
              # Calling +try+ on +nil+ always returns +nil+.
         | 
| 84 | 
            +
              # It becomes especially helpful when navigating through associations that may return +nil+.
         | 
| 85 | 
            +
              #
         | 
| 86 | 
            +
              #   nil.try(:name) # => nil
         | 
| 87 | 
            +
              #
         | 
| 88 | 
            +
              # Without +try+
         | 
| 89 | 
            +
              #   @person && @person.children.any? && @person.children.first.name
         | 
| 90 | 
            +
              #
         | 
| 91 | 
            +
              # With +try+
         | 
| 92 | 
            +
              #   @person.try(:children).try(:first).try(:name)
         | 
| 93 | 
            +
              def try(*args)
         | 
| 94 | 
            +
                nil
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              def try!(*args)
         | 
| 98 | 
            +
                nil
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
            end
         | 
| @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            module Bluesky
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class ViewController
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                include Clearwater::Component
         | 
| 6 | 
            +
                include DOMHelper
         | 
| 7 | 
            +
                include DSL
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def self.attribute(name, *args, &block)
         | 
| 10 | 
            +
                  case args.length
         | 
| 11 | 
            +
                  when 0
         | 
| 12 | 
            +
                    define_method(name) { data.fetch(name) }
         | 
| 13 | 
            +
                  when 1
         | 
| 14 | 
            +
                    if args[0].respond_to?(:call)
         | 
| 15 | 
            +
                      define_method(name) { data.fetch(name) { data.store(name, args[0].call) } }
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      define_method(name) { data.fetch(name, args[0]) }
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    raise ArgumentError, %{ wrong number of arguments
         | 
| 21 | 
            +
                                            (#{args.length} for 1..2) }
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  define_method("#{name}=") { |value| data.store(name, value) }
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def self.inherited(subclass)
         | 
| 27 | 
            +
                  define_method(subclass.name) do |**data|
         | 
| 28 | 
            +
                    subclass.new(delegate: self, data: data)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                attr_accessor :children, :parent, :data
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def initialize(*_, children: [], parent: nil, data: {})
         | 
| 35 | 
            +
                  @children = children
         | 
| 36 | 
            +
                  @parent = parent
         | 
| 37 | 
            +
                  @data = data
         | 
| 38 | 
            +
                  @appearance = :disappeared
         | 
| 39 | 
            +
                  @force_update = false
         | 
| 40 | 
            +
                  @delegate = self
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def force_update?
         | 
| 44 | 
            +
                  @force_update
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def view
         | 
| 48 | 
            +
                  nil
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def render
         | 
| 52 | 
            +
                  view
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def dispatch(target, action, *payload, &block)
         | 
| 56 | 
            +
                  parent.try(:dispatch, target, action, *payload, &block)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def notify(source, event, *payload)
         | 
| 60 | 
            +
                  parent.try(:notify, source, event, *payload)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def begin_appearance_transition(appearing)
         | 
| 64 | 
            +
                  if appearing
         | 
| 65 | 
            +
                    raise "Invalid appearance #{@appearance} when appearing" if @appearance != :disappeared
         | 
| 66 | 
            +
                    @appearance = :appearing
         | 
| 67 | 
            +
                    view_will_appear()
         | 
| 68 | 
            +
                  else
         | 
| 69 | 
            +
                    raise "Invalid appearance #{@appearance} when disappearing" if @appearance != :appeared
         | 
| 70 | 
            +
                    @appearance = :disappearing
         | 
| 71 | 
            +
                    view_will_disappear()
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def end_appearance_transition()
         | 
| 76 | 
            +
                  case @appearance
         | 
| 77 | 
            +
                  when :appearing
         | 
| 78 | 
            +
                    @appearance = :appeared
         | 
| 79 | 
            +
                    view_did_appear()
         | 
| 80 | 
            +
                  when :disappearing
         | 
| 81 | 
            +
                    @appearance = :disappeared
         | 
| 82 | 
            +
                    view_did_disappear()
         | 
| 83 | 
            +
                  else
         | 
| 84 | 
            +
                    raise "Invalid appearance #{@appearance} when transitioning"
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def add_child_view_controller(view_controller)
         | 
| 89 | 
            +
                  view_controller.will_move_to_parent_view_controller(self)
         | 
| 90 | 
            +
                  view_controller.remove_from_parent_view_controller
         | 
| 91 | 
            +
                  children.push(view_controller)
         | 
| 92 | 
            +
                  view_controller.parent = self
         | 
| 93 | 
            +
                  view_controller.did_move_to_parent_view_controller(self)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def remove_from_parent_view_controller
         | 
| 97 | 
            +
                  return unless parent
         | 
| 98 | 
            +
                  parent.children.delete(self)
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def show(_view_controller)
         | 
| 102 | 
            +
                  raise 'not implemented'
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def present(_view_controller)
         | 
| 106 | 
            +
                  raise 'not implemented'
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def dismiss
         | 
| 110 | 
            +
                  raise 'not implemented'
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def navigation_controller
         | 
| 114 | 
            +
                  parent.is_a?(NavigationController) ? parent :
         | 
| 115 | 
            +
                    parent.try(:navigation_controller)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                # Callbacks
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def will_move_to_parent_view_controller(view_controller)
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def did_move_to_parent_view_controller(view_controller)
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def view_will_appear
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def view_did_appear
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def view_will_disappear
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                def view_did_disappear
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                # Dispatch methods
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def force_update
         | 
| 141 | 
            +
                  @force_update = true
         | 
| 142 | 
            +
                  @parent.refresh do
         | 
| 143 | 
            +
                    @force_update = false
         | 
| 144 | 
            +
                    yield if block_given?
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                def refresh(&block)
         | 
| 149 | 
            +
                  @parent.refresh(&block)
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
    
        data/lib/bluesky.rb
    ADDED
    
    | @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require 'opal'
         | 
| 2 | 
            +
            require 'clearwater'
         | 
| 3 | 
            +
            #require 'clearwater/cached_render'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Clearwater
         | 
| 6 | 
            +
              module CachedRender
         | 
| 7 | 
            +
                class Wrapper
         | 
| 8 | 
            +
                  attr_reader :content
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize content
         | 
| 11 | 
            +
                    @content = content
         | 
| 12 | 
            +
                    @key = content.key
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  if RUBY_ENGINE == 'opal'
         | 
| 16 | 
            +
                  # Hook into vdom diff/patch
         | 
| 17 | 
            +
                  %x{
         | 
| 18 | 
            +
                    def.type = 'Thunk';
         | 
| 19 | 
            +
                    def.render = function cached_render(prev) {
         | 
| 20 | 
            +
                      var self = this;
         | 
| 21 | 
            +
                      if(prev && prev.vnode && #{!@content.should_render?(`prev.content`)}) {
         | 
| 22 | 
            +
                        self.content = prev.content;
         | 
| 23 | 
            +
                        return prev.vnode;
         | 
| 24 | 
            +
                      } else {
         | 
| 25 | 
            +
                        var content = #{Component.sanitize_content(@content.render)};
         | 
| 26 | 
            +
                        while(content && content.type == 'Thunk' && content.render) {
         | 
| 27 | 
            +
                          content = #{Component.sanitize_content(`content.render(prev)`)};
         | 
| 28 | 
            +
                        }
         | 
| 29 | 
            +
                        return content;
         | 
| 30 | 
            +
                      }
         | 
| 31 | 
            +
                    };
         | 
| 32 | 
            +
                  }
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            require_relative 'bluesky/try.rb'
         | 
| 39 | 
            +
            require_relative 'bluesky/dom_helper.rb'
         | 
| 40 | 
            +
            require_relative 'bluesky/node_builder.rb'
         | 
| 41 | 
            +
            require_relative 'bluesky/pure_component.rb'
         | 
| 42 | 
            +
            require_relative 'bluesky/view_controller.rb'
         | 
| 43 | 
            +
            require_relative 'bluesky/navigation_controller.rb'
         | 
| 44 | 
            +
            require_relative 'bluesky/application.rb'
         | 
| 45 | 
            +
            require_relative 'bluesky/version'
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            unless RUBY_ENGINE == 'opal'
         | 
| 48 | 
            +
              begin
         | 
| 49 | 
            +
                require 'opal'
         | 
| 50 | 
            +
                Opal.append_path File.expand_path('..', __FILE__).untaint
         | 
| 51 | 
            +
              rescue LoadError
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            require 'bluesky'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class RootView < Bluesky::PureComponent
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def render
         | 
| 6 | 
            +
                nil
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            class RootViewController < Bluesky::ViewController
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def view
         | 
| 13 | 
            +
                RootView.new
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            class Application < Bluesky::Application
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              root_view_controller RootViewController
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            describe Application do
         | 
| 25 | 
            +
              context 'when run' do
         | 
| 26 | 
            +
                it 'calls render on the root_view_controller' do
         | 
| 27 | 
            +
                  logger = double()
         | 
| 28 | 
            +
                  application = Application.new
         | 
| 29 | 
            +
                  application.logger = logger
         | 
| 30 | 
            +
                  expect(logger).to receive(:render).with(application)
         | 
| 31 | 
            +
                  application.run
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            # This file was generated by the `rspec --init` command. Conventionally, all
         | 
| 2 | 
            +
            # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
         | 
| 3 | 
            +
            # The generated `.rspec` file contains `--require spec_helper` which will cause
         | 
| 4 | 
            +
            # this file to always be loaded, without a need to explicitly require it in any
         | 
| 5 | 
            +
            # files.
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Given that it is always loaded, you are encouraged to keep this file as
         | 
| 8 | 
            +
            # light-weight as possible. Requiring heavyweight dependencies from this file
         | 
| 9 | 
            +
            # will add to the boot time of your test suite on EVERY test run, even for an
         | 
| 10 | 
            +
            # individual file that may not need all of that loaded. Instead, consider making
         | 
| 11 | 
            +
            # a separate helper file that requires the additional dependencies and performs
         | 
| 12 | 
            +
            # the additional setup, and require it from the spec files that actually need
         | 
| 13 | 
            +
            # it.
         | 
| 14 | 
            +
            #
         | 
| 15 | 
            +
            # The `.rspec` file also contains a few flags that are not defaults but that
         | 
| 16 | 
            +
            # users commonly want.
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
            # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
         | 
| 19 | 
            +
            require 'bundler/setup'
         | 
| 20 | 
            +
            Bundler.setup
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            require 'clearwater'
         | 
| 23 | 
            +
            require 'bluesky'
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            RSpec.configure do |config|
         | 
| 26 | 
            +
              # rspec-expectations config goes here. You can use an alternate
         | 
| 27 | 
            +
              # assertion/expectation library such as wrong or the stdlib/minitest
         | 
| 28 | 
            +
              # assertions if you prefer.
         | 
| 29 | 
            +
              config.expect_with :rspec do |expectations|
         | 
| 30 | 
            +
                # This option will default to `true` in RSpec 4. It makes the `description`
         | 
| 31 | 
            +
                # and `failure_message` of custom matchers include text for helper methods
         | 
| 32 | 
            +
                # defined using `chain`, e.g.:
         | 
| 33 | 
            +
                #     be_bigger_than(2).and_smaller_than(4).description
         | 
| 34 | 
            +
                #     # => "be bigger than 2 and smaller than 4"
         | 
| 35 | 
            +
                # ...rather than:
         | 
| 36 | 
            +
                #     # => "be bigger than 2"
         | 
| 37 | 
            +
                expectations.include_chain_clauses_in_custom_matcher_descriptions = true
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              # rspec-mocks config goes here. You can use an alternate test double
         | 
| 41 | 
            +
              # library (such as bogus or mocha) by changing the `mock_with` option here.
         | 
| 42 | 
            +
              config.mock_with :rspec do |mocks|
         | 
| 43 | 
            +
                # Prevents you from mocking or stubbing a method that does not exist on
         | 
| 44 | 
            +
                # a real object. This is generally recommended, and will default to
         | 
| 45 | 
            +
                # `true` in RSpec 4.
         | 
| 46 | 
            +
                mocks.verify_partial_doubles = true
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
         | 
| 50 | 
            +
              # have no way to turn it off -- the option exists only for backwards
         | 
| 51 | 
            +
              # compatibility in RSpec 3). It causes shared context metadata to be
         | 
| 52 | 
            +
              # inherited by the metadata hash of host groups and examples, rather than
         | 
| 53 | 
            +
              # triggering implicit auto-inclusion in groups with matching metadata.
         | 
| 54 | 
            +
              config.shared_context_metadata_behavior = :apply_to_host_groups
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            # The settings below are suggested to provide a good initial experience
         | 
| 57 | 
            +
            # with RSpec, but feel free to customize to your heart's content.
         | 
| 58 | 
            +
            =begin
         | 
| 59 | 
            +
              # This allows you to limit a spec run to individual examples or groups
         | 
| 60 | 
            +
              # you care about by tagging them with `:focus` metadata. When nothing
         | 
| 61 | 
            +
              # is tagged with `:focus`, all examples get run. RSpec also provides
         | 
| 62 | 
            +
              # aliases for `it`, `describe`, and `context` that include `:focus`
         | 
| 63 | 
            +
              # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
         | 
| 64 | 
            +
              config.filter_run_when_matching :focus
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              # Allows RSpec to persist some state between runs in order to support
         | 
| 67 | 
            +
              # the `--only-failures` and `--next-failure` CLI options. We recommend
         | 
| 68 | 
            +
              # you configure your source control system to ignore this file.
         | 
| 69 | 
            +
              config.example_status_persistence_file_path = "spec/examples.txt"
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              # Limits the available syntax to the non-monkey patched syntax that is
         | 
| 72 | 
            +
              # recommended. For more details, see:
         | 
| 73 | 
            +
              #   - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
         | 
| 74 | 
            +
              #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
         | 
| 75 | 
            +
              #   - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
         | 
| 76 | 
            +
              config.disable_monkey_patching!
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              # This setting enables warnings. It's recommended, but in some cases may
         | 
| 79 | 
            +
              # be too noisy due to issues in dependencies.
         | 
| 80 | 
            +
              config.warnings = true
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              # Many RSpec users commonly either run the entire suite or an individual
         | 
| 83 | 
            +
              # file, and it's useful to allow more verbose output when running an
         | 
| 84 | 
            +
              # individual spec file.
         | 
| 85 | 
            +
              if config.files_to_run.one?
         | 
| 86 | 
            +
                # Use the documentation formatter for detailed output,
         | 
| 87 | 
            +
                # unless a formatter has already been configured
         | 
| 88 | 
            +
                # (e.g. via a command-line flag).
         | 
| 89 | 
            +
                config.default_formatter = 'doc'
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              # Print the 10 slowest examples and example groups at the
         | 
| 93 | 
            +
              # end of the spec run, to help surface which specs are running
         | 
| 94 | 
            +
              # particularly slow.
         | 
| 95 | 
            +
              config.profile_examples = 10
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              # Run specs in random order to surface order dependencies. If you find an
         | 
| 98 | 
            +
              # order dependency and want to debug it, you can fix the order by providing
         | 
| 99 | 
            +
              # the seed, which is printed after each run.
         | 
| 100 | 
            +
              #     --seed 1234
         | 
| 101 | 
            +
              config.order = :random
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              # Seed global randomization in this process using the `--seed` CLI option.
         | 
| 104 | 
            +
              # Setting this allows you to use `--seed` to deterministically reproduce
         | 
| 105 | 
            +
              # test failures related to randomization by passing the same `--seed` value
         | 
| 106 | 
            +
              # as the one that triggered the failure.
         | 
| 107 | 
            +
              Kernel.srand config.seed
         | 
| 108 | 
            +
            =end
         | 
| 109 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,127 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: bluesky
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - John Susi
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2016-12-24 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: opal
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0.10'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0.10'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: clearwater
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - '='
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: 1.0.0.rc4
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - '='
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: 1.0.0.rc4
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: opal-rspec
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rspec
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ">="
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '0'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - ">="
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '0'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - ">="
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - ">="
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0'
         | 
| 83 | 
            +
            description: An app framework for Clearwater
         | 
| 84 | 
            +
            email: john@susi.se
         | 
| 85 | 
            +
            executables:
         | 
| 86 | 
            +
            - bluesky
         | 
| 87 | 
            +
            extensions: []
         | 
| 88 | 
            +
            extra_rdoc_files: []
         | 
| 89 | 
            +
            files:
         | 
| 90 | 
            +
            - bin/bluesky
         | 
| 91 | 
            +
            - lib/bluesky.rb
         | 
| 92 | 
            +
            - lib/bluesky/application.rb
         | 
| 93 | 
            +
            - lib/bluesky/dom_helper.rb
         | 
| 94 | 
            +
            - lib/bluesky/navigation_controller.rb
         | 
| 95 | 
            +
            - lib/bluesky/pure_component.rb
         | 
| 96 | 
            +
            - lib/bluesky/try.rb
         | 
| 97 | 
            +
            - lib/bluesky/version.rb
         | 
| 98 | 
            +
            - lib/bluesky/view_controller.rb
         | 
| 99 | 
            +
            - spec/bluesky_spec.rb
         | 
| 100 | 
            +
            - spec/spec_helper.rb
         | 
| 101 | 
            +
            homepage: http://rubygems.org/gems/bluesky
         | 
| 102 | 
            +
            licenses:
         | 
| 103 | 
            +
            - MIT
         | 
| 104 | 
            +
            metadata: {}
         | 
| 105 | 
            +
            post_install_message: 
         | 
| 106 | 
            +
            rdoc_options: []
         | 
| 107 | 
            +
            require_paths:
         | 
| 108 | 
            +
            - "/Users/john/Projects/bluesky/lib"
         | 
| 109 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 110 | 
            +
              requirements:
         | 
| 111 | 
            +
              - - ">="
         | 
| 112 | 
            +
                - !ruby/object:Gem::Version
         | 
| 113 | 
            +
                  version: '0'
         | 
| 114 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 115 | 
            +
              requirements:
         | 
| 116 | 
            +
              - - ">="
         | 
| 117 | 
            +
                - !ruby/object:Gem::Version
         | 
| 118 | 
            +
                  version: '0'
         | 
| 119 | 
            +
            requirements: []
         | 
| 120 | 
            +
            rubyforge_project: 
         | 
| 121 | 
            +
            rubygems_version: 2.4.8
         | 
| 122 | 
            +
            signing_key: 
         | 
| 123 | 
            +
            specification_version: 4
         | 
| 124 | 
            +
            summary: An app framework for Clearwater
         | 
| 125 | 
            +
            test_files:
         | 
| 126 | 
            +
            - spec/spec_helper.rb
         | 
| 127 | 
            +
            - spec/bluesky_spec.rb
         |