working on better abstractions. still held together with duct tape
This commit is contained in:
parent
c1bca09cce
commit
014dce272b
198
www/json/api.js
198
www/json/api.js
@ -13,170 +13,92 @@ define([
|
|||||||
|
|
||||||
api.ListMap = ListMap;
|
api.ListMap = ListMap;
|
||||||
|
|
||||||
var key;
|
var create = api.create = function (cfg) {
|
||||||
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 */
|
/* 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');
|
throw new Error('unsupported datatype');
|
||||||
}
|
}
|
||||||
|
|
||||||
var Config = {
|
var config = {
|
||||||
initialState: Sortify(config.data),
|
initialState: Sortify(cfg.data),
|
||||||
transformFunction: JsonOT.validate,
|
transformFunction: JsonOT.validate,
|
||||||
userName: userName,
|
userName: Crypto.rand64(8),
|
||||||
channel: channel,
|
channel: cfg.channel,
|
||||||
cryptKey: cryptKey,
|
cryptKey: cfg.cryptKey,
|
||||||
crypto: crypto,
|
crypto: Crypto,
|
||||||
|
websocketURL: Config.websocketURL,
|
||||||
};
|
};
|
||||||
|
|
||||||
var rt;
|
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
|
// 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
|
// 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
|
// 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
|
// onRemote
|
||||||
config.onRemote(info);
|
if (cfg.onRemote) {
|
||||||
|
cfg.onRemote(info);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onAbort = Config.onAbort = function (info) {
|
var onAbort = config.onAbort = function (info) {
|
||||||
// onAbort
|
// onAbort
|
||||||
config.onAbort(info);
|
cfg.onAbort(info);
|
||||||
};
|
};
|
||||||
|
|
||||||
rt =Realtime.start(Config);
|
rt = Realtime.start(config);
|
||||||
var proxy = rt.proxy = ListMap.makeProxy(data);
|
|
||||||
|
|
||||||
|
rt.proxy = proxy;
|
||||||
|
rt.realtime = realtime;
|
||||||
return rt;
|
return rt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,35 @@ define([
|
|||||||
return dat === null? 'null': isArray(dat)?'array': typeof(dat);
|
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 = {
|
var handlers = ListMap.handlers = {
|
||||||
get: function (obj, prop) {
|
get: function (obj, prop) {
|
||||||
// FIXME magic?
|
// FIXME magic?
|
||||||
@ -43,8 +72,10 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var makeProxy = ListMap.makeProxy = function (obj) {
|
var makeProxy = ListMap.makeProxy = function (obj, local) {
|
||||||
return new Proxy(obj, handlers);
|
local = local || ListMap.onLocal;
|
||||||
|
|
||||||
|
return new Proxy(obj, handlers); //makeHandlers(ListMap.onLocal));
|
||||||
};
|
};
|
||||||
|
|
||||||
var recursiveProxies = ListMap.recursiveProxies = function (obj) {
|
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 A and B, where A is the _older_ of the two */
|
||||||
ListMap.objects = function (A, B, f, path) {
|
ListMap.objects = function (A, B, f, path) {
|
||||||
var Akeys = Object.keys(A);
|
var Akeys = Object.keys(A);
|
||||||
@ -83,6 +120,7 @@ define([
|
|||||||
if (Akeys.indexOf(b) === -1) {
|
if (Akeys.indexOf(b) === -1) {
|
||||||
// there was an insertion
|
// there was an insertion
|
||||||
console.log("Inserting new key: [%s]", b);
|
console.log("Inserting new key: [%s]", b);
|
||||||
|
onChange(path, b);
|
||||||
|
|
||||||
switch (t_b) {
|
switch (t_b) {
|
||||||
case 'undefined':
|
case 'undefined':
|
||||||
@ -90,11 +128,11 @@ define([
|
|||||||
throw new Error("undefined type has key. this shouldn't happen?");
|
throw new Error("undefined type has key. this shouldn't happen?");
|
||||||
//break;
|
//break;
|
||||||
case 'array':
|
case 'array':
|
||||||
console.log('construct list');
|
//console.log('construct list');
|
||||||
A[b] = f(B[b]);
|
A[b] = f(B[b]);
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
console.log('construct map');
|
//console.log('construct map');
|
||||||
A[b] = f(B[b]);
|
A[b] = f(B[b]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -113,12 +151,12 @@ define([
|
|||||||
delete A[b];
|
delete A[b];
|
||||||
break;
|
break;
|
||||||
case 'array':
|
case 'array':
|
||||||
console.log('construct list');
|
//console.log('construct list');
|
||||||
A[b] = f(B[b]);
|
A[b] = f(B[b]);
|
||||||
// make a new proxy
|
// make a new proxy
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
console.log('construct map');
|
//console.log('construct map');
|
||||||
A[b] = f(B[b]);
|
A[b] = f(B[b]);
|
||||||
// make a new proxy
|
// make a new proxy
|
||||||
break;
|
break;
|
||||||
@ -133,6 +171,7 @@ define([
|
|||||||
if (['array', 'object'].indexOf(t_a) === -1) {
|
if (['array', 'object'].indexOf(t_a) === -1) {
|
||||||
// we can do deep equality...
|
// we can do deep equality...
|
||||||
if (A[b] !== B[b]) {
|
if (A[b] !== B[b]) {
|
||||||
|
onChange(path, b);
|
||||||
console.log("changed values from [%s] to [%s]", A[b], B[b]);
|
console.log("changed values from [%s] to [%s]", A[b], B[b]);
|
||||||
A[b] = B[b];
|
A[b] = B[b];
|
||||||
}
|
}
|
||||||
@ -152,6 +191,7 @@ define([
|
|||||||
});
|
});
|
||||||
Akeys.forEach(function (a) {
|
Akeys.forEach(function (a) {
|
||||||
if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') {
|
if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') {
|
||||||
|
onChange(path, a);
|
||||||
console.log("Deleting [%s]", a);
|
console.log("Deleting [%s]", a);
|
||||||
// the key was deleted!
|
// the key was deleted!
|
||||||
delete A[a];
|
delete A[a];
|
||||||
@ -205,6 +245,7 @@ define([
|
|||||||
ListMap.arrays(A[i], b, f, nextPath);
|
ListMap.arrays(A[i], b, f, nextPath);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
onChange(path, i);
|
||||||
A[i] = b;
|
A[i] = b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -262,11 +303,14 @@ define([
|
|||||||
|
|
||||||
switch (t_B) {
|
switch (t_B) {
|
||||||
case 'array':
|
case 'array':
|
||||||
|
ListMap.arrays(A, B, function (obj) {
|
||||||
|
return makeProxy(obj);
|
||||||
|
});
|
||||||
// idk
|
// idk
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
ListMap.objects(A, B, function (obj) {
|
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);
|
return makeProxy(obj);
|
||||||
}, []);
|
}, []);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,12 +1,74 @@
|
|||||||
define([
|
define([
|
||||||
'/json/api.js',
|
'/json/api.js',
|
||||||
|
'/common/crypto.js',
|
||||||
//'/customize/pad.js'
|
//'/customize/pad.js'
|
||||||
], function (RtListMap) {
|
], function (RtListMap, Crypto) {
|
||||||
var $ = window.jQuery;
|
var $ = window.jQuery;
|
||||||
|
|
||||||
var config = {
|
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: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// RtListMap.create(config);
|
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");
|
||||||
|
};
|
||||||
|
|
||||||
|
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('');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user