marionette / src /shared /util.ts
Thibaud Frère
feat: initial marionette v2 release
44aec57
/**
* 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));