0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-10-31 10:58:54 +01:00
construct/modules/static/js/watch.js
2017-12-24 19:26:05 -07:00

251 lines
6 KiB
JavaScript

/*
* IRCd Charybdis 5/Matrix
*
* Copyright (C) 2017 Charybdis Development Team
* Copyright (C) 2017 Jason Volk (jason@zemos.net)
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice is present in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
/**************************************
*
* Model watching
*
*/
/**
* Register a handler to be called when a variable at the path updates.
* The first element of ["client"] in the path is implied and should not
* be specified.
*
* Example: To watch for changes to mc.instance.ready:
*
* mc.watch(["instance", "ready"], (cur, old) => {});
*
* This function cannot be called until Angular inits the controllers
* (it can't be called from the initial outer script).
*/
mc.watch = function(path, handler)
{
let dir = mc.watch.tree;
for(let i = 0; i < path.length - 1; i++)
{
if(!(path[i] in dir))
dir[path[i]] = {};
dir = dir[path[i]];
}
let wrapper =
{
path: path,
handler: handler,
};
if((path.back() in dir))
{
dir[path.back()].push(wrapper);
return wrapper;
}
dir[path.back()] = [wrapper];
let watcher = () => maybe(() =>
{
let watched = client;
path.forEach((part) => watched = watched[part]);
return watched;
});
mc.watch.$scope.$watch(watcher, (cur, prev) =>
{
let handlers = dir[path.back()];
handlers.forEach((wrapper) => wrapper.handler(cur, prev));
});
return wrapper;
};
/**
* The watch tree describes handlers that fire when a variable that corresponds
* to the path in the tree is updated. The tree is rooted under `client`
* so anything in the client is reachable. The correspondence is
* `mc.watch.tree.instance.authentic` to watch the var `mc.instance.authentic`
*/
mc.watch.tree = {};
/**
* Deregister (unsubscribe) from a previous call to mc.watch()
* by passing the return value of mc.watch() to this function.
*/
mc.watch.unwatch = function(wrapper)
{
let path = wrapper.path;
let dir = mc.watch.tree;
for(let i = 0; i < path.length; i++)
{
if(!(path[i] in dir))
dir[path[i]] = {};
dir = dir[path[i]];
}
let idx = dir.indexOf(wrapper);
if(idx < 0)
return false;
dir.splice(idx, 1);
return true;
};
// Alias
mc.unwatch = mc.watch.unwatch;
/**
* Convenience to mc.watch() which only calls when the value makes
* a transition to your expectation.
*/
mc.watch.when = function(path, expect, handler)
{
return mc.watch(path, (cur, prev) =>
{
if(cur === expect)
handler(cur, prev);
});
};
/**
* Convenience to mc.watch() which only calls when the value makes
* a transition away from your expectation.
*/
mc.watch.was = function(path, expect, handler)
{
return mc.watch(path, (cur, prev) =>
{
if(prev === expect)
handler(cur, prev);
});
};
/**
* Convenience to mc.watch() which only calls when the value makes
* a transition to anything other than the expectation.
*/
mc.watch.unless = function(path, expect, handler)
{
return mc.watch(path, (cur, prev) =>
{
if(cur !== expect)
handler(cur, prev);
});
};
/**
* Convenience to mc.watch() which only calls when the specific
* transition from before -> after has occurred.
*/
mc.watch.transit = function(path, before, after, handler)
{
return mc.watch(path, (cur, prev) =>
{
if(prev === before && cur === after)
handler(cur, prev);
});
};
/**
* Convenience to mc.watch() which sets the value to desired when
* it is equal to expected.
*/
mc.watch.compare_exchange = function(path, expected, desired)
{
return mc.watch.when(path, expected, (cur, prev) =>
{
if(cur !== expected)
return;
Object.terminal_edge(client, path, (key, obj) =>
{
obj[key] = desired;
});
});
};
/**
* Convenience to mc.watch() which requires conditions to be
* satisfied to call handler.
*
* The condition structure requires a path, and then an optional
* suite of conditions where the key indicates the type of test for
* the value analogous to the mc.watch function.
*
* condition:
* {
* path: ["foo", "bar"]
* test: (cur, prev) => true,
* was: 123,
* when: 456,
* unless: 789,
* }
*/
mc.watch.conditions = function(handler, conditions, functor)
{
conditions.forEach((condition) =>
{
mc.watch(condition.path, (cur, prev) =>
{
if(("test" in condition))
condition.result = condition.test(cur, prev);
if(("when" in condition))
condition.result = cur === condition.when;
if(("was" in condition))
condition.result = prev === condition.was;
if(("unless" in condition))
condition.result = cur !== condition.unless;
if(("defined" in condition))
condition.result = condition["defined"]? cur !== undefined:
cur === undefined;
if(functor.call(conditions, (condition) => condition.result))
handler();
});
});
};
/**
* Convenience to mc.watch() which requires every condition to
* be satisfied to call handler
*/
mc.watch.every = function(handler, conditions)
{
mc.watch.conditions(handler, conditions, Array.prototype.every);
};
/**
* Convenience to mc.watch() which requires some condition to
* be satisfied to call handler
*/
mc.watch.some = function(handler, conditions)
{
mc.watch.conditions(handler, conditions, Array.prototype.some);
};