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 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