Add custom static assets example, extract preloadFont (#2932)

This PR adds a custom static assets example.

It also:
- extracts preloadFont into a async function to make custom preloading
easier
- accounts for file-based formats

### Change Type

- [x] `minor` — New feature

### Test Plan

1. Test the example.

### Release Notes

- Docs, added custom static assets example.
This commit is contained in:
Steve Ruiz 2024-02-23 13:58:06 +00:00 committed by GitHub
parent 303c8782e3
commit 521d84a611
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 199 additions and 66 deletions

Binary file not shown.

View file

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 27C12.1758 20.5205 18.0216 13.2433 23.0186 5.84185C24.5104 3.63226 20.6732 6.42142 19.8404 6.8542C17.9679 7.82738 15.3539 8.29145 13.2534 8.75236C11.7092 9.09121 16.0554 7.01119 17.6362 6.95544C20.1376 6.86723 22.6528 6.05707 25.1203 5.61407C26.7973 5.31299 25.6248 5.15208 25.2741 7.05667C24.5192 11.1564 24.9152 15.5399 24.9152 19.7111" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 519 B

View file

@ -4,7 +4,7 @@ import '@tldraw/tldraw/tldraw.css'
export default function BasicExample() {
return (
<div className="tldraw__editor">
<Tldraw />
<Tldraw persistenceKey="example" />
</div>
)
}

View file

@ -0,0 +1,11 @@
---
title: Static assets
component: ./StaticAssetsExample.tsx
category: basic
---
Use custom fonts, icons, and preload them using the `Tldraw` component.
---
The `Tldraw` component relies on static assets for fonts and icons. These must be pre-loaded in order that the component runs properly.

View file

@ -0,0 +1,26 @@
import { Tldraw } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css'
export default function StaticAssetsExample() {
return (
<div className="tldraw__editor">
<Tldraw
persistenceKey="static-assets"
assetUrls={{
fonts: {
// [1]
draw: '/ComicMono.woff',
},
icons: {
'tool-arrow': '/custom-arrow-icon.svg',
},
}}
/>
</div>
)
}
/**
These assets are stored in the /public folder of this Vite project, but this could be any URL.
By default, the Tldraw component will pull in assets from the @tldraw/assets package on Unpkg.
*/

View file

@ -1013,6 +1013,9 @@ export function parseTldrawJsonFile({ json, schema, }: {
json: string;
}): Result<TLStore, TldrawFileParseError>;
// @public (undocumented)
export function preloadFont(id: string, font: TLTypeFace): Promise<FontFace>;
// @public
export function removeFrame(editor: Editor, ids: TLShapeId[]): void;

View file

@ -11869,6 +11869,82 @@
],
"name": "parseTldrawJsonFile"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!preloadFont:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function preloadFont(id: "
},
{
"kind": "Content",
"text": "string"
},
{
"kind": "Content",
"text": ", font: "
},
{
"kind": "Reference",
"text": "TLTypeFace",
"canonicalReference": "@tldraw/tldraw!~TLTypeFace:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "Promise",
"canonicalReference": "!Promise:interface"
},
{
"kind": "Content",
"text": "<"
},
{
"kind": "Reference",
"text": "FontFace",
"canonicalReference": "!FontFace:interface"
},
{
"kind": "Content",
"text": ">"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/utils/assets/preload-font.ts",
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 9
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "id",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
},
{
"parameterName": "font",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"isOptional": false
}
],
"name": "preloadFont"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!removeFrame:function(1)",

View file

@ -1,5 +1,7 @@
/// <reference types="react" />
export { preloadFont } from './lib/utils/assets/preload-font'
export { useCanRedo, useCanUndo } from './lib/ui/hooks/menu-hooks'
// eslint-disable-next-line local/no-export-star

View file

