From 14504548730482a2147f8a58e672978f118d37f4 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Wed, 17 Apr 2024 11:57:08 +0100 Subject: [PATCH] "Soft preload" icons (#3507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- config/setupJest.ts | 4 ++ packages/tldraw/src/lib/Tldraw.tsx | 3 -- .../tldraw/src/lib/ui/context/asset-urls.tsx | 15 +++++++- .../src/lib/ui/hooks/usePreloadIcons.ts | 38 ------------------- 5 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 packages/tldraw/src/lib/ui/hooks/usePreloadIcons.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e70e7168d..e172c635b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -19,7 +19,7 @@ body: id: reproduction attributes: 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: required: false - type: dropdown diff --git a/config/setupJest.ts b/config/setupJest.ts index bc6cecb22..d89601dd3 100644 --- a/config/setupJest.ts +++ b/config/setupJest.ts @@ -11,6 +11,10 @@ import { TextDecoder, TextEncoder } from 'util' global.TextEncoder = TextEncoder global.TextDecoder = TextDecoder +Image.prototype.decode = async function () { + return true +} + function convertNumbersInObject(obj: any, roundToNearest: number) { if (!obj) return obj if (Array.isArray(obj)) { diff --git a/packages/tldraw/src/lib/Tldraw.tsx b/packages/tldraw/src/lib/Tldraw.tsx index 203cf49f9..159cf2023 100644 --- a/packages/tldraw/src/lib/Tldraw.tsx +++ b/packages/tldraw/src/lib/Tldraw.tsx @@ -109,13 +109,10 @@ export function Tldraw(props: TldrawProps) { ) const assets = useDefaultEditorAssetsWithOverrides(rest.assetUrls) - const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(assets) - if (preloadingError) { return Could not load assets. Please refresh the page. } - if (!preloadingComplete) { return Loading assets... } diff --git a/packages/tldraw/src/lib/ui/context/asset-urls.tsx b/packages/tldraw/src/lib/ui/context/asset-urls.tsx index 4fd3b9fd6..af35d5800 100644 --- a/packages/tldraw/src/lib/ui/context/asset-urls.tsx +++ b/packages/tldraw/src/lib/ui/context/asset-urls.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext } from 'react' +import { createContext, useContext, useEffect } from 'react' import { TLUiAssetUrls } from '../assetUrls' /** @internal */ @@ -14,6 +14,19 @@ export function AssetUrlsProvider({ assetUrls: TLUiAssetUrls 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 {children} } diff --git a/packages/tldraw/src/lib/ui/hooks/usePreloadIcons.ts b/packages/tldraw/src/lib/ui/hooks/usePreloadIcons.ts deleted file mode 100644 index ee7d1aa37..000000000 --- a/packages/tldraw/src/lib/ui/hooks/usePreloadIcons.ts +++ /dev/null @@ -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(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 -}