mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(stats): save reading history share images
Wait for the canvas export callback before starting the download so the browser can complete the PNG export. Closes #1967
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { ReadingHistoryItem } from '@bookorbit/types'
|
||||
import { filtersFromQuery, queryFromFilters } from '../composables/useReadingHistoryFilters'
|
||||
import { exportFileName, toCsv, toMarkdown } from '../lib/history-export'
|
||||
import { canvasToBlob, exportFileName, toCsv, toMarkdown } from '../lib/history-export'
|
||||
import { readSpanDays } from '../lib/history-format'
|
||||
import { historyRouteForChartClick } from '../lib/chart-month-link'
|
||||
import { fitTiles, withAlpha } from '../lib/share-poster'
|
||||
@@ -117,6 +117,24 @@ describe('reading history export', () => {
|
||||
expect(exportFileName('Q3 2026', 'md')).toBe('reading-history-q3-2026.md')
|
||||
expect(exportFileName('All time', 'csv')).toBe('reading-history-all-time.csv')
|
||||
})
|
||||
|
||||
it('waits for the canvas export callback when toBlob returns undefined', async () => {
|
||||
const blob = new Blob(['poster'], { type: 'image/png' })
|
||||
const canvas = {
|
||||
toBlob(callback: BlobCallback) {
|
||||
queueMicrotask(() => callback(blob))
|
||||
},
|
||||
} as HTMLCanvasElement
|
||||
|
||||
await expect(canvasToBlob(canvas, 'image/png')).resolves.toBe(blob)
|
||||
})
|
||||
|
||||
it('rejects when the canvas is unavailable or produces no data', async () => {
|
||||
const canvas = { toBlob: (callback: BlobCallback) => callback(null) } as HTMLCanvasElement
|
||||
|
||||
await expect(canvasToBlob(null, 'image/png')).rejects.toThrow('Canvas is unavailable')
|
||||
await expect(canvasToBlob(canvas, 'image/png')).rejects.toThrow('Canvas export returned no data')
|
||||
})
|
||||
})
|
||||
|
||||
describe('reading history helpers', () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useAuth } from '@/features/auth/composables/useAuth'
|
||||
import { useCoverVersions } from '@/features/book/composables/useCoverVersions'
|
||||
import { fetchReadingHistoryPage, fetchReadingHistorySummary } from '../api/reading-history.api'
|
||||
import { formatHours, formatMonthName } from '../lib/history-format'
|
||||
import { saveBlob } from '../lib/history-export'
|
||||
import { canvasToBlob, saveBlob } from '../lib/history-export'
|
||||
import { POSTER_SIZES, drawPoster, type PosterLook, type PosterShape, type PosterTile } from '../lib/share-poster'
|
||||
|
||||
type SharePeriod = 'year' | 'quarter' | 'month'
|
||||
@@ -240,10 +240,7 @@ watch(books, (list) => {
|
||||
watch([books, shape, look, showTitles, showRereads, showRatings, imageVersion, canvas, loading], () => void nextTick(scheduleDraw))
|
||||
|
||||
function posterBlob(): Promise<Blob> {
|
||||
return new Promise(
|
||||
(resolve, reject) =>
|
||||
canvas.value?.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('empty'))), 'image/png') ?? reject(new Error('no canvas')),
|
||||
)
|
||||
return canvasToBlob(canvas.value, 'image/png')
|
||||
}
|
||||
|
||||
function fileName() {
|
||||
|
||||
@@ -123,3 +123,11 @@ export function saveBlob(blob: Blob, fileName: string) {
|
||||
link.remove()
|
||||
setTimeout(() => URL.revokeObjectURL(url), 30_000)
|
||||
}
|
||||
|
||||
export function canvasToBlob(canvas: HTMLCanvasElement | null, type: string): Promise<Blob> {
|
||||
if (!canvas) return Promise.reject(new Error('Canvas is unavailable'))
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('Canvas export returned no data'))), type)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user