Skip to content

Commit 8e1ce93

Browse files
hh
1 parent 9ef0da8 commit 8e1ce93

5 files changed

Lines changed: 2944 additions & 717 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ prompt.md
5252
/skills
5353
.codex
5454
/.agents
55+
AGENTS.md

components/Yugioh/GridView.js

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Notification from "@/components/Notification";
44
import PriceTrendIndicator from "@/components/Yugioh/PriceTrendIndicator";
55

66
const FALLBACK_IMAGE = "/images/yugioh-card.png";
7+
const LOCAL_IMAGE_BASE_PATH = "/images/yugiohImages";
8+
const LOCAL_CROPPED_IMAGE_BASE_PATH = "/images/yugiohImagesCropped";
79

810
const formatCurrency = ( value ) => {
911
const numeric = Number( value );
@@ -28,6 +30,67 @@ const getPrimaryCardImage = ( card ) => (
2830
: null
2931
);
3032

33+
const getUniqueStrings = ( values ) => {
34+
const seen = new Set();
35+
36+
return values.reduce( ( list, value ) => {
37+
const normalized = normalizeOptionalString( value );
38+
if ( !normalized || seen.has( normalized ) ) {
39+
return list;
40+
}
41+
42+
seen.add( normalized );
43+
list.push( normalized );
44+
return list;
45+
}, [] );
46+
};
47+
48+
const getImageIdFromUrl = ( value ) => {
49+
const normalized = normalizeOptionalString( value );
50+
if ( !normalized ) {
51+
return null;
52+
}
53+
54+
const path = normalized.split( /[?#]/ )[ 0 ];
55+
const fileName = path.split( "/" ).pop() || "";
56+
const match = fileName.match( /^(.+?)\.(?:jpe?g|png|webp)$/i );
57+
return normalizeOptionalString( match?.[ 1 ] );
58+
};
59+
60+
const normalizeImageId = ( value ) => {
61+
const normalized = normalizeOptionalString( value );
62+
if ( !normalized ) {
63+
return null;
64+
}
65+
66+
const idFromUrl = getImageIdFromUrl( normalized );
67+
if ( idFromUrl ) {
68+
return idFromUrl;
69+
}
70+
71+
if ( /^(?:https?:)?\/\//i.test( normalized ) || normalized.startsWith( "/" ) ) {
72+
return null;
73+
}
74+
75+
return normalized.replace( /\.(?:jpe?g|png|webp)$/i, "" );
76+
};
77+
78+
const buildLocalImagePath = ( basePath, imageId ) => `${ basePath }/${ encodeURIComponent( String( imageId ) ) }.jpg`;
79+
80+
const getNextImageSources = ( image ) => {
81+
const rawSources = image.dataset.nextSrcs;
82+
if ( !rawSources ) {
83+
return [];
84+
}
85+
86+
try {
87+
const parsed = JSON.parse( rawSources );
88+
return Array.isArray( parsed ) ? parsed.filter( Boolean ) : [];
89+
} catch ( error ) {
90+
return rawSources.split( "|" ).filter( Boolean );
91+
}
92+
};
93+
3194
const GridView = ( { aggregatedData, onDeleteCard, onUpdateCard, sortConfig, handleSortChange, selectedCardIds, onSelectedCardIdsChange, folderNameMap = {} } ) => {
3295
const [ edit, setEdit ] = useState( {} );
3396
const [ editValues, setEditValues ] = useState( {} );
@@ -127,43 +190,54 @@ const GridView = ( { aggregatedData, onDeleteCard, onUpdateCard, sortConfig, han
127190
} );
128191
}, [] );
129192

130-
const getFullImagePath = useCallback( ( cardId ) => `/images/yugiohImages/${ String( cardId ) }.jpg`, [] );
193+
const getFullImagePath = useCallback( ( cardId ) => buildLocalImagePath( LOCAL_IMAGE_BASE_PATH, cardId ), [] );
194+
const getCroppedImagePath = useCallback( ( cardId ) => buildLocalImagePath( LOCAL_CROPPED_IMAGE_BASE_PATH, cardId ), [] );
131195

132196
const getCardImageSources = useCallback(
133197
( card ) => {
134198
const primaryCardImage = getPrimaryCardImage( card );
135-
const imageId =
136-
normalizeOptionalString( primaryCardImage?.id ) ||
137-
normalizeOptionalString( card?.cardImageId ) ||
138-
normalizeOptionalString( card?.cardId ) ||
139-
normalizeOptionalString( card?.cardDetailId ) ||
140-
normalizeOptionalString( card?.id );
141-
const localImageSrc = imageId ? getFullImagePath( imageId ) : null;
142-
const remoteImageSrc =
143-
normalizeOptionalString( card?.remoteImageUrl ) ||
144-
normalizeOptionalString( card?.image_url ) ||
145-
normalizeOptionalString( primaryCardImage?.image_url ) ||
146-
normalizeOptionalString( primaryCardImage?.image_url_cropped ) ||
147-
normalizeOptionalString( primaryCardImage?.image_url_small );
148-
const primaryImageSrc = remoteImageSrc || localImageSrc || FALLBACK_IMAGE;
149-
const secondaryImageSrc = primaryImageSrc === remoteImageSrc ? localImageSrc : remoteImageSrc;
199+
const remoteImageSources = getUniqueStrings( [
200+
card?.remoteImageUrl,
201+
card?.image_url,
202+
primaryCardImage?.image_url,
203+
primaryCardImage?.image_url_cropped,
204+
primaryCardImage?.image_url_small,
205+
] );
206+
const imageIds = getUniqueStrings( [
207+
normalizeImageId( primaryCardImage?.id ),
208+
normalizeImageId( card?.cardImageId ),
209+
normalizeImageId( card?.cardId ),
210+
normalizeImageId( card?.cardDetailId ),
211+
normalizeImageId( card?.id ),
212+
...remoteImageSources.map( getImageIdFromUrl ),
213+
] );
214+
const localImageSources = imageIds.flatMap( ( imageId ) => [
215+
getFullImagePath( imageId ),
216+
getCroppedImagePath( imageId ),
217+
] );
218+
const imageSources = getUniqueStrings( [
219+
...remoteImageSources,
220+
...localImageSources,
221+
FALLBACK_IMAGE,
222+
] );
223+
const primaryImageSrc = imageSources[ 0 ] || FALLBACK_IMAGE;
150224

151225
return {
152226
primaryImageSrc,
153-
secondaryImageSrc: secondaryImageSrc && secondaryImageSrc !== primaryImageSrc ? secondaryImageSrc : null,
227+
backupImageSrcs: imageSources.slice( 1 ).filter( ( source ) => source !== FALLBACK_IMAGE ),
154228
};
155229
},
156-
[ getFullImagePath ],
230+
[ getCroppedImagePath, getFullImagePath ],
157231
);
158232

159233
const handleCardImageError = useCallback( ( event ) => {
160234
const image = event.currentTarget;
161-
const nextSrc = image.dataset.nextSrc;
235+
const [ nextSrc, ...remainingSources ] = getNextImageSources( image );
162236
const fallbackSrc = image.dataset.fallbackSrc;
163237

164238
if ( nextSrc ) {
165239
image.src = nextSrc;
166-
image.dataset.nextSrc = "";
240+
image.dataset.nextSrcs = JSON.stringify( remainingSources );
167241
return;
168242
}
169243

@@ -276,7 +350,7 @@ const GridView = ( { aggregatedData, onDeleteCard, onUpdateCard, sortConfig, han
276350
{ memoizedAggregatedData.map( ( card ) => {
277351
if ( !card ) return null;
278352

279-
const { primaryImageSrc, secondaryImageSrc } = getCardImageSources( card );
353+
const { primaryImageSrc, backupImageSrcs } = getCardImageSources( card );
280354
const primaryCardImage = getPrimaryCardImage( card );
281355
const detailCardId =
282356
normalizeOptionalString( primaryCardImage?.id ) ||
@@ -413,7 +487,7 @@ const GridView = ( { aggregatedData, onDeleteCard, onUpdateCard, sortConfig, han
413487
src={ primaryImageSrc }
414488
alt={ `Card Image - ${ card.productName }` }
415489
loading="lazy"
416-
data-next-src={ secondaryImageSrc || "" }
490+
data-next-srcs={ JSON.stringify( backupImageSrcs ) }
417491
data-fallback-src={ FALLBACK_IMAGE }
418492
onError={ handleCardImageError }
419493
/>

next.config.js

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,9 @@
1-
const path = require("path");
2-
const ignoredAssetWatchPattern = /[\\/]public[\\/]images[\\/]yugiohImages(?:Cropped)?[\\/]/;
3-
4-
const mergeWebpackIgnoredPaths = ( ignored ) => {
5-
if ( !ignored ) {
6-
return [ ignoredAssetWatchPattern ];
7-
}
8-
9-
return Array.isArray( ignored )
10-
? [ ...ignored, ignoredAssetWatchPattern ]
11-
: [ ignored, ignoredAssetWatchPattern ];
12-
};
131
/** @type {import('next').NextConfig} */
142
const nextConfig = {
153
reactStrictMode: true,
164
productionBrowserSourceMaps: false,
175
turbopack: {
18-
root: path.join(__dirname),
19-
},
20-
21-
webpack: ( config, { dev } ) => {
22-
if ( dev ) {
23-
config.watchOptions = {
24-
...( config.watchOptions || {} ),
25-
ignored: mergeWebpackIgnoredPaths( config.watchOptions?.ignored ),
26-
};
27-
}
28-
29-
return config;
6+
root: __dirname,
307
},
318

329
async headers() {

0 commit comments

Comments
 (0)