Problem: the hexagonal refactor churns the backend tree for nine more phases; the UI delivery stack (web/ SPA, cmd/desktop Wails wrapper, compose/web image) must move to its own repo first so doc/layout rewrites land once on a backend-only tree. Change: - New repo git.hubris.network/dtoro/oikos-web (v0.33.0): web/, desktop/ (updateURL repointed to oikos-web releases), compose/, own CI (web + desktop jobs), own deploy script (CI-green gate, TOCTOU guard, version-tagged images, prune-to-3), own webhook receiver on :9798 + launchd unit, own compose project publishing the same 8091:80. - Cutover executed on mac-mini in order: oikos stack's web service stopped+removed, oikos-web project brought up on 8091; outer Caddy untouched (targets the published port) — serving + Authentik flow + /wails 404 quirk verified post-cutover. - Stripped from oikos: web/, cmd/desktop/, compose/web/, desktop CI workflow, ci.yml web job, Makefile ui/desktop/desktop-package/install targets, the compose web service, oikos-web from deploy.sh's fallback prune list; wails + go-keyring dropped from go.mod, vendor synced. - README / CONTRIBUTING / AGENTS.md / .agents dev+operations docs now point at the new repo; mbse + mascot design docs carry a path note. Risk: production SPA serving depends on the new pipeline now; rollback is versioned-image re-up of the old web service from a pre-split checkout (port 8091). Desktop builds installed before the split still check dtoro/oikos releases — one manual reinstall, noted in the oikos-web release notes. Verification: go vet, make test (race), make generate-check, golangci (no new findings; baseline down 400→365); post-cutover curls — localhost:8091 200, /wails/runtime.js 404, outer Caddy 302 Authentik.
262 lines
6.6 KiB
JavaScript
262 lines
6.6 KiB
JavaScript
/**
|
|
* Graphology ForceAtlas2 Helpers
|
|
* ===============================
|
|
*
|
|
* Miscellaneous helper functions.
|
|
*/
|
|
|
|
/**
|
|
* Constants.
|
|
*/
|
|
var PPN = 10;
|
|
var PPE = 3;
|
|
|
|
/**
|
|
* Very simple Object.assign-like function.
|
|
*
|
|
* @param {object} target - First object.
|
|
* @param {object} [...objects] - Objects to merge.
|
|
* @return {object}
|
|
*/
|
|
exports.assign = function (target) {
|
|
target = target || {};
|
|
|
|
var objects = Array.prototype.slice.call(arguments).slice(1),
|
|
i,
|
|
k,
|
|
l;
|
|
|
|
for (i = 0, l = objects.length; i < l; i++) {
|
|
if (!objects[i]) continue;
|
|
|
|
for (k in objects[i]) target[k] = objects[i][k];
|
|
}
|
|
|
|
return target;
|
|
};
|
|
|
|
/**
|
|
* Function used to validate the given settings.
|
|
*
|
|
* @param {object} settings - Settings to validate.
|
|
* @return {object|null}
|
|
*/
|
|
exports.validateSettings = function (settings) {
|
|
if ('linLogMode' in settings && typeof settings.linLogMode !== 'boolean')
|
|
return {message: 'the `linLogMode` setting should be a boolean.'};
|
|
|
|
if (
|
|
'outboundAttractionDistribution' in settings &&
|
|
typeof settings.outboundAttractionDistribution !== 'boolean'
|
|
)
|
|
return {
|
|
message:
|
|
'the `outboundAttractionDistribution` setting should be a boolean.'
|
|
};
|
|
|
|
if ('adjustSizes' in settings && typeof settings.adjustSizes !== 'boolean')
|
|
return {message: 'the `adjustSizes` setting should be a boolean.'};
|
|
|
|
if (
|
|
'edgeWeightInfluence' in settings &&
|
|
typeof settings.edgeWeightInfluence !== 'number'
|
|
)
|
|
return {
|
|
message: 'the `edgeWeightInfluence` setting should be a number.'
|
|
};
|
|
|
|
if (
|
|
'scalingRatio' in settings &&
|
|
!(typeof settings.scalingRatio === 'number' && settings.scalingRatio >= 0)
|
|
)
|
|
return {message: 'the `scalingRatio` setting should be a number >= 0.'};
|
|
|
|
if (
|
|
'strongGravityMode' in settings &&
|
|
typeof settings.strongGravityMode !== 'boolean'
|
|
)
|
|
return {message: 'the `strongGravityMode` setting should be a boolean.'};
|
|
|
|
if (
|
|
'gravity' in settings &&
|
|
!(typeof settings.gravity === 'number' && settings.gravity >= 0)
|
|
)
|
|
return {message: 'the `gravity` setting should be a number >= 0.'};
|
|
|
|
if (
|
|
'slowDown' in settings &&
|
|
!(typeof settings.slowDown === 'number' || settings.slowDown >= 0)
|
|
)
|
|
return {message: 'the `slowDown` setting should be a number >= 0.'};
|
|
|
|
if (
|
|
'barnesHutOptimize' in settings &&
|
|
typeof settings.barnesHutOptimize !== 'boolean'
|
|
)
|
|
return {message: 'the `barnesHutOptimize` setting should be a boolean.'};
|
|
|
|
if (
|
|
'barnesHutTheta' in settings &&
|
|
!(
|
|
typeof settings.barnesHutTheta === 'number' &&
|
|
settings.barnesHutTheta >= 0
|
|
)
|
|
)
|
|
return {message: 'the `barnesHutTheta` setting should be a number >= 0.'};
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Function generating a flat matrix for both nodes & edges of the given graph.
|
|
*
|
|
* @param {Graph} graph - Target graph.
|
|
* @param {function} getEdgeWeight - Edge weight getter function.
|
|
* @return {object} - Both matrices.
|
|
*/
|
|
exports.graphToByteArrays = function (graph, getEdgeWeight) {
|
|
var order = graph.order;
|
|
var size = graph.size;
|
|
var index = {};
|
|
var j;
|
|
|
|
// NOTE: float32 could lead to issues if edge array needs to index large
|
|
// number of nodes.
|
|
var NodeMatrix = new Float32Array(order * PPN);
|
|
var EdgeMatrix = new Float32Array(size * PPE);
|
|
|
|
// Iterate through nodes
|
|
j = 0;
|
|
graph.forEachNode(function (node, attr) {
|
|
// Node index
|
|
index[node] = j;
|
|
|
|
// Populating byte array
|
|
NodeMatrix[j] = attr.x;
|
|
NodeMatrix[j + 1] = attr.y;
|
|
NodeMatrix[j + 2] = 0; // dx
|
|
NodeMatrix[j + 3] = 0; // dy
|
|
NodeMatrix[j + 4] = 0; // old_dx
|
|
NodeMatrix[j + 5] = 0; // old_dy
|
|
NodeMatrix[j + 6] = 1; // mass
|
|
NodeMatrix[j + 7] = 1; // convergence
|
|
NodeMatrix[j + 8] = attr.size || 1;
|
|
NodeMatrix[j + 9] = attr.fixed ? 1 : 0;
|
|
j += PPN;
|
|
});
|
|
|
|
// Iterate through edges
|
|
j = 0;
|
|
graph.forEachEdge(function (edge, attr, source, target, sa, ta, u) {
|
|
var sj = index[source];
|
|
var tj = index[target];
|
|
|
|
var weight = getEdgeWeight(edge, attr, source, target, sa, ta, u);
|
|
|
|
// Incrementing mass to be a node's weighted degree
|
|
NodeMatrix[sj + 6] += weight;
|
|
NodeMatrix[tj + 6] += weight;
|
|
|
|
// Populating byte array
|
|
EdgeMatrix[j] = sj;
|
|
EdgeMatrix[j + 1] = tj;
|
|
EdgeMatrix[j + 2] = weight;
|
|
j += PPE;
|
|
});
|
|
|
|
return {
|
|
nodes: NodeMatrix,
|
|
edges: EdgeMatrix
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Function applying the layout back to the graph.
|
|
*
|
|
* @param {Graph} graph - Target graph.
|
|
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
* @param {function|null} outputReducer - A node reducer.
|
|
*/
|
|
exports.assignLayoutChanges = function (graph, NodeMatrix, outputReducer) {
|
|
var i = 0;
|
|
|
|
graph.updateEachNodeAttributes(function (node, attr) {
|
|
attr.x = NodeMatrix[i];
|
|
attr.y = NodeMatrix[i + 1];
|
|
|
|
i += PPN;
|
|
|
|
return outputReducer ? outputReducer(node, attr) : attr;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Function reading the positions (only) from the graph, to write them in the matrix.
|
|
*
|
|
* @param {Graph} graph - Target graph.
|
|
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
*/
|
|
exports.readGraphPositions = function (graph, NodeMatrix) {
|
|
var i = 0;
|
|
|
|
graph.forEachNode(function (node, attr) {
|
|
NodeMatrix[i] = attr.x;
|
|
NodeMatrix[i + 1] = attr.y;
|
|
|
|
i += PPN;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Function collecting the layout positions.
|
|
*
|
|
* @param {Graph} graph - Target graph.
|
|
* @param {Float32Array} NodeMatrix - Node matrix.
|
|
* @param {function|null} outputReducer - A nodes reducer.
|
|
* @return {object} - Map to node positions.
|
|
*/
|
|
exports.collectLayoutChanges = function (graph, NodeMatrix, outputReducer) {
|
|
var nodes = graph.nodes(),
|
|
positions = {};
|
|
|
|
for (var i = 0, j = 0, l = NodeMatrix.length; i < l; i += PPN) {
|
|
if (outputReducer) {
|
|
var newAttr = Object.assign({}, graph.getNodeAttributes(nodes[j]));
|
|
newAttr.x = NodeMatrix[i];
|
|
newAttr.y = NodeMatrix[i + 1];
|
|
newAttr = outputReducer(nodes[j], newAttr);
|
|
positions[nodes[j]] = {
|
|
x: newAttr.x,
|
|
y: newAttr.y
|
|
};
|
|
} else {
|
|
positions[nodes[j]] = {
|
|
x: NodeMatrix[i],
|
|
y: NodeMatrix[i + 1]
|
|
};
|
|
}
|
|
|
|
j++;
|
|
}
|
|
|
|
return positions;
|
|
};
|
|
|
|
/**
|
|
* Function returning a web worker from the given function.
|
|
*
|
|
* @param {function} fn - Function for the worker.
|
|
* @return {DOMString}
|
|
*/
|
|
exports.createWorker = function createWorker(fn) {
|
|
var xURL = window.URL || window.webkitURL;
|
|
var code = fn.toString();
|
|
var objectUrl = xURL.createObjectURL(
|
|
new Blob(['(' + code + ').call(this);'], {type: 'text/javascript'})
|
|
);
|
|
var worker = new Worker(objectUrl);
|
|
xURL.revokeObjectURL(objectUrl);
|
|
|
|
return worker;
|
|
};
|