@mingxy/opencode-mascot 0.2.6 → 0.2.7
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
CHANGED
|
@@ -4,7 +4,7 @@ import { createSignal } from "solid-js";
|
|
|
4
4
|
import type { JSX } from "@opentui/solid";
|
|
5
5
|
import type { MascotPack } from "../core/types";
|
|
6
6
|
import { createAnimatedRenderer } from "../core/ascii-renderer";
|
|
7
|
-
import { onCelebrate } from "../core/celebration-bus";
|
|
7
|
+
import { onCelebrate, onVersion } from "../core/celebration-bus";
|
|
8
8
|
|
|
9
9
|
interface HomeMascotProps {
|
|
10
10
|
mascots: Record<string, MascotPack>;
|
|
@@ -44,6 +44,10 @@ export function HomeMascot(props: HomeMascotProps): JSX.Element {
|
|
|
44
44
|
renderers[currentName()].celebrateUpdate(newVersion);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
onVersion((version) => {
|
|
48
|
+
renderers[currentName()].showVersion(version);
|
|
49
|
+
});
|
|
50
|
+
|
|
47
51
|
return (
|
|
48
52
|
<box
|
|
49
53
|
left={posX()}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
|
|
3
|
-
import { createSignal
|
|
3
|
+
import { createSignal } from "solid-js";
|
|
4
4
|
import type { JSX } from "@opentui/solid";
|
|
5
5
|
import type { MascotPack, MascotState } from "../core/types";
|
|
6
6
|
import { createAnimatedRenderer } from "../core/ascii-renderer";
|
|
7
|
-
import { onCelebrate } from "../core/celebration-bus";
|
|
7
|
+
import { onCelebrate, onVersion } from "../core/celebration-bus";
|
|
8
8
|
|
|
9
9
|
interface SidebarMascotProps {
|
|
10
10
|
mascots: Record<string, MascotPack>;
|
|
@@ -27,15 +27,6 @@ const DEFAULT_STATE_MAP: Partial<Record<MascotState, string>> = {
|
|
|
27
27
|
sleeping: "baozi",
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const MASCOT_WIDTH = 10;
|
|
31
|
-
const MASCOT_HEIGHT = 5;
|
|
32
|
-
const PEEK = 2;
|
|
33
|
-
const PEEK_INTERVAL = 1200;
|
|
34
|
-
|
|
35
|
-
function termWidth(): number {
|
|
36
|
-
return (typeof process !== "undefined" && process.stdout?.columns) || 80;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
40
31
|
const names = Object.keys(props.mascots);
|
|
41
32
|
const initialName =
|
|
@@ -52,89 +43,18 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
52
43
|
let dragAnchorY = 0;
|
|
53
44
|
let lastClickTime = 0;
|
|
54
45
|
let isDragging = false;
|
|
55
|
-
let hideSide: "left" | "right" | null = null;
|
|
56
|
-
let peekTimer: ReturnType<typeof setInterval> | null = null;
|
|
57
|
-
let returnTimer: ReturnType<typeof setInterval> | null = null;
|
|
58
46
|
|
|
59
47
|
const renderers: Record<string, ReturnType<typeof createAnimatedRenderer>> = {};
|
|
60
48
|
for (const [name, pack] of Object.entries(props.mascots)) {
|
|
61
49
|
renderers[name] = createAnimatedRenderer(pack);
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
const stopPeek = () => {
|
|
65
|
-
if (peekTimer) { clearInterval(peekTimer); peekTimer = null; }
|
|
66
|
-
};
|
|
67
|
-
const stopReturn = () => {
|
|
68
|
-
if (returnTimer) { clearInterval(returnTimer); returnTimer = null; }
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
onCleanup(() => { stopPeek(); stopReturn(); });
|
|
72
|
-
|
|
73
52
|
const switchToNext = () => {
|
|
74
53
|
const cur = currentName();
|
|
75
54
|
const idx = names.indexOf(cur);
|
|
76
55
|
setCurrentName(names[(idx + 1) % names.length]);
|
|
77
56
|
};
|
|
78
57
|
|
|
79
|
-
const startPeek = () => {
|
|
80
|
-
stopPeek();
|
|
81
|
-
let phase = false;
|
|
82
|
-
peekTimer = setInterval(() => {
|
|
83
|
-
phase = !phase;
|
|
84
|
-
const stretch = phase ? PEEK : 0;
|
|
85
|
-
const w = termWidth();
|
|
86
|
-
if (hideSide === "left") {
|
|
87
|
-
setPosX(-(MASCOT_WIDTH - PEEK) + stretch);
|
|
88
|
-
} else if (hideSide === "right") {
|
|
89
|
-
setPosX(w - PEEK - stretch);
|
|
90
|
-
}
|
|
91
|
-
}, PEEK_INTERVAL);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const returnToView = () => {
|
|
95
|
-
if (!hideSide) return;
|
|
96
|
-
stopPeek();
|
|
97
|
-
const w = termWidth();
|
|
98
|
-
const cur = posX();
|
|
99
|
-
const targetX = hideSide === "left" ? 0 : Math.max(0, w - MASCOT_WIDTH);
|
|
100
|
-
const step = targetX > cur ? 2 : -2;
|
|
101
|
-
returnTimer = setInterval(() => {
|
|
102
|
-
const now = posX();
|
|
103
|
-
if (Math.abs(now - targetX) <= 2) {
|
|
104
|
-
setPosX(targetX);
|
|
105
|
-
stopReturn();
|
|
106
|
-
hideSide = null;
|
|
107
|
-
renderers[currentName()].bounce();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
setPosX(now + step);
|
|
111
|
-
}, 16);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const checkEdge = () => {
|
|
115
|
-
const w = termWidth();
|
|
116
|
-
const x = posX();
|
|
117
|
-
if (x <= -(MASCOT_WIDTH - PEEK) + 1) {
|
|
118
|
-
hideSide = "left";
|
|
119
|
-
startPeek();
|
|
120
|
-
} else if (x >= w - PEEK - 1) {
|
|
121
|
-
hideSide = "right";
|
|
122
|
-
startPeek();
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const clampX = (rawX: number): number => {
|
|
127
|
-
const w = termWidth();
|
|
128
|
-
const minX = -(MASCOT_WIDTH - PEEK);
|
|
129
|
-
const maxX = w - PEEK;
|
|
130
|
-
return Math.max(minX, Math.min(rawX, maxX));
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const clampY = (rawY: number): number => {
|
|
134
|
-
const h = (typeof process !== "undefined" && process.stdout?.rows) || 24;
|
|
135
|
-
return Math.max(0, Math.min(rawY, h - MASCOT_HEIGHT));
|
|
136
|
-
};
|
|
137
|
-
|
|
138
58
|
const setStateWithSwitch = (s: MascotState) => {
|
|
139
59
|
const cur = currentName();
|
|
140
60
|
renderers[cur].setState(s);
|
|
@@ -149,7 +69,6 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
149
69
|
const payload = data as { type?: string; properties?: { sessionID?: string; status?: { type?: string } } } | null;
|
|
150
70
|
const statusType = payload?.properties?.status?.type;
|
|
151
71
|
if (statusType === "busy" || statusType === "retry") {
|
|
152
|
-
if (hideSide) returnToView();
|
|
153
72
|
renderers[currentName()].setState("busy");
|
|
154
73
|
} else {
|
|
155
74
|
setStateWithSwitch("idle");
|
|
@@ -179,6 +98,10 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
179
98
|
renderers[currentName()].celebrateUpdate(newVersion);
|
|
180
99
|
});
|
|
181
100
|
|
|
101
|
+
onVersion((version) => {
|
|
102
|
+
renderers[currentName()].showVersion(version);
|
|
103
|
+
});
|
|
104
|
+
|
|
182
105
|
return (
|
|
183
106
|
<box
|
|
184
107
|
position="absolute"
|
|
@@ -188,8 +111,6 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
188
111
|
zIndex={100}
|
|
189
112
|
flexDirection="column"
|
|
190
113
|
onMouseDown={(e: any) => {
|
|
191
|
-
if (hideSide) { returnToView(); return; }
|
|
192
|
-
|
|
193
114
|
const now = Date.now();
|
|
194
115
|
if (now - lastClickTime < 300) {
|
|
195
116
|
switchToNext();
|
|
@@ -212,8 +133,8 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
212
133
|
}}
|
|
213
134
|
onMouseDrag={(e: any) => {
|
|
214
135
|
if (e.modifiers?.alt && isDragging) {
|
|
215
|
-
setPosX(
|
|
216
|
-
setPosY(
|
|
136
|
+
setPosX(dragAnchorX + (e.x - dragStartX));
|
|
137
|
+
setPosY(dragAnchorY + (e.y - dragStartY));
|
|
217
138
|
e.preventDefault();
|
|
218
139
|
e.stopPropagation();
|
|
219
140
|
props.api.renderer.clearSelection();
|
|
@@ -223,14 +144,12 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
|
|
|
223
144
|
if (isDragging) {
|
|
224
145
|
isDragging = false;
|
|
225
146
|
renderers[currentName()].setDragging(false);
|
|
226
|
-
checkEdge();
|
|
227
147
|
}
|
|
228
148
|
}}
|
|
229
149
|
onMouseDragEnd={() => {
|
|
230
150
|
if (isDragging) {
|
|
231
151
|
isDragging = false;
|
|
232
152
|
renderers[currentName()].setDragging(false);
|
|
233
|
-
checkEdge();
|
|
234
153
|
}
|
|
235
154
|
}}
|
|
236
155
|
>
|
|
@@ -4,6 +4,15 @@ import { createSignal, onCleanup } from "solid-js";
|
|
|
4
4
|
import type { JSX } from "@opentui/solid";
|
|
5
5
|
import type { MascotPack, MascotState, EffectTimerCtx, EffectRenderCtx } from "./types";
|
|
6
6
|
|
|
7
|
+
const SUPERSCRIPT: Record<string, string> = {
|
|
8
|
+
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴",
|
|
9
|
+
"5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", ".": "·",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function toSuperscript(s: string): string {
|
|
13
|
+
return s.split("").map(c => SUPERSCRIPT[c] ?? c).join("");
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
const STATE_TO_FRAME: Record<MascotState, string> = {
|
|
8
17
|
idle: "default",
|
|
9
18
|
busy: "default",
|
|
@@ -49,6 +58,7 @@ export function createAnimatedRenderer(pack: MascotPack): {
|
|
|
49
58
|
setDragging: (v: boolean) => void;
|
|
50
59
|
celebrateUpdate: (newVersion: string) => void;
|
|
51
60
|
bounce: () => void;
|
|
61
|
+
showVersion: (version: string) => void;
|
|
52
62
|
} {
|
|
53
63
|
const anim = { ...DEFAULT_ANIM, ...pack.animations };
|
|
54
64
|
const fg = pack.colors?.defaultFg || undefined;
|
|
@@ -345,5 +355,10 @@ export function createAnimatedRenderer(pack: MascotPack): {
|
|
|
345
355
|
setTimeout(() => setJumpOffset(0), 450);
|
|
346
356
|
};
|
|
347
357
|
|
|
348
|
-
|
|
358
|
+
const showVersion = (version: string) => {
|
|
359
|
+
setCelebrate({ text: `ᵛ${toSuperscript(version)}`, count: 0 });
|
|
360
|
+
setTimeout(() => setCelebrate(null), 3000);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return { element, setState, toggleWalk, setDragging, celebrateUpdate, bounce, showVersion };
|
|
349
364
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const bus = new EventTarget();
|
|
2
2
|
|
|
3
3
|
const CELEBRATE_EVENT = "mascot:celebrate";
|
|
4
|
+
const VERSION_EVENT = "mascot:version";
|
|
4
5
|
|
|
5
6
|
export function emitCelebrate(newVersion: string): void {
|
|
6
7
|
bus.dispatchEvent(new CustomEvent(CELEBRATE_EVENT, { detail: { newVersion } }));
|
|
@@ -14,3 +15,16 @@ export function onCelebrate(handler: (newVersion: string) => void): () => void {
|
|
|
14
15
|
bus.addEventListener(CELEBRATE_EVENT, listener);
|
|
15
16
|
return () => bus.removeEventListener(CELEBRATE_EVENT, listener);
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
export function emitVersion(version: string): void {
|
|
20
|
+
bus.dispatchEvent(new CustomEvent(VERSION_EVENT, { detail: { version } }));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function onVersion(handler: (version: string) => void): () => void {
|
|
24
|
+
const listener = (e: Event) => {
|
|
25
|
+
const detail = (e as CustomEvent).detail as { version: string };
|
|
26
|
+
handler(detail.version);
|
|
27
|
+
};
|
|
28
|
+
bus.addEventListener(VERSION_EVENT, listener);
|
|
29
|
+
return () => bus.removeEventListener(VERSION_EVENT, listener);
|
|
30
|
+
}
|
package/tui.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { loadAllMascots } from "./src/core/mascot-loader"
|
|
|
7
7
|
import { SidebarMascot } from "./src/components/sidebar-mascot"
|
|
8
8
|
import { HomeMascot } from "./src/components/home-mascot"
|
|
9
9
|
import { checkAndUpdate } from "./src/core/updater"
|
|
10
|
-
import { emitCelebrate } from "./src/core/celebration-bus"
|
|
10
|
+
import { emitCelebrate, emitVersion } from "./src/core/celebration-bus"
|
|
11
11
|
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = dirname(__filename);
|
|
@@ -37,6 +37,8 @@ const tui: TuiPlugin = async (api, _options) => {
|
|
|
37
37
|
checkAndUpdate(pluginVersion, (newVersion) => {
|
|
38
38
|
emitCelebrate(newVersion);
|
|
39
39
|
}).catch(() => {});
|
|
40
|
+
|
|
41
|
+
setTimeout(() => emitVersion(pluginVersion), 1500);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
const plugin: TuiPluginModule = {
|