Skip to content

feat(skins): catálogo de skins de Mosaicary en el gestor de skins - #55

Merged
staFF6773 merged 5 commits into
CubicLauncherDevs:developfrom
MopicMP:feat/catalogo-skins
Oct 29, 2026
Merged

staFF6773 merged 5 commits into
CubicLauncherDevs:developfrom
MopicMP:feat/catalogo-skins

Conversation

@MopicMP

@MopicMP MopicMP commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Descripcion

Agrega un catálogo de skins al gestor de skins de las cuentas premium (Microsoft), como
hablamos en Discord. Un botón Catálogo, junto a "Subir skin", abre una ventana con las
skins de Mosaicary.

  • Búsqueda por nombre, etiqueta o jugador. Todos los filtros son listas desplegables:
    modelo (Clásico / Delgado), orden (nuevas, populares, más descargadas), ojos vivos (skins
    cuyos ojos se mueven con el paquete de recursos Fresh Moves), categoría y etiquetas
    (TagPicker.svelte: lista con búsqueda y casillas, hasta 10, alguna o todas a la vez).
  • Tarjetas por fila a elegir (3 a 6), recordado en localStorage. Al bajar por la lista
    aparece un botón «Filtros» que vuelve arriba.
  • En la vista previa de la skin, botones con icono: girar, caminar (WalkingAnimation de
    skin3d) y, si la skin trae los ojos de Fresh Moves, moverlos como en el juego. Muestran su
    nombre al pasar el ratón, se recuerdan, y no aparecen si las animaciones 3D están
    desactivadas en Ajustes.
  • El centro de giro de la vista previa pasa de la cintura al pecho: al acercar con la rueda
    se ve la cara y el torso, no la cintura. Sin desplazar con el botón derecho, y con una
    distancia mínima para que la cámara no entre en la figura.
  • Cada tarjeta muestra la skin puesta, en pleno paso y girada, como en las tarjetas de
    mosaicary.com. Las skins con ojos vivos parpadean y miran a los lados como en la web,
    salvo con «Reducir animaciones de la interfaz» activado.
  • La skin elegida queda pendiente en la vista previa, igual que un archivo subido, y se
    guarda con el mismo botón (uploadSkinUrl, que ahora sube el PNG como archivo: ver
    «Arreglo» abajo).
  • Mirar una skin no es descargarla: la vista previa y la detección de brazos piden el PNG
    con ?for=view, así Mosaicary no lo cuenta como descarga ni contra su límite de descargas
    por dirección. Solo guardar la skin lo descarga.
  • El modelo de brazos lo da el catálogo; la detección por píxeles de feat(skins): detectar el modelo de la skin al subirla #53 sigue bloqueando
    Clásico en una skin delgada.
  • Cada skin enlaza a su página en mosaicary.com, y arriba de la ventana hay enlaces a
    mosaicary.com y al repositorio en GitHub.
  • De la respuesta solo se aceptan direcciones de https://mosaicary.com: el PNG que se sube
    a Mojang sale de ahí.
  • En el perfil, «Activa» queda junto al nombre, y Cerrar sesión y Eliminar al final de la
    fila del nombre, también con la ventana más estrecha; debajo quedan solo los datos de la
    cuenta.

Arreglo: cambiar la skin por URL

Desde octubre de 2026 Mojang rechaza el cambio de skin por URL (POST /minecraft/profile/skins con {"variant", "url"} en JSON): su cortafuegos responde 403 con
una página HTML («The request is blocked»), que el launcher mostraba entera en la
notificación. La subida del PNG como archivo (multipart/form-data) sigue funcionando; es lo
que usan Prism y Modrinth. Afectaba también al armario de skins (skin_closet.rs), que
vuelve a poner una skin guardada por su URL.

upload_skin_url_impl ahora descarga la imagen (http o https, hasta 1 MB, con firma de PNG)
y la sube con la misma función que la subida desde archivo (upload_skin_bytes_impl). El
armario y el catálogo funcionan sin más cambios.

