move list/map functionality into wrapper file

pull/1/head
ansuz 9 years ago
parent 8885658431
commit c1bca09cce

@ -0,0 +1,184 @@
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/crypto.js',
'/common/realtime-input.js',
'/json/listmap.js',
'/common/json-ot.js',
'json.sortify',
'/bower_components/textpatcher/TextPatcher.amd.js',
'/bower_components/jquery/dist/jquery.min.js',
], function (Config, Crypto, Realtime, ListMap, JsonOT, Sortify, TextPatcher) {
var api = {};
api.ListMap = ListMap;
var key;
var channel = '';
var hash = false;
if (!/#/.test(window.location.href)) {
key = Crypto.genKey();
} else {
hash = window.location.hash.slice(1);
channel = hash.slice(0,32);
key = hash.slice(32);
}
var module = window.APP = {
TextPatcher: TextPatcher,
Sortify: Sortify,
};
var $repl = $('[name="repl"]');
var Map = module.Map = {};
var initializing = true;
var config = module.config = {
initialState: Sortify(Map) || '{}',
websocketURL: Config.websocketURL,
userName: Crypto.rand64(8),
channel: channel,
cryptKey: key,
crypto: Crypto,
transformFunction: JsonOT.validate
};
var setEditable = module.setEditable = function (bool) {
/* (dis)allow editing */
[$repl].forEach(function ($el) {
$el.attr('disabled', !bool);
});
};
setEditable(false);
var onInit = config.onInit = function (info) {
var realtime = module.realtime = info.realtime;
window.location.hash = info.channel + key;
// create your patcher
module.patchText = TextPatcher.create({
realtime: realtime,
logging: true,
});
};
/* we still need to pass in the function that bumps to ListMap.
this is no good. FIXME */
var onLocal = config.onLocal = ListMap.onLocal = module.bump = function () {
if (initializing) { return; }
var strung = Sortify(Map);
console.log(strung);
/* serialize local changes */
module.patchText(strung);
if (module.realtime.getUserDoc !== strung) {
module.patchText(strung);
}
};
var onRemote = config.onRemote = function (info) {
if (initializing) { return; }
/* integrate remote changes */
var proxy = module.proxy;
var userDoc = module.realtime.getUserDoc();
var parsed = JSON.parse(userDoc);
ListMap.update(proxy, parsed);
};
var onReady = config.onReady = function (info) {
console.log("READY");
var userDoc = module.realtime.getUserDoc();
var parsed = JSON.parse(userDoc);
Object.keys(parsed).forEach(function (key) {
module.proxy[key] = ListMap.recursiveProxies(parsed[key]);
});
setEditable(true);
initializing = false;
};
var onAbort = config.onAbort = function (info) {
window.alert("Network Connection Lost");
};
var rt = Realtime.start(config);
var proxy = module.proxy = ListMap.makeProxy(Map);
$repl.on('keyup', function (e) {
if (e.which === 13) {
var value = $repl.val();
if (!value.trim()) { return; }
console.log("evaluating `%s`", value);
var x = proxy;
console.log('> ', eval(value)); // jshint ignore:line
console.log();
$repl.val('');
}
});
var create = api.create = function (config) {
/* validate your inputs before proceeding */
if (['object', 'array'].indexOf(ListMap.type(config.data))) {
throw new Error('unsupported datatype');
}
var Config = {
initialState: Sortify(config.data),
transformFunction: JsonOT.validate,
userName: userName,
channel: channel,
cryptKey: cryptKey,
crypto: crypto,
};
var rt;
var onInit = Config.onInit = function (info) {
// onInit
config.onInit(info);
};
var onReady = Config.onReady = function (info) {
// onReady
config.onReady(info);
};
var onLocal = Config.onLocal = function () {
// onLocal
config.onLocal();
};
var onRemote = Config.onRemote = function (info) {
// onRemote
config.onRemote(info);
};
var onAbort = Config.onAbort = function (info) {
// onAbort
config.onAbort(info);
};
rt =Realtime.start(Config);
var proxy = rt.proxy = ListMap.makeProxy(data);
return rt;
};
return api;
});

