feat: Prioritize local card image paths over external URLs for all card displays and interactions.
This commit is contained in:
@@ -16,13 +16,21 @@ interface DeckBuilderViewProps {
|
||||
}
|
||||
|
||||
// Internal Helper to normalize card data for visuals
|
||||
const normalizeCard = (c: any): DraftCard => ({
|
||||
const normalizeCard = (c: any): DraftCard => {
|
||||
const targetId = c.scryfallId || c.id;
|
||||
const setCode = c.setCode || c.set;
|
||||
const localImage = (targetId && setCode)
|
||||
? `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||
: null;
|
||||
|
||||
return {
|
||||
...c,
|
||||
finish: c.finish || 'nonfoil',
|
||||
typeLine: c.typeLine || c.type_line,
|
||||
// Ensure image is top-level for components that expect it
|
||||
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||
});
|
||||
image: localImage || c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||
};
|
||||
};
|
||||
|
||||
const LAND_URL_MAP: Record<string, string> = {
|
||||
Plains: "https://cards.scryfall.io/normal/front/d/1/d1ea1858-ad25-4d13-9860-25c898b02c42.jpg",
|
||||
@@ -416,7 +424,21 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
||||
};
|
||||
|
||||
const submitDeck = () => {
|
||||
socketService.socket.emit('player_ready', { deck });
|
||||
// Normalize deck images to use local cache before submitting
|
||||
const preparedDeck = deck.map(c => {
|
||||
const targetId = c.scryfallId; // DraftCard uses scryfallId for the real ID
|
||||
const setCode = c.setCode || c.set;
|
||||
|
||||
if (targetId && setCode) {
|
||||
return {
|
||||
...c,
|
||||
image: `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
|
||||
socketService.socket.emit('player_ready', { deck: preparedDeck });
|
||||
};
|
||||
|
||||
// --- DnD Handlers ---
|
||||
@@ -526,13 +548,22 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
||||
const landSourceCards = useMemo(() => {
|
||||
// If we have specific lands from cube, use them.
|
||||
if (availableBasicLands && availableBasicLands.length > 0) {
|
||||
return availableBasicLands.map(land => ({
|
||||
return availableBasicLands.map(land => {
|
||||
const targetId = land.scryfallId || land.id;
|
||||
const setCode = land.setCode || land.set;
|
||||
|
||||
const localImage = (targetId && setCode)
|
||||
? `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||
: null;
|
||||
|
||||
return {
|
||||
...land,
|
||||
id: `land-source-${land.name}`, // stable ID for list
|
||||
isLandSource: true,
|
||||
// Ensure image is set for display
|
||||
image: land.image || land.image_uris?.normal
|
||||
}));
|
||||
image: localImage || land.image || land.image_uris?.normal
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise generate generic basics
|
||||
|
||||
@@ -9,11 +9,21 @@ import { DndContext, DragOverlay, useSensor, useSensors, MouseSensor, TouchSenso
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
// Helper to normalize card data for visuals
|
||||
const normalizeCard = (c: any) => ({
|
||||
// Helper to normalize card data for visuals
|
||||
const normalizeCard = (c: any) => {
|
||||
const targetId = c.scryfallId || c.id;
|
||||
const setCode = c.setCode || c.set;
|
||||
|
||||
const localImage = (targetId && setCode)
|
||||
? `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||
: null;
|
||||
|
||||
return {
|
||||
...c,
|
||||
finish: c.finish || 'nonfoil',
|
||||
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||
});
|
||||
image: localImage || c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||
};
|
||||
};
|
||||
|
||||
// Droppable Wrapper for Pool
|
||||
const PoolDroppable = ({ children, className, style }: any) => {
|
||||
@@ -633,7 +643,8 @@ const DraftCardItem = ({ rawCard, handlePick, setHoveredCard }: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PoolCardItem = ({ card, setHoveredCard, vertical = false }: any) => {
|
||||
const PoolCardItem = ({ card: rawCard, setHoveredCard, vertical = false }: any) => {
|
||||
const card = normalizeCard(rawCard);
|
||||
const { onTouchStart, onTouchEnd, onTouchMove, onClick } = useCardTouch(setHoveredCard, () => {
|
||||
if (window.matchMedia('(pointer: coarse)').matches) return;
|
||||
}, card);
|
||||
@@ -649,7 +660,7 @@ const PoolCardItem = ({ card, setHoveredCard, vertical = false }: any) => {
|
||||
onClick={onClick}
|
||||
>
|
||||
<img
|
||||
src={card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal}
|
||||
src={card.image}
|
||||
alt={card.name}
|
||||
className={`${vertical ? 'w-full h-full object-cover' : 'h-full w-auto object-contain'} rounded-lg shadow-lg border border-slate-700/50 group-hover:border-emerald-500/50 group-hover:shadow-emerald-500/20 transition-all`}
|
||||
draggable={false}
|
||||
|
||||
@@ -29,9 +29,17 @@ export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart,
|
||||
return () => unregisterCard(card.instanceId);
|
||||
}, [card.instanceId]);
|
||||
|
||||
// Robustly resolve Art Crop
|
||||
// Robustly resolve Art Crop
|
||||
let imageSrc = card.imageUrl;
|
||||
if (viewMode === 'cutout' && card.definition) {
|
||||
|
||||
if (card.definition && card.definition.set && card.definition.id) {
|
||||
if (viewMode === 'cutout') {
|
||||
imageSrc = `/cards/images/${card.definition.set}/crop/${card.definition.id}.jpg`;
|
||||
} else {
|
||||
imageSrc = `/cards/images/${card.definition.set}/full/${card.definition.id}.jpg`;
|
||||
}
|
||||
} else if (viewMode === 'cutout' && card.definition) {
|
||||
if (card.definition.image_uris?.art_crop) {
|
||||
imageSrc = card.definition.image_uris.art_crop;
|
||||
} else if (card.definition.card_faces?.[0]?.image_uris?.art_crop) {
|
||||
|
||||
@@ -501,7 +501,12 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
>
|
||||
{hoveredCard && (
|
||||
<img
|
||||
src={hoveredCard.imageUrl}
|
||||
src={(() => {
|
||||
if (hoveredCard.definition?.set && hoveredCard.definition?.id) {
|
||||
return `/cards/images/${hoveredCard.definition.set}/full/${hoveredCard.definition.id}.jpg`;
|
||||
}
|
||||
return hoveredCard.imageUrl;
|
||||
})()}
|
||||
alt={hoveredCard.name}
|
||||
className="w-full h-full object-cover rounded-xl shadow-2xl shadow-black ring-1 ring-white/10"
|
||||
/>
|
||||
|
||||
@@ -52,7 +52,12 @@ export const ZoneOverlay: React.FC<ZoneOverlayProps> = ({ zoneName, cards, onClo
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={card.imageUrl || 'https://via.placeholder.com/250x350'}
|
||||
src={(() => {
|
||||
if (card.definition?.set && card.definition?.id) {
|
||||
return `/cards/images/${card.definition.set}/full/${card.definition.id}.jpg`;
|
||||
}
|
||||
return card.imageUrl || 'https://via.placeholder.com/250x350';
|
||||
})()}
|
||||
alt={card.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user