@anthodev/pi-tools-style 0.1.3 → 0.2.0
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/README.md +155 -52
- package/index.ts +116 -90
- package/package.json +5 -5
- package/src/config-menu.ts +261 -0
- package/src/frame.ts +282 -97
- package/src/render-decorator.ts +181 -200
- package/src/settings.ts +202 -30
- package/src/tool-category.ts +3 -30
- package/src/tool-presentation.ts +1484 -0
- package/src/tool-renderer.ts +752 -0
- package/src/tool-spinner.ts +72 -89
- package/src/web-search-presentation.ts +986 -0
package/src/tool-spinner.ts
CHANGED
|
@@ -1,111 +1,94 @@
|
|
|
1
1
|
import type { IconMode } from "./tool-icon.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/** A public render-context state and its invalidation, never a Pi component. */
|
|
4
|
+
export interface ToolSpinnerTarget {
|
|
5
|
+
state: object;
|
|
6
|
+
invalidate: () => void;
|
|
5
7
|
}
|
|
6
|
-
|
|
7
|
-
interface ToolSpinnerTarget {
|
|
8
|
-
executionStarted?: unknown;
|
|
9
|
-
isPartial?: unknown;
|
|
10
|
-
ui?: SpinnerUi;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
8
|
interface SpinnerRuntime {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
frameIndex: number;
|
|
10
|
+
targets: Set<WeakRef<ToolSpinnerTarget>>;
|
|
11
|
+
references: WeakMap<object, WeakRef<ToolSpinnerTarget>>;
|
|
12
|
+
timer: NodeJS.Timeout | undefined;
|
|
17
13
|
}
|
|
18
|
-
|
|
19
14
|
const SPINNER_RUNTIME_KEY = Symbol.for("pi-tools-style:tool-spinner");
|
|
20
15
|
const FRAME_INTERVAL_MS = 80;
|
|
21
|
-
const STALE_TARGET_MS = 2_000;
|
|
22
16
|
const ASCII_FRAMES = ["|", "/", "-", "\\"] as const;
|
|
23
17
|
const NERD_FONT_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const runtime = getRuntime();
|
|
30
|
-
if (!isExecuting(target)) {
|
|
31
|
-
runtime.targets.delete(target);
|
|
32
|
-
stopTickerIfIdle(runtime);
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
runtime.targets.set(target, Date.now());
|
|
37
|
-
ensureTicker(runtime);
|
|
38
|
-
const frames = mode === "nerd-font" ? NERD_FONT_FRAMES : ASCII_FRAMES;
|
|
39
|
-
return frames[runtime.frameIndex % frames.length];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function clearToolSpinners(): void {
|
|
43
|
-
const runtime = getRuntime();
|
|
44
|
-
if (runtime.timer) clearInterval(runtime.timer);
|
|
45
|
-
runtime.timer = undefined;
|
|
46
|
-
runtime.frameIndex = 0;
|
|
47
|
-
runtime.targets.clear();
|
|
19
|
+
/** Public tools use their gated ticker; shell may supply its native-loader clock index. */
|
|
20
|
+
export function getToolSpinnerFrame(mode: IconMode, frameIndex = getRuntime().frameIndex): string {
|
|
21
|
+
const frames = mode === "nerd-font" ? NERD_FONT_FRAMES : ASCII_FRAMES;
|
|
22
|
+
return frames[frameIndex % frames.length]!;
|
|
48
23
|
}
|
|
49
24
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
25
|
+
/** Unconfirmed replay/export states use a static initial frame. */
|
|
26
|
+
export function toolSpinnerFrame(state: object, mode: IconMode): string {
|
|
27
|
+
const current = getRuntime();
|
|
28
|
+
const ref = current.references.get(state);
|
|
29
|
+
if (ref && current.targets.has(ref) && ref.deref()) return getToolSpinnerFrame(mode);
|
|
30
|
+
return mode === "nerd-font" ? NERD_FONT_FRAMES[0] : ASCII_FRAMES[0];
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
stopTickerIfIdle(runtime);
|
|
33
|
+
export function setToolSpinnerActive(target: ToolSpinnerTarget, active: boolean): void {
|
|
34
|
+
const current = getRuntime();
|
|
35
|
+
let ref = current.references.get(target.state);
|
|
36
|
+
if (!active) {
|
|
37
|
+
if (ref?.deref() === target) {
|
|
38
|
+
current.targets.delete(ref);
|
|
39
|
+
current.references.delete(target.state);
|
|
40
|
+
}
|
|
41
|
+
stopTickerIfIdle(current);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!ref || ref.deref() !== target) {
|
|
45
|
+
if (ref) current.targets.delete(ref);
|
|
46
|
+
ref = new WeakRef(target);
|
|
47
|
+
current.references.set(target.state, ref);
|
|
48
|
+
}
|
|
49
|
+
current.targets.add(ref);
|
|
50
|
+
if (!current.timer) {
|
|
51
|
+
current.timer = setInterval(() => tick(current), FRAME_INTERVAL_MS);
|
|
52
|
+
current.timer.unref?.();
|
|
53
|
+
}
|
|
80
54
|
}
|
|
81
55
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
56
|
+
export function clearToolSpinners(): void {
|
|
57
|
+
const current = getRuntime();
|
|
58
|
+
clearInterval(current.timer);
|
|
59
|
+
current.timer = undefined;
|
|
60
|
+
current.frameIndex = 0;
|
|
61
|
+
current.targets.clear();
|
|
62
|
+
current.references = new WeakMap();
|
|
87
63
|
}
|
|
88
64
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
65
|
+
function tick(current: SpinnerRuntime): void {
|
|
66
|
+
current.frameIndex++;
|
|
67
|
+
for (const ref of current.targets) {
|
|
68
|
+
const target = ref.deref();
|
|
69
|
+
if (!target) { current.targets.delete(ref); continue; }
|
|
70
|
+
try {
|
|
71
|
+
target.invalidate();
|
|
72
|
+
} catch {
|
|
73
|
+
// Detached TUI contexts cannot keep a presentation ticker alive.
|
|
74
|
+
current.targets.delete(ref);
|
|
75
|
+
if (current.references.get(target.state) === ref) current.references.delete(target.state);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
stopTickerIfIdle(current);
|
|
92
79
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
function stopTickerIfIdle(current: SpinnerRuntime): void {
|
|
81
|
+
if (current.targets.size > 0 || !current.timer) return;
|
|
82
|
+
clearInterval(current.timer);
|
|
83
|
+
current.timer = undefined;
|
|
84
|
+
current.frameIndex = 0;
|
|
96
85
|
}
|
|
97
|
-
|
|
98
86
|
function getRuntime(): SpinnerRuntime {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
targets: new Map(),
|
|
107
|
-
timer: undefined,
|
|
108
|
-
};
|
|
109
|
-
Reflect.set(globalThis, SPINNER_RUNTIME_KEY, runtime);
|
|
110
|
-
return runtime;
|
|
87
|
+
const current = Reflect.get(globalThis, SPINNER_RUNTIME_KEY) as SpinnerRuntime | undefined;
|
|
88
|
+
if (current?.targets instanceof Set) return current;
|
|
89
|
+
// Reload replaces the old private-component ticker, not a second clock.
|
|
90
|
+
clearInterval(current?.timer);
|
|
91
|
+
const next: SpinnerRuntime = { frameIndex: 0, targets: new Set(), references: new WeakMap(), timer: undefined };
|
|
92
|
+
Reflect.set(globalThis, SPINNER_RUNTIME_KEY, next);
|
|
93
|
+
return next;
|
|
111
94
|
}
|