From 2da02cf2593feb8b03489aebfbf992be918d2197 Mon Sep 17 00:00:00 2001 From: Bohdan Artiukhov <69891500+bohdanprog@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:12:49 +0200 Subject: [PATCH] feat: add new resolve web asset function (#2334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Summary Add a new function to resolve Image asset uri. ## Compatibility | OS | Implemented | | ------- | :---------: | | Web | ✅ | --- src/ReactNativeSVG.web.ts | 12 +++++- src/lib/resolveAssetUri.ts | 75 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/lib/resolveAssetUri.ts diff --git a/src/ReactNativeSVG.web.ts b/src/ReactNativeSVG.web.ts index a87789bc..9ea4b261 100644 --- a/src/ReactNativeSVG.web.ts +++ b/src/ReactNativeSVG.web.ts @@ -22,7 +22,11 @@ import type { TextProps } from './elements/Text'; import type { TextPathProps } from './elements/TextPath'; import type { TSpanProps } from './elements/TSpan'; import type { UseProps } from './elements/Use'; -import type { GestureResponderEvent, TransformsStyle } from 'react-native'; +import type { + GestureResponderEvent, + TransformsStyle, + ImageProps as RNImageProps, +} from 'react-native'; import { // @ts-ignore it is not seen in exports unstable_createElement as createElement, @@ -35,6 +39,7 @@ import type { import SvgTouchableMixin from './lib/SvgTouchableMixin'; import { resolve } from './lib/resolve'; import { transformsArrayToProps } from './lib/extract/extractTransform'; +import { resolveAssetUri } from './lib/resolveAssetUri'; type BlurEvent = object; type FocusEvent = object; @@ -54,6 +59,7 @@ interface BaseProps { delayPressOut?: number; disabled?: boolean; hitSlop?: EdgeInsetsProp; + href?: RNImageProps['source'] | string | number; nativeID?: string; touchSoundDisabled?: boolean; onBlur?: (e: BlurEvent) => void; @@ -200,6 +206,7 @@ const prepare = ( gradientTransform?: string; patternTransform?: string; 'transform-origin'?: string; + href?: RNImageProps['source'] | string | null; style?: object; ref?: unknown; } = { @@ -270,6 +277,9 @@ const prepare = ( if (props.onPress != null) { clean.onClick = props.onPress; } + if (props.href !== null) { + clean.href = resolveAssetUri(props.href)?.uri; + } return clean; }; diff --git a/src/lib/resolveAssetUri.ts b/src/lib/resolveAssetUri.ts new file mode 100644 index 00000000..a4f0193d --- /dev/null +++ b/src/lib/resolveAssetUri.ts @@ -0,0 +1,75 @@ +import { + ImageResolvedAssetSource, + PixelRatio, + type ImageProps as RNImageProps, +} from 'react-native'; +// @ts-expect-error react-native/assets-registry doesn't export types. +import { getAssetByID } from '@react-native/assets-registry/registry'; + +export type PackagerAsset = { + __packager_asset: boolean; + fileSystemLocation: string; + httpServerLocation: string; + width?: number; + height?: number; + scales: Array; + hash: string; + name: string; + type: string; +}; + +const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/; + +// Based on that function: https://github.com/necolas/react-native-web/blob/54c14d64dabd175e8055e1dc92e9196c821f9b7d/packages/react-native-web/src/exports/Image/index.js#L118-L156 +export function resolveAssetUri( + source?: RNImageProps['source'] | string | number +): Partial | null { + let src: Partial = {}; + if (typeof source === 'number') { + // get the URI from the packager + const asset: PackagerAsset | null = getAssetByID(source); + if (asset == null) { + throw new Error( + `Image: asset with ID "${source}" could not be found. Please check the image source or packager.` + ); + } + src = { + width: asset.width, + height: asset.height, + scale: asset.scales[0], + }; + if (asset.scales.length > 1) { + const preferredScale = PixelRatio.get(); + // Get the scale which is closest to the preferred scale + src.scale = asset.scales.reduce((prev, curr) => + Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) + ? curr + : prev + ); + } + const scaleSuffix = src.scale !== 1 ? `@${src.scale}x` : ''; + src.uri = asset + ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` + : ''; + } else if (typeof source === 'string') { + src.uri = source; + } else if ( + source && + !Array.isArray(source) && + typeof source.uri === 'string' + ) { + src.uri = source.uri; + } + + if (src.uri) { + const match = src?.uri?.match(svgDataUriPattern); + // inline SVG markup may contain characters (e.g., #, ") that need to be escaped + if (match) { + const [, prefix, svg] = match; + const encodedSvg = encodeURIComponent(svg); + src.uri = `${prefix}${encodedSvg}`; + return src; + } + } + return src; +}