mirror of
https://github.com/matrix-construct/construct
synced 2024-11-04 21:08:57 +01:00
252 lines
6 KiB
JavaScript
252 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);
|
||
|
};
|