@mingxy/opencode-mascot 0.2.6 → 0.2.8

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/opencode-mascot",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "OpenCode TUI mascot plugin framework - customizable ASCII mascots for your terminal",
5
5
  "author": "mingxy",
6
6
  "license": "MIT",
@@ -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()}
@@ -4,7 +4,7 @@ import { createSignal, onCleanup } 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>;
@@ -28,13 +28,9 @@ const DEFAULT_STATE_MAP: Partial<Record<MascotState, string>> = {
28
28
  };
29
29
 
30
30
  const MASCOT_WIDTH = 10;
31
- const MASCOT_HEIGHT = 5;
32
31
  const PEEK = 2;
33
32
  const PEEK_INTERVAL = 1200;
34
-
35
- function termWidth(): number {
36
- return (typeof process !== "undefined" && process.stdout?.columns) || 80;
37
- }
33
+ const EDGE_THRESHOLD = 3;
38
34
 
39
35
  export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
40
36
  const names = Object.keys(props.mascots);
@@ -46,6 +42,7 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
46
42
  const [currentName, setCurrentName] = createSignal(initialName);
47
43
  const [posX, setPosX] = createSignal(20);
48
44
  const [posY, setPosY] = createSignal(2);
45
+ const [containerWidth, setContainerWidth] = createSignal(0);
49
46
  let dragStartX = 0;
50
47
  let dragStartY = 0;
51
48
  let dragAnchorX = 0;
@@ -76,17 +73,40 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
76
73
  setCurrentName(names[(idx + 1) % names.length]);
77
74
  };
78
75
 
76
+ const getCw = () => containerWidth() || 30;
77
+
78
+ const clampX = (rawX: number): number => {
79
+ const cw = getCw();
80
+ return Math.max(-(MASCOT_WIDTH - PEEK), Math.min(rawX, cw - PEEK));
81
+ };
82
+
83
+ const clampY = (rawY: number): number => {
84
+ return Math.max(0, rawY);
85
+ };
86
+
87
+ const checkEdge = () => {
88
+ const cw = getCw();
89
+ const x = posX();
90
+ if (x <= -(MASCOT_WIDTH - PEEK) + EDGE_THRESHOLD) {
91
+ hideSide = "left";
92
+ startPeek();
93
+ } else if (x >= cw - PEEK - EDGE_THRESHOLD) {
94
+ hideSide = "right";
95
+ startPeek();
96
+ }
97
+ };
98
+
79
99
  const startPeek = () => {
80
100
  stopPeek();
81
101
  let phase = false;
82
102
  peekTimer = setInterval(() => {
83
103
  phase = !phase;
84
104
  const stretch = phase ? PEEK : 0;
85
- const w = termWidth();
105
+ const cw = getCw();
86
106
  if (hideSide === "left") {
87
107
  setPosX(-(MASCOT_WIDTH - PEEK) + stretch);
88
108
  } else if (hideSide === "right") {
89
- setPosX(w - PEEK - stretch);
109
+ setPosX(cw - PEEK - stretch);
90
110
  }
91
111
  }, PEEK_INTERVAL);
92
112
  };
@@ -94,13 +114,14 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
94
114
  const returnToView = () => {
95
115
  if (!hideSide) return;
96
116
  stopPeek();
97
- const w = termWidth();
117
+ const cw = getCw();
98
118
  const cur = posX();
99
- const targetX = hideSide === "left" ? 0 : Math.max(0, w - MASCOT_WIDTH);
100
- const step = targetX > cur ? 2 : -2;
119
+ const targetX = hideSide === "left" ? 0 : Math.max(0, cw - MASCOT_WIDTH);
120
+ const step = targetX > cur ? 1 : -1;
121
+
101
122
  returnTimer = setInterval(() => {
102
123
  const now = posX();
103
- if (Math.abs(now - targetX) <= 2) {
124
+ if (Math.abs(now - targetX) <= 1) {
104
125
  setPosX(targetX);
105
126
  stopReturn();
106
127
  hideSide = null;
@@ -111,30 +132,6 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
111
132
  }, 16);
112
133
  };
113
134
 
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
135
  const setStateWithSwitch = (s: MascotState) => {
139
136
  const cur = currentName();
140
137
  renderers[cur].setState(s);
@@ -179,6 +176,10 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
179
176
  renderers[currentName()].celebrateUpdate(newVersion);
180
177
  });
181
178
 
179
+ onVersion((version) => {
180
+ renderers[currentName()].showVersion(version);
181
+ });
182
+
182
183
  return (
183
184
  <box
184
185
  position="absolute"
@@ -187,6 +188,16 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
187
188
  alignItems="center"
188
189
  zIndex={100}
189
190
  flexDirection="column"
191
+ ref={(node: any) => {
192
+ if (node) {
193
+ setContainerWidth(node.width || 0);
194
+ if (node.onSizeChange !== undefined) {
195
+ node.onSizeChange = () => {
196
+ setContainerWidth(node.width || 0);
197
+ };
198
+ }
199
+ }
200
+ }}
190
201
  onMouseDown={(e: any) => {
191
202
  if (hideSide) { returnToView(); return; }
192
203
 
@@ -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
- return { element, setState, toggleWalk, setDragging, celebrateUpdate, bounce };
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 = {