@octane-xplat/gif 0.0.0-nightly.g2e22171
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/LICENSE +21 -0
- package/README.md +72 -0
- package/package.json +85 -0
- package/src/AnimatedImage.macos.tsrx +7 -0
- package/src/AnimatedImage.tsrx +30 -0
- package/src/AnimatedImage.web.tsrx +27 -0
- package/src/AnimatedImage.windows.tsrx +8 -0
- package/src/escape-props.ts +15 -0
- package/src/escape-props.web.ts +26 -0
- package/src/index.macos.ts +3 -0
- package/src/index.ts +5 -0
- package/src/index.web.ts +5 -0
- package/src/index.windows.ts +5 -0
- package/src/native-elements.d.ts +8 -0
- package/src/prefetch-batch.ts +55 -0
- package/src/prefetch.macos.ts +8 -0
- package/src/prefetch.ts +38 -0
- package/src/prefetch.web.ts +21 -0
- package/src/prefetch.windows.ts +8 -0
- package/src/props.ts +34 -0
- package/types/generated/.tsrx-typegen-manifest.json +16 -0
- package/types/generated/AnimatedImage.d.ts +6 -0
- package/types/generated/AnimatedImage.web.d.ts +5 -0
- package/types/generated/escape-props.d.ts +7 -0
- package/types/generated/escape-props.web.d.ts +6 -0
- package/types/generated/index.d.ts +3 -0
- package/types/generated/index.web.d.ts +3 -0
- package/types/generated/native-elements.d.ts +8 -0
- package/types/generated/prefetch-batch.d.ts +10 -0
- package/types/generated/prefetch.d.ts +12 -0
- package/types/generated/prefetch.web.d.ts +7 -0
- package/types/generated/props.d.ts +33 -0
- package/types/index.macos.d.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# `@octane-xplat/gif`
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
pnpm add @octane-xplat/gif
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Animated images (GIF, animated WebP) for Octane xplat apps: a plain
|
|
8
|
+
`<img>` on web and `@nativescript-community/ui-image`
|
|
9
|
+
(Fresco on Android / SDWebImage on iOS) on native, with a macOS entry.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { AnimatedImage } from '@octane-xplat/gif'
|
|
13
|
+
|
|
14
|
+
;<AnimatedImage
|
|
15
|
+
src="https://example.com/loader.gif"
|
|
16
|
+
alt="Loading"
|
|
17
|
+
stretch="aspectFit" // 'none' | 'fill' | 'aspectFit' | 'aspectFill'
|
|
18
|
+
width={96}
|
|
19
|
+
height={96}
|
|
20
|
+
/>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`stretch` shares the core `Image` grammar — web maps it to `object-fit`
|
|
24
|
+
and the plugin accepts the same values natively. For Lottie JSON
|
|
25
|
+
animations use [`@octane-xplat/lottie`](../lottie/README.md) instead.
|
|
26
|
+
|
|
27
|
+
## Prefetch
|
|
28
|
+
|
|
29
|
+
`prefetch` warms the image engine's disk cache for URLs you expect to show
|
|
30
|
+
soon — bytes on disk now, decode still at display time:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { prefetch } from '@octane-xplat/gif'
|
|
34
|
+
|
|
35
|
+
await prefetch(feed.map((post) => post.imageUrl)) // false if any URL fails
|
|
36
|
+
|
|
37
|
+
await prefetch(urls, {
|
|
38
|
+
headers: { Authorization: 'Bearer …' }, // Android only — see below
|
|
39
|
+
concurrency: 5, // max parallel fetches (default)
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
On Android that is Fresco's `prefetchToDiskCache`; on iOS, SDWebImage's
|
|
44
|
+
prefetcher storing to disk. On web it loads each URL through a throwaway
|
|
45
|
+
`<img>` so the browser HTTP cache is warm. It resolves `false` when any URL
|
|
46
|
+
fails — and always `false` on macOS, where the AppKit host has no image
|
|
47
|
+
pipeline yet. Same contract as `@octane-xplat/image`'s `prefetch`.
|
|
48
|
+
|
|
49
|
+
Options: `headers` is forwarded to the engine — **honored on Android,
|
|
50
|
+
dropped on iOS** (the plugin's prefetch path lacks the request-modifier
|
|
51
|
+
branch its display path has; an upstream patch is needed) — and ignored on
|
|
52
|
+
web. `concurrency` caps parallel fetches JS-side (default 5).
|
|
53
|
+
|
|
54
|
+
## Platform props
|
|
55
|
+
|
|
56
|
+
`ios`, `android`, and `web` escape bags apply after the shared props, the
|
|
57
|
+
same convention as `@octane-xplat/ui`. Native bags assign onto the ui-image
|
|
58
|
+
`Img` view, so engine props such as `noCache`, `decodeWidth`,
|
|
59
|
+
`decodeHeight`, `placeholderImageUri`, and `progressiveRenderingEnabled`
|
|
60
|
+
are reachable where the engine supports them:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<AnimatedImage
|
|
64
|
+
src={photo.url}
|
|
65
|
+
ios={{ noCache: true }}
|
|
66
|
+
android={{ decodeWidth: 288, decodeHeight: 288 }}
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Per-target limits are recorded in
|
|
71
|
+
[known limits](../../docs/verify/known-limits.md). Exercised by
|
|
72
|
+
[`AnimatedImageDemo`](../demos/src/AnimatedImageDemo.tsrx).
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octane-xplat/gif",
|
|
3
|
+
"version": "0.0.0-nightly.g2e22171",
|
|
4
|
+
"description": "Animated images (GIF/animated webp) for Octane xplat — web <img> + @nativescript-community/ui-image",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"android",
|
|
7
|
+
"animated-image",
|
|
8
|
+
"animated-webp",
|
|
9
|
+
"cross-platform",
|
|
10
|
+
"gif",
|
|
11
|
+
"image",
|
|
12
|
+
"ios",
|
|
13
|
+
"nativescript",
|
|
14
|
+
"octane",
|
|
15
|
+
"web",
|
|
16
|
+
"xplat"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/gif",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
23
|
+
"directory": "packages/gif"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"types",
|
|
27
|
+
"src",
|
|
28
|
+
"!src/**/*.test.*",
|
|
29
|
+
"!src/**/*.spec.*"
|
|
30
|
+
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"macos": {
|
|
35
|
+
"types": "./types/index.macos.d.ts",
|
|
36
|
+
"default": "./src/index.macos.ts"
|
|
37
|
+
},
|
|
38
|
+
"web": {
|
|
39
|
+
"types": "./types/generated/index.web.d.ts",
|
|
40
|
+
"default": "./src/index.web.ts"
|
|
41
|
+
},
|
|
42
|
+
"native": {
|
|
43
|
+
"types": "./types/generated/index.d.ts",
|
|
44
|
+
"default": "./src/index.ts"
|
|
45
|
+
},
|
|
46
|
+
"types": "./types/generated/index.d.ts",
|
|
47
|
+
"default": "./src/index.ts"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@nativescript-community/ui-image": "4.6.20"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@nativescript-community/octane": "0.2.5",
|
|
58
|
+
"@nativescript/core": "9.1.3",
|
|
59
|
+
"@octane-xplat/macos-renderer": "0.0.0-nightly.g2e22171",
|
|
60
|
+
"@octanejs/vite-plugin": "0.1.61",
|
|
61
|
+
"@tsrx/typescript-plugin": "0.4.11",
|
|
62
|
+
"octane": "0.11.0",
|
|
63
|
+
"tsrx-typegen": "0.3.0",
|
|
64
|
+
"typescript": "5.9.3",
|
|
65
|
+
"vite": "8.3.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
69
|
+
"@nativescript/core": ">=9.1.0 <10",
|
|
70
|
+
"octane": ">=0.6.3 <1"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@nativescript-community/octane": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@nativescript/core": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"test:packed": "node tests/packed-consumer.mjs",
|
|
82
|
+
"typegen": "tsrx-typegen --target octane",
|
|
83
|
+
"pack:check": "tsrx-typegen --pack-check"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** @jsxImportSource @octane-xplat/macos-renderer */
|
|
2
|
+
import type { AnimatedImageProps } from './props'
|
|
3
|
+
|
|
4
|
+
/** Show an explicit placeholder until the AppKit host has an animation view. */
|
|
5
|
+
export function AnimatedImage(_props: AnimatedImageProps) @{
|
|
6
|
+
<label className="vx-unsupported">Animated images are unsupported by the AppKit host</label>
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { registerElement } from '@nativescript-community/octane';
|
|
3
|
+
import { Img } from '@nativescript-community/ui-image';
|
|
4
|
+
import type { AnimatedImageProps } from './props';
|
|
5
|
+
import { applyEscapeProps } from './escape-props';
|
|
6
|
+
|
|
7
|
+
// @nativescript-community/ui-image — Fresco DraweeView on Android,
|
|
8
|
+
// SDAnimatedImageView on iOS when `animatedImageView` is set (plain UIImageView
|
|
9
|
+
// otherwise, which would still decode but not animate).
|
|
10
|
+
registerElement('animatedimage', Img);
|
|
11
|
+
|
|
12
|
+
/** AnimatedImage — GIF/animated-webp decode + autoplay via ui-image. `src`
|
|
13
|
+
* takes the Img grammar: remote URL, res://name, ~/bundle path, file path.
|
|
14
|
+
* There is intentionally no play/pause prop — <img> can't pause on web, so
|
|
15
|
+
* the shared contract is always-playing. */
|
|
16
|
+
export function AnimatedImage(props: AnimatedImageProps) @{
|
|
17
|
+
<animatedimage
|
|
18
|
+
ref={(v: any) => applyEscapeProps(v, props)}
|
|
19
|
+
id={props.id}
|
|
20
|
+
className={props.className}
|
|
21
|
+
style={props.style}
|
|
22
|
+
src={props.src}
|
|
23
|
+
stretch={props.stretch ?? 'aspectFit'}
|
|
24
|
+
width={props.width}
|
|
25
|
+
height={props.height}
|
|
26
|
+
animatedImageView={true}
|
|
27
|
+
autoPlayAnimations={true}
|
|
28
|
+
accessibilityLabel={props.alt}
|
|
29
|
+
/>
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnimatedImageProps } from './props';
|
|
2
|
+
import { applyEscapeProps } from './escape-props.web';
|
|
3
|
+
|
|
4
|
+
const OBJECT_FIT = {
|
|
5
|
+
none: 'none',
|
|
6
|
+
fill: 'fill',
|
|
7
|
+
aspectFit: 'contain',
|
|
8
|
+
aspectFill: 'cover',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
/** AnimatedImage — a plain <img>; browsers animate GIF/webp natively.
|
|
12
|
+
* `stretch` maps to object-fit. */
|
|
13
|
+
export function AnimatedImage(props: AnimatedImageProps) @{
|
|
14
|
+
<img
|
|
15
|
+
ref={(e: any) => applyEscapeProps(e, props)}
|
|
16
|
+
id={props.id}
|
|
17
|
+
className={props.className}
|
|
18
|
+
style={{
|
|
19
|
+
objectFit: OBJECT_FIT[props.stretch ?? 'aspectFit'],
|
|
20
|
+
width: props.width,
|
|
21
|
+
height: props.height,
|
|
22
|
+
...props.style,
|
|
23
|
+
}}
|
|
24
|
+
src={props.src}
|
|
25
|
+
alt={props.alt}
|
|
26
|
+
/>
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import type { AnimatedImageProps } from './props'
|
|
3
|
+
|
|
4
|
+
/** Unsupported placeholder — the Windows host has no animated-image engine
|
|
5
|
+
* (ui-image ships iOS/Android only). */
|
|
6
|
+
export function AnimatedImage(_props: AnimatedImageProps) @{
|
|
7
|
+
<label className="vx-unsupported" text="Animated images are unsupported on the Windows host" />
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isAndroid, isIOS } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
/** Apply only the active device's escape bag after the shared props — the
|
|
4
|
+
* same convention @octane-xplat/ui uses, kept local so this leaf does not
|
|
5
|
+
* depend on ui. */
|
|
6
|
+
export function applyEscapeProps(view: any, props: { ios?: any; android?: any }): void {
|
|
7
|
+
if (!view) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const bag = isIOS ? props.ios : isAndroid ? props.android : undefined
|
|
12
|
+
if (bag) {
|
|
13
|
+
Object.assign(view, bag)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Apply DOM escape props after the primitive's own props. Dash-named keys
|
|
2
|
+
* are attributes; other keys use the DOM property where available. Mirrors
|
|
3
|
+
* @octane-xplat/ui's web escape-props so this leaf does not depend on ui. */
|
|
4
|
+
export function applyEscapeProps(element: any, props: { web?: any }): void {
|
|
5
|
+
if (!element || !props.web) {
|
|
6
|
+
return
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
for (const [key, value] of Object.entries(props.web)) {
|
|
10
|
+
if (key.includes('-')) {
|
|
11
|
+
if (value == null || value === false) {
|
|
12
|
+
element.removeAttribute(key)
|
|
13
|
+
} else {
|
|
14
|
+
element.setAttribute(key, value === true ? '' : String(value))
|
|
15
|
+
}
|
|
16
|
+
} else if (key in element) {
|
|
17
|
+
try {
|
|
18
|
+
element[key] = value
|
|
19
|
+
} catch {
|
|
20
|
+
element.setAttribute(key, String(value))
|
|
21
|
+
}
|
|
22
|
+
} else if (value != null) {
|
|
23
|
+
element.setAttribute(key, String(value))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
|
|
2
|
+
// reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
|
|
3
|
+
export { AnimatedImage } from './AnimatedImage.tsrx'
|
|
4
|
+
export { prefetch } from './prefetch'
|
|
5
|
+
export type { AnimatedImageProps, PrefetchOptions } from './props'
|
package/src/index.web.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
|
|
2
|
+
// reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
|
|
3
|
+
export { AnimatedImage } from './AnimatedImage.web.tsrx'
|
|
4
|
+
export { prefetch } from './prefetch.web'
|
|
5
|
+
export type { AnimatedImageProps, PrefetchOptions } from './props'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Windows barrel — explicit leaf imports keep @nativescript-community/
|
|
2
|
+
// ui-image (iOS/Android only) out of the windows graph at the file boundary.
|
|
3
|
+
export { AnimatedImage } from './AnimatedImage.windows.tsrx'
|
|
4
|
+
export { prefetch } from './prefetch.windows'
|
|
5
|
+
export type { AnimatedImageProps, PrefetchOptions } from './props'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Img } from '@nativescript-community/ui-image'
|
|
2
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
3
|
+
|
|
4
|
+
declare module '@nativescript-community/octane/intrinsics' {
|
|
5
|
+
interface NativeScriptElements {
|
|
6
|
+
animatedimage: Attributes<typeof Img>
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** VENDORED — identical copy in packages/image/src/prefetch-batch.ts; keep in
|
|
2
|
+
* sync.
|
|
3
|
+
*
|
|
4
|
+
* Shared batch driver for the image-engine leaves' `prefetch` contract:
|
|
5
|
+
* normalizes `string | string[]`, caps in-flight requests JS-side, and
|
|
6
|
+
* resolves `true` only when every URL warms — `false` on the first failure
|
|
7
|
+
* (in-flight requests settle on their own; the result is already decided). */
|
|
8
|
+
|
|
9
|
+
/** Matches the `maxRequests = 5` ceiling NS core's ImageCache used. */
|
|
10
|
+
export const DEFAULT_PREFETCH_CONCURRENCY = 5
|
|
11
|
+
|
|
12
|
+
export function prefetchBatch(
|
|
13
|
+
srcs: string | string[],
|
|
14
|
+
concurrency: number | undefined,
|
|
15
|
+
warm: (url: string) => Promise<boolean>,
|
|
16
|
+
): Promise<boolean> {
|
|
17
|
+
const urls = Array.isArray(srcs) ? srcs : [srcs]
|
|
18
|
+
if (urls.length === 0) {
|
|
19
|
+
return Promise.resolve(true)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const requested = Math.floor(concurrency ?? DEFAULT_PREFETCH_CONCURRENCY)
|
|
23
|
+
const limit =
|
|
24
|
+
Number.isFinite(requested) && requested > 0 ? requested : DEFAULT_PREFETCH_CONCURRENCY
|
|
25
|
+
|
|
26
|
+
return new Promise<boolean>((resolve) => {
|
|
27
|
+
let next = 0
|
|
28
|
+
let active = Math.min(limit, urls.length)
|
|
29
|
+
// Surviving workers stop dequeuing once a failure decides the result —
|
|
30
|
+
// only already-running warm() calls settle on their own.
|
|
31
|
+
let failed = false
|
|
32
|
+
for (let i = 0; i < active; i++) {
|
|
33
|
+
void (async () => {
|
|
34
|
+
while (!failed && next < urls.length) {
|
|
35
|
+
const url = urls[next++]
|
|
36
|
+
try {
|
|
37
|
+
if (!(await warm(url))) {
|
|
38
|
+
failed = true
|
|
39
|
+
resolve(false)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
failed = true
|
|
44
|
+
resolve(false)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (--active === 0) {
|
|
50
|
+
resolve(true)
|
|
51
|
+
}
|
|
52
|
+
})()
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PrefetchOptions } from './props'
|
|
2
|
+
|
|
3
|
+
/** prefetch — no-op on the AppKit host: it has no image pipeline yet
|
|
4
|
+
* (AnimatedImage renders a placeholder there), so nothing is warmed.
|
|
5
|
+
* Resolves false to report that honestly. */
|
|
6
|
+
export function prefetch(_srcs: string | string[], _options?: PrefetchOptions): Promise<boolean> {
|
|
7
|
+
return Promise.resolve(false)
|
|
8
|
+
}
|
package/src/prefetch.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isAndroid } from '@nativescript/core'
|
|
2
|
+
import { getImagePipeline, initialize } from '@nativescript-community/ui-image'
|
|
3
|
+
import { prefetchBatch } from './prefetch-batch'
|
|
4
|
+
import type { PrefetchOptions } from './props'
|
|
5
|
+
|
|
6
|
+
/** prefetch — warm the image engine's disk cache for `srcs` without displaying
|
|
7
|
+
* anything. Android (Fresco): `prefetchToDiskCache` stores encoded bytes on
|
|
8
|
+
* disk and defers bitmap decode to display. iOS (SDWebImage): the prefetcher
|
|
9
|
+
* stores the downloaded image to the disk cache only. Either way a later
|
|
10
|
+
* `AnimatedImage` with the same src hits disk instead of the network.
|
|
11
|
+
* Resolves false when any URL fails; resolves false when the pipeline is not
|
|
12
|
+
* available yet (e.g. called before app launch).
|
|
13
|
+
* `options.concurrency` caps parallel fetches (default 5); `options.headers`
|
|
14
|
+
* is honored on Android and silently dropped on iOS (upstream gap — see
|
|
15
|
+
* PrefetchOptions). */
|
|
16
|
+
export function prefetch(srcs: string | string[], options?: PrefetchOptions): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
// The plugin lazily initializes Fresco inside createNativeView; a prefetch
|
|
19
|
+
// that runs before any Img mounts has to do it first. initialize() is a
|
|
20
|
+
// no-op once the plugin (or the app) has initialized. iOS has no equivalent
|
|
21
|
+
// requirement and its initialize() is not guarded, so it stays Android-only.
|
|
22
|
+
if (isAndroid) {
|
|
23
|
+
initialize()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const pipeline = getImagePipeline()
|
|
27
|
+
const { concurrency, ...engineOptions } = options ?? {}
|
|
28
|
+
|
|
29
|
+
return prefetchBatch(srcs, concurrency, (url) =>
|
|
30
|
+
pipeline.prefetchToDiskCache(url, engineOptions).then(
|
|
31
|
+
() => true,
|
|
32
|
+
() => false,
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
} catch {
|
|
36
|
+
return Promise.resolve(false)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { prefetchBatch } from './prefetch-batch'
|
|
2
|
+
import type { PrefetchOptions } from './props'
|
|
3
|
+
|
|
4
|
+
/** prefetch — load each `src` through a throwaway <img> so the response lands
|
|
5
|
+
* in the browser HTTP cache; decode and render stay with the displaying
|
|
6
|
+
* element. `options.headers` is ignored — an <img> request cannot carry
|
|
7
|
+
* custom headers. `options.concurrency` caps parallel loads (default 5).
|
|
8
|
+
* Resolves false when any URL fails. */
|
|
9
|
+
export function prefetch(srcs: string | string[], options?: PrefetchOptions): Promise<boolean> {
|
|
10
|
+
return prefetchBatch(
|
|
11
|
+
srcs,
|
|
12
|
+
options?.concurrency,
|
|
13
|
+
(url) =>
|
|
14
|
+
new Promise<boolean>((resolve) => {
|
|
15
|
+
const image = new Image()
|
|
16
|
+
image.onload = () => resolve(true)
|
|
17
|
+
image.onerror = () => resolve(false)
|
|
18
|
+
image.src = url
|
|
19
|
+
}),
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PrefetchOptions } from './props'
|
|
2
|
+
|
|
3
|
+
/** prefetch — no-op on Windows: there is no image pipeline to warm
|
|
4
|
+
* (AnimatedImage renders a placeholder there). Resolves false to report
|
|
5
|
+
* that honestly. */
|
|
6
|
+
export function prefetch(_srcs: string | string[], _options?: PrefetchOptions): Promise<boolean> {
|
|
7
|
+
return Promise.resolve(false)
|
|
8
|
+
}
|
package/src/props.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface AnimatedImageProps {
|
|
2
|
+
className?: any
|
|
3
|
+
style?: any
|
|
4
|
+
id?: string
|
|
5
|
+
src: string
|
|
6
|
+
alt?: string
|
|
7
|
+
/** Content fit — same grammar as core Image. Web maps to object-fit;
|
|
8
|
+
* ui-image (Fresco/SDWebImage) accepts the same values. */
|
|
9
|
+
stretch?: 'none' | 'fill' | 'aspectFit' | 'aspectFill'
|
|
10
|
+
width?: string | number
|
|
11
|
+
height?: string | number
|
|
12
|
+
/** Platform-specific properties are applied after shared props. Native
|
|
13
|
+
* bags assign onto the ui-image `Img` view — `noCache`, `decodeWidth`,
|
|
14
|
+
* `decodeHeight`, `placeholderImageUri`, `progressiveRenderingEnabled`,
|
|
15
|
+
* `cacheKey`, and the rest of its prop surface are reachable this way;
|
|
16
|
+
* the shared props stay the parity contract. */
|
|
17
|
+
ios?: any
|
|
18
|
+
android?: any
|
|
19
|
+
web?: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Options for `prefetch`. The same contract `@octane-xplat/image`'s
|
|
23
|
+
* `prefetch` carries. */
|
|
24
|
+
export interface PrefetchOptions {
|
|
25
|
+
/** Request headers for the prefetch fetch. Honored on Android (Fresco);
|
|
26
|
+
* **dropped on iOS** — the plugin's prefetch path builds its SDWebImage
|
|
27
|
+
* context without the download-request-modifier branch its `Img`
|
|
28
|
+
* display path has; an upstream patch is needed there. Ignored on web —
|
|
29
|
+
* a plain <img> cannot send custom headers. */
|
|
30
|
+
headers?: Record<string, string>
|
|
31
|
+
/** Max URLs fetched in parallel (JS-side cap). Default 5 — the same
|
|
32
|
+
* ceiling NS core's ImageCache used. */
|
|
33
|
+
concurrency?: number
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"files": [
|
|
4
|
+
"AnimatedImage.d.ts",
|
|
5
|
+
"AnimatedImage.web.d.ts",
|
|
6
|
+
"escape-props.d.ts",
|
|
7
|
+
"escape-props.web.d.ts",
|
|
8
|
+
"index.d.ts",
|
|
9
|
+
"index.web.d.ts",
|
|
10
|
+
"native-elements.d.ts",
|
|
11
|
+
"prefetch-batch.d.ts",
|
|
12
|
+
"prefetch.d.ts",
|
|
13
|
+
"prefetch.web.d.ts",
|
|
14
|
+
"props.d.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnimatedImageProps } from './props.js';
|
|
2
|
+
/** AnimatedImage — GIF/animated-webp decode + autoplay via ui-image. `src`
|
|
3
|
+
* takes the Img grammar: remote URL, res://name, ~/bundle path, file path.
|
|
4
|
+
* There is intentionally no play/pause prop — <img> can't pause on web, so
|
|
5
|
+
* the shared contract is always-playing. */
|
|
6
|
+
export declare function AnimatedImage(props: AnimatedImageProps): unknown;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
import type { AnimatedImageProps } from './props.js';
|
|
3
|
+
/** AnimatedImage — a plain <img>; browsers animate GIF/webp natively.
|
|
4
|
+
* `stretch` maps to object-fit. */
|
|
5
|
+
export declare function AnimatedImage(props: AnimatedImageProps): import("octane/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Apply only the active device's escape bag after the shared props — the
|
|
2
|
+
* same convention @octane-xplat/ui uses, kept local so this leaf does not
|
|
3
|
+
* depend on ui. */
|
|
4
|
+
export declare function applyEscapeProps(view: any, props: {
|
|
5
|
+
ios?: any;
|
|
6
|
+
android?: any;
|
|
7
|
+
}): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Apply DOM escape props after the primitive's own props. Dash-named keys
|
|
2
|
+
* are attributes; other keys use the DOM property where available. Mirrors
|
|
3
|
+
* @octane-xplat/ui's web escape-props so this leaf does not depend on ui. */
|
|
4
|
+
export declare function applyEscapeProps(element: any, props: {
|
|
5
|
+
web?: any;
|
|
6
|
+
}): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Img } from '@nativescript-community/ui-image'
|
|
2
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
3
|
+
|
|
4
|
+
declare module '@nativescript-community/octane/intrinsics' {
|
|
5
|
+
interface NativeScriptElements {
|
|
6
|
+
animatedimage: Attributes<typeof Img>
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** VENDORED — identical copy in packages/image/src/prefetch-batch.ts; keep in
|
|
2
|
+
* sync.
|
|
3
|
+
*
|
|
4
|
+
* Shared batch driver for the image-engine leaves' `prefetch` contract:
|
|
5
|
+
* normalizes `string | string[]`, caps in-flight requests JS-side, and
|
|
6
|
+
* resolves `true` only when every URL warms — `false` on the first failure
|
|
7
|
+
* (in-flight requests settle on their own; the result is already decided). */
|
|
8
|
+
/** Matches the `maxRequests = 5` ceiling NS core's ImageCache used. */
|
|
9
|
+
export declare const DEFAULT_PREFETCH_CONCURRENCY = 5;
|
|
10
|
+
export declare function prefetchBatch(srcs: string | string[], concurrency: number | undefined, warm: (url: string) => Promise<boolean>): Promise<boolean>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PrefetchOptions } from './props.js';
|
|
2
|
+
/** prefetch — warm the image engine's disk cache for `srcs` without displaying
|
|
3
|
+
* anything. Android (Fresco): `prefetchToDiskCache` stores encoded bytes on
|
|
4
|
+
* disk and defers bitmap decode to display. iOS (SDWebImage): the prefetcher
|
|
5
|
+
* stores the downloaded image to the disk cache only. Either way a later
|
|
6
|
+
* `AnimatedImage` with the same src hits disk instead of the network.
|
|
7
|
+
* Resolves false when any URL fails; resolves false when the pipeline is not
|
|
8
|
+
* available yet (e.g. called before app launch).
|
|
9
|
+
* `options.concurrency` caps parallel fetches (default 5); `options.headers`
|
|
10
|
+
* is honored on Android and silently dropped on iOS (upstream gap — see
|
|
11
|
+
* PrefetchOptions). */
|
|
12
|
+
export declare function prefetch(srcs: string | string[], options?: PrefetchOptions): Promise<boolean>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PrefetchOptions } from './props.js';
|
|
2
|
+
/** prefetch — load each `src` through a throwaway <img> so the response lands
|
|
3
|
+
* in the browser HTTP cache; decode and render stay with the displaying
|
|
4
|
+
* element. `options.headers` is ignored — an <img> request cannot carry
|
|
5
|
+
* custom headers. `options.concurrency` caps parallel loads (default 5).
|
|
6
|
+
* Resolves false when any URL fails. */
|
|
7
|
+
export declare function prefetch(srcs: string | string[], options?: PrefetchOptions): Promise<boolean>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AnimatedImageProps {
|
|
2
|
+
className?: any;
|
|
3
|
+
style?: any;
|
|
4
|
+
id?: string;
|
|
5
|
+
src: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
/** Content fit — same grammar as core Image. Web maps to object-fit;
|
|
8
|
+
* ui-image (Fresco/SDWebImage) accepts the same values. */
|
|
9
|
+
stretch?: 'none' | 'fill' | 'aspectFit' | 'aspectFill';
|
|
10
|
+
width?: string | number;
|
|
11
|
+
height?: string | number;
|
|
12
|
+
/** Platform-specific properties are applied after shared props. Native
|
|
13
|
+
* bags assign onto the ui-image `Img` view — `noCache`, `decodeWidth`,
|
|
14
|
+
* `decodeHeight`, `placeholderImageUri`, `progressiveRenderingEnabled`,
|
|
15
|
+
* `cacheKey`, and the rest of its prop surface are reachable this way;
|
|
16
|
+
* the shared props stay the parity contract. */
|
|
17
|
+
ios?: any;
|
|
18
|
+
android?: any;
|
|
19
|
+
web?: any;
|
|
20
|
+
}
|
|
21
|
+
/** Options for `prefetch`. The same contract `@octane-xplat/image`'s
|
|
22
|
+
* `prefetch` carries. */
|
|
23
|
+
export interface PrefetchOptions {
|
|
24
|
+
/** Request headers for the prefetch fetch. Honored on Android (Fresco);
|
|
25
|
+
* **dropped on iOS** — the plugin's prefetch path builds its SDWebImage
|
|
26
|
+
* context without the download-request-modifier branch its `Img`
|
|
27
|
+
* display path has; an upstream patch is needed there. Ignored on web —
|
|
28
|
+
* a plain <img> cannot send custom headers. */
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
/** Max URLs fetched in parallel (JS-side cap). Default 5 — the same
|
|
31
|
+
* ceiling NS core's ImageCache used. */
|
|
32
|
+
concurrency?: number;
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnimatedImageProps, PrefetchOptions } from './generated/props.js'
|
|
2
|
+
|
|
3
|
+
export type { AnimatedImageProps, PrefetchOptions } from './generated/props.js'
|
|
4
|
+
export declare function AnimatedImage(props: AnimatedImageProps): unknown
|
|
5
|
+
export declare function prefetch(
|
|
6
|
+
srcs: string | string[],
|
|
7
|
+
options?: PrefetchOptions,
|
|
8
|
+
): Promise<boolean>
|