Skip to content

Diálogo Editar carta

Pablo Martínez López edited this page Oct 20, 2026 · 1 revision

Diálogo Editar carta

English · Español

«Editar carta» es el modal para retocar la imagen ya existente de una carta: rotación, encuadre con zoom/desplazamiento y once ajustes de color y detalle. Aplica el resultado a esa carta, a sus copias o a todas las cartas, y muestra una vista previa en vivo renderizada por el mismo código de Rust que el PDF. No es el diseñador de cartas; véase Relación con la vista del diseñador más abajo.

Diálogo Editar carta

Ficheros

Fichero Función
src/features/card-editor/card-editor-dialog.tsx CardEditorDialog (montado una vez en src/App.tsx) y su Contenido interno
src/features/card-editor/propagar.ts Alcance, copiasDe, aplicarEdicion: funciones puras que deciden qué cartas reciben la edición
src/features/card-editor/__tests__/propagar.test.ts Tests de las reglas de alcance
src/lib/imagen-viva.ts useAlRitmo, useRafaga, useImagenViva (actualización de imágenes en tiempo real, compartida con la Interfaz del diseñador)
src/lib/image-url.ts parametros, urlMiniatura, EDICION_NEUTRA

Cómo se abre

  • Doble clic en una casilla de carta de la vista previa (src/features/preview/slot-view.tsx, solo si la carta tiene imagen: carta?.front).
  • Opción «Editar carta» del menú contextual (src/features/preview/slot-menu.tsx; desactivada con varias cartas seleccionadas o cuando la carta no tiene imagen).

Ambas llaman a useUi.getState().abrir({ tipo: "editar", id }); CardEditorDialog lee useUi.dialogo, busca la carta con cartaPorId(id) (src/features/preview/acciones-carta.ts) y solo se abre si existe carta.front.

Qué se edita: CardEdits

CardEdits (src-tauri/crates/proxy-core/src/model.rs, binding en src/bindings/CardEdits) se guarda en cada DeckCard.edits:

Campo Significado Interfaz
rotation r0, r90, r180, r270 (sin pérdida) Dos botones: girar a la izquierda/derecha, muestra 0/90/180/270
crop zoom (>= 1), offsetX, offsetY en [-1, 1] dentro de la holgura que deja el zoom (0 = centrado) Deslizador de zoom 1x-2x, deslizadores de encuadre horizontal/vertical, arrastre sobre la imagen cuando el zoom es > 1
adjust Adjustments: los once retoques Deslizadores

Los once retoques, en el orden de la lista RETOQUES (el orden de la herramienta web original que esta app clona): brillo, exposición, contraste, saturación, calidez, intensidad (todos de -100 a 100); nitidez, claridad, reducción de ruido, viñeta, desvanecido (todos de 0 a 100). 0 = sin efecto, y EDICION_NEUTRA es el valor con todo a cero. Rust los aplica en un orden fijo (reducción de ruido, exposición, brillo, contraste, calidez, saturación, intensidad, claridad, nitidez, desvanecido, viñeta) con radios en milímetros, de modo que una miniatura de 488 px y un PDF a 600 ppp se vean igual (src-tauri/crates/proxy-core/src/imaging/adjust.rs).

Encuadre en Rust (imaging/frame.rs): rotar, luego caja de recorte a partir del zoom y el desplazamiento, luego remuestreo Lanczos3 al tamaño exacto de la casilla. La imagen se estira a la forma de la carta; solo si su proporción se aleja más de un 15 % (MAX_ESTIRADO) se recorta para cubrir. Véase Imagen y PDF.

Mecánica de la vista previa en vivo

flowchart LR
  S["deslizadores / botones / arrastre"] --> B["borrador: CardEdits (useState)"]
  B -->|"useAlRitmo 60 ms"| P["pintado"]
  P -->|"useRafaga"| W{"¿ráfaga?"}
  W -->|sí| U1["urlMiniatura 488 px"]
  W -->|no| U2["urlMiniatura 745 px"]
  U1 --> V["useImagenViva"]
  U2 --> V
  V --> I["img (la última cargada)"]
  B -->|"filtroAproximado"| F["el filtro CSS salva el intervalo"]
  F --> I
