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
|
// Internal Helper to normalize card data for visuals
|
||||||
const normalizeCard = (c: any): DraftCard => ({
|
const normalizeCard = (c: any): DraftCard => {
|
||||||
...c,
|
const targetId = c.scryfallId || c.id;
|
||||||
finish: c.finish || 'nonfoil',
|
const setCode = c.setCode || c.set;
|
||||||
typeLine: c.typeLine || c.type_line,
|
const localImage = (targetId && setCode)
|
||||||
// Ensure image is top-level for components that expect it
|
? `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||||
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
: null;
|
||||||
});
|
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
finish: c.finish || 'nonfoil',
|
||||||
|
typeLine: c.typeLine || c.type_line,
|
||||||
|
// Ensure image is top-level for components that expect it
|
||||||
|
image: localImage || c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const LAND_URL_MAP: Record<string, string> = {
|
const LAND_URL_MAP: Record<string, string> = {
|
||||||
Plains: "https://cards.scryfall.io/normal/front/d/1/d1ea1858-ad25-4d13-9860-25c898b02c42.jpg",
|
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 = () => {
|
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 ---
|
// --- DnD Handlers ---
|
||||||
@@ -526,13 +548,22 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
const landSourceCards = useMemo(() => {
|
const landSourceCards = useMemo(() => {
|
||||||
// If we have specific lands from cube, use them.
|
// If we have specific lands from cube, use them.
|
||||||
if (availableBasicLands && availableBasicLands.length > 0) {
|
if (availableBasicLands && availableBasicLands.length > 0) {
|
||||||
return availableBasicLands.map(land => ({
|
return availableBasicLands.map(land => {
|
||||||
...land,
|
const targetId = land.scryfallId || land.id;
|
||||||
id: `land-source-${land.name}`, // stable ID for list
|
const setCode = land.setCode || land.set;
|
||||||
isLandSource: true,
|
|
||||||
// Ensure image is set for display
|
const localImage = (targetId && setCode)
|
||||||
image: land.image || land.image_uris?.normal
|
? `/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: localImage || land.image || land.image_uris?.normal
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise generate generic basics
|
// Otherwise generate generic basics
|
||||||
|
|||||||
@@ -9,11 +9,21 @@ import { DndContext, DragOverlay, useSensor, useSensors, MouseSensor, TouchSenso
|
|||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
|
||||||
// Helper to normalize card data for visuals
|
// Helper to normalize card data for visuals
|
||||||
const normalizeCard = (c: any) => ({
|
// Helper to normalize card data for visuals
|
||||||
...c,
|
const normalizeCard = (c: any) => {
|
||||||
finish: c.finish || 'nonfoil',
|
const targetId = c.scryfallId || c.id;
|
||||||
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
const setCode = c.setCode || c.set;
|
||||||
});
|
|
||||||
|
const localImage = (targetId && setCode)
|
||||||
|
? `/cards/images/${setCode}/full/${targetId}.jpg`
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
finish: c.finish || 'nonfoil',
|
||||||
|
image: localImage || c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Droppable Wrapper for Pool
|
// Droppable Wrapper for Pool
|
||||||
const PoolDroppable = ({ children, className, style }: any) => {
|
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, () => {
|
const { onTouchStart, onTouchEnd, onTouchMove, onClick } = useCardTouch(setHoveredCard, () => {
|
||||||
if (window.matchMedia('(pointer: coarse)').matches) return;
|
if (window.matchMedia('(pointer: coarse)').matches) return;
|
||||||
}, card);
|
}, card);
|
||||||
@@ -649,7 +660,7 @@ const PoolCardItem = ({ card, setHoveredCard, vertical = false }: any) => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal}
|
src={card.image}
|
||||||
alt={card.name}
|
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`}
|
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}
|
draggable={false}
|
||||||
|
|||||||
@@ -29,9 +29,17 @@ export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart,
|
|||||||
return () => unregisterCard(card.instanceId);
|
return () => unregisterCard(card.instanceId);
|
||||||
}, [card.instanceId]);
|
}, [card.instanceId]);
|
||||||
|
|
||||||
|
// Robustly resolve Art Crop
|
||||||
// Robustly resolve Art Crop
|
// Robustly resolve Art Crop
|
||||||
let imageSrc = card.imageUrl;
|
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) {
|
if (card.definition.image_uris?.art_crop) {
|
||||||
imageSrc = card.definition.image_uris.art_crop;
|
imageSrc = card.definition.image_uris.art_crop;
|
||||||
} else if (card.definition.card_faces?.[0]?.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 && (
|
{hoveredCard && (
|
||||||
<img
|
<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}
|
alt={hoveredCard.name}
|
||||||
className="w-full h-full object-cover rounded-xl shadow-2xl shadow-black ring-1 ring-white/10"
|
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
|
<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}
|
alt={card.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user