Rendimiento

  • Ninguna petición hasta que se abre el catálogo; lo cargado se conserva al cerrarlo.
  • Las tarjetas son imágenes ya dibujadas por el servidor (render, 256×256, unos 12–15 KB
    cada una, en caché para siempre) con loading="lazy": nada de 3D por tarjeta. Los ojos
    vivos son un APNG (live) en el que cada fotograma trae solo los píxeles de los ojos que
    cambian: 2–12 KB más que la imagen fija, y el navegador no lo anima fuera de la vista.
  • Páginas de 24 que se cargan solas al acercarse al final de la lista
    (IntersectionObserver con una pantalla de margen). La búsqueda espera 350 ms mientras
    se escribe, cancela la petición anterior (AbortController) y descarta respuestas viejas
    por generación, como el buscador de modpacks de CurseForge.
  • Lo cargado de cada combinación de filtros se guarda 5 minutos: volver de Delgado a
    Clásico no repite la petición.
  • La lista va en bloques de cuatro filas, cada uno su propia rejilla. Solo los bloques a
    menos de una pantalla de la vista llevan sus tarjetas; los demás quedan vacíos con la
    altura que medían, y el navegador no maqueta ni pinta los que quedan fuera de la vista
    (content-visibility). La lista se guarda con $state.raw: se reemplaza entera, sin
    proxy por tarjeta ni copia al guardarla.
  • En la vista previa, caminar se pausa tras 5 s sin actividad, igual que el giro
    (IDLE_MS).

Medido en la ventana del launcher (WebView2, 800×600, portátil de 2 núcleos), desplazando
con la rueda, antes (una sola rejilla) y después (en bloques):

  • Desplazamiento con 800 tarjetas cargadas: de 47 fps con pausas de hasta 300 ms a 59–60 fps
    sin tareas largas.
  • Desplazamiento con 1600 tarjetas: 59 fps.
  • Tarjetas en la página: de todas las cargadas a 16–48.
  • Memoria privada de WebView2: de 514 MB con 1600 tarjetas y subiendo a unos 400 MB con
    3400, estable.
  • Abrir otra vez el catálogo con todo cargado: de una tarea de 337 ms a ninguna tarea larga.
  • Vista previa caminando y girando, sin tocarla: del 22 % de un núcleo al 1,2 %.

Con una sola rejilla, cada imagen que llegaba y cada página nueva volvían a maquetar todas
las tarjetas: hasta 124 ms por vez con 800. El botón «Filtros» sabe si la barra de filtros
quedó atrás por un IntersectionObserver, sin medir nada en cada evento de desplazamiento.

Archivos

  • src-tauri/src/commands/profile.rs: el cambio de skin por URL sube el PNG como archivo.
  • src/lib/api/mosaicarySkins.ts: dirección, lectura validada de la respuesta, petición y
    la dirección para mirar una skin (catalogViewUrl).
  • src/lib/components/profiles/SkinCatalogModal.svelte: la ventana, sobre ModalBase,
    llevada al body con use:portal como ModpackUpdateModal: dentro del perfil quedaba
    recortada bajo la barra lateral.
  • src/lib/components/profiles/TagPicker.svelte: la lista de etiquetas con búsqueda.
  • src/lib/components/profiles/SkinCapeManager.svelte: el botón y la skin pendiente desde
    una URL.
  • src-tauri/tauri.conf.json: https://mosaicary.com en connect-src (img-src ya
    permite https:).
  • i18n: userMenu.skinCape.catalog.* en es-ES y en-US.
  • src/lib/components/profiles/SkinPreview.svelte y Skin3dViewer.svelte: los
    interruptores «Girar», «Caminar» y «Mover los ojos» (rotate, walk, eyes), la pausa
    sin actividad también al caminar y el centro de giro en el pecho.
  • src/lib/utils/freshEyes.ts: lee la copia de los ojos que Fresh Moves usa en las esquinas
    de la skin y calcula sus movimientos.
  • src/lib/components/profiles/ProfileView.svelte: la fila del nombre con el estado y las
    acciones.
  • src/lib/icons/registry.ts, static/images/icons/brand/mosaicary.png,
    static/images/icons/ui/eye.svg: iconos.
  • tests/frontend/ui/mosaicarySkins.test.mjs (13 tests: la dirección con sus filtros y
    etiquetas, la de mirar una skin, la lectura y lo guardado por filtros) y
    freshEyes.test.mjs (5 tests).

La API está descrita en https://mosaicary.com/developers. Solo devuelve skins revisadas y
de 64 px, las que Mojang acepta.

Tipo de cambio

  • Bug fix (type:bug)
  • Nueva funcionalidad (type:feature)
  • Mejora (type:enhancement)
  • Refactor (type:refactor)
  • Documentacion (type:docs)
  • Tests (type:test)
  • Rendimiento (type:perf)
  • Mantenimiento / CI / deps (type:chore)
  • Breaking change (type:breaking)

Plataforma

  • Linux (platform:linux)
  • Windows (platform:windows)
  • macOS (platform:macos)

Checklist

  • El codigo compila sin errores (bun run build && cargo build)
  • Los tests pasan (bun run test:all) (ver «Información adicional»)
  • Se agregaron tests si es necesario
  • La documentacion fue actualizada si aplica
  • Se probo manualmente en las plataformas afectadas

