Skip to content

Commit 7b57748

Browse files
authored
Merge pull request #58 from AJFrio/perf-optimize-image-url-normalization-2488389365171026236
⚡ Optimize Product List Rendering Performance
2 parents 1431a1e + 122e0e9 commit 7b57748

File tree

2 files changed

+101
-56
lines changed

2 files changed

+101
-56
lines changed

benchmarks/normalize-image-url.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { normalizeImageUrl } from '../src/lib/utils.js'
2+
3+
const ITERATIONS = 100000;
4+
5+
const testCases = [
6+
{ name: 'Empty string', url: '' },
7+
{ name: 'Simple URL', url: 'https://example.com/image.jpg' },
8+
{ name: 'Google Drive URL', url: 'https://drive.google.com/file/d/1234567890abcdef/view?usp=sharing' },
9+
];
10+
11+
console.log(`Running benchmark with ${ITERATIONS} iterations...`);
12+
13+
for (const { name, url } of testCases) {
14+
const start = performance.now();
15+
for (let i = 0; i < ITERATIONS; i++) {
16+
normalizeImageUrl(url);
17+
}
18+
const end = performance.now();
19+
console.log(`${name}: ${(end - start).toFixed(2)}ms`);
20+
}
21+
22+
// Simulate cached access
23+
const cachedUrl = 'https://drive.google.com/file/d/1234567890abcdef/view?usp=sharing';
24+
const normalizedCached = normalizeImageUrl(cachedUrl);
25+
const startCached = performance.now();
26+
for (let i = 0; i < ITERATIONS; i++) {
27+
const _ = normalizedCached;
28+
}
29+
const endCached = performance.now();
30+
console.log(`Cached access (simulated): ${(endCached - startCached).toFixed(2)}ms`);