Loading
  • borrador guarda las ediciones que se están editando. pintado = useAlRitmo(borrador, 60) deja pasar el valor como máximo cada 60 ms (el último siempre llega), de modo que a Rust se le pide una imagen nueva a la velocidad del deslizador pero nunca se le inunda.
  • La URL de la imagen es urlMiniatura(front.blob, parametros(ajustes, pintado, false, 0), ancho) → pbimg://…/r/<blob>/<base64url params>/<width> (src-tauri/src/protocol.rs). parametros empaqueta en PreviewParams el edgeDarkening del mazo, el modo de sangrado, el relleno de esquinas, el tamaño de carta y las ediciones. Rust la renderiza con AppState::miniatura (src-tauri/src/render.rs), el mismo imaging::pipeline::render que usa el PDF, y cachea el JPEG en app_cache_dir/renders/<RenderSpec key>.jpg.
  • useRafaga(pintado) es verdadero mientras las ediciones cambian más a menudo que cada 250 ms; entonces la petición es de 488 px de ancho, y si no, de 745 px.
  • useImagenViva(url) mantiene en pantalla la imagen anterior hasta que ha cargado la nueva (sin parpadeos ni huecos en blanco).
  • Entre el movimiento de un deslizador y la llegada de la imagen nueva, filtroAproximado(borrador.adjust, enPantalla.adjust) aplica un filtro CSS brightness/contrast/saturate calculado a partir de la diferencia entre el borrador actual y los ajustes con los que se renderizó la imagen mostrada. Un mapa porUrl (se vacía al pasar de 64 entradas) recuerda qué ediciones usó cada URL pedida, de manera que enPantalla es exacto aunque aún se muestre una imagen más antigua. Solo se aproximan el brillo, la exposición, el contraste, la saturación y la intensidad; la rotación, el encuadre, la nitidez y el resto simplemente esperan a Rust.
  • Mientras una imagen carga aparece un indicador de progreso (cargando).

Arrastrar la imagen

alBajar solo inicia un arrastre cuando borrador.crop.zoom > 1. alMover convierte el desplazamiento del puntero (como fracción del marco) en incrementos de desplazamiento escalados por la holgura 1 - 1/zoom, limita ambos desplazamientos a [-1, 1] y los escribe en el borrador. El cursor muestra grab solo cuando el zoom es > 1.

Aviso del pie

Para las cartas de Scryfall y las fichas (source.kind === "scryfall" | "token"), un zoom superior a 1.05 muestra un aviso ámbar: «Con este zoom se recorta el pie de la carta (artista y copyright).». Es solo un aviso.

Aplicar: reglas de alcance (propagar.ts)

Botón Alcance Efecto (aplicarEdicion)
«Aplicar» carta Solo esta carta recibe la edición completa. Desactivado cuando nada ha cambiado (JSON.stringify(borrador) === JSON.stringify(carta.edits)).
«Aplicar también a sus copias (n)» (solo si n > 0) copias Esta carta más cada carta con el mismo hash de imagen frontal (front.blob) recibe la edición completa: rotación, encuadre y retoques, porque la imagen es idéntica.
«Aplicar a todas las cartas» todas Esta carta recibe la edición completa; cualquier otra carta con imagen recibe solo los ajustes de color (adjust) fusionados en sus propios edits (se conservan su rotación y su encuadre: dependen de cada imagen).
  • copiasDe(cartas, carta) = las otras cartas cuyo front.blob es igual al de esta carta; una carta sin imagen no tiene copias aunque haya otras casillas en blanco.
  • Las cartas que no se tocan conservan la misma identidad de objeto (los tests lo comprueban), lo que mantiene barato a React y al historial.
  • Las tres pasan por una sola llamada a useDeck.getState().setCartas(...), así que un solo paso de deshacer revierte toda la operación (véase Stores de estado). Después se muestra un anuncio («Carta retocada», «Carta y sus N copias retocadas» o «Retoques aplicados a todas las cartas») y el diálogo se cierra.
  • «Restablecer todo» devuelve el borrador a EDICION_NEUTRA (desactivado cuando ya es neutro); no cierra ni aplica.
  • Cerrar el diálogo con la X descarta el borrador. El diálogo no pasa cambiosSinGuardar, así que se aplica el comportamiento por defecto de src/components/ui/dialog.tsx (que sigue los controles tocados). Deducido: el comportamiento exacto de confirmar al cerrar viene de ese componente; léelo antes de fiarte de él.

