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:
parent
303c8782e3
commit
521d84a611
10 changed files with 199 additions and 66 deletions
BIN
apps/examples/public/ComicMono.woff
Normal file
BIN
apps/examples/public/ComicMono.woff
Normal file
Binary file not shown.
4
apps/examples/public/custom-arrow-icon.svg
Normal file
4
apps/examples/public/custom-arrow-icon.svg
Normal 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 |
|
@ -4,7 +4,7 @@ import '@tldraw/tldraw/tldraw.css'
|
|||
export default function BasicExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw />
|
||||
<Tldraw persistenceKey="example" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
11
apps/examples/src/examples/static-assets/README.md
Normal file
11
apps/examples/src/examples/static-assets/README.md
Normal 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.
|
|
@ -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.
|
||||
*/
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
56
packages/tldraw/src/lib/utils/assets/preload-font.ts
Normal file
56
packages/tldraw/src/lib/utils/assets/preload-font.ts
Normal 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
|
||||
}
|
Loading…
Reference in a new issue