boosterarena-mcp 0.2.2 → 0.3.1
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/LEEME.md +10 -1
- package/dist/nucleo.mjs +282 -25
- package/package.json +1 -1
- package/proyectos.mjs +2 -1
- package/servidor.mjs +23 -6
package/LEEME.md
CHANGED
|
@@ -54,7 +54,16 @@ habla con Booster con **tu** sesión.
|
|
|
54
54
|
borrador y tú decides cuándo se publica.
|
|
55
55
|
|
|
56
56
|
**Contenido** — poner texto, imágenes, figuras y dibujos de la biblioteca (más de 3.000
|
|
57
|
-
ilustraciones y 280 fondos, que se buscan por nombre en español o inglés)
|
|
57
|
+
ilustraciones y 280 fondos, que se buscan por nombre en español o inglés), y animaciones Lottie
|
|
58
|
+
(`poner_lottie`: de la galería de Booster o por URL). El fondo puede ser una imagen tuya
|
|
59
|
+
(`poner_fondo` con `url`), y cada capa puede llevar el suyo (`capa` en `poner_fondo`, o `fondo`
|
|
60
|
+
en `nueva_capa`).
|
|
61
|
+
|
|
62
|
+
**Trayectorias** — `programar … moverPorTrayectoria` con `hasta` {x, y} (a dónde llega el centro
|
|
63
|
+
del objeto, en % del slide) traza el camino solo; `puntos` para curvas o escalas y `duracion` en
|
|
64
|
+
ms. Un objeto puede tener varios caminos con nombre: en una secuencia de clics cada uno sale de
|
|
65
|
+
donde lo dejó el anterior, y `trayectoriaTerminada` apunta a cada uno por su nombre exacto.
|
|
66
|
+
`recolocar` acepta `x`/`y` fuera del slide para que un objeto entre en escena.
|
|
58
67
|
|
|
59
68
|
**Boosters y Boost Arenas** — colocarlos y configurarlos por nombre. `describir` te dice qué
|
|
60
69
|
acepta cada uno antes de intentarlo, así que el agente no tiene que adivinar los campos.
|
package/dist/nucleo.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// ../src/insert/catalogos.ts
|
|
2
2
|
var ILUS_BASE = "https://assets.boosterarena.com/ilustraciones";
|
|
3
|
+
var ANIM_PRO_BASE = "https://assets.boosterarena.com/animados-pro";
|
|
3
4
|
var FONDOS_BASE = "https://assets.boosterarena.com/fondos";
|
|
4
5
|
var FLAG_ICONS = ["us", "gb", "mx", "ca", "es", "fr", "de", "it", "br", "jp", "cn", "kr", "in", "ru", "au", "nl", "pt", "ar", "co", "eg", "za", "sa", "tr", "gr"].map((c) => `circle-flags:${c}`);
|
|
5
6
|
var ILLUS_ICONS = ["idea", "reading", "graduation-cap", "library", "planner", "calendar", "alarm-clock", "globe", "landscape", "gallery", "camera", "film", "music", "like", "rating", "sports-mode", "night-landscape", "panorama", "picture", "news", "document", "about", "conference-call", "voice-presentation"].map((n2) => `flat-color-icons:${n2}`);
|
|
@@ -67,6 +68,7 @@ var limpiarVacias = (ws) => {
|
|
|
67
68
|
const utiles = ws.filter((w) => !VACIAS.has(w));
|
|
68
69
|
return utiles.length ? utiles : ws;
|
|
69
70
|
};
|
|
71
|
+
var palabrasDeBusqueda = (q) => limpiarVacias(q.split(/[\s/,_-]+/).filter(Boolean)).map(raiz);
|
|
70
72
|
var raiz = (w) => {
|
|
71
73
|
if (w.length <= 3) return w;
|
|
72
74
|
let r = w;
|
|
@@ -143,7 +145,7 @@ function cargarBiblioteca() {
|
|
|
143
145
|
});
|
|
144
146
|
return cargando;
|
|
145
147
|
}
|
|
146
|
-
var t\u00E9rminos = (consulta) =>
|
|
148
|
+
var t\u00E9rminos = (consulta) => palabrasDeBusqueda(sinTildes(consulta));
|
|
147
149
|
function mejorIlustracion(lista2, consulta) {
|
|
148
150
|
if (!lista2.length) return null;
|
|
149
151
|
const palabras = t\u00E9rminos(consulta);
|
|
@@ -231,6 +233,37 @@ function buscarFondo(consulta) {
|
|
|
231
233
|
parecidos: h.parecidos.map((f) => f.nombre.es)
|
|
232
234
|
};
|
|
233
235
|
}
|
|
236
|
+
var anims = null;
|
|
237
|
+
async function cargarAnimaciones() {
|
|
238
|
+
if (anims?.length) return;
|
|
239
|
+
anims = await fetch(`${ANIM_PRO_BASE}/catalogo.json`, { cache: "no-cache" }).then((r) => r.ok ? r.json() : Promise.reject(new Error(String(r.status)))).then((j) => Array.isArray(j?.elementos) ? j.elementos : []).catch(() => []);
|
|
240
|
+
}
|
|
241
|
+
function buscarAnimacion(consulta) {
|
|
242
|
+
const lista2 = anims ?? [];
|
|
243
|
+
if (!lista2.length) return null;
|
|
244
|
+
const palabras = t\u00E9rminos(consulta);
|
|
245
|
+
const ordena = (a, b) => b.p - a.p || a.e.nombre.es.localeCompare(b.e.nombre.es);
|
|
246
|
+
let exacto = true;
|
|
247
|
+
let orden = lista2.map((e2) => ({ e: e2, p: palabras.length ? puntuar(e2, palabras) : 1 })).filter((x) => x.p > 0);
|
|
248
|
+
if (!orden.length) {
|
|
249
|
+
exacto = false;
|
|
250
|
+
orden = lista2.map((e2) => {
|
|
251
|
+
const d = puntuarDetalle(e2, palabras);
|
|
252
|
+
return { e: e2, p: d.encajadas * 1e3 + d.total };
|
|
253
|
+
}).filter((x) => x.p > 0);
|
|
254
|
+
}
|
|
255
|
+
if (!orden.length) return null;
|
|
256
|
+
orden.sort(ordena);
|
|
257
|
+
const e = orden[0].e;
|
|
258
|
+
return {
|
|
259
|
+
src: `${ANIM_PRO_BASE}/${e.archivo}`,
|
|
260
|
+
nombre: e.nombre.es,
|
|
261
|
+
plan: e.plan,
|
|
262
|
+
caja: { w: e.w, h: e.h, dw: e.dw, dh: e.dh },
|
|
263
|
+
exacto,
|
|
264
|
+
parecidos: orden.slice(1, 3).map((x) => x.e.nombre.es)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
234
267
|
|
|
235
268
|
// ../src/shared/textEffects.ts
|
|
236
269
|
var COLOR_EFFECTS = [
|
|
@@ -1330,7 +1363,7 @@ var efectosMov = MOTION_EFFECTS.map((e) => e.id);
|
|
|
1330
1363
|
function construirCatalogo(opts) {
|
|
1331
1364
|
const fn = (name, description, parameters) => ({ type: "function", function: { name, description, parameters } });
|
|
1332
1365
|
const monedas = ["quitafondo", "magichat", "transformar"].filter((t) => opts.boosters.includes(t));
|
|
1333
|
-
|
|
1366
|
+
const lista2 = [
|
|
1334
1367
|
// ── PONER COSAS EN EL SLIDE ───────────────────────────────────────────────
|
|
1335
1368
|
fn(
|
|
1336
1369
|
"poner_texto",
|
|
@@ -1656,6 +1689,45 @@ function construirCatalogo(opts) {
|
|
|
1656
1689
|
obj({ mensaje: str("Lo que le dices al maestro. Una o dos frases, en espa\xF1ol, en su tono.") }, ["mensaje"])
|
|
1657
1690
|
)
|
|
1658
1691
|
];
|
|
1692
|
+
return opts.sinNavegador ? conLoDelMcp(lista2, fn) : lista2;
|
|
1693
|
+
}
|
|
1694
|
+
var PUNTO = obj({ x: num("En % del ancho del slide."), y: num("En % del alto del slide.") }, ["x", "y"]);
|
|
1695
|
+
function conLoDelMcp(lista2, fn) {
|
|
1696
|
+
const props = (nombre) => (lista2.find((t) => t.function.name === nombre)?.function.parameters).properties;
|
|
1697
|
+
const programar = props("programar");
|
|
1698
|
+
programar.trayectoria = str('Con "moverPorTrayectoria" o el evento "trayectoriaTerminada": el NOMBRE del camino ("Trayectoria 1", "a la boca"). Se busca por nombre EXACTO. Un objeto puede tener varios: dale a cada uno el suyo y as\xED cada `clickStep` o cada `trayectoriaTerminada` apunta al que toca. Si el camino no existe todav\xEDa, hace falta `hasta` o `puntos` para trazarlo.');
|
|
1699
|
+
programar.hasta = { ...PUNTO, description: 'Solo con "moverPorTrayectoria": a D\xD3NDE llega el CENTRO del objeto, en % del slide. El camino se traza en recta desde donde est\xE9 el objeto \u2014 o, en un `clickStep` 2, 3\u2026, desde donde lo dej\xF3 el paso anterior. Si ya existe un camino con ese `trayectoria`, se retraza.' };
|
|
1700
|
+
programar.puntos = { type: "array", items: PUNTO, description: 'Solo con "moverPorTrayectoria": puntos por los que PASA el centro del objeto, en % del slide, para una curva o un recorrido con escalas. El \xFAltimo es la llegada (o, si mandas tambi\xE9n `hasta`, `hasta` va detr\xE1s).' };
|
|
1701
|
+
programar.duracion = num('Solo con "moverPorTrayectoria": cu\xE1nto tarda el recorrido, en milisegundos. Sin esto, 2000.');
|
|
1702
|
+
const fondo = props("poner_fondo");
|
|
1703
|
+
fondo.url = str("Una imagen ya SUBIDA (con `subir_imagen`), https://. Usa esto, `busca` o `color`: uno solo.");
|
|
1704
|
+
fondo.capa = str("El `ref` o el nombre de una CAPA para ponerle el fondo a ella en vez de al slide. Cada capa puede llevar el suyo.");
|
|
1705
|
+
props("nueva_capa").fondo = str("Fondo propio de la capa: la URL https:// de una imagen ya subida o un color en hex. Sin esto, la capa deja ver el slide de debajo.");
|
|
1706
|
+
for (const t of lista2) {
|
|
1707
|
+
const p = t.function.parameters.properties;
|
|
1708
|
+
if (!t.function.name.startsWith("poner_") || !p.x || t.function.name === "poner_texto_plantilla") continue;
|
|
1709
|
+
p.x = num("Borde IZQUIERDO, en % del ancho del slide. Puede ser NEGATIVO o pasar de 100 para que nazca FUERA del slide y entre con una trayectoria.");
|
|
1710
|
+
p.y = num("Borde SUPERIOR, en % del alto del slide. Tambi\xE9n puede quedar fuera, como `x`.");
|
|
1711
|
+
}
|
|
1712
|
+
const recolocar = props("recolocar");
|
|
1713
|
+
recolocar.x = num("Nuevo borde izquierdo en %. Puede ser NEGATIVO o pasar de 100 para dejar el objeto FUERA del slide y que entre con una trayectoria (el monstruo que entra solo: x = -50).");
|
|
1714
|
+
recolocar.y = num("Nuevo borde superior en %. Tambi\xE9n puede quedar fuera del slide, como `x`.");
|
|
1715
|
+
props("cambiar_estilo").gris_al_tocar = bool("true = en el \u25B6 Player, al tocar el objeto se vuelve gris (y otro toque le devuelve el color). Le dice al coach qu\xE9 botones ya us\xF3. false lo quita.");
|
|
1716
|
+
const i = lista2.findIndex((t) => t.function.name === "poner_emoji");
|
|
1717
|
+
lista2.splice(i + 1, 0, fn(
|
|
1718
|
+
"poner_lottie",
|
|
1719
|
+
"Pone una ANIMACI\xD3N Lottie: de la galer\xEDa de animaciones de Booster (busc\xE1ndola por palabra) o una URL propia de un .json de Lottie. Hace de decoraci\xF3n (las esquinas) o de bot\xF3n: se programa con `programar` como cualquier objeto.",
|
|
1720
|
+
obj({
|
|
1721
|
+
ref: REF,
|
|
1722
|
+
x: X,
|
|
1723
|
+
y: Y,
|
|
1724
|
+
w: W,
|
|
1725
|
+
capa: CAPA,
|
|
1726
|
+
busca: str('Qu\xE9 animaci\xF3n buscar en la galer\xEDa, en espa\xF1ol, ingl\xE9s o portugu\xE9s: "regalo", "trofeo", "estrella", "altavoz".'),
|
|
1727
|
+
url: str("En vez de `busca`: la URL https:// de un Lottie .json (lottie.host, o uno subido).")
|
|
1728
|
+
}, ["ref", "x", "y", "w"])
|
|
1729
|
+
));
|
|
1730
|
+
return lista2;
|
|
1659
1731
|
}
|
|
1660
1732
|
|
|
1661
1733
|
// ../src/insert/shapes.ts
|
|
@@ -1711,6 +1783,33 @@ var FORMA_DEF = {
|
|
|
1711
1783
|
blob: { anch: 26, alto: "cuadrado", color: "#c4b5fd" }
|
|
1712
1784
|
};
|
|
1713
1785
|
|
|
1786
|
+
// ../src/canvas/audioVol.ts
|
|
1787
|
+
var AUDIO_VOL_INICIAL = 0.7;
|
|
1788
|
+
|
|
1789
|
+
// ../src/canvas/bucleCapas.ts
|
|
1790
|
+
var alMostrar = (t) => t.event.type === "layerShown" || t.event.type === "slideStart";
|
|
1791
|
+
function capasQueAbre(t) {
|
|
1792
|
+
return [t.action, ...(t.luego ?? []).map((p) => p.action)].filter((a) => a.type === "showLayer" && a.layerId).map((a) => a.layerId);
|
|
1793
|
+
}
|
|
1794
|
+
function abreAlMostrarse(slide, capaId) {
|
|
1795
|
+
const capa = slide.layers.find((l) => l.id === capaId);
|
|
1796
|
+
if (!capa) return [];
|
|
1797
|
+
return capa.els.flatMap((el) => [...el.triggers ?? [], ...el.gtriggers ?? []]).filter(alMostrar).flatMap(capasQueAbre);
|
|
1798
|
+
}
|
|
1799
|
+
function abreEnBucle(slide, desde, t) {
|
|
1800
|
+
if (!alMostrar(t)) return false;
|
|
1801
|
+
const vistas = /* @__PURE__ */ new Set();
|
|
1802
|
+
const pendientes = [...capasQueAbre(t)];
|
|
1803
|
+
while (pendientes.length) {
|
|
1804
|
+
const id = pendientes.pop();
|
|
1805
|
+
if (id === desde) return true;
|
|
1806
|
+
if (vistas.has(id)) continue;
|
|
1807
|
+
vistas.add(id);
|
|
1808
|
+
pendientes.push(...abreAlMostrarse(slide, id));
|
|
1809
|
+
}
|
|
1810
|
+
return false;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1714
1813
|
// ../src/shared/fontsAncho.ts
|
|
1715
1814
|
var ANCHO_LETRA = {
|
|
1716
1815
|
"Poppins": { 100: [712, 689], 200: [724, 692], 300: [736, 696], 400: [747, 699], 500: [760, 703], 600: [770, 708], 700: [782, 715], 800: [793, 723], 900: [805, 731] },
|
|
@@ -1849,11 +1948,28 @@ var ESQUEMAS_BOOSTERS = {
|
|
|
1849
1948
|
campos: {
|
|
1850
1949
|
images: lista("Las im\xE1genes que la varita saca una a una.", objeto("Una imagen.", {
|
|
1851
1950
|
src: imagen("La imagen.", { obligatorio: true }),
|
|
1852
|
-
label: texto("R\xF3tulo que se planta
|
|
1951
|
+
label: texto("R\xF3tulo que se planta con ella. Vac\xEDo = sin r\xF3tulo."),
|
|
1952
|
+
dest: objeto("D\xD3NDE cae la imagen al salir de la varita, en % del slide. Sin esto, se reparten solas en filas de 4.", {
|
|
1953
|
+
x: num2("Borde izquierdo, en % del ancho.", { entero: false, obligatorio: true }),
|
|
1954
|
+
y: num2("Borde superior, en % del alto.", { entero: false, obligatorio: true }),
|
|
1955
|
+
w: num2("Ancho, en % del ancho del slide. El alto sale de la imagen.", { entero: false, min: 2, max: 100, obligatorio: true })
|
|
1956
|
+
}),
|
|
1957
|
+
labelDest: objeto("D\xF3nde cae el R\xD3TULO, aparte de la imagen, en % del slide. Sin esto, justo debajo de ella.", {
|
|
1958
|
+
x: num2("Borde izquierdo, en % del ancho.", { entero: false, obligatorio: true }),
|
|
1959
|
+
y: num2("Borde superior, en % del alto.", { entero: false, obligatorio: true }),
|
|
1960
|
+
w: num2("Ancho, en % del ancho del slide.", { entero: false, min: 2, max: 100, obligatorio: true })
|
|
1961
|
+
}),
|
|
1962
|
+
labelColor: color('Color del r\xF3tulo plantado. Sin esto, casi negro: sobre un fondo oscuro pon "#ffffff".')
|
|
1853
1963
|
}), { max: 30 })
|
|
1854
|
-
}
|
|
1964
|
+
},
|
|
1965
|
+
nota: "Pop-up City: cada lugar sale de la varita y cae en SU sitio de la ciudad con `dest`, y su r\xF3tulo en el suyo con `labelDest`."
|
|
1855
1966
|
},
|
|
1856
|
-
ejemplo: {
|
|
1967
|
+
ejemplo: {
|
|
1968
|
+
images: [
|
|
1969
|
+
{ src: "https://assets.boosterarena.com/u/ej/cat.webp", label: "cat" },
|
|
1970
|
+
{ src: "https://assets.boosterarena.com/u/ej/bank.webp", label: "bank", dest: { x: 62, y: 40, w: 16 }, labelDest: { x: 62, y: 72, w: 16 }, labelColor: "#ffffff" }
|
|
1971
|
+
]
|
|
1972
|
+
}
|
|
1857
1973
|
},
|
|
1858
1974
|
tombola: {
|
|
1859
1975
|
donde: "wcfg.ext",
|
|
@@ -2759,8 +2875,12 @@ var slideDe = (b) => {
|
|
|
2759
2875
|
return sc.slides[Math.min(b.slide, sc.slides.length - 1)];
|
|
2760
2876
|
};
|
|
2761
2877
|
function listaDestino(b, capa) {
|
|
2762
|
-
const s = slideDe(b);
|
|
2763
2878
|
const ref = txt(capa).trim();
|
|
2879
|
+
if (!ref && slideDe(b).base.some((e) => e.kind === "arena")) {
|
|
2880
|
+
nuevoSlide(b, "");
|
|
2881
|
+
b.bitacora.push("\u{1F4C4} Slide nuevo: el de la arena es solo para la arena");
|
|
2882
|
+
}
|
|
2883
|
+
const s = slideDe(b);
|
|
2764
2884
|
if (!ref) return { els: s.base };
|
|
2765
2885
|
const capaObj = capaPorNombre(b, ref) ?? crearCapa(b, ref);
|
|
2766
2886
|
b.capaDeTrabajo = capaObj.id;
|
|
@@ -2839,7 +2959,33 @@ function encajar(el) {
|
|
|
2839
2959
|
el.y = +Math.max(0, Math.min(100 - el.h, el.y)).toFixed(2);
|
|
2840
2960
|
return el;
|
|
2841
2961
|
}
|
|
2842
|
-
function
|
|
2962
|
+
function caminoPorNombre(suyas, nombre, estricto) {
|
|
2963
|
+
const q = nombre.trim().toLowerCase();
|
|
2964
|
+
if (!q) return suyas[0];
|
|
2965
|
+
return suyas.find((m) => m.name.trim().toLowerCase() === q) ?? (estricto ? void 0 : suyas.find((m) => m.name.toLowerCase().includes(q)) ?? suyas[0]);
|
|
2966
|
+
}
|
|
2967
|
+
function puntosPedidos(a) {
|
|
2968
|
+
const esPt = (p) => !!p && typeof p === "object" && Number.isFinite(p.x) && Number.isFinite(p.y);
|
|
2969
|
+
const pts = Array.isArray(a.puntos) ? a.puntos.filter(esPt) : [];
|
|
2970
|
+
if (esPt(a.hasta)) pts.push(a.hasta);
|
|
2971
|
+
return pts.map((p) => ({ x: Math.max(-40, Math.min(140, p.x)), y: Math.max(-40, Math.min(140, p.y)) }));
|
|
2972
|
+
}
|
|
2973
|
+
function salidaDelCamino(el, movido, a) {
|
|
2974
|
+
const paso = Math.round(num3(a.paso, 1));
|
|
2975
|
+
if (txt(a.evento) !== "clickStep" || paso < 2) return { x: 0, y: 0 };
|
|
2976
|
+
const previo = (el.triggers ?? []).find((t) => t.event.type === "clickStep" && t.event.step === paso - 1 && t.action.type === "playMotion" && t.action.targetId === movido.id);
|
|
2977
|
+
const idPrevio = previo?.action.type === "playMotion" ? previo.action.motionId : "";
|
|
2978
|
+
const cam = (movido.motions ?? []).find((m) => m.id === idPrevio);
|
|
2979
|
+
return cam?.path.length ? { ...cam.path[cam.path.length - 1] } : { x: 0, y: 0 };
|
|
2980
|
+
}
|
|
2981
|
+
function trazarCamino(a, destinos) {
|
|
2982
|
+
const r2 = (p) => ({ x: +p.x.toFixed(2), y: +p.y.toFixed(2) });
|
|
2983
|
+
if (destinos.length > 1) return [a, ...destinos].map(r2);
|
|
2984
|
+
const z = destinos[0];
|
|
2985
|
+
const n2 = Math.max(4, Math.min(40, Math.round(Math.hypot(z.x - a.x, z.y - a.y) / 2.5)));
|
|
2986
|
+
return Array.from({ length: n2 + 1 }, (_, i) => r2({ x: a.x + (z.x - a.x) * i / n2, y: a.y + (z.y - a.y) * i / n2 }));
|
|
2987
|
+
}
|
|
2988
|
+
function armarEvento(a, el, estricto) {
|
|
2843
2989
|
switch (txt(a.evento)) {
|
|
2844
2990
|
case "hover":
|
|
2845
2991
|
return { type: "hover" };
|
|
@@ -2852,9 +2998,7 @@ function armarEvento(a, el) {
|
|
|
2852
2998
|
case "clickStep":
|
|
2853
2999
|
return { type: "clickStep", step: Math.max(1, Math.round(num3(a.paso, 1))) };
|
|
2854
3000
|
case "trayectoriaTerminada": {
|
|
2855
|
-
const
|
|
2856
|
-
const suyas = el.motions ?? [];
|
|
2857
|
-
const cam = (nombre ? suyas.find((m) => m.name.toLowerCase().includes(nombre)) : void 0) ?? suyas[0];
|
|
3001
|
+
const cam = caminoPorNombre(el.motions ?? [], txt(a.trayectoria), estricto);
|
|
2858
3002
|
return cam ? { type: "motionEnd", motionId: cam.id } : { type: "click" };
|
|
2859
3003
|
}
|
|
2860
3004
|
default:
|
|
@@ -2910,10 +3054,32 @@ function armarAccion(b, a, el) {
|
|
|
2910
3054
|
if (d && !quien) return { accion: null, fallo: `No encuentro el objeto "${d}" para moverlo.` };
|
|
2911
3055
|
const movido = quien ?? buscarEl(b, txt(a.objetivo));
|
|
2912
3056
|
if (!movido) return { accion: null, fallo: "No s\xE9 qu\xE9 objeto tiene que moverse." };
|
|
2913
|
-
const nombre = txt(a.trayectoria).trim()
|
|
3057
|
+
const nombre = txt(a.trayectoria).trim();
|
|
2914
3058
|
const suyas = movido.motions ?? [];
|
|
2915
|
-
const
|
|
3059
|
+
const pts = puntosPedidos(a);
|
|
3060
|
+
if (pts.length) {
|
|
3061
|
+
const c = { x: movido.x + movido.w / 2, y: movido.y + movido.h / 2 };
|
|
3062
|
+
const path = trazarCamino(salidaDelCamino(el, movido, a), pts.map((p) => ({ x: p.x - c.x, y: p.y - c.y })));
|
|
3063
|
+
const duration = Math.round(Math.max(200, Math.min(6e4, num3(a.duracion, 2e3))));
|
|
3064
|
+
const igual = nombre ? suyas.find((m) => m.name.trim().toLowerCase() === nombre.toLowerCase()) : void 0;
|
|
3065
|
+
if (igual) {
|
|
3066
|
+
igual.path = path;
|
|
3067
|
+
igual.duration = duration;
|
|
3068
|
+
} else {
|
|
3069
|
+
let n2 = suyas.length + 1;
|
|
3070
|
+
while (suyas.some((m) => m.name === `Trayectoria ${n2}`)) n2 += 1;
|
|
3071
|
+
movido.motions = [...suyas, { id: nid("mo"), name: nombre || `Trayectoria ${n2}`, path, duration }];
|
|
3072
|
+
}
|
|
3073
|
+
const cam2 = igual ?? movido.motions[movido.motions.length - 1];
|
|
3074
|
+
b.tocados.push(movido.id);
|
|
3075
|
+
b.bitacora.push(`\u219D ${describirEl(movido)}: camino \xAB${cam2.name}\xBB${igual ? " retrazado" : ""}`);
|
|
3076
|
+
return { accion: { type: "playMotion", targetId: movido.id, motionId: cam2.id } };
|
|
3077
|
+
}
|
|
3078
|
+
const cam = caminoPorNombre(suyas, nombre, b.estricto);
|
|
2916
3079
|
if (cam) return { accion: { type: "playMotion", targetId: movido.id, motionId: cam.id } };
|
|
3080
|
+
if (b.estricto) {
|
|
3081
|
+
return { accion: null, fallo: `${nombre && suyas.length ? `${describirEl(movido)} no tiene ning\xFAn camino \xAB${nombre}\xBB (tiene: ${suyas.map((m) => `\xAB${m.name}\xBB`).join(", ")}). ` : ""}Para trazarlo, pasa \`hasta\` {x, y} \u2014a d\xF3nde llega el centro del objeto, en % del slide\u2014 o \`puntos\`.` };
|
|
3082
|
+
}
|
|
2917
3083
|
const nuevo = { id: nid("mo"), name: txt(a.trayectoria) || "Trayectoria 1", path: [], duration: 2e3 };
|
|
2918
3084
|
movido.motions = [...suyas, nuevo];
|
|
2919
3085
|
b.tocados.push(movido.id);
|
|
@@ -2951,6 +3117,25 @@ function armarAccion(b, a, el) {
|
|
|
2951
3117
|
}
|
|
2952
3118
|
}
|
|
2953
3119
|
function ejecutar(b, nombre, args, fab) {
|
|
3120
|
+
const antes = b.creados.length;
|
|
3121
|
+
const dijo = ejecutarUna(b, nombre, args, fab);
|
|
3122
|
+
return b.estricto && !b.fallo ? dijo + dejarFuera(b, nombre, args, antes) : dijo;
|
|
3123
|
+
}
|
|
3124
|
+
var enElAreaDeTrabajo = (v, lado) => Math.max(-lado - 30, Math.min(130, v));
|
|
3125
|
+
function dejarFuera(b, nombre, args, antes) {
|
|
3126
|
+
if (!nombre.startsWith("poner_") || ["poner_fondo", "poner_arena", "poner_audio", "poner_texto_plantilla"].includes(nombre)) return "";
|
|
3127
|
+
if (b.creados.length !== antes + 1) return "";
|
|
3128
|
+
const el = buscarEl(b, b.creados[antes]);
|
|
3129
|
+
if (!el) return "";
|
|
3130
|
+
const x = typeof args.x === "number" && Number.isFinite(args.x) ? args.x : null;
|
|
3131
|
+
const y = typeof args.y === "number" && Number.isFinite(args.y) ? args.y : null;
|
|
3132
|
+
const sale = (v, lado) => v !== null && (v < 0 || v + lado > 100);
|
|
3133
|
+
if (!sale(x, el.w) && !sale(y, el.h)) return "";
|
|
3134
|
+
if (sale(x, el.w)) el.x = +enElAreaDeTrabajo(x, el.w).toFixed(2);
|
|
3135
|
+
if (sale(y, el.h)) el.y = +enElAreaDeTrabajo(y, el.h).toFixed(2);
|
|
3136
|
+
return " Qued\xF3 FUERA del slide (o asomando): no se ve entero hasta que una trayectoria lo meta.";
|
|
3137
|
+
}
|
|
3138
|
+
function ejecutarUna(b, nombre, args, fab) {
|
|
2954
3139
|
switch (nombre) {
|
|
2955
3140
|
// ── Poner cosas ──────────────────────────────────────────────────────────
|
|
2956
3141
|
case "poner_texto": {
|
|
@@ -3108,6 +3293,36 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3108
3293
|
b.bitacora.push(`\u{1F5BC}\uFE0F Imagen subida${dentroDe ? ` en la capa \xAB${dentroDe}\xBB` : ""}`);
|
|
3109
3294
|
return `Puesta la imagen (id ${el.id}).`;
|
|
3110
3295
|
}
|
|
3296
|
+
// Solo lo ofrece el MCP (`catalogo.ts`, `sinNavegador`): el Robot no pone animaciones.
|
|
3297
|
+
case "poner_lottie": {
|
|
3298
|
+
const url = txt(args.url).trim();
|
|
3299
|
+
const busca = txt(args.busca).trim();
|
|
3300
|
+
if (!url && !busca) return no(b, "Dime qu\xE9 animaci\xF3n buscar (`busca`) o p\xE1same la `url` de un Lottie.");
|
|
3301
|
+
if (url && !/^https?:\/\//i.test(url)) return no(b, "La `url` tiene que ser una direcci\xF3n https:// de un .json de Lottie.");
|
|
3302
|
+
const hallada = url ? null : buscarAnimacion(busca);
|
|
3303
|
+
if (!url && !hallada) {
|
|
3304
|
+
return no(b, `La galer\xEDa de animaciones no tiene nada parecido a "${busca}" (o no contest\xF3). Prueba otra palabra, en singular, o pon un dibujo con \`poner_dibujo\`.`);
|
|
3305
|
+
}
|
|
3306
|
+
if (hallada?.plan === "pro" && !b.premium) {
|
|
3307
|
+
return no(b, `La animaci\xF3n \xAB${hallada.nombre}\xBB es del plan Pro y esta cuenta no lo tiene. Se activa desde boosterarena.com.`);
|
|
3308
|
+
}
|
|
3309
|
+
const w = pct(args.w, 20);
|
|
3310
|
+
const prop = hallada?.caja.w && hallada.caja.h ? hallada.caja.w / hallada.caja.h : 1;
|
|
3311
|
+
const el = encajar({
|
|
3312
|
+
id: nid("lottie"),
|
|
3313
|
+
kind: "lottie",
|
|
3314
|
+
x: pct(args.x, 40),
|
|
3315
|
+
y: pct(args.y, 30),
|
|
3316
|
+
w,
|
|
3317
|
+
h: +(w / prop * (1920 / 1080)).toFixed(1),
|
|
3318
|
+
src: hallada?.src ?? url
|
|
3319
|
+
});
|
|
3320
|
+
const { dentroDe } = plantar(b, el, args.ref, args.capa);
|
|
3321
|
+
b.bitacora.push(`\u2728 Animaci\xF3n ${hallada ? `\xAB${hallada.nombre}\xBB` : "propia"}${dentroDe ? ` en la capa \xAB${dentroDe}\xBB` : ""}`);
|
|
3322
|
+
if (!hallada) return `Animaci\xF3n puesta (id ${el.id}).`;
|
|
3323
|
+
const otros = hallada.parecidos.length ? ` Otras parecidas: ${hallada.parecidos.map((x) => `"${x}"`).join(", ")}.` : "";
|
|
3324
|
+
return hallada.exacto ? `Animaci\xF3n "${hallada.nombre}" puesta (id ${el.id}, exacto: true).${otros}` : `Animaci\xF3n "${hallada.nombre}" puesta (id ${el.id}, exacto: false). \u26A0\uFE0F OJO: no hay ninguna que sea "${busca}"; es la m\xE1s cercana.${otros}`;
|
|
3325
|
+
}
|
|
3111
3326
|
case "poner_audio": {
|
|
3112
3327
|
const url = txt(args.url).trim();
|
|
3113
3328
|
if (!/^https?:\/\//i.test(url)) {
|
|
@@ -3121,7 +3336,8 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3121
3336
|
w: 16,
|
|
3122
3337
|
h: 7,
|
|
3123
3338
|
src: url,
|
|
3124
|
-
name: txt(args.nombre) || "Audio"
|
|
3339
|
+
name: txt(args.nombre) || "Audio",
|
|
3340
|
+
vol: AUDIO_VOL_INICIAL
|
|
3125
3341
|
};
|
|
3126
3342
|
plantar(b, el, args.ref, void 0);
|
|
3127
3343
|
b.bitacora.push(`\u{1F50A} Audio \xAB${el.name}\xBB`);
|
|
@@ -3206,21 +3422,35 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3206
3422
|
case "poner_fondo": {
|
|
3207
3423
|
const color2 = txt(args.color).trim();
|
|
3208
3424
|
const busca = txt(args.busca).trim();
|
|
3425
|
+
const url = txt(args.url).trim();
|
|
3426
|
+
const refCapa = txt(args.capa).trim();
|
|
3427
|
+
const capa = refCapa ? capaPorNombre(b, refCapa) : void 0;
|
|
3428
|
+
if (refCapa && !capa) {
|
|
3429
|
+
return no(b, `No hay ninguna capa "${refCapa}" en este slide. Cr\xE9ala antes con \`nueva_capa\` (que tambi\xE9n acepta \`fondo\`).`);
|
|
3430
|
+
}
|
|
3431
|
+
const destino = capa ?? slideDe(b);
|
|
3432
|
+
const d\u00F3nde = capa ? ` de la capa \xAB${capa.name}\xBB` : "";
|
|
3433
|
+
if (url) {
|
|
3434
|
+
if (!/^https?:\/\//i.test(url)) return no(b, "La `url` tiene que ser una imagen ya SUBIDA (https://). S\xFAbela antes con `subir_imagen`.");
|
|
3435
|
+
destino.background = url;
|
|
3436
|
+
b.bitacora.push(`\u{1F3DE}\uFE0F Fondo propio${d\u00F3nde}`);
|
|
3437
|
+
return `Fondo${d\u00F3nde} puesto con esa imagen.`;
|
|
3438
|
+
}
|
|
3209
3439
|
if (busca) {
|
|
3210
3440
|
const f = buscarFondo(busca);
|
|
3211
3441
|
if (!f) return no(b, `No hay ning\xFAn fondo parecido a "${busca}". Puedes poner un color liso.`);
|
|
3212
|
-
|
|
3213
|
-
b.bitacora.push(`\u{1F3DE}\uFE0F Fondo: ${f.nombre}`);
|
|
3442
|
+
destino.background = f.src;
|
|
3443
|
+
b.bitacora.push(`\u{1F3DE}\uFE0F Fondo${d\u00F3nde}: ${f.nombre}`);
|
|
3214
3444
|
const otros = f.parecidos.length ? ` Otros parecidos: ${f.parecidos.map((x) => `"${x}"`).join(", ")}.` : "";
|
|
3215
|
-
return f.exacto ? `Fondo puesto: "${f.nombre}" (exacto: true).${otros}` : `Fondo puesto: "${f.nombre}" (exacto: false). \u26A0\uFE0F OJO: no hay ning\xFAn fondo que sea "${busca}"; es lo m\xE1s cercano.${otros} Si no sirve, prueba otra palabra o pon un color liso.`;
|
|
3445
|
+
return f.exacto ? `Fondo${d\u00F3nde} puesto: "${f.nombre}" (exacto: true).${otros}` : `Fondo${d\u00F3nde} puesto: "${f.nombre}" (exacto: false). \u26A0\uFE0F OJO: no hay ning\xFAn fondo que sea "${busca}"; es lo m\xE1s cercano.${otros} Si no sirve, prueba otra palabra o pon un color liso.`;
|
|
3216
3446
|
}
|
|
3217
3447
|
if (!color2) return no(b, "Dime un color o una escena a buscar.");
|
|
3218
3448
|
if (!COLOR_DE_FONDO.test(color2)) {
|
|
3219
3449
|
return no(b, `"${color2}" no es un color que el slide sepa pintar. M\xE1ndalo en hex ("#0f172a"), rgb(), hsl() o como un degradado CSS.`);
|
|
3220
3450
|
}
|
|
3221
|
-
|
|
3222
|
-
b.bitacora.push(`\u{1F3A8} Fondo ${color2}`);
|
|
3223
|
-
return
|
|
3451
|
+
destino.background = color2;
|
|
3452
|
+
b.bitacora.push(`\u{1F3A8} Fondo ${color2}${d\u00F3nde}`);
|
|
3453
|
+
return `Fondo de color${d\u00F3nde} puesto.`;
|
|
3224
3454
|
}
|
|
3225
3455
|
case "poner_booster": {
|
|
3226
3456
|
const tipo = txt(args.tipo);
|
|
@@ -3348,6 +3578,12 @@ ${fallosEnTexto(r.fallos)}
|
|
|
3348
3578
|
|
|
3349
3579
|
Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarlo.`);
|
|
3350
3580
|
}
|
|
3581
|
+
if (el.widget === "magicwand") {
|
|
3582
|
+
const imgs = cfg.valor.images ?? [];
|
|
3583
|
+
imgs.forEach((im) => {
|
|
3584
|
+
if (!im.id) im.id = nid("wand");
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3351
3587
|
el.wcfg = ficha.donde === "wcfg.ext" ? { ...el.wcfg, ext: cfg.valor } : { ...el.wcfg?.ext ? { ext: el.wcfg.ext } : {}, ...cfg.valor };
|
|
3352
3588
|
b.tocados.push(el.id);
|
|
3353
3589
|
b.bitacora.push(`\u270E Booster ${el.widget} configurado`);
|
|
@@ -3361,9 +3597,14 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3361
3597
|
}
|
|
3362
3598
|
case "nueva_capa": {
|
|
3363
3599
|
const r = txt(args.ref).trim();
|
|
3600
|
+
const fondo = txt(args.fondo).trim();
|
|
3601
|
+
if (fondo && !/^https?:\/\//i.test(fondo) && !COLOR_DE_FONDO.test(fondo)) {
|
|
3602
|
+
return no(b, `"${fondo}" no sirve de fondo: tiene que ser la URL https:// de una imagen ya subida o un color en hex.`);
|
|
3603
|
+
}
|
|
3364
3604
|
const capa = crearCapa(b, txt(args.nombre) || r);
|
|
3365
3605
|
if (r) b.aliasCapa.set(r, capa.id);
|
|
3366
|
-
|
|
3606
|
+
if (fondo) capa.background = fondo;
|
|
3607
|
+
return `Capa "${capa.name}" creada${fondo ? " con su fondo" : ""}. Para poner cosas dentro, p\xE1sale capa="${r || capa.name}".`;
|
|
3367
3608
|
}
|
|
3368
3609
|
case "nueva_escena": {
|
|
3369
3610
|
const nombre2 = txt(args.nombre) || `Escena ${b.doc.length + 1}`;
|
|
@@ -3389,9 +3630,22 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3389
3630
|
b.bitacora.push(`\u{1F9F9} Sin programaci\xF3n: ${describirEl(el)}`);
|
|
3390
3631
|
return `Le quit\xE9 ${n0} disparador(es).`;
|
|
3391
3632
|
}
|
|
3633
|
+
if (el.kind === "widget" || el.kind === "arena") {
|
|
3634
|
+
return no(b, `${describirEl(el)} es un booster o una arena: no lleva disparadores, ya trae su propio funcionamiento. Programa otro objeto del slide (un texto, una imagen, un bot\xF3n).`);
|
|
3635
|
+
}
|
|
3392
3636
|
const { accion, fallo } = armarAccion(b, args, el);
|
|
3393
3637
|
if (!accion) return no(b, fallo ?? "No entend\xED qu\xE9 acci\xF3n quer\xEDas.");
|
|
3394
|
-
|
|
3638
|
+
if (b.estricto && txt(args.evento) === "trayectoriaTerminada" && !caminoPorNombre(el.motions ?? [], txt(args.trayectoria), true)) {
|
|
3639
|
+
return no(b, `${describirEl(el)} no tiene ${txt(args.trayectoria) ? `ning\xFAn camino \xAB${txt(args.trayectoria)}\xBB` : "ning\xFAn camino"}, as\xED que \xABal terminar su trayectoria\xBB no tendr\xEDa cu\xE1ndo dispararse. Progr\xE1male antes \`moverPorTrayectoria\` con \`hasta\`.`);
|
|
3640
|
+
}
|
|
3641
|
+
const trig = { id: nid("trig"), event: armarEvento(args, el, b.estricto), action: accion };
|
|
3642
|
+
if (trig.event.type === "motionEnd" && accion.type === "playMotion" && trig.event.motionId === accion.motionId) {
|
|
3643
|
+
return no(b, "Eso har\xEDa un BUCLE: al terminar el camino volver\xEDa a empezar el mismo camino, sin fin. Si quieres que d\xE9 vueltas, usa otro evento; si quieres encadenar, dale otro nombre al segundo camino.");
|
|
3644
|
+
}
|
|
3645
|
+
const capaDelEl = slideDe(b).layers.find((l) => l.els.some((e) => e.id === el.id));
|
|
3646
|
+
if (capaDelEl && abreEnBucle(slideDe(b), capaDelEl.id, trig)) {
|
|
3647
|
+
return no(b, `Eso har\xEDa un BUCLE: al mostrarse la capa \xAB${capaDelEl.name}\xBB se volver\xEDa a abrir a s\xED misma sin parar y la clase se congelar\xEDa. Usa otra capa, \`cerrarCapa\` o el evento de clic.`);
|
|
3648
|
+
}
|
|
3395
3649
|
el.triggers = args.primero === true ? [trig, ...el.triggers ?? []] : [...el.triggers ?? [], trig];
|
|
3396
3650
|
b.tocados.push(el.id);
|
|
3397
3651
|
b.bitacora.push(`\u26A1 ${describirEl(el)}: ${describirTrigger(trig, b)}`);
|
|
@@ -3411,12 +3665,13 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3411
3665
|
el.w = w;
|
|
3412
3666
|
}
|
|
3413
3667
|
if (num3(args.alto, 0) > 0) el.h = pct(args.alto, el.h);
|
|
3414
|
-
if (typeof args.x === "number") el.x = pct(args.x, el.x);
|
|
3415
|
-
if (typeof args.y === "number") el.y = pct(args.y, el.y);
|
|
3416
|
-
|
|
3668
|
+
if (typeof args.x === "number") el.x = b.estricto ? +enElAreaDeTrabajo(args.x, el.w).toFixed(2) : pct(args.x, el.x);
|
|
3669
|
+
if (typeof args.y === "number") el.y = b.estricto ? +enElAreaDeTrabajo(args.y, el.h).toFixed(2) : pct(args.y, el.y);
|
|
3670
|
+
const afuera = b.estricto && (el.x < 0 || el.y < 0 || el.x + el.w > 100 || el.y + el.h > 100);
|
|
3671
|
+
if (!afuera) encajar(el);
|
|
3417
3672
|
b.tocados.push(el.id);
|
|
3418
3673
|
b.bitacora.push(`\u2194\uFE0F Recolocado: ${describirEl(el)}`);
|
|
3419
|
-
return "Movido.";
|
|
3674
|
+
return afuera ? "Movido. Qued\xF3 FUERA del slide: no se ve hasta que una trayectoria lo meta." : "Movido.";
|
|
3420
3675
|
}
|
|
3421
3676
|
case "cambiar_estilo": {
|
|
3422
3677
|
const el = buscarEl(b, txt(args.objetivo));
|
|
@@ -3438,6 +3693,7 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3438
3693
|
if (txt(args.align)) el.align = txt(args.align);
|
|
3439
3694
|
if (typeof args.opacidad === "number") el.opacity = Math.max(0, Math.min(1, args.opacidad));
|
|
3440
3695
|
if (typeof args.giro === "number") el.rot = args.giro || void 0;
|
|
3696
|
+
if (typeof args.gris_al_tocar === "boolean") el.grayClick = args.gris_al_tocar || void 0;
|
|
3441
3697
|
b.tocados.push(el.id);
|
|
3442
3698
|
b.bitacora.push(`\u{1F3A8} Retocado: ${describirEl(el)}`);
|
|
3443
3699
|
return "Estilo cambiado.";
|
|
@@ -4026,6 +4282,7 @@ export {
|
|
|
4026
4282
|
FORMA_DEF,
|
|
4027
4283
|
buscarDibujo,
|
|
4028
4284
|
buscarFondo,
|
|
4285
|
+
cargarAnimaciones,
|
|
4029
4286
|
cargarBiblioteca,
|
|
4030
4287
|
construirArena,
|
|
4031
4288
|
construirCatalogo,
|
package/package.json
CHANGED
package/proyectos.mjs
CHANGED
|
@@ -50,7 +50,8 @@ async function pedir(token, ruta, opciones = {}) {
|
|
|
50
50
|
|
|
51
51
|
/** Los proyectos del maestro, del más reciente al más viejo. Sin el documento. */
|
|
52
52
|
export async function listar(token, { tipo, limite = 50 } = {}) {
|
|
53
|
-
|
|
53
|
+
// `deleted_at=is.null`: lo que el maestro mandó a la Papelera no se lista.
|
|
54
|
+
const q = new URLSearchParams({ select: COLUMNAS_LISTA, order: 'updated_at.desc', limit: String(limite), deleted_at: 'is.null' });
|
|
54
55
|
if (tipo) q.set('type', `eq.${tipo}`);
|
|
55
56
|
return pedir(token, `projects?${q}`);
|
|
56
57
|
}
|
package/servidor.mjs
CHANGED
|
@@ -138,6 +138,7 @@ function herramientasDelLienzo() {
|
|
|
138
138
|
boosters: nucleo.ALL_WIDGETS.filter((w) => w.ready).map((w) => w.type),
|
|
139
139
|
arenas: nucleo.ARENAS.filter((a) => a.ready).map((a) => a.type),
|
|
140
140
|
formas: Object.keys(nucleo.FORMA_DEF),
|
|
141
|
+
sinNavegador: true, // trazar trayectorias, lotties, fondos por URL y en capas, gris al tocar
|
|
141
142
|
}).map((t) => ({
|
|
142
143
|
name: t.function.name,
|
|
143
144
|
description: t.function.description,
|
|
@@ -281,6 +282,7 @@ function queEs(e) {
|
|
|
281
282
|
if (e.kind === 'arena') return `ARENA ${e.arena}`;
|
|
282
283
|
if (e.kind === 'widget') return `booster ${e.widget}${e.widget === 'instructions' ? ` «${corto(e.wcfg?.ext?.title, 24)}»` : ''}`;
|
|
283
284
|
if (e.kind === 'image') return 'imagen';
|
|
285
|
+
if (e.kind === 'lottie') return 'animación';
|
|
284
286
|
if (e.kind === 'audio') return `audio «${corto(e.name, 24)}»`;
|
|
285
287
|
if (e.kind === 'shape' && e.shape === 'button') return `botón «${corto(e.text, 24)}»`;
|
|
286
288
|
if (e.kind === 'shape' && e.hotspot) return 'zona caliente';
|
|
@@ -294,6 +296,14 @@ function queHace(t, doc, slide) {
|
|
|
294
296
|
const cuando = { click: 'al tocarlo', hover: 'al pasar encima', slideStart: 'al entrar al slide',
|
|
295
297
|
layerShown: 'al abrirse su capa', time: `a los ${ev.seconds} s`, clickStep: `en el clic ${ev.step}`,
|
|
296
298
|
motionEnd: 'al terminar su trayectoria' }[ev.type] ?? ev.type;
|
|
299
|
+
// El nombre del camino, que es por lo que se le apunta: «al terminar "Trayectoria 2"».
|
|
300
|
+
const camino = (id) => {
|
|
301
|
+
for (const x of [...slide.base, ...slide.layers.flatMap((l) => l.els)]) {
|
|
302
|
+
const m = (x.motions ?? []).find((mm) => mm.id === id);
|
|
303
|
+
if (m) return `«${m.name}»`;
|
|
304
|
+
}
|
|
305
|
+
return 'su trayectoria';
|
|
306
|
+
};
|
|
297
307
|
const a = t.action ?? {};
|
|
298
308
|
const capa = (id) => slide.layers.find((l) => l.id === id)?.name ?? id;
|
|
299
309
|
const qué = a.type === 'showLayer' ? `abre la capa «${capa(a.layerId)}»`
|
|
@@ -304,17 +314,22 @@ function queHace(t, doc, slide) {
|
|
|
304
314
|
: a.type === 'speak' ? `lee «${corto(a.text, 24)}»`
|
|
305
315
|
: a.type === 'openUrl' ? `abre ${a.url}`
|
|
306
316
|
: a.type === 'hideEl' ? `oculta ${a.targetId || 'el objeto'}`
|
|
307
|
-
: a.type === 'playMotion' ?
|
|
317
|
+
: a.type === 'playMotion' ? `recorre ${camino(a.motionId)}${a.targetId !== t.ownerId ? ` (${a.targetId})` : ''}`
|
|
308
318
|
: a.type;
|
|
309
319
|
const luego = (t.luego ?? []).length ? ` (+${t.luego.length} en cadena)` : '';
|
|
310
|
-
return `${cuando} → ${qué}${luego}`;
|
|
320
|
+
return `${ev.type === 'motionEnd' ? `al terminar ${camino(ev.motionId)}` : cuando} → ${qué}${luego}`;
|
|
311
321
|
}
|
|
312
322
|
|
|
313
323
|
function listarEls(els, doc, slide, sangria) {
|
|
314
324
|
const filas = [];
|
|
315
325
|
for (const e of els) {
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
const donde = `@${Math.round(e.x)},${Math.round(e.y)} ${Math.round(e.w)}×${Math.round(e.h)}`;
|
|
327
|
+
filas.push(`${sangria}· ${queEs(e)} ${donde}${e.grayClick ? ' · gris al tocar' : ''} [${e.id}]`);
|
|
328
|
+
for (const m of e.motions ?? []) {
|
|
329
|
+
const fin = m.path[m.path.length - 1];
|
|
330
|
+
filas.push(`${sangria} ↝ «${m.name}» ${m.duration} ms${fin ? `, termina desplazado (${Math.round(fin.x)}, ${Math.round(fin.y)})` : ' — ⚠️ SIN CAMINO: no se mueve'}`);
|
|
331
|
+
}
|
|
332
|
+
for (const t of e.triggers ?? []) filas.push(`${sangria} ⚡ ${queHace({ ...t, ownerId: e.id }, doc, slide)}`);
|
|
318
333
|
}
|
|
319
334
|
return filas;
|
|
320
335
|
}
|
|
@@ -330,7 +345,7 @@ function resumenDelDoc(doc, { escena, slide } = {}) {
|
|
|
330
345
|
filas.push(` slide ${j + 1}${s.name ? ` «${s.name}»` : ''}${fondo}${s.base.length || s.layers.length ? '' : ' (vacío)'}`);
|
|
331
346
|
filas.push(...listarEls(s.base, doc, s, ' '));
|
|
332
347
|
for (const l of s.layers) {
|
|
333
|
-
filas.push(` ▣ capa «${l.name}» [${l.id}] — ${l.els.length} objeto(s)${l.background ? ` · fondo ${l.background}` : ''}`);
|
|
348
|
+
filas.push(` ▣ capa «${l.name}» [${l.id}] — ${l.els.length} objeto(s)${l.background ? ` · fondo ${/^https?:/.test(l.background) ? 'imagen' : l.background}` : ''}`);
|
|
334
349
|
filas.push(...listarEls(l.els, doc, s, ' '));
|
|
335
350
|
}
|
|
336
351
|
});
|
|
@@ -370,7 +385,9 @@ async function llamar(nombre, args = {}) {
|
|
|
370
385
|
yo.proyecto = p;
|
|
371
386
|
yo.borrador = nucleo.nuevoBorrador(Array.isArray(p.document) ? p.document : [], 0, 0, '');
|
|
372
387
|
yo.borrador.estricto = true; // sin nadie delante: lo que el Robot resuelve solo, aquí se niega
|
|
373
|
-
|
|
388
|
+
yo.borrador.premium = yo.plan === 'pro';
|
|
389
|
+
// `poner_dibujo`, `poner_fondo` y `poner_lottie` buscan en caliente.
|
|
390
|
+
await Promise.all([nucleo.cargarBiblioteca(), nucleo.cargarAnimaciones()]);
|
|
374
391
|
// El id va en la PRIMERA línea y con su nombre: antes no venía y había que llamar a
|
|
375
392
|
// `listar_proyectos` y buscar por título, que con ocho pilotos de nombre parecido es jugársela.
|
|
376
393
|
// No hay URL propia de un proyecto: el editor no abre proyectos por dirección, se entra desde la
|