reslop 0.2.6 → 0.2.7
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.
- package/CHANGELOG.md +11 -1
- package/lib/ansi.js +42 -2
- package/lib/api.js +1 -1
- package/lib/cli.js +21 -1
- package/lib/dashboard/model.js +28 -3
- package/lib/dashboard/npm.js +116 -19
- package/lib/files.js +13 -7
- package/lib/find.js +61 -0
- package/lib/git-deps.js +1 -0
- package/lib/git-worktree.js +22 -4
- package/lib/keys.js +26 -1
- package/lib/render/chrome.js +257 -79
- package/lib/render/dash-blocks.js +297 -46
- package/lib/render/dash-table.js +6 -1
- package/lib/render/files.js +23 -4
- package/lib/render/frame.js +4 -0
- package/lib/render/notes.js +13 -2
- package/lib/render/packages.js +93 -0
- package/lib/render/primitives.js +13 -6
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +107 -12
- package/lib/review.js +5 -4
- package/lib/session/actions.js +136 -30
- package/lib/session/branches.js +3 -5
- package/lib/session/commit.js +31 -4
- package/lib/session/compose.js +19 -4
- package/lib/session/dashboard.js +160 -15
- package/lib/session/dispatch.js +38 -4
- package/lib/session/files-pane.js +17 -2
- package/lib/session/find.js +94 -0
- package/lib/session/lifecycle.js +27 -0
- package/lib/session/navigation.js +2 -0
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +14 -0
- package/lib/session/tasks.js +3 -3
- package/lib/session/view.js +15 -2
- package/lib/session/watch.js +5 -0
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +14 -0
- package/lib/workspace.js +166 -0
- package/package.json +1 -1
package/lib/render/primitives.js
CHANGED
|
@@ -17,7 +17,8 @@ const fill = (width, fgRgb, bgRgb, color) =>
|
|
|
17
17
|
|
|
18
18
|
const markPrefix = (selected) => ` ${selected ? FILE_MARK : ' '} `;
|
|
19
19
|
|
|
20
|
-
const isListPane = (pane) =>
|
|
20
|
+
const isListPane = (pane) =>
|
|
21
|
+
LIST_PANES.includes(pane) || pane === 'dashboard' || pane === 'repos';
|
|
21
22
|
|
|
22
23
|
const isTaskView = (view) => view.item?.origin === 'task';
|
|
23
24
|
|
|
@@ -111,11 +112,19 @@ const paintListWindow = (count, focus, pane, lineAt) => {
|
|
|
111
112
|
return paneResult({ body, fileHits, editHits, cursor, listScroll: start });
|
|
112
113
|
};
|
|
113
114
|
|
|
115
|
+
const insertedAt = (extra, count) => {
|
|
116
|
+
if (extra.at === 'start') return 0;
|
|
117
|
+
if (extra.at !== 'index') return count;
|
|
118
|
+
const index = extra.index ?? count;
|
|
119
|
+
return Math.max(0, Math.min(index, count));
|
|
120
|
+
};
|
|
121
|
+
|
|
114
122
|
const paintCursorList = (items, cursor, pane, paintRow, extra = null) => {
|
|
115
123
|
const lineAt = (index) => {
|
|
116
124
|
const entry = items[index];
|
|
117
125
|
if (!entry) return null;
|
|
118
|
-
|
|
126
|
+
const selected = !extra && index === cursor;
|
|
127
|
+
return listLine(paintRow(entry, selected, index), index);
|
|
119
128
|
};
|
|
120
129
|
if (!extra) return paintListWindow(items.length, cursor, pane, lineAt);
|
|
121
130
|
const edit = listLine(extra.row, -1, extra.cursor, extra.edits);
|
|
@@ -124,10 +133,8 @@ const paintCursorList = (items, cursor, pane, paintRow, extra = null) => {
|
|
|
124
133
|
const replaced = (i) => (i === at ? edit : lineAt(i));
|
|
125
134
|
return paintListWindow(items.length, at, pane, replaced);
|
|
126
135
|
}
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const shift = first ? 1 : 0;
|
|
130
|
-
const inserted = (i) => (i === at ? edit : lineAt(i - shift));
|
|
136
|
+
const at = insertedAt(extra, items.length);
|
|
137
|
+
const inserted = (i) => (i === at ? edit : lineAt(i < at ? i : i - 1));
|
|
131
138
|
return paintListWindow(items.length + 1, at, pane, inserted);
|
|
132
139
|
};
|
|
133
140
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { THEME } = ansi;
|
|
5
|
+
const primitives = require('./primitives.js');
|
|
6
|
+
const { paintBodyFill, paneResult } = primitives;
|
|
7
|
+
const tiles = require('./tiles.js');
|
|
8
|
+
const { seg, spinner, equalGrid, pageWindow, placeEqualTiles } = tiles;
|
|
9
|
+
const { paintTile, composeBands, tileHits, pageFill } = tiles;
|
|
10
|
+
const { segmentsWidth, clipSegments } = tiles;
|
|
11
|
+
const table = require('./dash-table.js');
|
|
12
|
+
const { size, isHot } = table;
|
|
13
|
+
|
|
14
|
+
const REPO_MIN_H = 6;
|
|
15
|
+
const MARGIN_MIN_H = 12;
|
|
16
|
+
const EXT_LIMIT = 3;
|
|
17
|
+
const DIM_BACKS = {
|
|
18
|
+
head: THEME.dashDimHeadBg,
|
|
19
|
+
tile: THEME.dashDimTileBg,
|
|
20
|
+
row: THEME.dashDimRowBg,
|
|
21
|
+
};
|
|
22
|
+
const RUN_ICON = {
|
|
23
|
+
passed: ['✔', 'add'],
|
|
24
|
+
failed: ['✖', 'error'],
|
|
25
|
+
aborted: ['⊘', 'muted'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const extName = (key) => {
|
|
29
|
+
const text = `${key ?? ''}`;
|
|
30
|
+
if (!text || text === '(none)') return 'none';
|
|
31
|
+
return text.startsWith('.') ? text.slice(1) : text;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const blinkTone = (frame) => (Math.abs(frame) % 2 ? 'hot' : 'warn');
|
|
35
|
+
|
|
36
|
+
const spin = (frame) => seg(spinner(frame), 'warn', true);
|
|
37
|
+
|
|
38
|
+
const rowPending = (row) =>
|
|
39
|
+
!row.filesReady ||
|
|
40
|
+
!row.commitsReady ||
|
|
41
|
+
!row.diffReady ||
|
|
42
|
+
!row.tasksReady ||
|
|
43
|
+
!row.runsReady ||
|
|
44
|
+
!row.npmReady;
|
|
45
|
+
|
|
46
|
+
const fitAside = (left, right, width) => {
|
|
47
|
+
const rightW = segmentsWidth(right);
|
|
48
|
+
const room = Math.max(0, width - rightW - 1);
|
|
49
|
+
const clipped = clipSegments(left, room);
|
|
50
|
+
const gap = Math.max(1, width - segmentsWidth(clipped) - rightW);
|
|
51
|
+
return [...clipped, seg(' '.repeat(gap)), ...right];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const titleLine = (row, ctx, selected, width) => {
|
|
55
|
+
const hot = isHot(row.activityAt, ctx.now);
|
|
56
|
+
const tone = hot ? blinkTone(ctx.frame) : 'text';
|
|
57
|
+
const left = [seg(row.name, tone, selected || hot)];
|
|
58
|
+
if (hot) left.push(seg(' ●', tone, true));
|
|
59
|
+
const right = [];
|
|
60
|
+
if (rowPending(row)) right.push(spin(ctx.frame), seg(' '));
|
|
61
|
+
const label = row.filesReady ? size(row.bytes) : '…';
|
|
62
|
+
const sizeTone = row.filesReady ? 'text' : 'muted';
|
|
63
|
+
right.push(seg(label, sizeTone, row.filesReady));
|
|
64
|
+
return fitAside(left, right, width);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const extLine = (row, ctx) => {
|
|
68
|
+
if (!row.filesReady) return [spin(ctx.frame)];
|
|
69
|
+
const exts = row.exts.slice(0, EXT_LIMIT);
|
|
70
|
+
if (!exts.length) return [seg('no files', 'muted')];
|
|
71
|
+
const segs = [];
|
|
72
|
+
for (let i = 0; i < exts.length; i++) {
|
|
73
|
+
if (i) segs.push(seg(' '));
|
|
74
|
+
segs.push(seg(extName(exts[i].key), 'sha'));
|
|
75
|
+
segs.push(seg(` ${size(exts[i].bytes)}`, 'text'));
|
|
76
|
+
}
|
|
77
|
+
return segs;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const countValue = (ready, text, frame) =>
|
|
81
|
+
ready ? seg(text, 'text', true) : spin(frame);
|
|
82
|
+
|
|
83
|
+
const countLine = (row, ctx) => [
|
|
84
|
+
seg('tasks: ', 'muted'),
|
|
85
|
+
countValue(row.tasksReady, `${row.tasksDone}/${row.tasks}`, ctx.frame),
|
|
86
|
+
seg(' commits: ', 'muted'),
|
|
87
|
+
countValue(row.commitsReady, `${row.commits}`, ctx.frame),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const resultNote = (run) => {
|
|
91
|
+
const result = run.result;
|
|
92
|
+
if (!result) return [];
|
|
93
|
+
if (typeof result.tests === 'number') {
|
|
94
|
+
const note = [seg(` ${result.tests}`, 'muted')];
|
|
95
|
+
if (result.failed) note.push(seg(` ${result.failed} fail`, 'error'));
|
|
96
|
+
return note;
|
|
97
|
+
}
|
|
98
|
+
if (typeof result.errors === 'number') {
|
|
99
|
+
return [seg(` ${result.errors} err`, result.errors ? 'error' : 'muted')];
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const runLine = (row, ctx) => {
|
|
105
|
+
if (!row.runsReady) return [seg('run: ', 'muted'), spin(ctx.frame)];
|
|
106
|
+
const run = row.run;
|
|
107
|
+
if (!run) return [seg('run: ', 'muted'), seg('idle', 'muted')];
|
|
108
|
+
if (run.status === 'running') {
|
|
109
|
+
const seen = run.done + run.failed;
|
|
110
|
+
const total = run.expected > 0 ? `/${run.expected}` : '';
|
|
111
|
+
const fail = run.failed ? ` ${run.failed} fail` : '';
|
|
112
|
+
return [
|
|
113
|
+
seg(`${spinner(ctx.frame)} `, 'warn', true),
|
|
114
|
+
seg(run.name || 'run', 'text'),
|
|
115
|
+
seg(` ${seen}${total}${fail}`, 'warn'),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
const icon = RUN_ICON[run.status] ?? ['?', 'muted'];
|
|
119
|
+
return [
|
|
120
|
+
seg(`${icon[0]} `, icon[1], true),
|
|
121
|
+
seg(run.name || 'run', 'text'),
|
|
122
|
+
seg(` ${run.status}`, icon[1]),
|
|
123
|
+
...resultNote(run),
|
|
124
|
+
];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const npmLine = (row, ctx) => {
|
|
128
|
+
if (!row.npmReady) return [seg('deps: ', 'muted'), spin(ctx.frame)];
|
|
129
|
+
const npm = row.npm;
|
|
130
|
+
if (!npm || !npm.hasManifest) return [seg('no package.json', 'muted')];
|
|
131
|
+
const modules = npm.modules || { count: 0, bytes: 0 };
|
|
132
|
+
const segs = [
|
|
133
|
+
seg('deps: ', 'muted'),
|
|
134
|
+
seg(`${npm.deps}`, 'text'),
|
|
135
|
+
seg(' dev: ', 'muted'),
|
|
136
|
+
seg(`${npm.dev}`, 'text'),
|
|
137
|
+
seg(' all: ', 'muted'),
|
|
138
|
+
seg(`${modules.count}`, 'text'),
|
|
139
|
+
seg(' (', 'muted'),
|
|
140
|
+
seg(size(modules.bytes), 'text'),
|
|
141
|
+
seg(')', 'muted'),
|
|
142
|
+
];
|
|
143
|
+
if (npm.audit) {
|
|
144
|
+
segs.push(
|
|
145
|
+
seg(' '),
|
|
146
|
+
seg('🚨 ', 'error'),
|
|
147
|
+
seg(`${npm.audit}`, 'error', true),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (npm.outdated) {
|
|
151
|
+
segs.push(
|
|
152
|
+
seg(' '),
|
|
153
|
+
seg('⚠️ ', 'warn'),
|
|
154
|
+
seg(`${npm.outdated}`, 'warn', true),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return segs;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const diffLine = (row, ctx) => {
|
|
161
|
+
if (!row.diffReady) return [seg('diff: ', 'muted'), spin(ctx.frame)];
|
|
162
|
+
const diff = row.diff;
|
|
163
|
+
const added = diff.stagedAdded + diff.unstagedAdded;
|
|
164
|
+
const removed = diff.stagedRemoved + diff.unstagedRemoved;
|
|
165
|
+
return [
|
|
166
|
+
seg('diff: ', 'muted'),
|
|
167
|
+
seg(`+${diff.stagedAdded}/${added}`, 'add'),
|
|
168
|
+
seg(' '),
|
|
169
|
+
seg(`-${diff.stagedRemoved}/${removed}`, 'del'),
|
|
170
|
+
];
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const repoContent = (row, ctx, selected, width) => ({
|
|
174
|
+
titleLine: titleLine(row, ctx, selected, width),
|
|
175
|
+
lines: [
|
|
176
|
+
extLine(row, ctx),
|
|
177
|
+
countLine(row, ctx),
|
|
178
|
+
runLine(row, ctx),
|
|
179
|
+
diffLine(row, ctx),
|
|
180
|
+
npmLine(row, ctx),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const paintRepos = (model, bands, color) => {
|
|
185
|
+
const painted = new Map();
|
|
186
|
+
const ctx = { now: model.now, frame: model.frame };
|
|
187
|
+
const selected = model.tiles[model.cursor];
|
|
188
|
+
const selectedId = selected ? selected.id : '';
|
|
189
|
+
for (const band of bands) {
|
|
190
|
+
for (const place of band.tiles) {
|
|
191
|
+
const row = model.tiles.find((tile) => tile.id === place.id);
|
|
192
|
+
if (!row) continue;
|
|
193
|
+
const tile = { id: row.id, key: '', title: row.name };
|
|
194
|
+
const inner = Math.max(0, place.w - 2);
|
|
195
|
+
const picked = row.id === selectedId;
|
|
196
|
+
const content = repoContent(row, ctx, picked, inner);
|
|
197
|
+
const backs = picked ? null : DIM_BACKS;
|
|
198
|
+
const rows = paintTile(tile, content, place.w, band.h, color, backs);
|
|
199
|
+
painted.set(place.id, rows);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return painted;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const paintBodyRepos = (view, width, color, bodyH, headerLines) => {
|
|
206
|
+
const model = view.repos;
|
|
207
|
+
if (!model || !model.tiles.length) {
|
|
208
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
209
|
+
return paneResult({ body: new Array(bodyH).fill(blank) });
|
|
210
|
+
}
|
|
211
|
+
const margin = bodyH >= MARGIN_MIN_H ? 1 : 0;
|
|
212
|
+
const height = Math.max(1, bodyH - margin * 2);
|
|
213
|
+
const grid = equalGrid(width, height, model.tiles.length, 34, REPO_MIN_H);
|
|
214
|
+
const page = pageWindow(model.tiles.length, model.cursor, grid.cap);
|
|
215
|
+
const shown = model.tiles.slice(page.start, page.end);
|
|
216
|
+
const bands = placeEqualTiles(shown, grid);
|
|
217
|
+
model.cols = grid.cols;
|
|
218
|
+
const painted = paintRepos(model, bands, color);
|
|
219
|
+
const rows = composeBands(bands, painted, width, height, color);
|
|
220
|
+
const pad = new Array(margin).fill(pageFill(width, color));
|
|
221
|
+
const body = [...pad, ...rows, ...pad];
|
|
222
|
+
const fileHits = tileHits(bands, headerLines + margin);
|
|
223
|
+
return paneResult({ body, fileHits });
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
module.exports = { REPO_MIN_H, extName, paintBodyRepos };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { THEME, paint } = ansi;
|
|
5
|
+
|
|
6
|
+
const paintPathText = (text, fg, bg, color, bold, spans) => {
|
|
7
|
+
if (!spans.length) return paint(text, fg, bg, color, bold);
|
|
8
|
+
let out = '';
|
|
9
|
+
let at = 0;
|
|
10
|
+
for (const span of spans) {
|
|
11
|
+
if (span.start > at) {
|
|
12
|
+
out += paint(text.slice(at, span.start), fg, bg, color, bold);
|
|
13
|
+
}
|
|
14
|
+
const on = span.on === true;
|
|
15
|
+
const markFg = on ? THEME.searchOnFg : THEME.searchFg;
|
|
16
|
+
const markBg = on ? THEME.searchOnBg : THEME.searchBg;
|
|
17
|
+
const slice = text.slice(span.start, span.end);
|
|
18
|
+
out += paint(slice, markFg, markBg, color, true);
|
|
19
|
+
at = span.end;
|
|
20
|
+
}
|
|
21
|
+
if (at < text.length) out += paint(text.slice(at), fg, bg, color, bold);
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = { paintPathText };
|
package/lib/render/tiles.js
CHANGED
|
@@ -19,6 +19,7 @@ const seg = (text, tone = 'text', bold = false) => ({
|
|
|
19
19
|
text: `${text}`,
|
|
20
20
|
tone,
|
|
21
21
|
bold,
|
|
22
|
+
bg: null,
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
const toneColors = () => ({
|
|
@@ -30,7 +31,12 @@ const toneColors = () => ({
|
|
|
30
31
|
warn: THEME.warnFg,
|
|
31
32
|
error: THEME.errorFg,
|
|
32
33
|
sha: THEME.shaFg,
|
|
33
|
-
|
|
34
|
+
dep: THEME.pkgDepFg,
|
|
35
|
+
dev: THEME.pkgDevFg,
|
|
36
|
+
pkg: THEME.pkgFg,
|
|
37
|
+
chain: THEME.pkgChainFg,
|
|
38
|
+
heat: THEME.heatFg,
|
|
39
|
+
blink: THEME.heatBlinkFg,
|
|
34
40
|
key: THEME.dashKeyFg,
|
|
35
41
|
});
|
|
36
42
|
|
|
@@ -78,7 +84,8 @@ const paintSegments = (segs, width, bg, color, fill = ' ', colors = null) => {
|
|
|
78
84
|
}
|
|
79
85
|
let out = '';
|
|
80
86
|
for (const item of clipped) {
|
|
81
|
-
|
|
87
|
+
const pieceBg = item.bg ?? bg;
|
|
88
|
+
out += paint(item.text, ink[item.tone], pieceBg, color, item.bold);
|
|
82
89
|
}
|
|
83
90
|
return out + paint(fill.repeat(pad), ink.muted, bg, color);
|
|
84
91
|
};
|
|
@@ -173,9 +180,15 @@ const paintFramed = (line, width, bg, color, fill = ' ', colors = null) => {
|
|
|
173
180
|
return edge + paintSegments(line, inner, bg, color, fill, colors) + edge;
|
|
174
181
|
};
|
|
175
182
|
|
|
176
|
-
const
|
|
183
|
+
const paintFlushRight = (line, width, bg, color) => {
|
|
184
|
+
const edge = paintSegments([seg(' ')], 1, bg, color);
|
|
185
|
+
const inner = Math.max(0, width - 1);
|
|
186
|
+
return edge + paintSegments(line, inner, bg, color);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const paintHead = (tile, content, width, color, headBg) => {
|
|
177
190
|
const fill = color ? ' ' : RULE;
|
|
178
|
-
const bg =
|
|
191
|
+
const bg = headBg;
|
|
179
192
|
const colors = headColors();
|
|
180
193
|
if (content.titleLine) {
|
|
181
194
|
return paintFramed(content.titleLine, width, bg, color, fill, colors);
|
|
@@ -184,22 +197,28 @@ const paintHead = (tile, content, width, color) => {
|
|
|
184
197
|
return paintSegments(segs, width, bg, color, fill, colors);
|
|
185
198
|
};
|
|
186
199
|
|
|
187
|
-
const rowBackground = (index, filled) => {
|
|
188
|
-
if (filled && index % 2 === 1) return
|
|
189
|
-
return
|
|
200
|
+
const rowBackground = (index, filled, backs) => {
|
|
201
|
+
if (filled && index % 2 === 1) return backs.row;
|
|
202
|
+
return backs.tile;
|
|
190
203
|
};
|
|
191
204
|
|
|
192
|
-
const paintTile = (tile, content, width, height, color) => {
|
|
205
|
+
const paintTile = (tile, content, width, height, color, backs = null) => {
|
|
206
|
+
const shade = backs ?? {
|
|
207
|
+
head: THEME.dashHeadBg,
|
|
208
|
+
tile: THEME.dashTileBg,
|
|
209
|
+
row: THEME.dashRowBg,
|
|
210
|
+
};
|
|
193
211
|
const { lines, footer } = content;
|
|
194
|
-
const rows = [paintHead(tile, content, width, color)];
|
|
212
|
+
const rows = [paintHead(tile, content, width, color, shade.head)];
|
|
195
213
|
const bodyRoom = footer ? height - 2 : height - 1;
|
|
196
214
|
for (let i = 0; i < bodyRoom; i++) {
|
|
197
|
-
const bg = rowBackground(i, i < lines.length);
|
|
215
|
+
const bg = rowBackground(i, i < lines.length, shade);
|
|
198
216
|
rows.push(paintFramed(lines[i] ?? [], width, bg, color));
|
|
199
217
|
}
|
|
200
218
|
if (footer && height > 1) {
|
|
201
|
-
const bg = rowBackground(bodyRoom, true);
|
|
202
|
-
|
|
219
|
+
const bg = rowBackground(bodyRoom, true, shade);
|
|
220
|
+
const paintRow = content.footerFlush ? paintFlushRight : paintFramed;
|
|
221
|
+
rows.push(paintRow(footer, width, bg, color));
|
|
203
222
|
}
|
|
204
223
|
return rows;
|
|
205
224
|
};
|
|
@@ -229,6 +248,78 @@ const composeBands = (bands, painted, width, height, color) => {
|
|
|
229
248
|
return rows.slice(0, height);
|
|
230
249
|
};
|
|
231
250
|
|
|
251
|
+
const gridSize = (width, height, cols, rows) => ({
|
|
252
|
+
tileW: Math.floor((width - (cols - 1) * GAP_X) / cols),
|
|
253
|
+
tileH: Math.floor((height - (rows - 1) * GAP_Y) / rows),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const equalGrid = (width, height, count, minW = MIN_W, minH = MIN_H) => {
|
|
257
|
+
const safe = Math.max(1, count);
|
|
258
|
+
const colCap = Math.max(
|
|
259
|
+
1,
|
|
260
|
+
Math.min(safe, Math.floor((width + GAP_X) / (minW + GAP_X)) || 1),
|
|
261
|
+
);
|
|
262
|
+
const rowCap = Math.max(
|
|
263
|
+
1,
|
|
264
|
+
Math.min(safe, Math.floor((height + GAP_Y) / (minH + GAP_Y)) || 1),
|
|
265
|
+
);
|
|
266
|
+
let best = null;
|
|
267
|
+
for (let cols = 1; cols <= colCap; cols++) {
|
|
268
|
+
for (let rows = 1; rows <= rowCap; rows++) {
|
|
269
|
+
const size = gridSize(width, height, cols, rows);
|
|
270
|
+
if (size.tileW < minW || size.tileH < minH) continue;
|
|
271
|
+
const cap = cols * rows;
|
|
272
|
+
const shown = Math.min(safe, cap);
|
|
273
|
+
const score = shown * 1000000 + size.tileW * size.tileH;
|
|
274
|
+
if (!best || score > best.score) {
|
|
275
|
+
best = { cols, rows, cap, ...size, score };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (best) return best;
|
|
280
|
+
return {
|
|
281
|
+
cols: 1,
|
|
282
|
+
rows: 1,
|
|
283
|
+
cap: 1,
|
|
284
|
+
tileW: Math.max(1, width),
|
|
285
|
+
tileH: Math.max(1, height),
|
|
286
|
+
score: 0,
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const pageWindow = (count, cursor, cap) => {
|
|
291
|
+
const room = Math.max(1, cap);
|
|
292
|
+
if (count <= room) return { start: 0, end: count };
|
|
293
|
+
const at = Math.min(Math.max(0, cursor), count - 1);
|
|
294
|
+
let start = Math.floor(at / room) * room;
|
|
295
|
+
if (start + room > count) start = count - room;
|
|
296
|
+
return { start, end: start + room };
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const placeEqualTiles = (tiles, grid) => {
|
|
300
|
+
const bands = [];
|
|
301
|
+
const cols = Math.max(1, grid.cols);
|
|
302
|
+
const rows = Math.ceil(tiles.length / cols);
|
|
303
|
+
for (let row = 0; row < rows; row++) {
|
|
304
|
+
const slice = tiles.slice(row * cols, (row + 1) * cols);
|
|
305
|
+
if (!slice.length) break;
|
|
306
|
+
const placed = [];
|
|
307
|
+
for (let i = 0; i < slice.length; i++) {
|
|
308
|
+
placed.push({
|
|
309
|
+
id: slice[i].id,
|
|
310
|
+
x: i * (grid.tileW + GAP_X),
|
|
311
|
+
w: grid.tileW,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
bands.push({
|
|
315
|
+
y: row * (grid.tileH + GAP_Y),
|
|
316
|
+
h: grid.tileH,
|
|
317
|
+
tiles: placed,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return bands;
|
|
321
|
+
};
|
|
322
|
+
|
|
232
323
|
const tileHits = (bands, headerLines) => {
|
|
233
324
|
const hits = [];
|
|
234
325
|
for (const band of bands) {
|
|
@@ -253,6 +344,10 @@ module.exports = {
|
|
|
253
344
|
spinner,
|
|
254
345
|
titleSegments,
|
|
255
346
|
layoutTiles,
|
|
347
|
+
equalGrid,
|
|
348
|
+
pageWindow,
|
|
349
|
+
placeEqualTiles,
|
|
350
|
+
paintSegments,
|
|
256
351
|
paintTile,
|
|
257
352
|
composeBands,
|
|
258
353
|
tileHits,
|
package/lib/review.js
CHANGED
|
@@ -75,7 +75,7 @@ const setCode = (store, key, note) => {
|
|
|
75
75
|
putNote(store, store.code, key, note, Boolean(note.clear));
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
const addTask = (store, file, text = '', done = false, kind = '
|
|
78
|
+
const addTask = (store, file, text = '', done = false, kind = 'features') => {
|
|
79
79
|
const id = store.nextTaskId;
|
|
80
80
|
store.nextTaskId += 1;
|
|
81
81
|
const todo = { id, file, text, done, kind: taskKind({ kind }) };
|
|
@@ -202,11 +202,12 @@ const SECTION_LINE = {
|
|
|
202
202
|
const BACKLOG_HEAD = 'Backlog';
|
|
203
203
|
const LEGACY_BACKLOG = 'TODOs';
|
|
204
204
|
const TASK_TITLES = new Map([
|
|
205
|
-
[BACKLOG_HEAD, '
|
|
206
|
-
[LEGACY_BACKLOG, '
|
|
207
|
-
['Issues', '
|
|
205
|
+
[BACKLOG_HEAD, 'debt'],
|
|
206
|
+
[LEGACY_BACKLOG, 'debt'],
|
|
207
|
+
['Issues', 'debt'],
|
|
208
208
|
['Bug Reports', 'bugs'],
|
|
209
209
|
['Feature Requests', 'features'],
|
|
210
|
+
...TASK_KINDS.map((kind) => [kind.title, kind.id]),
|
|
210
211
|
]);
|
|
211
212
|
|
|
212
213
|
const AGENT_INSTRUCTIONS = [
|