bluesky 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,3 @@
1
+ module Bluesky
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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