ctql
Version:
Clock Time Quest Log: Interactive, RPG-style quest tracker for Solo Devs
140 lines (129 loc) • 3.69 kB
text/typescript
import { loadState, state, StateOptions } from "../state";
import quest from "../quest_handling";
import { progressBar } from "../decorators/progress";
import { msToHumanReadable, msToMinutes } from "../time";
import { pointsToMinutes } from "../points_to_time_goal";
import fig from "../decorators/figlet";
import * as p from "@clack/prompts";
import { exitMsg } from "../decorators/exit_msg";
function isQuestLineComplete() {
loadState();
const numQuests = (state.get(StateOptions.NumQuests) as number) || undefined;
const questsCompleted =
(state.get(StateOptions.NumQuestsFinished) as number) || undefined;
if (!numQuests) {
return { ok: false, result: false };
}
if (numQuests > 0 && numQuests == questsCompleted) {
return { ok: true, result: true };
}
return { ok: true, result: false };
}
export function displayStats() {
const isComplete = isQuestLineComplete();
const messages: string[] = [];
let time: number = 0;
if (isComplete.ok == false) {
exitMsg("State malformed");
}
if (isComplete.ok == true && isComplete.result == false) {
const currentQuestId = state.get(StateOptions.CurrentQuestId) as
| number
| undefined;
if (!currentQuestId) {
return { ok: false, data: "🏰 No Quest started." };
}
let currentQuest = quest.quests.get(currentQuestId);
if (!currentQuest) {
return { ok: false, data: "🏰 No Quest started." };
}
messages.push(`\n[Current Step Stats]\n`);
messages.push(`[Name]: ${currentQuest.name}\n`);
messages.push(`[Description]: ${currentQuest.description}\n`);
if (currentQuest.timeStarted) {
const now = new Date();
time = now.getTime() - currentQuest.timeStarted.getTime();
messages.push(`[Time Spent]: ${msToHumanReadable(time)}\n`);
let pointsEstimate = pointsToMinutes(currentQuest.points);
const timeSpentMinutes = msToMinutes(time);
const percentageUsed = (
(timeSpentMinutes / pointsEstimate) *
100
).toFixed(2);
if (pointsEstimate > timeSpentMinutes) {
messages.push(
`[Estimated Time Remaining]: ${(
pointsEstimate - timeSpentMinutes
).toFixed(1)}0 minutes.\n`
);
}
messages.push(`[Used]: ${percentageUsed}% of estimated time\n`);
}
}
messages.push(`\n[Quest Stats]\n`);
let numQuests = quest.quests.size;
let numQuestsFinished = state.get(StateOptions.NumQuestsFinished) as
| number
| undefined;
if (numQuestsFinished) {
messages.push(
`[Progress:]\n${progressBar(numQuestsFinished, numQuests)}\n`
);
}
const timeElapsed =
(state.get(StateOptions.TimeElapsed) as number | undefined) || 0;
messages.push(
`[Total Project Time]: ${msToHumanReadable(timeElapsed + time)}\n`
);
if (!numQuestsFinished && !timeElapsed) {
messages.push(
`Complete a Quest Step to see the rest of your Quest Stats\n`
);
}
return {
ok: true,
data: messages.join(""),
};
}
export function renderStats() {
fig.title.p("CTQL");
fig.subtitle.p("stats");
quest.load();
const statsRes = displayStats();
if (statsRes.ok) {
p.log.info(statsRes.data);
console.log("use [ctrl+d] to mark complete, [q] to quit process");
} else {
p.log.warn(statsRes.data);
}
}
let intervalId: NodeJS.Timeout | null;
let isPaused = false;
function startInterval() {
if (!isPaused) {
intervalId = setInterval(() => {
renderStats();
}, 1000);
}
}
function pauseInterval() {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
isPaused = true;
console.log("Paused.");
}
}
function resumeInterval() {
if (isPaused) {
isPaused = false;
startInterval();
}
}
const render = {
do: renderStats,
start: startInterval,
pause: pauseInterval,
resume: resumeInterval,
};
export default render;