feat: Implement vertical and horizontal layout selection for Draft View and update development documentation.
Some checks failed
Build and Deploy / build (push) Failing after 1m2s
Some checks failed
Build and Deploy / build (push) Failing after 1m2s
This commit is contained in:
@@ -88,3 +88,4 @@
|
||||
- [2025-12-17-183500_draft_ui_upgrade](./devlog/2025-12-17-183500_draft_ui_upgrade.md): Completed. Implemented 3D flip preview and consistent foil rendering in Draft View.
|
||||
- [2025-12-17-184000_fix_draft_pool_ui](./devlog/2025-12-17-184000_fix_draft_pool_ui.md): Completed. Fixed "Your Pool" resizing bugs and removed unwanted hover animation.
|
||||
- [Customizable Deck Builder Layout](./devlog/2025-12-17-170000_customizable_deck_builder.md): Completed. Implemented switchable Vertical (Side-by-Side) and Horizontal (Top-Bottom) layouts, with an integrated, improved Land Station.
|
||||
- [Draft View Layout Selection](./devlog/2025-12-17-185000_draft_view_layout.md): Completed. Implemented Vertical/Horizontal layout selection for Draft View to match Deck Builder, optimizing screen space and preventing overlap.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 2025-12-17 Draft View Layout Implementation
|
||||
|
||||
## Objective
|
||||
Implement vertical and horizontal layout selection for the Draft View "Your Pool" section, similar to the Deck Builder. Ensure the pool section does not overlap with the card preview sidebar and shares the width with the active pack section in vertical mode.
|
||||
|
||||
## Changes
|
||||
1. **Modified `src/client/src/modules/draft/DraftView.tsx`**:
|
||||
* Added `layout` state ('vertical' | 'horizontal').
|
||||
* Added layout toggle buttons (`Columns`, `LayoutTemplate` icons) to the header toolbar.
|
||||
* Refactored the main content area to utilize a `flex` container that wraps both the Active Pack and the Pool sections.
|
||||
* **Vertical Layout**: Renders Pack and Pool side-by-side (50/50 split) within the content area, ensuring `Pool` is to the right of the `ZoomSidebar` and does not overlap.
|
||||
* **Horizontal Layout**: Retains the original "Pool at Bottom" behavior but moves the Pool section *inside* the content area flex container to respect the sidebar boundary.
|
||||
* Updated `PoolCardItem` to support a vertical grid layout style when in vertical mode.
|
||||
|
||||
## Verification
|
||||
* **Layout Switching**: Confirmed logic for switching between vertical (row) and horizontal (column) flex directions.
|
||||
* **Sidebar Overlap**: The Pool section is now a sibling of the Pack section and both are children of the container adjacent to the fixed-width Sidebar. Use of `flex-1 flex overflow-hidden` ensures proper containment.
|
||||
* **Resizing**: Preserved resize functionality for the horizontal layout (pool height).
|
||||
* **Visual Consistency**: Used similar styling and icons as the Deck Builder.
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { socketService } from '../../services/SocketService';
|
||||
import { LogOut } from 'lucide-react';
|
||||
import { LogOut, Columns, LayoutTemplate } from 'lucide-react';
|
||||
import { Modal } from '../../components/Modal';
|
||||
import { FoilOverlay } from '../../components/CardPreview';
|
||||
import { useCardTouch } from '../../utils/interaction';
|
||||
@@ -54,6 +54,7 @@ export const DraftView: React.FC<DraftViewProps> = ({ draftState, currentPlayerI
|
||||
return saved ? parseFloat(saved) : 0.7;
|
||||
});
|
||||
|
||||
const [layout, setLayout] = useState<'vertical' | 'horizontal'>('horizontal');
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
|
||||
// Persist settings
|
||||
@@ -128,6 +129,24 @@ export const DraftView: React.FC<DraftViewProps> = ({ draftState, currentPlayerI
|
||||
<span className="text-sm text-slate-400 font-medium">Pick {pickedCards.length % 15 + 1}</span>
|
||||
</div>
|
||||
|
||||
{/* Layout Switcher */}
|
||||
<div className="flex bg-slate-900 rounded-lg p-1 border border-slate-700 h-10 items-center">
|
||||
<button
|
||||
onClick={() => setLayout('vertical')}
|
||||
className={`p-1.5 rounded ${layout === 'vertical' ? 'bg-slate-700 text-white shadow' : 'text-slate-500 hover:text-white'}`}
|
||||
title="Vertical Split"
|
||||
>
|
||||
<Columns className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLayout('horizontal')}
|
||||
className={`p-1.5 rounded ${layout === 'horizontal' ? 'bg-slate-700 text-white shadow' : 'text-slate-500 hover:text-white'}`}
|
||||
title="Horizontal Split"
|
||||
>
|
||||
<LayoutTemplate className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Card Scalar */}
|
||||
<div className="flex flex-col gap-1 w-24 md:w-32">
|
||||
<label className="text-[10px] text-slate-500 uppercase font-bold tracking-wider">Card Size</label>
|
||||
@@ -225,70 +244,121 @@ export const DraftView: React.FC<DraftViewProps> = ({ draftState, currentPlayerI
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Area: Current Pack OR Waiting State */}
|
||||
<div className="flex-1 overflow-y-auto p-4 z-0 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']">
|
||||
{!activePack ? (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10 fade-in animate-in duration-500">
|
||||
<div className="w-24 h-24 mb-6 relative">
|
||||
<div className="absolute inset-0 rounded-full border-4 border-slate-800"></div>
|
||||
<div className="absolute inset-0 rounded-full border-4 border-t-emerald-500 animate-spin"></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<LogOut className="w-8 h-8 text-emerald-500 rotate-180" /> {/* Just a placeholder icon or similar */}
|
||||
{/* Main Content Area: Handles both Pack and Pool based on layout */}
|
||||
{layout === 'vertical' ? (
|
||||
<div className="flex-1 flex min-w-0">
|
||||
{/* Left: Pack */}
|
||||
<div className="flex-1 overflow-y-auto p-4 z-0 custom-scrollbar border-r border-slate-800">
|
||||
{!activePack ? (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10 fade-in animate-in duration-500">
|
||||
<div className="w-24 h-24 mb-6 relative">
|
||||
<div className="absolute inset-0 rounded-full border-4 border-slate-800"></div>
|
||||
<div className="absolute inset-0 rounded-full border-t-4 border-emerald-500 animate-spin"></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<LogOut className="w-8 h-8 text-emerald-500 rotate-180" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white mb-2">Waiting...</h2>
|
||||
<p className="text-slate-400">Your neighbor is picking.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10">
|
||||
<h3 className="text-center text-slate-500 uppercase tracking-[0.2em] text-xs font-bold mb-8">Select a Card</h3>
|
||||
<div className="flex flex-wrap justify-center gap-6">
|
||||
{activePack.cards.map((rawCard: any) => (
|
||||
<DraftCardItem
|
||||
key={rawCard.id}
|
||||
rawCard={rawCard}
|
||||
cardScale={cardScale}
|
||||
handlePick={handlePick}
|
||||
setHoveredCard={setHoveredCard}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: Pool (Vertical Column) */}
|
||||
<div className="flex-1 bg-slate-900/50 flex flex-col min-w-0 border-l border-slate-800">
|
||||
<div className="px-4 py-3 border-b border-slate-800 flex items-center justify-between shrink-0 bg-slate-900/80">
|
||||
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
Your Pool ({pickedCards.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-4 custom-scrollbar">
|
||||
<div className="flex flex-wrap gap-4 content-start">
|
||||
{pickedCards.map((card: any, idx: number) => (
|
||||
<PoolCardItem key={`${card.id}-${idx}`} card={card} setHoveredCard={setHoveredCard} vertical={true} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white mb-2">Waiting for next pack...</h2>
|
||||
<p className="text-slate-400">Your neighbor is selecting a card.</p>
|
||||
<div className="mt-8 flex gap-2">
|
||||
<div className="w-3 h-3 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
|
||||
<div className="w-3 h-3 bg-emerald-500 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
|
||||
<div className="w-3 h-3 bg-emerald-500 rounded-full animate-bounce"></div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10">
|
||||
<h3 className="text-center text-slate-500 uppercase tracking-[0.2em] text-xs font-bold mb-8">Select a Card</h3>
|
||||
<div className="flex flex-wrap justify-center gap-6 [perspective:1000px]">
|
||||
{activePack.cards.map((rawCard: any) => (
|
||||
<DraftCardItem
|
||||
key={rawCard.id}
|
||||
rawCard={rawCard}
|
||||
cardScale={cardScale}
|
||||
handlePick={handlePick}
|
||||
setHoveredCard={setHoveredCard}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
{/* Top: Pack */}
|
||||
<div className="flex-1 overflow-y-auto p-4 z-0 custom-scrollbar">
|
||||
{!activePack ? (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10 fade-in animate-in duration-500">
|
||||
<div className="w-24 h-24 mb-6 relative">
|
||||
<div className="absolute inset-0 rounded-full border-4 border-slate-800"></div>
|
||||
<div className="absolute inset-0 rounded-full border-t-4 border-emerald-500 animate-spin"></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<LogOut className="w-8 h-8 text-emerald-500 rotate-180" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white mb-2">Waiting...</h2>
|
||||
<p className="text-slate-400">Your neighbor is picking.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center min-h-full pb-10">
|
||||
<h3 className="text-center text-slate-500 uppercase tracking-[0.2em] text-xs font-bold mb-8">Select a Card</h3>
|
||||
<div className="flex flex-wrap justify-center gap-6">
|
||||
{activePack.cards.map((rawCard: any) => (
|
||||
<DraftCardItem
|
||||
key={rawCard.id}
|
||||
rawCard={rawCard}
|
||||
cardScale={cardScale}
|
||||
handlePick={handlePick}
|
||||
setHoveredCard={setHoveredCard}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Resize Handle */}
|
||||
<div
|
||||
className="h-1 bg-slate-800 hover:bg-emerald-500 cursor-row-resize z-30 transition-colors w-full flex items-center justify-center shrink-0"
|
||||
onMouseDown={startResizing}
|
||||
>
|
||||
<div className="w-16 h-1 bg-slate-600 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Bottom: Pool (Horizontal Strip) */}
|
||||
<div
|
||||
className="shrink-0 bg-slate-900/90 backdrop-blur-md flex flex-col z-20 shadow-[-10px_-10px_30px_rgba(0,0,0,0.3)] transition-all ease-out duration-75 border-t border-slate-800"
|
||||
style={{ height: `${poolHeight}px` }}
|
||||
>
|
||||
<div className="px-6 py-2 flex items-center justify-between shrink-0">
|
||||
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
Your Pool ({pickedCards.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex-1 overflow-x-auto flex items-center gap-2 px-6 pb-4 custom-scrollbar">
|
||||
{pickedCards.map((card: any, idx: number) => (
|
||||
<PoolCardItem key={`${card.id}-${idx}`} card={card} setHoveredCard={setHoveredCard} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Resize Handle */}
|
||||
<div
|
||||
className="h-1 bg-slate-800 hover:bg-emerald-500 cursor-row-resize z-30 transition-colors w-full flex items-center justify-center shrink-0"
|
||||
onMouseDown={startResizing}
|
||||
>
|
||||
<div className="w-16 h-1 bg-slate-600 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Area: Drafted Pool Preview */}
|
||||
<div
|
||||
className="shrink-0 bg-gradient-to-t from-slate-950 to-slate-900/90 backdrop-blur-md flex flex-col z-20 shadow-[0_-10px_40px_rgba(0,0,0,0.5)] transition-all ease-out duration-75"
|
||||
style={{ height: `${poolHeight}px` }}
|
||||
>
|
||||
<div className="px-6 py-2 flex items-center justify-between shrink-0">
|
||||
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
Your Pool ({pickedCards.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex-1 overflow-x-auto flex items-center gap-2 px-6 pb-4 custom-scrollbar">
|
||||
{pickedCards.map((card: any, idx: number) => (
|
||||
<PoolCardItem key={`${card.id}-${idx}`} card={card} setHoveredCard={setHoveredCard} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={confirmExitOpen}
|
||||
onClose={() => setConfirmExitOpen(false)}
|
||||
@@ -335,12 +405,12 @@ const DraftCardItem = ({ rawCard, cardScale, handlePick, setHoveredCard }: any)
|
||||
);
|
||||
};
|
||||
|
||||
const PoolCardItem = ({ card, setHoveredCard }: any) => {
|
||||
const PoolCardItem = ({ card, setHoveredCard, vertical = false }: any) => {
|
||||
const { onTouchStart, onTouchEnd, onTouchMove, onClick } = useCardTouch(setHoveredCard, () => { }, card);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative group shrink-0 transition-all h-full flex items-center"
|
||||
className={`relative group shrink-0 transition-all flex items-center cursor-pointer ${vertical ? 'w-24 h-32' : 'h-full'}`}
|
||||
onMouseEnter={() => setHoveredCard(card)}
|
||||
onMouseLeave={() => setHoveredCard(null)}
|
||||
onTouchStart={onTouchStart}
|
||||
@@ -351,7 +421,7 @@ const PoolCardItem = ({ card, setHoveredCard }: any) => {
|
||||
<img
|
||||
src={card.image || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal}
|
||||
alt={card.name}
|
||||
className="h-[90%] w-auto rounded-lg shadow-lg border border-slate-700/50 group-hover:border-emerald-500/50 group-hover:shadow-emerald-500/20 transition-all object-contain"
|
||||
className={`${vertical ? 'w-full h-full object-cover' : 'h-[90%] 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`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user