@buoy-gg/images 7.0.3
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 +58 -0
- package/README.md +58 -0
- package/lib/commonjs/capture/actions.js +227 -0
- package/lib/commonjs/capture/actions.js.map +1 -0
- package/lib/commonjs/capture/install.js +41 -0
- package/lib/commonjs/capture/install.js.map +1 -0
- package/lib/commonjs/capture/installExpoImage.js +99 -0
- package/lib/commonjs/capture/installExpoImage.js.map +1 -0
- package/lib/commonjs/capture/installRnImage.js +128 -0
- package/lib/commonjs/capture/installRnImage.js.map +1 -0
- package/lib/commonjs/capture/overrides.js +286 -0
- package/lib/commonjs/capture/overrides.js.map +1 -0
- package/lib/commonjs/capture/savings.js +193 -0
- package/lib/commonjs/capture/savings.js.map +1 -0
- package/lib/commonjs/capture/shared.js +367 -0
- package/lib/commonjs/capture/shared.js.map +1 -0
- package/lib/commonjs/components/ImageDetail.js +444 -0
- package/lib/commonjs/components/ImageDetail.js.map +1 -0
- package/lib/commonjs/components/ImageDetailFooter.js +232 -0
- package/lib/commonjs/components/ImageDetailFooter.js.map +1 -0
- package/lib/commonjs/components/ImageRow.js +164 -0
- package/lib/commonjs/components/ImageRow.js.map +1 -0
- package/lib/commonjs/components/ImagesHeader.js +63 -0
- package/lib/commonjs/components/ImagesHeader.js.map +1 -0
- package/lib/commonjs/components/ImagesIcon.js +59 -0
- package/lib/commonjs/components/ImagesIcon.js.map +1 -0
- package/lib/commonjs/components/ImagesListFooter.js +194 -0
- package/lib/commonjs/components/ImagesListFooter.js.map +1 -0
- package/lib/commonjs/components/ImagesModal.js +173 -0
- package/lib/commonjs/components/ImagesModal.js.map +1 -0
- package/lib/commonjs/components/ImagesRoot.js +24 -0
- package/lib/commonjs/components/ImagesRoot.js.map +1 -0
- package/lib/commonjs/components/InsightsBar.js +59 -0
- package/lib/commonjs/components/InsightsBar.js.map +1 -0
- package/lib/commonjs/export.js +81 -0
- package/lib/commonjs/export.js.map +1 -0
- package/lib/commonjs/format.js +117 -0
- package/lib/commonjs/format.js.map +1 -0
- package/lib/commonjs/hooks/useImageRecords.js +20 -0
- package/lib/commonjs/hooks/useImageRecords.js.map +1 -0
- package/lib/commonjs/index.js +236 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +45 -0
- package/lib/commonjs/preset.js.map +1 -0
- package/lib/commonjs/register.js +19 -0
- package/lib/commonjs/register.js.map +1 -0
- package/lib/commonjs/state.js +54 -0
- package/lib/commonjs/state.js.map +1 -0
- package/lib/commonjs/store/imageEventStore.js +160 -0
- package/lib/commonjs/store/imageEventStore.js.map +1 -0
- package/lib/commonjs/store/insights.js +76 -0
- package/lib/commonjs/store/insights.js.map +1 -0
- package/lib/commonjs/sync/imagesSyncAdapter.js +315 -0
- package/lib/commonjs/sync/imagesSyncAdapter.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/capture/actions.js +214 -0
- package/lib/module/capture/actions.js.map +1 -0
- package/lib/module/capture/install.js +36 -0
- package/lib/module/capture/install.js.map +1 -0
- package/lib/module/capture/installExpoImage.js +94 -0
- package/lib/module/capture/installExpoImage.js.map +1 -0
- package/lib/module/capture/installRnImage.js +122 -0
- package/lib/module/capture/installRnImage.js.map +1 -0
- package/lib/module/capture/overrides.js +269 -0
- package/lib/module/capture/overrides.js.map +1 -0
- package/lib/module/capture/savings.js +189 -0
- package/lib/module/capture/savings.js.map +1 -0
- package/lib/module/capture/shared.js +359 -0
- package/lib/module/capture/shared.js.map +1 -0
- package/lib/module/components/ImageDetail.js +440 -0
- package/lib/module/components/ImageDetail.js.map +1 -0
- package/lib/module/components/ImageDetailFooter.js +228 -0
- package/lib/module/components/ImageDetailFooter.js.map +1 -0
- package/lib/module/components/ImageRow.js +160 -0
- package/lib/module/components/ImageRow.js.map +1 -0
- package/lib/module/components/ImagesHeader.js +59 -0
- package/lib/module/components/ImagesHeader.js.map +1 -0
- package/lib/module/components/ImagesIcon.js +55 -0
- package/lib/module/components/ImagesIcon.js.map +1 -0
- package/lib/module/components/ImagesListFooter.js +190 -0
- package/lib/module/components/ImagesListFooter.js.map +1 -0
- package/lib/module/components/ImagesModal.js +169 -0
- package/lib/module/components/ImagesModal.js.map +1 -0
- package/lib/module/components/ImagesRoot.js +20 -0
- package/lib/module/components/ImagesRoot.js.map +1 -0
- package/lib/module/components/InsightsBar.js +55 -0
- package/lib/module/components/InsightsBar.js.map +1 -0
- package/lib/module/export.js +76 -0
- package/lib/module/export.js.map +1 -0
- package/lib/module/format.js +108 -0
- package/lib/module/format.js.map +1 -0
- package/lib/module/hooks/useImageRecords.js +16 -0
- package/lib/module/hooks/useImageRecords.js.map +1 -0
- package/lib/module/index.js +73 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/preset.js +40 -0
- package/lib/module/preset.js.map +1 -0
- package/lib/module/register.js +19 -0
- package/lib/module/register.js.map +1 -0
- package/lib/module/state.js +50 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/store/imageEventStore.js +146 -0
- package/lib/module/store/imageEventStore.js.map +1 -0
- package/lib/module/store/insights.js +71 -0
- package/lib/module/store/insights.js.map +1 -0
- package/lib/module/sync/imagesSyncAdapter.js +311 -0
- package/lib/module/sync/imagesSyncAdapter.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/capture/actions.d.ts +49 -0
- package/lib/typescript/capture/actions.d.ts.map +1 -0
- package/lib/typescript/capture/install.d.ts +15 -0
- package/lib/typescript/capture/install.d.ts.map +1 -0
- package/lib/typescript/capture/installExpoImage.d.ts +23 -0
- package/lib/typescript/capture/installExpoImage.d.ts.map +1 -0
- package/lib/typescript/capture/installRnImage.d.ts +24 -0
- package/lib/typescript/capture/installRnImage.d.ts.map +1 -0
- package/lib/typescript/capture/overrides.d.ts +83 -0
- package/lib/typescript/capture/overrides.d.ts.map +1 -0
- package/lib/typescript/capture/savings.d.ts +37 -0
- package/lib/typescript/capture/savings.d.ts.map +1 -0
- package/lib/typescript/capture/shared.d.ts +61 -0
- package/lib/typescript/capture/shared.d.ts.map +1 -0
- package/lib/typescript/components/ImageDetail.d.ts +13 -0
- package/lib/typescript/components/ImageDetail.d.ts.map +1 -0
- package/lib/typescript/components/ImageDetailFooter.d.ts +19 -0
- package/lib/typescript/components/ImageDetailFooter.d.ts.map +1 -0
- package/lib/typescript/components/ImageRow.d.ts +15 -0
- package/lib/typescript/components/ImageRow.d.ts.map +1 -0
- package/lib/typescript/components/ImagesHeader.d.ts +25 -0
- package/lib/typescript/components/ImagesHeader.d.ts.map +1 -0
- package/lib/typescript/components/ImagesIcon.d.ts +13 -0
- package/lib/typescript/components/ImagesIcon.d.ts.map +1 -0
- package/lib/typescript/components/ImagesListFooter.d.ts +8 -0
- package/lib/typescript/components/ImagesListFooter.d.ts.map +1 -0
- package/lib/typescript/components/ImagesModal.d.ts +17 -0
- package/lib/typescript/components/ImagesModal.d.ts.map +1 -0
- package/lib/typescript/components/ImagesRoot.d.ts +11 -0
- package/lib/typescript/components/ImagesRoot.d.ts.map +1 -0
- package/lib/typescript/components/InsightsBar.d.ts +13 -0
- package/lib/typescript/components/InsightsBar.d.ts.map +1 -0
- package/lib/typescript/export.d.ts +12 -0
- package/lib/typescript/export.d.ts.map +1 -0
- package/lib/typescript/format.d.ts +26 -0
- package/lib/typescript/format.d.ts.map +1 -0
- package/lib/typescript/hooks/useImageRecords.d.ts +11 -0
- package/lib/typescript/hooks/useImageRecords.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +33 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +38 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/register.d.ts +15 -0
- package/lib/typescript/register.d.ts.map +1 -0
- package/lib/typescript/state.d.ts +54 -0
- package/lib/typescript/state.d.ts.map +1 -0
- package/lib/typescript/store/imageEventStore.d.ts +34 -0
- package/lib/typescript/store/imageEventStore.d.ts.map +1 -0
- package/lib/typescript/store/insights.d.ts +11 -0
- package/lib/typescript/store/insights.d.ts.map +1 -0
- package/lib/typescript/sync/imagesSyncAdapter.d.ts +205 -0
- package/lib/typescript/sync/imagesSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +121 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +67 -0
- package/register.d.ts +1 -0
- package/register.js +15 -0
- package/src/capture/actions.ts +206 -0
- package/src/capture/install.ts +36 -0
- package/src/capture/installExpoImage.ts +115 -0
- package/src/capture/installRnImage.tsx +133 -0
- package/src/capture/overrides.ts +282 -0
- package/src/capture/savings.ts +193 -0
- package/src/capture/shared.ts +387 -0
- package/src/components/ImageDetail.tsx +456 -0
- package/src/components/ImageDetailFooter.tsx +270 -0
- package/src/components/ImageRow.tsx +148 -0
- package/src/components/ImagesHeader.tsx +77 -0
- package/src/components/ImagesIcon.tsx +66 -0
- package/src/components/ImagesListFooter.tsx +193 -0
- package/src/components/ImagesModal.tsx +195 -0
- package/src/components/ImagesRoot.tsx +18 -0
- package/src/components/InsightsBar.tsx +51 -0
- package/src/export.ts +100 -0
- package/src/format.ts +85 -0
- package/src/hooks/useImageRecords.ts +24 -0
- package/src/index.tsx +108 -0
- package/src/preset.tsx +35 -0
- package/src/register.ts +17 -0
- package/src/state.ts +88 -0
- package/src/store/imageEventStore.ts +162 -0
- package/src/store/insights.ts +66 -0
- package/src/sync/imagesSyncAdapter.ts +261 -0
- package/src/types.ts +111 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync adapter for the Images tool, consumed by @buoy-gg/external-sync's
|
|
3
|
+
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
4
|
+
* this package doesn't need a dependency on it — same pattern as js-top's
|
|
5
|
+
* adapter). Exposes the live image registry to the desktop dashboard and the
|
|
6
|
+
* MCP server, plus every action the on-device UI has: reload/retry,
|
|
7
|
+
* simulation overrides (force error/loading/blank/URL swap), locate-flash,
|
|
8
|
+
* global modes, and cache clears.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
evictExpoDiskEntry,
|
|
13
|
+
hardReloadAllMounted,
|
|
14
|
+
hardReloadRecord,
|
|
15
|
+
retryRecord,
|
|
16
|
+
} from "../capture/actions";
|
|
17
|
+
import { getCaptureStatus } from "../capture/install";
|
|
18
|
+
import {
|
|
19
|
+
clearAllOverrides,
|
|
20
|
+
clearOverride,
|
|
21
|
+
flashAllMounted,
|
|
22
|
+
flashRecord,
|
|
23
|
+
getGlobalModes,
|
|
24
|
+
setBlankImages,
|
|
25
|
+
setNetworkMode,
|
|
26
|
+
setOverride,
|
|
27
|
+
setOverrideForAllMounted,
|
|
28
|
+
} from "../capture/overrides";
|
|
29
|
+
import { proveSavings } from "../capture/savings";
|
|
30
|
+
import { computeInsights } from "../store/insights";
|
|
31
|
+
import type { NetworkMode, OverrideSource } from "../capture/overrides";
|
|
32
|
+
import {
|
|
33
|
+
clearRecords,
|
|
34
|
+
computeStats,
|
|
35
|
+
estDecodedBytes,
|
|
36
|
+
estWastedBytes,
|
|
37
|
+
getRecord,
|
|
38
|
+
getSnapshot,
|
|
39
|
+
neededPixels,
|
|
40
|
+
oversizeFactor,
|
|
41
|
+
subscribe,
|
|
42
|
+
} from "../store/imageEventStore";
|
|
43
|
+
import type { ImageRecord } from "../types";
|
|
44
|
+
|
|
45
|
+
/** Slim wire form of a record (heavy/derived fields precomputed + rounded). */
|
|
46
|
+
function toWire(record: ImageRecord) {
|
|
47
|
+
const factor = oversizeFactor(record);
|
|
48
|
+
return {
|
|
49
|
+
id: record.id,
|
|
50
|
+
lib: record.lib,
|
|
51
|
+
uri: record.uri,
|
|
52
|
+
kind: record.sourceKind,
|
|
53
|
+
status: record.status,
|
|
54
|
+
mounted: record.mounted,
|
|
55
|
+
cache: record.cacheVerdict ?? null,
|
|
56
|
+
cacheSource: record.cacheVerdictSource ?? null,
|
|
57
|
+
ms: record.durationMs != null ? Math.round(record.durationMs) : null,
|
|
58
|
+
intrinsic: record.intrinsic ?? null,
|
|
59
|
+
layout: record.layout
|
|
60
|
+
? {
|
|
61
|
+
width: Math.round(record.layout.width),
|
|
62
|
+
height: Math.round(record.layout.height),
|
|
63
|
+
}
|
|
64
|
+
: null,
|
|
65
|
+
neededPx: neededPixels(record),
|
|
66
|
+
oversizeFactor: factor != null ? Math.round(factor * 100) / 100 : null,
|
|
67
|
+
decodedKB: Math.round(estDecodedBytes(record) / 1024),
|
|
68
|
+
wastedKB: Math.round(estWastedBytes(record) / 1024),
|
|
69
|
+
progressSeen: record.progressSeen === true,
|
|
70
|
+
bytesTotal: record.bytesTotal ?? null,
|
|
71
|
+
error: record.error ?? null,
|
|
72
|
+
errorCode: record.errorCode ?? null,
|
|
73
|
+
loadCount: record.loadCount,
|
|
74
|
+
overrideLabel: record.overrideLabel ?? null,
|
|
75
|
+
hasAltText: record.hasAltText ?? null,
|
|
76
|
+
layoutShifts: record.layoutShifts ?? 0,
|
|
77
|
+
ageMs: Date.now() - record.createdAt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function requireRecord(params: unknown): ImageRecord {
|
|
82
|
+
const id = (params as { id?: number })?.id;
|
|
83
|
+
if (typeof id !== "number") throw new Error("Missing numeric `id` param");
|
|
84
|
+
const record = getRecord(id);
|
|
85
|
+
if (!record) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Image record ${id} not found (cleared or evicted) — re-run the list action for current ids.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return record;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function overview() {
|
|
94
|
+
const snapshot = getSnapshot();
|
|
95
|
+
return {
|
|
96
|
+
stats: computeStats(snapshot),
|
|
97
|
+
captureStatus: getCaptureStatus(),
|
|
98
|
+
globalModes: getGlobalModes(),
|
|
99
|
+
insights: computeInsights(snapshot),
|
|
100
|
+
records: [...snapshot].reverse(), // newest first
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const imagesSyncAdapter = {
|
|
105
|
+
version: 1,
|
|
106
|
+
getSnapshot: () => {
|
|
107
|
+
const { records, ...rest } = overview();
|
|
108
|
+
return { ...rest, records: records.slice(0, 100).map(toWire) };
|
|
109
|
+
},
|
|
110
|
+
subscribe: (onChange: () => void) => subscribe(onChange),
|
|
111
|
+
actions: {
|
|
112
|
+
/** List records (newest first). Params: { limit?, status? }. */
|
|
113
|
+
list: (params: unknown) => {
|
|
114
|
+
const p = (params ?? {}) as { limit?: number; status?: string };
|
|
115
|
+
const limit = Math.min(Math.max(p.limit ?? 50, 1), 200);
|
|
116
|
+
const { records, ...rest } = overview();
|
|
117
|
+
const filtered = p.status
|
|
118
|
+
? records.filter((r) => r.status === p.status)
|
|
119
|
+
: records;
|
|
120
|
+
return {
|
|
121
|
+
...rest,
|
|
122
|
+
total: filtered.length,
|
|
123
|
+
records: filtered.slice(0, limit).map(toWire),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
/** Full detail for one record. Params: { id }. */
|
|
127
|
+
getDetail: (params: unknown) => {
|
|
128
|
+
const record = requireRecord(params);
|
|
129
|
+
return { ...toWire(record), errorHeaders: record.errorHeaders ?? null };
|
|
130
|
+
},
|
|
131
|
+
/** Bypass-cache reload. Params: { id }. */
|
|
132
|
+
hardReload: (params: unknown) => hardReloadRecord(requireRecord(params)),
|
|
133
|
+
/** Re-encode at displayed size as WebP, return real byte savings. Params: { id }. */
|
|
134
|
+
proveSavings: (params: unknown) => proveSavings(requireRecord(params)),
|
|
135
|
+
/** Plain fresh load attempt. Params: { id }. */
|
|
136
|
+
retry: (params: unknown) => retryRecord(requireRecord(params)),
|
|
137
|
+
/** Evict the expo-image disk entry for this record. Params: { id }. */
|
|
138
|
+
evictDisk: async (params: unknown) => {
|
|
139
|
+
const record = requireRecord(params);
|
|
140
|
+
const evicted = await evictExpoDiskEntry(record);
|
|
141
|
+
return {
|
|
142
|
+
ok: evicted,
|
|
143
|
+
message: evicted
|
|
144
|
+
? "Disk cache entry deleted"
|
|
145
|
+
: "No disk entry found (expo-image + expo-file-system required)",
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
/** Flash a red border on the on-screen image. Params: { id }. */
|
|
149
|
+
flash: (params: unknown) => {
|
|
150
|
+
flashRecord(requireRecord(params).id);
|
|
151
|
+
return { ok: true, message: "Flashing for 2.5s" };
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Apply a simulation override. Params: { id, kind, uri? } where kind is
|
|
155
|
+
* 'error' | 'hang' | 'blank' | 'url' ('url' requires uri).
|
|
156
|
+
*/
|
|
157
|
+
setOverride: (params: unknown) => {
|
|
158
|
+
const record = requireRecord(params);
|
|
159
|
+
const p = params as { kind?: string; uri?: string };
|
|
160
|
+
const kinds: Record<string, { source: OverrideSource; label: string }> = {
|
|
161
|
+
error: { source: { type: "error" }, label: "Forced error" },
|
|
162
|
+
hang: { source: { type: "hang" }, label: "Forced loading" },
|
|
163
|
+
blank: { source: { type: "blank" }, label: "Blanked" },
|
|
164
|
+
};
|
|
165
|
+
if (p.kind === "url") {
|
|
166
|
+
if (!p.uri) throw new Error("kind 'url' requires a `uri` param");
|
|
167
|
+
setOverride(record.id, {
|
|
168
|
+
source: { type: "url", uri: p.uri },
|
|
169
|
+
label: "URL swapped",
|
|
170
|
+
});
|
|
171
|
+
return { ok: true, message: `Source overridden to ${p.uri}` };
|
|
172
|
+
}
|
|
173
|
+
const entry = p.kind ? kinds[p.kind] : undefined;
|
|
174
|
+
if (!entry) throw new Error(`Unknown override kind: ${String(p.kind)}`);
|
|
175
|
+
if (!record.mounted) {
|
|
176
|
+
return { ok: false, message: "Instance unmounted — overrides need the image on screen" };
|
|
177
|
+
}
|
|
178
|
+
setOverride(record.id, entry);
|
|
179
|
+
return { ok: true, message: `${entry.label} applied` };
|
|
180
|
+
},
|
|
181
|
+
/** Remove a simulation override. Params: { id }. */
|
|
182
|
+
clearOverride: (params: unknown) => {
|
|
183
|
+
clearOverride(requireRecord(params).id);
|
|
184
|
+
return { ok: true, message: "Override removed — original source restored" };
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* Mass action on every mounted image. Params: { kind } where kind is
|
|
188
|
+
* 'error' | 'loading' | 'blank' | 'reload' | 'flash' | 'restore'.
|
|
189
|
+
*/
|
|
190
|
+
massAction: async (params: unknown) => {
|
|
191
|
+
const kind = (params as { kind?: string })?.kind;
|
|
192
|
+
switch (kind) {
|
|
193
|
+
case "error": {
|
|
194
|
+
const n = setOverrideForAllMounted({ type: "error" }, "Forced error");
|
|
195
|
+
return { ok: n > 0, message: `Forced error on ${n} images` };
|
|
196
|
+
}
|
|
197
|
+
case "loading": {
|
|
198
|
+
const n = setOverrideForAllMounted({ type: "hang" }, "Forced loading");
|
|
199
|
+
return { ok: n > 0, message: `Forced loading on ${n} images` };
|
|
200
|
+
}
|
|
201
|
+
case "blank": {
|
|
202
|
+
const n = setOverrideForAllMounted({ type: "blank" }, "Blanked");
|
|
203
|
+
return { ok: n > 0, message: `Blanked ${n} images` };
|
|
204
|
+
}
|
|
205
|
+
case "reload":
|
|
206
|
+
return hardReloadAllMounted();
|
|
207
|
+
case "flash": {
|
|
208
|
+
const n = flashAllMounted();
|
|
209
|
+
return { ok: n > 0, message: `Flashing ${n} images` };
|
|
210
|
+
}
|
|
211
|
+
case "restore": {
|
|
212
|
+
const n = clearAllOverrides();
|
|
213
|
+
return { ok: true, message: `Restored ${n} images` };
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
throw new Error(`Unknown mass kind: ${String(kind)}`);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
/** Global network simulation. Params: { mode: 'normal'|'offline'|'cold' }. */
|
|
220
|
+
setNetworkMode: (params: unknown) => {
|
|
221
|
+
const mode = (params as { mode?: NetworkMode })?.mode;
|
|
222
|
+
if (mode !== "normal" && mode !== "offline" && mode !== "cold") {
|
|
223
|
+
throw new Error("mode must be 'normal' | 'offline' | 'cold'");
|
|
224
|
+
}
|
|
225
|
+
setNetworkMode(mode);
|
|
226
|
+
return { ok: true, modes: getGlobalModes() };
|
|
227
|
+
},
|
|
228
|
+
/** Chrome-style disable-images. Params: { enabled: boolean }. */
|
|
229
|
+
setBlankImages: (params: unknown) => {
|
|
230
|
+
setBlankImages((params as { enabled?: boolean })?.enabled === true);
|
|
231
|
+
return { ok: true, modes: getGlobalModes() };
|
|
232
|
+
},
|
|
233
|
+
/** Clear the captured registry (keeps in-flight loads). */
|
|
234
|
+
clearRecords: () => {
|
|
235
|
+
clearRecords();
|
|
236
|
+
return { ok: true, message: "Registry cleared" };
|
|
237
|
+
},
|
|
238
|
+
/** Clear expo-image caches. Params: { memory?, disk? } (default both). */
|
|
239
|
+
clearExpoCaches: async (params: unknown) => {
|
|
240
|
+
const p = (params ?? {}) as { memory?: boolean; disk?: boolean };
|
|
241
|
+
const doMemory = p.memory !== false;
|
|
242
|
+
const doDisk = p.disk !== false;
|
|
243
|
+
try {
|
|
244
|
+
const expo = require("expo-image");
|
|
245
|
+
const results: string[] = [];
|
|
246
|
+
if (doMemory) {
|
|
247
|
+
await expo.Image.clearMemoryCache();
|
|
248
|
+
results.push("memory");
|
|
249
|
+
}
|
|
250
|
+
if (doDisk) {
|
|
251
|
+
await expo.Image.clearDiskCache();
|
|
252
|
+
results.push("disk");
|
|
253
|
+
}
|
|
254
|
+
return { ok: true, message: `Cleared expo-image ${results.join(" + ")} cache` };
|
|
255
|
+
} catch {
|
|
256
|
+
return { ok: false, message: "expo-image not installed" };
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
getCaptureStatus: () => getCaptureStatus(),
|
|
260
|
+
},
|
|
261
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the Images tool.
|
|
3
|
+
*
|
|
4
|
+
* One ImageRecord = one load lifecycle of one mounted image instance for one
|
|
5
|
+
* source URI. If the same component instance switches to a new URI, the old
|
|
6
|
+
* record is finalized and a new one starts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Which image library rendered the instance. */
|
|
10
|
+
export type ImageLib = "rn" | "expo";
|
|
11
|
+
|
|
12
|
+
/** Coarse classification of where the source points. */
|
|
13
|
+
export type SourceKind = "network" | "asset" | "file" | "data" | "other";
|
|
14
|
+
|
|
15
|
+
export type ImageStatus = "pending" | "loading" | "loaded" | "error";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Where the decoded image came from.
|
|
19
|
+
* - expo-image reports this directly on onLoad (`cacheType`).
|
|
20
|
+
* - RN core is classified after load via `Image.queryCache` (iOS can report
|
|
21
|
+
* 'disk/memory'; misses come back as undefined, shown as unknown).
|
|
22
|
+
*/
|
|
23
|
+
export type CacheVerdict = "none" | "disk" | "memory" | "disk/memory";
|
|
24
|
+
|
|
25
|
+
export interface ImageRecord {
|
|
26
|
+
id: number;
|
|
27
|
+
lib: ImageLib;
|
|
28
|
+
uri: string;
|
|
29
|
+
sourceKind: SourceKind;
|
|
30
|
+
status: ImageStatus;
|
|
31
|
+
/** False once the owning component instance unmounted. */
|
|
32
|
+
mounted: boolean;
|
|
33
|
+
/** ms epoch when the record was created (≈ render of the instance). */
|
|
34
|
+
createdAt: number;
|
|
35
|
+
/** ms epoch of onLoadStart (native began working on the source). */
|
|
36
|
+
startedAt?: number;
|
|
37
|
+
loadedAt?: number;
|
|
38
|
+
/** ms epoch of the last onError (used for out-of-order event defense). */
|
|
39
|
+
erroredAt?: number;
|
|
40
|
+
/** expo-image only: when pixels were actually presented (onDisplay). */
|
|
41
|
+
displayedAt?: number;
|
|
42
|
+
/** startedAt → loadedAt (falls back to createdAt → loadedAt). */
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
/** Decoded bitmap size in physical pixels (from onLoad). */
|
|
45
|
+
intrinsic?: { width: number; height: number };
|
|
46
|
+
/** Laid-out view size in dp (from injected onLayout). */
|
|
47
|
+
layout?: { width: number; height: number };
|
|
48
|
+
cacheVerdict?: CacheVerdict;
|
|
49
|
+
cacheVerdictSource?: "event" | "queryCache";
|
|
50
|
+
/** True if byte progress events fired (a real network download happened). */
|
|
51
|
+
progressSeen?: boolean;
|
|
52
|
+
bytesLoaded?: number;
|
|
53
|
+
bytesTotal?: number;
|
|
54
|
+
/** expo-image iOS only (Android reports null). */
|
|
55
|
+
mediaType?: string | null;
|
|
56
|
+
isAnimated?: boolean;
|
|
57
|
+
error?: string;
|
|
58
|
+
/** iOS RN core only: HTTP status code from onError. */
|
|
59
|
+
errorCode?: number;
|
|
60
|
+
/** iOS RN core only: response headers from onError. */
|
|
61
|
+
errorHeaders?: Record<string, string>;
|
|
62
|
+
/** How many load cycles this record has seen (source retries/reloads). */
|
|
63
|
+
loadCount: number;
|
|
64
|
+
/** Set while a simulation override is active (e.g. "Forced error"). */
|
|
65
|
+
overrideLabel?: string;
|
|
66
|
+
/** Whether the component had alt/accessibilityLabel at last render. */
|
|
67
|
+
hasAltText?: boolean;
|
|
68
|
+
/** Times the laid-out size changed AFTER the image loaded (CLS signal). */
|
|
69
|
+
layoutShifts?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Cross-record findings computed from the live registry. */
|
|
73
|
+
export interface ImageInsights {
|
|
74
|
+
/** Same URI loaded by 2+ mounted instances (duplicate decodes). */
|
|
75
|
+
duplicates: { uri: string; count: number }[];
|
|
76
|
+
/** Records with suspiciously many load cycles (render-thrash reloads). */
|
|
77
|
+
retryStorms: { id: number; uri: string; loadCount: number }[];
|
|
78
|
+
/** >4 RN-core images loading at once (iOS caps concurrent loads at 4). */
|
|
79
|
+
queueSaturated: boolean;
|
|
80
|
+
/** Mounted images missing alt/accessibilityLabel. */
|
|
81
|
+
missingAlt: number;
|
|
82
|
+
/** Images whose layout changed after load (layout-shift suspects). */
|
|
83
|
+
layoutShifters: { id: number; uri: string; shifts: number }[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Aggregate stats derived from the current buffer. */
|
|
87
|
+
export interface ImageStats {
|
|
88
|
+
total: number;
|
|
89
|
+
loading: number;
|
|
90
|
+
loaded: number;
|
|
91
|
+
errors: number;
|
|
92
|
+
/** Records whose bytes came over the network this session. */
|
|
93
|
+
networkLoads: number;
|
|
94
|
+
/** Σ intrinsic w×h×4 across loaded, still-mounted records. */
|
|
95
|
+
estDecodedBytes: number;
|
|
96
|
+
/** Σ estimated wasted decoded bytes from oversized sources (mounted). */
|
|
97
|
+
estWastedBytes: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Capture capability report — what the interceptors managed to hook. */
|
|
101
|
+
export interface CaptureStatus {
|
|
102
|
+
installed: boolean;
|
|
103
|
+
/** RN core decorator landed before Image module evaluation. */
|
|
104
|
+
rnDecoratorActive: boolean;
|
|
105
|
+
/** RN core Image module was already evaluated when we tried (too late). */
|
|
106
|
+
rnDecoratorTooLate: boolean;
|
|
107
|
+
/** expo-image found and prototype-patched. */
|
|
108
|
+
expoPatched: boolean;
|
|
109
|
+
/** expo-image not installed in this app. */
|
|
110
|
+
expoAvailable: boolean;
|
|
111
|
+
}
|