lsd-pi 1.1.4 → 1.1.6
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 +2 -1
- package/dist/headless-ui.js +2 -0
- package/dist/onboarding.js +11 -8
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
- package/dist/resources/extensions/cache-timer/index.js +5 -0
- package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/dist/resources/extensions/codex-rotate/README.md +9 -3
- package/dist/resources/extensions/codex-rotate/commands.js +15 -8
- package/dist/resources/extensions/codex-rotate/index.js +17 -8
- package/dist/resources/extensions/memory/auto-extract.js +196 -80
- package/dist/resources/extensions/memory/dream.js +86 -19
- package/dist/resources/extensions/shared/rtk.js +89 -87
- package/dist/resources/extensions/subagent/index.js +33 -7
- package/dist/startup-model-validation.js +12 -2
- package/dist/update-check.js +2 -2
- package/dist/update-cmd.js +3 -3
- package/dist/welcome-screen.js +43 -14
- package/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +43 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +18 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
- package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +36 -22
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +23 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -77
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +4 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +49 -13
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +251 -39
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
- package/packages/pi-coding-agent/package.json +9 -4
- package/packages/pi-coding-agent/src/core/agent-session.clear-queue.test.ts +50 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +50 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
- package/packages/pi-coding-agent/src/core/settings-manager.ts +27 -0
- package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
- package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +37 -24
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +22 -70
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +45 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +104 -81
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +5 -19
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +55 -13
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +296 -48
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
- package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
- package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js +105 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +4 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +57 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/loader.d.ts +26 -6
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +178 -18
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +65 -3
- package/packages/pi-tui/src/components/loader.ts +196 -19
- package/pkg/dist/modes/interactive/theme/themes.js +2 -2
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
- package/src/resources/extensions/cache-timer/index.ts +102 -96
- package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/src/resources/extensions/codex-rotate/README.md +9 -3
- package/src/resources/extensions/codex-rotate/commands.ts +335 -329
- package/src/resources/extensions/codex-rotate/index.ts +85 -75
- package/src/resources/extensions/memory/auto-extract.ts +330 -204
- package/src/resources/extensions/memory/dream.ts +88 -21
- package/src/resources/extensions/memory/tests/auto-extract.test.ts +200 -144
- package/src/resources/extensions/shared/rtk.js +112 -0
- package/src/resources/extensions/subagent/index.ts +35 -6
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
import type { TUI } from "../tui.js";
|
|
2
2
|
import { Text } from "./text.js";
|
|
3
3
|
/**
|
|
4
|
-
* Loader component
|
|
4
|
+
* Loader component — braille spinner + animated word cycling.
|
|
5
|
+
*
|
|
6
|
+
* Each word types in character by character (typewriter), holds with a moving
|
|
7
|
+
* blue→cyan shimmer gradient, then erases right-to-left before the next word.
|
|
5
8
|
*/
|
|
6
9
|
export declare class Loader extends Text {
|
|
7
10
|
private spinnerColorFn;
|
|
8
|
-
private
|
|
9
|
-
private message;
|
|
11
|
+
private _messageColorFn;
|
|
10
12
|
private frames;
|
|
11
13
|
private currentFrame;
|
|
12
|
-
private
|
|
14
|
+
private spinnerIntervalId;
|
|
13
15
|
private ui;
|
|
14
|
-
|
|
16
|
+
private message;
|
|
17
|
+
private cycleMessages;
|
|
18
|
+
private cycleIndex;
|
|
19
|
+
private phase;
|
|
20
|
+
private visibleChars;
|
|
21
|
+
private wordAnimId;
|
|
22
|
+
private readonly gradStart;
|
|
23
|
+
private readonly gradEnd;
|
|
24
|
+
private shimmerTick;
|
|
25
|
+
private shimmerIntervalId;
|
|
26
|
+
constructor(ui: TUI, spinnerColorFn: (str: string) => string, _messageColorFn: (str: string) => string, message?: string);
|
|
15
27
|
render(width: number): string[];
|
|
28
|
+
setCycleMessages(messages: string[], _intervalMs?: number): void;
|
|
29
|
+
clearCycleMessages(): void;
|
|
30
|
+
setMessage(message: string): void;
|
|
31
|
+
resumeCycle(): void;
|
|
16
32
|
start(): void;
|
|
17
33
|
stop(): void;
|
|
18
34
|
dispose(): void;
|
|
19
|
-
|
|
35
|
+
private startWordAnimation;
|
|
36
|
+
private stopWordAnimation;
|
|
37
|
+
private scheduleWordTick;
|
|
38
|
+
private currentWord;
|
|
39
|
+
private renderAnimatedWord;
|
|
20
40
|
private updateDisplay;
|
|
21
41
|
}
|
|
22
42
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAqCjC;;;;;GAKG;AACH,qBAAa,MAAO,SAAQ,IAAI;IA2B9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,eAAe;IA1BxB,OAAO,CAAC,MAAM,CAAsD;IACpE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,EAAE,CAAoB;IAG9B,OAAO,CAAC,OAAO,CAAc;IAG7B,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAA+B;IAGjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6C;IACvE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+C;IAGvE,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,iBAAiB,CAA+B;gBAGvD,EAAE,EAAE,GAAG,EACC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EACvC,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EAChD,OAAO,GAAE,MAAmB;IAQ7B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAM/B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,WAAW,SAAO;IAUvD,kBAAkB;IAKlB,UAAU,CAAC,OAAO,EAAE,MAAM;IAS1B,WAAW;IAMX,KAAK;IAqBL,IAAI;IAMJ,OAAO;IAOP,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,aAAa;CASrB"}
|
|
@@ -1,53 +1,213 @@
|
|
|
1
1
|
import { Text } from "./text.js";
|
|
2
|
+
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
3
|
+
const ESC = "\x1b[";
|
|
4
|
+
const RESET = "\x1b[0m";
|
|
5
|
+
const DIM = "\x1b[2m";
|
|
6
|
+
const BOLD = "\x1b[1m";
|
|
7
|
+
/** 24-bit foreground color */
|
|
8
|
+
function rgb(r, g, b, s) {
|
|
9
|
+
return `${ESC}38;2;${r};${g};${b}m${s}${RESET}`;
|
|
10
|
+
}
|
|
11
|
+
/** Interpolate between two RGB colors at position t ∈ [0,1] */
|
|
12
|
+
function lerpColor(r1, g1, b1, r2, g2, b2, t) {
|
|
13
|
+
return [
|
|
14
|
+
Math.round(r1 + (r2 - r1) * t),
|
|
15
|
+
Math.round(g1 + (g2 - g1) * t),
|
|
16
|
+
Math.round(b1 + (b2 - b1) * t),
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
// ─── Timing constants ─────────────────────────────────────────────────────────
|
|
20
|
+
const TYPING_SPEED = 55; // ms per character typed
|
|
21
|
+
const HOLD_TIME = 5000; // ms to hold the full word
|
|
22
|
+
const ERASE_SPEED = 35; // ms per character erased
|
|
2
23
|
/**
|
|
3
|
-
* Loader component
|
|
24
|
+
* Loader component — braille spinner + animated word cycling.
|
|
25
|
+
*
|
|
26
|
+
* Each word types in character by character (typewriter), holds with a moving
|
|
27
|
+
* blue→cyan shimmer gradient, then erases right-to-left before the next word.
|
|
4
28
|
*/
|
|
5
29
|
export class Loader extends Text {
|
|
6
|
-
constructor(ui, spinnerColorFn,
|
|
30
|
+
constructor(ui, spinnerColorFn, _messageColorFn, message = "Loading…") {
|
|
7
31
|
super("", 1, 0);
|
|
8
32
|
this.spinnerColorFn = spinnerColorFn;
|
|
9
|
-
this.
|
|
10
|
-
|
|
33
|
+
this._messageColorFn = _messageColorFn;
|
|
34
|
+
// ── spinner ───────────────────────────────────────────────────────────────
|
|
11
35
|
this.frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
36
|
this.currentFrame = 0;
|
|
13
|
-
this.
|
|
37
|
+
this.spinnerIntervalId = null;
|
|
14
38
|
this.ui = null;
|
|
39
|
+
// ── message state ─────────────────────────────────────────────────────────
|
|
40
|
+
this.message = "";
|
|
41
|
+
// ── word animation ────────────────────────────────────────────────────────
|
|
42
|
+
this.cycleMessages = null;
|
|
43
|
+
this.cycleIndex = 0;
|
|
44
|
+
this.phase = "typing";
|
|
45
|
+
this.visibleChars = 0;
|
|
46
|
+
this.wordAnimId = null;
|
|
47
|
+
// ── shimmer gradient: theme blueHigh → blueXhigh ─────────────────────────
|
|
48
|
+
this.gradStart = [147, 197, 253]; // #93c5fd (blue-300)
|
|
49
|
+
this.gradEnd = [191, 219, 254]; // #bfdbfe (blue-200)
|
|
50
|
+
// Shimmer ticks independently at ~40ms for smooth gradient movement
|
|
51
|
+
this.shimmerTick = 0;
|
|
52
|
+
this.shimmerIntervalId = null;
|
|
15
53
|
this.ui = ui;
|
|
54
|
+
this.message = message;
|
|
16
55
|
this.start();
|
|
17
56
|
}
|
|
18
57
|
render(width) {
|
|
19
58
|
return ["", ...super.render(width)];
|
|
20
59
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
60
|
+
// ── public API ────────────────────────────────────────────────────────────
|
|
61
|
+
setCycleMessages(messages, _intervalMs = 3000) {
|
|
62
|
+
this.cycleMessages = [...messages];
|
|
63
|
+
this.cycleIndex = 0;
|
|
64
|
+
this.message = this.cycleMessages[0];
|
|
65
|
+
if (this.spinnerIntervalId) {
|
|
66
|
+
this.startWordAnimation();
|
|
24
67
|
}
|
|
25
68
|
this.updateDisplay();
|
|
26
|
-
|
|
69
|
+
}
|
|
70
|
+
clearCycleMessages() {
|
|
71
|
+
this.stopWordAnimation();
|
|
72
|
+
this.cycleMessages = null;
|
|
73
|
+
}
|
|
74
|
+
setMessage(message) {
|
|
75
|
+
// When an explicit message is set externally (e.g. "Waiting for approval…"),
|
|
76
|
+
// pause the cycle so it doesn't immediately overwrite the override.
|
|
77
|
+
this.stopWordAnimation();
|
|
78
|
+
this.cycleMessages = null;
|
|
79
|
+
this.message = message;
|
|
80
|
+
this.updateDisplay();
|
|
81
|
+
}
|
|
82
|
+
resumeCycle() {
|
|
83
|
+
if (this.cycleMessages && !this.wordAnimId) {
|
|
84
|
+
this.startWordAnimation();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
start() {
|
|
88
|
+
if (this.spinnerIntervalId)
|
|
89
|
+
clearInterval(this.spinnerIntervalId);
|
|
90
|
+
this.spinnerIntervalId = setInterval(() => {
|
|
27
91
|
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
28
92
|
this.updateDisplay();
|
|
29
93
|
}, 80);
|
|
94
|
+
// Shimmer ticks independently at ~40ms for smooth gradient movement
|
|
95
|
+
if (this.shimmerIntervalId)
|
|
96
|
+
clearInterval(this.shimmerIntervalId);
|
|
97
|
+
this.shimmerIntervalId = setInterval(() => {
|
|
98
|
+
this.shimmerTick++;
|
|
99
|
+
if (this.cycleMessages)
|
|
100
|
+
this.updateDisplay();
|
|
101
|
+
}, 40);
|
|
102
|
+
if (this.cycleMessages) {
|
|
103
|
+
this.startWordAnimation();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.updateDisplay();
|
|
107
|
+
}
|
|
30
108
|
}
|
|
31
109
|
stop() {
|
|
32
|
-
if (this.
|
|
33
|
-
clearInterval(this.
|
|
34
|
-
this.
|
|
110
|
+
if (this.spinnerIntervalId) {
|
|
111
|
+
clearInterval(this.spinnerIntervalId);
|
|
112
|
+
this.spinnerIntervalId = null;
|
|
35
113
|
}
|
|
114
|
+
if (this.shimmerIntervalId) {
|
|
115
|
+
clearInterval(this.shimmerIntervalId);
|
|
116
|
+
this.shimmerIntervalId = null;
|
|
117
|
+
}
|
|
118
|
+
this.stopWordAnimation();
|
|
36
119
|
}
|
|
37
120
|
dispose() {
|
|
38
121
|
this.stop();
|
|
39
122
|
this.ui = null;
|
|
40
123
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
124
|
+
// ── word animation internals ──────────────────────────────────────────────
|
|
125
|
+
startWordAnimation() {
|
|
126
|
+
this.stopWordAnimation();
|
|
127
|
+
this.phase = "typing";
|
|
128
|
+
this.visibleChars = 0;
|
|
129
|
+
this.scheduleWordTick();
|
|
130
|
+
}
|
|
131
|
+
stopWordAnimation() {
|
|
132
|
+
if (this.wordAnimId) {
|
|
133
|
+
clearTimeout(this.wordAnimId);
|
|
134
|
+
this.wordAnimId = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
scheduleWordTick() {
|
|
138
|
+
const word = this.currentWord();
|
|
139
|
+
if (this.phase === "typing") {
|
|
140
|
+
if (this.visibleChars < word.length) {
|
|
141
|
+
this.wordAnimId = setTimeout(() => {
|
|
142
|
+
this.visibleChars++;
|
|
143
|
+
this.updateDisplay();
|
|
144
|
+
this.scheduleWordTick();
|
|
145
|
+
}, TYPING_SPEED);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Full word shown → hold
|
|
149
|
+
this.phase = "hold";
|
|
150
|
+
this.wordAnimId = setTimeout(() => {
|
|
151
|
+
this.phase = "erase";
|
|
152
|
+
this.scheduleWordTick();
|
|
153
|
+
}, HOLD_TIME);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (this.phase === "erase") {
|
|
157
|
+
if (this.visibleChars > 0) {
|
|
158
|
+
this.wordAnimId = setTimeout(() => {
|
|
159
|
+
this.visibleChars--;
|
|
160
|
+
this.updateDisplay();
|
|
161
|
+
this.scheduleWordTick();
|
|
162
|
+
}, ERASE_SPEED);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Advance to next word
|
|
166
|
+
if (this.cycleMessages) {
|
|
167
|
+
this.cycleIndex = (this.cycleIndex + 1) % this.cycleMessages.length;
|
|
168
|
+
this.message = this.cycleMessages[this.cycleIndex];
|
|
169
|
+
}
|
|
170
|
+
this.phase = "typing";
|
|
171
|
+
this.scheduleWordTick();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
currentWord() {
|
|
176
|
+
return this.message;
|
|
177
|
+
}
|
|
178
|
+
// ── rendering ─────────────────────────────────────────────────────────────
|
|
179
|
+
renderAnimatedWord() {
|
|
180
|
+
const word = this.currentWord();
|
|
181
|
+
const visible = word.slice(0, this.visibleChars);
|
|
182
|
+
if (!visible)
|
|
183
|
+
return DIM + " " + RESET;
|
|
184
|
+
// Each char gets a colour position that travels through the gradient.
|
|
185
|
+
// The "wave" offset shifts every shimmerTick for a moving shimmer.
|
|
186
|
+
let out = "";
|
|
187
|
+
for (let i = 0; i < visible.length; i++) {
|
|
188
|
+
const wave = (i / Math.max(word.length - 1, 1));
|
|
189
|
+
// Slow rightward drift
|
|
190
|
+
const drift = (this.shimmerTick * 0.025) % 1;
|
|
191
|
+
const t = ((wave + drift) % 1);
|
|
192
|
+
const [r, g, b] = lerpColor(...this.gradStart, ...this.gradEnd, t);
|
|
193
|
+
// Last char being typed gets a bright flash
|
|
194
|
+
const isEdge = (this.phase === "typing" && i === visible.length - 1);
|
|
195
|
+
const char = isEdge
|
|
196
|
+
? `${BOLD}${rgb(255, 255, 255, visible[i])}`
|
|
197
|
+
: rgb(r, g, b, visible[i]);
|
|
198
|
+
out += char;
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
44
201
|
}
|
|
45
202
|
updateDisplay() {
|
|
46
203
|
const frame = this.frames[this.currentFrame];
|
|
47
|
-
this.
|
|
48
|
-
|
|
204
|
+
const spinner = this.spinnerColorFn(frame);
|
|
205
|
+
const text = this.cycleMessages
|
|
206
|
+
? this.renderAnimatedWord()
|
|
207
|
+
: this._messageColorFn(this.message);
|
|
208
|
+
this.setText(`${spinner} ${text}`);
|
|
209
|
+
if (this.ui)
|
|
49
210
|
this.ui.requestRender();
|
|
50
|
-
}
|
|
51
211
|
}
|
|
52
212
|
}
|
|
53
213
|
//# sourceMappingURL=loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,MAAM,OAAO,MAAO,SAAQ,IAAI;IAM/B,YACC,EAAO,EACC,cAAuC,EACvC,cAAuC,EACvC,UAAkB,YAAY;QAEtC,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAJR,mBAAc,GAAd,cAAc,CAAyB;QACvC,mBAAc,GAAd,cAAc,CAAyB;QACvC,YAAO,GAAP,OAAO,CAAuB;QAT/B,WAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5D,iBAAY,GAAG,CAAC,CAAC;QACjB,eAAU,GAA0B,IAAI,CAAC;QACzC,OAAE,GAAe,IAAI,CAAC;QAS7B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YAClC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YACjE,IAAI,CAAC,aAAa,EAAE,CAAC;QACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IACR,CAAC;IAED,IAAI;QACH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACxB,CAAC;IACF,CAAC;IAED,OAAO;QACN,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,UAAU,CAAC,OAAe;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnF,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACzB,CAAC;IACF,CAAC;CACD","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\n/**\n * Loader component that updates every 80ms with spinning animation\n */\nexport class Loader extends Text {\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate intervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate messageColorFn: (str: string) => string,\n\t\tprivate message: string = \"Loading...\",\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\tstart() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t}\n\t\tthis.updateDisplay();\n\t\tthis.intervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\t}\n\n\tstop() {\n\t\tif (this.intervalId) {\n\t\t\tclearInterval(this.intervalId);\n\t\t\tthis.intervalId = null;\n\t\t}\n\t}\n\n\tdispose() {\n\t\tthis.stop();\n\t\tthis.ui = null;\n\t}\n\n\tsetMessage(message: string) {\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tthis.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);\n\t\tif (this.ui) {\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/components/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,iFAAiF;AAEjF,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,KAAK,GAAG,SAAS,CAAC;AACxB,MAAM,GAAG,GAAG,SAAS,CAAC;AACtB,MAAM,IAAI,GAAG,SAAS,CAAC;AAEvB,8BAA8B;AAC9B,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;IACtD,OAAO,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;AACjD,CAAC;AAED,+DAA+D;AAC/D,SAAS,SAAS,CACjB,EAAU,EAAE,EAAU,EAAE,EAAU,EAClC,EAAU,EAAE,EAAU,EAAE,EAAU,EAClC,CAAS;IAET,OAAO;QACN,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;KAC9B,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,MAAM,YAAY,GAAG,EAAE,CAAC,CAAG,yBAAyB;AACpD,MAAM,SAAS,GAAM,IAAI,CAAC,CAAC,2BAA2B;AACtD,MAAM,WAAW,GAAI,EAAE,CAAC,CAAG,0BAA0B;AAMrD;;;;;GAKG;AACH,MAAM,OAAO,MAAO,SAAQ,IAAI;IAyB/B,YACC,EAAO,EACC,cAAuC,EACvC,eAAwC,EAChD,UAAkB,UAAU;QAE5B,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAJR,mBAAc,GAAd,cAAc,CAAyB;QACvC,oBAAe,GAAf,eAAe,CAAyB;QA3BjD,6EAA6E;QACrE,WAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC5D,iBAAY,GAAG,CAAC,CAAC;QACjB,sBAAiB,GAA0B,IAAI,CAAC;QAChD,OAAE,GAAe,IAAI,CAAC;QAE9B,6EAA6E;QACrE,YAAO,GAAW,EAAE,CAAC;QAE7B,6EAA6E;QACrE,kBAAa,GAAoB,IAAI,CAAC;QACtC,eAAU,GAAG,CAAC,CAAC;QACf,UAAK,GAAU,QAAQ,CAAC;QACxB,iBAAY,GAAG,CAAC,CAAC;QACjB,eAAU,GAA0B,IAAI,CAAC;QAEjD,4EAA4E;QAC3D,cAAS,GAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,qBAAqB;QAC5E,YAAO,GAA+B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,qBAAqB;QAE7F,oEAAoE;QAC5D,gBAAW,GAAG,CAAC,CAAC;QAChB,sBAAiB,GAA0B,IAAI,CAAC;QASvD,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,OAAO,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,6EAA6E;IAE7E,gBAAgB,CAAC,QAAkB,EAAE,WAAW,GAAG,IAAI;QACtD,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC5B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,kBAAkB;QACjB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,UAAU,CAAC,OAAe;QACzB,6EAA6E;QAC7E,oEAAoE;QACpE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,WAAW;QACV,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YAC5C,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC3B,CAAC;IACF,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,iBAAiB;YAAE,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAClE,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;YACzC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;YACjE,IAAI,CAAC,aAAa,EAAE,CAAC;QACtB,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,oEAAoE;QACpE,IAAI,IAAI,CAAC,iBAAiB;YAAE,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAClE,IAAI,CAAC,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;YACzC,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,aAAa;gBAAE,IAAI,CAAC,aAAa,EAAE,CAAC;QAC9C,CAAC,EAAE,EAAE,CAAC,CAAC;QAEP,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC3B,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,aAAa,EAAE,CAAC;QACtB,CAAC;IACF,CAAC;IAED,IAAI;QACH,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAAC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAAC,CAAC;QACrG,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAAC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAAC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAAC,CAAC;QACrG,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO;QACN,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,6EAA6E;IAErE,kBAAkB;QACzB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACzB,CAAC;IAEO,iBAAiB;QACxB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAAC,CAAC;IAChF,CAAC;IAEO,gBAAgB;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAEhC,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;oBACjC,IAAI,CAAC,YAAY,EAAE,CAAC;oBACpB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACzB,CAAC,EAAE,YAAY,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACP,yBAAyB;gBACzB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;gBACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;oBACjC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;oBACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACzB,CAAC,EAAE,SAAS,CAAC,CAAC;YACf,CAAC;QACF,CAAC;aAAM,IAAI,IAAI,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;oBACjC,IAAI,CAAC,YAAY,EAAE,CAAC;oBACpB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACzB,CAAC,EAAE,WAAW,CAAC,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACP,uBAAuB;gBACvB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACxB,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;oBACpE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACpD,CAAC;gBACD,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;gBACtB,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACzB,CAAC;QACF,CAAC;IACF,CAAC;IAEO,WAAW;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,6EAA6E;IAErE,kBAAkB;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO;YAAE,OAAO,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC;QAEvC,sEAAsE;QACtE,mEAAmE;QACnE,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChD,uBAAuB;YACvB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAEnE,4CAA4C;YAC5C,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACrE,MAAM,IAAI,GAAG,MAAM;gBAClB,CAAC,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;gBAC5C,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,GAAG,IAAI,IAAI,CAAC;QACb,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;IAEO,aAAa;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa;YAC9B,CAAC,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC3B,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IACtC,CAAC;CACD","sourcesContent":["import type { TUI } from \"../tui.js\";\nimport { Text } from \"./text.js\";\n\n// ─── ANSI helpers ─────────────────────────────────────────────────────────────\n\nconst ESC = \"\\x1b[\";\nconst RESET = \"\\x1b[0m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\n\n/** 24-bit foreground color */\nfunction rgb(r: number, g: number, b: number, s: string): string {\n\treturn `${ESC}38;2;${r};${g};${b}m${s}${RESET}`;\n}\n\n/** Interpolate between two RGB colors at position t ∈ [0,1] */\nfunction lerpColor(\n\tr1: number, g1: number, b1: number,\n\tr2: number, g2: number, b2: number,\n\tt: number,\n): [number, number, number] {\n\treturn [\n\t\tMath.round(r1 + (r2 - r1) * t),\n\t\tMath.round(g1 + (g2 - g1) * t),\n\t\tMath.round(b1 + (b2 - b1) * t),\n\t];\n}\n\n// ─── Timing constants ─────────────────────────────────────────────────────────\n\nconst TYPING_SPEED = 55; // ms per character typed\nconst HOLD_TIME = 5000; // ms to hold the full word\nconst ERASE_SPEED = 35; // ms per character erased\n\n// ─── Phase type ───────────────────────────────────────────────────────────────\n\ntype Phase = \"typing\" | \"hold\" | \"erase\";\n\n/**\n * Loader component — braille spinner + animated word cycling.\n *\n * Each word types in character by character (typewriter), holds with a moving\n * blue→cyan shimmer gradient, then erases right-to-left before the next word.\n */\nexport class Loader extends Text {\n\t// ── spinner ───────────────────────────────────────────────────────────────\n\tprivate frames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\tprivate currentFrame = 0;\n\tprivate spinnerIntervalId: NodeJS.Timeout | null = null;\n\tprivate ui: TUI | null = null;\n\n\t// ── message state ─────────────────────────────────────────────────────────\n\tprivate message: string = \"\";\n\n\t// ── word animation ────────────────────────────────────────────────────────\n\tprivate cycleMessages: string[] | null = null;\n\tprivate cycleIndex = 0;\n\tprivate phase: Phase = \"typing\";\n\tprivate visibleChars = 0;\n\tprivate wordAnimId: NodeJS.Timeout | null = null;\n\n\t// ── shimmer gradient: theme blueHigh → blueXhigh ─────────────────────────\n\tprivate readonly gradStart: [number, number, number] = [147, 197, 253]; // #93c5fd (blue-300)\n\tprivate readonly gradEnd: [number, number, number] = [191, 219, 254]; // #bfdbfe (blue-200)\n\n\t// Shimmer ticks independently at ~40ms for smooth gradient movement\n\tprivate shimmerTick = 0;\n\tprivate shimmerIntervalId: NodeJS.Timeout | null = null;\n\n\tconstructor(\n\t\tui: TUI,\n\t\tprivate spinnerColorFn: (str: string) => string,\n\t\tprivate _messageColorFn: (str: string) => string,\n\t\tmessage: string = \"Loading…\",\n\t) {\n\t\tsuper(\"\", 1, 0);\n\t\tthis.ui = ui;\n\t\tthis.message = message;\n\t\tthis.start();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [\"\", ...super.render(width)];\n\t}\n\n\t// ── public API ────────────────────────────────────────────────────────────\n\n\tsetCycleMessages(messages: string[], _intervalMs = 3000) {\n\t\tthis.cycleMessages = [...messages];\n\t\tthis.cycleIndex = 0;\n\t\tthis.message = this.cycleMessages[0];\n\t\tif (this.spinnerIntervalId) {\n\t\t\tthis.startWordAnimation();\n\t\t}\n\t\tthis.updateDisplay();\n\t}\n\n\tclearCycleMessages() {\n\t\tthis.stopWordAnimation();\n\t\tthis.cycleMessages = null;\n\t}\n\n\tsetMessage(message: string) {\n\t\t// When an explicit message is set externally (e.g. \"Waiting for approval…\"),\n\t\t// pause the cycle so it doesn't immediately overwrite the override.\n\t\tthis.stopWordAnimation();\n\t\tthis.cycleMessages = null;\n\t\tthis.message = message;\n\t\tthis.updateDisplay();\n\t}\n\n\tresumeCycle() {\n\t\tif (this.cycleMessages && !this.wordAnimId) {\n\t\t\tthis.startWordAnimation();\n\t\t}\n\t}\n\n\tstart() {\n\t\tif (this.spinnerIntervalId) clearInterval(this.spinnerIntervalId);\n\t\tthis.spinnerIntervalId = setInterval(() => {\n\t\t\tthis.currentFrame = (this.currentFrame + 1) % this.frames.length;\n\t\t\tthis.updateDisplay();\n\t\t}, 80);\n\n\t\t// Shimmer ticks independently at ~40ms for smooth gradient movement\n\t\tif (this.shimmerIntervalId) clearInterval(this.shimmerIntervalId);\n\t\tthis.shimmerIntervalId = setInterval(() => {\n\t\t\tthis.shimmerTick++;\n\t\t\tif (this.cycleMessages) this.updateDisplay();\n\t\t}, 40);\n\n\t\tif (this.cycleMessages) {\n\t\t\tthis.startWordAnimation();\n\t\t} else {\n\t\t\tthis.updateDisplay();\n\t\t}\n\t}\n\n\tstop() {\n\t\tif (this.spinnerIntervalId) { clearInterval(this.spinnerIntervalId); this.spinnerIntervalId = null; }\n\t\tif (this.shimmerIntervalId) { clearInterval(this.shimmerIntervalId); this.shimmerIntervalId = null; }\n\t\tthis.stopWordAnimation();\n\t}\n\n\tdispose() {\n\t\tthis.stop();\n\t\tthis.ui = null;\n\t}\n\n\t// ── word animation internals ──────────────────────────────────────────────\n\n\tprivate startWordAnimation() {\n\t\tthis.stopWordAnimation();\n\t\tthis.phase = \"typing\";\n\t\tthis.visibleChars = 0;\n\t\tthis.scheduleWordTick();\n\t}\n\n\tprivate stopWordAnimation() {\n\t\tif (this.wordAnimId) { clearTimeout(this.wordAnimId); this.wordAnimId = null; }\n\t}\n\n\tprivate scheduleWordTick() {\n\t\tconst word = this.currentWord();\n\n\t\tif (this.phase === \"typing\") {\n\t\t\tif (this.visibleChars < word.length) {\n\t\t\t\tthis.wordAnimId = setTimeout(() => {\n\t\t\t\t\tthis.visibleChars++;\n\t\t\t\t\tthis.updateDisplay();\n\t\t\t\t\tthis.scheduleWordTick();\n\t\t\t\t}, TYPING_SPEED);\n\t\t\t} else {\n\t\t\t\t// Full word shown → hold\n\t\t\t\tthis.phase = \"hold\";\n\t\t\t\tthis.wordAnimId = setTimeout(() => {\n\t\t\t\t\tthis.phase = \"erase\";\n\t\t\t\t\tthis.scheduleWordTick();\n\t\t\t\t}, HOLD_TIME);\n\t\t\t}\n\t\t} else if (this.phase === \"erase\") {\n\t\t\tif (this.visibleChars > 0) {\n\t\t\t\tthis.wordAnimId = setTimeout(() => {\n\t\t\t\t\tthis.visibleChars--;\n\t\t\t\t\tthis.updateDisplay();\n\t\t\t\t\tthis.scheduleWordTick();\n\t\t\t\t}, ERASE_SPEED);\n\t\t\t} else {\n\t\t\t\t// Advance to next word\n\t\t\t\tif (this.cycleMessages) {\n\t\t\t\t\tthis.cycleIndex = (this.cycleIndex + 1) % this.cycleMessages.length;\n\t\t\t\t\tthis.message = this.cycleMessages[this.cycleIndex];\n\t\t\t\t}\n\t\t\t\tthis.phase = \"typing\";\n\t\t\t\tthis.scheduleWordTick();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate currentWord(): string {\n\t\treturn this.message;\n\t}\n\n\t// ── rendering ─────────────────────────────────────────────────────────────\n\n\tprivate renderAnimatedWord(): string {\n\t\tconst word = this.currentWord();\n\t\tconst visible = word.slice(0, this.visibleChars);\n\t\tif (!visible) return DIM + \" \" + RESET;\n\n\t\t// Each char gets a colour position that travels through the gradient.\n\t\t// The \"wave\" offset shifts every shimmerTick for a moving shimmer.\n\t\tlet out = \"\";\n\t\tfor (let i = 0; i < visible.length; i++) {\n\t\t\tconst wave = (i / Math.max(word.length - 1, 1));\n\t\t\t// Slow rightward drift\n\t\t\tconst drift = (this.shimmerTick * 0.025) % 1;\n\t\t\tconst t = ((wave + drift) % 1);\n\t\t\tconst [r, g, b] = lerpColor(...this.gradStart, ...this.gradEnd, t);\n\n\t\t\t// Last char being typed gets a bright flash\n\t\t\tconst isEdge = (this.phase === \"typing\" && i === visible.length - 1);\n\t\t\tconst char = isEdge\n\t\t\t\t? `${BOLD}${rgb(255, 255, 255, visible[i])}`\n\t\t\t\t: rgb(r, g, b, visible[i]);\n\t\t\tout += char;\n\t\t}\n\t\treturn out;\n\t}\n\n\tprivate updateDisplay() {\n\t\tconst frame = this.frames[this.currentFrame];\n\t\tconst spinner = this.spinnerColorFn(frame);\n\t\tconst text = this.cycleMessages\n\t\t\t? this.renderAnimatedWord()\n\t\t\t: this._messageColorFn(this.message);\n\t\tthis.setText(`${spinner} ${text}`);\n\t\tif (this.ui) this.ui.requestRender();\n\t}\n}\n"]}
|
|
@@ -163,6 +163,10 @@ export class Editor implements Component, Focusable {
|
|
|
163
163
|
private pastes: Map<number, string> = new Map();
|
|
164
164
|
private pasteCounter: number = 0;
|
|
165
165
|
|
|
166
|
+
// Drag-and-drop image path markers (visual only; expanded back on submit)
|
|
167
|
+
private droppedImagePaths: Map<number, string> = new Map();
|
|
168
|
+
private droppedImageCounter: number = 0;
|
|
169
|
+
|
|
166
170
|
// Bracketed paste mode buffering
|
|
167
171
|
private pasteBuffer: string = "";
|
|
168
172
|
private isInPaste: boolean = false;
|
|
@@ -877,9 +881,51 @@ export class Editor implements Component, Focusable {
|
|
|
877
881
|
const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
|
|
878
882
|
result = result.replace(markerRegex, pasteContent);
|
|
879
883
|
}
|
|
884
|
+
return this.expandDroppedImageMarkers(result);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private expandDroppedImageMarkers(text: string): string {
|
|
888
|
+
let result = text;
|
|
889
|
+
for (const [imageId, imagePath] of this.droppedImagePaths) {
|
|
890
|
+
const markerRegex = new RegExp(`<Image${imageId}>`, "g");
|
|
891
|
+
result = result.replace(markerRegex, imagePath);
|
|
892
|
+
}
|
|
880
893
|
return result;
|
|
881
894
|
}
|
|
882
895
|
|
|
896
|
+
private extractDroppedImagePath(pastedText: string): string | null {
|
|
897
|
+
const trimmed = pastedText.trim();
|
|
898
|
+
if (!trimmed || /[\n\r\t]/.test(trimmed)) return null;
|
|
899
|
+
|
|
900
|
+
const unquoted =
|
|
901
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
902
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
903
|
+
? trimmed.slice(1, -1)
|
|
904
|
+
: trimmed;
|
|
905
|
+
|
|
906
|
+
let maybePath = unquoted;
|
|
907
|
+
if (unquoted.startsWith("file://")) {
|
|
908
|
+
try {
|
|
909
|
+
maybePath = decodeURI(unquoted.replace(/^file:\/\//, ""));
|
|
910
|
+
} catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
const normalizedPath = maybePath.replace(/\\ /g, " ");
|
|
915
|
+
|
|
916
|
+
if (!/\.(png|jpe?g|gif|webp|bmp|tiff?|svg|heic|heif|avif)$/i.test(normalizedPath)) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (!normalizedPath.startsWith("/") && !normalizedPath.startsWith("~/") && !normalizedPath.startsWith("./") && !normalizedPath.startsWith("../")) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Return the normalized filesystem path (not the raw trimmed input)
|
|
925
|
+
// This ensures file:// URIs are decoded and escaped spaces are normalized
|
|
926
|
+
return normalizedPath;
|
|
927
|
+
}
|
|
928
|
+
|
|
883
929
|
getLines(): string[] {
|
|
884
930
|
return [...this.state.lines];
|
|
885
931
|
}
|
|
@@ -1059,16 +1105,29 @@ export class Editor implements Component, Focusable {
|
|
|
1059
1105
|
.filter((char) => char === "\n" || char.charCodeAt(0) >= 32)
|
|
1060
1106
|
.join("");
|
|
1061
1107
|
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1108
|
+
// Check if we need a leading space for readability
|
|
1109
|
+
// This applies to file:// URIs, absolute paths (~, /), or relative paths (., ..)
|
|
1110
|
+
let needsLeadingSpace = false;
|
|
1111
|
+
if (/^(file:\/\/|[/~.])/.test(filteredText)) {
|
|
1065
1112
|
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
|
1066
1113
|
const charBeforeCursor = this.state.cursorCol > 0 ? currentLine[this.state.cursorCol - 1] : "";
|
|
1067
1114
|
if (charBeforeCursor && /\w/.test(charBeforeCursor)) {
|
|
1115
|
+
needsLeadingSpace = true;
|
|
1068
1116
|
filteredText = ` ${filteredText}`;
|
|
1069
1117
|
}
|
|
1070
1118
|
}
|
|
1071
1119
|
|
|
1120
|
+
const droppedImagePath = this.extractDroppedImagePath(filteredText);
|
|
1121
|
+
if (droppedImagePath) {
|
|
1122
|
+
this.droppedImageCounter++;
|
|
1123
|
+
const imageId = this.droppedImageCounter;
|
|
1124
|
+
this.droppedImagePaths.set(imageId, droppedImagePath);
|
|
1125
|
+
// Preserve leading space for readability (e.g., "foo" -> "foo <Image1>" -> "foo /tmp/image.png")
|
|
1126
|
+
const toInsert = needsLeadingSpace ? ` <Image${imageId}>` : `<Image${imageId}>`;
|
|
1127
|
+
this.insertTextAtCursorInternal(toInsert);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1072
1131
|
// Split into lines to check for large paste
|
|
1073
1132
|
const pastedLines = filteredText.split("\n");
|
|
1074
1133
|
|
|
@@ -1138,10 +1197,13 @@ export class Editor implements Component, Focusable {
|
|
|
1138
1197
|
const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
|
|
1139
1198
|
result = result.replace(markerRegex, pasteContent);
|
|
1140
1199
|
}
|
|
1200
|
+
result = this.expandDroppedImageMarkers(result);
|
|
1141
1201
|
|
|
1142
1202
|
this.state = { lines: [""], cursorLine: 0, cursorCol: 0 };
|
|
1143
1203
|
this.pastes.clear();
|
|
1144
1204
|
this.pasteCounter = 0;
|
|
1205
|
+
this.droppedImagePaths.clear();
|
|
1206
|
+
this.droppedImageCounter = 0;
|
|
1145
1207
|
this.historyIndex = -1;
|
|
1146
1208
|
this.scrollOffset = 0;
|
|
1147
1209
|
this.undoStack.clear();
|