boosterarena-mcp 0.2.2 → 0.3.0
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 +258 -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,39 @@ 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
|
+
const recolocar = props("recolocar");
|
|
1707
|
+
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).");
|
|
1708
|
+
recolocar.y = num("Nuevo borde superior en %. Tambi\xE9n puede quedar fuera del slide, como `x`.");
|
|
1709
|
+
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.");
|
|
1710
|
+
const i = lista2.findIndex((t) => t.function.name === "poner_emoji");
|
|
1711
|
+
lista2.splice(i + 1, 0, fn(
|
|
1712
|
+
"poner_lottie",
|
|
1713
|
+
"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.",
|
|
1714
|
+
obj({
|
|
1715
|
+
ref: REF,
|
|
1716
|
+
x: X,
|
|
1717
|
+
y: Y,
|
|
1718
|
+
w: W,
|
|
1719
|
+
capa: CAPA,
|
|
1720
|
+
busca: str('Qu\xE9 animaci\xF3n buscar en la galer\xEDa, en espa\xF1ol, ingl\xE9s o portugu\xE9s: "regalo", "trofeo", "estrella", "altavoz".'),
|
|
1721
|
+
url: str("En vez de `busca`: la URL https:// de un Lottie .json (lottie.host, o uno subido).")
|
|
1722
|
+
}, ["ref", "x", "y", "w"])
|
|
1723
|
+
));
|
|
1724
|
+
return lista2;
|
|
1659
1725
|
}
|
|
1660
1726
|
|
|
1661
1727
|
// ../src/insert/shapes.ts
|
|
@@ -1711,6 +1777,33 @@ var FORMA_DEF = {
|
|
|
1711
1777
|
blob: { anch: 26, alto: "cuadrado", color: "#c4b5fd" }
|
|
1712
1778
|
};
|
|
1713
1779
|
|
|
1780
|
+
// ../src/canvas/audioVol.ts
|
|
1781
|
+
var AUDIO_VOL_INICIAL = 0.7;
|
|
1782
|
+
|
|
1783
|
+
// ../src/canvas/bucleCapas.ts
|
|
1784
|
+
var alMostrar = (t) => t.event.type === "layerShown" || t.event.type === "slideStart";
|
|
1785
|
+
function capasQueAbre(t) {
|
|
1786
|
+
return [t.action, ...(t.luego ?? []).map((p) => p.action)].filter((a) => a.type === "showLayer" && a.layerId).map((a) => a.layerId);
|
|
1787
|
+
}
|
|
1788
|
+
function abreAlMostrarse(slide, capaId) {
|
|
1789
|
+
const capa = slide.layers.find((l) => l.id === capaId);
|
|
1790
|
+
if (!capa) return [];
|
|
1791
|
+
return capa.els.flatMap((el) => [...el.triggers ?? [], ...el.gtriggers ?? []]).filter(alMostrar).flatMap(capasQueAbre);
|
|
1792
|
+
}
|
|
1793
|
+
function abreEnBucle(slide, desde, t) {
|
|
1794
|
+
if (!alMostrar(t)) return false;
|
|
1795
|
+
const vistas = /* @__PURE__ */ new Set();
|
|
1796
|
+
const pendientes = [...capasQueAbre(t)];
|
|
1797
|
+
while (pendientes.length) {
|
|
1798
|
+
const id = pendientes.pop();
|
|
1799
|
+
if (id === desde) return true;
|
|
1800
|
+
if (vistas.has(id)) continue;
|
|
1801
|
+
vistas.add(id);
|
|
1802
|
+
pendientes.push(...abreAlMostrarse(slide, id));
|
|
1803
|
+
}
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1714
1807
|
// ../src/shared/fontsAncho.ts
|
|
1715
1808
|
var ANCHO_LETRA = {
|
|
1716
1809
|
"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 +1942,28 @@ var ESQUEMAS_BOOSTERS = {
|
|
|
1849
1942
|
campos: {
|
|
1850
1943
|
images: lista("Las im\xE1genes que la varita saca una a una.", objeto("Una imagen.", {
|
|
1851
1944
|
src: imagen("La imagen.", { obligatorio: true }),
|
|
1852
|
-
label: texto("R\xF3tulo que se planta
|
|
1945
|
+
label: texto("R\xF3tulo que se planta con ella. Vac\xEDo = sin r\xF3tulo."),
|
|
1946
|
+
dest: objeto("D\xD3NDE cae la imagen al salir de la varita, en % del slide. Sin esto, se reparten solas en filas de 4.", {
|
|
1947
|
+
x: num2("Borde izquierdo, en % del ancho.", { entero: false, obligatorio: true }),
|
|
1948
|
+
y: num2("Borde superior, en % del alto.", { entero: false, obligatorio: true }),
|
|
1949
|
+
w: num2("Ancho, en % del ancho del slide. El alto sale de la imagen.", { entero: false, min: 2, max: 100, obligatorio: true })
|
|
1950
|
+
}),
|
|
1951
|
+
labelDest: objeto("D\xF3nde cae el R\xD3TULO, aparte de la imagen, en % del slide. Sin esto, justo debajo de ella.", {
|
|
1952
|
+
x: num2("Borde izquierdo, en % del ancho.", { entero: false, obligatorio: true }),
|
|
1953
|
+
y: num2("Borde superior, en % del alto.", { entero: false, obligatorio: true }),
|
|
1954
|
+
w: num2("Ancho, en % del ancho del slide.", { entero: false, min: 2, max: 100, obligatorio: true })
|
|
1955
|
+
}),
|
|
1956
|
+
labelColor: color('Color del r\xF3tulo plantado. Sin esto, casi negro: sobre un fondo oscuro pon "#ffffff".')
|
|
1853
1957
|
}), { max: 30 })
|
|
1854
|
-
}
|
|
1958
|
+
},
|
|
1959
|
+
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
1960
|
},
|
|
1856
|
-
ejemplo: {
|
|
1961
|
+
ejemplo: {
|
|
1962
|
+
images: [
|
|
1963
|
+
{ src: "https://assets.boosterarena.com/u/ej/cat.webp", label: "cat" },
|
|
1964
|
+
{ 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" }
|
|
1965
|
+
]
|
|
1966
|
+
}
|
|
1857
1967
|
},
|
|
1858
1968
|
tombola: {
|
|
1859
1969
|
donde: "wcfg.ext",
|
|
@@ -2759,8 +2869,12 @@ var slideDe = (b) => {
|
|
|
2759
2869
|
return sc.slides[Math.min(b.slide, sc.slides.length - 1)];
|
|
2760
2870
|
};
|
|
2761
2871
|
function listaDestino(b, capa) {
|
|
2762
|
-
const s = slideDe(b);
|
|
2763
2872
|
const ref = txt(capa).trim();
|
|
2873
|
+
if (!ref && slideDe(b).base.some((e) => e.kind === "arena")) {
|
|
2874
|
+
nuevoSlide(b, "");
|
|
2875
|
+
b.bitacora.push("\u{1F4C4} Slide nuevo: el de la arena es solo para la arena");
|
|
2876
|
+
}
|
|
2877
|
+
const s = slideDe(b);
|
|
2764
2878
|
if (!ref) return { els: s.base };
|
|
2765
2879
|
const capaObj = capaPorNombre(b, ref) ?? crearCapa(b, ref);
|
|
2766
2880
|
b.capaDeTrabajo = capaObj.id;
|
|
@@ -2839,7 +2953,33 @@ function encajar(el) {
|
|
|
2839
2953
|
el.y = +Math.max(0, Math.min(100 - el.h, el.y)).toFixed(2);
|
|
2840
2954
|
return el;
|
|
2841
2955
|
}
|
|
2842
|
-
function
|
|
2956
|
+
function caminoPorNombre(suyas, nombre, estricto) {
|
|
2957
|
+
const q = nombre.trim().toLowerCase();
|
|
2958
|
+
if (!q) return suyas[0];
|
|
2959
|
+
return suyas.find((m) => m.name.trim().toLowerCase() === q) ?? (estricto ? void 0 : suyas.find((m) => m.name.toLowerCase().includes(q)) ?? suyas[0]);
|
|
2960
|
+
}
|
|
2961
|
+
function puntosPedidos(a) {
|
|
2962
|
+
const esPt = (p) => !!p && typeof p === "object" && Number.isFinite(p.x) && Number.isFinite(p.y);
|
|
2963
|
+
const pts = Array.isArray(a.puntos) ? a.puntos.filter(esPt) : [];
|
|
2964
|
+
if (esPt(a.hasta)) pts.push(a.hasta);
|
|
2965
|
+
return pts.map((p) => ({ x: Math.max(-40, Math.min(140, p.x)), y: Math.max(-40, Math.min(140, p.y)) }));
|
|
2966
|
+
}
|
|
2967
|
+
function salidaDelCamino(el, movido, a) {
|
|
2968
|
+
const paso = Math.round(num3(a.paso, 1));
|
|
2969
|
+
if (txt(a.evento) !== "clickStep" || paso < 2) return { x: 0, y: 0 };
|
|
2970
|
+
const previo = (el.triggers ?? []).find((t) => t.event.type === "clickStep" && t.event.step === paso - 1 && t.action.type === "playMotion" && t.action.targetId === movido.id);
|
|
2971
|
+
const idPrevio = previo?.action.type === "playMotion" ? previo.action.motionId : "";
|
|
2972
|
+
const cam = (movido.motions ?? []).find((m) => m.id === idPrevio);
|
|
2973
|
+
return cam?.path.length ? { ...cam.path[cam.path.length - 1] } : { x: 0, y: 0 };
|
|
2974
|
+
}
|
|
2975
|
+
function trazarCamino(a, destinos) {
|
|
2976
|
+
const r2 = (p) => ({ x: +p.x.toFixed(2), y: +p.y.toFixed(2) });
|
|
2977
|
+
if (destinos.length > 1) return [a, ...destinos].map(r2);
|
|
2978
|
+
const z = destinos[0];
|
|
2979
|
+
const n2 = Math.max(4, Math.min(40, Math.round(Math.hypot(z.x - a.x, z.y - a.y) / 2.5)));
|
|
2980
|
+
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 }));
|
|
2981
|
+
}
|
|
2982
|
+
function armarEvento(a, el, estricto) {
|
|
2843
2983
|
switch (txt(a.evento)) {
|
|
2844
2984
|
case "hover":
|
|
2845
2985
|
return { type: "hover" };
|
|
@@ -2852,9 +2992,7 @@ function armarEvento(a, el) {
|
|
|
2852
2992
|
case "clickStep":
|
|
2853
2993
|
return { type: "clickStep", step: Math.max(1, Math.round(num3(a.paso, 1))) };
|
|
2854
2994
|
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];
|
|
2995
|
+
const cam = caminoPorNombre(el.motions ?? [], txt(a.trayectoria), estricto);
|
|
2858
2996
|
return cam ? { type: "motionEnd", motionId: cam.id } : { type: "click" };
|
|
2859
2997
|
}
|
|
2860
2998
|
default:
|
|
@@ -2910,10 +3048,32 @@ function armarAccion(b, a, el) {
|
|
|
2910
3048
|
if (d && !quien) return { accion: null, fallo: `No encuentro el objeto "${d}" para moverlo.` };
|
|
2911
3049
|
const movido = quien ?? buscarEl(b, txt(a.objetivo));
|
|
2912
3050
|
if (!movido) return { accion: null, fallo: "No s\xE9 qu\xE9 objeto tiene que moverse." };
|
|
2913
|
-
const nombre = txt(a.trayectoria).trim()
|
|
3051
|
+
const nombre = txt(a.trayectoria).trim();
|
|
2914
3052
|
const suyas = movido.motions ?? [];
|
|
2915
|
-
const
|
|
3053
|
+
const pts = puntosPedidos(a);
|
|
3054
|
+
if (pts.length) {
|
|
3055
|
+
const c = { x: movido.x + movido.w / 2, y: movido.y + movido.h / 2 };
|
|
3056
|
+
const path = trazarCamino(salidaDelCamino(el, movido, a), pts.map((p) => ({ x: p.x - c.x, y: p.y - c.y })));
|
|
3057
|
+
const duration = Math.round(Math.max(200, Math.min(6e4, num3(a.duracion, 2e3))));
|
|
3058
|
+
const igual = nombre ? suyas.find((m) => m.name.trim().toLowerCase() === nombre.toLowerCase()) : void 0;
|
|
3059
|
+
if (igual) {
|
|
3060
|
+
igual.path = path;
|
|
3061
|
+
igual.duration = duration;
|
|
3062
|
+
} else {
|
|
3063
|
+
let n2 = suyas.length + 1;
|
|
3064
|
+
while (suyas.some((m) => m.name === `Trayectoria ${n2}`)) n2 += 1;
|
|
3065
|
+
movido.motions = [...suyas, { id: nid("mo"), name: nombre || `Trayectoria ${n2}`, path, duration }];
|
|
3066
|
+
}
|
|
3067
|
+
const cam2 = igual ?? movido.motions[movido.motions.length - 1];
|
|
3068
|
+
b.tocados.push(movido.id);
|
|
3069
|
+
b.bitacora.push(`\u219D ${describirEl(movido)}: camino \xAB${cam2.name}\xBB${igual ? " retrazado" : ""}`);
|
|
3070
|
+
return { accion: { type: "playMotion", targetId: movido.id, motionId: cam2.id } };
|
|
3071
|
+
}
|
|
3072
|
+
const cam = caminoPorNombre(suyas, nombre, b.estricto);
|
|
2916
3073
|
if (cam) return { accion: { type: "playMotion", targetId: movido.id, motionId: cam.id } };
|
|
3074
|
+
if (b.estricto) {
|
|
3075
|
+
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\`.` };
|
|
3076
|
+
}
|
|
2917
3077
|
const nuevo = { id: nid("mo"), name: txt(a.trayectoria) || "Trayectoria 1", path: [], duration: 2e3 };
|
|
2918
3078
|
movido.motions = [...suyas, nuevo];
|
|
2919
3079
|
b.tocados.push(movido.id);
|
|
@@ -3108,6 +3268,36 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3108
3268
|
b.bitacora.push(`\u{1F5BC}\uFE0F Imagen subida${dentroDe ? ` en la capa \xAB${dentroDe}\xBB` : ""}`);
|
|
3109
3269
|
return `Puesta la imagen (id ${el.id}).`;
|
|
3110
3270
|
}
|
|
3271
|
+
// Solo lo ofrece el MCP (`catalogo.ts`, `sinNavegador`): el Robot no pone animaciones.
|
|
3272
|
+
case "poner_lottie": {
|
|
3273
|
+
const url = txt(args.url).trim();
|
|
3274
|
+
const busca = txt(args.busca).trim();
|
|
3275
|
+
if (!url && !busca) return no(b, "Dime qu\xE9 animaci\xF3n buscar (`busca`) o p\xE1same la `url` de un Lottie.");
|
|
3276
|
+
if (url && !/^https?:\/\//i.test(url)) return no(b, "La `url` tiene que ser una direcci\xF3n https:// de un .json de Lottie.");
|
|
3277
|
+
const hallada = url ? null : buscarAnimacion(busca);
|
|
3278
|
+
if (!url && !hallada) {
|
|
3279
|
+
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\`.`);
|
|
3280
|
+
}
|
|
3281
|
+
if (hallada?.plan === "pro" && !b.premium) {
|
|
3282
|
+
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.`);
|
|
3283
|
+
}
|
|
3284
|
+
const w = pct(args.w, 20);
|
|
3285
|
+
const prop = hallada?.caja.w && hallada.caja.h ? hallada.caja.w / hallada.caja.h : 1;
|
|
3286
|
+
const el = encajar({
|
|
3287
|
+
id: nid("lottie"),
|
|
3288
|
+
kind: "lottie",
|
|
3289
|
+
x: pct(args.x, 40),
|
|
3290
|
+
y: pct(args.y, 30),
|
|
3291
|
+
w,
|
|
3292
|
+
h: +(w / prop * (1920 / 1080)).toFixed(1),
|
|
3293
|
+
src: hallada?.src ?? url
|
|
3294
|
+
});
|
|
3295
|
+
const { dentroDe } = plantar(b, el, args.ref, args.capa);
|
|
3296
|
+
b.bitacora.push(`\u2728 Animaci\xF3n ${hallada ? `\xAB${hallada.nombre}\xBB` : "propia"}${dentroDe ? ` en la capa \xAB${dentroDe}\xBB` : ""}`);
|
|
3297
|
+
if (!hallada) return `Animaci\xF3n puesta (id ${el.id}).`;
|
|
3298
|
+
const otros = hallada.parecidos.length ? ` Otras parecidas: ${hallada.parecidos.map((x) => `"${x}"`).join(", ")}.` : "";
|
|
3299
|
+
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}`;
|
|
3300
|
+
}
|
|
3111
3301
|
case "poner_audio": {
|
|
3112
3302
|
const url = txt(args.url).trim();
|
|
3113
3303
|
if (!/^https?:\/\//i.test(url)) {
|
|
@@ -3121,7 +3311,8 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3121
3311
|
w: 16,
|
|
3122
3312
|
h: 7,
|
|
3123
3313
|
src: url,
|
|
3124
|
-
name: txt(args.nombre) || "Audio"
|
|
3314
|
+
name: txt(args.nombre) || "Audio",
|
|
3315
|
+
vol: AUDIO_VOL_INICIAL
|
|
3125
3316
|
};
|
|
3126
3317
|
plantar(b, el, args.ref, void 0);
|
|
3127
3318
|
b.bitacora.push(`\u{1F50A} Audio \xAB${el.name}\xBB`);
|
|
@@ -3206,21 +3397,35 @@ El ref "${refBase}" es el rengl\xF3n grande; cada uno es tambi\xE9n "${refBase}_
|
|
|
3206
3397
|
case "poner_fondo": {
|
|
3207
3398
|
const color2 = txt(args.color).trim();
|
|
3208
3399
|
const busca = txt(args.busca).trim();
|
|
3400
|
+
const url = txt(args.url).trim();
|
|
3401
|
+
const refCapa = txt(args.capa).trim();
|
|
3402
|
+
const capa = refCapa ? capaPorNombre(b, refCapa) : void 0;
|
|
3403
|
+
if (refCapa && !capa) {
|
|
3404
|
+
return no(b, `No hay ninguna capa "${refCapa}" en este slide. Cr\xE9ala antes con \`nueva_capa\` (que tambi\xE9n acepta \`fondo\`).`);
|
|
3405
|
+
}
|
|
3406
|
+
const destino = capa ?? slideDe(b);
|
|
3407
|
+
const d\u00F3nde = capa ? ` de la capa \xAB${capa.name}\xBB` : "";
|
|
3408
|
+
if (url) {
|
|
3409
|
+
if (!/^https?:\/\//i.test(url)) return no(b, "La `url` tiene que ser una imagen ya SUBIDA (https://). S\xFAbela antes con `subir_imagen`.");
|
|
3410
|
+
destino.background = url;
|
|
3411
|
+
b.bitacora.push(`\u{1F3DE}\uFE0F Fondo propio${d\u00F3nde}`);
|
|
3412
|
+
return `Fondo${d\u00F3nde} puesto con esa imagen.`;
|
|
3413
|
+
}
|
|
3209
3414
|
if (busca) {
|
|
3210
3415
|
const f = buscarFondo(busca);
|
|
3211
3416
|
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}`);
|
|
3417
|
+
destino.background = f.src;
|
|
3418
|
+
b.bitacora.push(`\u{1F3DE}\uFE0F Fondo${d\u00F3nde}: ${f.nombre}`);
|
|
3214
3419
|
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.`;
|
|
3420
|
+
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
3421
|
}
|
|
3217
3422
|
if (!color2) return no(b, "Dime un color o una escena a buscar.");
|
|
3218
3423
|
if (!COLOR_DE_FONDO.test(color2)) {
|
|
3219
3424
|
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
3425
|
}
|
|
3221
|
-
|
|
3222
|
-
b.bitacora.push(`\u{1F3A8} Fondo ${color2}`);
|
|
3223
|
-
return
|
|
3426
|
+
destino.background = color2;
|
|
3427
|
+
b.bitacora.push(`\u{1F3A8} Fondo ${color2}${d\u00F3nde}`);
|
|
3428
|
+
return `Fondo de color${d\u00F3nde} puesto.`;
|
|
3224
3429
|
}
|
|
3225
3430
|
case "poner_booster": {
|
|
3226
3431
|
const tipo = txt(args.tipo);
|
|
@@ -3348,6 +3553,12 @@ ${fallosEnTexto(r.fallos)}
|
|
|
3348
3553
|
|
|
3349
3554
|
Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarlo.`);
|
|
3350
3555
|
}
|
|
3556
|
+
if (el.widget === "magicwand") {
|
|
3557
|
+
const imgs = cfg.valor.images ?? [];
|
|
3558
|
+
imgs.forEach((im) => {
|
|
3559
|
+
if (!im.id) im.id = nid("wand");
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3351
3562
|
el.wcfg = ficha.donde === "wcfg.ext" ? { ...el.wcfg, ext: cfg.valor } : { ...el.wcfg?.ext ? { ext: el.wcfg.ext } : {}, ...cfg.valor };
|
|
3352
3563
|
b.tocados.push(el.id);
|
|
3353
3564
|
b.bitacora.push(`\u270E Booster ${el.widget} configurado`);
|
|
@@ -3361,9 +3572,14 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3361
3572
|
}
|
|
3362
3573
|
case "nueva_capa": {
|
|
3363
3574
|
const r = txt(args.ref).trim();
|
|
3575
|
+
const fondo = txt(args.fondo).trim();
|
|
3576
|
+
if (fondo && !/^https?:\/\//i.test(fondo) && !COLOR_DE_FONDO.test(fondo)) {
|
|
3577
|
+
return no(b, `"${fondo}" no sirve de fondo: tiene que ser la URL https:// de una imagen ya subida o un color en hex.`);
|
|
3578
|
+
}
|
|
3364
3579
|
const capa = crearCapa(b, txt(args.nombre) || r);
|
|
3365
3580
|
if (r) b.aliasCapa.set(r, capa.id);
|
|
3366
|
-
|
|
3581
|
+
if (fondo) capa.background = fondo;
|
|
3582
|
+
return `Capa "${capa.name}" creada${fondo ? " con su fondo" : ""}. Para poner cosas dentro, p\xE1sale capa="${r || capa.name}".`;
|
|
3367
3583
|
}
|
|
3368
3584
|
case "nueva_escena": {
|
|
3369
3585
|
const nombre2 = txt(args.nombre) || `Escena ${b.doc.length + 1}`;
|
|
@@ -3389,9 +3605,22 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3389
3605
|
b.bitacora.push(`\u{1F9F9} Sin programaci\xF3n: ${describirEl(el)}`);
|
|
3390
3606
|
return `Le quit\xE9 ${n0} disparador(es).`;
|
|
3391
3607
|
}
|
|
3608
|
+
if (el.kind === "widget" || el.kind === "arena") {
|
|
3609
|
+
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).`);
|
|
3610
|
+
}
|
|
3392
3611
|
const { accion, fallo } = armarAccion(b, args, el);
|
|
3393
3612
|
if (!accion) return no(b, fallo ?? "No entend\xED qu\xE9 acci\xF3n quer\xEDas.");
|
|
3394
|
-
|
|
3613
|
+
if (b.estricto && txt(args.evento) === "trayectoriaTerminada" && !caminoPorNombre(el.motions ?? [], txt(args.trayectoria), true)) {
|
|
3614
|
+
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\`.`);
|
|
3615
|
+
}
|
|
3616
|
+
const trig = { id: nid("trig"), event: armarEvento(args, el, b.estricto), action: accion };
|
|
3617
|
+
if (trig.event.type === "motionEnd" && accion.type === "playMotion" && trig.event.motionId === accion.motionId) {
|
|
3618
|
+
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.");
|
|
3619
|
+
}
|
|
3620
|
+
const capaDelEl = slideDe(b).layers.find((l) => l.els.some((e) => e.id === el.id));
|
|
3621
|
+
if (capaDelEl && abreEnBucle(slideDe(b), capaDelEl.id, trig)) {
|
|
3622
|
+
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.`);
|
|
3623
|
+
}
|
|
3395
3624
|
el.triggers = args.primero === true ? [trig, ...el.triggers ?? []] : [...el.triggers ?? [], trig];
|
|
3396
3625
|
b.tocados.push(el.id);
|
|
3397
3626
|
b.bitacora.push(`\u26A1 ${describirEl(el)}: ${describirTrigger(trig, b)}`);
|
|
@@ -3411,12 +3640,14 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3411
3640
|
el.w = w;
|
|
3412
3641
|
}
|
|
3413
3642
|
if (num3(args.alto, 0) > 0) el.h = pct(args.alto, el.h);
|
|
3414
|
-
|
|
3415
|
-
if (typeof args.
|
|
3416
|
-
|
|
3643
|
+
const fuera = (v, lado) => Math.max(-lado - 30, Math.min(130, v));
|
|
3644
|
+
if (typeof args.x === "number") el.x = b.estricto ? +fuera(args.x, el.w).toFixed(2) : pct(args.x, el.x);
|
|
3645
|
+
if (typeof args.y === "number") el.y = b.estricto ? +fuera(args.y, el.h).toFixed(2) : pct(args.y, el.y);
|
|
3646
|
+
const afuera = b.estricto && (el.x < 0 || el.y < 0 || el.x + el.w > 100 || el.y + el.h > 100);
|
|
3647
|
+
if (!afuera) encajar(el);
|
|
3417
3648
|
b.tocados.push(el.id);
|
|
3418
3649
|
b.bitacora.push(`\u2194\uFE0F Recolocado: ${describirEl(el)}`);
|
|
3419
|
-
return "Movido.";
|
|
3650
|
+
return afuera ? "Movido. Qued\xF3 FUERA del slide: no se ve hasta que una trayectoria lo meta." : "Movido.";
|
|
3420
3651
|
}
|
|
3421
3652
|
case "cambiar_estilo": {
|
|
3422
3653
|
const el = buscarEl(b, txt(args.objetivo));
|
|
@@ -3438,6 +3669,7 @@ Pide \`describir("${el.widget}")\` para ver la forma exacta y vuelve a intentarl
|
|
|
3438
3669
|
if (txt(args.align)) el.align = txt(args.align);
|
|
3439
3670
|
if (typeof args.opacidad === "number") el.opacity = Math.max(0, Math.min(1, args.opacidad));
|
|
3440
3671
|
if (typeof args.giro === "number") el.rot = args.giro || void 0;
|
|
3672
|
+
if (typeof args.gris_al_tocar === "boolean") el.grayClick = args.gris_al_tocar || void 0;
|
|
3441
3673
|
b.tocados.push(el.id);
|
|
3442
3674
|
b.bitacora.push(`\u{1F3A8} Retocado: ${describirEl(el)}`);
|
|
3443
3675
|
return "Estilo cambiado.";
|
|
@@ -4026,6 +4258,7 @@ export {
|
|
|
4026
4258
|
FORMA_DEF,
|
|
4027
4259
|
buscarDibujo,
|
|
4028
4260
|
buscarFondo,
|
|
4261
|
+
cargarAnimaciones,
|
|
4029
4262
|
cargarBiblioteca,
|
|
4030
4263
|
construirArena,
|
|
4031
4264
|
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
|