Screenshots / logs (si aplica)

Perfil: la fila del nombre y los botones de la vista previa (girar, caminar, ojos).

Perfil

El catálogo.

Catálogo

Informacion adicional

La traducción al ruso de las nuevas claves la mando aparte al repositorio Translations.

Pasan bun run lint, bun run check, Prettier, cargo fmt --check y los tests nuevos
(mosaicarySkins, freshEyes, skinModel: 27). En esta máquina con Windows cargo test
no llega a ejecutar nada: el ejecutable de tests termina con STATUS_ENTRYPOINT_NOT_FOUND
al arrancar, antes del primer test; y unos pocos tests de frontend ajenos a este PR
fallan por la ruta local con espacios. El CI cubre ambos.

Con Rust 1.99, cargo clippy -- -D warnings se detiene en
crates/zellkern/src/resolvers/command.rs:142 (clippy::unnecessary_map_or), código que este
PR no toca; con esa regla permitida, el resto pasa sin avisos.

Desde octubre de 2026 Mojang rechaza el cambio de skin por URL (JSON con
`url`): su cortafuegos responde 403 con una página HTML, que se mostraba
entera en la notificación. La subida del PNG como archivo (multipart) sigue
funcionando, así que `upload_skin_url_impl` descarga la imagen (http o https,
hasta 1 MB, con firma de PNG) y la sube con la misma función que la subida
desde archivo.

Arregla también el armario de skins, que vuelve a poner una skin guardada
por su URL.
…kins

Un botón «Catálogo» junto a la subida de skins abre una ventana con las
skins de mosaicary.com para cuentas premium: búsqueda por nombre, etiqueta o
jugador, filtros de modelo, orden, ojos vivos, categoría y etiquetas, y
tarjetas por fila a elegir. La skin elegida queda pendiente en la vista
previa y se guarda con el mismo botón.

Las tarjetas son imágenes que dibuja el servidor, cargadas por páginas al
acercarse al final. La lista va en bloques de cuatro filas: solo los cercanos
a la vista llevan sus tarjetas y el navegador no maqueta ni pinta los demás.

En la vista previa, botones para girar, caminar y mover los ojos de Fresh
Moves; caminar también se pausa sin actividad, y el centro de giro pasa al
pecho.
… fila

«Activa» queda junto al nombre, y Cerrar sesión y Eliminar al final de la
fila del nombre, también con la ventana más estrecha; debajo quedan solo los
datos de la cuenta.
La vista previa y la detección de brazos piden el PNG con for=view; solo guardar la skin lo descarga sin él. Así Mosaicary no cuenta como descarga cada skin mirada ni la descuenta de su límite diario de descargas por dirección.

@staFF6773 staFF6773 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

El tamaño se comprueba después de leer toda la respuesta con bytes().await. Si el servidor devuelve un archivo enorme, podría consumir mucha RAM antes de rechazarlo. ¿Se podría leer por bloques y detener la descarga al superar el límite de 1 MiB?

download_skin leía toda la respuesta con bytes() antes de mirar el tamaño, así que un
servidor sin Content-Length (o con uno falso) podía hacerle reservar memoria sin límite.
Ahora cuenta lo que llega con chunk() y corta en cuanto pasa de 1 MiB, como ya hacen
avatar.rs y ely_profile.rs.
@MopicMP

MopicMP commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Entendido, recibido, procesado. Corregido en de2a26a:

  • download_skin ahora lee la respuesta por bloques con chunk() y corta en cuanto lo recibido pasa de 1 MiB, sin esperar al resto. Es el mismo patrón que ya usan avatar.rs y ely_profile.rs.
  • Si el servidor declara un Content-Length mayor que el límite, se rechaza antes de leer nada, como antes.
  • Tres tests con un servidor local en src-tauri/src/tests/commands/profile.rs: una respuesta sin Content-Length que nunca cierra la conexión (con bytes().await la descarga se quedaba esperando hasta el timeout; ahora corta al pasar el límite), un Content-Length declarado mayor que el límite y una skin de exactamente 1 MiB que llega entera. Comprobé que el primero falla con el código anterior.

@staFF6773
staFF6773 merged commit 02ae43f into CubicLauncherDevs:develop Oct 29, 2026
5 checks passed
@staFF6773 staFF6773 added type:feature Nueva funcionalidad type:enhancement Mejora de funcionalidad existente labels Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:enhancement Mejora de funcionalidad existente type:feature Nueva funcionalidad

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants