get image aspect ratio when restoring
This commit is contained in:
@@ -461,7 +461,7 @@ export const ComposePost = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Convert server draft to composer posts (videos returned separately)
|
// Convert server draft to composer posts (videos returned separately)
|
||||||
const {posts, restoredVideos} = draftToComposerPosts(
|
const {posts, restoredVideos} = await draftToComposerPosts(
|
||||||
draftSummary.draft,
|
draftSummary.draft,
|
||||||
loadedMedia,
|
loadedMedia,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import {type AppBskyDraftDefs, RichText} from '@atproto/api'
|
import {type AppBskyDraftDefs, RichText} from '@atproto/api'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
|
import {getImageDim} from '#/lib/media/manip'
|
||||||
import {type ComposerImage} from '#/state/gallery'
|
import {type ComposerImage} from '#/state/gallery'
|
||||||
import {type Gif} from '#/state/queries/tenor'
|
import {type Gif} from '#/state/queries/tenor'
|
||||||
import {
|
import {
|
||||||
@@ -399,146 +400,170 @@ function parseGifFromUrl(
|
|||||||
* the compression and upload pipeline. The caller should handle the restoredVideos
|
* the compression and upload pipeline. The caller should handle the restoredVideos
|
||||||
* by initiating video processing for each entry.
|
* by initiating video processing for each entry.
|
||||||
*/
|
*/
|
||||||
export function draftToComposerPosts(
|
export async function draftToComposerPosts(
|
||||||
draft: AppBskyDraftDefs.Draft,
|
draft: AppBskyDraftDefs.Draft,
|
||||||
loadedMedia: Map<string, string>,
|
loadedMedia: Map<string, string>,
|
||||||
): {posts: PostDraft[]; restoredVideos: Map<number, RestoredVideo>} {
|
): Promise<{posts: PostDraft[]; restoredVideos: Map<number, RestoredVideo>}> {
|
||||||
const restoredVideos = new Map<number, RestoredVideo>()
|
const restoredVideos = new Map<number, RestoredVideo>()
|
||||||
|
|
||||||
const posts = draft.posts.map((post, index) => {
|
const posts = await Promise.all(
|
||||||
const richtext = new RichText({text: post.text || ''})
|
draft.posts.map(async (post, index) => {
|
||||||
richtext.detectFacetsWithoutResolution()
|
const richtext = new RichText({text: post.text || ''})
|
||||||
|
richtext.detectFacetsWithoutResolution()
|
||||||
|
|
||||||
const embed: EmbedDraft = {
|
const embed: EmbedDraft = {
|
||||||
quote: undefined,
|
quote: undefined,
|
||||||
link: undefined,
|
link: undefined,
|
||||||
media: undefined,
|
media: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore images
|
||||||
|
if (post.embedImages && post.embedImages.length > 0) {
|
||||||
|
const imagePromises = post.embedImages.map(async img => {
|
||||||
|
const path = loadedMedia.get(img.localRef.path)
|
||||||
|
if (!path) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = 0
|
||||||
|
let height = 0
|
||||||
|
try {
|
||||||
|
const dims = await getImageDim(path)
|
||||||
|
width = dims.width
|
||||||
|
height = dims.height
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to get image dimensions', {
|
||||||
|
path,
|
||||||
|
error: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Restore images
|
|
||||||
if (post.embedImages && post.embedImages.length > 0) {
|
|
||||||
const images: ComposerImage[] = []
|
|
||||||
for (const img of post.embedImages) {
|
|
||||||
const path = loadedMedia.get(img.localRef.path)
|
|
||||||
if (path) {
|
|
||||||
logger.debug('restoring image with localRefPath', {
|
logger.debug('restoring image with localRefPath', {
|
||||||
localRefPath: img.localRef.path,
|
localRefPath: img.localRef.path,
|
||||||
loadedPath: path,
|
loadedPath: path,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
})
|
})
|
||||||
images.push({
|
|
||||||
|
return {
|
||||||
alt: img.alt || '',
|
alt: img.alt || '',
|
||||||
// Preserve the original localRefPath for reuse when saving
|
// Preserve the original localRefPath for reuse when saving
|
||||||
localRefPath: img.localRef.path,
|
localRefPath: img.localRef.path,
|
||||||
source: {
|
source: {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
path,
|
path,
|
||||||
width: 0, // Will be recalculated when loaded
|
width,
|
||||||
height: 0,
|
height,
|
||||||
mime: 'image/jpeg', // Default, will be detected
|
mime: 'image/jpeg',
|
||||||
},
|
},
|
||||||
|
} as ComposerImage
|
||||||
|
})
|
||||||
|
|
||||||
|
const images = (await Promise.all(imagePromises)).filter(
|
||||||
|
(img): img is ComposerImage => img !== null,
|
||||||
|
)
|
||||||
|
if (images.length > 0) {
|
||||||
|
embed.media = {type: 'images', images}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore GIF from external embed
|
||||||
|
if (post.embedExternals) {
|
||||||
|
for (const ext of post.embedExternals) {
|
||||||
|
const gifData = parseGifFromUrl(ext.uri)
|
||||||
|
if (gifData) {
|
||||||
|
// Reconstruct a Gif object with all required properties
|
||||||
|
const mediaObject = {
|
||||||
|
url: gifData.url,
|
||||||
|
dims: [gifData.width, gifData.height] as [number, number],
|
||||||
|
duration: 0,
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
embed.media = {
|
||||||
|
type: 'gif',
|
||||||
|
gif: {
|
||||||
|
id: '',
|
||||||
|
created: 0,
|
||||||
|
hasaudio: false,
|
||||||
|
hascaption: false,
|
||||||
|
flags: '',
|
||||||
|
tags: [],
|
||||||
|
title: '',
|
||||||
|
content_description: gifData.alt || '',
|
||||||
|
itemurl: '',
|
||||||
|
url: gifData.url, // Required for useResolveGifQuery
|
||||||
|
media_formats: {
|
||||||
|
gif: mediaObject,
|
||||||
|
tinygif: mediaObject,
|
||||||
|
preview: mediaObject,
|
||||||
|
},
|
||||||
|
} as Gif,
|
||||||
|
alt: gifData.alt,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect video for restoration (processed async by caller)
|
||||||
|
if (post.embedVideos && post.embedVideos.length > 0) {
|
||||||
|
const vid = post.embedVideos[0]
|
||||||
|
const videoUri = loadedMedia.get(vid.localRef.path)
|
||||||
|
if (videoUri) {
|
||||||
|
const mimeType = parseVideoMimeType(vid.localRef.path)
|
||||||
|
logger.debug('found video to restore', {
|
||||||
|
localRefPath: vid.localRef.path,
|
||||||
|
videoUri,
|
||||||
|
altText: vid.alt,
|
||||||
|
mimeType,
|
||||||
|
captionCount: vid.captions?.length ?? 0,
|
||||||
|
})
|
||||||
|
restoredVideos.set(index, {
|
||||||
|
uri: videoUri,
|
||||||
|
altText: vid.alt || '',
|
||||||
|
mimeType,
|
||||||
|
localRefPath: vid.localRef.path,
|
||||||
|
captions:
|
||||||
|
vid.captions?.map(c => ({lang: c.lang, content: c.content})) ??
|
||||||
|
[],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (images.length > 0) {
|
|
||||||
embed.media = {type: 'images', images}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore GIF from external embed
|
// Restore quote embed
|
||||||
if (post.embedExternals) {
|
if (post.embedRecords && post.embedRecords.length > 0) {
|
||||||
for (const ext of post.embedExternals) {
|
const record = post.embedRecords[0]
|
||||||
const gifData = parseGifFromUrl(ext.uri)
|
embed.quote = {type: 'link', uri: record.record.uri}
|
||||||
if (gifData) {
|
}
|
||||||
// Reconstruct a Gif object with all required properties
|
|
||||||
const mediaObject = {
|
// Restore link embed (only if not a GIF)
|
||||||
url: gifData.url,
|
if (post.embedExternals && !embed.media) {
|
||||||
dims: [gifData.width, gifData.height] as [number, number],
|
for (const ext of post.embedExternals) {
|
||||||
duration: 0,
|
const gifData = parseGifFromUrl(ext.uri)
|
||||||
size: 0,
|
if (!gifData) {
|
||||||
|
embed.link = {type: 'link', uri: ext.uri}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
embed.media = {
|
|
||||||
type: 'gif',
|
|
||||||
gif: {
|
|
||||||
id: '',
|
|
||||||
created: 0,
|
|
||||||
hasaudio: false,
|
|
||||||
hascaption: false,
|
|
||||||
flags: '',
|
|
||||||
tags: [],
|
|
||||||
title: '',
|
|
||||||
content_description: gifData.alt || '',
|
|
||||||
itemurl: '',
|
|
||||||
url: gifData.url, // Required for useResolveGifQuery
|
|
||||||
media_formats: {
|
|
||||||
gif: mediaObject,
|
|
||||||
tinygif: mediaObject,
|
|
||||||
preview: mediaObject,
|
|
||||||
},
|
|
||||||
} as Gif,
|
|
||||||
alt: gifData.alt,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Collect video for restoration (processed async by caller)
|
// Parse labels
|
||||||
if (post.embedVideos && post.embedVideos.length > 0) {
|
const labels: string[] = []
|
||||||
const vid = post.embedVideos[0]
|
if (post.labels && 'values' in post.labels) {
|
||||||
const videoUri = loadedMedia.get(vid.localRef.path)
|
for (const val of post.labels.values) {
|
||||||
if (videoUri) {
|
labels.push(val.val)
|
||||||
const mimeType = parseVideoMimeType(vid.localRef.path)
|
|
||||||
logger.debug('found video to restore', {
|
|
||||||
localRefPath: vid.localRef.path,
|
|
||||||
videoUri,
|
|
||||||
altText: vid.alt,
|
|
||||||
mimeType,
|
|
||||||
captionCount: vid.captions?.length ?? 0,
|
|
||||||
})
|
|
||||||
restoredVideos.set(index, {
|
|
||||||
uri: videoUri,
|
|
||||||
altText: vid.alt || '',
|
|
||||||
mimeType,
|
|
||||||
localRefPath: vid.localRef.path,
|
|
||||||
captions:
|
|
||||||
vid.captions?.map(c => ({lang: c.lang, content: c.content})) ?? [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore quote embed
|
|
||||||
if (post.embedRecords && post.embedRecords.length > 0) {
|
|
||||||
const record = post.embedRecords[0]
|
|
||||||
embed.quote = {type: 'link', uri: record.record.uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore link embed (only if not a GIF)
|
|
||||||
if (post.embedExternals && !embed.media) {
|
|
||||||
for (const ext of post.embedExternals) {
|
|
||||||
const gifData = parseGifFromUrl(ext.uri)
|
|
||||||
if (!gifData) {
|
|
||||||
embed.link = {type: 'link', uri: ext.uri}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Parse labels
|
return {
|
||||||
const labels: string[] = []
|
id: `draft-post-${index}`,
|
||||||
if (post.labels && 'values' in post.labels) {
|
richtext,
|
||||||
for (const val of post.labels.values) {
|
shortenedGraphemeLength: richtext.graphemeLength,
|
||||||
labels.push(val.val)
|
labels,
|
||||||
}
|
embed,
|
||||||
}
|
} as PostDraft
|
||||||
|
}),
|
||||||
return {
|
)
|
||||||
id: `draft-post-${index}`,
|
|
||||||
richtext,
|
|
||||||
shortenedGraphemeLength: richtext.graphemeLength,
|
|
||||||
labels,
|
|
||||||
embed,
|
|
||||||
} as PostDraft
|
|
||||||
})
|
|
||||||
|
|
||||||
return {posts, restoredVideos}
|
return {posts, restoredVideos}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user