@tldraw/editor
Version: 
tldraw infinite canvas SDK (editor).
89 lines (76 loc) • 2.44 kB
text/typescript
import { MediaHelpers } from '@tldraw/utils'
import { getRenderedChildren } from './domUtils'
import { resourceToDataUrl } from './fetchCache'
function copyAttrs(source: Element, target: Element) {
	const attrs = Array.from(source.attributes)
	attrs.forEach((attr) => {
		target.setAttribute(attr.name, attr.value)
	})
}
function replace(original: HTMLElement, replacement: HTMLElement) {
	original.replaceWith(replacement)
	return replacement
}
async function createImage(dataUrl: string | null, cloneAttributesFrom?: HTMLElement) {
	const image = document.createElement('img')
	if (cloneAttributesFrom) {
		copyAttrs(cloneAttributesFrom, image)
	}
	image.setAttribute('src', dataUrl ?? 'data:')
	image.setAttribute('decoding', 'sync')
	image.setAttribute('loading', 'eager')
	try {
		await image.decode()
	} catch {
		// this is fine
	}
	return image
}
async function getCanvasReplacement(canvas: HTMLCanvasElement) {
	try {
		const dataURL = canvas.toDataURL()
		return await createImage(dataURL, canvas)
	} catch {
		return await createImage(null, canvas)
	}
}
async function getVideoReplacement(video: HTMLVideoElement) {
	try {
		const dataUrl = await MediaHelpers.getVideoFrameAsDataUrl(video)
		return createImage(dataUrl, video)
	} catch (err) {
		console.error('Could not get video frame', err)
	}
	if (video.poster) {
		const dataUrl = await resourceToDataUrl(video.poster)
		return createImage(dataUrl, video)
	}
	return createImage(null, video)
}
export async function embedMedia(node: HTMLElement) {
	if (node instanceof HTMLCanvasElement) {
		return replace(node, await getCanvasReplacement(node))
	} else if (node instanceof HTMLVideoElement) {
		return replace(node, await getVideoReplacement(node))
	} else if (node instanceof HTMLImageElement) {
		const src = node.currentSrc || node.src
		const dataUrl = await resourceToDataUrl(src)
		node.setAttribute('src', dataUrl ?? 'data:')
		node.setAttribute('decoding', 'sync')
		node.setAttribute('loading', 'eager')
		try {
			await node.decode()
		} catch {
			// this is fine
		}
		return node
	} else if (node instanceof HTMLInputElement) {
		// if an input has a value, make sure it's serialized when we convert to svg
		node.setAttribute('value', node.value)
	} else if (node instanceof HTMLTextAreaElement) {
		node.textContent = node.value
	}
	await Promise.all(
		Array.from(getRenderedChildren(node), (child) => embedMedia(child as HTMLElement))
	)
}