goat 0.3.45 → 0.3.46
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/state-srv +23 -11
- data/goat.gemspec +2 -2
- data/lib/goat.rb +96 -208
- data/lib/goat/common.rb +13 -4
- data/lib/goat/dom.rb +627 -0
- data/lib/goat/extn.rb +1 -1
- data/lib/goat/goat.js +61 -39
- data/lib/goat/state-srv.rb +47 -7
- metadata +5 -5
- data/lib/goat/html.rb +0 -301
data/bin/state-srv
CHANGED
@@ -223,6 +223,24 @@ module Goat
|
|
223
223
|
nil
|
224
224
|
end
|
225
225
|
|
226
|
+
def components_updated(msg)
|
227
|
+
ensure_keys(msg, %w{txn pgid updates})
|
228
|
+
|
229
|
+
pgid = msg['pgid']
|
230
|
+
txn = msg['txn']
|
231
|
+
updates = msg['updates'].map{|u| ComponentUpdate.from_hash(u)}
|
232
|
+
|
233
|
+
updates.map(&:added).flatten(1).each do |c|
|
234
|
+
@registry.add_component(pgid, c)
|
235
|
+
end
|
236
|
+
updates.map(&:removed).flatten.each{|c| @registry.delete_component(c)}
|
237
|
+
|
238
|
+
updates.each {|u| handle_component_update(txn, u)}
|
239
|
+
send_updates(pgid)
|
240
|
+
|
241
|
+
{'type' => 'update_ack', 'components' => updates.map{|u| u.skel.id}}
|
242
|
+
end
|
243
|
+
|
226
244
|
def live_components(msg)
|
227
245
|
ensure_keys(msg, %w{class spec})
|
228
246
|
|
@@ -239,7 +257,11 @@ module Goat
|
|
239
257
|
def fetch_component(msg)
|
240
258
|
ensure_keys(msg, %w{id})
|
241
259
|
|
242
|
-
@registry.find_id(msg['id'])
|
260
|
+
if c = @registry.find_id(msg['id'])
|
261
|
+
c.to_hash
|
262
|
+
else
|
263
|
+
raise "Couldn't find component #{msg['id']}"
|
264
|
+
end
|
243
265
|
end
|
244
266
|
|
245
267
|
def send_message(chsrv_id, type, msg)
|
@@ -307,16 +329,6 @@ module Goat
|
|
307
329
|
end
|
308
330
|
end
|
309
331
|
|
310
|
-
def components_updated(msg)
|
311
|
-
ensure_keys(msg, %w{txn pgid updates})
|
312
|
-
|
313
|
-
txn = msg['txn']
|
314
|
-
msg['updates'].each{|u| handle_component_update(txn, ComponentUpdate.from_hash(u))}
|
315
|
-
send_updates(msg['pgid'])
|
316
|
-
|
317
|
-
nil
|
318
|
-
end
|
319
|
-
|
320
332
|
def page_connected(msg)
|
321
333
|
ensure_keys(msg, %w{pgid chsrv version})
|
322
334
|
|
data/goat.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'goat'
|
3
|
-
s.version = '0.3.
|
3
|
+
s.version = '0.3.46'
|
4
4
|
s.summary = 'Pre-release beta version of Goat'
|
5
5
|
s.author = 'Patrick Collison'
|
6
6
|
s.email = 'patrick@collison.ie'
|
@@ -31,7 +31,7 @@ spec = Gem::Specification.new do |s|
|
|
31
31
|
lib/goat/dynamic.rb
|
32
32
|
lib/goat/extn.rb
|
33
33
|
lib/goat/goat.js
|
34
|
-
lib/goat/
|
34
|
+
lib/goat/dom.rb
|
35
35
|
lib/goat/js/component.js
|
36
36
|
lib/goat/mongo.rb
|
37
37
|
lib/goat/net-common.rb
|
data/lib/goat.rb
CHANGED
@@ -24,7 +24,7 @@ require 'goat/common'
|
|
24
24
|
require 'goat/net-common'
|
25
25
|
require 'goat/notifications'
|
26
26
|
require 'goat/extn'
|
27
|
-
require 'goat/
|
27
|
+
require 'goat/dom'
|
28
28
|
require 'goat/static'
|
29
29
|
require 'goat/autobind'
|
30
30
|
require 'goat/state-srv'
|
@@ -806,10 +806,7 @@ module Goat
|
|
806
806
|
|
807
807
|
def register_page(distiller)
|
808
808
|
live = distiller.all_components.select(&:live_enabled?)
|
809
|
-
pg_spec = live.map
|
810
|
-
c.pgid = @id
|
811
|
-
c.skel
|
812
|
-
end
|
809
|
+
pg_spec = live.map(&:skel)
|
813
810
|
|
814
811
|
StateSrvClient.register_page(@id, self.user, pg_spec)
|
815
812
|
end
|
@@ -822,7 +819,7 @@ module Goat
|
|
822
819
|
|
823
820
|
def inject_html_from_dom(canvas)
|
824
821
|
exp = nil
|
825
|
-
helper = ExpansionHelper.new
|
822
|
+
helper = ExpansionHelper.new(@id)
|
826
823
|
|
827
824
|
Dynamic.let(:expander => helper) do
|
828
825
|
exp = DOMTools.expanded_dom(self._dom)
|
@@ -856,11 +853,6 @@ module Goat
|
|
856
853
|
render_to_layout(canvas, layout)
|
857
854
|
end
|
858
855
|
|
859
|
-
#def render_component(c)
|
860
|
-
# canvas.components << c
|
861
|
-
# c.html
|
862
|
-
#end
|
863
|
-
|
864
856
|
def response
|
865
857
|
Rack::Response.new(self.html, 200, {})
|
866
858
|
end
|
@@ -898,183 +890,25 @@ module Goat
|
|
898
890
|
end
|
899
891
|
end
|
900
892
|
|
901
|
-
class
|
902
|
-
|
903
|
-
class DOMDiff
|
904
|
-
def self.diff(old, new, id)
|
905
|
-
self.new(old, new, id).diff
|
906
|
-
end
|
907
|
-
|
908
|
-
def initialize(old, new, id)
|
909
|
-
@old = old
|
910
|
-
@new = new
|
911
|
-
@id = id
|
912
|
-
@diffs = []
|
913
|
-
end
|
914
|
-
|
915
|
-
def diff
|
916
|
-
nested_application(minimized_changes(desc(@old, @new, @id)))
|
917
|
-
end
|
918
|
-
|
919
|
-
def nested_application(ch)
|
920
|
-
applied = Set.new
|
921
|
-
|
922
|
-
ch.sort_by{|x| x[3]}.each do |c|
|
923
|
-
if (c[0] == :rem || c[0] == :add) && !applied.include?(c)
|
924
|
-
if n = ch.detect{|x| x != c && x[2] == c[2] && x[3] >= c[3]}
|
925
|
-
n[3] += (c[0] == :rem) ? -1 : 1
|
926
|
-
end
|
927
|
-
end
|
928
|
-
|
929
|
-
applied << c
|
930
|
-
end
|
931
|
-
|
932
|
-
ch
|
933
|
-
end
|
934
|
-
|
935
|
-
def minimized_changes(ch)
|
936
|
-
new = []
|
937
|
-
merged = Set.new
|
938
|
-
ch.each do |c|
|
939
|
-
if c[0] == :rem
|
940
|
-
if n = ch.detect{|x| x[0] == :add && x[2] == c[2] && x[3] == c[3]}
|
941
|
-
new << [:rep, *n[1..3]]
|
942
|
-
merged << n
|
943
|
-
else
|
944
|
-
new << c
|
945
|
-
end
|
946
|
-
else
|
947
|
-
new << c
|
948
|
-
end
|
949
|
-
end
|
950
|
-
new.reject{|c| merged.include?(c)}
|
951
|
-
end
|
952
|
-
|
953
|
-
def added(new, par, pos=nil); [:add, new, par, pos]; end
|
954
|
-
def removed(old, par, pos=nil); [:rem, old, par, pos]; end
|
955
|
-
|
956
|
-
def dom_node?(node)
|
957
|
-
node.is_a?(Array) && node.first.is_a?(Symbol)
|
958
|
-
end
|
959
|
-
def tag(node); node[0]; end
|
960
|
-
def attrs(node); node[1] if node[1].is_a?(Hash); end
|
961
|
-
def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
|
962
|
-
def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
|
963
|
-
|
964
|
-
def localized_change(dold, dnew, par, changes)
|
965
|
-
old = dold.element
|
966
|
-
new = dnew.element
|
967
|
-
#$stderr.puts "changes: #{changes.inspect} / #{old.inspect} / #{par.inspect}"
|
968
|
-
if changes.all?{|ch| ch[2].nil?} && par
|
969
|
-
[added(new, par, dold.position), removed(old, par, dnew.position)]
|
970
|
-
else
|
971
|
-
changes
|
972
|
-
end
|
973
|
-
end
|
974
|
-
|
975
|
-
def is_replacement?(d1, d2)
|
976
|
-
d1 && d2 && (
|
977
|
-
(d1.action == '+' && d2.action == '-') ||
|
978
|
-
(d1.action == '-' && d2.action == '+'))
|
979
|
-
end
|
980
|
-
|
981
|
-
def old_and_new(d1, d2)
|
982
|
-
d1.action == '+' ? [d2, d1] : [d1, d2]
|
983
|
-
end
|
984
|
-
|
985
|
-
def array_desc(old, new, par)
|
986
|
-
#$stderr.puts "array_desc #{old.inspect} #{new.inspect} #{par.inspect}"
|
987
|
-
|
988
|
-
chgs = Diff::LCS.diff(old, new).map do |diff|
|
989
|
-
#$stderr.puts "diff: #{diff.inspect}"
|
990
|
-
if is_replacement?(diff[0], diff[1])
|
991
|
-
dold, dnew = old_and_new(diff[0], diff[1])
|
992
|
-
old = dold.element
|
993
|
-
new = dnew.element
|
994
|
-
if dom_node?(old) && dom_node?(new) && tag(old) == tag(new) && compare_attrs(old, new)
|
995
|
-
localized_change(dold, dnew, par, desc(body(old), body(new), domid(old)))
|
996
|
-
elsif old.is_a?(Array) && new.is_a?(Array) #&& old.all?{|x| !dom_node?(x)} && new.all?{|x| !dom_node?(x)}
|
997
|
-
array_desc(old, new, par)
|
998
|
-
else
|
999
|
-
[added(new, par, diff[1].position), removed(old, par, diff[0].position)]
|
1000
|
-
end
|
1001
|
-
else
|
1002
|
-
if diff.size == 1
|
1003
|
-
diff = diff[0]
|
1004
|
-
if diff.action == '+'
|
1005
|
-
[added(diff.element, par, diff.position)]
|
1006
|
-
elsif diff.action == '-'
|
1007
|
-
[removed(diff.element, par, diff.position)]
|
1008
|
-
else
|
1009
|
-
raise "Don't understand diff in a bad way"
|
1010
|
-
end
|
1011
|
-
else
|
1012
|
-
$stderr.puts "Don't understand diff"
|
1013
|
-
#$stderr.puts "Diff failed"
|
1014
|
-
raise JustRerenderError
|
1015
|
-
end
|
1016
|
-
end
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
chgs.flatten(1)
|
1020
|
-
end
|
1021
|
-
|
1022
|
-
def compare_attrs(a, b)
|
1023
|
-
if a.is_a?(Hash) && b.is_a?(Hash)
|
1024
|
-
a_, b_ = a.clone, b.clone
|
1025
|
-
a.select{|k,v| a_.delete(k) if v =~ /^dom_/}
|
1026
|
-
b.select{|k,v| b_.delete(k) if v =~ /^dom_/}
|
1027
|
-
a_ == b_
|
1028
|
-
else
|
1029
|
-
false
|
1030
|
-
end
|
1031
|
-
end
|
1032
|
-
|
1033
|
-
def node_desc(old, new, par)
|
1034
|
-
#$stderr.puts "node_desc #{old.inspect} #{new.inspect} #{par.inspect}"
|
893
|
+
class ExpansionHelper
|
894
|
+
attr_reader :components
|
1035
895
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
bold = body(old)
|
1040
|
-
bnew = body(new)
|
1041
|
-
if bold && bnew
|
1042
|
-
desc(bold, bnew, domid(old))
|
1043
|
-
elsif bold && !bnew
|
1044
|
-
[removed(bold, domid(old))]
|
1045
|
-
else
|
1046
|
-
[added(bnew, domid(old))]
|
1047
|
-
end
|
1048
|
-
end
|
896
|
+
def initialize(pgid)
|
897
|
+
@components = {}
|
898
|
+
@pgid = pgid
|
1049
899
|
end
|
1050
900
|
|
1051
|
-
def
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
if old.class != new.class
|
1056
|
-
[added(new, par), removed(old, par)]
|
1057
|
-
elsif old.is_a?(Array)
|
1058
|
-
dom_node?(old) ? node_desc(old, new, domid(old)) : array_desc(old, new, par)
|
1059
|
-
elsif old.is_a?(String)
|
1060
|
-
if old != new
|
1061
|
-
[added(new, par), removed(old, par)]
|
1062
|
-
end
|
1063
|
-
else
|
1064
|
-
raise TypeError.new("Unknown object in the DOM: #{old.class} #{new.class}")
|
1065
|
-
end
|
901
|
+
def component_used(c)
|
902
|
+
@components[c.id] = c
|
903
|
+
c.pgid = @pgid
|
1066
904
|
end
|
1067
|
-
end
|
1068
905
|
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
def initialize
|
1073
|
-
@components = Set.new
|
906
|
+
def component(id)
|
907
|
+
@components[id]
|
1074
908
|
end
|
1075
909
|
|
1076
|
-
def
|
1077
|
-
@components
|
910
|
+
def components
|
911
|
+
@components.values
|
1078
912
|
end
|
1079
913
|
end
|
1080
914
|
|
@@ -1107,6 +941,11 @@ module Goat
|
|
1107
941
|
end
|
1108
942
|
|
1109
943
|
def self.dispatch_updates(txn, pgid, ups)
|
944
|
+
pgups = ups.map{|u| u.skel.pgid}.uniq
|
945
|
+
if pgups.size > 1
|
946
|
+
raise "Got updates for different pages: #{pgups.inspect}"
|
947
|
+
end
|
948
|
+
|
1110
949
|
StateSrvClient.components_updated(
|
1111
950
|
txn,
|
1112
951
|
pgid,
|
@@ -1150,7 +989,8 @@ module Goat
|
|
1150
989
|
def self.rerender_and_update_inner(to_process)
|
1151
990
|
# send updates as we calculate them; don't wait to compute everything
|
1152
991
|
unless to_process.empty?
|
1153
|
-
|
992
|
+
skel = to_process.shift
|
993
|
+
rerender(skel)
|
1154
994
|
unless to_process.empty?
|
1155
995
|
EM.next_tick { rerender_and_update_inner(to_process) }
|
1156
996
|
end
|
@@ -1179,7 +1019,7 @@ module Goat
|
|
1179
1019
|
def rerender
|
1180
1020
|
Profile.in(:rerender)
|
1181
1021
|
|
1182
|
-
helper = ExpansionHelper.new
|
1022
|
+
helper = ExpansionHelper.new(@pgid)
|
1183
1023
|
|
1184
1024
|
Profile.in(:expansion)
|
1185
1025
|
Dynamic.let(:expander => helper) do
|
@@ -1188,19 +1028,21 @@ module Goat
|
|
1188
1028
|
Profile.out(:expansion)
|
1189
1029
|
|
1190
1030
|
Profile.in(:diff)
|
1191
|
-
diff = DOMDiff.
|
1031
|
+
diff = DOMTools::DOMDiff.dom_diff(@old_dom, @expanded_dom, @id)
|
1192
1032
|
Profile.out(:diff)
|
1193
1033
|
|
1034
|
+
Goat.logd "#{self.class}/#{self.id} diff: " + diff.inspect
|
1035
|
+
|
1194
1036
|
if diff.size == 0
|
1195
1037
|
# pass
|
1196
|
-
|
1197
|
-
|
1198
|
-
rerender_partially(diff)
|
1038
|
+
elsif diff.size <= 3
|
1039
|
+
rerender_partially(helper, diff)
|
1199
1040
|
else
|
1200
|
-
rerender_fully(helper.components)
|
1041
|
+
rerender_fully(helper, helper.components)
|
1201
1042
|
end
|
1202
|
-
rescue
|
1203
|
-
|
1043
|
+
rescue Goat::DOMTools::PartialUpdateFailed
|
1044
|
+
Goat.logw "Partial update failed; falling back on a full rerender"
|
1045
|
+
rerender_fully(helper, helper.components)
|
1204
1046
|
ensure
|
1205
1047
|
Profile.out(:rerender)
|
1206
1048
|
end
|
@@ -1231,16 +1073,48 @@ module Goat
|
|
1231
1073
|
@live_spec = skel.live_spec
|
1232
1074
|
end
|
1233
1075
|
|
1234
|
-
def rerender_partially(diff)
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1076
|
+
def rerender_partially(expander, diff)
|
1077
|
+
updates = []
|
1078
|
+
|
1079
|
+
added, removed = [], []
|
1080
|
+
|
1081
|
+
diff.each do |d|
|
1082
|
+
type, par, pos, tree, id = d
|
1083
|
+
cs = []
|
1084
|
+
u = {
|
1085
|
+
'type' => type.to_s,
|
1086
|
+
'parent' => par,
|
1087
|
+
'position' => pos
|
1088
|
+
}
|
1089
|
+
|
1090
|
+
if type == :add
|
1091
|
+
added += DOMTools.dom_components(tree).map{|cid| expander.component(cid)}
|
1092
|
+
|
1093
|
+
distiller = DOMDistiller.new(tree, added + [self])
|
1094
|
+
u['html'] = dom_html(tree)
|
1095
|
+
u['js'] = distiller.script
|
1096
|
+
u['css'] = distiller.style
|
1097
|
+
elsif type == :rem
|
1098
|
+
b = DOMTools.body(tree).first
|
1099
|
+
u['tag'] = DOMTools.tag(b).to_s if DOMTools.dom_node?(b)
|
1100
|
+
u['id'] = DOMTools.attrs(tree) ? DOMTools.attrs(tree)[:id] : nil
|
1101
|
+
|
1102
|
+
removed += DOMTools.dom_components(tree) # just want the raw IDs
|
1103
|
+
else
|
1104
|
+
raise "Bad diff: #{d.inspect}"
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
raise "Bad position" unless u['position'].kind_of?(Integer)
|
1108
|
+
|
1109
|
+
updates << u
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
UpdateDispatcher.component_updated(
|
1113
|
+
@pgid,
|
1114
|
+
ComponentUpdate.new(self.skel, updates, added.map(&:skel), removed))
|
1241
1115
|
end
|
1242
1116
|
|
1243
|
-
def rerender_fully(cs)
|
1117
|
+
def rerender_fully(expander, cs)
|
1244
1118
|
Profile.in(:distillation)
|
1245
1119
|
distiller = DOMDistiller.new(@expanded_dom, cs + [self])
|
1246
1120
|
js = distiller.script
|
@@ -1249,16 +1123,19 @@ module Goat
|
|
1249
1123
|
|
1250
1124
|
Profile.in(:update_send)
|
1251
1125
|
|
1126
|
+
added = (DOMTools.dom_components(@expanded_dom) - DOMTools.dom_components(@old_dom)).map{|cid| expander.component(cid)}
|
1127
|
+
removed = DOMTools.dom_components(@old_dom) - DOMTools.dom_components(@expanded_dom)
|
1128
|
+
|
1252
1129
|
UpdateDispatcher.component_updated(
|
1253
1130
|
@pgid,
|
1254
1131
|
ComponentUpdate.new(
|
1255
1132
|
self.skel,
|
1256
1133
|
[{'type' => 'rep',
|
1257
|
-
'html' => dom_html(@expanded_dom),
|
1134
|
+
'html' => dom_html(component(@expanded_dom)),
|
1258
1135
|
'js' => js,
|
1259
1136
|
'css' => css,
|
1260
1137
|
'parent' => @id,
|
1261
|
-
'position' => nil}]))
|
1138
|
+
'position' => nil}], added.map(&:skel), removed))
|
1262
1139
|
|
1263
1140
|
Profile.out(:update_send)
|
1264
1141
|
end
|
@@ -1354,13 +1231,28 @@ module Goat
|
|
1354
1231
|
DOMTools.inject_prefixes(self.id, dom)
|
1355
1232
|
end
|
1356
1233
|
|
1357
|
-
def component(
|
1234
|
+
def component(tree)
|
1235
|
+
# if the first element of the tree is a tbody tag, we don't enclose it in
|
1236
|
+
# a div (as we usually do for almost all components), since we'll likely
|
1237
|
+
# end up in an (illegal) <table><div><tbody> situation. Instead, we use
|
1238
|
+
# the tbody itself as the container. In doing so, we clobber existing
|
1239
|
+
# attributes on the tbody: if you have a top-level tbody tag in a Component,
|
1240
|
+
# we're stealing it for our own uses.
|
1241
|
+
|
1358
1242
|
attrs = {:id => id, :class => component_name_hierarchy.join(' ')}
|
1359
1243
|
|
1360
|
-
if
|
1361
|
-
[:tbody, attrs, body
|
1244
|
+
if DOMTools.car_tag(tree) == :tbody
|
1245
|
+
[:tbody, attrs, DOMTools.body(DOMTools.normalized_tags(tree).first)]
|
1362
1246
|
else
|
1363
|
-
[:div, attrs,
|
1247
|
+
[:div, attrs, tree]
|
1248
|
+
end
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
def unencapsulated(tree)
|
1252
|
+
if DOMTools.car_tag(tree) == :tbody
|
1253
|
+
DOMTools.body(DOMTools.normalized_tags(tree).first)
|
1254
|
+
else
|
1255
|
+
tree
|
1364
1256
|
end
|
1365
1257
|
end
|
1366
1258
|
|
@@ -1405,10 +1297,6 @@ module Goat
|
|
1405
1297
|
self.class.scope_css(self.__css, @id, '#')
|
1406
1298
|
end
|
1407
1299
|
|
1408
|
-
def model_changed(item, notif)
|
1409
|
-
render
|
1410
|
-
end
|
1411
|
-
|
1412
1300
|
def register_callback(callback)
|
1413
1301
|
# if @callbacks.values.include?(callback)
|
1414
1302
|
# @callbacks.to_a.detect{|k, v| k if v == callback}
|