diff --git a/www/json/api.js b/www/json/api.js index ae01cf289..20ace5914 100644 --- a/www/json/api.js +++ b/www/json/api.js @@ -13,170 +13,92 @@ define([ 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) { + var create = api.create = function (cfg) { /* validate your inputs before proceeding */ - if (['object', 'array'].indexOf(ListMap.type(config.data))) { + if (['object', 'array'].indexOf(ListMap.type(cfg.data))) { throw new Error('unsupported datatype'); } - var Config = { - initialState: Sortify(config.data), + var config = { + initialState: Sortify(cfg.data), transformFunction: JsonOT.validate, - userName: userName, - channel: channel, - cryptKey: cryptKey, - crypto: crypto, + userName: Crypto.rand64(8), + channel: cfg.channel, + cryptKey: cfg.cryptKey, + crypto: Crypto, + websocketURL: Config.websocketURL, }; var rt; + var proxy = ListMap.makeProxy(cfg.data); + var realtime; + + var onInit = config.onInit = function (info) { + realtime = info.realtime; + // create your patcher + realtime.patchText = TextPatcher.create({ + realtime: realtime, + logging: config.logging || false, + }); - var onInit = Config.onInit = function (info) { // onInit - config.onInit(info); + cfg.onInit(info); }; - var onReady = Config.onReady = function (info) { + var onReady = config.onReady = function (info) { + var userDoc = realtime.getUserDoc(); + var parsed = JSON.parse(userDoc); + + // update your proxy to the state of the userDoc + Object.keys(parsed).forEach(function (key) { + proxy[key] = ListMap.recursiveProxies(parsed[key]); + }); + // onReady - config.onReady(info); + cfg.onReady(info); }; - var onLocal = Config.onLocal = function () { + // FIXME + var onLocal = config.onLocal = ListMap.onLocal = function () { + var strung = Sortify(proxy); + + realtime.patchText(strung); + + // try harder + if (realtime.getUserDoc() !== strung) { + realtime.patchText(strung); + } + // onLocal - config.onLocal(); + if (cfg.onLocal) { + cfg.onLocal(); + } + + // TODO actually emit 'change' events, or something like them }; - var onRemote = Config.onRemote = function (info) { + var onRemote = config.onRemote = function (info) { + var userDoc = realtime.getUserDoc(); + var parsed = JSON.parse(userDoc); + + ListMap.update(proxy, parsed); + // onRemote - config.onRemote(info); + if (cfg.onRemote) { + cfg.onRemote(info); + } }; - var onAbort = Config.onAbort = function (info) { + var onAbort = config.onAbort = function (info) { // onAbort - config.onAbort(info); + cfg.onAbort(info); }; - rt =Realtime.start(Config); - var proxy = rt.proxy = ListMap.makeProxy(data); + rt = Realtime.start(config); + rt.proxy = proxy; + rt.realtime = realtime; return rt; }; diff --git a/www/json/listmap.js b/www/json/listmap.js index c453a93d2..998469931 100644 --- a/www/json/listmap.js +++ b/www/json/listmap.js @@ -15,6 +15,35 @@ define([ return dat === null? 'null': isArray(dat)?'array': typeof(dat); }; + var makeHandlers = function (cb) { + return { + 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; + } + + cb(); + return obj[prop]; + }, + }; + }; + var handlers = ListMap.handlers = { get: function (obj, prop) { // FIXME magic? @@ -43,8 +72,10 @@ define([ } }; - var makeProxy = ListMap.makeProxy = function (obj) { - return new Proxy(obj, handlers); + var makeProxy = ListMap.makeProxy = function (obj, local) { + local = local || ListMap.onLocal; + + return new Proxy(obj, handlers); //makeHandlers(ListMap.onLocal)); }; var recursiveProxies = ListMap.recursiveProxies = function (obj) { @@ -66,6 +97,12 @@ define([ } }; + var onChange = function (path, key) { + var P = path.slice(0); + P.push(key); + console.log('change at path [%s]', P.join(',')); + }; + /* 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); @@ -83,6 +120,7 @@ define([ if (Akeys.indexOf(b) === -1) { // there was an insertion console.log("Inserting new key: [%s]", b); + onChange(path, b); switch (t_b) { case 'undefined': @@ -90,11 +128,11 @@ define([ throw new Error("undefined type has key. this shouldn't happen?"); //break; case 'array': - console.log('construct list'); + //console.log('construct list'); A[b] = f(B[b]); break; case 'object': - console.log('construct map'); + //console.log('construct map'); A[b] = f(B[b]); break; default: @@ -113,12 +151,12 @@ define([ delete A[b]; break; case 'array': - console.log('construct list'); + //console.log('construct list'); A[b] = f(B[b]); // make a new proxy break; case 'object': - console.log('construct map'); + //console.log('construct map'); A[b] = f(B[b]); // make a new proxy break; @@ -133,6 +171,7 @@ define([ if (['array', 'object'].indexOf(t_a) === -1) { // we can do deep equality... if (A[b] !== B[b]) { + onChange(path, b); console.log("changed values from [%s] to [%s]", A[b], B[b]); A[b] = B[b]; } @@ -152,6 +191,7 @@ define([ }); Akeys.forEach(function (a) { if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') { + onChange(path, a); console.log("Deleting [%s]", a); // the key was deleted! delete A[a]; @@ -205,6 +245,7 @@ define([ ListMap.arrays(A[i], b, f, nextPath); break; default: + onChange(path, i); A[i] = b; break; } @@ -262,11 +303,14 @@ define([ switch (t_B) { case 'array': + ListMap.arrays(A, B, function (obj) { + return makeProxy(obj); + }); // idk break; case 'object': ListMap.objects(A, B, function (obj) { - console.log("constructing new proxy for type [%s]", type(obj)); + //console.log("constructing new proxy for type [%s]", type(obj)); return makeProxy(obj); }, []); break; diff --git a/www/json/main.js b/www/json/main.js index 8ae03c393..5e96cea5e 100644 --- a/www/json/main.js +++ b/www/json/main.js @@ -1,12 +1,74 @@ define([ '/json/api.js', + '/common/crypto.js', //'/customize/pad.js' -], function (RtListMap) { +], function (RtListMap, Crypto) { var $ = window.jQuery; + 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 = { + channel: channel, + cryptKey: key, + data: {}, + }; + + var module = window.APP = {}; + + var $repl = $('[name="repl"]'); + + var setEditable = module.setEditable = function (bool) { + [$repl].forEach(function ($el) { + $el.attr('disabled', !bool); + }); + }; + + var initializing = true; + + // TODO replace with `proxy.on('init'` ? + // or just remove? + var onInit = config.onInit = function (info) { + console.log("initializing!"); + window.location.hash = info.channel + key; + }; + + // TODO replace with `proxy.on('ready'` ? + var onReady = config.onReady = function (info) { + setEditable(true); + }; + + setEditable(false); + // TODO replace with `proxy.on('disconnect'` ? + var onAbort = config.onAbort = function (info) { + setEditable(false); + window.alert("Network connection lost"); }; -// RtListMap.create(config); + var rt = module.rt = RtListMap.create(config); + + // set up user interface hooks + $repl.on('keyup', function (e) { + if (e.which === 13) { + var value = $repl.val(); + + if (!value.trim()) { return; } + + console.log("evaluating `%s`", value); + var x = rt.proxy; + + console.log('> ', eval(value)); // jshint ignore:line + console.log(); + $repl.val(''); + } + }); });