@ -1,24 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { TLTypeFace, preloadFont } from '../../utils/assets/preload-font'
import { TLEditorAssetUrls } from '../../utils/static-assets/assetUrls'
export type TLTypeFace = {
url: string
display?: any // FontDisplay
featureSettings?: string
stretch?: string
style?: string
unicodeRange?: string
variant?: string
weight?: string
}
export type TLTypeFaces = {
draw: TLTypeFace
monospace: TLTypeFace
serif: TLTypeFace
sansSerif: TLTypeFace
}
enum PreloadStatus {
SUCCESS,
FAILED,
@ -29,60 +12,22 @@ const usePreloadFont = (id: string, font: TLTypeFace): PreloadStatus => {
const [state, setState] = useState<PreloadStatus>(PreloadStatus.WAITING)
useEffect(() => {
const {
url,
style = 'normal',
weight = '500',
display,
featureSettings,
stretch,
unicodeRange,
variant,
} = font
let cancelled = false
setState(PreloadStatus.WAITING)
const descriptors: FontFaceDescriptors = {
style,
weight,
display,
featureSettings,
stretch,
unicodeRange,
variant,
}
const fontInstance = new FontFace(id, `url(${url})`, descriptors)
fontInstance
.load()
preloadFont(id, font)
.then(() => {
if (cancelled) return
document.fonts.add(fontInstance)
setState(PreloadStatus.SUCCESS)
})
.catch((err) => {
.catch((err: any) => {
if (cancelled) return
console.error(err)
setState(PreloadStatus.FAILED)
})
// @ts-expect-error
fontInstance.$$_url = url
// @ts-expect-error
fontInstance.$$_fontface = `
@font-face {
font-family: ${fontInstance.family};
font-stretch: ${fontInstance.stretch};
font-weight: ${fontInstance.weight};
font-style: ${fontInstance.style};
src: url("${url}") format("woff2")
}`
return () => {
document.fonts.delete(fontInstance)
cancelled = true
}
}, [id, font])
@ -92,15 +37,25 @@ const usePreloadFont = (id: string, font: TLTypeFace): PreloadStatus => {
function getTypefaces(assetUrls: TLEditorAssetUrls) {
return {
draw: { url: assetUrls.fonts.draw },
serif: { url: assetUrls.fonts.serif },
sansSerif: { url: assetUrls.fonts.sansSerif },
monospace: { url: assetUrls.fonts.monospace },
draw: {
url: assetUrls.fonts.draw,
format: assetUrls.fonts.draw.split('.').pop(),
},
serif: {
url: assetUrls.fonts.serif,
format: assetUrls.fonts.serif.split('.').pop(),
},
sansSerif: {
url: assetUrls.fonts.sansSerif,
format: assetUrls.fonts.sansSerif.split('.').pop(),
},
monospace: {
url: assetUrls.fonts.monospace,
format: assetUrls.fonts.monospace.split('.').pop(),
},
}
}
// todo: Expose this via a public API (prop on <Tldraw>).
export function usePreloadAssets(assetUrls: TLEditorAssetUrls) {
const typefaces = useMemo(() => getTypefaces(assetUrls), [assetUrls])

View file

@ -0,0 +1,56 @@
/** @public */
export type TLTypeFace = {
url: string
display?: any // FontDisplay
featureSettings?: string
stretch?: string
style?: string
unicodeRange?: string
variant?: string
weight?: string
format?: string
}
/** @public */
export async function preloadFont(id: string, font: TLTypeFace) {
const {
url,
style = 'normal',
weight = '500',
display,
featureSettings,
stretch,
unicodeRange,
variant,
format,
} = font
const descriptors: FontFaceDescriptors = {
style,
weight,
display,
featureSettings,
stretch,
unicodeRange,
variant,
}
const fontInstance = new FontFace(id, `url(${url})`, descriptors)
await fontInstance.load()
document.fonts.add(fontInstance)
// @ts-expect-error
fontInstance.$$_url = url
// @ts-expect-error
fontInstance.$$_fontface = `
@font-face {
font-family: ${fontInstance.family};
font-stretch: ${fontInstance.stretch};
font-weight: ${fontInstance.weight};
font-style: ${fontInstance.style};
src: url("${url}") format("${format}")
}`
return fontInstance
}