"Soft preload" icons (#3507)
This PR includes a "soft preload" feature for icons, where icons will be loaded when the canvas first mounts. The component will not wait for icons to finish loading before showing the editor, but this should help with "pop in" on menu icons. ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `improvement` — Improving existing features ### Test Plan 1. Load the component 2. After load, open a menu for the first time 3. The icons should immediately be visible ### Release Notes - Improve icon preloading
This commit is contained in:
parent
a253af95d9
commit
1450454873
5 changed files with 19 additions and 43 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -19,7 +19,7 @@ body:
|
||||||
id: reproduction
|
id: reproduction
|
||||||
attributes:
|
attributes:
|
||||||
label: How can we reproduce the bug?
|
label: How can we reproduce the bug?
|
||||||
description: If you can make the bug happen again, please share the steps involved.
|
description: If you can make the bug happen again, please share the steps involved. You can [fork this CodeSandbox](https://codesandbox.io/p/sandbox/tldraw-example-n539u) to make a reproduction.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|
|
@ -11,6 +11,10 @@ import { TextDecoder, TextEncoder } from 'util'
|
||||||
global.TextEncoder = TextEncoder
|
global.TextEncoder = TextEncoder
|
||||||
global.TextDecoder = TextDecoder
|
global.TextDecoder = TextDecoder
|
||||||
|
|
||||||
|
Image.prototype.decode = async function () {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function convertNumbersInObject(obj: any, roundToNearest: number) {
|
function convertNumbersInObject(obj: any, roundToNearest: number) {
|
||||||
if (!obj) return obj
|
if (!obj) return obj
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
|
|
|
@ -109,13 +109,10 @@ export function Tldraw(props: TldrawProps) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const assets = useDefaultEditorAssetsWithOverrides(rest.assetUrls)
|
const assets = useDefaultEditorAssetsWithOverrides(rest.assetUrls)
|
||||||
|
|
||||||
const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(assets)
|
const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(assets)
|
||||||
|
|
||||||
if (preloadingError) {
|
if (preloadingError) {
|
||||||
return <ErrorScreen>Could not load assets. Please refresh the page.</ErrorScreen>
|
return <ErrorScreen>Could not load assets. Please refresh the page.</ErrorScreen>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preloadingComplete) {
|
if (!preloadingComplete) {
|
||||||
return <LoadingScreen>Loading assets...</LoadingScreen>
|
return <LoadingScreen>Loading assets...</LoadingScreen>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createContext, useContext } from 'react'
|
import { createContext, useContext, useEffect } from 'react'
|
||||||
import { TLUiAssetUrls } from '../assetUrls'
|
import { TLUiAssetUrls } from '../assetUrls'
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -14,6 +14,19 @@ export function AssetUrlsProvider({
|
||||||
assetUrls: TLUiAssetUrls
|
assetUrls: TLUiAssetUrls
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
for (const src of Object.values(assetUrls.icons)) {
|
||||||
|
const image = new Image()
|
||||||
|
image.src = src
|
||||||
|
image.decode()
|
||||||
|
}
|
||||||
|
for (const src of Object.values(assetUrls.embedIcons)) {
|
||||||
|
const image = new Image()
|
||||||
|
image.src = src
|
||||||
|
image.decode()
|
||||||
|
}
|
||||||
|
}, [assetUrls])
|
||||||
|
|
||||||
return <AssetUrlsContext.Provider value={assetUrls}>{children}</AssetUrlsContext.Provider>
|
return <AssetUrlsContext.Provider value={assetUrls}>{children}</AssetUrlsContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { useAssetUrls } from '../context/asset-urls'
|
|
||||||
import { iconTypes } from '../icon-types'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export function usePreloadIcons(): boolean {
|
|
||||||
const [isLoaded, setIsLoaded] = useState<boolean>(false)
|
|
||||||
const assetUrls = useAssetUrls()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
async function loadImages() {
|
|
||||||
// Run through all of the icons and load them. It doesn't matter
|
|
||||||
// if any of the images don't load; though we expect that they would.
|
|
||||||
// Instead, we just want to make sure that the browser has cached
|
|
||||||
// all of the icons it can so that they're available when we need them.
|
|
||||||
|
|
||||||
await Promise.allSettled(
|
|
||||||
iconTypes.map((icon) => {
|
|
||||||
const image = new Image()
|
|
||||||
image.src = assetUrls.icons[icon]
|
|
||||||
return image.decode()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cancelled) return
|
|
||||||
setIsLoaded(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadImages()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [isLoaded, assetUrls])
|
|
||||||
|
|
||||||
return isLoaded
|
|
||||||
}
|
|
Loading…
Reference in a new issue