feat: low-res webp thumbnails for library previews
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
@@ -66,7 +66,7 @@ export default function LibraryApp() {
|
|||||||
{items.map((v) => (
|
{items.map((v) => (
|
||||||
<article key={v.id} className={`kachel ${sel.has(v.id) ? 'gewaehlt' : ''}`}>
|
<article key={v.id} className={`kachel ${sel.has(v.id) ? 'gewaehlt' : ''}`}>
|
||||||
<div className="kachel-bild" onClick={() => toggle(v.id)}>
|
<div className="kachel-bild" onClick={() => toggle(v.id)}>
|
||||||
<img src={`/api/items/${v.id}/file`} alt="" loading="lazy" />
|
<img src={`/api/items/${v.id}/file?thumb=1`} alt="" loading="lazy" />
|
||||||
<span className="haken">{sel.has(v.id) ? '✓' : ''}</span>
|
<span className="haken">{sel.has(v.id) ? '✓' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<input className="name" defaultValue={v.filename || ''} onBlur={(e) => rename(v.id, e.target.value)} />
|
<input className="name" defaultValue={v.filename || ''} onBlur={(e) => rename(v.id, e.target.value)} />
|
||||||
|
|||||||
@@ -7,15 +7,23 @@ export const prerender = false;
|
|||||||
// Streamt das Ergebnis- (oder Quell-)Bild aus dem Objektspeicher.
|
// Streamt das Ergebnis- (oder Quell-)Bild aus dem Objektspeicher.
|
||||||
export const GET: APIRoute = async ({ params, url, locals }) => {
|
export const GET: APIRoute = async ({ params, url, locals }) => {
|
||||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||||
const which = url.searchParams.get('src') === '1' ? 'source_path' : 'result_path';
|
const q = url.searchParams;
|
||||||
const item = await one<any>(`SELECT ${which} AS path, filename, has_alpha FROM items WHERE id=$1`, [params.id]);
|
const wantThumb = q.get('thumb') === '1';
|
||||||
if (!item?.path) return new Response('Nicht gefunden', { status: 404 });
|
const wantSrc = q.get('src') === '1';
|
||||||
|
const item = await one<any>(
|
||||||
|
`SELECT source_path, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]);
|
||||||
|
// Vorschau: Thumbnail bevorzugen, sonst Ergebnis. Quelle nur explizit.
|
||||||
|
const path = wantSrc ? item?.source_path
|
||||||
|
: wantThumb ? (item?.thumb_path || item?.result_path)
|
||||||
|
: item?.result_path;
|
||||||
|
if (!path) return new Response('Nicht gefunden', { status: 404 });
|
||||||
try {
|
try {
|
||||||
const buf = await getObject(item.path);
|
const buf = await getObject(path);
|
||||||
const download = url.searchParams.get('download') === '1';
|
const download = q.get('download') === '1';
|
||||||
|
const isWebp = path.endsWith('.webp');
|
||||||
return new Response(buf, {
|
return new Response(buf, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': item.has_alpha ? 'image/png' : 'image/png',
|
'Content-Type': isWebp ? 'image/webp' : 'image/png',
|
||||||
'Cache-Control': 'private, max-age=300',
|
'Cache-Control': 'private, max-age=300',
|
||||||
...(download ? { 'Content-Disposition': `attachment; filename="${item.filename || 'klarbild.png'}"` } : {}),
|
...(download ? { 'Content-Disposition': `attachment; filename="${item.filename || 'klarbild.png'}"` } : {}),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user