Relación con la vista del diseñador

Dos mecanismos distintos editan el aspecto de una carta:

Editar carta (esta página) Diseñador («Diseñar»)
Qué cambia Los píxeles de una imagen existente: DeckCard.edits Un CardDesign estructurado que renderiza el motor
Datos CardEdits (rotación, recorte, ajustes) DeckCard.design (Interfaz del diseñador, Motor del diseñador)
PDF Lo aplica imaging::pipeline::render a partir del blob original Se vuelve a pintar desde el diseño al tamaño de impresión (render_desde_imagen tras diseno::pintar)
Copias Copias con la misma imagen mediante propagar.ts Grupo de la misma carta de Scryfall mediante grupos.ts

Puntos de interacción, verificados en el código:

  • Una carta diseñada es una carta del mazo cuyo front es una miniatura JPEG con ImageOrigin::Designed. «Editar carta» se puede abrir para ella (tiene front).
  • Guardar un diseño restablece edits a su valor por defecto (diseno::vista::aplicar pone nueva.edits = CardEdits::default()), y el camino de cartas diseñadas del exportador ignora edits y el oscurecimiento de bordes (encargo en src-tauri/src/commands/export.rs pone spec.edits = CardEdits::default(), spec.edge = None; render_desde_imagen se salta las esquinas, el encuadre y los retoques; edge_for devuelve None para Designed).
  • La vista previa en pantalla de una carta diseñada sigue pasando por /r/… con los edits de la carta, así que un retoque hecho con «Editar carta» (o con «Aplicar a todas las cartas», que toca todas las cartas con imagen, incluidas las diseñadas) cambia la miniatura en pantalla pero no el PDF impreso. Deducido de esos caminos de código; la interfaz no avisa de ello. Para cambiar el aspecto impreso de una carta diseñada, usa el diseñador.
  • «Recortar de una carta…» del diseñador (src/features/disenar/recortar-dialog.tsx) lista solo las cartas con imagen que no están diseñadas y usa su blob original, no sus ediciones.
  • Ediciones de la imagen original frente a la instantánea «before»: cuando se crea un diseño, design.before.edits conserva las ediciones de la carta para que «Quitar diseño» las restaure.

Modos de fallo y casos límite

  • La carta no tiene imagen: la opción del menú está desactivada y el diálogo no se abre (open={!!carta?.front}).
  • La carta desaparece mientras el diálogo está abierto (borrada, deshacer): cartaPorId devuelve undefined y el diálogo se desmonta.
  • Una imagen de vista previa que no carga no se muestra y la última imagen buena sigue visible (CargaUltima se salta las cargas fallidas); el diálogo no muestra ningún aviso de error.
  • El rango de zoom aquí es 1x-2x, más estrecho que el zoom de ilustración del diseñador (1x-3x); el propio modelo admite cualquier zoom >= 1.

Tests

src/features/card-editor/__tests__/propagar.test.ts: las copias son cartas con la misma imagen; una carta sin imagen no tiene copias; «solo esta carta» deja idénticos los demás objetos; «copias» copia todo solo a las cartas con la misma imagen; «todas» da la edición completa a esta carta y solo el color al resto; se conservan el orden y el número. No hay tests de componente para el diálogo (el comportamiento en vivo lo cubren src/lib/__tests__/imagen-viva.test.ts para las utilidades de carga y la ejecución de la app). Rust: los tests de renderizado y de ajustes de la tubería están en src-tauri/crates/proxy-core/src/imaging/ (Tests y CI).

Por dónde empezar para ampliarlo

  • Retoque nuevo: añade un campo a Adjustments en model.rs, impleméntalo en imaging/adjust.rs (ten en cuenta el orden fijo y los radios en mm), regenera los bindings con pnpm bindings, añádelo a RETOQUES en card-editor-dialog.tsx con el texto fuente en español y su entrada en inglés en src/i18n/en/ (lo comprueba pnpm idiomas), y decide si filtroAproximado debe aproximarlo.
  • Para cambiar a qué cartas llega una edición: edita solo propagar.ts y su test.

Véase también

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

Layout, images, PDF · Maquetación, imagen y PDF

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally