"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
pull/3512/head
Steve Ruiz 2024-04-17 11:57:08 +01:00 zatwierdzone przez GitHub
rodzic a253af95d9
commit 1450454873
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
5 zmienionych plików z 19 dodań i 43 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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)) {

Wyświetl plik

@ -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>
} }

Wyświetl plik

@ -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>
} }

Wyświetl plik

@ -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
}