diff --git a/src/client/src/App.tsx b/src/client/src/App.tsx index c311100..c23efd4 100644 --- a/src/client/src/App.tsx +++ b/src/client/src/App.tsx @@ -7,8 +7,32 @@ import { DeckTester } from './modules/tester/DeckTester'; import { Pack } from './services/PackGeneratorService'; export const App: React.FC = () => { - const [activeTab, setActiveTab] = useState<'draft' | 'bracket' | 'lobby' | 'tester'>('draft'); - const [generatedPacks, setGeneratedPacks] = useState([]); + const [activeTab, setActiveTab] = useState<'draft' | 'bracket' | 'lobby' | 'tester'>(() => { + const saved = localStorage.getItem('activeTab'); + return (saved as 'draft' | 'bracket' | 'lobby' | 'tester') || 'draft'; + }); + + const [generatedPacks, setGeneratedPacks] = useState(() => { + try { + const saved = localStorage.getItem('generatedPacks'); + return saved ? JSON.parse(saved) : []; + } catch (e) { + console.error("Failed to load packs from storage", e); + return []; + } + }); + + React.useEffect(() => { + localStorage.setItem('activeTab', activeTab); + }, [activeTab]); + + React.useEffect(() => { + try { + localStorage.setItem('generatedPacks', JSON.stringify(generatedPacks)); + } catch (e) { + console.error("Failed to save packs to storage", e); + } + }, [generatedPacks]); return (
diff --git a/src/client/src/modules/cube/CubeManager.tsx b/src/client/src/modules/cube/CubeManager.tsx index 0cc247a..dc310ee 100644 --- a/src/client/src/modules/cube/CubeManager.tsx +++ b/src/client/src/modules/cube/CubeManager.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Layers, RotateCcw, Box, Check, Loader2, Upload, LayoutGrid, List, Sliders, Settings, Users, Download, Copy, FileDown } from 'lucide-react'; +import { Layers, RotateCcw, Box, Check, Loader2, Upload, LayoutGrid, List, Sliders, Settings, Users, Download, Copy, FileDown, Trash2 } from 'lucide-react'; import { CardParserService } from '../../services/CardParserService'; import { ScryfallService, ScryfallCard, ScryfallSet } from '../../services/ScryfallService'; import { PackGeneratorService, ProcessedPools, SetsMap, Pack, PackGenerationSettings } from '../../services/PackGeneratorService'; @@ -20,33 +20,91 @@ export const CubeManager: React.FC = ({ packs, setPacks, onGoT const generatorService = React.useMemo(() => new PackGeneratorService(), []); // --- State --- - const [inputText, setInputText] = useState(''); + const [inputText, setInputText] = useState(() => localStorage.getItem('cube_inputText') || ''); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(''); const [copySuccess, setCopySuccess] = useState(false); - const [rawScryfallData, setRawScryfallData] = useState(null); + const [rawScryfallData, setRawScryfallData] = useState(() => { + try { + const saved = localStorage.getItem('cube_rawScryfallData'); + return saved ? JSON.parse(saved) : null; + } catch (e) { + console.warn("Failed to load rawScryfallData from local storage", e); + return null; + } + }); + + useEffect(() => { + try { + if (rawScryfallData) { + localStorage.setItem('cube_rawScryfallData', JSON.stringify(rawScryfallData)); + } else { + localStorage.removeItem('cube_rawScryfallData'); + } + } catch (e) { + console.warn("Failed to save rawScryfallData to local storage (likely quota exceeded)", e); + } + }, [rawScryfallData]); const [processedData, setProcessedData] = useState<{ pools: ProcessedPools, sets: SetsMap } | null>(null); - const [filters, setFilters] = useState({ - ignoreBasicLands: true, - ignoreCommander: true, - ignoreTokens: true + const [filters, setFilters] = useState<{ + ignoreBasicLands: boolean; + ignoreCommander: boolean; + ignoreTokens: boolean; + }>(() => { + try { + const saved = localStorage.getItem('cube_filters'); + return saved ? JSON.parse(saved) : { + ignoreBasicLands: true, + ignoreCommander: true, + ignoreTokens: true + }; + } catch { + return { + ignoreBasicLands: true, + ignoreCommander: true, + ignoreTokens: true + }; + } }); // UI State const [viewMode, setViewMode] = useState<'list' | 'grid' | 'stack'>('list'); // Generation Settings - const [genSettings, setGenSettings] = useState({ - mode: 'mixed', - rarityMode: 'peasant' + const [genSettings, setGenSettings] = useState(() => { + try { + const saved = localStorage.getItem('cube_genSettings'); + return saved ? JSON.parse(saved) : { + mode: 'mixed', + rarityMode: 'peasant' + }; + } catch { + return { + mode: 'mixed', + rarityMode: 'peasant' + }; + } }); - const [sourceMode, setSourceMode] = useState<'upload' | 'set'>('upload'); + const [sourceMode, setSourceMode] = useState<'upload' | 'set'>(() => + (localStorage.getItem('cube_sourceMode') as 'upload' | 'set') || 'upload' + ); const [availableSets, setAvailableSets] = useState([]); - const [selectedSet, setSelectedSet] = useState(''); - const [numBoxes, setNumBoxes] = useState(3); + const [selectedSet, setSelectedSet] = useState(() => localStorage.getItem('cube_selectedSet') || ''); + const [numBoxes, setNumBoxes] = useState(() => { + const saved = localStorage.getItem('cube_numBoxes'); + return saved ? parseInt(saved) : 3; + }); + + // --- Persistence Effects --- + useEffect(() => localStorage.setItem('cube_inputText', inputText), [inputText]); + useEffect(() => localStorage.setItem('cube_filters', JSON.stringify(filters)), [filters]); + useEffect(() => localStorage.setItem('cube_genSettings', JSON.stringify(genSettings)), [genSettings]); + useEffect(() => localStorage.setItem('cube_sourceMode', sourceMode), [sourceMode]); + useEffect(() => localStorage.setItem('cube_selectedSet', selectedSet), [selectedSet]); + useEffect(() => localStorage.setItem('cube_numBoxes', numBoxes.toString()), [numBoxes]); const fileInputRef = useRef(null); @@ -216,6 +274,20 @@ export const CubeManager: React.FC = ({ packs, setPacks, onGoT setFilters(prev => ({ ...prev, [key]: !prev[key] })); }; + const handleReset = () => { + if (window.confirm("Are you sure you want to clear this session? All parsed cards and generated packs will be lost.")) { + setPacks([]); + setInputText(''); + setRawScryfallData(null); + setProcessedData(null); + setSelectedSet(''); + localStorage.removeItem('cube_inputText'); + localStorage.removeItem('cube_rawScryfallData'); + localStorage.removeItem('cube_selectedSet'); + // We keep filters and settings as they are user preferences + } + }; + return (
@@ -408,6 +480,15 @@ export const CubeManager: React.FC = ({ packs, setPacks, onGoT {loading ? : } {loading ? 'Generating...' : '2. Generate Packs'} + + {/* Reset Button */} +
diff --git a/src/client/src/modules/lobby/LobbyManager.tsx b/src/client/src/modules/lobby/LobbyManager.tsx index 10ee562..9755a6b 100644 --- a/src/client/src/modules/lobby/LobbyManager.tsx +++ b/src/client/src/modules/lobby/LobbyManager.tsx @@ -11,11 +11,22 @@ interface LobbyManagerProps { export const LobbyManager: React.FC = ({ generatedPacks }) => { const [activeRoom, setActiveRoom] = useState(null); - const [playerName, setPlayerName] = useState(''); + const [playerName, setPlayerName] = useState(() => localStorage.getItem('player_name') || ''); const [joinRoomId, setJoinRoomId] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); - const [playerId] = useState(() => Math.random().toString(36).substring(2) + Date.now().toString(36)); // Simple persistent ID + const [playerId] = useState(() => { + const saved = localStorage.getItem('player_id'); + if (saved) return saved; + const newId = Math.random().toString(36).substring(2) + Date.now().toString(36); + localStorage.setItem('player_id', newId); + return newId; + }); + + // Persist player name + React.useEffect(() => { + localStorage.setItem('player_name', playerName); + }, [playerName]); const connect = () => { if (!socketService.socket.connected) { diff --git a/src/client/src/services/CardParserService.ts b/src/client/src/services/CardParserService.ts index fae4786..5c8a096 100644 --- a/src/client/src/services/CardParserService.ts +++ b/src/client/src/services/CardParserService.ts @@ -34,7 +34,7 @@ export class CardParserService { const qty = parseInt(parts[0]); // If valid CSV structure if (!isNaN(qty)) { - const name = parts[1]; // We can keep name for reference, but we use ID if present + // const name = parts[1]; // We can keep name for reference, but we use ID if present const finishRaw = parts[2]?.toLowerCase(); const finish = (finishRaw === 'foil' || finishRaw === 'etched') ? 'foil' : (finishRaw === 'normal' ? 'normal' : undefined);