agent-timelines 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_timelines/__init__.py +0 -0
- agent_timelines/__main__.py +3 -0
- agent_timelines/actors.js +202 -0
- agent_timelines/assets/LICENSES.txt +98 -0
- agent_timelines/assets/PressStart2P-Regular.ttf +0 -0
- agent_timelines/assets/one-bit.png +0 -0
- agent_timelines/assets/tiny-dungeon.png +0 -0
- agent_timelines/cli.py +197 -0
- agent_timelines/dungeon.js +211 -0
- agent_timelines/gantt.js +63 -0
- agent_timelines/lobby.js +46 -0
- agent_timelines/timeline.py +579 -0
- agent_timelines/viewer.html +474 -0
- agent_timelines-0.2.0.dist-info/METADATA +153 -0
- agent_timelines-0.2.0.dist-info/RECORD +20 -0
- agent_timelines-0.2.0.dist-info/WHEEL +5 -0
- agent_timelines-0.2.0.dist-info/entry_points.txt +2 -0
- agent_timelines-0.2.0.dist-info/licenses/LICENSE +24 -0
- agent_timelines-0.2.0.dist-info/licenses/src/agent_timelines/assets/LICENSES.txt +98 -0
- agent_timelines-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/* Procedural dungeon for the agent tree: one room per node, one corridor per parent->child edge.
|
|
2
|
+
* Layout is append-only: room i depends only on seed, node i and rooms 0..i-1, in world coordinates
|
|
3
|
+
* (root at 0,0, may be negative). tiles[y][x] is world cell (x+ox, y+oy); rooms/paths/deco use world coords.
|
|
4
|
+
* Every random look choice hashes (seed, room id) or (seed, world cell), so appending nodes never redecorates.
|
|
5
|
+
* Plain ES5 syntax (Math.imul aside); window.Dungeon, and module.exports for Node. */
|
|
6
|
+
(function (root) {
|
|
7
|
+
'use strict';
|
|
8
|
+
// Sprite indices are row*cols+col, 16px. tiles[][] are all 'td'; deco entries name their sheet.
|
|
9
|
+
var COLS = { td: 12, ob: 48 }; // td = Kenney Tiny Dungeon, ob = Kenney 1-Bit Pack (colored)
|
|
10
|
+
var FLOOR = 48, FLOOR_SPECK = 49, FLOOR_STONES = 42; // td sand: plain, specks, loose stones (rooms)
|
|
11
|
+
var CORRIDOR = 0, CORR_HOLES = 12, CORR_RUBBLE = 24; // td brown dirt: plain, holes, rubble (corridors)
|
|
12
|
+
var DOOR = 45; // td wooden door in a dark stone frame
|
|
13
|
+
var WALL = 40; // td grey brick wall face (walkable floor directly south)
|
|
14
|
+
var WALL_GRATE = 28; // td brick face with a barred grate (rare face variant)
|
|
15
|
+
var WALL_TOP = 37; // td wall cap, horizontal stone bars, wall on both sides
|
|
16
|
+
var TOP_L = 36, TOP_R = 38, TOP_LR = 39; // td wall cap with a dark outline on the left / right / both ends
|
|
17
|
+
var BANNER = 29; // td red and gold shield banner hung on a brick face
|
|
18
|
+
var HEARTH_L = 10, HEARTH_R = 11; // td two-wide dark fireplace arch with glowing embers (left, right half)
|
|
19
|
+
var TABLE = 72; // td wooden table
|
|
20
|
+
var STOOL = 73; // td small wooden stool
|
|
21
|
+
var BARREL = 82; // td banded wooden barrel
|
|
22
|
+
var CRATE = 67; // td wooden crate with iron bands
|
|
23
|
+
var CHEST = 89; // td closed treasure chest
|
|
24
|
+
var CABINET = 63, CABINET_B = 75; // td tall wooden shelf/cabinets with drawers (two variants)
|
|
25
|
+
var OB_SKULL = 720; // ob skull and crossbones
|
|
26
|
+
var OB_BONES = 721; // ob bone pile
|
|
27
|
+
var OB_WEB = 722; // ob cobweb
|
|
28
|
+
var OB_CANDLE = 724; // ob lit candle (used as a wall sconce and on the table)
|
|
29
|
+
var OB_CANDELABRA = 725; // ob three-armed lit candelabra
|
|
30
|
+
var OB_FLAME = 495; // ob orange flame, burning inside the hearth arch
|
|
31
|
+
var OB_NOTICE = 352; // ob notice board on legs
|
|
32
|
+
var OB_RUG_L = 629, OB_RUG = 630, OB_RUG_R = 631; // ob red patterned carpet: left end, middle, right end
|
|
33
|
+
var WALK = [FLOOR, FLOOR_SPECK, FLOOR_STONES, CORRIDOR, CORR_HOLES, CORR_RUBBLE, DOOR];
|
|
34
|
+
var DEF = { minW: 5, maxW: 9, minH: 4, maxH: 7, rootW: 15, rootH: 11 };
|
|
35
|
+
|
|
36
|
+
function prng(str) { // FNV-1a string hash -> mulberry32
|
|
37
|
+
var a = 2166136261, i;
|
|
38
|
+
for (i = 0; i < str.length; i++) a = Math.imul(a ^ str.charCodeAt(i), 16777619);
|
|
39
|
+
return function () {
|
|
40
|
+
a = (a + 0x6D2B79F5) | 0;
|
|
41
|
+
var t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
42
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
43
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
47
|
+
function inBox(q, x, y, pad) { return x >= q.x - pad && x < q.x + q.w + pad && y >= q.y - pad && y < q.y + q.h + pad; }
|
|
48
|
+
|
|
49
|
+
// Door cell in a's wall facing b, plus the free cell just outside it. Doors never sit at a wall's ends
|
|
50
|
+
// (the hall: only its middle 5 cells), so corner and wall props placed earlier can never block a later door.
|
|
51
|
+
function door(a, b) {
|
|
52
|
+
var dx = b.cx - a.cx, dy = b.cy - a.cy, m = a.hall ? 2 : a.w + a.h;
|
|
53
|
+
var hz = Math.abs(dx) * (a.h + b.h) > Math.abs(dy) * (a.w + b.w);
|
|
54
|
+
var sx = hz ? (dx > 0 ? 1 : -1) : 0, sy = hz ? 0 : (dy > 0 ? 1 : -1);
|
|
55
|
+
var x = hz ? (sx > 0 ? a.x + a.w : a.x - 1) : clamp(b.cx, Math.max(a.x + 1, a.cx - m), Math.min(a.x + a.w - 2, a.cx + m));
|
|
56
|
+
var y = hz ? clamp(b.cy, Math.max(a.y + 1, a.cy - m), Math.min(a.y + a.h - 2, a.cy + m)) : (sy > 0 ? a.y + a.h : a.y - 1);
|
|
57
|
+
return { d: [x, y], o: [x + sx, y + sy] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// BFS between two door-outside cells around every room's wall ring; null if sealed in.
|
|
61
|
+
function route(rooms, A, B) {
|
|
62
|
+
var x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9, i;
|
|
63
|
+
for (i = 0; i < rooms.length; i++) {
|
|
64
|
+
x0 = Math.min(x0, rooms[i].x - 6); y0 = Math.min(y0, rooms[i].y - 6);
|
|
65
|
+
x1 = Math.max(x1, rooms[i].x + rooms[i].w + 6); y1 = Math.max(y1, rooms[i].y + rooms[i].h + 6);
|
|
66
|
+
}
|
|
67
|
+
var W = x1 - x0, prev = {}, q = [A], head = 0, dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]];
|
|
68
|
+
function key(c) { return (c[1] - y0) * W + c[0] - x0; }
|
|
69
|
+
prev[key(A)] = null;
|
|
70
|
+
while (head < q.length) {
|
|
71
|
+
var c = q[head++];
|
|
72
|
+
if (c[0] === B[0] && c[1] === B[1]) {
|
|
73
|
+
var out = [];
|
|
74
|
+
for (; c; c = prev[key(c)]) out.unshift(c);
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
for (i = 0; i < 4; i++) {
|
|
78
|
+
var n = [c[0] + dirs[i][0], c[1] + dirs[i][1]], j, bad = n[0] < x0 || n[0] >= x1 || n[1] < y0 || n[1] >= y1 || key(n) in prev;
|
|
79
|
+
for (j = 0; !bad && j < rooms.length; j++) bad = inBox(rooms[j], n[0], n[1], 1);
|
|
80
|
+
if (!bad) { prev[key(n)] = c; q.push(n); }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Props for one room, from seed + room geometry only. Agent rooms: interior corners and the two end
|
|
87
|
+
// cells of the top wall face, which door() never uses. Hall: everything stays off its door zones.
|
|
88
|
+
function decorate(seed, q) {
|
|
89
|
+
var out = [], X0 = q.x, Y0 = q.y, X1 = q.x + q.w - 1, Y1 = q.y + q.h - 1, cx = q.cx, cy = q.cy, x, y;
|
|
90
|
+
function put(x, y, sheet, idx, solid) {
|
|
91
|
+
if (x >= X0 && x <= X1 && y >= Y0 - 1 && y <= Y1) out.push({ x: x, y: y, sheet: sheet, idx: idx, solid: !!solid });
|
|
92
|
+
}
|
|
93
|
+
if (q.hall) {
|
|
94
|
+
[X0, cx - 3, cx + 3, X1].forEach(function (x) { put(x, Y0 - 1, 'td', BANNER); });
|
|
95
|
+
put(X0 + 2, Y0 - 1, 'td', HEARTH_L); put(X0 + 3, Y0 - 1, 'td', HEARTH_R);
|
|
96
|
+
put(X0 + 2, Y0 - 1, 'ob', OB_FLAME); put(X0 + 3, Y0 - 1, 'ob', OB_FLAME); put(X0 + 1, Y0 - 1, 'ob', OB_NOTICE);
|
|
97
|
+
for (y = cy - 2; y <= cy + 2; y++) for (x = X0 + 2; x <= X1 - 2; x++) put(x, y, 'ob', x === X0 + 2 ? OB_RUG_L : x === X1 - 2 ? OB_RUG_R : OB_RUG);
|
|
98
|
+
for (x = cx - 3; x <= cx + 3; x++) put(x, cy, 'td', TABLE, 1);
|
|
99
|
+
[cx - 3, cx - 1, cx + 1, cx + 3].forEach(function (x) { put(x, cy - 1, 'td', STOOL, 1); put(x, cy + 1, 'td', STOOL, 1); });
|
|
100
|
+
put(cx - 4, cy, 'td', STOOL, 1); put(cx + 4, cy, 'td', STOOL, 1);
|
|
101
|
+
put(cx - 2, cy, 'ob', OB_CANDLE); put(cx + 2, cy, 'ob', OB_CANDLE);
|
|
102
|
+
for (x = X1 - 3; x < X1; x++) put(x, Y0, 'td', x % 2 ? CABINET : CABINET_B, 1);
|
|
103
|
+
put(X0, Y0, 'td', CRATE, 1); put(X1, Y0, 'ob', OB_CANDELABRA, 1);
|
|
104
|
+
put(X0, cy + 3, 'ob', OB_CANDELABRA, 1); put(X1, cy + 3, 'ob', OB_CANDELABRA, 1);
|
|
105
|
+
put(X0, Y1, 'td', BARREL, 1); put(X0 + 1, Y1, 'td', BARREL, 1); put(X1 - 1, Y1, 'td', CRATE, 1); put(X1, Y1, 'td', CHEST, 1);
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
var r = prng(seed + '/deco/' + q.id), props = [['td', BARREL, 1], ['td', BARREL, 1], ['td', CRATE, 1], ['td', CHEST, 1],
|
|
109
|
+
['ob', OB_SKULL], ['ob', OB_BONES], ['ob', OB_CANDELABRA, 1]];
|
|
110
|
+
[[X0, Y0], [X1, Y0], [X0, Y1], [X1, Y1]].forEach(function (c, j) {
|
|
111
|
+
var v = r(), p = j < 2 && v < 0.2 ? ['ob', OB_WEB] : props[r() * props.length | 0];
|
|
112
|
+
if (v < 0.65) put(c[0], c[1], p[0], p[1], p[2]);
|
|
113
|
+
});
|
|
114
|
+
[X0, X1].forEach(function (x) { if (r() < 0.6) put(x, Y0 - 1, 'ob', OB_CANDLE); });
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function generate(seed, nodes, opts) {
|
|
119
|
+
var o = {}, k, rooms = [], paths = [], cells = [], idx = {};
|
|
120
|
+
for (k in DEF) o[k] = opts && opts[k] != null ? opts[k] : DEF[k];
|
|
121
|
+
|
|
122
|
+
nodes.forEach(function (n, i) {
|
|
123
|
+
var r = prng(seed + '/' + n.id), par = i ? rooms[idx[n.parent] || 0] : null;
|
|
124
|
+
var w = o.rootW, h = o.rootH, x = -(w >> 1), y = -(h >> 1), room, p;
|
|
125
|
+
for (k = 0; par; k++) { // candidate spots spiral outward from the parent until one fits and routes
|
|
126
|
+
w = o.minW + (r() * (o.maxW - o.minW + 1) | 0); h = o.minH + (r() * (o.maxH - o.minH + 1) | 0);
|
|
127
|
+
var a = r() * 6.2832, g = 3 + r() * (2 + k / 4);
|
|
128
|
+
x = Math.round(par.cx + Math.cos(a) * ((par.w + w) / 2 + g) - w / 2);
|
|
129
|
+
y = Math.round(par.cy + Math.sin(a) * ((par.h + h) / 2 + g) - h / 2);
|
|
130
|
+
// 3 free cells (wall, lane, wall) between rooms; no corridor cell may touch the new walls
|
|
131
|
+
var ok = rooms.every(function (q) { return x - 3 >= q.x + q.w || x + w + 3 <= q.x || y - 3 >= q.y + q.h || y + h + 3 <= q.y; });
|
|
132
|
+
room = { id: n.id, x: x, y: y, w: w, h: h, cx: x + (w >> 1), cy: y + (h >> 1) };
|
|
133
|
+
ok = ok && !cells.some(function (c) { return inBox(room, c[0], c[1], 1); });
|
|
134
|
+
if (ok && (p = route(rooms.concat(room), door(par, room).o, door(room, par).o))) break;
|
|
135
|
+
}
|
|
136
|
+
room = { id: n.id, x: x, y: y, w: w, h: h, cx: x + (w >> 1), cy: y + (h >> 1) };
|
|
137
|
+
if (!i) room.hall = true;
|
|
138
|
+
if (par) {
|
|
139
|
+
p = [door(par, room).d].concat(p, [door(room, par).d]);
|
|
140
|
+
paths.push({ from: par.id, to: n.id, cells: p });
|
|
141
|
+
cells.push.apply(cells, p);
|
|
142
|
+
}
|
|
143
|
+
idx[n.id] = i;
|
|
144
|
+
rooms.push(room);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Rasterise floors and doors, each floor cell's variant hashed from its world cell.
|
|
148
|
+
var T = {}, x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
|
|
149
|
+
function at(x, y) { return T[x + ',' + y]; }
|
|
150
|
+
function hash(x, y) { return prng(seed + '#' + x + ',' + y)(); }
|
|
151
|
+
function set(x, y, t) { T[x + ',' + y] = t; x0 = Math.min(x0, x - 1); y0 = Math.min(y0, y - 2); x1 = Math.max(x1, x + 2); y1 = Math.max(y1, y + 2); }
|
|
152
|
+
rooms.forEach(function (q) {
|
|
153
|
+
for (var yy = q.y; yy < q.y + q.h; yy++) for (var xx = q.x; xx < q.x + q.w; xx++) {
|
|
154
|
+
var v = hash(xx, yy);
|
|
155
|
+
set(xx, yy, v < 0.05 ? FLOOR_STONES : v < 0.2 ? FLOOR_SPECK : FLOOR);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
paths.forEach(function (pa) {
|
|
159
|
+
pa.cells.forEach(function (c) { var v = hash(c[0], c[1]); set(c[0], c[1], v < 0.06 ? CORR_RUBBLE : v < 0.12 ? CORR_HOLES : CORRIDOR); });
|
|
160
|
+
});
|
|
161
|
+
paths.forEach(function (pa) { [pa.cells[0], pa.cells[pa.cells.length - 1]].forEach(function (c) { T[c[0] + ',' + c[1]] = DOOR; }); });
|
|
162
|
+
|
|
163
|
+
// Walls, 3/4 view: a brick face where floor is directly south, a cap row above every face, and a cap
|
|
164
|
+
// on any other empty cell touching floor. Caps get a dark outline on each end that is not more wall.
|
|
165
|
+
var W = {}, x, y, dx, dy;
|
|
166
|
+
for (y = y0; y < y1; y++) for (x = x0; x < x1; x++) {
|
|
167
|
+
if (at(x, y) != null) continue;
|
|
168
|
+
var near = false;
|
|
169
|
+
for (dy = -1; dy <= 1; dy++) for (dx = -1; dx <= 1; dx++) near = near || at(x + dx, y + dy) != null;
|
|
170
|
+
if (at(x, y + 1) != null) W[x + ',' + y] = hash(x, y) < 0.08 ? WALL_GRATE : WALL;
|
|
171
|
+
else if (near) W[x + ',' + y] = WALL_TOP;
|
|
172
|
+
}
|
|
173
|
+
function face(x, y) { var t = W[x + ',' + y]; return t === WALL || t === WALL_GRATE; }
|
|
174
|
+
for (y = y0; y < y1; y++) for (x = x0; x < x1; x++) {
|
|
175
|
+
if (at(x, y) == null && !W[x + ',' + y] && (face(x - 1, y + 1) || face(x, y + 1) || face(x + 1, y + 1))) W[x + ',' + y] = 'cap';
|
|
176
|
+
}
|
|
177
|
+
var tiles = [];
|
|
178
|
+
for (y = y0; y < y1; y++) {
|
|
179
|
+
var row = [];
|
|
180
|
+
for (x = x0; x < x1; x++) {
|
|
181
|
+
var t = at(x, y) != null ? at(x, y) : W[x + ',' + y];
|
|
182
|
+
if (t === WALL_TOP || t === 'cap') {
|
|
183
|
+
var l = !!W[(x - 1) + ',' + y], r = !!W[(x + 1) + ',' + y];
|
|
184
|
+
t = l && r ? WALL_TOP : l ? TOP_R : r ? TOP_L : TOP_LR;
|
|
185
|
+
}
|
|
186
|
+
row.push(t == null ? -1 : t);
|
|
187
|
+
}
|
|
188
|
+
tiles.push(row);
|
|
189
|
+
}
|
|
190
|
+
var deco = [];
|
|
191
|
+
rooms.forEach(function (q) { deco.push.apply(deco, decorate(seed, q)); });
|
|
192
|
+
return { w: x1 - x0, h: y1 - y0, ox: x0, oy: y0, tiles: tiles, rooms: rooms, paths: paths, deco: deco };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// sheets = { td: Image, ob: Image }; tiles first, then deco in list order (rug under table under candle).
|
|
196
|
+
function render(ctx, d, sheets, scale) {
|
|
197
|
+
var S = 16 * scale;
|
|
198
|
+
function draw(sheet, t, x, y) {
|
|
199
|
+
ctx.drawImage(sheets[sheet], (t % COLS[sheet]) * 16, ((t / COLS[sheet]) | 0) * 16, 16, 16, x * S, y * S, S, S);
|
|
200
|
+
}
|
|
201
|
+
ctx.imageSmoothingEnabled = false;
|
|
202
|
+
d.tiles.forEach(function (row, y) { row.forEach(function (t, x) { if (t >= 0) draw('td', t, x, y); }); });
|
|
203
|
+
d.deco.forEach(function (e) { draw(e.sheet, e.idx, e.x - d.ox, e.y - d.oy); });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
var Dungeon = { generate: generate, render: render, prng: prng, COLS: COLS, T: {
|
|
207
|
+
FLOOR: FLOOR, CORRIDOR: CORRIDOR, DOOR: DOOR, WALL: WALL, WALL_TOP: WALL_TOP, WALK: WALK,
|
|
208
|
+
BANNER: BANNER, HEARTH_L: HEARTH_L, TABLE: TABLE, CABINET: CABINET, OB_RUG: OB_RUG } };
|
|
209
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = Dungeon;
|
|
210
|
+
root.Dungeon = Dungeon;
|
|
211
|
+
})(typeof window !== 'undefined' ? window : this);
|
agent_timelines/gantt.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Pure layout for the timeline (Gantt) view: one row per node in tree order, one bar per node on a shared time axis. Plain ES5;
|
|
2
|
+
* window.Gantt, and module.exports for Node. The viewer draws the result; it applies a minimum bar width in CSS, not here.
|
|
3
|
+
* shortcut: a session with a long idle gap squeezes its bars into the two ends; zoom is the answer for now, gap compression is the upgrade. */
|
|
4
|
+
(function (root) {
|
|
5
|
+
'use strict';
|
|
6
|
+
var STEPS = [10, 30, 60, 300, 600, 1800, 3600, 7200, 21600, 86400]; // tick spacing in seconds
|
|
7
|
+
var MAXTICKS = 8;
|
|
8
|
+
function num(x) { return typeof x === 'number' && isFinite(x); }
|
|
9
|
+
function clamp(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); }
|
|
10
|
+
function p2(n) { return n < 10 ? '0' + n : '' + n; }
|
|
11
|
+
|
|
12
|
+
function ticks(t0, t1, x) {
|
|
13
|
+
var span = t1 - t0, step = STEPS[STEPS.length - 1];
|
|
14
|
+
for (var i = 0; i < STEPS.length; i++) { if (Math.floor(span / STEPS[i]) + 1 <= MAXTICKS) { step = STEPS[i]; break; } }
|
|
15
|
+
if (Math.floor(span / step) + 1 > MAXTICKS) step = 86400 * Math.ceil(span / 86400 / MAXTICKS); // longer than 8 days: whole days
|
|
16
|
+
var off = -new Date(t0 * 1000).getTimezoneOffset() * 60, out = []; // ticks fall on local clock multiples of step
|
|
17
|
+
for (var t = Math.ceil((t0 + off) / step) * step - off; t <= t1; t += step) {
|
|
18
|
+
var d = new Date(t * 1000);
|
|
19
|
+
out.push({ x: x(t), label: step >= 86400 ? p2(d.getMonth() + 1) + '-' + p2(d.getDate()) : p2(d.getHours()) + ':' + p2(d.getMinutes()) + (step < 60 ? ':' + p2(d.getSeconds()) : '') });
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// nodes: main first, each {id, parent, start, end, running}. t0 / t1 (optional) only widen the range: the caller passes "now" for a live session.
|
|
25
|
+
// Returns {t0, t1, rows: [{id, depth, x, w}], ticks: [{x, label}]}; x and w are fractions of the range, 0..1.
|
|
26
|
+
function layout(nodes, t0, t1) {
|
|
27
|
+
nodes = nodes || [];
|
|
28
|
+
var starts = nodes.map(function (n) { return n.start; }).filter(num), ends = nodes.map(function (n) { return n.end; }).filter(num).concat(starts);
|
|
29
|
+
var lo = Math.min.apply(null, starts.concat(num(t0) ? [t0] : [])), hi = Math.max.apply(null, ends.concat(num(t1) ? [t1] : []));
|
|
30
|
+
if (!isFinite(lo)) lo = isFinite(hi) ? hi : 0;
|
|
31
|
+
if (!isFinite(hi) || hi <= lo) hi = lo + 1;
|
|
32
|
+
var span = hi - lo, fx = function (t) { return clamp((t - lo) / span, 0, 1); };
|
|
33
|
+
var by = {}, kids = {}, rows = [], seen = {};
|
|
34
|
+
nodes.forEach(function (n) { by[n.id] = n; });
|
|
35
|
+
var rootId = nodes.length ? nodes[0].id : null;
|
|
36
|
+
nodes.slice(1).forEach(function (n) { // an unknown (or self) parent hangs the node under main
|
|
37
|
+
var p = by[n.parent] && n.parent !== n.id ? n.parent : rootId;
|
|
38
|
+
(kids[p] = kids[p] || []).push(n);
|
|
39
|
+
});
|
|
40
|
+
function bar(n, depth) {
|
|
41
|
+
var x = 1, w = 0;
|
|
42
|
+
if (num(n.start)) {
|
|
43
|
+
var e = num(n.end) ? n.end : n.running ? hi : n.start;
|
|
44
|
+
x = fx(n.start); w = Math.max(0, fx(e) - x);
|
|
45
|
+
}
|
|
46
|
+
rows.push({ id: n.id, depth: depth, x: x, w: w });
|
|
47
|
+
}
|
|
48
|
+
function walk(n, depth) {
|
|
49
|
+
if (seen[n.id]) return;
|
|
50
|
+
seen[n.id] = 1;
|
|
51
|
+
bar(n, depth);
|
|
52
|
+
(kids[n.id] || []).sort(function (a, b) { return (num(a.start) ? a.start : Infinity) - (num(b.start) ? b.start : Infinity) || 0; })
|
|
53
|
+
.forEach(function (k) { walk(k, depth + 1); });
|
|
54
|
+
}
|
|
55
|
+
if (nodes.length) walk(nodes[0], 0);
|
|
56
|
+
nodes.forEach(function (n) { walk(n, 1); }); // a parent cycle is unreachable from main: still shown once, under it
|
|
57
|
+
return { t0: lo, t1: hi, rows: rows, ticks: ticks(lo, hi, fx) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var Gantt = { layout: layout };
|
|
61
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = Gantt;
|
|
62
|
+
root.Gantt = Gantt;
|
|
63
|
+
})(typeof window !== 'undefined' ? window : this);
|
agent_timelines/lobby.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Pure helpers for the lobby list: search, project filter, paging. Plain ES5; window.Lobby, and module.exports for Node.
|
|
2
|
+
* Rows are the lobby rows of index.data.js (no prompt or reply text, so search covers cwd, id, planner and last event only). */
|
|
3
|
+
(function (root) {
|
|
4
|
+
'use strict';
|
|
5
|
+
function s(x) { return x == null ? '' : String(x); }
|
|
6
|
+
function parts(cwd) { return s(cwd).split('/').filter(Boolean); }
|
|
7
|
+
|
|
8
|
+
// Rows in proj (when set) whose cwd + id + planner + last text contain every space-separated word of q, case-insensitive.
|
|
9
|
+
function filter(rows, q, proj) {
|
|
10
|
+
var words = s(q).toLowerCase().split(/\s+/).filter(Boolean);
|
|
11
|
+
return (rows || []).filter(function (r) {
|
|
12
|
+
if (proj && s(r.cwd) !== proj) return false;
|
|
13
|
+
var hay = [r.cwd, r.id, r.planner, r.last].map(s).join('\n').toLowerCase();
|
|
14
|
+
return words.every(function (w) { return hay.indexOf(w) >= 0; });
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// [{cwd, name, n}] newest first. name = last path part; two cwds with the same last part get their parent folder added: "api (work)".
|
|
19
|
+
function projects(rows) {
|
|
20
|
+
var by = {}, list = [];
|
|
21
|
+
(rows || []).forEach(function (r) {
|
|
22
|
+
var c = s(r.cwd);
|
|
23
|
+
if (!c) return;
|
|
24
|
+
if (!by[c]) { by[c] = { cwd: c, name: parts(c).pop() || '/', n: 0, t: -Infinity }; list.push(by[c]); }
|
|
25
|
+
by[c].n++;
|
|
26
|
+
by[c].t = Math.max(by[c].t, +r.updated || 0);
|
|
27
|
+
});
|
|
28
|
+
var seen = {};
|
|
29
|
+
list.forEach(function (p) { seen[p.name] = (seen[p.name] || 0) + 1; });
|
|
30
|
+
list.forEach(function (p) { if (seen[p.name] > 1) p.name += ' (' + (parts(p.cwd).slice(-2, -1)[0] || p.cwd) + ')'; });
|
|
31
|
+
list.sort(function (a, b) { return b.t - a.t; }); // stable in modern engines: equal times keep first-seen order
|
|
32
|
+
return list.map(function (p) { return { cwd: p.cwd, name: p.name, n: p.n }; });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// One page of list; page is 0-based and clamped, so a list that shrank never leaves you on an empty page.
|
|
36
|
+
function page(list, pg, size) {
|
|
37
|
+
list = list || [];
|
|
38
|
+
size = size > 0 ? Math.floor(size) : 1;
|
|
39
|
+
var pages = Math.max(1, Math.ceil(list.length / size)), p = Math.max(0, Math.min(pages - 1, Math.floor(+pg) || 0));
|
|
40
|
+
return { items: list.slice(p * size, p * size + size), page: p, pages: pages };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var Lobby = { filter: filter, projects: projects, page: page };
|
|
44
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = Lobby;
|
|
45
|
+
root.Lobby = Lobby;
|
|
46
|
+
})(typeof window !== 'undefined' ? window : this);
|