@inoo-ch/payload-image-optimizer 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/RegenerationButton.js +32 -20
- package/dist/components/RegenerationButton.js.map +1 -1
- package/package.json +59 -31
- package/src/components/RegenerationButton.tsx +22 -9
- package/dist/components/ImageBox.d.ts +0 -8
- package/dist/components/OptimizationStatus.d.ts +0 -4
- package/dist/components/RegenerationButton.d.ts +0 -2
- package/dist/defaults.d.ts +0 -3
- package/dist/endpoints/regenerate.d.ts +0 -4
- package/dist/exports/client.d.ts +0 -6
- package/dist/exports/rsc.d.ts +0 -1
- package/dist/fields/imageOptimizerField.d.ts +0 -4
- package/dist/hooks/afterChange.d.ts +0 -3
- package/dist/hooks/beforeChange.d.ts +0 -3
- package/dist/index.d.ts +0 -6
- package/dist/processing/index.d.ts +0 -17
- package/dist/tasks/convertFormats.d.ts +0 -12
- package/dist/tasks/regenerateDocument.d.ts +0 -18
- package/dist/translations/index.d.ts +0 -1
- package/dist/types.d.ts +0 -57
- package/dist/utilities/getImageOptimizerProps.d.ts +0 -24
- package/dist/utilities/resolveStaticDir.d.ts +0 -3
- package/dist/utilities/storage.d.ts +0 -18
- package/dist/utilities/thumbhash.d.ts +0 -2
|
@@ -167,7 +167,7 @@ export const RegenerationButton = ()=>{
|
|
|
167
167
|
};
|
|
168
168
|
}, []);
|
|
169
169
|
if (!collectionSlug) return null;
|
|
170
|
-
const progressPercent = progress && progress.total > 0 ? Math.round(progress.complete / progress.total * 100) : 0;
|
|
170
|
+
const progressPercent = progress && progress.total > 0 ? Math.round((progress.complete + progress.errored) / progress.total * 100) : 0;
|
|
171
171
|
const showProgressBar = isRunning && progress || stalled && progress;
|
|
172
172
|
// Stats computations
|
|
173
173
|
const statsPercent = stats && stats.total > 0 ? Math.round(stats.complete / stats.total * 100) : 0;
|
|
@@ -234,11 +234,13 @@ export const RegenerationButton = ()=>{
|
|
|
234
234
|
fontSize: '13px'
|
|
235
235
|
},
|
|
236
236
|
children: [
|
|
237
|
-
"
|
|
238
|
-
progress.pending,
|
|
237
|
+
"Processing finished with issues. ",
|
|
238
|
+
progress.errored + progress.pending,
|
|
239
239
|
" image",
|
|
240
|
-
progress.pending !== 1 ? 's' : '',
|
|
241
|
-
" failed
|
|
240
|
+
progress.errored + progress.pending !== 1 ? 's' : '',
|
|
241
|
+
" failed",
|
|
242
|
+
progress.pending > 0 ? ` (${progress.pending} stuck)` : '',
|
|
243
|
+
". Re-run to retry."
|
|
242
244
|
]
|
|
243
245
|
}),
|
|
244
246
|
showProgressBar && /*#__PURE__*/ _jsxs("div", {
|
|
@@ -280,33 +282,43 @@ export const RegenerationButton = ()=>{
|
|
|
280
282
|
})
|
|
281
283
|
]
|
|
282
284
|
}),
|
|
283
|
-
/*#__PURE__*/
|
|
285
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
284
286
|
style: {
|
|
285
287
|
height: '6px',
|
|
286
288
|
backgroundColor: '#e5e7eb',
|
|
287
289
|
borderRadius: '3px',
|
|
288
|
-
overflow: 'hidden'
|
|
290
|
+
overflow: 'hidden',
|
|
291
|
+
display: 'flex'
|
|
289
292
|
},
|
|
290
|
-
children:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
children: [
|
|
294
|
+
/*#__PURE__*/ _jsx("div", {
|
|
295
|
+
style: {
|
|
296
|
+
height: '100%',
|
|
297
|
+
width: `${progress.total > 0 ? Math.round(progress.complete / progress.total * 100) : 0}%`,
|
|
298
|
+
backgroundColor: '#10b981',
|
|
299
|
+
transition: 'width 0.3s ease'
|
|
300
|
+
}
|
|
301
|
+
}),
|
|
302
|
+
progress.errored > 0 && /*#__PURE__*/ _jsx("div", {
|
|
303
|
+
style: {
|
|
304
|
+
height: '100%',
|
|
305
|
+
width: `${progress.total > 0 ? Math.round(progress.errored / progress.total * 100) : 0}%`,
|
|
306
|
+
backgroundColor: '#ef4444',
|
|
307
|
+
transition: 'width 0.3s ease'
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
]
|
|
299
311
|
})
|
|
300
312
|
]
|
|
301
313
|
}),
|
|
302
|
-
!isRunning &&
|
|
314
|
+
!isRunning && progress && progress.complete > 0 && queued !== 0 && /*#__PURE__*/ _jsxs("span", {
|
|
303
315
|
style: {
|
|
304
316
|
fontSize: '13px'
|
|
305
317
|
},
|
|
306
318
|
children: [
|
|
307
319
|
/*#__PURE__*/ _jsxs("span", {
|
|
308
320
|
style: {
|
|
309
|
-
color: '#10b981'
|
|
321
|
+
color: progress.errored > 0 || stalled ? '#f59e0b' : '#10b981'
|
|
310
322
|
},
|
|
311
323
|
children: [
|
|
312
324
|
"Done! ",
|
|
@@ -316,13 +328,13 @@ export const RegenerationButton = ()=>{
|
|
|
316
328
|
" optimized."
|
|
317
329
|
]
|
|
318
330
|
}),
|
|
319
|
-
progress.errored > 0 && /*#__PURE__*/ _jsxs("span", {
|
|
331
|
+
(progress.errored > 0 || stalled && progress.pending > 0) && /*#__PURE__*/ _jsxs("span", {
|
|
320
332
|
style: {
|
|
321
333
|
color: '#ef4444'
|
|
322
334
|
},
|
|
323
335
|
children: [
|
|
324
336
|
' ',
|
|
325
|
-
progress.errored,
|
|
337
|
+
progress.errored + (stalled ? progress.pending : 0),
|
|
326
338
|
" failed."
|
|
327
339
|
]
|
|
328
340
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/RegenerationButton.tsx"],"sourcesContent":["'use client'\n\nimport React, { useState, useEffect, useCallback, useRef } from 'react'\n\ntype RegenerationProgress = {\n total: number\n complete: number\n errored: number\n pending: number\n}\n\nconst STALL_THRESHOLD = 5\n\nexport const RegenerationButton: React.FC = () => {\n const [isRunning, setIsRunning] = useState(false)\n const [progress, setProgress] = useState<RegenerationProgress | null>(null)\n const [queued, setQueued] = useState<number | null>(null)\n const [force, setForce] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [stalled, setStalled] = useState(false)\n const [collectionSlug, setCollectionSlug] = useState<string | null>(null)\n const [stats, setStats] = useState<RegenerationProgress | null>(null)\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n const stallRef = useRef({ lastProcessed: 0, stallCount: 0 })\n const prevIsRunningRef = useRef(false)\n\n // Extract collection slug from URL after mount to avoid hydration mismatch\n useEffect(() => {\n const slug = window.location.pathname.split('/collections/')[1]?.split('/')[0] ?? null\n setCollectionSlug(slug)\n }, [])\n\n // Fetch optimization stats (independent of regeneration)\n const fetchStats = useCallback(async () => {\n if (!collectionSlug) return\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (res.ok) {\n const data: RegenerationProgress = await res.json()\n setStats(data)\n }\n } catch {\n // ignore stats fetch errors\n }\n }, [collectionSlug])\n\n const stopPolling = useCallback(() => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n intervalRef.current = null\n }\n }, [])\n\n const pollProgress = useCallback(async () => {\n if (!collectionSlug) return\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (res.ok) {\n const data: RegenerationProgress = await res.json()\n setProgress(data)\n\n // Stop polling when no more pending\n if (data.pending <= 0) {\n setIsRunning(false)\n stopPolling()\n return\n }\n\n // Stall detection\n const processed = data.complete + data.errored\n if (processed === stallRef.current.lastProcessed) {\n stallRef.current.stallCount += 1\n } else {\n stallRef.current.stallCount = 0\n stallRef.current.lastProcessed = processed\n }\n\n if (stallRef.current.stallCount >= STALL_THRESHOLD) {\n stopPolling()\n setIsRunning(false)\n setStalled(true)\n }\n }\n } catch {\n // ignore polling errors\n }\n }, [collectionSlug, stopPolling])\n\n // On mount (once collectionSlug is known), check if there's an ongoing job and resume polling\n useEffect(() => {\n if (!collectionSlug) return\n let cancelled = false\n const checkOngoing = async () => {\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (!res.ok || cancelled) return\n const data: RegenerationProgress = await res.json()\n // Always store stats on mount\n setStats(data)\n if (data.pending > 0) {\n setProgress(data)\n setIsRunning(true)\n setStalled(false)\n setQueued(null)\n stallRef.current = { lastProcessed: data.complete + data.errored, stallCount: 0 }\n intervalRef.current = setInterval(pollProgress, 2000)\n }\n } catch {\n // ignore\n }\n }\n checkOngoing()\n return () => {\n cancelled = true\n }\n }, [collectionSlug, pollProgress])\n\n // Refresh stats when regeneration finishes (isRunning transitions from true to false)\n useEffect(() => {\n if (prevIsRunningRef.current && !isRunning) {\n fetchStats()\n }\n prevIsRunningRef.current = isRunning\n }, [isRunning, fetchStats])\n\n const handleRegenerate = async () => {\n if (!collectionSlug) return\n setError(null)\n setStalled(false)\n setIsRunning(true)\n setQueued(null)\n setProgress(null)\n stallRef.current = { lastProcessed: 0, stallCount: 0 }\n\n try {\n const res = await fetch('/api/image-optimizer/regenerate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ collectionSlug, force }),\n })\n\n if (!res.ok) {\n const data = await res.json()\n throw new Error(data.error || 'Failed to start regeneration')\n }\n\n const data = await res.json()\n setQueued(data.queued)\n\n if (data.queued === 0) {\n setIsRunning(false)\n return\n }\n\n // Start polling\n intervalRef.current = setInterval(pollProgress, 2000)\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err))\n setIsRunning(false)\n }\n }\n\n // Cleanup interval on unmount\n useEffect(() => {\n return () => {\n if (intervalRef.current) clearInterval(intervalRef.current)\n }\n }, [])\n\n if (!collectionSlug) return null\n\n const progressPercent =\n progress && progress.total > 0\n ? Math.round((progress.complete / progress.total) * 100)\n : 0\n\n const showProgressBar = (isRunning && progress) || (stalled && progress)\n\n // Stats computations\n const statsPercent =\n stats && stats.total > 0\n ? Math.round((stats.complete / stats.total) * 100)\n : 0\n const allOptimized = stats && stats.total > 0 && stats.complete === stats.total\n\n return (\n <div\n style={{\n padding: '16px 24px',\n borderBottom: '1px solid #e5e7eb',\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n flexWrap: 'wrap',\n }}\n >\n <button\n onClick={handleRegenerate}\n disabled={isRunning}\n style={{\n backgroundColor: isRunning ? '#9ca3af' : '#4f46e5',\n color: '#fff',\n border: 'none',\n borderRadius: '6px',\n padding: '8px 16px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: isRunning ? 'not-allowed' : 'pointer',\n }}\n >\n {isRunning ? 'Regenerating...' : 'Regenerate Images'}\n </button>\n\n <label\n style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' }}\n >\n <input\n type=\"checkbox\"\n checked={force}\n onChange={(e) => setForce(e.target.checked)}\n disabled={isRunning}\n />\n Force re-process all\n </label>\n\n {error && (\n <span style={{ color: '#ef4444', fontSize: '13px' }}>{error}</span>\n )}\n\n {queued === 0 && !isRunning && !stalled && (\n <span style={{ color: '#10b981', fontSize: '13px' }}>\n All images already optimized.\n </span>\n )}\n\n {stalled && progress && (\n <span style={{ color: '#f59e0b', fontSize: '13px' }}>\n Process stalled. {progress.pending} image{progress.pending !== 1 ? 's' : ''} failed to process.\n </span>\n )}\n\n {showProgressBar && (\n <div style={{ flex: 1, minWidth: '200px' }}>\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n fontSize: '12px',\n marginBottom: '4px',\n }}\n >\n <span>\n {progress.complete} / {progress.total} complete\n </span>\n {progress.errored > 0 && (\n <span style={{ color: '#ef4444' }}>{progress.errored} errors</span>\n )}\n <span>{progressPercent}%</span>\n </div>\n <div\n style={{\n height: '6px',\n backgroundColor: '#e5e7eb',\n borderRadius: '3px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n height: '100%',\n width: `${progressPercent}%`,\n backgroundColor: stalled ? '#f59e0b' : '#10b981',\n borderRadius: '3px',\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n </div>\n )}\n\n {!isRunning && !stalled && progress && progress.complete > 0 && queued !== 0 && (\n <span style={{ fontSize: '13px' }}>\n <span style={{ color: '#10b981' }}>\n Done! {progress.complete}/{progress.total} optimized.\n </span>\n {progress.errored > 0 && (\n <span style={{ color: '#ef4444' }}>\n {' '}{progress.errored} failed.\n </span>\n )}\n </span>\n )}\n\n {/* Persistent optimization stats — always visible when not actively regenerating */}\n {!isRunning && stats && stats.total > 0 && (\n <div\n style={{\n marginLeft: 'auto',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-end',\n gap: '4px',\n minWidth: '180px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }}>\n {allOptimized ? (\n <span style={{ color: '#10b981' }}>\n ✓ All {stats.total} images optimized\n </span>\n ) : (\n <>\n <span style={{ color: '#6b7280' }}>\n {stats.complete}/{stats.total} optimized\n </span>\n {stats.errored > 0 && (\n <>\n <span style={{ color: '#d1d5db' }}>·</span>\n <span style={{ color: '#ef4444' }}>{stats.errored} errors</span>\n </>\n )}\n </>\n )}\n </div>\n {!allOptimized && (\n <div\n style={{\n width: '100%',\n height: '3px',\n backgroundColor: '#e5e7eb',\n borderRadius: '2px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n height: '100%',\n width: `${statsPercent}%`,\n backgroundColor: stats.errored > 0 ? '#f59e0b' : '#10b981',\n borderRadius: '2px',\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],"names":["React","useState","useEffect","useCallback","useRef","STALL_THRESHOLD","RegenerationButton","isRunning","setIsRunning","progress","setProgress","queued","setQueued","force","setForce","error","setError","stalled","setStalled","collectionSlug","setCollectionSlug","stats","setStats","intervalRef","stallRef","lastProcessed","stallCount","prevIsRunningRef","slug","window","location","pathname","split","fetchStats","res","fetch","ok","data","json","stopPolling","current","clearInterval","pollProgress","pending","processed","complete","errored","cancelled","checkOngoing","setInterval","handleRegenerate","method","headers","body","JSON","stringify","Error","err","message","String","progressPercent","total","Math","round","showProgressBar","statsPercent","allOptimized","div","style","padding","borderBottom","display","alignItems","gap","flexWrap","button","onClick","disabled","backgroundColor","color","border","borderRadius","fontSize","fontWeight","cursor","label","input","type","checked","onChange","e","target","span","flex","minWidth","justifyContent","marginBottom","height","overflow","width","transition","marginLeft","flexDirection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/RegenerationButton.tsx"],"sourcesContent":["'use client'\n\nimport React, { useState, useEffect, useCallback, useRef } from 'react'\n\ntype RegenerationProgress = {\n total: number\n complete: number\n errored: number\n pending: number\n}\n\nconst STALL_THRESHOLD = 5\n\nexport const RegenerationButton: React.FC = () => {\n const [isRunning, setIsRunning] = useState(false)\n const [progress, setProgress] = useState<RegenerationProgress | null>(null)\n const [queued, setQueued] = useState<number | null>(null)\n const [force, setForce] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [stalled, setStalled] = useState(false)\n const [collectionSlug, setCollectionSlug] = useState<string | null>(null)\n const [stats, setStats] = useState<RegenerationProgress | null>(null)\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n const stallRef = useRef({ lastProcessed: 0, stallCount: 0 })\n const prevIsRunningRef = useRef(false)\n\n // Extract collection slug from URL after mount to avoid hydration mismatch\n useEffect(() => {\n const slug = window.location.pathname.split('/collections/')[1]?.split('/')[0] ?? null\n setCollectionSlug(slug)\n }, [])\n\n // Fetch optimization stats (independent of regeneration)\n const fetchStats = useCallback(async () => {\n if (!collectionSlug) return\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (res.ok) {\n const data: RegenerationProgress = await res.json()\n setStats(data)\n }\n } catch {\n // ignore stats fetch errors\n }\n }, [collectionSlug])\n\n const stopPolling = useCallback(() => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n intervalRef.current = null\n }\n }, [])\n\n const pollProgress = useCallback(async () => {\n if (!collectionSlug) return\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (res.ok) {\n const data: RegenerationProgress = await res.json()\n setProgress(data)\n\n // Stop polling when no more pending\n if (data.pending <= 0) {\n setIsRunning(false)\n stopPolling()\n return\n }\n\n // Stall detection\n const processed = data.complete + data.errored\n if (processed === stallRef.current.lastProcessed) {\n stallRef.current.stallCount += 1\n } else {\n stallRef.current.stallCount = 0\n stallRef.current.lastProcessed = processed\n }\n\n if (stallRef.current.stallCount >= STALL_THRESHOLD) {\n stopPolling()\n setIsRunning(false)\n setStalled(true)\n }\n }\n } catch {\n // ignore polling errors\n }\n }, [collectionSlug, stopPolling])\n\n // On mount (once collectionSlug is known), check if there's an ongoing job and resume polling\n useEffect(() => {\n if (!collectionSlug) return\n let cancelled = false\n const checkOngoing = async () => {\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (!res.ok || cancelled) return\n const data: RegenerationProgress = await res.json()\n // Always store stats on mount\n setStats(data)\n if (data.pending > 0) {\n setProgress(data)\n setIsRunning(true)\n setStalled(false)\n setQueued(null)\n stallRef.current = { lastProcessed: data.complete + data.errored, stallCount: 0 }\n intervalRef.current = setInterval(pollProgress, 2000)\n }\n } catch {\n // ignore\n }\n }\n checkOngoing()\n return () => {\n cancelled = true\n }\n }, [collectionSlug, pollProgress])\n\n // Refresh stats when regeneration finishes (isRunning transitions from true to false)\n useEffect(() => {\n if (prevIsRunningRef.current && !isRunning) {\n fetchStats()\n }\n prevIsRunningRef.current = isRunning\n }, [isRunning, fetchStats])\n\n const handleRegenerate = async () => {\n if (!collectionSlug) return\n setError(null)\n setStalled(false)\n setIsRunning(true)\n setQueued(null)\n setProgress(null)\n stallRef.current = { lastProcessed: 0, stallCount: 0 }\n\n try {\n const res = await fetch('/api/image-optimizer/regenerate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ collectionSlug, force }),\n })\n\n if (!res.ok) {\n const data = await res.json()\n throw new Error(data.error || 'Failed to start regeneration')\n }\n\n const data = await res.json()\n setQueued(data.queued)\n\n if (data.queued === 0) {\n setIsRunning(false)\n return\n }\n\n // Start polling\n intervalRef.current = setInterval(pollProgress, 2000)\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err))\n setIsRunning(false)\n }\n }\n\n // Cleanup interval on unmount\n useEffect(() => {\n return () => {\n if (intervalRef.current) clearInterval(intervalRef.current)\n }\n }, [])\n\n if (!collectionSlug) return null\n\n const progressPercent =\n progress && progress.total > 0\n ? Math.round(((progress.complete + progress.errored) / progress.total) * 100)\n : 0\n\n const showProgressBar = (isRunning && progress) || (stalled && progress)\n\n // Stats computations\n const statsPercent =\n stats && stats.total > 0\n ? Math.round((stats.complete / stats.total) * 100)\n : 0\n const allOptimized = stats && stats.total > 0 && stats.complete === stats.total\n\n return (\n <div\n style={{\n padding: '16px 24px',\n borderBottom: '1px solid #e5e7eb',\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n flexWrap: 'wrap',\n }}\n >\n <button\n onClick={handleRegenerate}\n disabled={isRunning}\n style={{\n backgroundColor: isRunning ? '#9ca3af' : '#4f46e5',\n color: '#fff',\n border: 'none',\n borderRadius: '6px',\n padding: '8px 16px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: isRunning ? 'not-allowed' : 'pointer',\n }}\n >\n {isRunning ? 'Regenerating...' : 'Regenerate Images'}\n </button>\n\n <label\n style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' }}\n >\n <input\n type=\"checkbox\"\n checked={force}\n onChange={(e) => setForce(e.target.checked)}\n disabled={isRunning}\n />\n Force re-process all\n </label>\n\n {error && (\n <span style={{ color: '#ef4444', fontSize: '13px' }}>{error}</span>\n )}\n\n {queued === 0 && !isRunning && !stalled && (\n <span style={{ color: '#10b981', fontSize: '13px' }}>\n All images already optimized.\n </span>\n )}\n\n {stalled && progress && (\n <span style={{ color: '#f59e0b', fontSize: '13px' }}>\n Processing finished with issues. {progress.errored + progress.pending} image\n {progress.errored + progress.pending !== 1 ? 's' : ''} failed\n {progress.pending > 0 ? ` (${progress.pending} stuck)` : ''}.\n Re-run to retry.\n </span>\n )}\n\n {showProgressBar && (\n <div style={{ flex: 1, minWidth: '200px' }}>\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n fontSize: '12px',\n marginBottom: '4px',\n }}\n >\n <span>\n {progress.complete} / {progress.total} complete\n </span>\n {progress.errored > 0 && (\n <span style={{ color: '#ef4444' }}>{progress.errored} errors</span>\n )}\n <span>{progressPercent}%</span>\n </div>\n <div\n style={{\n height: '6px',\n backgroundColor: '#e5e7eb',\n borderRadius: '3px',\n overflow: 'hidden',\n display: 'flex',\n }}\n >\n <div\n style={{\n height: '100%',\n width: `${progress.total > 0 ? Math.round((progress.complete / progress.total) * 100) : 0}%`,\n backgroundColor: '#10b981',\n transition: 'width 0.3s ease',\n }}\n />\n {progress.errored > 0 && (\n <div\n style={{\n height: '100%',\n width: `${progress.total > 0 ? Math.round((progress.errored / progress.total) * 100) : 0}%`,\n backgroundColor: '#ef4444',\n transition: 'width 0.3s ease',\n }}\n />\n )}\n </div>\n </div>\n )}\n\n {!isRunning && progress && progress.complete > 0 && queued !== 0 && (\n <span style={{ fontSize: '13px' }}>\n <span style={{ color: progress.errored > 0 || stalled ? '#f59e0b' : '#10b981' }}>\n Done! {progress.complete}/{progress.total} optimized.\n </span>\n {(progress.errored > 0 || (stalled && progress.pending > 0)) && (\n <span style={{ color: '#ef4444' }}>\n {' '}{progress.errored + (stalled ? progress.pending : 0)} failed.\n </span>\n )}\n </span>\n )}\n\n {/* Persistent optimization stats — always visible when not actively regenerating */}\n {!isRunning && stats && stats.total > 0 && (\n <div\n style={{\n marginLeft: 'auto',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-end',\n gap: '4px',\n minWidth: '180px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }}>\n {allOptimized ? (\n <span style={{ color: '#10b981' }}>\n ✓ All {stats.total} images optimized\n </span>\n ) : (\n <>\n <span style={{ color: '#6b7280' }}>\n {stats.complete}/{stats.total} optimized\n </span>\n {stats.errored > 0 && (\n <>\n <span style={{ color: '#d1d5db' }}>·</span>\n <span style={{ color: '#ef4444' }}>{stats.errored} errors</span>\n </>\n )}\n </>\n )}\n </div>\n {!allOptimized && (\n <div\n style={{\n width: '100%',\n height: '3px',\n backgroundColor: '#e5e7eb',\n borderRadius: '2px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n height: '100%',\n width: `${statsPercent}%`,\n backgroundColor: stats.errored > 0 ? '#f59e0b' : '#10b981',\n borderRadius: '2px',\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],"names":["React","useState","useEffect","useCallback","useRef","STALL_THRESHOLD","RegenerationButton","isRunning","setIsRunning","progress","setProgress","queued","setQueued","force","setForce","error","setError","stalled","setStalled","collectionSlug","setCollectionSlug","stats","setStats","intervalRef","stallRef","lastProcessed","stallCount","prevIsRunningRef","slug","window","location","pathname","split","fetchStats","res","fetch","ok","data","json","stopPolling","current","clearInterval","pollProgress","pending","processed","complete","errored","cancelled","checkOngoing","setInterval","handleRegenerate","method","headers","body","JSON","stringify","Error","err","message","String","progressPercent","total","Math","round","showProgressBar","statsPercent","allOptimized","div","style","padding","borderBottom","display","alignItems","gap","flexWrap","button","onClick","disabled","backgroundColor","color","border","borderRadius","fontSize","fontWeight","cursor","label","input","type","checked","onChange","e","target","span","flex","minWidth","justifyContent","marginBottom","height","overflow","width","transition","marginLeft","flexDirection"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inoo-ch/payload-image-optimizer",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Payload CMS plugin for automatic image optimization — WebP/AVIF conversion, resize, EXIF strip, ThumbHash placeholders, and bulk regeneration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -24,28 +24,46 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
27
|
-
"import": "./
|
|
28
|
-
"types": "./
|
|
29
|
-
"default": "./
|
|
27
|
+
"import": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts",
|
|
29
|
+
"default": "./src/index.ts"
|
|
30
30
|
},
|
|
31
31
|
"./client": {
|
|
32
|
-
"import": "./
|
|
33
|
-
"types": "./
|
|
34
|
-
"default": "./
|
|
32
|
+
"import": "./src/exports/client.ts",
|
|
33
|
+
"types": "./src/exports/client.ts",
|
|
34
|
+
"default": "./src/exports/client.ts"
|
|
35
35
|
},
|
|
36
36
|
"./rsc": {
|
|
37
|
-
"import": "./
|
|
38
|
-
"types": "./
|
|
39
|
-
"default": "./
|
|
37
|
+
"import": "./src/exports/rsc.ts",
|
|
38
|
+
"types": "./src/exports/rsc.ts",
|
|
39
|
+
"default": "./src/exports/rsc.ts"
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
"main": "./
|
|
43
|
-
"types": "./
|
|
42
|
+
"main": "./src/index.ts",
|
|
43
|
+
"types": "./src/index.ts",
|
|
44
44
|
"files": [
|
|
45
45
|
"dist",
|
|
46
46
|
"src",
|
|
47
47
|
"AGENT_DOCS.md"
|
|
48
48
|
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
51
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
52
|
+
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
53
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
54
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
55
|
+
"dev": "next dev dev --turbo",
|
|
56
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
57
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
58
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
59
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
60
|
+
"generate:types": "pnpm dev:generate-types",
|
|
61
|
+
"lint": "eslint",
|
|
62
|
+
"lint:fix": "eslint ./src --fix",
|
|
63
|
+
"test": "pnpm test:int && pnpm test:e2e",
|
|
64
|
+
"test:e2e": "playwright test",
|
|
65
|
+
"test:int": "vitest"
|
|
66
|
+
},
|
|
49
67
|
"devDependencies": {
|
|
50
68
|
"@eslint/eslintrc": "^3.2.0",
|
|
51
69
|
"@payloadcms/db-mongodb": "3.79.0",
|
|
@@ -91,26 +109,36 @@
|
|
|
91
109
|
"node": "^18.20.2 || >=20.9.0",
|
|
92
110
|
"pnpm": "^9 || ^10"
|
|
93
111
|
},
|
|
112
|
+
"publishConfig": {
|
|
113
|
+
"exports": {
|
|
114
|
+
".": {
|
|
115
|
+
"import": "./dist/index.js",
|
|
116
|
+
"types": "./dist/index.d.ts",
|
|
117
|
+
"default": "./dist/index.js"
|
|
118
|
+
},
|
|
119
|
+
"./client": {
|
|
120
|
+
"import": "./dist/exports/client.js",
|
|
121
|
+
"types": "./dist/exports/client.d.ts",
|
|
122
|
+
"default": "./dist/exports/client.js"
|
|
123
|
+
},
|
|
124
|
+
"./rsc": {
|
|
125
|
+
"import": "./dist/exports/rsc.js",
|
|
126
|
+
"types": "./dist/exports/rsc.d.ts",
|
|
127
|
+
"default": "./dist/exports/rsc.js"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"main": "./dist/index.js",
|
|
131
|
+
"types": "./dist/index.d.ts"
|
|
132
|
+
},
|
|
133
|
+
"pnpm": {
|
|
134
|
+
"onlyBuiltDependencies": [
|
|
135
|
+
"sharp",
|
|
136
|
+
"esbuild",
|
|
137
|
+
"unrs-resolver"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
94
140
|
"registry": "https://registry.npmjs.org/",
|
|
95
141
|
"dependencies": {
|
|
96
142
|
"thumbhash": "^0.1.1"
|
|
97
|
-
},
|
|
98
|
-
"scripts": {
|
|
99
|
-
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
100
|
-
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
101
|
-
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
102
|
-
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
103
|
-
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
104
|
-
"dev": "next dev dev --turbo",
|
|
105
|
-
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
106
|
-
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
107
|
-
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
108
|
-
"generate:importmap": "pnpm dev:generate-importmap",
|
|
109
|
-
"generate:types": "pnpm dev:generate-types",
|
|
110
|
-
"lint": "eslint",
|
|
111
|
-
"lint:fix": "eslint ./src --fix",
|
|
112
|
-
"test": "pnpm test:int && pnpm test:e2e",
|
|
113
|
-
"test:e2e": "playwright test",
|
|
114
|
-
"test:int": "vitest"
|
|
115
143
|
}
|
|
116
|
-
}
|
|
144
|
+
}
|
|
@@ -177,7 +177,7 @@ export const RegenerationButton: React.FC = () => {
|
|
|
177
177
|
|
|
178
178
|
const progressPercent =
|
|
179
179
|
progress && progress.total > 0
|
|
180
|
-
? Math.round((progress.complete / progress.total) * 100)
|
|
180
|
+
? Math.round(((progress.complete + progress.errored) / progress.total) * 100)
|
|
181
181
|
: 0
|
|
182
182
|
|
|
183
183
|
const showProgressBar = (isRunning && progress) || (stalled && progress)
|
|
@@ -241,7 +241,10 @@ export const RegenerationButton: React.FC = () => {
|
|
|
241
241
|
|
|
242
242
|
{stalled && progress && (
|
|
243
243
|
<span style={{ color: '#f59e0b', fontSize: '13px' }}>
|
|
244
|
-
|
|
244
|
+
Processing finished with issues. {progress.errored + progress.pending} image
|
|
245
|
+
{progress.errored + progress.pending !== 1 ? 's' : ''} failed
|
|
246
|
+
{progress.pending > 0 ? ` (${progress.pending} stuck)` : ''}.
|
|
247
|
+
Re-run to retry.
|
|
245
248
|
</span>
|
|
246
249
|
)}
|
|
247
250
|
|
|
@@ -269,29 +272,39 @@ export const RegenerationButton: React.FC = () => {
|
|
|
269
272
|
backgroundColor: '#e5e7eb',
|
|
270
273
|
borderRadius: '3px',
|
|
271
274
|
overflow: 'hidden',
|
|
275
|
+
display: 'flex',
|
|
272
276
|
}}
|
|
273
277
|
>
|
|
274
278
|
<div
|
|
275
279
|
style={{
|
|
276
280
|
height: '100%',
|
|
277
|
-
width: `${
|
|
278
|
-
backgroundColor:
|
|
279
|
-
borderRadius: '3px',
|
|
281
|
+
width: `${progress.total > 0 ? Math.round((progress.complete / progress.total) * 100) : 0}%`,
|
|
282
|
+
backgroundColor: '#10b981',
|
|
280
283
|
transition: 'width 0.3s ease',
|
|
281
284
|
}}
|
|
282
285
|
/>
|
|
286
|
+
{progress.errored > 0 && (
|
|
287
|
+
<div
|
|
288
|
+
style={{
|
|
289
|
+
height: '100%',
|
|
290
|
+
width: `${progress.total > 0 ? Math.round((progress.errored / progress.total) * 100) : 0}%`,
|
|
291
|
+
backgroundColor: '#ef4444',
|
|
292
|
+
transition: 'width 0.3s ease',
|
|
293
|
+
}}
|
|
294
|
+
/>
|
|
295
|
+
)}
|
|
283
296
|
</div>
|
|
284
297
|
</div>
|
|
285
298
|
)}
|
|
286
299
|
|
|
287
|
-
{!isRunning &&
|
|
300
|
+
{!isRunning && progress && progress.complete > 0 && queued !== 0 && (
|
|
288
301
|
<span style={{ fontSize: '13px' }}>
|
|
289
|
-
<span style={{ color: '#10b981' }}>
|
|
302
|
+
<span style={{ color: progress.errored > 0 || stalled ? '#f59e0b' : '#10b981' }}>
|
|
290
303
|
Done! {progress.complete}/{progress.total} optimized.
|
|
291
304
|
</span>
|
|
292
|
-
{progress.errored > 0 && (
|
|
305
|
+
{(progress.errored > 0 || (stalled && progress.pending > 0)) && (
|
|
293
306
|
<span style={{ color: '#ef4444' }}>
|
|
294
|
-
{' '}{progress.errored} failed.
|
|
307
|
+
{' '}{progress.errored + (stalled ? progress.pending : 0)} failed.
|
|
295
308
|
</span>
|
|
296
309
|
)}
|
|
297
310
|
</span>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { type ImageProps } from 'next/image';
|
|
3
|
-
import type { MediaResource } from '../types.js';
|
|
4
|
-
export interface ImageBoxProps extends Omit<ImageProps, 'src' | 'alt'> {
|
|
5
|
-
media: MediaResource | string;
|
|
6
|
-
alt?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare const ImageBox: React.FC<ImageBoxProps>;
|
package/dist/defaults.d.ts
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { ImageOptimizerConfig, ResolvedCollectionOptimizerConfig, ResolvedImageOptimizerConfig } from './types.js';
|
|
2
|
-
export declare const resolveConfig: (config: ImageOptimizerConfig) => ResolvedImageOptimizerConfig;
|
|
3
|
-
export declare const resolveCollectionConfig: (resolvedConfig: ResolvedImageOptimizerConfig, collectionSlug: string) => ResolvedCollectionOptimizerConfig;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { PayloadHandler } from 'payload';
|
|
2
|
-
import type { ResolvedImageOptimizerConfig } from '../types.js';
|
|
3
|
-
export declare const createRegenerateHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => PayloadHandler;
|
|
4
|
-
export declare const createRegenerateStatusHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => PayloadHandler;
|
package/dist/exports/client.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { OptimizationStatus } from '../components/OptimizationStatus.js';
|
|
2
|
-
export { ImageBox } from '../components/ImageBox.js';
|
|
3
|
-
export type { ImageBoxProps } from '../components/ImageBox.js';
|
|
4
|
-
export { getImageOptimizerProps } from '../utilities/getImageOptimizerProps.js';
|
|
5
|
-
export type { ImageOptimizerProps } from '../utilities/getImageOptimizerProps.js';
|
|
6
|
-
export { RegenerationButton } from '../components/RegenerationButton.js';
|
package/dist/exports/rsc.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { Config } from 'payload';
|
|
2
|
-
import type { ImageOptimizerConfig } from './types.js';
|
|
3
|
-
export type { ImageOptimizerConfig, ImageFormat, FormatQuality, CollectionOptimizerConfig, ImageOptimizerData, MediaResource, FieldsOverride } from './types.js';
|
|
4
|
-
export { defaultImageOptimizerFields } from './fields/imageOptimizerField.js';
|
|
5
|
-
export { encodeImageToThumbHash, decodeThumbHashToDataURL } from './utilities/thumbhash.js';
|
|
6
|
-
export declare const imageOptimizer: (pluginOptions: ImageOptimizerConfig) => (config: Config) => Config;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export declare function stripAndResize(buffer: Buffer, maxDimensions: {
|
|
2
|
-
width: number;
|
|
3
|
-
height: number;
|
|
4
|
-
}, stripMetadata: boolean): Promise<{
|
|
5
|
-
buffer: Buffer;
|
|
6
|
-
width: number;
|
|
7
|
-
height: number;
|
|
8
|
-
size: number;
|
|
9
|
-
}>;
|
|
10
|
-
export declare function generateThumbHash(buffer: Buffer): Promise<string>;
|
|
11
|
-
export declare function convertFormat(buffer: Buffer, format: 'webp' | 'avif', quality: number): Promise<{
|
|
12
|
-
buffer: Buffer;
|
|
13
|
-
width: number;
|
|
14
|
-
height: number;
|
|
15
|
-
size: number;
|
|
16
|
-
mimeType: string;
|
|
17
|
-
}>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ResolvedImageOptimizerConfig } from '../types.js';
|
|
2
|
-
export declare const createConvertFormatsHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => ({ input, req }: {
|
|
3
|
-
input: {
|
|
4
|
-
collectionSlug: string;
|
|
5
|
-
docId: string;
|
|
6
|
-
};
|
|
7
|
-
req: any;
|
|
8
|
-
}) => Promise<{
|
|
9
|
-
output: {
|
|
10
|
-
variantsGenerated: number;
|
|
11
|
-
};
|
|
12
|
-
}>;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ResolvedImageOptimizerConfig } from '../types.js';
|
|
2
|
-
export declare const createRegenerateDocumentHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => ({ input, req }: {
|
|
3
|
-
input: {
|
|
4
|
-
collectionSlug: string;
|
|
5
|
-
docId: string;
|
|
6
|
-
};
|
|
7
|
-
req: any;
|
|
8
|
-
}) => Promise<{
|
|
9
|
-
output: {
|
|
10
|
-
status: string;
|
|
11
|
-
reason: string;
|
|
12
|
-
};
|
|
13
|
-
} | {
|
|
14
|
-
output: {
|
|
15
|
-
status: string;
|
|
16
|
-
reason?: undefined;
|
|
17
|
-
};
|
|
18
|
-
}>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const translations: Record<string, Record<string, Record<string, string>>>;
|
package/dist/types.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { CollectionSlug, Field } from 'payload';
|
|
2
|
-
export type ImageFormat = 'webp' | 'avif';
|
|
3
|
-
export type FormatQuality = {
|
|
4
|
-
format: ImageFormat;
|
|
5
|
-
quality: number;
|
|
6
|
-
};
|
|
7
|
-
export type CollectionOptimizerConfig = {
|
|
8
|
-
formats?: FormatQuality[];
|
|
9
|
-
maxDimensions?: {
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
};
|
|
13
|
-
replaceOriginal?: boolean;
|
|
14
|
-
};
|
|
15
|
-
export type FieldsOverride = (args: {
|
|
16
|
-
defaultFields: Field[];
|
|
17
|
-
}) => Field[];
|
|
18
|
-
export type ImageOptimizerConfig = {
|
|
19
|
-
collections: Partial<Record<CollectionSlug, true | CollectionOptimizerConfig>>;
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
fieldsOverride?: FieldsOverride;
|
|
22
|
-
formats?: FormatQuality[];
|
|
23
|
-
generateThumbHash?: boolean;
|
|
24
|
-
maxDimensions?: {
|
|
25
|
-
width: number;
|
|
26
|
-
height: number;
|
|
27
|
-
};
|
|
28
|
-
replaceOriginal?: boolean;
|
|
29
|
-
stripMetadata?: boolean;
|
|
30
|
-
};
|
|
31
|
-
export type ResolvedCollectionOptimizerConfig = {
|
|
32
|
-
formats: FormatQuality[];
|
|
33
|
-
maxDimensions: {
|
|
34
|
-
width: number;
|
|
35
|
-
height: number;
|
|
36
|
-
};
|
|
37
|
-
replaceOriginal: boolean;
|
|
38
|
-
};
|
|
39
|
-
export type ResolvedImageOptimizerConfig = Required<Pick<ImageOptimizerConfig, 'formats' | 'generateThumbHash' | 'maxDimensions' | 'stripMetadata'>> & {
|
|
40
|
-
collections: ImageOptimizerConfig['collections'];
|
|
41
|
-
disabled: boolean;
|
|
42
|
-
replaceOriginal: boolean;
|
|
43
|
-
};
|
|
44
|
-
export type ImageOptimizerData = {
|
|
45
|
-
thumbHash?: string | null;
|
|
46
|
-
};
|
|
47
|
-
export type MediaResource = {
|
|
48
|
-
url?: string | null;
|
|
49
|
-
alt?: string | null;
|
|
50
|
-
width?: number | null;
|
|
51
|
-
height?: number | null;
|
|
52
|
-
filename?: string | null;
|
|
53
|
-
focalX?: number | null;
|
|
54
|
-
focalY?: number | null;
|
|
55
|
-
imageOptimizer?: ImageOptimizerData | null;
|
|
56
|
-
updatedAt?: string;
|
|
57
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { MediaResource } from '../types.js';
|
|
2
|
-
export type ImageOptimizerProps = {
|
|
3
|
-
placeholder: 'blur' | 'empty';
|
|
4
|
-
blurDataURL?: string;
|
|
5
|
-
style: {
|
|
6
|
-
objectPosition: string;
|
|
7
|
-
};
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Extracts image optimization props from a Payload media resource.
|
|
11
|
-
*
|
|
12
|
-
* Returns props that can be spread onto a Next.js `<Image>` component to add
|
|
13
|
-
* ThumbHash blur placeholders and focal-point-based object positioning.
|
|
14
|
-
*
|
|
15
|
-
* Works with any component — including the Payload website template's `ImageMedia`:
|
|
16
|
-
*
|
|
17
|
-
* ```tsx
|
|
18
|
-
* import { getImageOptimizerProps } from '@inoo-ch/payload-image-optimizer/client'
|
|
19
|
-
*
|
|
20
|
-
* const optimizerProps = getImageOptimizerProps(resource)
|
|
21
|
-
* <NextImage {...existingProps} {...optimizerProps} />
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export declare function getImageOptimizerProps(resource: MediaResource | null | undefined): ImageOptimizerProps;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns true when the collection uses cloud/external storage (disableLocalStorage: true).
|
|
3
|
-
* When true, files are uploaded by external adapter hooks — no local FS writes should happen.
|
|
4
|
-
*/
|
|
5
|
-
export declare function isCloudStorage(collectionConfig: {
|
|
6
|
-
upload?: boolean | Record<string, any>;
|
|
7
|
-
}): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Reads a file buffer from local disk or fetches it from URL.
|
|
10
|
-
* Tries local disk first (when available), falls back to URL fetch.
|
|
11
|
-
* This makes the plugin storage-agnostic — works with local FS and cloud storage alike.
|
|
12
|
-
*/
|
|
13
|
-
export declare function fetchFileBuffer(doc: {
|
|
14
|
-
filename?: string;
|
|
15
|
-
url?: string;
|
|
16
|
-
}, collectionConfig: {
|
|
17
|
-
upload?: boolean | Record<string, any>;
|
|
18
|
-
}): Promise<Buffer>;
|