Spaces:
Sleeping
Sleeping
| /** | |
| * Pure helpers β no DOM, no SDK, no IO. Safe to import from anywhere. | |
| * | |
| * Ported from `reachy_mini_marionette/lib/util.js`; the shape and | |
| * behaviour are 1:1 so on-disk move IDs / labels stay stable when a | |
| * user migrates from the v1 app. | |
| */ | |
| export const EMOJIS = [ | |
| "πΊ", | |
| "π", | |
| "π€", | |
| "β¨", | |
| "π΅", | |
| "π", | |
| "π", | |
| "π₯", | |
| "π§ ", | |
| "πͺ©", | |
| "π", | |
| "π¦Ύ", | |
| "π", | |
| "π", | |
| "π", | |
| "π", | |
| "π¨", | |
| "πͺ", | |
| ] as const; | |
| export function newId(): string { | |
| return Math.random().toString(36).slice(2, 10); | |
| } | |
| export function pickRandomEmoji(): string { | |
| return EMOJIS[Math.floor(Math.random() * EMOJIS.length)]!; | |
| } | |
| export function fmtTime(s: number): string { | |
| if (!Number.isFinite(s) || s < 0) return "0:00"; | |
| const m = Math.floor(s / 60); | |
| const r = Math.floor(s % 60); | |
| return `${m}:${String(r).padStart(2, "0")}`; | |
| } | |
| export function slugify(s: string): string { | |
| return ( | |
| String(s) | |
| .toLowerCase() | |
| .replace(/[^a-z0-9]+/g, "-") | |
| .replace(/^-+|-+$/g, "") | |
| .slice(0, 48) || "move" | |
| ); | |
| } | |
| /** | |
| * Next "Move N" label that doesn't collide with anything in the | |
| * supplied list (case-insensitive). Used both in the wizard (default | |
| * before the user types) and after-save (auto-name when the input is | |
| * blank). | |
| */ | |
| export function nextDefaultMoveName(existingLabels: Iterable<string>): string { | |
| const used = new Set( | |
| Array.from(existingLabels, (m) => | |
| String(m || "") | |
| .trim() | |
| .toLowerCase(), | |
| ), | |
| ); | |
| let n = used.size + 1; | |
| while (used.has(`move ${n}`)) n++; | |
| return `Move ${n}`; | |
| } | |
| export const sleep = (ms: number): Promise<void> => | |
| new Promise((r) => setTimeout(r, ms)); | |