src/components/admin/ProductWorkspace.jsx

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,71 @@ function VariantGroupEditor({
178178
</div>
179179
)
180180
}
181+
182+
function ProductListItem({ product, isActive, onSelect, collectionName }) {
183+
const rawPreviewImage =
184+
Array.isArray(product.images) && product.images.length > 0
185+
? product.images[0]
186+
: product.imageUrl || ''
187+
188+
const previewImage = useMemo(() => normalizeImageUrl(rawPreviewImage), [rawPreviewImage])
189+
190+
return (
191+
<button
192+
type="button"
193+
onClick={() => onSelect(product.id)}
194+
className={`w-full rounded-lg border p-3 text-left transition-all duration-200 ${
195+
isActive
196+
? 'border-[var(--admin-accent)] bg-[var(--admin-accent)]/10 shadow-[var(--admin-shadow)]'
197+
: 'border-[var(--admin-border-primary)] bg-[var(--admin-bg-elevated)] hover:border-[var(--admin-border-secondary)]'
198+
}`}
199+
>
200+
<div className="flex gap-3">
201+
{previewImage ? (
202+
<img
203+
src={previewImage}
204+
alt={product.name}
205+
className="h-12 w-12 flex-shrink-0 rounded-md object-cover"
206+
/>
207+
) : (
208+
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-md bg-[var(--admin-bg-secondary)]">
209+
<Package className="h-6 w-6 text-[var(--admin-text-muted)]" />
210+
</div>
211+
)}
212+
<div className="flex-1 space-y-1">
213+
<div className="flex items-center justify-between gap-3">
214+
<span className="font-medium text-[var(--admin-text-primary)] text-sm truncate">
215+
{product.name || 'Untitled product'}
216+
</span>
217+
<span className="text-xs font-semibold text-[var(--admin-text-secondary)]">
218+
{formatCurrency(product.price, product.currency)}
219+
</span>
220+
</div>
221+
<p className="text-xs text-[var(--admin-text-muted)] truncate">
222+
{product.tagline || product.description || 'No description provided.'}
223+
</p>
224+
<div className="flex flex-wrap items-center gap-1.5 text-[11px]">
225+
{collectionName ? (
226+
<span className="rounded-full bg-[var(--admin-bg-secondary)] px-2 py-0.5 text-[var(--admin-text-secondary)]">
227+
{collectionName}
228+
</span>
229+
) : (
230+
<span className="rounded-full bg-[var(--admin-bg-secondary)] px-2 py-0.5 text-[var(--admin-text-muted)]">
231+
No collection
232+
</span>
233+
)}
234+
{product.archived && (
235+
<span className="rounded-full bg-[var(--admin-text-muted)]/20 px-2 py-0.5 text-[var(--admin-text-secondary)]">
236+
Archived
237+
</span>
238+
)}
239+
</div>
240+
</div>
241+
</div>
242+
</button>
243+
)
244+
}
245+
181246
export function ProductWorkspace() {
182247
const [products, setProducts] = useState([])
183248
const [collections, setCollections] = useState([])
@@ -694,68 +759,18 @@ export function ProductWorkspace() {
694759
</div>
695760
) : (
696761
filteredProducts.map((product) => {
697-
const previewImage =
698-
Array.isArray(product.images) && product.images.length > 0
699-
? product.images[0]
700-
: product.imageUrl || ''
701762
const isActive = !isCreating && selectedId === product.id
702763
const collectionName = product.collectionId
703764
? collectionLookup.get(product.collectionId)
704765
: null
705766
return (
706-
<button
767+
<ProductListItem
707768
key={product.id}
708-
type="button"
709-
onClick={() => handleSelectProduct(product.id)}
710-
className={`w-full rounded-lg border p-3 text-left transition-all duration-200 ${
711-
isActive
712-
? 'border-[var(--admin-accent)] bg-[var(--admin-accent)]/10 shadow-[var(--admin-shadow)]'
713-
: 'border-[var(--admin-border-primary)] bg-[var(--admin-bg-elevated)] hover:border-[var(--admin-border-secondary)]'
714-
}`}
715-
>
716-
<div className="flex gap-3">
717-
{previewImage ? (
718-
<img
719-
src={normalizeImageUrl(previewImage)}
720-
alt={product.name}
721-
className="h-12 w-12 flex-shrink-0 rounded-md object-cover"
722-
/>
723-
) : (
724-
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-md bg-[var(--admin-bg-secondary)]">
725-
<Package className="h-6 w-6 text-[var(--admin-text-muted)]" />
726-
</div>
727-
)}
728-
<div className="flex-1 space-y-1">
729-
<div className="flex items-center justify-between gap-3">
730-
<span className="font-medium text-[var(--admin-text-primary)] text-sm truncate">
731-
{product.name || 'Untitled product'}
732-
</span>
733-
<span className="text-xs font-semibold text-[var(--admin-text-secondary)]">
734-
{formatCurrency(product.price, product.currency)}
735-
</span>
736-
</div>
737-
<p className="text-xs text-[var(--admin-text-muted)] truncate">
738-
{product.tagline || product.description || 'No description provided.'}
739-
</p>
740-
<div className="flex flex-wrap items-center gap-1.5 text-[11px]">
741-
{collectionName ? (
742-
<span className="rounded-full bg-[var(--admin-bg-secondary)] px-2 py-0.5 text-[var(--admin-text-secondary)]">
743-
{collectionName}
744-
</span>
745-
) : (
746-
<span className="rounded-full bg-[var(--admin-bg-secondary)] px-2 py-0.5 text-[var(--admin-text-muted)]">
747-
No collection
748-
</span>
749-
)}
750-
{product.archived && (
751-
<span className="rounded-full bg-[var(--admin-text-muted)]/20 px-2 py-0.5 text-[var(--admin-text-secondary)]">
752-
Archived
753-
</span>
754-
)}
755-
</div>
756-
</div>
757-
</div>
758-
</button>
769+
product={product}
770+
isActive={isActive}
771+
collectionName={collectionName}
772+
onSelect={handleSelectProduct}
773+
/>
759774
)
760775
})
761776
)}

0 commit comments

Comments
 (0)