@ -1,38 +1,94 @@
define(function () {
var compare = {};
define([
'/bower_components/proxy-polyfill/proxy.min.js', // https://github.com/GoogleChrome/proxy-polyfill
],function () {
var Proxy = window.Proxy;
var isArray = compare.isArray = function (obj) {
return Object.prototype.toString.call(obj)==='[object Array]'
var ListMap = {};
var isArray = ListMap.isArray = function (obj) {
return Object.prototype.toString.call(obj)==='[object Array]';
};
/* Arrays and nulls both register as 'object' when using native typeof
we need to distinguish them as their own types, so use this instead. */
var type = ListMap.type = function (dat) {
return dat === null? 'null': isArray(dat)?'array': typeof(dat);
};
var handlers = ListMap.handlers = {
get: function (obj, prop) {
// FIXME magic?
if (prop === 'length' && typeof(obj.length) === 'number') { return obj.length; }
return obj[prop];
},
set: function (obj, prop, value) {
if (prop === 'on') {
throw new Error("'on' is a reserved attribute name for realtime lists and maps");
}
if (obj[prop] === value) { return value; }
var t_value = ListMap.type(value);
if (['array', 'object'].indexOf(t_value) !== -1) {
console.log("Constructing new proxy for value with type [%s]", t_value);
var proxy = obj[prop] = ListMap.makeProxy(value);
} else {
console.log("Setting [%s] to [%s]", prop, value);
obj[prop] = value;
}
// FIXME this is NO GOOD
ListMap.onLocal();
return obj[prop];
}
};
var makeProxy = ListMap.makeProxy = function (obj) {
return new Proxy(obj, handlers);
};
var type = compare.type = function (dat) {
return dat === null?
'null':
isArray(dat)?'array': typeof(dat);
var recursiveProxies = ListMap.recursiveProxies = function (obj) {
var t_obj = type(obj);
var proxy;
switch (t_obj) {
case 'object':
proxy = makeProxy({});
ListMap.objects(proxy, obj, makeProxy, []);
return proxy;
case 'array':
proxy = makeProxy([]);
ListMap.arrays(proxy, obj, makeProxy, []);
return proxy;
default:
return obj;
}
};
/* compare objects A and B, where A is the _older_ of the two */
compare.objects = function (A, B, f, path) {
/* ListMap objects A and B, where A is the _older_ of the two */
ListMap.objects = function (A, B, f, path) {
var Akeys = Object.keys(A);
var Bkeys = Object.keys(B);
console.log("inspecting path [%s]", path.join(','));
//console.log("inspecting path [%s]", path.join(','));
/* iterating over the keys in B will tell you if a new key exists
it will not tell you if a key has been removed.
to accomplish that you will need to iterate over A's keys */
Bkeys.forEach(function (b) {
console.log(b);
//console.log(b);
var t_b = type(B[b]);
if (Akeys.indexOf(b) === -1) {
// there was an insertion
console.log("Inserting new key: [%s]", b);
var t_b = type(B[b]);
switch (t_b) {
case 'undefined':
// umm. this should never happen?
throw new Error("undefined type has key. this shouldn't happen?");
break;
//break;
case 'array':
console.log('construct list');
A[b] = f(B[b]);
@ -48,7 +104,6 @@ define(function () {
} else {
// the key already existed
var t_a = type(A[b]);
var t_b = type(B[b]);
if (t_a !== t_b) {
// its type changed!
@ -86,10 +141,10 @@ define(function () {
nextPath.push(b);
if (t_a === 'object') {
// it's an object
compare.objects(A[b], B[b], f, nextPath);
ListMap.objects(A[b], B[b], f, nextPath);
} else {
// it's an array
compare.arrays(A[b], B[b], f, nextPath);
ListMap.arrays(A[b], B[b], f, nextPath);
}
}
}
@ -104,11 +159,11 @@ define(function () {
});
};
compare.arrays = function (A, B, f, path) {
ListMap.arrays = function (A, B, f, path) {
var l_A = A.length;
var l_B = B.length;
// TODO do things with the path
// TODO do things with the path (callbacks)
if (l_A !== l_B) {
// B is longer than Aj
@ -144,10 +199,10 @@ define(function () {
switch (t_b) {
case 'object':
compare.objects(A[i], b, f, nextPath);
ListMap.objects(A[i], b, f, nextPath);
break;
case 'array':
compare.arrays(A[i], b, f, nextPath);
ListMap.arrays(A[i], b, f, nextPath);
break;
default:
A[i] = b;
@ -182,10 +237,10 @@ define(function () {
// same type
switch (t_b) {
case 'object':
compare.objects(A[i], B[i], f, nextPath);
ListMap.objects(A[i], B[i], f, nextPath);
break;
case 'array':
compare.arrays(A[i], B[i], f, nextPath);
ListMap.arrays(A[i], B[i], f, nextPath);
break;
default:
A[i] = B[i];
@ -196,5 +251,29 @@ define(function () {
}
};
return compare;
var update = ListMap.update = function (A, B) {
var t_A = type(A);
var t_B = type(B);
if (t_A !== t_B) {
throw new Error("Proxy updates can't result in type changes");
}
switch (t_B) {
case 'array':
// idk
break;
case 'object':
ListMap.objects(A, B, function (obj) {
console.log("constructing new proxy for type [%s]", type(obj));
return makeProxy(obj);
}, []);
break;
default:
throw new Error("unsupported realtime datatype");
}
};
return ListMap;
});

@ -1,204 +1,12 @@
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/realtime-input.js',
'/common/crypto.js',
'/bower_components/textpatcher/TextPatcher.amd.js',
'json.sortify',
'/common/json-ot.js',
'/json/compare.js',
'/bower_components/proxy-polyfill/proxy.min.js', // https://github.com/GoogleChrome/proxy-polyfill
'/bower_components/jquery/dist/jquery.min.js',
'/customize/pad.js'
], function (Config, Realtime, Crypto, TextPatcher, Sortify, JsonOT, Compare) {
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy#A_complete_traps_list_example
// https://github.com/xwiki-labs/RealtimeJSON
// https://github.com/xwiki-labs/ChainJSON
'/json/api.js',
//'/customize/pad.js'
], function (RtListMap) {
var $ = window.jQuery;
var Proxy = window.Proxy;
var key;
var channel = '';
var hash = false;
if (!/#/.test(window.location.href)) {
key = Crypto.genKey();
} else {
hash = window.location.hash.slice(1);
channel = hash.slice(0,32);
key = hash.slice(32);
}
var config = {
var module = window.APP = {
TextPatcher: TextPatcher,
Sortify: Sortify,
};
var $repl = $('[name="repl"]');
var Map = module.Map = {};
var initializing = true;
var config = module.config = {
initialState: Sortify(Map) || '{}',
websocketURL: Config.websocketURL,
userName: Crypto.rand64(8),
channel: channel,
cryptKey: key,
crypto: Crypto,
transformFunction: JsonOT.validate
};
var setEditable = module.setEditable = function (bool) {
/* (dis)allow editing */
[$repl].forEach(function ($el) {
$el.attr('disabled', !bool);
});
};
setEditable(false);
var onInit = config.onInit = function (info) {
var realtime = module.realtime = info.realtime;
window.location.hash = info.channel + key;
// create your patcher
module.patchText = TextPatcher.create({
realtime: realtime,
logging: true,
});
};
var onLocal = config.onLocal = module.bump = function () {
if (initializing) { return; }
var strung = Sortify(Map);
console.log(strung);
/* serialize local changes */
module.patchText(strung);
if (module.realtime.getUserDoc !== strung) {
module.patchText(strung);
}
};
var onRemote = config.onRemote = function (info) {
if (initializing) { return; }
/* integrate remote changes */
var proxy = module.proxy;
var userDoc = module.realtime.getUserDoc();
var parsed = JSON.parse(userDoc);
if (Compare.isArray(parsed)) {
// what's different about arrays?
} else if (Compare.type(parsed) === 'object') { /*
don't use native typeof because 'null' is an object, but you can't
proxy it, so you need to distinguish */
Compare.objects(Map, parsed, function (obj) {
console.log("constructing new proxy for type [%s]", Compare.type(obj));
return module.makeProxy(obj);
}, []);
} else {
throw new Error("unsupported realtime datatype");
}
};
var onReady = config.onReady = function (info) {
console.log("READY");
var userDoc = module.realtime.getUserDoc();
var parsed = JSON.parse(userDoc);
//Compare.objects(module.proxy, parsed, module.makeProxy, []);
Object.keys(parsed).forEach(function (key) {
Map[key] = module.recursiveProxies(parsed[key]);
});
setEditable(true);
initializing = false;
};
var onAbort = config.onAbort = function (info) {
window.alert("Network Connection Lost");
};
var rt = Realtime.start(config);
var handler = {
get: function (obj, prop) {
// FIXME magic?
if (prop === 'length' && typeof(obj.length) === 'number') {
return obj.length;
}
//console.log("Getting [%s]", prop);
return obj[prop];
},
set: function (obj, prop, value) {
if (prop === 'on') {
throw new Error("'on' is a reserved attribute name for realtime lists and maps");
}
if (obj[prop] === value) { return value; }
var t_value = Compare.type(value);
if (['array', 'object'].indexOf(t_value) !== -1) {
console.log("Constructing new proxy for value with type [%s]", t_value);
var proxy = obj[prop] = module.makeProxy(value);
//onLocal();
//return proxy;
} else {
console.log("Setting [%s] to [%s]", prop, value);
obj[prop] = value;
}
onLocal();
return obj[prop];
}
};
var makeProxy = module.makeProxy = function (obj) {
return new Proxy(obj, handler);
};
var recursiveProxies = module.recursiveProxies = function (obj) {
var t_obj = Compare.type(obj);
var proxy;
switch (t_obj) {
case 'object':
proxy = makeProxy({});
Compare.objects(proxy, obj, makeProxy, []);
return proxy;
case 'array':
proxy = makeProxy([]);
Compare.arrays(proxy, obj, makeProxy, []);
return proxy;
default:
return obj;
}
};
var proxy = module.proxy = makeProxy(Map);
$repl.on('keyup', function (e) {
if (e.which === 13) {
var value = $repl.val();
if (!value.trim()) { return; }
console.log("evaluating `%s`", value);
var x = proxy;
console.log('> ', eval(value)); // jshint ignore:line
//console.log(Sortify(proxy));
console.log();
$repl.val('');
}
});
// RtListMap.create(config);
});

Loading…
Cancel
Save