fixup file helpers (#3130)
We had a couple regressions in #3110: first a missing `await` was causing fonts not to get properly embedded in exports. second, some `readAsText` calls were replaced with `readAsDataURL` calls. ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [ ] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [x] `bugfix` — Bug fix - [ ] `feature` — New feature - [ ] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know
This commit is contained in:
parent
adebb680e5
commit
0a48aea7bb
10 changed files with 171 additions and 93 deletions
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
AssetRecordType,
|
AssetRecordType,
|
||||||
Editor,
|
Editor,
|
||||||
|
FileHelpers,
|
||||||
MediaHelpers,
|
MediaHelpers,
|
||||||
TLAsset,
|
TLAsset,
|
||||||
TLAssetId,
|
TLAssetId,
|
||||||
|
@ -96,7 +97,7 @@ export function registerDefaultExternalContentHandlers(
|
||||||
typeName: 'asset',
|
typeName: 'asset',
|
||||||
props: {
|
props: {
|
||||||
name,
|
name,
|
||||||
src: await MediaHelpers.blobToDataUrl(file),
|
src: await FileHelpers.blobToDataUrl(file),
|
||||||
w: size.w,
|
w: size.w,
|
||||||
h: size.h,
|
h: size.h,
|
||||||
mimeType: file.type,
|
mimeType: file.type,
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion'
|
||||||
async function getDataURIFromURL(url: string): Promise<string> {
|
async function getDataURIFromURL(url: string): Promise<string> {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
return FileHelpers.fileToBase64(blob)
|
return FileHelpers.blobToDataUrl(blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
|
@ -23,12 +23,12 @@ export function getFontDefForExport(fontStyle: TLDefaultFontStyle): SvgExportDef
|
||||||
const font = findFont(fontStyle)
|
const font = findFont(fontStyle)
|
||||||
if (!font) return null
|
if (!font) return null
|
||||||
|
|
||||||
const url = (font as any).$$_url
|
const url: string = (font as any).$$_url
|
||||||
const fontFaceRule = (font as any).$$_fontface
|
const fontFaceRule: string = (font as any).$$_fontface
|
||||||
if (!url || !fontFaceRule) return null
|
if (!url || !fontFaceRule) return null
|
||||||
|
|
||||||
const fontFile = await (await fetch(url)).blob()
|
const fontFile = await (await fetch(url)).blob()
|
||||||
const base64FontFile = FileHelpers.fileToBase64(fontFile)
|
const base64FontFile = await FileHelpers.blobToDataUrl(fontFile)
|
||||||
|
|
||||||
const newFontFaceRule = fontFaceRule.replace(url, base64FontFile)
|
const newFontFaceRule = fontFaceRule.replace(url, base64FontFile)
|
||||||
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
const style = document.createElementNS('http://www.w3.org/2000/svg', 'style')
|
||||||
|
|
|
@ -250,27 +250,30 @@ const handlePasteFromClipboardApi = async (
|
||||||
if (item.types.includes('text/html')) {
|
if (item.types.includes('text/html')) {
|
||||||
things.push({
|
things.push({
|
||||||
type: 'html',
|
type: 'html',
|
||||||
source: new Promise<string>((r) =>
|
source: (async () => {
|
||||||
item.getType('text/html').then((blob) => FileHelpers.fileToBase64(blob).then(r))
|
const blob = await item.getType('text/html')
|
||||||
),
|
return await FileHelpers.blobToText(blob)
|
||||||
|
})(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.types.includes('text/uri-list')) {
|
if (item.types.includes('text/uri-list')) {
|
||||||
things.push({
|
things.push({
|
||||||
type: 'url',
|
type: 'url',
|
||||||
source: new Promise<string>((r) =>
|
source: (async () => {
|
||||||
item.getType('text/uri-list').then((blob) => FileHelpers.fileToBase64(blob).then(r))
|
const blob = await item.getType('text/uri-list')
|
||||||
),
|
return await FileHelpers.blobToText(blob)
|
||||||
|
})(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.types.includes('text/plain')) {
|
if (item.types.includes('text/plain')) {
|
||||||
things.push({
|
things.push({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
source: new Promise<string>((r) =>
|
source: (async () => {
|
||||||
item.getType('text/plain').then((blob) => FileHelpers.fileToBase64(blob).then(r))
|
const blob = await item.getType('text/plain')
|
||||||
),
|
return await FileHelpers.blobToText(blob)
|
||||||
|
})(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ export async function getSvgAsString(svg: SVGElement) {
|
||||||
if (src) {
|
if (src) {
|
||||||
if (!src.startsWith('data:')) {
|
if (!src.startsWith('data:')) {
|
||||||
const blob = await (await fetch(src)).blob()
|
const blob = await (await fetch(src)).blob()
|
||||||
const base64 = await FileHelpers.fileToBase64(blob)
|
const base64 = await FileHelpers.blobToDataUrl(blob)
|
||||||
img.setAttribute('xlink:href', base64)
|
img.setAttribute('xlink:href', base64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ export async function serializeTldrawJson(store: TLStore): Promise<string> {
|
||||||
let assetSrcToSave
|
let assetSrcToSave
|
||||||
try {
|
try {
|
||||||
// try to save the asset as a base64 string
|
// try to save the asset as a base64 string
|
||||||
assetSrcToSave = await FileHelpers.fileToBase64(
|
assetSrcToSave = await FileHelpers.blobToDataUrl(
|
||||||
await (await fetch(record.props.src)).blob()
|
await (await fetch(record.props.src)).blob()
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -62,9 +62,10 @@ export type Expand<T> = T extends infer O ? {
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export class FileHelpers {
|
export class FileHelpers {
|
||||||
// @internal (undocumented)
|
static blobToDataUrl(file: Blob): Promise<string>;
|
||||||
static base64ToFile(dataURL: string): Promise<ArrayBuffer>;
|
static blobToText(file: Blob): Promise<string>;
|
||||||
static fileToBase64(file: Blob): Promise<string>;
|
// (undocumented)
|
||||||
|
static dataUrlToArrayBuffer(dataURL: string): Promise<ArrayBuffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @internal
|
// @internal
|
||||||
|
@ -177,7 +178,6 @@ export function mapObjectMapValues<Key extends string, ValueBefore, ValueAfter>(
|
||||||
|
|
||||||
// @public
|
// @public
|
||||||
export class MediaHelpers {
|
export class MediaHelpers {
|
||||||
static blobToDataUrl(blob: Blob): Promise<string>;
|
|
||||||
static getImageSize(blob: Blob): Promise<{
|
static getImageSize(blob: Blob): Promise<{
|
||||||
w: number;
|
w: number;
|
||||||
h: number;
|
h: number;
|
||||||
|
|
|
@ -519,12 +519,12 @@
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"kind": "Method",
|
"kind": "Method",
|
||||||
"canonicalReference": "@tldraw/utils!FileHelpers.fileToBase64:member(1)",
|
"canonicalReference": "@tldraw/utils!FileHelpers.blobToDataUrl:member(1)",
|
||||||
"docComment": "/**\n * Convert a file to base64.\n *\n * @param value - The file as a blob.\n *\n * @example\n * ```ts\n * const A = fileToBase64('./test.png')\n * ```\n *\n * @public\n */\n",
|
"docComment": "/**\n * Convert a file to a base64 encoded data url.\n *\n * @param value - The file as a blob.\n *\n * @example\n * ```ts\n * const A = FileHelpers.toDataUrl(myImageFile)\n * ```\n *\n */\n",
|
||||||
"excerptTokens": [
|
"excerptTokens": [
|
||||||
{
|
{
|
||||||
"kind": "Content",
|
"kind": "Content",
|
||||||
"text": "static fileToBase64(file: "
|
"text": "static blobToDataUrl(file: "
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "Reference",
|
"kind": "Reference",
|
||||||
|
@ -569,7 +569,123 @@
|
||||||
],
|
],
|
||||||
"isOptional": false,
|
"isOptional": false,
|
||||||
"isAbstract": false,
|
"isAbstract": false,
|
||||||
"name": "fileToBase64"
|
"name": "blobToDataUrl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Method",
|
||||||
|
"canonicalReference": "@tldraw/utils!FileHelpers.blobToText:member(1)",
|
||||||
|
"docComment": "/**\n * Convert a file to a unicode text string.\n *\n * @param value - The file as a blob.\n *\n * @example\n * ```ts\n * const A = FileHelpers.fileToDataUrl(myTextFile)\n * ```\n *\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "static blobToText(file: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Blob",
|
||||||
|
"canonicalReference": "!Blob:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "): "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Promise",
|
||||||
|
"canonicalReference": "!Promise:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<string>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isStatic": true,
|
||||||
|
"returnTypeTokenRange": {
|
||||||
|
"startIndex": 3,
|
||||||
|
"endIndex": 5
|
||||||
|
},
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"isProtected": false,
|
||||||
|
"overloadIndex": 1,
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"parameterName": "file",
|
||||||
|
"parameterTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
},
|
||||||
|
"isOptional": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isOptional": false,
|
||||||
|
"isAbstract": false,
|
||||||
|
"name": "blobToText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Method",
|
||||||
|
"canonicalReference": "@tldraw/utils!FileHelpers.dataUrlToArrayBuffer:member(1)",
|
||||||
|
"docComment": "/**\n * @param dataURL - The file as a string.\n *\n * from https://stackoverflow.com/a/53817185\n */\n",
|
||||||
|
"excerptTokens": [
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "static dataUrlToArrayBuffer(dataURL: "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "): "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "Promise",
|
||||||
|
"canonicalReference": "!Promise:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": "<"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Reference",
|
||||||
|
"text": "ArrayBuffer",
|
||||||
|
"canonicalReference": "!ArrayBuffer:interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ">"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Content",
|
||||||
|
"text": ";"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isStatic": true,
|
||||||
|
"returnTypeTokenRange": {
|
||||||
|
"startIndex": 3,
|
||||||
|
"endIndex": 7
|
||||||
|
},
|
||||||
|
"releaseTag": "Public",
|
||||||
|
"isProtected": false,
|
||||||
|
"overloadIndex": 1,
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"parameterName": "dataURL",
|
||||||
|
"parameterTypeTokenRange": {
|
||||||
|
"startIndex": 1,
|
||||||
|
"endIndex": 2
|
||||||
|
},
|
||||||
|
"isOptional": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isOptional": false,
|
||||||
|
"isAbstract": false,
|
||||||
|
"name": "dataUrlToArrayBuffer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"implementsTokenRanges": []
|
"implementsTokenRanges": []
|
||||||
|
@ -1844,60 +1960,6 @@
|
||||||
"name": "MediaHelpers",
|
"name": "MediaHelpers",
|
||||||
"preserveMemberOrder": false,
|
"preserveMemberOrder": false,
|
||||||
"members": [
|
"members": [
|
||||||
{
|
|
||||||
"kind": "Method",
|
|
||||||
"canonicalReference": "@tldraw/utils!MediaHelpers.blobToDataUrl:member(1)",
|
|
||||||
"docComment": "/**\n * Read a blob into a data url\n *\n * @public\n */\n",
|
|
||||||
"excerptTokens": [
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": "static blobToDataUrl(blob: "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "Blob",
|
|
||||||
"canonicalReference": "!Blob:interface"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": "): "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Reference",
|
|
||||||
"text": "Promise",
|
|
||||||
"canonicalReference": "!Promise:interface"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": "<string>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"kind": "Content",
|
|
||||||
"text": ";"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isStatic": true,
|
|
||||||
"returnTypeTokenRange": {
|
|
||||||
"startIndex": 3,
|
|
||||||
"endIndex": 5
|
|
||||||
},
|
|
||||||
"releaseTag": "Public",
|
|
||||||
"isProtected": false,
|
|
||||||
"overloadIndex": 1,
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"parameterName": "blob",
|
|
||||||
"parameterTypeTokenRange": {
|
|
||||||
"startIndex": 1,
|
|
||||||
"endIndex": 2
|
|
||||||
},
|
|
||||||
"isOptional": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isOptional": false,
|
|
||||||
"isAbstract": false,
|
|
||||||
"name": "blobToDataUrl"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"kind": "Method",
|
"kind": "Method",
|
||||||
"canonicalReference": "@tldraw/utils!MediaHelpers.getImageSize:member(1)",
|
"canonicalReference": "@tldraw/utils!MediaHelpers.getImageSize:member(1)",
|
||||||
|
|
|
@ -6,29 +6,27 @@
|
||||||
export class FileHelpers {
|
export class FileHelpers {
|
||||||
/**
|
/**
|
||||||
* @param dataURL - The file as a string.
|
* @param dataURL - The file as a string.
|
||||||
* @internal
|
|
||||||
*
|
*
|
||||||
* from https://stackoverflow.com/a/53817185
|
* from https://stackoverflow.com/a/53817185
|
||||||
*/
|
*/
|
||||||
static async base64ToFile(dataURL: string) {
|
static async dataUrlToArrayBuffer(dataURL: string) {
|
||||||
return fetch(dataURL).then(function (result) {
|
return fetch(dataURL).then(function (result) {
|
||||||
return result.arrayBuffer()
|
return result.arrayBuffer()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a file to base64.
|
* Convert a file to a base64 encoded data url.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* const A = fileToBase64('./test.png')
|
* const A = FileHelpers.toDataUrl(myImageFile)
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param value - The file as a blob.
|
* @param value - The file as a blob.
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
static async fileToBase64(file: Blob): Promise<string> {
|
static async blobToDataUrl(file: Blob): Promise<string> {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
@ -39,4 +37,27 @@ export class FileHelpers {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a file to a unicode text string.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const A = FileHelpers.fileToDataUrl(myTextFile)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param value - The file as a blob.
|
||||||
|
*/
|
||||||
|
static async blobToText(file: Blob): Promise<string> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = () => resolve(reader.result as string)
|
||||||
|
reader.onerror = (error) => reject(error)
|
||||||
|
reader.onabort = (error) => reject(error)
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { FileHelpers } from './file'
|
|
||||||
import { PngHelpers } from './png'
|
import { PngHelpers } from './png'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,14 +40,6 @@ export class MediaHelpers {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a blob into a data url
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
static blobToDataUrl(blob: Blob) {
|
|
||||||
return FileHelpers.fileToBase64(blob)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the size of a video blob
|
* Get the size of a video blob
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue