react-native-fast-image 10.5.0 → 10.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +13 -14
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +88 -29
- package/dist/index.cjs.js +6 -24
- package/dist/index.cjs.js.flow +1 -1
- package/dist/index.cjs.web.js +19 -5
- package/dist/index.cjs.web.js.flow +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -24
- package/dist/index.js.flow +1 -1
- package/dist/index.web.js +19 -5
- package/dist/index.web.js.flow +1 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +0 -2
- package/ios/FastImage/FFFThumbHash.h +3 -0
- package/ios/FastImage/FFFThumbHash.m +5 -1
- package/ios/FastImage/FFFastImageView.h +9 -7
- package/ios/FastImage/FFFastImageView.m +26 -19
- package/ios/FastImage/FFFastImageViewComponentView.mm +61 -14
- package/package.json +1 -1
- package/src/index.js.flow +1 -1
- package/src/index.tsx +29 -69
- package/src/index.web.tsx +42 -14
- package/src/specs/FastImageViewNativeComponent.ts +10 -13
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ It's a single native view, which also takes React Native's View props, such as a
|
|
|
107
107
|
|
|
108
108
|
#### `source`
|
|
109
109
|
|
|
110
|
-
**Type:** `number | Source | Source[]`
|
|
110
|
+
**Type:** `number | Source | (number | Source)[]`
|
|
111
111
|
|
|
112
112
|
Source for the remote image to load: a `Source`, a `require()`d image, or an array of the same image at different sizes.
|
|
113
113
|
|
|
@@ -129,6 +129,7 @@ When `source` changes, the image that's showing stays until the new one has load
|
|
|
129
129
|
- When the view's size changes so that another size fits better, it loads that one (with its load events), and keeps showing the current image until then, without a `transition`.
|
|
130
130
|
- Each size is cached separately. A `cacheKey` applies to its own entry: give each size its own key (or none), or the sizes would replace each other in the cache.
|
|
131
131
|
- `FastImage.preload`, `getCachePath` and `writeToCache` take one size: pass the one a view will show. An array fails with `{ ok: false, error }`.
|
|
132
|
+
- A `require()`d image can be one of the sizes: its size is the file's.
|
|
132
133
|
- An array of one is the same as that source.
|
|
133
134
|
|
|
134
135
|
---
|
|
@@ -291,7 +292,7 @@ Notes:
|
|
|
291
292
|
|
|
292
293
|
**Type:** `number`
|
|
293
294
|
|
|
294
|
-
**Deprecated:** Use `placeholder`, which does the same.
|
|
295
|
+
**Deprecated:** Use `placeholder`, which does the same. With both, `placeholder` shows.
|
|
295
296
|
|
|
296
297
|
---
|
|
297
298
|
|
|
@@ -21,6 +21,7 @@ import com.bumptech.glide.RequestManager;
|
|
|
21
21
|
import com.facebook.react.bridge.ColorPropConverter;
|
|
22
22
|
import com.facebook.react.bridge.Dynamic;
|
|
23
23
|
import com.facebook.react.bridge.DynamicFromObject;
|
|
24
|
+
import com.facebook.react.bridge.JavaOnlyArray;
|
|
24
25
|
import com.facebook.react.bridge.ReadableArray;
|
|
25
26
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
27
|
import com.facebook.react.bridge.ReadableType;
|
|
@@ -307,23 +308,26 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
307
308
|
|
|
308
309
|
@Override
|
|
309
310
|
public void setSource(FastImageViewWithUrl view, Dynamic source) {
|
|
310
|
-
|
|
311
|
+
// One source, or several (an array).
|
|
312
|
+
view.setSource(source.isNull() ? null
|
|
313
|
+
: source.getType() == ReadableType.Array ? source.asArray()
|
|
314
|
+
: JavaOnlyArray.of(source.asMap()));
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// The `placeholder` prop: a url to load apart from the image, as a source
|
|
319
|
-
// (a map), or the resolved asset's uri or a data uri (a string).
|
|
317
|
+
// The `placeholder` prop: a thumbhash or blurDataUri, or a url to load
|
|
318
|
+
// apart from the image, as a source (a map), or the resolved asset's uri
|
|
319
|
+
// or a data uri (a string).
|
|
320
320
|
@Override
|
|
321
321
|
public void setPlaceholder(FastImageViewWithUrl view, Dynamic source) {
|
|
322
322
|
if (source.getType() == ReadableType.Map) {
|
|
323
|
+
ReadableMap map = source.asMap();
|
|
324
|
+
boolean hash = map.hasKey("thumbhash") || map.hasKey("blurDataUri");
|
|
323
325
|
view.setPlaceholder(null);
|
|
324
|
-
view.
|
|
326
|
+
view.setPlaceholderHash(hash ? FastImageViewWithUrl.sourcePlaceholder(map) : null);
|
|
327
|
+
view.setPlaceholderSource(hash ? null : map);
|
|
325
328
|
return;
|
|
326
329
|
}
|
|
330
|
+
view.setPlaceholderHash(null);
|
|
327
331
|
view.setPlaceholderSource(null);
|
|
328
332
|
String uri = source.isNull() ? null : source.asString();
|
|
329
333
|
if (uri != null && FastImageThumbHash.isDataUri(uri)) {
|
|
@@ -351,11 +355,6 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
351
355
|
view.setRecyclingKey(recyclingKey);
|
|
352
356
|
}
|
|
353
357
|
|
|
354
|
-
@Override
|
|
355
|
-
public void setSourcePlaceholders(FastImageViewWithUrl view, @Nullable ReadableArray placeholders) {
|
|
356
|
-
view.setSourcePlaceholders(placeholders);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
358
|
@Override
|
|
360
359
|
public void setLoopCount(FastImageViewWithUrl view, int loopCount) {
|
|
361
360
|
view.setLoopCount(loopCount);
|
|
@@ -48,6 +48,7 @@ import com.bumptech.glide.request.transition.Transition;
|
|
|
48
48
|
import com.bumptech.glide.request.transition.TransitionFactory;
|
|
49
49
|
import com.facebook.react.bridge.ReadableArray;
|
|
50
50
|
import com.facebook.react.bridge.ReadableMap;
|
|
51
|
+
import com.facebook.react.bridge.ReadableType;
|
|
51
52
|
import com.facebook.react.bridge.WritableMap;
|
|
52
53
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
53
54
|
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
|
@@ -58,6 +59,10 @@ import com.facebook.react.uimanager.PointerEvents;
|
|
|
58
59
|
import com.facebook.react.uimanager.ReactPointerEventsView;
|
|
59
60
|
import com.facebook.react.uimanager.style.LogicalEdge;
|
|
60
61
|
|
|
62
|
+
import java.util.ArrayList;
|
|
63
|
+
import java.util.HashMap;
|
|
64
|
+
import java.util.List;
|
|
65
|
+
import java.util.Objects;
|
|
61
66
|
import javax.annotation.Nonnull;
|
|
62
67
|
|
|
63
68
|
class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEventsView, ReactHitSlopView {
|
|
@@ -213,16 +218,72 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
213
218
|
return false;
|
|
214
219
|
}
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
// The `source` prop: one source, or several (sizes of one image), each
|
|
222
|
+
// with its thumbhash or blurDataUri if it has one. Only a change besides
|
|
223
|
+
// those loads again: a new thumbhash for the same image (e.g. one that
|
|
224
|
+
// arrives later) shows at once (see updatePlaceholder).
|
|
225
|
+
public void setSource(@Nullable ReadableArray sources) {
|
|
226
|
+
List<Object> key = sourceKey(sources);
|
|
227
|
+
if (!mHasSource || !Objects.equals(key, mSourceKey)) {
|
|
228
|
+
mHasSource = true;
|
|
229
|
+
mSourceKey = key;
|
|
230
|
+
mNeedsReload = true;
|
|
231
|
+
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
232
|
+
mSourceIndex = -1;
|
|
233
|
+
if (mSources == null) mSource = sources != null && sources.size() == 1 ? map(sources, 0) : null;
|
|
234
|
+
}
|
|
235
|
+
mSourcePlaceholders = new ArrayList<>();
|
|
236
|
+
for (int i = 0; sources != null && i < sources.size(); i++) {
|
|
237
|
+
mSourcePlaceholders.add(sourcePlaceholder(map(sources, i)));
|
|
238
|
+
}
|
|
239
|
+
updatePlaceholder();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The sources without their thumbhashes and blurDataUris, to compare.
|
|
243
|
+
private boolean mHasSource = false;
|
|
244
|
+
@Nullable
|
|
245
|
+
private List<Object> mSourceKey = null;
|
|
246
|
+
|
|
247
|
+
@Nullable
|
|
248
|
+
private static List<Object> sourceKey(@Nullable ReadableArray sources) {
|
|
249
|
+
if (sources == null) return null;
|
|
250
|
+
List<Object> key = new ArrayList<>();
|
|
251
|
+
for (int i = 0; i < sources.size(); i++) {
|
|
252
|
+
ReadableMap source = map(sources, i);
|
|
253
|
+
key.add(source == null ? null : withoutPlaceholders(source));
|
|
254
|
+
}
|
|
255
|
+
return key;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A source without its thumbhash and blurDataUri.
|
|
259
|
+
private static HashMap<String, Object> withoutPlaceholders(ReadableMap source) {
|
|
260
|
+
HashMap<String, Object> map = source.toHashMap();
|
|
261
|
+
map.remove("thumbhash");
|
|
262
|
+
map.remove("blurDataUri");
|
|
263
|
+
return map;
|
|
220
264
|
}
|
|
221
265
|
|
|
222
|
-
// The `source` prop. With several `sources`, mSource is the one picked for
|
|
223
|
-
// the view's size instead.
|
|
224
266
|
@Nullable
|
|
225
|
-
private ReadableMap
|
|
267
|
+
private static ReadableMap map(ReadableArray array, int index) {
|
|
268
|
+
return array.getType(index) == ReadableType.Map ? array.getMap(index) : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// A source's (or `placeholder`'s) ThumbHash, or its blurDataUri if it's a
|
|
272
|
+
// data uri (JS warns about another), or null.
|
|
273
|
+
@Nullable
|
|
274
|
+
static String sourcePlaceholder(@Nullable ReadableMap source) {
|
|
275
|
+
if (source == null) return null;
|
|
276
|
+
String thumbhash = string(source, "thumbhash");
|
|
277
|
+
if (thumbhash != null && !thumbhash.isEmpty()) return thumbhash;
|
|
278
|
+
String blurDataUri = string(source, "blurDataUri");
|
|
279
|
+
return blurDataUri != null && FastImageThumbHash.isDataUri(blurDataUri) ? blurDataUri : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@Nullable
|
|
283
|
+
private static String string(ReadableMap map, String key) {
|
|
284
|
+
return map.hasKey(key) && map.getType(key) == ReadableType.String ? map.getString(key) : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
226
287
|
// Several sources (2 or more) of the same image at different sizes, or
|
|
227
288
|
// null: the view loads the one whose size is closest to its own.
|
|
228
289
|
@Nullable
|
|
@@ -240,13 +301,6 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
240
301
|
@Nullable
|
|
241
302
|
private FastImageViewManager mManager;
|
|
242
303
|
|
|
243
|
-
public void setSources(@Nullable ReadableArray sources) {
|
|
244
|
-
mNeedsReload = true;
|
|
245
|
-
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
246
|
-
mSourceIndex = -1;
|
|
247
|
-
if (mSources == null) mSource = mPropSource;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
304
|
// The box the image is drawn in: the view inside its padding, which is the
|
|
251
305
|
// border widths (see updateBorderPadding).
|
|
252
306
|
private int contentWidth() {
|
|
@@ -310,19 +364,20 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
310
364
|
return true;
|
|
311
365
|
}
|
|
312
366
|
|
|
367
|
+
// Shows at once, as a new source placeholder does (see updatePlaceholder),
|
|
368
|
+
// without loading the image again.
|
|
313
369
|
public void setPlaceholder(@Nullable Drawable placeholder) {
|
|
314
|
-
// None again (the placeholder prop is a url) doesn't reload.
|
|
315
|
-
if (placeholder == null && mPlaceholder == null) return;
|
|
316
|
-
mNeedsReload = true;
|
|
317
370
|
mPlaceholder = placeholder;
|
|
371
|
+
updatePlaceholder();
|
|
318
372
|
}
|
|
319
373
|
|
|
320
|
-
// The
|
|
321
|
-
// for
|
|
322
|
-
|
|
323
|
-
//
|
|
374
|
+
// The sources' ThumbHashes or blurDataUris, by the source's index (null
|
|
375
|
+
// for none): the picked source's shows instead of the placeholder prop.
|
|
376
|
+
private List<String> mSourcePlaceholders = new ArrayList<>();
|
|
377
|
+
// `placeholder` as a thumbhash or blurDataUri: shows for the sources
|
|
378
|
+
// without their own.
|
|
324
379
|
@Nullable
|
|
325
|
-
private
|
|
380
|
+
private String mPlaceholderHash = null;
|
|
326
381
|
// What shows while the image loads and if it fails: the picked source's
|
|
327
382
|
// ThumbHash or blurDataUri decoded (FastImageThumbHash), or the placeholder
|
|
328
383
|
// prop. FastImage's own drawable either way, which Glide doesn't recycle.
|
|
@@ -337,8 +392,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
337
392
|
@Nullable
|
|
338
393
|
private String mInvalidPlaceholder = null;
|
|
339
394
|
|
|
340
|
-
public void
|
|
341
|
-
|
|
395
|
+
public void setPlaceholderHash(@Nullable String hash) {
|
|
396
|
+
if (Objects.equals(hash, mPlaceholderHash)) return;
|
|
397
|
+
mPlaceholderHash = hash;
|
|
342
398
|
updatePlaceholder();
|
|
343
399
|
}
|
|
344
400
|
|
|
@@ -450,13 +506,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
450
506
|
}
|
|
451
507
|
}
|
|
452
508
|
|
|
453
|
-
// The picked source's ThumbHash or blurDataUri, or
|
|
509
|
+
// The picked source's ThumbHash or blurDataUri, or `placeholder`'s, or
|
|
510
|
+
// null.
|
|
454
511
|
@Nullable
|
|
455
512
|
private String pickedSourcePlaceholder() {
|
|
456
513
|
int index = mSources == null ? 0 : mSourceIndex;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
514
|
+
// None until one is picked.
|
|
515
|
+
if (index < 0) return null;
|
|
516
|
+
String key = index < mSourcePlaceholders.size() ? mSourcePlaceholders.get(index) : null;
|
|
517
|
+
return key != null ? key : mPlaceholderHash;
|
|
460
518
|
}
|
|
461
519
|
|
|
462
520
|
// Sets mShownPlaceholder for the picked source. A view showing the previous
|
|
@@ -1759,7 +1817,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1759
1817
|
// resolved: fire onError and onLoadEnd and show the placeholder prop,
|
|
1760
1818
|
// as on iOS (#1028, #945).
|
|
1761
1819
|
if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
|
|
1762
|
-
|
|
1820
|
+
// Without its thumbhash or blurDataUri (a data uri can be long).
|
|
1821
|
+
String error = "Invalid source: " + withoutPlaceholders(mSource);
|
|
1763
1822
|
WritableMap event = new WritableNativeMap();
|
|
1764
1823
|
event.putString("error", error);
|
|
1765
1824
|
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
package/dist/index.cjs.js
CHANGED
|
@@ -280,16 +280,6 @@ function dataUri(value) {
|
|
|
280
280
|
}
|
|
281
281
|
//#endregion
|
|
282
282
|
//#region src/index.tsx
|
|
283
|
-
function withoutPlaceholders(source) {
|
|
284
|
-
if (!hasSourcePlaceholder(source)) return source;
|
|
285
|
-
const { thumbhash: _thumbhash, blurDataUri: _blurDataUri, ...rest } = source;
|
|
286
|
-
return rest;
|
|
287
|
-
}
|
|
288
|
-
function sourcePlaceholder(source) {
|
|
289
|
-
if (!source || typeof source !== "object") return "";
|
|
290
|
-
const { thumbhash, blurDataUri } = source;
|
|
291
|
-
return thumbhash || dataUri(blurDataUri) || "";
|
|
292
|
-
}
|
|
293
283
|
const resolvePlaceholder = (placeholder) => {
|
|
294
284
|
if (typeof placeholder === "string") {
|
|
295
285
|
if (isImageName(placeholder)) return react_native.Platform.OS === "android" ? placeholder : { uri: placeholder };
|
|
@@ -361,18 +351,12 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
361
351
|
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
362
352
|
const fit = resolveObjectFit(objectFit, style);
|
|
363
353
|
const position = objectPositionOrCentered(objectPosition);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
resolvedSource: sources ? void 0 : react_native.Image.resolveAssetSource(withoutPlaceholders(single)),
|
|
371
|
-
sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
|
|
372
|
-
sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
|
|
373
|
-
};
|
|
374
|
-
}, [source, shared]);
|
|
375
|
-
const resolvedPlaceholder = (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
354
|
+
const resolvedSource = (0, react.useMemo)(() => {
|
|
355
|
+
for (const entry of [source].flat()) dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
356
|
+
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
357
|
+
return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
|
|
358
|
+
}, [source]);
|
|
359
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
376
360
|
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
|
|
377
361
|
...props,
|
|
378
362
|
ref: forwardedRef,
|
|
@@ -381,8 +365,6 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
381
365
|
loopCount: loopCount(loop),
|
|
382
366
|
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
|
|
383
367
|
source: resolvedSource,
|
|
384
|
-
sources,
|
|
385
|
-
sourcePlaceholders,
|
|
386
368
|
placeholder: resolvedPlaceholder,
|
|
387
369
|
onFastImageLoadStart: onLoadStart,
|
|
388
370
|
onFastImageProgress: onProgress,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
package/dist/index.cjs.web.js
CHANGED
|
@@ -303,7 +303,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
303
303
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
304
304
|
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
305
305
|
const [failedPlaceholder, setFailedPlaceholder] = (0, react.useState)();
|
|
306
|
-
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview
|
|
306
|
+
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
307
307
|
const [result, setResult] = (0, react.useState)();
|
|
308
308
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
309
309
|
const svgId = (0, react.useId)().replace(/[^\w-]/g, "");
|
|
@@ -416,10 +416,9 @@ function shared(source, key, preview) {
|
|
|
416
416
|
function resolveUri(source) {
|
|
417
417
|
let uri;
|
|
418
418
|
if (typeof source === "number") {
|
|
419
|
-
var _asset$scales$;
|
|
420
419
|
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
421
420
|
if (!asset) return void 0;
|
|
422
|
-
const scale =
|
|
421
|
+
const scale = assetScale(asset);
|
|
423
422
|
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
424
423
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
425
424
|
} else if (typeof source === "string") uri = source;
|
|
@@ -428,9 +427,24 @@ function resolveUri(source) {
|
|
|
428
427
|
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
429
428
|
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
430
429
|
}
|
|
431
|
-
function
|
|
430
|
+
function assetScale(asset) {
|
|
431
|
+
var _asset$scales$;
|
|
432
|
+
return asset.scales.reduce((best, scale) => Math.abs(scale - react_native.PixelRatio.get()) < Math.abs(best - react_native.PixelRatio.get()) ? scale : best, (_asset$scales$ = asset.scales[0]) !== null && _asset$scales$ !== void 0 ? _asset$scales$ : 1);
|
|
433
|
+
}
|
|
434
|
+
function sizedSource(source) {
|
|
435
|
+
if (typeof source !== "number") return source;
|
|
436
|
+
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
437
|
+
return asset ? {
|
|
438
|
+
uri: resolveUri(source),
|
|
439
|
+
width: asset.width,
|
|
440
|
+
height: asset.height,
|
|
441
|
+
scale: assetScale(asset)
|
|
442
|
+
} : void 0;
|
|
443
|
+
}
|
|
444
|
+
function sizedSources(entries) {
|
|
432
445
|
var _largest$uri, _sources$find;
|
|
433
|
-
const
|
|
446
|
+
const sources = entries.map(sizedSource);
|
|
447
|
+
const widths = sources.filter((source) => !!(source === null || source === void 0 ? void 0 : source.uri) && !!source.width).map((source) => {
|
|
434
448
|
var _source$width, _source$scale;
|
|
435
449
|
return {
|
|
436
450
|
uri: source.uri,
|
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
package/dist/index.d.ts
CHANGED
|
@@ -331,9 +331,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
331
331
|
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
332
332
|
* pass the one a view will show. An array fails with
|
|
333
333
|
* `{ ok: false, error }`.
|
|
334
|
+
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
334
335
|
* - An array of one is the same as that source.
|
|
335
336
|
*/
|
|
336
|
-
source?: Source | Source
|
|
337
|
+
source?: Source | Array<Source | ImageRequireSource> | ImageRequireSource;
|
|
337
338
|
/**
|
|
338
339
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
339
340
|
*
|
|
@@ -363,7 +364,8 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
363
364
|
*/
|
|
364
365
|
placeholder?: ImageRequireSource | string | SourcePlaceholder | PlaceholderSource;
|
|
365
366
|
/**
|
|
366
|
-
* @deprecated Use `placeholder`, which does the same.
|
|
367
|
+
* @deprecated Use `placeholder`, which does the same. With both,
|
|
368
|
+
* `placeholder` shows.
|
|
367
369
|
*/
|
|
368
370
|
defaultSource?: ImageRequireSource;
|
|
369
371
|
/**
|
package/dist/index.js
CHANGED
|
@@ -253,16 +253,6 @@ function dataUri(value) {
|
|
|
253
253
|
}
|
|
254
254
|
//#endregion
|
|
255
255
|
//#region src/index.tsx
|
|
256
|
-
function withoutPlaceholders(source) {
|
|
257
|
-
if (!hasSourcePlaceholder(source)) return source;
|
|
258
|
-
const { thumbhash: _thumbhash, blurDataUri: _blurDataUri, ...rest } = source;
|
|
259
|
-
return rest;
|
|
260
|
-
}
|
|
261
|
-
function sourcePlaceholder(source) {
|
|
262
|
-
if (!source || typeof source !== "object") return "";
|
|
263
|
-
const { thumbhash, blurDataUri } = source;
|
|
264
|
-
return thumbhash || dataUri(blurDataUri) || "";
|
|
265
|
-
}
|
|
266
256
|
const resolvePlaceholder = (placeholder) => {
|
|
267
257
|
if (typeof placeholder === "string") {
|
|
268
258
|
if (isImageName(placeholder)) return Platform.OS === "android" ? placeholder : { uri: placeholder };
|
|
@@ -334,18 +324,12 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
334
324
|
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
335
325
|
const fit = resolveObjectFit(objectFit, style);
|
|
336
326
|
const position = objectPositionOrCentered(objectPosition);
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
resolvedSource: sources ? void 0 : Image.resolveAssetSource(withoutPlaceholders(single)),
|
|
344
|
-
sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
|
|
345
|
-
sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
|
|
346
|
-
};
|
|
347
|
-
}, [source, shared]);
|
|
348
|
-
const resolvedPlaceholder = (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
327
|
+
const resolvedSource = useMemo(() => {
|
|
328
|
+
for (const entry of [source].flat()) dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
329
|
+
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
330
|
+
return Array.isArray(single) ? single.map((entry) => Image.resolveAssetSource(entry)) : Image.resolveAssetSource(single);
|
|
331
|
+
}, [source]);
|
|
332
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
349
333
|
return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
|
|
350
334
|
...props,
|
|
351
335
|
ref: forwardedRef,
|
|
@@ -354,8 +338,6 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
354
338
|
loopCount: loopCount(loop),
|
|
355
339
|
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
|
|
356
340
|
source: resolvedSource,
|
|
357
|
-
sources,
|
|
358
|
-
sourcePlaceholders,
|
|
359
341
|
placeholder: resolvedPlaceholder,
|
|
360
342
|
onFastImageLoadStart: onLoadStart,
|
|
361
343
|
onFastImageProgress: onProgress,
|
package/dist/index.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
package/dist/index.web.js
CHANGED
|
@@ -276,7 +276,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
276
276
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
277
277
|
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
278
278
|
const [failedPlaceholder, setFailedPlaceholder] = useState();
|
|
279
|
-
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview
|
|
279
|
+
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
280
280
|
const [result, setResult] = useState();
|
|
281
281
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
282
282
|
const svgId = useId().replace(/[^\w-]/g, "");
|
|
@@ -389,10 +389,9 @@ function shared(source, key, preview) {
|
|
|
389
389
|
function resolveUri(source) {
|
|
390
390
|
let uri;
|
|
391
391
|
if (typeof source === "number") {
|
|
392
|
-
var _asset$scales$;
|
|
393
392
|
const asset = getAssetByID(source);
|
|
394
393
|
if (!asset) return void 0;
|
|
395
|
-
const scale =
|
|
394
|
+
const scale = assetScale(asset);
|
|
396
395
|
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
397
396
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
398
397
|
} else if (typeof source === "string") uri = source;
|
|
@@ -401,9 +400,24 @@ function resolveUri(source) {
|
|
|
401
400
|
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
402
401
|
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
403
402
|
}
|
|
404
|
-
function
|
|
403
|
+
function assetScale(asset) {
|
|
404
|
+
var _asset$scales$;
|
|
405
|
+
return asset.scales.reduce((best, scale) => Math.abs(scale - PixelRatio.get()) < Math.abs(best - PixelRatio.get()) ? scale : best, (_asset$scales$ = asset.scales[0]) !== null && _asset$scales$ !== void 0 ? _asset$scales$ : 1);
|
|
406
|
+
}
|
|
407
|
+
function sizedSource(source) {
|
|
408
|
+
if (typeof source !== "number") return source;
|
|
409
|
+
const asset = getAssetByID(source);
|
|
410
|
+
return asset ? {
|
|
411
|
+
uri: resolveUri(source),
|
|
412
|
+
width: asset.width,
|
|
413
|
+
height: asset.height,
|
|
414
|
+
scale: assetScale(asset)
|
|
415
|
+
} : void 0;
|
|
416
|
+
}
|
|
417
|
+
function sizedSources(entries) {
|
|
405
418
|
var _largest$uri, _sources$find;
|
|
406
|
-
const
|
|
419
|
+
const sources = entries.map(sizedSource);
|
|
420
|
+
const widths = sources.filter((source) => !!(source === null || source === void 0 ? void 0 : source.uri) && !!source.width).map((source) => {
|
|
407
421
|
var _source$width, _source$scale;
|
|
408
422
|
return {
|
|
409
423
|
uri: source.uri,
|
package/dist/index.web.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
|
@@ -20,9 +20,7 @@ type LoadEndEvent = Readonly<{
|
|
|
20
20
|
}>;
|
|
21
21
|
export interface NativeProps extends ViewProps {
|
|
22
22
|
source?: CodegenTypes.UnsafeMixed;
|
|
23
|
-
sources?: CodegenTypes.UnsafeMixed;
|
|
24
23
|
placeholder?: CodegenTypes.UnsafeMixed;
|
|
25
|
-
sourcePlaceholders?: ReadonlyArray<string>;
|
|
26
24
|
objectFit?: CodegenTypes.WithDefault<string, 'cover'>;
|
|
27
25
|
objectPositionX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>;
|
|
28
26
|
objectPositionXOffset?: CodegenTypes.Float;
|
|
@@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
15
15
|
// Main thread.
|
|
16
16
|
+ (nullable UIImage*) imageForPlaceholder: (nullable NSString*)placeholder;
|
|
17
17
|
|
|
18
|
+
// Whether a placeholder is a data uri (a blurDataUri) rather than a ThumbHash.
|
|
19
|
+
+ (BOOL) isDataUri: (nullable NSString*)placeholder;
|
|
20
|
+
|
|
18
21
|
// The placeholder for a base64 ThumbHash (standard or url-safe, with or
|
|
19
22
|
// without padding), or nil for an empty or invalid one. Main thread.
|
|
20
23
|
+ (nullable UIImage*) imageForThumbHash: (nullable NSString*)thumbhash;
|
|
@@ -200,8 +200,12 @@ static UIImage* FFFBlurredPreview(UIImage* image) {
|
|
|
200
200
|
return cache;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
+ (BOOL) isDataUri: (NSString*)placeholder {
|
|
204
|
+
return placeholder && [placeholder rangeOfString: @"data:" options: NSCaseInsensitiveSearch | NSAnchoredSearch].location != NSNotFound;
|
|
205
|
+
}
|
|
206
|
+
|
|
203
207
|
+ (UIImage*) imageForPlaceholder: (NSString*)placeholder {
|
|
204
|
-
if ([
|
|
208
|
+
if (![self isDataUri: placeholder]) {
|
|
205
209
|
return [self imageForThumbHash: placeholder];
|
|
206
210
|
}
|
|
207
211
|
UIImage* cached = [[self cache] objectForKey: placeholder];
|
|
@@ -28,9 +28,8 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
|
|
|
28
28
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
|
|
29
29
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
|
|
30
30
|
@property (nonatomic, assign) FFFObjectFit objectFit;
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
// loads the one whose size is closest to its own.
|
|
31
|
+
// The `source` prop: one source, or several (sizes of one image), of which
|
|
32
|
+
// the view loads the one whose size is closest to its own.
|
|
34
33
|
@property (nonatomic, copy) NSArray<FFFastImageSource *> *sources;
|
|
35
34
|
// The `placeholder` prop.
|
|
36
35
|
@property (nonatomic, strong) UIImage *placeholder;
|
|
@@ -38,11 +37,14 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
|
|
|
38
37
|
// image, at a high priority (or only from the cache, with cache cacheOnly),
|
|
39
38
|
// it shows over the other placeholders once it has.
|
|
40
39
|
@property (nonatomic, strong) FFFastImageSource *placeholderSource;
|
|
41
|
-
// The
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
40
|
+
// The sources' ThumbHashes or blurDataUris (data uris), by the source's index
|
|
41
|
+
// (an empty string for none): the picked source's shows instead of
|
|
42
|
+
// placeholder. Set apart from the sources, so a new one for the same image
|
|
43
|
+
// doesn't load it again.
|
|
45
44
|
@property (nonatomic, copy) NSArray<NSString *> *sourcePlaceholders;
|
|
45
|
+
// `placeholder` as a thumbhash or blurDataUri: shows for the sources without
|
|
46
|
+
// their own.
|
|
47
|
+
@property (nonatomic, copy) NSString *placeholderHash;
|
|
46
48
|
@property (nonatomic, strong) UIColor *imageColor;
|
|
47
49
|
// When it changes, the next image doesn't replace the current one: the view
|
|
48
50
|
// clears first (for views reused for other content, like list rows).
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
// downsample, or several sources: a load waits for the view's size (see
|
|
45
45
|
// didSetProps).
|
|
46
46
|
@property(nonatomic, assign) BOOL waitsForSize;
|
|
47
|
-
// The `source` prop
|
|
48
|
-
// the view's size
|
|
49
|
-
@property(nonatomic, strong) FFFastImageSource*
|
|
47
|
+
// The source that loads: the `source` prop's, or with several the one
|
|
48
|
+
// picked for the view's size.
|
|
49
|
+
@property(nonatomic, strong) FFFastImageSource* source;
|
|
50
50
|
// Several sources: the next load switches to another one for a new view
|
|
51
51
|
// size, and the one after it is loading. It's the same picture at another
|
|
52
52
|
// size, so it doesn't fade in.
|
|
@@ -749,21 +749,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
749
749
|
return event;
|
|
750
750
|
}
|
|
751
751
|
|
|
752
|
-
- (void) setSource: (FFFastImageSource*)source {
|
|
753
|
-
if (self.propSource != source) {
|
|
754
|
-
self.propSource = source;
|
|
755
|
-
if (![self picksSource]) {
|
|
756
|
-
_source = source;
|
|
757
|
-
}
|
|
758
|
-
_needsReload = YES;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
752
|
- (void) setSources: (NSArray<FFFastImageSource*>*)sources {
|
|
763
753
|
if (_sources != sources) {
|
|
764
754
|
_sources = [sources copy];
|
|
765
755
|
if (![self picksSource]) {
|
|
766
|
-
_source =
|
|
756
|
+
_source = _sources.firstObject;
|
|
767
757
|
}
|
|
768
758
|
_needsReload = YES;
|
|
769
759
|
}
|
|
@@ -847,11 +837,16 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
847
837
|
return [_dynamicRangeLimit isEqualToString: @"constrained"] || [_dynamicRangeLimit isEqualToString: @"no-limit"];
|
|
848
838
|
}
|
|
849
839
|
|
|
840
|
+
// Shows at once, as a new source placeholder does (see updateHashImage),
|
|
841
|
+
// without loading the image again.
|
|
850
842
|
- (void) setPlaceholder: (UIImage*)placeholder {
|
|
851
|
-
if (_placeholder
|
|
852
|
-
|
|
853
|
-
_needsReload = YES;
|
|
843
|
+
if (_placeholder == placeholder) {
|
|
844
|
+
return;
|
|
854
845
|
}
|
|
846
|
+
UIImage* previous = [self shownPlaceholder];
|
|
847
|
+
BOOL showing = self.showsPlaceholder;
|
|
848
|
+
_placeholder = placeholder;
|
|
849
|
+
[self showChangedPlaceholder: previous showing: showing];
|
|
855
850
|
}
|
|
856
851
|
|
|
857
852
|
- (void) setSourcePlaceholders: (NSArray<NSString*>*)sourcePlaceholders {
|
|
@@ -859,6 +854,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
859
854
|
[self updateHashImage];
|
|
860
855
|
}
|
|
861
856
|
|
|
857
|
+
- (void) setPlaceholderHash: (NSString*)placeholderHash {
|
|
858
|
+
_placeholderHash = [placeholderHash copy];
|
|
859
|
+
[self updateHashImage];
|
|
860
|
+
}
|
|
861
|
+
|
|
862
862
|
// What shows while the image loads and if it fails: placeholderSource's
|
|
863
863
|
// image once it has loaded, the picked source's placeholder (its ThumbHash or
|
|
864
864
|
// blurDataUri), or the placeholder prop.
|
|
@@ -912,6 +912,9 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
912
912
|
}
|
|
913
913
|
|
|
914
914
|
- (void) setPlaceholderSource: (FFFastImageSource*)placeholderSource {
|
|
915
|
+
if (!placeholderSource && !_placeholderSource) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
915
918
|
_placeholderSource = placeholderSource;
|
|
916
919
|
[self cancelUrlPlaceholderLoad];
|
|
917
920
|
[self showUrlPlaceholderImage: nil];
|
|
@@ -979,14 +982,18 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
979
982
|
[self showChangedPlaceholder: previous showing: showing];
|
|
980
983
|
}
|
|
981
984
|
|
|
982
|
-
// The picked source's ThumbHash or blurDataUri, or nil.
|
|
985
|
+
// The picked source's ThumbHash or blurDataUri, or placeholderHash, or nil.
|
|
983
986
|
- (NSString*) pickedSourcePlaceholder {
|
|
984
987
|
NSUInteger index = 0;
|
|
985
988
|
if ([self picksSource]) {
|
|
986
989
|
index = _source ? [_sources indexOfObjectIdenticalTo: _source] : NSNotFound;
|
|
990
|
+
// None until one is picked.
|
|
991
|
+
if (index == NSNotFound) {
|
|
992
|
+
return nil;
|
|
993
|
+
}
|
|
987
994
|
}
|
|
988
995
|
NSString* hash = index < _sourcePlaceholders.count ? _sourcePlaceholders[index] : nil;
|
|
989
|
-
return hash.length > 0 ? hash : nil;
|
|
996
|
+
return hash.length > 0 ? hash : _placeholderHash.length > 0 ? _placeholderHash : nil;
|
|
990
997
|
}
|
|
991
998
|
|
|
992
999
|
// Decodes the picked source's placeholder. A view showing the previous
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#import <react/renderer/components/RNFastImageSpec/RCTComponentViewHelpers.h>
|
|
9
9
|
|
|
10
10
|
#import "FFFastImageView.h"
|
|
11
|
+
#import "FFFThumbHash.h"
|
|
11
12
|
#import "RCTConvert+FFFastImage.h"
|
|
12
13
|
|
|
13
14
|
using namespace facebook::react;
|
|
@@ -102,6 +103,41 @@ static id _Nullable FFFJSON(const folly::dynamic &value)
|
|
|
102
103
|
return value.isNull() ? nil : FFFObjectFromDynamic(value);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// A source (or `placeholder`) without its thumbhash and blurDataUri, to
|
|
107
|
+
// compare: one source, or each of several.
|
|
108
|
+
static folly::dynamic FFFWithoutPlaceholders(const folly::dynamic &value)
|
|
109
|
+
{
|
|
110
|
+
if (value.isArray()) {
|
|
111
|
+
folly::dynamic result = folly::dynamic::array;
|
|
112
|
+
for (const auto &entry : value) {
|
|
113
|
+
result.push_back(FFFWithoutPlaceholders(entry));
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
if (!value.isObject()) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
folly::dynamic result = value;
|
|
121
|
+
result.erase("thumbhash");
|
|
122
|
+
result.erase("blurDataUri");
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A source's (or `placeholder`'s) ThumbHash, or its blurDataUri if it's a data
|
|
127
|
+
// uri (JS warns about another), or nil.
|
|
128
|
+
static NSString *_Nullable FFFSourcePlaceholder(NSDictionary *source)
|
|
129
|
+
{
|
|
130
|
+
id thumbhash = source[@"thumbhash"];
|
|
131
|
+
if ([thumbhash isKindOfClass:[NSString class]] && [thumbhash length] > 0) {
|
|
132
|
+
return thumbhash;
|
|
133
|
+
}
|
|
134
|
+
id blurDataUri = source[@"blurDataUri"];
|
|
135
|
+
if ([blurDataUri isKindOfClass:[NSString class]] && [FFFThumbHash isDataUri:blurDataUri]) {
|
|
136
|
+
return blurDataUri;
|
|
137
|
+
}
|
|
138
|
+
return nil;
|
|
139
|
+
}
|
|
140
|
+
|
|
105
141
|
// Whether the placeholder prop is a url to load apart from the image (a
|
|
106
142
|
// source with an http or https uri), rather than an image to show at once: a
|
|
107
143
|
// bundled image (served by the packager in development), a file or a data uri.
|
|
@@ -242,25 +278,36 @@ typedef NS_OPTIONS(int32_t, FFFEvent) {
|
|
|
242
278
|
FFFastImageView *view = _imageView;
|
|
243
279
|
|
|
244
280
|
if (all || oldViewProps.source != newViewProps.source) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
281
|
+
// One source, or several (an array). Only a change besides their
|
|
282
|
+
// thumbhashes and blurDataUris loads again: a new one for the same
|
|
283
|
+
// image (e.g. one that arrives later) shows at once.
|
|
284
|
+
BOOL loads = all || FFFWithoutPlaceholders(oldViewProps.source) != FFFWithoutPlaceholders(newViewProps.source);
|
|
285
|
+
id json = FFFJSON(newViewProps.source);
|
|
286
|
+
NSArray *entries = [json isKindOfClass:[NSArray class]] ? json : json ? @[ json ] : @[];
|
|
287
|
+
NSMutableArray<FFFastImageSource *> *sources = [NSMutableArray arrayWithCapacity:entries.count];
|
|
288
|
+
NSMutableArray<NSString *> *placeholders = [NSMutableArray arrayWithCapacity:entries.count];
|
|
289
|
+
for (id entry in entries) {
|
|
290
|
+
if ([entry isKindOfClass:[NSDictionary class]]) {
|
|
291
|
+
if (loads) {
|
|
292
|
+
[sources addObject:[RCTConvert FFFastImageSource:entry]];
|
|
293
|
+
}
|
|
294
|
+
[placeholders addObject:FFFSourcePlaceholder(entry) ?: @""];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (loads) {
|
|
298
|
+
view.sources = sources.count > 0 ? sources : nil;
|
|
299
|
+
}
|
|
300
|
+
view.sourcePlaceholders = placeholders;
|
|
250
301
|
}
|
|
251
302
|
if (all || oldViewProps.placeholder != newViewProps.placeholder) {
|
|
303
|
+
// A thumbhash or blurDataUri, a url to load, or an image to show.
|
|
252
304
|
id placeholder = FFFJSON(newViewProps.placeholder);
|
|
253
|
-
BOOL
|
|
254
|
-
|
|
305
|
+
BOOL hash = [placeholder isKindOfClass:[NSDictionary class]] && (placeholder[@"thumbhash"] || placeholder[@"blurDataUri"]);
|
|
306
|
+
BOOL loads = !hash && FFFIsUrlPlaceholder(placeholder);
|
|
307
|
+
view.placeholderHash = hash ? FFFSourcePlaceholder(placeholder) : nil;
|
|
308
|
+
view.placeholder = placeholder && !hash && !loads ? [RCTConvert UIImage:placeholder] : nil;
|
|
255
309
|
view.placeholderSource = loads ? [RCTConvert FFFastImageSource:placeholder] : nil;
|
|
256
310
|
}
|
|
257
|
-
if (all || oldViewProps.sourcePlaceholders != newViewProps.sourcePlaceholders) {
|
|
258
|
-
NSMutableArray<NSString *> *placeholders = [NSMutableArray arrayWithCapacity:newViewProps.sourcePlaceholders.size()];
|
|
259
|
-
for (const auto &placeholder : newViewProps.sourcePlaceholders) {
|
|
260
|
-
[placeholders addObject:[NSString stringWithUTF8String:placeholder.c_str()]];
|
|
261
|
-
}
|
|
262
|
-
view.sourcePlaceholders = placeholders.count > 0 ? placeholders : nil;
|
|
263
|
-
}
|
|
264
311
|
if (all || oldViewProps.objectFit != newViewProps.objectFit) {
|
|
265
312
|
view.objectFit = [RCTConvert FFFObjectFit:FFFString(newViewProps.objectFit)];
|
|
266
313
|
}
|
package/package.json
CHANGED
package/src/index.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
package/src/index.tsx
CHANGED
|
@@ -362,9 +362,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
362
362
|
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
363
363
|
* pass the one a view will show. An array fails with
|
|
364
364
|
* `{ ok: false, error }`.
|
|
365
|
+
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
365
366
|
* - An array of one is the same as that source.
|
|
366
367
|
*/
|
|
367
|
-
source?: Source | Source
|
|
368
|
+
source?: Source | Array<Source | ImageRequireSource> | ImageRequireSource
|
|
368
369
|
/**
|
|
369
370
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
370
371
|
*
|
|
@@ -398,7 +399,8 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
398
399
|
| SourcePlaceholder
|
|
399
400
|
| PlaceholderSource
|
|
400
401
|
/**
|
|
401
|
-
* @deprecated Use `placeholder`, which does the same.
|
|
402
|
+
* @deprecated Use `placeholder`, which does the same. With both,
|
|
403
|
+
* `placeholder` shows.
|
|
402
404
|
*/
|
|
403
405
|
defaultSource?: ImageRequireSource
|
|
404
406
|
/**
|
|
@@ -707,25 +709,6 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
707
709
|
children?: "FastImage doesn't take children: use FastImageBackground for content over an image"
|
|
708
710
|
}
|
|
709
711
|
|
|
710
|
-
// A source without its thumbhash and blurDataUri.
|
|
711
|
-
function withoutPlaceholders<T>(source: T): T {
|
|
712
|
-
if (!hasSourcePlaceholder(source)) return source
|
|
713
|
-
const {
|
|
714
|
-
thumbhash: _thumbhash,
|
|
715
|
-
blurDataUri: _blurDataUri,
|
|
716
|
-
...rest
|
|
717
|
-
} = source as Source
|
|
718
|
-
return rest as T
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// What the native view shows while a source loads: its ThumbHash (decoded
|
|
722
|
-
// natively), or its blurDataUri, or '' for neither.
|
|
723
|
-
function sourcePlaceholder(source: unknown): string {
|
|
724
|
-
if (!source || typeof source !== 'object') return ''
|
|
725
|
-
const { thumbhash, blurDataUri } = source as Source
|
|
726
|
-
return thumbhash || dataUri(blurDataUri) || ''
|
|
727
|
-
}
|
|
728
|
-
|
|
729
712
|
const resolvePlaceholder = (
|
|
730
713
|
// A source object here has no url (e.g. { uri: name }, a bundled image).
|
|
731
714
|
placeholder?: ImageRequireSource | string | PlaceholderSource,
|
|
@@ -911,53 +894,32 @@ function FastImageBase({
|
|
|
911
894
|
const fit = resolveObjectFit(objectFit, style)
|
|
912
895
|
const position = objectPositionOrCentered(objectPosition)
|
|
913
896
|
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
// Several sources are picked from natively, for the view's size; one
|
|
924
|
-
// in an array is a plain source.
|
|
925
|
-
const sources = Array.isArray(source)
|
|
926
|
-
? source.length === 1
|
|
927
|
-
? undefined
|
|
928
|
-
: source
|
|
929
|
-
: undefined
|
|
930
|
-
const single = Array.isArray(source) ? source[0] : source
|
|
931
|
-
// Sources' thumbhashes and blurDataUris go to the native view apart
|
|
932
|
-
// from the sources, so a new one for the same image doesn't load it
|
|
933
|
-
// again, by the source's index ('' for none). A source's own wins
|
|
934
|
-
// over `placeholder`'s.
|
|
935
|
-
const placeholders = (sources ?? [single]).map(
|
|
936
|
-
(entry) => sourcePlaceholder(entry) || shared,
|
|
937
|
-
)
|
|
938
|
-
return {
|
|
939
|
-
resolvedSource: sources
|
|
940
|
-
? undefined
|
|
941
|
-
: Image.resolveAssetSource(withoutPlaceholders(single) as any),
|
|
942
|
-
sources: sources?.some(hasSourcePlaceholder)
|
|
943
|
-
? sources.map(withoutPlaceholders)
|
|
944
|
-
: sources,
|
|
945
|
-
sourcePlaceholders: placeholders.some(Boolean)
|
|
946
|
-
? placeholders
|
|
947
|
-
: undefined,
|
|
897
|
+
// One source, or several in an array, picked from natively for the
|
|
898
|
+
// view's size (one in an array is a plain source), with their thumbhashes
|
|
899
|
+
// and blurDataUris: the native view doesn't load the image again when
|
|
900
|
+
// only those change. Memoized on source, so an unchanged source reaches
|
|
901
|
+
// the native view as the same object, and React's prop diff stops at it.
|
|
902
|
+
const resolvedSource = useMemo(() => {
|
|
903
|
+
// A blurDataUri that isn't a data uri is ignored, with a warning.
|
|
904
|
+
for (const entry of [source].flat()) {
|
|
905
|
+
dataUri((entry as Source | undefined)?.blurDataUri)
|
|
948
906
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
)
|
|
907
|
+
const single =
|
|
908
|
+
Array.isArray(source) && source.length === 1 ? source[0] : source
|
|
909
|
+
return Array.isArray(single)
|
|
910
|
+
? single.map((entry) => Image.resolveAssetSource(entry as any))
|
|
911
|
+
: Image.resolveAssetSource(single as any)
|
|
912
|
+
}, [source])
|
|
913
|
+
// `placeholder` as the native view takes it: a thumbhash or blurDataUri
|
|
914
|
+
// as it is (for the sources without their own), a url as a source loaded
|
|
915
|
+
// apart from the image, or an image shown at once. defaultSource only
|
|
916
|
+
// applies without one (an empty thumbhash or blurDataUri is none).
|
|
917
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder)
|
|
918
|
+
? placeholder.thumbhash || dataUri(placeholder.blurDataUri)
|
|
919
|
+
? placeholder
|
|
920
|
+
: resolvePlaceholder(defaultSource)
|
|
921
|
+
: (placeholderSource(placeholder) ??
|
|
922
|
+
resolvePlaceholder(placeholder ?? defaultSource))
|
|
961
923
|
|
|
962
924
|
return (
|
|
963
925
|
<FastImageView
|
|
@@ -974,8 +936,6 @@ function FastImageBase({
|
|
|
974
936
|
defaultTransition ?? builtInTransition(),
|
|
975
937
|
)}
|
|
976
938
|
source={resolvedSource}
|
|
977
|
-
sources={sources}
|
|
978
|
-
sourcePlaceholders={sourcePlaceholders}
|
|
979
939
|
placeholder={resolvedPlaceholder}
|
|
980
940
|
onFastImageLoadStart={onLoadStart}
|
|
981
941
|
onFastImageProgress={onProgress}
|
package/src/index.web.tsx
CHANGED
|
@@ -141,14 +141,18 @@ function FastImageBase({
|
|
|
141
141
|
// like any image: it can't look in its cache only (cache: 'cacheOnly').
|
|
142
142
|
const urlPlaceholder = placeholderSource(placeholder)?.uri
|
|
143
143
|
const [failedPlaceholder, setFailedPlaceholder] = useState<string>()
|
|
144
|
-
//
|
|
145
|
-
//
|
|
144
|
+
// defaultSource only shows without `placeholder`, as on iOS and Android,
|
|
145
|
+
// or in place of a thumbhash or blurDataUri the web can't show (one of
|
|
146
|
+
// several sizes', or a thumbhash without react-native-fast-image/thumbhash,
|
|
147
|
+
// or an empty one). Not with a url (above).
|
|
146
148
|
const defaultUri =
|
|
147
149
|
blurUri ??
|
|
148
150
|
resolveUri(
|
|
149
|
-
preview
|
|
151
|
+
preview
|
|
150
152
|
? defaultSource
|
|
151
|
-
:
|
|
153
|
+
: urlPlaceholder
|
|
154
|
+
? undefined
|
|
155
|
+
: (placeholder ?? defaultSource),
|
|
152
156
|
)
|
|
153
157
|
// Whether the image for `key` loaded, or failed.
|
|
154
158
|
const [result, setResult] = useState<{ key: string; ok: boolean }>()
|
|
@@ -341,14 +345,7 @@ function resolveUri(source: unknown): string | undefined {
|
|
|
341
345
|
if (typeof source === 'number') {
|
|
342
346
|
const asset: PackagerAsset | undefined = getAssetByID(source)
|
|
343
347
|
if (!asset) return undefined
|
|
344
|
-
const scale = asset
|
|
345
|
-
(best, scale) =>
|
|
346
|
-
Math.abs(scale - PixelRatio.get()) <
|
|
347
|
-
Math.abs(best - PixelRatio.get())
|
|
348
|
-
? scale
|
|
349
|
-
: best,
|
|
350
|
-
asset.scales[0] ?? 1,
|
|
351
|
-
)
|
|
348
|
+
const scale = assetScale(asset)
|
|
352
349
|
const suffix = scale !== 1 ? `@${scale}x` : ''
|
|
353
350
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`
|
|
354
351
|
} else if (typeof source === 'string') {
|
|
@@ -370,6 +367,36 @@ type PackagerAsset = {
|
|
|
370
367
|
name: string
|
|
371
368
|
type: string
|
|
372
369
|
scales: number[]
|
|
370
|
+
width?: number
|
|
371
|
+
height?: number
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// The scale of an asset's file closest to the screen's.
|
|
375
|
+
function assetScale(asset: PackagerAsset): number {
|
|
376
|
+
return asset.scales.reduce(
|
|
377
|
+
(best, scale) =>
|
|
378
|
+
Math.abs(scale - PixelRatio.get()) <
|
|
379
|
+
Math.abs(best - PixelRatio.get())
|
|
380
|
+
? scale
|
|
381
|
+
: best,
|
|
382
|
+
asset.scales[0] ?? 1,
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// One of several sizes: a require()d image in react-native-web's asset
|
|
387
|
+
// registry as a source with its file's size (Expo's web builds make it a
|
|
388
|
+
// { uri, width, height } object already).
|
|
389
|
+
function sizedSource(source: Source | number): Source | undefined {
|
|
390
|
+
if (typeof source !== 'number') return source
|
|
391
|
+
const asset: PackagerAsset | undefined = getAssetByID(source)
|
|
392
|
+
return asset
|
|
393
|
+
? {
|
|
394
|
+
uri: resolveUri(source),
|
|
395
|
+
width: asset.width,
|
|
396
|
+
height: asset.height,
|
|
397
|
+
scale: assetScale(asset),
|
|
398
|
+
}
|
|
399
|
+
: undefined
|
|
373
400
|
}
|
|
374
401
|
|
|
375
402
|
// Several sizes of an image, as a srcset (each size's width, times its
|
|
@@ -379,9 +406,10 @@ type PackagerAsset = {
|
|
|
379
406
|
// is laid out at; it only applies to lazy images, and otherwise, or in
|
|
380
407
|
// browsers without it, the next value, 100vw (the viewport's width). src is the largest, for browsers without
|
|
381
408
|
// srcset.
|
|
382
|
-
function sizedSources(
|
|
409
|
+
function sizedSources(entries: (Source | number)[]) {
|
|
410
|
+
const sources = entries.map(sizedSource)
|
|
383
411
|
const widths = sources
|
|
384
|
-
.filter((source) => source?.uri && source.width)
|
|
412
|
+
.filter((source): source is Source => !!source?.uri && !!source.width)
|
|
385
413
|
.map((source) => ({
|
|
386
414
|
uri: source.uri as string,
|
|
387
415
|
width: Math.round((source.width ?? 0) * (source.scale ?? 1)),
|
|
@@ -40,21 +40,18 @@ type LoadEndEvent = Readonly<{
|
|
|
40
40
|
}>
|
|
41
41
|
|
|
42
42
|
export interface NativeProps extends ViewProps {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
43
|
+
// The `source` prop: a source ({ uri, headers, priority, cache, ... },
|
|
44
|
+
// with its thumbhash or blurDataUri), or several (sizes of one image) in
|
|
45
|
+
// an array, picked from for the view's size. A change to the thumbhashes
|
|
46
|
+
// and blurDataUris only doesn't load the image again.
|
|
45
47
|
source?: CodegenTypes.UnsafeMixed
|
|
46
|
-
//
|
|
47
|
-
sources
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
48
|
+
// The `placeholder` prop: a thumbhash or blurDataUri ({ thumbhash } or
|
|
49
|
+
// { blurDataUri }, for the sources without their own), a url to load
|
|
50
|
+
// apart from the image, as a source ({ uri, headers, cacheKey, cache }),
|
|
51
|
+
// or an image shown at once: a resolved asset source ({ uri, width,
|
|
52
|
+
// height, scale }) or a data uri ({ uri } on iOS; the uri on Android, as
|
|
53
|
+
// a string).
|
|
52
54
|
placeholder?: CodegenTypes.UnsafeMixed
|
|
53
|
-
// What shows in place of placeholder while a source loads: its ThumbHash,
|
|
54
|
-
// or its blurDataUri (blurred natively), by the source's index ('' for
|
|
55
|
-
// none; one for a single source). Apart from the sources, so a new one
|
|
56
|
-
// for the same image doesn't load it again.
|
|
57
|
-
sourcePlaceholders?: ReadonlyArray<string>
|
|
58
55
|
// objectFit, resolved (fill, contain, cover, none or scale-down).
|
|
59
56
|
objectFit?: CodegenTypes.WithDefault<string, 'cover'>
|
|
60
57
|
// objectPosition, as CSS computes it: on each axis the image's left or top
|