goat 0.3.45 → 0.3.46
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.
- 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}
|