tldraw
Version:
A tiny little drawing editor.
8 lines (7 loc) • 6.89 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../../src/lib/shapes/shared/useImageOrVideoAsset.ts"],
"sourcesContent": ["import {\n\tEditor,\n\tTLAssetId,\n\tTLImageAsset,\n\tTLImageShape,\n\tTLShapeId,\n\tTLVideoAsset,\n\tTLVideoShape,\n\tdebounce,\n\treact,\n\tuseDelaySvgExport,\n\tuseEditor,\n\tuseSvgExportContext,\n} from '@tldraw/editor'\nimport { useEffect, useMemo, useRef, useState } from 'react'\n\n/**\n * This is a handy helper hook that resolves an asset to an optimized URL for a given shape, or its\n * {@link @tldraw/editor#Editor.createTemporaryAssetPreview | placeholder} if the asset is still\n * uploading. This is used in particular for high-resolution images when you want lower and higher\n * resolution depending on the context.\n *\n * For image scaling to work, you need to implement scaled URLs in\n * {@link @tldraw/tlschema#TLAssetStore.resolve}.\n *\n * @public\n */\nexport function useImageOrVideoAsset({\n\tshapeId,\n\tassetId,\n}: {\n\tshapeId: TLShapeId\n\tassetId: TLAssetId | null\n}) {\n\tconst editor = useEditor()\n\tconst isExport = !!useSvgExportContext()\n\tconst isReady = useDelaySvgExport()\n\n\tconst resolveAssetUrlDebounced = useMemo(() => debounce(resolveAssetUrl, 500), [])\n\n\t// We use a state to store the result of the asset resolution, and we're going to avoid updating this whenever we can\n\tconst [result, setResult] = useState<{\n\t\tasset: (TLImageAsset | TLVideoAsset) | null\n\t\turl: string | null\n\t}>(() => ({\n\t\tasset: assetId ? editor.getAsset<TLImageAsset | TLVideoAsset>(assetId) ?? null : null,\n\t\turl: null as string | null,\n\t}))\n\n\t// A flag for whether we've resolved the asset URL at least once, after which we can debounce\n\tconst didAlreadyResolve = useRef(false)\n\n\t// The last URL that we've seen for the shape\n\tconst previousUrl = useRef<string | null>(null)\n\n\tuseEffect(() => {\n\t\tif (!assetId) return\n\n\t\tlet isCancelled = false\n\t\tlet cancelDebounceFn: (() => void) | undefined\n\n\t\tconst cleanupEffectScheduler = react('update state', () => {\n\t\t\tif (!isExport && editor.getCulledShapes().has(shapeId)) return\n\n\t\t\t// Get the fresh asset\n\t\t\tconst asset = editor.getAsset<TLImageAsset | TLVideoAsset>(assetId)\n\t\t\tif (!asset) return\n\n\t\t\t// Get the fresh shape\n\t\t\tconst shape = editor.getShape<TLImageShape | TLVideoShape>(shapeId)\n\t\t\tif (!shape) return\n\n\t\t\t// Set initial preview for the shape if it has no source (if it was pasted into a local project as base64)\n\t\t\tif (!asset.props.src) {\n\t\t\t\tconst preview = editor.getTemporaryAssetPreview(asset.id)\n\t\t\t\tif (preview) {\n\t\t\t\t\tif (previousUrl.current !== preview) {\n\t\t\t\t\t\tpreviousUrl.current = preview // just for kicks, let's save the url as the previous URL\n\t\t\t\t\t\tsetResult((prev) => ({ ...prev, isPlaceholder: true, url: preview })) // set the preview as the URL\n\t\t\t\t\t\tisReady() // let the SVG export know we're ready for export\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// aside ...we could bail here if the only thing that has changed is the shape has changed from culled to not culled\n\n\t\t\tconst screenScale = editor.getZoomLevel() * (shape.props.w / asset.props.w)\n\n\t\t\tfunction resolve(asset: TLImageAsset | TLVideoAsset, url: string | null) {\n\t\t\t\tif (isCancelled) return // don't update if the hook has remounted\n\t\t\t\tif (previousUrl.current === url) return // don't update the state if the url is the same\n\t\t\t\tdidAlreadyResolve.current = true // mark that we've resolved our first image\n\t\t\t\tpreviousUrl.current = url // keep the url around to compare with the next one\n\t\t\t\tsetResult({ asset, url })\n\t\t\t\tisReady() // let the SVG export know we're ready for export\n\t\t\t}\n\n\t\t\t// If we already resolved the URL, debounce fetching potentially multiple image variations.\n\t\t\tif (didAlreadyResolve.current) {\n\t\t\t\tresolveAssetUrlDebounced(editor, assetId, screenScale, isExport, (url) =>\n\t\t\t\t\tresolve(asset, url)\n\t\t\t\t)\n\t\t\t\tcancelDebounceFn = resolveAssetUrlDebounced.cancel // cancel the debounce when the hook unmounts\n\t\t\t} else {\n\t\t\t\tresolveAssetUrl(editor, assetId, screenScale, isExport, (url) => resolve(asset, url))\n\t\t\t}\n\t\t})\n\n\t\treturn () => {\n\t\t\tcleanupEffectScheduler()\n\t\t\tcancelDebounceFn?.()\n\t\t\tisCancelled = true\n\t\t}\n\t}, [editor, assetId, isExport, isReady, shapeId, resolveAssetUrlDebounced])\n\n\treturn result\n}\n\nfunction resolveAssetUrl(\n\teditor: Editor,\n\tassetId: TLAssetId,\n\tscreenScale: number,\n\tisExport: boolean,\n\tcallback: (url: string | null) => void\n) {\n\teditor\n\t\t.resolveAssetUrl(assetId, {\n\t\t\tscreenScale,\n\t\t\tshouldResolveToOriginal: isExport,\n\t\t})\n\t\t// There's a weird bug with out debounce function that doesn't\n\t\t// make it work right with async functions, so we use a callback\n\t\t// here instead of returning a promise.\n\t\t.then((url) => {\n\t\t\tcallback(url)\n\t\t})\n}\n\n/**\n * @deprecated Use {@link useImageOrVideoAsset} instead.\n *\n * @public\n */\nexport const useAsset = useImageOrVideoAsset\n"],
"mappings": "AAAA;AAAA,EAQC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,WAAW,SAAS,QAAQ,gBAAgB;AAa9C,SAAS,qBAAqB;AAAA,EACpC;AAAA,EACA;AACD,GAGG;AACF,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,CAAC,CAAC,oBAAoB;AACvC,QAAM,UAAU,kBAAkB;AAElC,QAAM,2BAA2B,QAAQ,MAAM,SAAS,iBAAiB,GAAG,GAAG,CAAC,CAAC;AAGjF,QAAM,CAAC,QAAQ,SAAS,IAAI,SAGzB,OAAO;AAAA,IACT,OAAO,UAAU,OAAO,SAAsC,OAAO,KAAK,OAAO;AAAA,IACjF,KAAK;AAAA,EACN,EAAE;AAGF,QAAM,oBAAoB,OAAO,KAAK;AAGtC,QAAM,cAAc,OAAsB,IAAI;AAE9C,YAAU,MAAM;AACf,QAAI,CAAC,QAAS;AAEd,QAAI,cAAc;AAClB,QAAI;AAEJ,UAAM,yBAAyB,MAAM,gBAAgB,MAAM;AAC1D,UAAI,CAAC,YAAY,OAAO,gBAAgB,EAAE,IAAI,OAAO,EAAG;AAGxD,YAAM,QAAQ,OAAO,SAAsC,OAAO;AAClE,UAAI,CAAC,MAAO;AAGZ,YAAM,QAAQ,OAAO,SAAsC,OAAO;AAClE,UAAI,CAAC,MAAO;AAGZ,UAAI,CAAC,MAAM,MAAM,KAAK;AACrB,cAAM,UAAU,OAAO,yBAAyB,MAAM,EAAE;AACxD,YAAI,SAAS;AACZ,cAAI,YAAY,YAAY,SAAS;AACpC,wBAAY,UAAU;AACtB,sBAAU,CAAC,UAAU,EAAE,GAAG,MAAM,eAAe,MAAM,KAAK,QAAQ,EAAE;AACpE,oBAAQ;AAAA,UACT;AACA;AAAA,QACD;AAAA,MACD;AAIA,YAAM,cAAc,OAAO,aAAa,KAAK,MAAM,MAAM,IAAI,MAAM,MAAM;AAEzE,eAAS,QAAQA,QAAoC,KAAoB;AACxE,YAAI,YAAa;AACjB,YAAI,YAAY,YAAY,IAAK;AACjC,0BAAkB,UAAU;AAC5B,oBAAY,UAAU;AACtB,kBAAU,EAAE,OAAAA,QAAO,IAAI,CAAC;AACxB,gBAAQ;AAAA,MACT;AAGA,UAAI,kBAAkB,SAAS;AAC9B;AAAA,UAAyB;AAAA,UAAQ;AAAA,UAAS;AAAA,UAAa;AAAA,UAAU,CAAC,QACjE,QAAQ,OAAO,GAAG;AAAA,QACnB;AACA,2BAAmB,yBAAyB;AAAA,MAC7C,OAAO;AACN,wBAAgB,QAAQ,SAAS,aAAa,UAAU,CAAC,QAAQ,QAAQ,OAAO,GAAG,CAAC;AAAA,MACrF;AAAA,IACD,CAAC;AAED,WAAO,MAAM;AACZ,6BAAuB;AACvB,yBAAmB;AACnB,oBAAc;AAAA,IACf;AAAA,EACD,GAAG,CAAC,QAAQ,SAAS,UAAU,SAAS,SAAS,wBAAwB,CAAC;AAE1E,SAAO;AACR;AAEA,SAAS,gBACR,QACA,SACA,aACA,UACA,UACC;AACD,SACE,gBAAgB,SAAS;AAAA,IACzB;AAAA,IACA,yBAAyB;AAAA,EAC1B,CAAC,EAIA,KAAK,CAAC,QAAQ;AACd,aAAS,GAAG;AAAA,EACb,CAAC;AACH;AAOO,MAAM,WAAW;",
"names": ["asset"]
}