@quolu/lattice 0.12.19 → 0.12.20
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/package.json +1 -1
- package/src/todo-gantt-html.mjs +36 -11
package/package.json
CHANGED
package/src/todo-gantt-html.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { serializeJsonForScript } from './todo-markdown-renderer.mjs';
|
|
4
4
|
import { renderTodoGanttSvg, TODO_GANTT_STATUS_PRESENTATION } from './todo-gantt-svg.mjs';
|
|
5
5
|
|
|
6
|
-
export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.
|
|
6
|
+
export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.v16';
|
|
7
7
|
export const TODO_GANTT_PROSE_MAX_BYTES = 8 * 1024 * 1024;
|
|
8
8
|
export const TODO_GANTT_HTML_MAX_BYTES = 24 * 1024 * 1024;
|
|
9
9
|
|
|
@@ -43,6 +43,10 @@ function refKey(ref) {
|
|
|
43
43
|
return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function compareText(left, right) {
|
|
47
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
function digest(value) {
|
|
47
51
|
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
48
52
|
}
|
|
@@ -108,16 +112,37 @@ function renderTaskIndexEntry(section, lookup) {
|
|
|
108
112
|
return `<li><button type="button" data-select-node-key="${escapeHtmlAttribute(selectKey)}"><span class="task-index-status status-${escapeHtmlAttribute(section.state.status)}" role="img" aria-label="${escapeHtmlAttribute(status.label)}">${escapeHtmlText(status.mark)}</span><span class="task-index-reference">${escapeHtmlText(taskReference(section, lookup))}</span><strong>${escapeHtmlText(section.task.title)}</strong>${blockedReason}</button></li>`;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
/** plan_key -> 最終活動時刻。journalの末尾eventが、そのplanが最後に動いた時点。 */
|
|
116
|
+
function planActivity(readModel) {
|
|
117
|
+
return new Map((readModel?.members ?? []).map((member) => {
|
|
118
|
+
const events = member.journal?.events ?? [];
|
|
119
|
+
const last = events.at(-1)?.recorded_at ?? events[0]?.recorded_at ?? '';
|
|
120
|
+
return [member.plan.plan_key, last];
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderTaskIndex(sections, lookup, folds = new Set(), activity = new Map()) {
|
|
125
|
+
const byPlan = new Map();
|
|
113
126
|
for (const section of sections) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
plan = { planKey: section.ref.plan_key, tasks: [] };
|
|
117
|
-
plans.push(plan);
|
|
118
|
-
}
|
|
119
|
-
plan.tasks.push(section);
|
|
127
|
+
if (!byPlan.has(section.ref.plan_key)) byPlan.set(section.ref.plan_key, []);
|
|
128
|
+
byPlan.get(section.ref.plan_key).push(section);
|
|
120
129
|
}
|
|
130
|
+
// 全ToDoが図から外れたplanは終わった仕事。読む側が先に見たいのは動いているplanなので、
|
|
131
|
+
// 動いているものを最終活動の新しい順で上へ、終わったものを古い順で下へまとめる。
|
|
132
|
+
// plan内のToDo順は登録順のまま触らない。
|
|
133
|
+
const plans = [...byPlan.entries()].map(([planKey, tasks]) => ({
|
|
134
|
+
planKey,
|
|
135
|
+
tasks,
|
|
136
|
+
settled: tasks.every((section) => folds.has(refKey(section.ref))),
|
|
137
|
+
lastActivity: activity.get(planKey) ?? '',
|
|
138
|
+
}));
|
|
139
|
+
plans.sort((left, right) => {
|
|
140
|
+
if (left.settled !== right.settled) return left.settled ? 1 : -1;
|
|
141
|
+
const order = left.settled
|
|
142
|
+
? compareText(left.lastActivity, right.lastActivity)
|
|
143
|
+
: compareText(right.lastActivity, left.lastActivity);
|
|
144
|
+
return order !== 0 ? order : compareText(left.planKey, right.planKey);
|
|
145
|
+
});
|
|
121
146
|
return plans.map((plan) => {
|
|
122
147
|
const drawn = plan.tasks.filter((section) => !folds.has(refKey(section.ref)));
|
|
123
148
|
const folded = plan.tasks.filter((section) => folds.has(refKey(section.ref)));
|
|
@@ -247,8 +272,8 @@ function renderRightPane(sections, layout, presentation, readModel) {
|
|
|
247
272
|
: '<p class="fold-note">完走済みのため図には描いていません。図に出すには <code>lattice todo gantt --scope all</code> を実行してください。</p>';
|
|
248
273
|
return `<article class="task-detail" data-detail-key="${escapeHtmlAttribute(key)}" hidden><header><span class="detail-status status-${escapeHtmlAttribute(section.state.status)}">${escapeHtmlText(status.mark)} ${escapeHtmlText(status.label)}</span><span class="detail-reference">${escapeHtmlText(taskReference(section, lookup))}</span></header><h1>${escapeHtmlText(section.task.title)}</h1><p class="detail-category"><strong>カテゴリ:</strong> ${escapeHtmlText(category)}</p>${categoryDescription}<p><strong>正規ID:</strong> <code>${escapeHtmlText(`${section.ref.plan_key}/${section.task.task_id}`)}</code></p>${blockedReason}${readiness}${foldedNote}<section><h2>前提工程</h2>${renderRelationList(incoming.get(key), sectionByKey, lookup, '登録済みの前提工程はありません。', folds)}</section><section><h2>後続工程</h2>${renderRelationList(outgoing.get(key), sectionByKey, lookup, '登録済みの後続工程はありません。', folds)}</section><p class="anchor-status">${escapeHtmlText(anchorText)}</p><details class="task-diagnostics"><summary>開発者向け診断</summary><dl><dt>canonical ref</dt><dd><code>${escapeHtmlText(`${section.ref.project_id}/${section.ref.plan_key}/${section.task.task_id}`)}</code></dd><dt>anchor</dt><dd>${escapeHtmlText(section.anchorOutcome.anchored ? 'verified' : section.anchorOutcome.reason)}</dd></dl></details></article>`;
|
|
249
274
|
}).join('');
|
|
250
|
-
const taskIndex = renderTaskIndex(sections, lookup, folds);
|
|
251
|
-
return `<div class="right-toolbar"><button type="button" data-show-overview>概要</button><button type="button" data-show-selected hidden>選択工程へ戻る</button><button type="button" data-show-task-index>全工程一覧</button></div><div class="right-content">${overview}<div data-right-panel="details" hidden>${details}</div><section class="task-index" data-right-panel="task-index" hidden><h1>全工程</h1><p>Lattice
|
|
275
|
+
const taskIndex = renderTaskIndex(sections, lookup, folds, planActivity(readModel));
|
|
276
|
+
return `<div class="right-toolbar"><button type="button" data-show-overview>概要</button><button type="button" data-show-selected hidden>選択工程へ戻る</button><button type="button" data-show-task-index>全工程一覧</button></div><div class="right-content">${overview}<div data-right-panel="details" hidden>${details}</div><section class="task-index" data-right-panel="task-index" hidden><h1>全工程</h1><p>Latticeに登録された全工程を現在の状態とともに表示しています。planは動いているものを最終活動の新しい順で上に、完走したものを古い順で下にまとめ、plan内は登録順です。</p>${taskIndex}</section></div>`;
|
|
252
277
|
}
|
|
253
278
|
|
|
254
279
|
function renderDiagramLegend(presentation, layout = null, expandable = false) {
|