feat: Consolidate card and land dragging into a single wrapper and manage basic lands directly in the deck.
This commit is contained in:
@@ -24,17 +24,29 @@ const normalizeCard = (c: any): DraftCard => ({
|
|||||||
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
image: c.image || c.image_uris?.normal || c.card_faces?.[0]?.image_uris?.normal
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draggable Wrapper for Cards
|
const LAND_URL_MAP: Record<string, string> = {
|
||||||
const DraggableCardWrapper = ({ children, card, source, disabled }: any) => {
|
Plains: "https://cards.scryfall.io/normal/front/d/1/d1ea1858-ad25-4d13-9860-25c898b02c42.jpg",
|
||||||
|
Island: "https://cards.scryfall.io/normal/front/2/f/2f3069b3-c15c-4399-ab99-c88c0379435b.jpg",
|
||||||
|
Swamp: "https://cards.scryfall.io/normal/front/1/7/17d0571f-df6c-4b53-912f-9cb4d5a9d224.jpg",
|
||||||
|
Mountain: "https://cards.scryfall.io/normal/front/f/5/f5383569-42b7-4c07-b67f-2736bc88bd37.jpg",
|
||||||
|
Forest: "https://cards.scryfall.io/normal/front/1/f/1fa688da-901d-4876-be11-884d6b677271.jpg"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Universal Wrapper handling both Pool Cards (Move) and Land Sources (Copy/Ghost)
|
||||||
|
const UniversalCardWrapper = ({ children, card, source, disabled }: any) => {
|
||||||
|
const isLand = card.isLandSource;
|
||||||
|
const dndId = isLand ? `land-source-${card.name}` : card.id;
|
||||||
|
const dndData = isLand ? { card, type: 'land' } : { card, source };
|
||||||
|
|
||||||
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
||||||
id: card.id,
|
id: dndId,
|
||||||
data: { card, source },
|
data: dndData,
|
||||||
disabled
|
disabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const style = transform ? {
|
const style = transform ? {
|
||||||
transform: CSS.Translate.toString(transform),
|
transform: CSS.Translate.toString(transform),
|
||||||
opacity: isDragging ? 0 : 1,
|
opacity: isDragging ? (isLand ? 0.5 : 0) : 1,
|
||||||
zIndex: isDragging ? 999 : undefined
|
zIndex: isDragging ? 999 : undefined
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
@@ -45,28 +57,6 @@ const DraggableCardWrapper = ({ children, card, source, disabled }: any) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draggable Wrapper for Lands (Special case: ID is generic until dropped)
|
|
||||||
const DraggableLandWrapper = ({ children, land }: any) => {
|
|
||||||
const id = `land-source-${land.name}`;
|
|
||||||
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
|
||||||
id: id,
|
|
||||||
data: { card: land, type: 'land' }
|
|
||||||
});
|
|
||||||
|
|
||||||
// For lands, we want to copy, so don't hide original
|
|
||||||
const style = transform ? {
|
|
||||||
transform: CSS.Translate.toString(transform),
|
|
||||||
zIndex: isDragging ? 999 : undefined,
|
|
||||||
opacity: isDragging ? 0.5 : 1 // Show ghost
|
|
||||||
} : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={setNodeRef} style={style} {...listeners} {...attributes} className="relative z-0">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Droppable Zone
|
// Droppable Zone
|
||||||
const DroppableZone = ({ id, children, className }: any) => {
|
const DroppableZone = ({ id, children, className }: any) => {
|
||||||
const { setNodeRef, isOver } = useDroppable({ id });
|
const { setNodeRef, isOver } = useDroppable({ id });
|
||||||
@@ -147,13 +137,20 @@ const CardsDisplay: React.FC<{
|
|||||||
|
|
||||||
// Use CSS var for grid
|
// Use CSS var for grid
|
||||||
if (viewMode === 'list') {
|
if (viewMode === 'list') {
|
||||||
const sorted = [...cards].sort((a, b) => (a.cmc || 0) - (b.cmc || 0));
|
const sorted = [...cards].sort((a, b) => {
|
||||||
|
// Lands always first
|
||||||
|
if (a.isLandSource && !b.isLandSource) return -1;
|
||||||
|
if (!a.isLandSource && b.isLandSource) return 1;
|
||||||
|
// Then CMC
|
||||||
|
return (a.cmc || 0) - (b.cmc || 0);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 w-full">
|
<div className="flex flex-col gap-1 w-full">
|
||||||
{sorted.map(c => (
|
{sorted.map(c => (
|
||||||
<DraggableCardWrapper key={c.id} card={c} source={source}>
|
<UniversalCardWrapper key={c.id || c.name} card={c} source={source}>
|
||||||
<ListItem card={normalizeCard(c)} onClick={() => onCardClick(c)} onHover={onHover} />
|
<ListItem card={normalizeCard(c)} onClick={() => onCardClick(c)} onHover={onHover} />
|
||||||
</DraggableCardWrapper>
|
</UniversalCardWrapper>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -161,9 +158,7 @@ const CardsDisplay: React.FC<{
|
|||||||
|
|
||||||
if (viewMode === 'stack') {
|
if (viewMode === 'stack') {
|
||||||
return (
|
return (
|
||||||
<div className="h-full min-w-full w-max"> {/* Allow native scrolling from parent */}
|
<div className="h-full min-w-full w-max">
|
||||||
{/* StackView doesn't support DnD yet, so we disable it or handle it differently.
|
|
||||||
For now, drag from StackView is not implemented, falling back to Click. */}
|
|
||||||
<StackView
|
<StackView
|
||||||
cards={cards.map(normalizeCard)}
|
cards={cards.map(normalizeCard)}
|
||||||
cardWidth={cardWidth}
|
cardWidth={cardWidth}
|
||||||
@@ -178,9 +173,9 @@ const CardsDisplay: React.FC<{
|
|||||||
disableHoverPreview={true}
|
disableHoverPreview={true}
|
||||||
groupBy={groupBy}
|
groupBy={groupBy}
|
||||||
renderWrapper={(card, children) => (
|
renderWrapper={(card, children) => (
|
||||||
<DraggableCardWrapper key={card.id} card={card} source={source}>
|
<UniversalCardWrapper key={card.id || card.name} card={card} source={source}>
|
||||||
{children}
|
{children}
|
||||||
</DraggableCardWrapper>
|
</UniversalCardWrapper>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,7 +197,7 @@ const CardsDisplay: React.FC<{
|
|||||||
const isFoil = card.finish === 'foil';
|
const isFoil = card.finish === 'foil';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableCardWrapper key={card.id} card={card} source={source}>
|
<UniversalCardWrapper key={card.id || card.name} card={card} source={source}>
|
||||||
<DeckCardItem
|
<DeckCardItem
|
||||||
card={card}
|
card={card}
|
||||||
useArtCrop={useArtCrop}
|
useArtCrop={useArtCrop}
|
||||||
@@ -210,7 +205,7 @@ const CardsDisplay: React.FC<{
|
|||||||
onCardClick={onCardClick}
|
onCardClick={onCardClick}
|
||||||
onHover={onHover}
|
onHover={onHover}
|
||||||
/>
|
/>
|
||||||
</DraggableCardWrapper>
|
</UniversalCardWrapper>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -301,7 +296,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
|
|
||||||
const [pool, setPool] = useState<any[]>(initialPool);
|
const [pool, setPool] = useState<any[]>(initialPool);
|
||||||
const [deck, setDeck] = useState<any[]>([]);
|
const [deck, setDeck] = useState<any[]>([]);
|
||||||
const [lands, setLands] = useState({ Plains: 0, Island: 0, Swamp: 0, Mountain: 0, Forest: 0 });
|
// const [lands, setLands] = useState(...); // REMOVED: Managed directly in deck now
|
||||||
const [hoveredCard, setHoveredCard] = useState<any>(null);
|
const [hoveredCard, setHoveredCard] = useState<any>(null);
|
||||||
const [displayCard, setDisplayCard] = useState<any>(null);
|
const [displayCard, setDisplayCard] = useState<any>(null);
|
||||||
|
|
||||||
@@ -362,26 +357,38 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
|
|
||||||
const applySuggestion = () => {
|
const applySuggestion = () => {
|
||||||
if (!landSuggestion) return;
|
if (!landSuggestion) return;
|
||||||
if (availableBasicLands && availableBasicLands.length > 0) {
|
|
||||||
const newLands: any[] = [];
|
const newLands: any[] = [];
|
||||||
Object.entries(landSuggestion).forEach(([type, count]) => {
|
Object.entries(landSuggestion).forEach(([type, count]) => {
|
||||||
if (count <= 0) return;
|
if ((count as number) <= 0) return;
|
||||||
const landCard = availableBasicLands.find(l => l.name === type) || availableBasicLands.find(l => l.name.includes(type));
|
|
||||||
if (landCard) {
|
// Find real land from cube or create generic
|
||||||
for (let i = 0; i < count; i++) {
|
let landCard = availableBasicLands && availableBasicLands.length > 0
|
||||||
const newLand = {
|
? (availableBasicLands.find(l => l.name === type) || availableBasicLands.find(l => l.name.includes(type)))
|
||||||
...landCard,
|
: null;
|
||||||
id: `land-${landCard.scryfallId}-${Date.now()}-${Math.random().toString(36).substr(2, 5)}-${i}`,
|
|
||||||
image_uris: landCard.image_uris || { normal: landCard.image }
|
if (!landCard) {
|
||||||
};
|
landCard = {
|
||||||
newLands.push(newLand);
|
id: `basic-source-${type}`,
|
||||||
}
|
name: type,
|
||||||
}
|
image_uris: { normal: LAND_URL_MAP[type] },
|
||||||
});
|
typeLine: "Basic Land",
|
||||||
if (newLands.length > 0) setDeck(prev => [...prev, ...newLands]);
|
scryfallId: `generic-${type}`
|
||||||
} else {
|
};
|
||||||
setLands(landSuggestion);
|
}
|
||||||
}
|
|
||||||
|
for (let i = 0; i < (count as number); i++) {
|
||||||
|
const newLand = {
|
||||||
|
...landCard,
|
||||||
|
id: `land-${type}-${Date.now()}-${Math.random().toString(36).substr(2, 5)}-${i}`,
|
||||||
|
image_uris: landCard.image_uris || { normal: landCard.image || LAND_URL_MAP[type] },
|
||||||
|
typeLine: landCard.typeLine || "Basic Land"
|
||||||
|
};
|
||||||
|
newLands.push(newLand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newLands.length > 0) setDeck(prev => [...prev, ...newLands]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Actions ---
|
// --- Actions ---
|
||||||
@@ -408,35 +415,10 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLandChange = (type: string, delta: number) => {
|
|
||||||
setLands(prev => ({ ...prev, [type]: Math.max(0, prev[type as keyof typeof lands] + delta) }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitDeck = () => {
|
const submitDeck = () => {
|
||||||
const genericLandCards = Object.entries(lands).flatMap(([type, count]) => {
|
socketService.socket.emit('player_ready', { deck });
|
||||||
const landUrlMap: any = {
|
|
||||||
Plains: "https://cards.scryfall.io/normal/front/d/1/d1ea1858-ad25-4d13-9860-25c898b02c42.jpg",
|
|
||||||
Island: "https://cards.scryfall.io/normal/front/2/f/2f3069b3-c15c-4399-ab99-c88c0379435b.jpg",
|
|
||||||
Swamp: "https://cards.scryfall.io/normal/front/1/7/17d0571f-df6c-4b53-912f-9cb4d5a9d224.jpg",
|
|
||||||
Mountain: "https://cards.scryfall.io/normal/front/f/5/f5383569-42b7-4c07-b67f-2736bc88bd37.jpg",
|
|
||||||
Forest: "https://cards.scryfall.io/normal/front/1/f/1fa688da-901d-4876-be11-884d6b677271.jpg"
|
|
||||||
};
|
|
||||||
return Array(count).fill(null).map((_, i) => ({
|
|
||||||
id: `basic-${type}-${i}`,
|
|
||||||
name: type,
|
|
||||||
image_uris: { normal: landUrlMap[type] },
|
|
||||||
typeLine: "Basic Land"
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const fullDeck = [...deck, ...genericLandCards];
|
|
||||||
socketService.socket.emit('player_ready', { deck: fullDeck });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedLands = useMemo(() => {
|
|
||||||
return [...(availableBasicLands || [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
}, [availableBasicLands]);
|
|
||||||
|
|
||||||
// --- DnD Handlers ---
|
// --- DnD Handlers ---
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(MouseSensor, { activationConstraint: { distance: 10 } }),
|
useSensor(MouseSensor, { activationConstraint: { distance: 10 } }),
|
||||||
@@ -540,66 +522,94 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// --- Render Functions ---
|
// --- Render Functions ---
|
||||||
const renderLandStation = () => (
|
// --- Consolidated Pool Logic ---
|
||||||
<div className="bg-slate-900/40 rounded border border-slate-700/50 p-2 mb-2 shrink-0 flex flex-col gap-2">
|
const landSourceCards = useMemo(() => {
|
||||||
{/* Header & Advisor */}
|
// If we have specific lands from cube, use them.
|
||||||
<div className="flex justify-between items-center bg-slate-800/50 p-2 rounded">
|
if (availableBasicLands && availableBasicLands.length > 0) {
|
||||||
<h4 className="text-xs font-bold text-slate-400 uppercase">Land Station</h4>
|
return availableBasicLands.map(land => ({
|
||||||
{landSuggestion ? (
|
...land,
|
||||||
<div className="flex items-center gap-2">
|
id: `land-source-${land.name}`, // stable ID for list
|
||||||
<span className="text-[10px] text-slate-500">Advice:</span>
|
isLandSource: true,
|
||||||
<div className="flex gap-1">
|
// Ensure image is set for display
|
||||||
|
image: land.image || land.image_uris?.normal
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise generate generic basics
|
||||||
|
const types = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest'];
|
||||||
|
return types.map(type => ({
|
||||||
|
id: `basic-source-${type}`,
|
||||||
|
name: type,
|
||||||
|
isLandSource: true,
|
||||||
|
image: LAND_URL_MAP[type],
|
||||||
|
typeLine: `Basic Land — ${type}`,
|
||||||
|
rarity: 'common',
|
||||||
|
cmc: 0,
|
||||||
|
set: 'LEA', // Dummy set for visuals
|
||||||
|
colors: type === 'Plains' ? ['W'] : type === 'Island' ? ['U'] : type === 'Swamp' ? ['B'] : type === 'Mountain' ? ['R'] : ['G']
|
||||||
|
}));
|
||||||
|
}, [availableBasicLands]);
|
||||||
|
|
||||||
|
// Removed displayPool memo to keep them separate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const LandAdvice = () => {
|
||||||
|
if (!landSuggestion) return null;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between bg-amber-900/40 p-2 rounded-lg border border-amber-700/50 mb-2 mx-1 animate-in fade-in slide-in-from-top-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-amber-500/20 p-1.5 rounded-md">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-amber-400"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-[10px] font-bold text-amber-200 uppercase tracking-wider">Recommended Lands</span>
|
||||||
|
<div className="flex gap-2 text-xs font-medium text-slate-300">
|
||||||
{Object.entries(landSuggestion).map(([type, count]) => {
|
{Object.entries(landSuggestion).map(([type, count]) => {
|
||||||
if ((count as number) <= 0) return null;
|
if ((count as number) <= 0) return null;
|
||||||
const color = type === 'Plains' ? 'text-amber-200' : type === 'Island' ? 'text-blue-200' : type === 'Swamp' ? 'text-purple-200' : type === 'Mountain' ? 'text-red-200' : 'text-emerald-200';
|
const colorClass = type === 'Plains' ? 'text-yellow-200' : type === 'Island' ? 'text-blue-200' : type === 'Swamp' ? 'text-purple-200' : type === 'Mountain' ? 'text-red-200' : 'text-emerald-200';
|
||||||
return <span key={type} className={`text-[10px] font-bold ${color}`}>{type[0]}:{count as number}</span>
|
return <span key={type} className={colorClass}>{count as number} {type}</span>
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={applySuggestion} className="bg-emerald-700 hover:bg-emerald-600 text-white text-[10px] px-2 py-0.5 rounded shadow font-bold uppercase">Auto-Fill</button>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<span className="text-[10px] text-slate-600 italic">Add spells for advice</span>
|
<button
|
||||||
)}
|
onClick={applySuggestion}
|
||||||
|
className="bg-amber-600 hover:bg-amber-500 text-white text-xs px-3 py-1.5 rounded-md shadow-lg font-bold uppercase tracking-wider transition-all hover:scale-105 active:scale-95 flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Check className="w-3 h-3" /> Auto-Fill
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
{/* Land Scroll */}
|
const LandRow = () => (
|
||||||
{availableBasicLands && availableBasicLands.length > 0 ? (
|
<div className="flex flex-col gap-2 mb-4 shrink-0">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto custom-scrollbar pb-1">
|
<LandAdvice />
|
||||||
{sortedLands.map((land) => (
|
<div className="flex flex-wrap gap-2 px-1 justify-center sm:justify-start">
|
||||||
<DraggableLandWrapper key={land.scryfallId} land={land}>
|
{landSourceCards.map(land => (
|
||||||
<div
|
<div
|
||||||
className="relative group cursor-pointer shrink-0"
|
key={land.id}
|
||||||
onClick={() => addLandToDeck(land)}
|
onClick={() => addLandToDeck(land)}
|
||||||
onMouseEnter={() => setHoveredCard(land)}
|
onMouseEnter={() => setHoveredCard(land)}
|
||||||
onMouseLeave={() => setHoveredCard(null)}
|
onMouseLeave={() => setHoveredCard(null)}
|
||||||
>
|
className="relative group cursor-pointer hover:scale-105 transition-transform"
|
||||||
<img
|
style={{ width: '85px' }}
|
||||||
src={land.image || land.image_uris?.normal}
|
>
|
||||||
className="w-16 rounded shadow group-hover:scale-105 transition-transform"
|
<div className="aspect-[2.5/3.5] rounded-md overflow-hidden shadow-sm border border-slate-700 group-hover:border-purple-400 relative">
|
||||||
alt={land.name}
|
<img src={land.image || land.image_uris?.normal} className="w-full h-full object-cover" draggable={false} />
|
||||||
draggable={false}
|
{/* Click Only Indicator */}
|
||||||
/>
|
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
||||||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 bg-black/40 rounded transition-opacity">
|
|
||||||
<span className="text-white font-bold text-[10px] bg-black/50 px-1 rounded">+</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DraggableLandWrapper>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex justify-between px-2">
|
|
||||||
{Object.keys(lands).map(type => (
|
|
||||||
<div key={type} className="flex flex-col items-center">
|
|
||||||
<div className="text-[10px] font-bold text-slate-500">{type[0]}</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<button onClick={() => handleLandChange(type, -1)} className="w-5 h-5 bg-slate-700 rounded text-slate-300 flex items-center justify-center font-bold text-xs">-</button>
|
|
||||||
<span className="w-4 text-center text-xs font-bold">{lands[type as keyof typeof lands]}</span>
|
|
||||||
<button onClick={() => handleLandChange(type, 1)} className="w-5 h-5 bg-slate-700 rounded text-slate-300 flex items-center justify-center font-bold text-xs">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
|
||||||
</div>
|
<span className="text-white text-xs font-bold bg-emerald-600/90 px-2 py-1 rounded shadow-lg backdrop-blur-sm border border-emerald-400/50 flex items-center gap-1">
|
||||||
)}
|
<span className="text-[10px]">+</span> ADD
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="h-px bg-gradient-to-r from-transparent via-slate-700 to-transparent w-full mt-2" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -853,7 +863,8 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<span>Card Pool ({pool.length})</span>
|
<span>Card Pool ({pool.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
||||||
{renderLandStation()}
|
{/* Land Station Merged into Display */}
|
||||||
|
<LandRow />
|
||||||
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={localCardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" source="pool" groupBy={groupBy} />
|
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={localCardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" source="pool" groupBy={groupBy} />
|
||||||
</div>
|
</div>
|
||||||
</DroppableZone>
|
</DroppableZone>
|
||||||
@@ -880,7 +891,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<span>Card Pool ({pool.length})</span>
|
<span>Card Pool ({pool.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
||||||
{renderLandStation()}
|
<LandRow />
|
||||||
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={localCardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" source="pool" groupBy={groupBy} />
|
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={localCardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" source="pool" groupBy={groupBy} />
|
||||||
</div>
|
</div>
|
||||||
</DroppableZone>
|
</DroppableZone>
|
||||||
|
|||||||
@@ -103,7 +103,14 @@ export const DraftView: React.FC<DraftViewProps> = ({ draftState, currentPlayerI
|
|||||||
}
|
}
|
||||||
}, [cardScale]);
|
}, [cardScale]);
|
||||||
|
|
||||||
const [layout, setLayout] = useState<'vertical' | 'horizontal'>('vertical'); // Default to vertical for consistency
|
const [layout, setLayout] = useState<'vertical' | 'horizontal'>(() => {
|
||||||
|
const saved = localStorage.getItem('draft_layout');
|
||||||
|
return (saved as 'vertical' | 'horizontal') || 'vertical';
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('draft_layout', layout);
|
||||||
|
}, [layout]);
|
||||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => {
|
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => {
|
||||||
return localStorage.getItem('draft_sidebarCollapsed') === 'true';
|
return localStorage.getItem('draft_sidebarCollapsed') === 'true';
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user