repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/afk/src/widget.ts
Rawimport type { Theme } from "@earendil-works/pi-coding-agent";
import {
type Component,
type TUI,
truncateToWidth,
} from "@earendil-works/pi-tui";
const AWAKE = [" /\\_/\\", " ( o.o ) working", " > ^ <"];
const BLINK = [" /\\_/\\", " ( -.- ) working", " > ^ <"];
const NAPPING = [" /\\_/\\", " ( -.- ) zzz", " > ^ <"];
export type AfkWidget = Component & { invalidate(): void; dispose(): void };
export function createAfkWidget(
tui: TUI,
theme: Theme,
isNapping: () => boolean,
): AfkWidget {
let blinking = false;
let disposed = false;
let timer: ReturnType<typeof setTimeout> | undefined;
const syncAnimation = (): void => {
if (disposed || isNapping()) {
clearTimeout(timer);
timer = undefined;
return;
}
if (timer !== undefined) return;
// Only the blink transitions change pixels; naps need no timer.
timer = setTimeout(
() => {
timer = undefined;
if (!isNapping()) blinking = !blinking;
tui.requestRender();
syncAnimation();
},
blinking ? 700 : 2_100,
);
timer.unref?.();
};
syncAnimation();
return {
render(width: number): string[] {
const art = isNapping() ? NAPPING : blinking ? BLINK : AWAKE;
return art.map((line) =>
truncateToWidth(theme.fg("accent", line), width),
);
},
invalidate(): void {
syncAnimation();
tui.requestRender();
},
dispose(): void {
if (disposed) return;
disposed = true;
syncAnimation();
},
};
}