feat: Refine session clear to preserve UI preferences while resetting game state and standardize image cache paths to full and crop subdirectories.
Some checks failed
Build and Deploy / build (push) Failing after 1m0s
Some checks failed
Build and Deploy / build (push) Failing after 1m0s
This commit is contained in:
@@ -454,24 +454,27 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, avail
|
||||
localStorage.removeItem('generatedPacks');
|
||||
localStorage.removeItem('availableLands');
|
||||
|
||||
// 3. Reset Local State
|
||||
// 3. Reset Local State to Defaults
|
||||
// This will trigger the useEffect hooks to update localStorage accordingly
|
||||
setInputText('');
|
||||
setRawScryfallData(null);
|
||||
setProcessedData(null);
|
||||
setSelectedSets([]);
|
||||
setSearchTerm(''); // Clear search
|
||||
|
||||
// 4. Clear Local Persistence
|
||||
localStorage.removeItem('cube_inputText');
|
||||
localStorage.removeItem('cube_rawScryfallData');
|
||||
localStorage.removeItem('cube_selectedSets');
|
||||
localStorage.removeItem('cube_viewMode');
|
||||
localStorage.removeItem('cube_gameTypeFilter');
|
||||
// We can optionally clear source mode, or leave it. Let's leave it for UX continuity or clear it?
|
||||
// Let's clear it to full reset.
|
||||
// localStorage.removeItem('cube_sourceMode');
|
||||
setFilters({
|
||||
ignoreBasicLands: false,
|
||||
ignoreCommander: false,
|
||||
ignoreTokens: false
|
||||
});
|
||||
|
||||
// 5. Reset UI Filters/Views to defaults
|
||||
setViewMode('list');
|
||||
setGenSettings({
|
||||
mode: 'mixed',
|
||||
rarityMode: 'peasant'
|
||||
});
|
||||
|
||||
setSourceMode('upload');
|
||||
setNumBoxes(1);
|
||||
setGameTypeFilter('all');
|
||||
|
||||
showToast("Session cleared successfully.", "success");
|
||||
|
||||
@@ -107,10 +107,10 @@ export class PackGeneratorService {
|
||||
layout: layout,
|
||||
colors: cardData.colors || [],
|
||||
image: useLocalImages
|
||||
? `${window.location.origin}/cards/images/${cardData.set}/art_full/${cardData.id}.jpg`
|
||||
? `${window.location.origin}/cards/images/${cardData.set}/full/${cardData.id}.jpg`
|
||||
: (cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || ''),
|
||||
imageArtCrop: useLocalImages
|
||||
? `${window.location.origin}/cards/images/${cardData.set}/art_crop/${cardData.id}.jpg`
|
||||
? `${window.location.origin}/cards/images/${cardData.set}/crop/${cardData.id}.jpg`
|
||||
: (cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || ''),
|
||||
set: cardData.set_name,
|
||||
setCode: cardData.set,
|
||||
|
||||
@@ -116,6 +116,16 @@ app.get('/api/sets', async (_req: Request, res: Response) => {
|
||||
app.get('/api/sets/:code/cards', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const cards = await scryfallService.fetchSetCards(req.params.code);
|
||||
|
||||
// Implicitly cache images for these cards so local URLs work
|
||||
if (cards.length > 0) {
|
||||
console.log(`[API] Triggering image cache for set ${req.params.code} (${cards.length} potential images)...`);
|
||||
// We await this to ensure images are ready before user views them,
|
||||
// although it might slow down the "Fetching..." phase.
|
||||
// Given the user requirement "upon downloading metadata, also ... must be cached", we wait.
|
||||
await cardService.cacheImages(cards);
|
||||
}
|
||||
|
||||
res.json(cards);
|
||||
} catch (e: any) {
|
||||
res.status(500).json({ error: e.message });
|
||||
@@ -131,6 +141,12 @@ app.post('/api/cards/parse', async (req: Request, res: Response) => {
|
||||
const uniqueIds = identifiers.map(id => id.type === 'id' ? { id: id.value } : { name: id.value });
|
||||
const uniqueCards = await scryfallService.fetchCollection(uniqueIds);
|
||||
|
||||
// Cache Images for the resolved cards
|
||||
if (uniqueCards.length > 0) {
|
||||
console.log(`[API] Triggering image cache for parsed lists (${uniqueCards.length} unique cards)...`);
|
||||
await cardService.cacheImages(uniqueCards);
|
||||
}
|
||||
|
||||
// Expand
|
||||
const expanded: any[] = [];
|
||||
const cardMap = new Map();
|
||||
|
||||
@@ -9,17 +9,11 @@ const __dirname = path.dirname(__filename);
|
||||
const CARDS_DIR = path.join(__dirname, '../public/cards');
|
||||
|
||||
export class CardService {
|
||||
private imagesDir: string;
|
||||
// Remove imagesDir property as we use CARDS_DIR directly
|
||||
private metadataDir: string;
|
||||
|
||||
constructor() {
|
||||
this.imagesDir = path.join(CARDS_DIR, 'images');
|
||||
this.metadataDir = path.join(CARDS_DIR, 'metadata');
|
||||
|
||||
// Directory creation is handled by FileStorageManager on write for Local,
|
||||
// and not needed for Redis.
|
||||
// Migration logic removed as it's FS specific and one-time.
|
||||
// If we need migration to Redis, it should be a separate script.
|
||||
}
|
||||
|
||||
async cacheImages(cards: any[]): Promise<number> {
|
||||
@@ -54,9 +48,9 @@ export class CardService {
|
||||
|
||||
const tasks: Promise<void>[] = [];
|
||||
|
||||
// Task 1: Normal Image (art_full)
|
||||
// Task 1: Normal Image (full)
|
||||
if (imageUrl) {
|
||||
const filePath = path.join(this.imagesDir, setCode, 'art_full', `${uuid}.jpg`);
|
||||
const filePath = path.join(CARDS_DIR, 'images', setCode, 'full', `${uuid}.jpg`);
|
||||
tasks.push((async () => {
|
||||
if (await fileStorageManager.exists(filePath)) return;
|
||||
try {
|
||||
@@ -65,19 +59,19 @@ export class CardService {
|
||||
const buffer = await response.arrayBuffer();
|
||||
await fileStorageManager.saveFile(filePath, Buffer.from(buffer));
|
||||
downloadedCount++;
|
||||
console.log(`Cached art_full: ${setCode}/${uuid}.jpg`);
|
||||
console.log(`Cached full: ${setCode}/${uuid}.jpg`);
|
||||
} else {
|
||||
console.error(`Failed to download art_full ${imageUrl}: ${response.statusText}`);
|
||||
console.error(`Failed to download full ${imageUrl}: ${response.statusText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error downloading art_full for ${uuid}:`, err);
|
||||
console.error(`Error downloading full for ${uuid}:`, err);
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
// Task 2: Art Crop (art_crop)
|
||||
// Task 2: Art Crop (crop)
|
||||
if (cropUrl) {
|
||||
const cropPath = path.join(this.imagesDir, setCode, 'art_crop', `${uuid}.jpg`);
|
||||
const cropPath = path.join(CARDS_DIR, 'images', setCode, 'crop', `${uuid}.jpg`);
|
||||
tasks.push((async () => {
|
||||
if (await fileStorageManager.exists(cropPath)) return;
|
||||
try {
|
||||
@@ -85,12 +79,12 @@ export class CardService {
|
||||
if (response.ok) {
|
||||
const buffer = await response.arrayBuffer();
|
||||
await fileStorageManager.saveFile(cropPath, Buffer.from(buffer));
|
||||
console.log(`Cached art_crop: ${setCode}/${uuid}.jpg`);
|
||||
console.log(`Cached crop: ${setCode}/${uuid}.jpg`);
|
||||
} else {
|
||||
console.error(`Failed to download art_crop ${cropUrl}: ${response.statusText}`);
|
||||
console.error(`Failed to download crop ${cropUrl}: ${response.statusText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error downloading art_crop for ${uuid}:`, err);
|
||||
console.error(`Error downloading crop for ${uuid}:`, err);
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ export class PackGeneratorService {
|
||||
typeLine: typeLine,
|
||||
layout: layout,
|
||||
colors: cardData.colors || [],
|
||||
image: cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || '',
|
||||
imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '',
|
||||
image: `/cards/images/${cardData.set}/full/${cardData.id}.jpg`,
|
||||
imageArtCrop: `/cards/images/${cardData.set}/crop/${cardData.id}.jpg`,
|
||||
set: cardData.set_name,
|
||||
setCode: cardData.set,
|
||||
setType: setType,
|
||||
|
||||
Reference in New Issue
Block a user