Add “download image” option to web, simplify downloads (#10025)

This commit is contained in:
Samuel Newman
2026-03-11 23:44:19 +02:00
committed by GitHub
parent 0c7b2d5353
commit 80429ec902
5 changed files with 108 additions and 18 deletions
+14 -7
View File
@@ -22,6 +22,7 @@ import {logger} from '#/logger'
import {IS_ANDROID, IS_IOS} from '#/env'
import {type PickerImage} from './picker.shared'
import {type Dimensions} from './types'
import {convertCdnPreset} from './util'
export async function compressIfNeeded(
img: PickerImage,
@@ -94,14 +95,20 @@ export async function shareImageModal({uri}: {uri: string}) {
const ALBUM_NAME = 'Bluesky'
/**
* Saves an image to the user's device. Uses the CDN's `download` preset
* which uses the JPEG version with the Content-Disposition header set to
* `attachment; filename=<filename>`. On native this saves to the media library;
* on web it triggers a browser download.
*/
export async function saveImageToMediaLibrary({uri}: {uri: string}) {
const downloadedPath = await downloadImage(uri, String(uuid.v4()), 15e3)
const {uri: jpegUri} = await manipulateAsync(downloadedPath, [], {
format: SaveFormat.JPEG,
compress: 1.0,
})
void safeDeleteAsync(downloadedPath)
const imagePath = await moveToPermanentPath(jpegUri, '.jpg')
const downloadUri = convertCdnPreset(uri, 'download')
const downloadedPath = await downloadImage(
downloadUri,
String(uuid.v4()),
20e3,
)
const imagePath = await moveToPermanentPath(downloadedPath, '.jpg')
// save
try {
+17 -8
View File
@@ -1,8 +1,6 @@
/// <reference lib="dom" />
import {type PickerImage} from './picker.shared'
import {type Dimensions} from './types'
import {blobToDataUri, getDataUriSize} from './util'
import {blobToDataUri, convertCdnPreset, getDataUriSize} from './util'
export async function compressIfNeeded(
img: PickerImage,
@@ -44,9 +42,17 @@ export async function shareImageModal(_opts: {uri: string}) {
throw new Error('TODO')
}
export async function saveImageToMediaLibrary(_opts: {uri: string}) {
// TODO
throw new Error('TODO')
/**
* Saves an image to the user's device. Uses the CDN's `download` preset
* which uses the JPEG version with the Content-Disposition header set to
* `attachment; filename=<filename>`. On native this saves to the media library;
* on web it triggers a browser download.
*/
export async function saveImageToMediaLibrary({uri}: {uri: string}) {
const downloadUri = convertCdnPreset(uri, 'download')
const segments = downloadUri.split('/')
const filename = `bluesky-${segments.at(-1)}.jpg`
downloadUrl(downloadUri, filename)
}
export async function getImageDim(path: string): Promise<Dimensions> {
@@ -162,17 +168,20 @@ export async function saveBytesToDisk(
) {
const blob = new Blob([bytes], {type})
const url = URL.createObjectURL(blob)
await downloadUrl(url, filename)
downloadUrl(url, filename)
// Firefox requires a small delay
setTimeout(() => URL.revokeObjectURL(url), 100)
return true
}
async function downloadUrl(href: string, filename: string) {
function downloadUrl(href: string, filename: string) {
const a = document.createElement('a')
a.href = href
a.download = filename
a.style.display = 'none'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
export async function safeDeleteAsync() {
+19
View File
@@ -26,3 +26,22 @@ export function blobToDataUri(blob: Blob): Promise<string> {
reader.readAsDataURL(blob)
})
}
export type ImgproxyPreset =
| 'default'
| 'avatar_thumbnail'
| 'avatar'
| 'banner'
| 'feed_fullsize'
| 'feed_thumbnail'
| 'download'
const IMGPROXY_PRESET_RE =
/(?<=\/img\/)(default|avatar_thumbnail|avatar|banner|feed_fullsize|feed_thumbnail|download)(?=\/)/
/**
* Replaces any imgproxy preset in a CDN URI with the given preset.
*/
export function convertCdnPreset(uri: string, preset: ImgproxyPreset): string {
return uri.replace(IMGPROXY_PRESET_RE, preset)
}