Continue Working widget shows pixelated thumbnails

Aperta
#536 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
javascript
Ambito
frontend

Direzione di ricerca

Inizia in components/continue-working/index.js, in getProjectThumbnail(), e verifica come vengono risolte le miniature dei canvas IIIF e i servizi di immagini per Presentation 2 e 3. Esegui jekyll s, accedi e visita / per verificare miniature nitide di circa 200 CSS px, richieste IIIF da 400 px e l’assenza di 404 dovuti a URL malformati nel Network panel.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug component

Summary

The "Continue Working" widget on the home page renders project thumbnails at very low resolution. Images appear visibly pixelated, especially on HiDPI displays.

Reproduction

  1. Sign in and visit the home page (/).
  2. Observe the project thumbnails in the "Continue Working" card.
  3. Thumbnails render blurry/pixelated rather than crisp at the ~200 CSS px display size.

Reproduced against the Italian Paleography manuscript hosted on iiif.library.utoronto.ca (IIIF Image API 2 / Presentation API 2.1).

Root cause

In components/continue-working/index.js → getProjectThumbnail(), two stacked bugs combine to deliver a tiny image:

Bug 1 — canvas.thumbnail trusted blindly without checking declared size

Line 194:

let thumbnailUrl = canvas.thumbnail?.id ?? canvas.thumbnail?.['@id'] ?? canvas.thumbnail
if (!thumbnailUrl) {
    // …construct a sized URL from the IIIF service…
}

For the Toronto manifest the canvas advertises:

"thumbnail": {
  "@id": ".../full/80,/0/default.jpg",
  "width": 80, "height": 120
}

So thumbnailUrl is set to an 80-pixel-wide image and the entire fallback path is skipped. The widget displays it in a flex: 1 1 200px; aspect-ratio: 1; object-fit: cover section → stretched from 80 → ~200 CSS px (2.5×, worse on HiDPI). That's the pixelation.

The IIIF Presentation spec lets canvas.thumbnail be any size — it's metadata, not a sizing constraint. Trusting it without checking declared width/height is the bug.

Bug 2 — Latent: malformed URLs in the fallback path

Even if canvas.thumbnail were missing, the constructed fallback URLs are malformed. Lines 209–213 build:

`${baseUrl}/full/${isV3 ? 'max' : 'full'}/200,/0/default.jpg`

That produces e.g. …/full/full/200,/0/default.jpg — three size-related segments where IIIF wants region/size/rotation. Verified against the Toronto server:

GET …/full/full/200,/0/default.jpg → 404
GET …/full/200,/0/default.jpg      → 200

The ${isV3 ? 'max' : 'full'}/ insert stuffs a size token in front of the actual size 200,, pushing rotation/quality into invalid path positions. Both the square and full attempts 404, hitting the final .catch(() => thumbnailUrl = imageUrl) — which on Toronto is …/full/512,/0/default.jpg (still small).

Bug 3 — Latent: thumbnail as array not handled

IIIF Presentation 3.0 §5.3.1 requires thumbnail to be an array of objects. The current canvas.thumbnail?.id ?? canvas.thumbnail?.['@id'] chain returns undefined for arrays and falls through to canvas.thumbnail itself (the array) as the URL — which renders as [object Object] in the <img src>.

Suggested fix

Always derive a sized thumbnail from the IIIF Image service when one is available; only fall back to canvas.thumbnail when its declared width is sufficient; correctly construct the IIIF URL.

async getProjectThumbnail(project, annotationPageId) {
    try {
        if (!annotationPageId) return this.generateProjectPlaceholder(project)
        const annotationPage = await vault.get(
            `${TPEN.servicesURL}/project/${project._id}/page/${annotationPageId}`,
            'annotationpage', true
        )
        if (!annotationPage?.target) return this.generateProjectPlaceholder(project)
        const canvas = await vault.getWithFallback(annotationPage.target, 'canvas', project.manifest)
        if (!canvas) return this.generateProjectPlaceholder(project)

        const isV3 = Array.isArray(canvas.items) || canvas.type === "Canvas"
        const annotation = isV3 ? canvas.items?.[0]?.items?.[0] : canvas.images?.[0]
        const rawService = isV3 ? annotation?.body?.service : annotation?.resource?.service
        const service = Array.isArray(rawService) ? rawService[0] : rawService
        const serviceBase = service?.id ?? service?.['@id']

        // Prefer service-derived sized thumbnail (sharp at ~200 CSS px on HiDPI)
        if (serviceBase) {
            const base = serviceBase.replace(/\/$/, '')
            return `${base}/full/400,/0/default.jpg`
        }

        // canvas.thumbnail can be array (P3) or object/string (P2/P3)
        const t = Array.isArray(canvas.thumbnail) ? canvas.thumbnail[0] : canvas.thumbnail
        const tWidth = t?.width ?? 0
        if (tWidth >= 200) {
            return t?.id ?? t?.['@id'] ?? (typeof t === 'string' ? t : null) ?? this.generateProjectPlaceholder(project)
        }

        // Last resort — the painting body's image URL
        const imageUrl = isV3
            ? annotation?.body?.id ?? annotation?.body?.['@id']
            : annotation?.resource?.['@id'] ?? annotation?.resource?.id
        return imageUrl ?? this.generateProjectPlaceholder(project)
    } catch (error) {
        console.error('Error getting thumbnail:', error)
        return this.generateProjectPlaceholder(project)
    }
}

What this changes:

  • Always asks the IIIF service for a 400 px-wide rendition first (sharp on HiDPI 2× displays at 200 CSS px).
  • Only uses canvas.thumbnail when no service is available and the declared thumbnail is at least 200 wide.
  • Drops the broken square/max-prefix attempt chain.
  • Handles thumbnail as an array (Presentation 3.0 shape).

Verification

  1. jekyll s, sign in, visit /.
  2. Confirm "Continue Working" thumbnails render crisply (no visible pixelation) at the ~200 CSS px display size.
  3. Confirm Network panel shows …/full/400,/0/default.jpg requests, no 404s on …/full/full/200,/… or …/square/full/200,/….
Lingua principale
JavaScript
Stelle
2
Fork
3
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di CenterForDigitalHumanities/TPEN-interfaces

Tutte le issue di CenterForDigitalHumanities/TPEN-interfaces

Issue simili

Altre issue su JavaScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.