@buoy-gg/images 7.0.41 → 7.0.43
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 +11 -70
- package/lib/commonjs/autoCapture.js +1 -4
- package/lib/commonjs/autoCapture.web.js +1 -6
- package/lib/commonjs/capture/actions.js +1 -243
- package/lib/commonjs/capture/install.js +1 -45
- package/lib/commonjs/capture/install.web.js +1 -250
- package/lib/commonjs/capture/installExpoImage.js +1 -99
- package/lib/commonjs/capture/installRnImage.js +1 -132
- package/lib/commonjs/capture/nativeImageModules.js +1 -30
- package/lib/commonjs/capture/nativeImageModules.web.js +1 -18
- package/lib/commonjs/capture/overrides.js +1 -346
- package/lib/commonjs/capture/rnDecoratorEarly.js +1 -55
- package/lib/commonjs/capture/savings.js +1 -209
- package/lib/commonjs/capture/savings.web.js +1 -71
- package/lib/commonjs/capture/shared.js +1 -378
- package/lib/commonjs/components/ImageDetail.js +1 -455
- package/lib/commonjs/components/ImageDetailFooter.js +1 -158
- package/lib/commonjs/components/ImageRow.js +1 -156
- package/lib/commonjs/components/ImagesHeader.js +1 -63
- package/lib/commonjs/components/ImagesIcon.js +1 -19
- package/lib/commonjs/components/ImagesListFooter.js +1 -126
- package/lib/commonjs/components/ImagesModal.js +1 -240
- package/lib/commonjs/components/ImagesRoot.js +1 -24
- package/lib/commonjs/components/ImagesRoot.web.js +1 -13
- package/lib/commonjs/components/InsightsBar.js +1 -59
- package/lib/commonjs/export.js +1 -81
- package/lib/commonjs/format.js +1 -117
- package/lib/commonjs/hooks/useImageRecords.js +1 -20
- package/lib/commonjs/index.js +1 -261
- package/lib/commonjs/preset.js +1 -39
- package/lib/commonjs/register.js +1 -19
- package/lib/commonjs/registerEarly.js +1 -14
- package/lib/commonjs/remote.js +1 -42
- package/lib/commonjs/state.js +1 -58
- package/lib/commonjs/store/imageEventStore.js +1 -238
- package/lib/commonjs/store/insights.js +1 -76
- package/lib/commonjs/sync/imagesSyncAdapter.js +1 -361
- package/lib/commonjs/types.js +0 -1
- package/lib/module/autoCapture.js +1 -4
- package/lib/module/autoCapture.web.js +1 -5
- package/lib/module/capture/actions.js +1 -230
- package/lib/module/capture/install.js +1 -40
- package/lib/module/capture/install.web.js +1 -243
- package/lib/module/capture/installExpoImage.js +1 -94
- package/lib/module/capture/installRnImage.js +1 -125
- package/lib/module/capture/nativeImageModules.js +1 -24
- package/lib/module/capture/nativeImageModules.web.js +1 -12
- package/lib/module/capture/overrides.js +1 -329
- package/lib/module/capture/rnDecoratorEarly.js +1 -50
- package/lib/module/capture/savings.js +1 -205
- package/lib/module/capture/savings.web.js +1 -67
- package/lib/module/capture/shared.js +1 -369
- package/lib/module/components/ImageDetail.js +1 -451
- package/lib/module/components/ImageDetailFooter.js +1 -154
- package/lib/module/components/ImageRow.js +1 -152
- package/lib/module/components/ImagesHeader.js +1 -59
- package/lib/module/components/ImagesIcon.js +1 -12
- package/lib/module/components/ImagesListFooter.js +1 -122
- package/lib/module/components/ImagesModal.js +1 -235
- package/lib/module/components/ImagesRoot.js +1 -20
- package/lib/module/components/ImagesRoot.web.js +1 -9
- package/lib/module/components/InsightsBar.js +1 -55
- package/lib/module/export.js +1 -76
- package/lib/module/format.js +1 -108
- package/lib/module/hooks/useImageRecords.js +1 -16
- package/lib/module/index.js +1 -79
- package/lib/module/preset.js +1 -35
- package/lib/module/register.js +1 -19
- package/lib/module/registerEarly.js +1 -14
- package/lib/module/remote.js +1 -36
- package/lib/module/state.js +1 -54
- package/lib/module/store/imageEventStore.js +1 -221
- package/lib/module/store/insights.js +1 -71
- package/lib/module/sync/imagesSyncAdapter.js +1 -356
- package/lib/module/types.js +0 -1
- package/lib/web/chunks/chunk-TTS5EH4R.mjs +1 -2
- package/lib/web/index.mjs +3 -4
- package/lib/web/register.mjs +1 -2
- package/package.json +3 -7
- package/register.js +1 -15
- package/lib/commonjs/autoCapture.js.map +0 -1
- package/lib/commonjs/autoCapture.web.js.map +0 -1
- package/lib/commonjs/capture/actions.js.map +0 -1
- package/lib/commonjs/capture/install.js.map +0 -1
- package/lib/commonjs/capture/install.web.js.map +0 -1
- package/lib/commonjs/capture/installExpoImage.js.map +0 -1
- package/lib/commonjs/capture/installRnImage.js.map +0 -1
- package/lib/commonjs/capture/nativeImageModules.js.map +0 -1
- package/lib/commonjs/capture/nativeImageModules.web.js.map +0 -1
- package/lib/commonjs/capture/overrides.js.map +0 -1
- package/lib/commonjs/capture/rnDecoratorEarly.js.map +0 -1
- package/lib/commonjs/capture/savings.js.map +0 -1
- package/lib/commonjs/capture/savings.web.js.map +0 -1
- package/lib/commonjs/capture/shared.js.map +0 -1
- package/lib/commonjs/components/ImageDetail.js.map +0 -1
- package/lib/commonjs/components/ImageDetailFooter.js.map +0 -1
- package/lib/commonjs/components/ImageRow.js.map +0 -1
- package/lib/commonjs/components/ImagesHeader.js.map +0 -1
- package/lib/commonjs/components/ImagesIcon.js.map +0 -1
- package/lib/commonjs/components/ImagesListFooter.js.map +0 -1
- package/lib/commonjs/components/ImagesModal.js.map +0 -1
- package/lib/commonjs/components/ImagesRoot.js.map +0 -1
- package/lib/commonjs/components/ImagesRoot.web.js.map +0 -1
- package/lib/commonjs/components/InsightsBar.js.map +0 -1
- package/lib/commonjs/export.js.map +0 -1
- package/lib/commonjs/format.js.map +0 -1
- package/lib/commonjs/hooks/useImageRecords.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/register.js.map +0 -1
- package/lib/commonjs/registerEarly.js.map +0 -1
- package/lib/commonjs/remote.js.map +0 -1
- package/lib/commonjs/state.js.map +0 -1
- package/lib/commonjs/store/imageEventStore.js.map +0 -1
- package/lib/commonjs/store/insights.js.map +0 -1
- package/lib/commonjs/sync/imagesSyncAdapter.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/autoCapture.js.map +0 -1
- package/lib/module/autoCapture.web.js.map +0 -1
- package/lib/module/capture/actions.js.map +0 -1
- package/lib/module/capture/install.js.map +0 -1
- package/lib/module/capture/install.web.js.map +0 -1
- package/lib/module/capture/installExpoImage.js.map +0 -1
- package/lib/module/capture/installRnImage.js.map +0 -1
- package/lib/module/capture/nativeImageModules.js.map +0 -1
- package/lib/module/capture/nativeImageModules.web.js.map +0 -1
- package/lib/module/capture/overrides.js.map +0 -1
- package/lib/module/capture/rnDecoratorEarly.js.map +0 -1
- package/lib/module/capture/savings.js.map +0 -1
- package/lib/module/capture/savings.web.js.map +0 -1
- package/lib/module/capture/shared.js.map +0 -1
- package/lib/module/components/ImageDetail.js.map +0 -1
- package/lib/module/components/ImageDetailFooter.js.map +0 -1
- package/lib/module/components/ImageRow.js.map +0 -1
- package/lib/module/components/ImagesHeader.js.map +0 -1
- package/lib/module/components/ImagesIcon.js.map +0 -1
- package/lib/module/components/ImagesListFooter.js.map +0 -1
- package/lib/module/components/ImagesModal.js.map +0 -1
- package/lib/module/components/ImagesRoot.js.map +0 -1
- package/lib/module/components/ImagesRoot.web.js.map +0 -1
- package/lib/module/components/InsightsBar.js.map +0 -1
- package/lib/module/export.js.map +0 -1
- package/lib/module/format.js.map +0 -1
- package/lib/module/hooks/useImageRecords.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/register.js.map +0 -1
- package/lib/module/registerEarly.js.map +0 -1
- package/lib/module/remote.js.map +0 -1
- package/lib/module/state.js.map +0 -1
- package/lib/module/store/imageEventStore.js.map +0 -1
- package/lib/module/store/insights.js.map +0 -1
- package/lib/module/sync/imagesSyncAdapter.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/autoCapture.d.ts.map +0 -1
- package/lib/typescript/autoCapture.web.d.ts.map +0 -1
- package/lib/typescript/capture/actions.d.ts.map +0 -1
- package/lib/typescript/capture/install.d.ts.map +0 -1
- package/lib/typescript/capture/install.web.d.ts.map +0 -1
- package/lib/typescript/capture/installExpoImage.d.ts.map +0 -1
- package/lib/typescript/capture/installRnImage.d.ts.map +0 -1
- package/lib/typescript/capture/nativeImageModules.d.ts.map +0 -1
- package/lib/typescript/capture/nativeImageModules.web.d.ts.map +0 -1
- package/lib/typescript/capture/overrides.d.ts.map +0 -1
- package/lib/typescript/capture/rnDecoratorEarly.d.ts.map +0 -1
- package/lib/typescript/capture/savings.d.ts.map +0 -1
- package/lib/typescript/capture/savings.web.d.ts.map +0 -1
- package/lib/typescript/capture/shared.d.ts.map +0 -1
- package/lib/typescript/components/ImageDetail.d.ts.map +0 -1
- package/lib/typescript/components/ImageDetailFooter.d.ts.map +0 -1
- package/lib/typescript/components/ImageRow.d.ts.map +0 -1
- package/lib/typescript/components/ImagesHeader.d.ts.map +0 -1
- package/lib/typescript/components/ImagesIcon.d.ts.map +0 -1
- package/lib/typescript/components/ImagesListFooter.d.ts.map +0 -1
- package/lib/typescript/components/ImagesModal.d.ts.map +0 -1
- package/lib/typescript/components/ImagesRoot.d.ts.map +0 -1
- package/lib/typescript/components/ImagesRoot.web.d.ts.map +0 -1
- package/lib/typescript/components/InsightsBar.d.ts.map +0 -1
- package/lib/typescript/export.d.ts.map +0 -1
- package/lib/typescript/format.d.ts.map +0 -1
- package/lib/typescript/hooks/useImageRecords.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/register.d.ts.map +0 -1
- package/lib/typescript/registerEarly.d.ts.map +0 -1
- package/lib/typescript/remote.d.ts.map +0 -1
- package/lib/typescript/state.d.ts.map +0 -1
- package/lib/typescript/store/imageEventStore.d.ts.map +0 -1
- package/lib/typescript/store/insights.d.ts.map +0 -1
- package/lib/typescript/sync/imagesSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
- package/lib/web/chunks/chunk-7CWLRQ2L.mjs +0 -2
- package/lib/web/chunks/chunk-B5A6CYSS.mjs +0 -2
- package/lib/web/chunks/chunk-LKTR6X2V.mjs +0 -2
|
@@ -1,221 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* imageEventStore
|
|
5
|
-
*
|
|
6
|
-
* Bounded buffer of ImageRecords fed by the capture layer (installRnImage /
|
|
7
|
-
* installExpoImage). Follows the console tool's store shape — Set of
|
|
8
|
-
* listeners, frame-coalesced emit so bursty image grids don't turn every
|
|
9
|
-
* event into its own React commit — but ALL mutable state lives on the
|
|
10
|
-
* globalThis singleton (see ../state.ts) so a dual module instance of this
|
|
11
|
-
* package still reads/writes one shared store.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { PixelRatio } from "react-native";
|
|
15
|
-
import { remoteImagesDispatch } from "../remote";
|
|
16
|
-
import { imagesState } from "../state";
|
|
17
|
-
/** Max records retained before the oldest are dropped. */
|
|
18
|
-
const MAX_RECORDS = 500;
|
|
19
|
-
function emit() {
|
|
20
|
-
const s = imagesState();
|
|
21
|
-
if (s.emitScheduled) return;
|
|
22
|
-
s.emitScheduled = true;
|
|
23
|
-
requestAnimationFrame(() => {
|
|
24
|
-
s.emitScheduled = false;
|
|
25
|
-
for (const listener of s.listeners) listener();
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Snapshot identity only changes on emit, so useSyncExternalStore stays cheap. */
|
|
30
|
-
function invalidate() {
|
|
31
|
-
const s = imagesState();
|
|
32
|
-
s.snapshot = [...s.records];
|
|
33
|
-
emit();
|
|
34
|
-
}
|
|
35
|
-
export function createRecord(partial) {
|
|
36
|
-
const s = imagesState();
|
|
37
|
-
const record = {
|
|
38
|
-
...partial,
|
|
39
|
-
id: s.nextId++,
|
|
40
|
-
createdAt: Date.now(),
|
|
41
|
-
status: "pending",
|
|
42
|
-
mounted: true,
|
|
43
|
-
loadCount: 0
|
|
44
|
-
};
|
|
45
|
-
s.records.push(record);
|
|
46
|
-
s.byId.set(record.id, record);
|
|
47
|
-
if (s.records.length > MAX_RECORDS) {
|
|
48
|
-
const dropped = s.records.splice(0, s.records.length - MAX_RECORDS);
|
|
49
|
-
for (const d of dropped) s.byId.delete(d.id);
|
|
50
|
-
}
|
|
51
|
-
invalidate();
|
|
52
|
-
return record;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Mutate a record in place (records are owned by the store) and notify. */
|
|
56
|
-
export function updateRecord(id, patch) {
|
|
57
|
-
const s = imagesState();
|
|
58
|
-
const record = s.byId.get(id);
|
|
59
|
-
if (!record) return undefined;
|
|
60
|
-
Object.assign(record, patch);
|
|
61
|
-
invalidate();
|
|
62
|
-
return record;
|
|
63
|
-
}
|
|
64
|
-
export function getRecord(id) {
|
|
65
|
-
return imagesState().byId.get(id);
|
|
66
|
-
}
|
|
67
|
-
export function getSnapshot() {
|
|
68
|
-
return imagesState().snapshot;
|
|
69
|
-
}
|
|
70
|
-
export function subscribe(listener) {
|
|
71
|
-
const s = imagesState();
|
|
72
|
-
s.listeners.add(listener);
|
|
73
|
-
return () => imagesState().listeners.delete(listener);
|
|
74
|
-
}
|
|
75
|
-
export function clearRecords() {
|
|
76
|
-
// Mirror mode: clear the device's registry too, or the next snapshot would
|
|
77
|
-
// refill this store straight away.
|
|
78
|
-
void remoteImagesDispatch()?.("clearRecords");
|
|
79
|
-
const s = imagesState();
|
|
80
|
-
// Keep records still actively loading so in-flight events don't orphan.
|
|
81
|
-
s.records = s.records.filter(r => r.mounted && r.status !== "loaded" && r.status !== "error");
|
|
82
|
-
s.byId = new Map(s.records.map(r => [r.id, r]));
|
|
83
|
-
invalidate();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// Mirror mode (desktop dashboard)
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Dashboards never instrument their own images — the store is purely a mirror
|
|
92
|
-
* of the synced device. Makes installImageCapture() a permanent no-op.
|
|
93
|
-
*/
|
|
94
|
-
export function disableImagesCapture() {
|
|
95
|
-
imagesState().captureDisabled = true;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Replace the whole store from a synced adapter snapshot (wire records are
|
|
100
|
-
* newest-first; the store keeps oldest-first and useImageRecords reverses).
|
|
101
|
-
* Also adopts the device's global modes / capture status and derives the
|
|
102
|
-
* DEVICE's PixelRatio so oversize math doesn't use the dashboard display's.
|
|
103
|
-
*/
|
|
104
|
-
export function replaceImagesFromSnapshot(payload) {
|
|
105
|
-
const s = imagesState();
|
|
106
|
-
const wire = payload.records ?? [];
|
|
107
|
-
|
|
108
|
-
// neededPx was computed on device as round(layout * PixelRatio.get()), so
|
|
109
|
-
// the widest record recovers the device scale with the least rounding error.
|
|
110
|
-
let bestWidth = 0;
|
|
111
|
-
for (const w of wire) {
|
|
112
|
-
if (w.layout && w.neededPx && w.layout.width > bestWidth) {
|
|
113
|
-
bestWidth = w.layout.width;
|
|
114
|
-
s.mirrorPixelRatio = w.neededPx.width / w.layout.width;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const now = Date.now();
|
|
118
|
-
s.records = [...wire].reverse().map(w => ({
|
|
119
|
-
id: w.id,
|
|
120
|
-
lib: w.lib,
|
|
121
|
-
uri: w.uri,
|
|
122
|
-
sourceKind: w.kind,
|
|
123
|
-
status: w.status,
|
|
124
|
-
mounted: w.mounted,
|
|
125
|
-
createdAt: now - w.ageMs,
|
|
126
|
-
durationMs: w.ms ?? undefined,
|
|
127
|
-
intrinsic: w.intrinsic ?? undefined,
|
|
128
|
-
layout: w.layout ?? undefined,
|
|
129
|
-
cacheVerdict: w.cache ?? undefined,
|
|
130
|
-
cacheVerdictSource: w.cacheSource ?? undefined,
|
|
131
|
-
progressSeen: w.progressSeen,
|
|
132
|
-
// The wire carries only the total; a settled download loaded all of it.
|
|
133
|
-
bytesLoaded: w.status === "loaded" ? w.bytesTotal ?? undefined : undefined,
|
|
134
|
-
bytesTotal: w.bytesTotal ?? undefined,
|
|
135
|
-
error: w.error ?? undefined,
|
|
136
|
-
errorCode: w.errorCode ?? undefined,
|
|
137
|
-
loadCount: w.loadCount,
|
|
138
|
-
overrideLabel: w.overrideLabel ?? undefined,
|
|
139
|
-
hasAltText: w.hasAltText ?? undefined,
|
|
140
|
-
layoutShifts: w.layoutShifts
|
|
141
|
-
}));
|
|
142
|
-
s.byId = new Map(s.records.map(r => [r.id, r]));
|
|
143
|
-
if (payload.globalModes) s.globalModes = {
|
|
144
|
-
...payload.globalModes
|
|
145
|
-
};
|
|
146
|
-
if (payload.captureStatus) s.mirrorCaptureStatus = payload.captureStatus;
|
|
147
|
-
invalidate();
|
|
148
|
-
// Mode toggles render through the actions bus, not the record store.
|
|
149
|
-
for (const l of s.actionListeners) l();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// Derived math (shared by rows, detail view, and stats)
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
|
|
156
|
-
/** Device PixelRatio on device; the SYNCED device's ratio on a mirror. */
|
|
157
|
-
export function effectivePixelRatio() {
|
|
158
|
-
return imagesState().mirrorPixelRatio ?? PixelRatio.get();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Physical pixels actually needed to fill the laid-out view 1:1. */
|
|
162
|
-
export function neededPixels(record) {
|
|
163
|
-
if (!record.layout || record.layout.width <= 0 || record.layout.height <= 0) return null;
|
|
164
|
-
const scale = effectivePixelRatio();
|
|
165
|
-
return {
|
|
166
|
-
width: Math.round(record.layout.width * scale),
|
|
167
|
-
height: Math.round(record.layout.height * scale)
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Linear oversize factor: how many times larger (per axis, area-normalized)
|
|
173
|
-
* the decoded bitmap is vs what the view needs. 1 = perfect, 2 = 4× the pixels.
|
|
174
|
-
*/
|
|
175
|
-
export function oversizeFactor(record) {
|
|
176
|
-
const needed = neededPixels(record);
|
|
177
|
-
if (!needed || !record.intrinsic) return null;
|
|
178
|
-
const neededArea = needed.width * needed.height;
|
|
179
|
-
const intrinsicArea = record.intrinsic.width * record.intrinsic.height;
|
|
180
|
-
if (neededArea <= 0 || intrinsicArea <= 0) return null;
|
|
181
|
-
return Math.sqrt(intrinsicArea / neededArea);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Estimated decoded-bitmap memory for a loaded record (RGBA8888). */
|
|
185
|
-
export function estDecodedBytes(record) {
|
|
186
|
-
if (!record.intrinsic) return 0;
|
|
187
|
-
return record.intrinsic.width * record.intrinsic.height * 4;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Decoded bytes beyond what the laid-out size needs (0 if not oversized). */
|
|
191
|
-
export function estWastedBytes(record) {
|
|
192
|
-
const needed = neededPixels(record);
|
|
193
|
-
if (!needed || !record.intrinsic) return 0;
|
|
194
|
-
const wasted = (record.intrinsic.width * record.intrinsic.height - needed.width * needed.height) * 4;
|
|
195
|
-
return Math.max(0, wasted);
|
|
196
|
-
}
|
|
197
|
-
export function computeStats(list) {
|
|
198
|
-
const stats = {
|
|
199
|
-
total: list.length,
|
|
200
|
-
loading: 0,
|
|
201
|
-
loaded: 0,
|
|
202
|
-
errors: 0,
|
|
203
|
-
networkLoads: 0,
|
|
204
|
-
estDecodedBytes: 0,
|
|
205
|
-
estWastedBytes: 0
|
|
206
|
-
};
|
|
207
|
-
for (const r of list) {
|
|
208
|
-
// Unmounted in-flight records are stale (the instance died mid-load) —
|
|
209
|
-
// they must not count as live in-flight work.
|
|
210
|
-
if ((r.status === "loading" || r.status === "pending") && r.mounted) stats.loading++;
|
|
211
|
-
if (r.status === "loaded") stats.loaded++;
|
|
212
|
-
if (r.status === "error") stats.errors++;
|
|
213
|
-
if (r.progressSeen || r.cacheVerdict === "none") stats.networkLoads++;
|
|
214
|
-
if (r.mounted && r.status === "loaded") {
|
|
215
|
-
stats.estDecodedBytes += estDecodedBytes(r);
|
|
216
|
-
stats.estWastedBytes += estWastedBytes(r);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return stats;
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=imageEventStore.js.map
|
|
1
|
+
"use strict";import{PixelRatio as a}from"react-native";import{remoteImagesDispatch as c}from"../remote";import{imagesState as i}from"../state";const u=500;function l(){const e=i();if(e.emitScheduled)return;e.emitScheduled=true;requestAnimationFrame(()=>{e.emitScheduled=false;for(const t of e.listeners)t()})}function s(){const e=i();e.snapshot=[...e.records];l()}export function createRecord(e){const t=i();const o={...e,id:t.nextId++,createdAt:Date.now(),status:"pending",mounted:true,loadCount:0};t.records.push(o);t.byId.set(o.id,o);if(t.records.length>u){const r=t.records.splice(0,t.records.length-u);for(const d of r)t.byId.delete(d.id)}s();return o}export function updateRecord(e,t){const o=i();const r=o.byId.get(e);if(!r)return void 0;Object.assign(r,t);s();return r}export function getRecord(e){return i().byId.get(e)}export function getSnapshot(){return i().snapshot}export function subscribe(e){const t=i();t.listeners.add(e);return()=>i().listeners.delete(e)}export function clearRecords(){void c()?.("clearRecords");const e=i();e.records=e.records.filter(t=>t.mounted&&t.status!=="loaded"&&t.status!=="error");e.byId=new Map(e.records.map(t=>[t.id,t]));s()}export function disableImagesCapture(){i().captureDisabled=true}export function replaceImagesFromSnapshot(e){const t=i();const o=e.records??[];let r=0;for(const n of o){if(n.layout&&n.neededPx&&n.layout.width>r){r=n.layout.width;t.mirrorPixelRatio=n.neededPx.width/n.layout.width}}const d=Date.now();t.records=[...o].reverse().map(n=>({id:n.id,lib:n.lib,uri:n.uri,sourceKind:n.kind,status:n.status,mounted:n.mounted,createdAt:d-n.ageMs,durationMs:n.ms??void 0,intrinsic:n.intrinsic??void 0,layout:n.layout??void 0,cacheVerdict:n.cache??void 0,cacheVerdictSource:n.cacheSource??void 0,progressSeen:n.progressSeen,bytesLoaded:n.status==="loaded"?n.bytesTotal??void 0:void 0,bytesTotal:n.bytesTotal??void 0,error:n.error??void 0,errorCode:n.errorCode??void 0,loadCount:n.loadCount,overrideLabel:n.overrideLabel??void 0,hasAltText:n.hasAltText??void 0,layoutShifts:n.layoutShifts}));t.byId=new Map(t.records.map(n=>[n.id,n]));if(e.globalModes)t.globalModes={...e.globalModes};if(e.captureStatus)t.mirrorCaptureStatus=e.captureStatus;s();for(const n of t.actionListeners)n()}export function effectivePixelRatio(){return i().mirrorPixelRatio??a.get()}export function neededPixels(e){if(!e.layout||e.layout.width<=0||e.layout.height<=0)return null;const t=effectivePixelRatio();return{width:Math.round(e.layout.width*t),height:Math.round(e.layout.height*t)}}export function oversizeFactor(e){const t=neededPixels(e);if(!t||!e.intrinsic)return null;const o=t.width*t.height;const r=e.intrinsic.width*e.intrinsic.height;if(o<=0||r<=0)return null;return Math.sqrt(r/o)}export function estDecodedBytes(e){if(!e.intrinsic)return 0;return e.intrinsic.width*e.intrinsic.height*4}export function estWastedBytes(e){const t=neededPixels(e);if(!t||!e.intrinsic)return 0;const o=(e.intrinsic.width*e.intrinsic.height-t.width*t.height)*4;return Math.max(0,o)}export function computeStats(e){const t={total:e.length,loading:0,loaded:0,errors:0,networkLoads:0,estDecodedBytes:0,estWastedBytes:0};for(const o of e){if((o.status==="loading"||o.status==="pending")&&o.mounted)t.loading++;if(o.status==="loaded")t.loaded++;if(o.status==="error")t.errors++;if(o.progressSeen||o.cacheVerdict==="none")t.networkLoads++;if(o.mounted&&o.status==="loaded"){t.estDecodedBytes+=estDecodedBytes(o);t.estWastedBytes+=estWastedBytes(o)}}return t}
|
|
@@ -1,71 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cross-record insights — findings computed purely from data the registry
|
|
5
|
-
* already holds (no extra instrumentation): duplicate simultaneous loads,
|
|
6
|
-
* retry storms, iOS image-queue saturation, missing alt text, and
|
|
7
|
-
* layout-shifting images.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** loadCount at/above this flags render-thrash reload storms. */
|
|
11
|
-
const RETRY_STORM_THRESHOLD = 5;
|
|
12
|
-
/** RCTImageLoader allows 4 concurrent loading tasks on iOS. */
|
|
13
|
-
const IOS_CONCURRENT_LOAD_LIMIT = 4;
|
|
14
|
-
export function computeInsights(records) {
|
|
15
|
-
const byUri = new Map();
|
|
16
|
-
const retryStorms = [];
|
|
17
|
-
const layoutShifters = [];
|
|
18
|
-
let missingAlt = 0;
|
|
19
|
-
let rnLoading = 0;
|
|
20
|
-
for (const r of records) {
|
|
21
|
-
if (r.mounted && r.uri) {
|
|
22
|
-
byUri.set(r.uri, (byUri.get(r.uri) ?? 0) + 1);
|
|
23
|
-
}
|
|
24
|
-
if (r.loadCount >= RETRY_STORM_THRESHOLD) {
|
|
25
|
-
retryStorms.push({
|
|
26
|
-
id: r.id,
|
|
27
|
-
uri: r.uri,
|
|
28
|
-
loadCount: r.loadCount
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if ((r.layoutShifts ?? 0) > 0) {
|
|
32
|
-
layoutShifters.push({
|
|
33
|
-
id: r.id,
|
|
34
|
-
uri: r.uri,
|
|
35
|
-
shifts: r.layoutShifts ?? 0
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
if (r.mounted && r.hasAltText === false) missingAlt++;
|
|
39
|
-
if (r.lib === "rn" && r.mounted && r.status === "loading") rnLoading++;
|
|
40
|
-
}
|
|
41
|
-
const duplicates = [...byUri.entries()].filter(([, count]) => count > 1).map(([uri, count]) => ({
|
|
42
|
-
uri,
|
|
43
|
-
count
|
|
44
|
-
})).sort((a, b) => b.count - a.count);
|
|
45
|
-
return {
|
|
46
|
-
duplicates,
|
|
47
|
-
retryStorms: retryStorms.sort((a, b) => b.loadCount - a.loadCount),
|
|
48
|
-
queueSaturated: rnLoading > IOS_CONCURRENT_LOAD_LIMIT,
|
|
49
|
-
missingAlt,
|
|
50
|
-
layoutShifters
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Compact one-line chips for UI/MCP surfacing. Empty array = all clear. */
|
|
55
|
-
export function insightChips(insights) {
|
|
56
|
-
const chips = [];
|
|
57
|
-
if (insights.duplicates.length > 0) {
|
|
58
|
-
const extra = insights.duplicates.reduce((n, d) => n + d.count - 1, 0);
|
|
59
|
-
chips.push(`${insights.duplicates.length} duplicate URL${insights.duplicates.length > 1 ? "s" : ""} (${extra} redundant loads)`);
|
|
60
|
-
}
|
|
61
|
-
if (insights.retryStorms.length > 0) {
|
|
62
|
-
chips.push(`${insights.retryStorms.length} retry storm${insights.retryStorms.length > 1 ? "s" : ""}`);
|
|
63
|
-
}
|
|
64
|
-
if (insights.queueSaturated) chips.push("iOS image queue saturated (>4 loading)");
|
|
65
|
-
if (insights.missingAlt > 0) chips.push(`${insights.missingAlt} missing alt text`);
|
|
66
|
-
if (insights.layoutShifters.length > 0) {
|
|
67
|
-
chips.push(`${insights.layoutShifters.length} layout-shifting image${insights.layoutShifters.length > 1 ? "s" : ""}`);
|
|
68
|
-
}
|
|
69
|
-
return chips;
|
|
70
|
-
}
|
|
71
|
-
//# sourceMappingURL=insights.js.map
|
|
1
|
+
"use strict";const i=5;const d=4;export function computeInsights(e){const u=new Map;const r=[];const l=[];let s=0;let n=0;for(const t of e){if(t.mounted&&t.uri){u.set(t.uri,(u.get(t.uri)??0)+1)}if(t.loadCount>=i){r.push({id:t.id,uri:t.uri,loadCount:t.loadCount})}if((t.layoutShifts??0)>0){l.push({id:t.id,uri:t.uri,shifts:t.layoutShifts??0})}if(t.mounted&&t.hasAltText===false)s++;if(t.lib==="rn"&&t.mounted&&t.status==="loading")n++}const a=[...u.entries()].filter(([,t])=>t>1).map(([t,o])=>({uri:t,count:o})).sort((t,o)=>o.count-t.count);return{duplicates:a,retryStorms:r.sort((t,o)=>o.loadCount-t.loadCount),queueSaturated:n>d,missingAlt:s,layoutShifters:l}}export function insightChips(e){const u=[];if(e.duplicates.length>0){const r=e.duplicates.reduce((l,s)=>l+s.count-1,0);u.push(`${e.duplicates.length} duplicate URL${e.duplicates.length>1?"s":""} (${r} redundant loads)`)}if(e.retryStorms.length>0){u.push(`${e.retryStorms.length} retry storm${e.retryStorms.length>1?"s":""}`)}if(e.queueSaturated)u.push("iOS image queue saturated (>4 loading)");if(e.missingAlt>0)u.push(`${e.missingAlt} missing alt text`);if(e.layoutShifters.length>0){u.push(`${e.layoutShifters.length} layout-shifting image${e.layoutShifters.length>1?"s":""}`)}return u}
|
|
@@ -1,356 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { loadExpoImage } from "../capture/nativeImageModules";
|
|
4
|
-
/**
|
|
5
|
-
* Sync adapter for the Images tool, consumed by @buoy-gg/external-sync's
|
|
6
|
-
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
7
|
-
* this package doesn't need a dependency on it — same pattern as js-top's
|
|
8
|
-
* adapter). Exposes the live image registry to the desktop dashboard and the
|
|
9
|
-
* MCP server, plus every action the on-device UI has: reload/retry,
|
|
10
|
-
* simulation overrides (force error/loading/blank/URL swap), locate-flash,
|
|
11
|
-
* global modes, and cache clears.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { evictExpoDiskEntry, hardReloadAllMounted, hardReloadRecord, retryRecord } from "../capture/actions";
|
|
15
|
-
import { getCaptureStatus } from "../capture/install";
|
|
16
|
-
import { clearAllOverrides, clearOverride, flashAllMounted, flashRecord, getGlobalModes, setBlankImages, setNetworkMode, setOverride, setOverrideForAllMounted } from "../capture/overrides";
|
|
17
|
-
import { proveSavings } from "../capture/savings";
|
|
18
|
-
import { computeInsights } from "../store/insights";
|
|
19
|
-
import { clearRecords, computeStats, estDecodedBytes, estWastedBytes, getRecord, getSnapshot, neededPixels, oversizeFactor, subscribe } from "../store/imageEventStore";
|
|
20
|
-
/**
|
|
21
|
-
* `data:` URIs (and any URI over this) are NOT copied onto the snapshot.
|
|
22
|
-
* A single inline PNG is routinely hundreds of KB to several MB of base64;
|
|
23
|
-
* 100 of those on every ~200ms tick exceed the 2MB emit budget and drop
|
|
24
|
-
* the whole images panel. The list already uses a "?" thumb for `data`
|
|
25
|
-
* sources (see ImageRow), so the payload is unused on the wire. Dashboards
|
|
26
|
-
* still get `kind: "data"` and a short unique stub for the row label.
|
|
27
|
-
*/
|
|
28
|
-
export const SNAPSHOT_URI_INLINE_LIMIT = 2 * 1024;
|
|
29
|
-
export function toWireUri(uri) {
|
|
30
|
-
if (!uri) return uri;
|
|
31
|
-
if (uri.startsWith("data:")) {
|
|
32
|
-
const comma = uri.indexOf(",");
|
|
33
|
-
const header = comma >= 0 ? uri.slice(0, Math.min(comma + 1, 64)) : "data:";
|
|
34
|
-
const payload = comma >= 0 ? uri.slice(comma + 1) : uri;
|
|
35
|
-
const hint = payload.length > 16 ? `${payload.slice(0, 8)}…${payload.slice(-8)}` : payload;
|
|
36
|
-
return `${header}[${uri.length} chars ${hint}]`;
|
|
37
|
-
}
|
|
38
|
-
if (uri.length <= SNAPSHOT_URI_INLINE_LIMIT) return uri;
|
|
39
|
-
return `${uri.slice(0, SNAPSHOT_URI_INLINE_LIMIT)}… [${uri.length - SNAPSHOT_URI_INLINE_LIMIT} more]`;
|
|
40
|
-
}
|
|
41
|
-
function capText(value, limit = SNAPSHOT_URI_INLINE_LIMIT) {
|
|
42
|
-
if (!value) return null;
|
|
43
|
-
if (value.length <= limit) return value;
|
|
44
|
-
return `${value.slice(0, limit)}… [${value.length - limit} more]`;
|
|
45
|
-
}
|
|
46
|
-
function toWireInsights(insights) {
|
|
47
|
-
return {
|
|
48
|
-
...insights,
|
|
49
|
-
duplicates: insights.duplicates.map(d => ({
|
|
50
|
-
...d,
|
|
51
|
-
uri: toWireUri(d.uri)
|
|
52
|
-
})),
|
|
53
|
-
retryStorms: insights.retryStorms.map(s => ({
|
|
54
|
-
...s,
|
|
55
|
-
uri: toWireUri(s.uri)
|
|
56
|
-
})),
|
|
57
|
-
layoutShifters: insights.layoutShifters.map(s => ({
|
|
58
|
-
...s,
|
|
59
|
-
uri: toWireUri(s.uri)
|
|
60
|
-
}))
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Slim wire form of a record (heavy/derived fields precomputed + rounded). */
|
|
65
|
-
function toWire(record) {
|
|
66
|
-
const factor = oversizeFactor(record);
|
|
67
|
-
return {
|
|
68
|
-
id: record.id,
|
|
69
|
-
lib: record.lib,
|
|
70
|
-
uri: toWireUri(record.uri),
|
|
71
|
-
kind: record.sourceKind,
|
|
72
|
-
status: record.status,
|
|
73
|
-
mounted: record.mounted,
|
|
74
|
-
cache: record.cacheVerdict ?? null,
|
|
75
|
-
cacheSource: record.cacheVerdictSource ?? null,
|
|
76
|
-
ms: record.durationMs != null ? Math.round(record.durationMs) : null,
|
|
77
|
-
intrinsic: record.intrinsic ?? null,
|
|
78
|
-
layout: record.layout ? {
|
|
79
|
-
width: Math.round(record.layout.width),
|
|
80
|
-
height: Math.round(record.layout.height)
|
|
81
|
-
} : null,
|
|
82
|
-
neededPx: neededPixels(record),
|
|
83
|
-
oversizeFactor: factor != null ? Math.round(factor * 100) / 100 : null,
|
|
84
|
-
decodedKB: Math.round(estDecodedBytes(record) / 1024),
|
|
85
|
-
wastedKB: Math.round(estWastedBytes(record) / 1024),
|
|
86
|
-
progressSeen: record.progressSeen === true,
|
|
87
|
-
bytesTotal: record.bytesTotal ?? null,
|
|
88
|
-
error: capText(record.error),
|
|
89
|
-
errorCode: record.errorCode ?? null,
|
|
90
|
-
loadCount: record.loadCount,
|
|
91
|
-
overrideLabel: record.overrideLabel ?? null,
|
|
92
|
-
hasAltText: record.hasAltText ?? null,
|
|
93
|
-
layoutShifts: record.layoutShifts ?? 0,
|
|
94
|
-
ageMs: Date.now() - record.createdAt
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function requireRecord(params) {
|
|
98
|
-
const id = params?.id;
|
|
99
|
-
if (typeof id !== "number") throw new Error("Missing numeric `id` param");
|
|
100
|
-
const record = getRecord(id);
|
|
101
|
-
if (!record) {
|
|
102
|
-
throw new Error(`Image record ${id} not found (cleared or evicted) — re-run the list action for current ids.`);
|
|
103
|
-
}
|
|
104
|
-
return record;
|
|
105
|
-
}
|
|
106
|
-
function overview() {
|
|
107
|
-
const snapshot = getSnapshot();
|
|
108
|
-
return {
|
|
109
|
-
stats: computeStats(snapshot),
|
|
110
|
-
captureStatus: getCaptureStatus(),
|
|
111
|
-
globalModes: getGlobalModes(),
|
|
112
|
-
insights: toWireInsights(computeInsights(snapshot)),
|
|
113
|
-
records: [...snapshot].reverse() // newest first
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
export const imagesSyncAdapter = {
|
|
117
|
-
version: 1,
|
|
118
|
-
getSnapshot: () => {
|
|
119
|
-
const {
|
|
120
|
-
records,
|
|
121
|
-
...rest
|
|
122
|
-
} = overview();
|
|
123
|
-
return {
|
|
124
|
-
...rest,
|
|
125
|
-
records: records.slice(0, 100).map(toWire)
|
|
126
|
-
};
|
|
127
|
-
},
|
|
128
|
-
subscribe: onChange => subscribe(onChange),
|
|
129
|
-
actions: {
|
|
130
|
-
/** List records (newest first). Params: { limit?, status? }. */
|
|
131
|
-
list: params => {
|
|
132
|
-
const p = params ?? {};
|
|
133
|
-
const limit = Math.min(Math.max(p.limit ?? 50, 1), 200);
|
|
134
|
-
const {
|
|
135
|
-
records,
|
|
136
|
-
...rest
|
|
137
|
-
} = overview();
|
|
138
|
-
const filtered = p.status ? records.filter(r => r.status === p.status) : records;
|
|
139
|
-
return {
|
|
140
|
-
...rest,
|
|
141
|
-
total: filtered.length,
|
|
142
|
-
records: filtered.slice(0, limit).map(toWire)
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
/** Full detail for one record. Params: { id }. */
|
|
146
|
-
getDetail: params => {
|
|
147
|
-
const record = requireRecord(params);
|
|
148
|
-
return {
|
|
149
|
-
...toWire(record),
|
|
150
|
-
errorHeaders: record.errorHeaders ?? null
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
/** Bypass-cache reload. Params: { id }. */
|
|
154
|
-
hardReload: params => hardReloadRecord(requireRecord(params)),
|
|
155
|
-
/** Re-encode at displayed size as WebP, return real byte savings. Params: { id }. */
|
|
156
|
-
proveSavings: params => proveSavings(requireRecord(params)),
|
|
157
|
-
/** Plain fresh load attempt. Params: { id }. */
|
|
158
|
-
retry: params => retryRecord(requireRecord(params)),
|
|
159
|
-
/** Evict the expo-image disk entry for this record. Params: { id }. */
|
|
160
|
-
evictDisk: async params => {
|
|
161
|
-
const record = requireRecord(params);
|
|
162
|
-
const evicted = await evictExpoDiskEntry(record);
|
|
163
|
-
return {
|
|
164
|
-
ok: evicted,
|
|
165
|
-
message: evicted ? "Disk cache entry deleted" : "No disk entry found (expo-image + expo-file-system required)"
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
/** Flash a red border on the on-screen image. Params: { id }. */
|
|
169
|
-
flash: params => {
|
|
170
|
-
flashRecord(requireRecord(params).id);
|
|
171
|
-
return {
|
|
172
|
-
ok: true,
|
|
173
|
-
message: "Flashing for 2.5s"
|
|
174
|
-
};
|
|
175
|
-
},
|
|
176
|
-
/**
|
|
177
|
-
* Apply a simulation override. Params: { id, kind, uri? } where kind is
|
|
178
|
-
* 'error' | 'hang' | 'blank' | 'url' ('url' requires uri).
|
|
179
|
-
*/
|
|
180
|
-
setOverride: params => {
|
|
181
|
-
const record = requireRecord(params);
|
|
182
|
-
const p = params;
|
|
183
|
-
const kinds = {
|
|
184
|
-
error: {
|
|
185
|
-
source: {
|
|
186
|
-
type: "error"
|
|
187
|
-
},
|
|
188
|
-
label: "Forced error"
|
|
189
|
-
},
|
|
190
|
-
hang: {
|
|
191
|
-
source: {
|
|
192
|
-
type: "hang"
|
|
193
|
-
},
|
|
194
|
-
label: "Forced loading"
|
|
195
|
-
},
|
|
196
|
-
blank: {
|
|
197
|
-
source: {
|
|
198
|
-
type: "blank"
|
|
199
|
-
},
|
|
200
|
-
label: "Blanked"
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
if (p.kind === "url") {
|
|
204
|
-
if (!p.uri) throw new Error("kind 'url' requires a `uri` param");
|
|
205
|
-
setOverride(record.id, {
|
|
206
|
-
source: {
|
|
207
|
-
type: "url",
|
|
208
|
-
uri: p.uri
|
|
209
|
-
},
|
|
210
|
-
label: "URL swapped"
|
|
211
|
-
});
|
|
212
|
-
return {
|
|
213
|
-
ok: true,
|
|
214
|
-
message: `Source overridden to ${p.uri}`
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const entry = p.kind ? kinds[p.kind] : undefined;
|
|
218
|
-
if (!entry) throw new Error(`Unknown override kind: ${String(p.kind)}`);
|
|
219
|
-
if (!record.mounted) {
|
|
220
|
-
return {
|
|
221
|
-
ok: false,
|
|
222
|
-
message: "Instance unmounted — overrides need the image on screen"
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
setOverride(record.id, entry);
|
|
226
|
-
return {
|
|
227
|
-
ok: true,
|
|
228
|
-
message: `${entry.label} applied`
|
|
229
|
-
};
|
|
230
|
-
},
|
|
231
|
-
/** Remove a simulation override. Params: { id }. */
|
|
232
|
-
clearOverride: params => {
|
|
233
|
-
clearOverride(requireRecord(params).id);
|
|
234
|
-
return {
|
|
235
|
-
ok: true,
|
|
236
|
-
message: "Override removed — original source restored"
|
|
237
|
-
};
|
|
238
|
-
},
|
|
239
|
-
/**
|
|
240
|
-
* Mass action on every mounted image. Params: { kind } where kind is
|
|
241
|
-
* 'error' | 'loading' | 'blank' | 'reload' | 'flash' | 'restore'.
|
|
242
|
-
*/
|
|
243
|
-
massAction: async params => {
|
|
244
|
-
const kind = params?.kind;
|
|
245
|
-
switch (kind) {
|
|
246
|
-
case "error":
|
|
247
|
-
{
|
|
248
|
-
const n = setOverrideForAllMounted({
|
|
249
|
-
type: "error"
|
|
250
|
-
}, "Forced error");
|
|
251
|
-
return {
|
|
252
|
-
ok: n > 0,
|
|
253
|
-
message: `Forced error on ${n} images`
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
case "loading":
|
|
257
|
-
{
|
|
258
|
-
const n = setOverrideForAllMounted({
|
|
259
|
-
type: "hang"
|
|
260
|
-
}, "Forced loading");
|
|
261
|
-
return {
|
|
262
|
-
ok: n > 0,
|
|
263
|
-
message: `Forced loading on ${n} images`
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
case "blank":
|
|
267
|
-
{
|
|
268
|
-
const n = setOverrideForAllMounted({
|
|
269
|
-
type: "blank"
|
|
270
|
-
}, "Blanked");
|
|
271
|
-
return {
|
|
272
|
-
ok: n > 0,
|
|
273
|
-
message: `Blanked ${n} images`
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
case "reload":
|
|
277
|
-
return hardReloadAllMounted();
|
|
278
|
-
case "flash":
|
|
279
|
-
{
|
|
280
|
-
const n = flashAllMounted();
|
|
281
|
-
return {
|
|
282
|
-
ok: n > 0,
|
|
283
|
-
message: `Flashing ${n} images`
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
case "restore":
|
|
287
|
-
{
|
|
288
|
-
const n = clearAllOverrides();
|
|
289
|
-
return {
|
|
290
|
-
ok: true,
|
|
291
|
-
message: `Restored ${n} images`
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
default:
|
|
295
|
-
throw new Error(`Unknown mass kind: ${String(kind)}`);
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
/** Global network simulation. Params: { mode: 'normal'|'offline'|'cold' }. */
|
|
299
|
-
setNetworkMode: params => {
|
|
300
|
-
const mode = params?.mode;
|
|
301
|
-
if (mode !== "normal" && mode !== "offline" && mode !== "cold") {
|
|
302
|
-
throw new Error("mode must be 'normal' | 'offline' | 'cold'");
|
|
303
|
-
}
|
|
304
|
-
setNetworkMode(mode);
|
|
305
|
-
return {
|
|
306
|
-
ok: true,
|
|
307
|
-
modes: getGlobalModes()
|
|
308
|
-
};
|
|
309
|
-
},
|
|
310
|
-
/** Chrome-style disable-images. Params: { enabled: boolean }. */
|
|
311
|
-
setBlankImages: params => {
|
|
312
|
-
setBlankImages(params?.enabled === true);
|
|
313
|
-
return {
|
|
314
|
-
ok: true,
|
|
315
|
-
modes: getGlobalModes()
|
|
316
|
-
};
|
|
317
|
-
},
|
|
318
|
-
/** Clear the captured registry (keeps in-flight loads). */
|
|
319
|
-
clearRecords: () => {
|
|
320
|
-
clearRecords();
|
|
321
|
-
return {
|
|
322
|
-
ok: true,
|
|
323
|
-
message: "Registry cleared"
|
|
324
|
-
};
|
|
325
|
-
},
|
|
326
|
-
/** Clear expo-image caches. Params: { memory?, disk? } (default both). */
|
|
327
|
-
clearExpoCaches: async params => {
|
|
328
|
-
const p = params ?? {};
|
|
329
|
-
const doMemory = p.memory !== false;
|
|
330
|
-
const doDisk = p.disk !== false;
|
|
331
|
-
try {
|
|
332
|
-
const expo = loadExpoImage();
|
|
333
|
-
const results = [];
|
|
334
|
-
if (doMemory) {
|
|
335
|
-
await expo.Image.clearMemoryCache();
|
|
336
|
-
results.push("memory");
|
|
337
|
-
}
|
|
338
|
-
if (doDisk) {
|
|
339
|
-
await expo.Image.clearDiskCache();
|
|
340
|
-
results.push("disk");
|
|
341
|
-
}
|
|
342
|
-
return {
|
|
343
|
-
ok: true,
|
|
344
|
-
message: `Cleared expo-image ${results.join(" + ")} cache`
|
|
345
|
-
};
|
|
346
|
-
} catch {
|
|
347
|
-
return {
|
|
348
|
-
ok: false,
|
|
349
|
-
message: "expo-image not installed"
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
getCaptureStatus: () => getCaptureStatus()
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
//# sourceMappingURL=imagesSyncAdapter.js.map
|
|
1
|
+
"use strict";import{loadExpoImage as g}from"../capture/nativeImageModules";import{evictExpoDiskEntry as p,hardReloadAllMounted as f,hardReloadRecord as k,retryRecord as y}from"../capture/actions";import{getCaptureStatus as d}from"../capture/install";import{clearAllOverrides as w,clearOverride as b,flashAllMounted as S,flashRecord as v,getGlobalModes as i,setBlankImages as M,setNetworkMode as $,setOverride as c,setOverrideForAllMounted as l}from"../capture/overrides";import{proveSavings as x}from"../capture/savings";import{computeInsights as I}from"../store/insights";import{clearRecords as R,computeStats as E,estDecodedBytes as F,estWastedBytes as A,getRecord as C,getSnapshot as B,neededPixels as O,oversizeFactor as D,subscribe as T}from"../store/imageEventStore";export const SNAPSHOT_URI_INLINE_LIMIT=2*1024;export function toWireUri(e){if(!e)return e;if(e.startsWith("data:")){const r=e.indexOf(",");const t=r>=0?e.slice(0,Math.min(r+1,64)):"data:";const o=r>=0?e.slice(r+1):e;const s=o.length>16?`${o.slice(0,8)}\u2026${o.slice(-8)}`:o;return`${t}[${e.length} chars ${s}]`}if(e.length<=SNAPSHOT_URI_INLINE_LIMIT)return e;return`${e.slice(0,SNAPSHOT_URI_INLINE_LIMIT)}\u2026 [${e.length-SNAPSHOT_URI_INLINE_LIMIT} more]`}function N(e,r=SNAPSHOT_URI_INLINE_LIMIT){if(!e)return null;if(e.length<=r)return e;return`${e.slice(0,r)}\u2026 [${e.length-r} more]`}function L(e){return{...e,duplicates:e.duplicates.map(r=>({...r,uri:toWireUri(r.uri)})),retryStorms:e.retryStorms.map(r=>({...r,uri:toWireUri(r.uri)})),layoutShifters:e.layoutShifters.map(r=>({...r,uri:toWireUri(r.uri)}))}}function u(e){const r=D(e);return{id:e.id,lib:e.lib,uri:toWireUri(e.uri),kind:e.sourceKind,status:e.status,mounted:e.mounted,cache:e.cacheVerdict??null,cacheSource:e.cacheVerdictSource??null,ms:e.durationMs!=null?Math.round(e.durationMs):null,intrinsic:e.intrinsic??null,layout:e.layout?{width:Math.round(e.layout.width),height:Math.round(e.layout.height)}:null,neededPx:O(e),oversizeFactor:r!=null?Math.round(r*100)/100:null,decodedKB:Math.round(F(e)/1024),wastedKB:Math.round(A(e)/1024),progressSeen:e.progressSeen===true,bytesTotal:e.bytesTotal??null,error:N(e.error),errorCode:e.errorCode??null,loadCount:e.loadCount,overrideLabel:e.overrideLabel??null,hasAltText:e.hasAltText??null,layoutShifts:e.layoutShifts??0,ageMs:Date.now()-e.createdAt}}function n(e){const r=e?.id;if(typeof r!=="number")throw new Error("Missing numeric `id` param");const t=C(r);if(!t){throw new Error(`Image record ${r} not found (cleared or evicted) \u2014 re-run the list action for current ids.`)}return t}function m(){const e=B();return{stats:E(e),captureStatus:d(),globalModes:i(),insights:L(I(e)),records:[...e].reverse()}}export const imagesSyncAdapter={version:1,getSnapshot:()=>{const{records:e,...r}=m();return{...r,records:e.slice(0,100).map(u)}},subscribe:e=>T(e),actions:{list:e=>{const r=e??{};const t=Math.min(Math.max(r.limit??50,1),200);const{records:o,...s}=m();const a=r.status?o.filter(h=>h.status===r.status):o;return{...s,total:a.length,records:a.slice(0,t).map(u)}},getDetail:e=>{const r=n(e);return{...u(r),errorHeaders:r.errorHeaders??null}},hardReload:e=>k(n(e)),proveSavings:e=>x(n(e)),retry:e=>y(n(e)),evictDisk:async e=>{const r=n(e);const t=await p(r);return{ok:t,message:t?"Disk cache entry deleted":"No disk entry found (expo-image + expo-file-system required)"}},flash:e=>{v(n(e).id);return{ok:true,message:"Flashing for 2.5s"}},setOverride:e=>{const r=n(e);const t=e;const o={error:{source:{type:"error"},label:"Forced error"},hang:{source:{type:"hang"},label:"Forced loading"},blank:{source:{type:"blank"},label:"Blanked"}};if(t.kind==="url"){if(!t.uri)throw new Error("kind 'url' requires a `uri` param");c(r.id,{source:{type:"url",uri:t.uri},label:"URL swapped"});return{ok:true,message:`Source overridden to ${t.uri}`}}const s=t.kind?o[t.kind]:void 0;if(!s)throw new Error(`Unknown override kind: ${String(t.kind)}`);if(!r.mounted){return{ok:false,message:"Instance unmounted \u2014 overrides need the image on screen"}}c(r.id,s);return{ok:true,message:`${s.label} applied`}},clearOverride:e=>{b(n(e).id);return{ok:true,message:"Override removed \u2014 original source restored"}},massAction:async e=>{const r=e?.kind;switch(r){case"error":{const t=l({type:"error"},"Forced error");return{ok:t>0,message:`Forced error on ${t} images`}}case"loading":{const t=l({type:"hang"},"Forced loading");return{ok:t>0,message:`Forced loading on ${t} images`}}case"blank":{const t=l({type:"blank"},"Blanked");return{ok:t>0,message:`Blanked ${t} images`}}case"reload":return f();case"flash":{const t=S();return{ok:t>0,message:`Flashing ${t} images`}}case"restore":{const t=w();return{ok:true,message:`Restored ${t} images`}}default:throw new Error(`Unknown mass kind: ${String(r)}`)}},setNetworkMode:e=>{const r=e?.mode;if(r!=="normal"&&r!=="offline"&&r!=="cold"){throw new Error("mode must be 'normal' | 'offline' | 'cold'")}$(r);return{ok:true,modes:i()}},setBlankImages:e=>{M(e?.enabled===true);return{ok:true,modes:i()}},clearRecords:()=>{R();return{ok:true,message:"Registry cleared"}},clearExpoCaches:async e=>{const r=e??{};const t=r.memory!==false;const o=r.disk!==false;try{const s=g();const a=[];if(t){await s.Image.clearMemoryCache();a.push("memory")}if(o){await s.Image.clearDiskCache();a.push("disk")}return{ok:true,message:`Cleared expo-image ${a.join(" + ")} cache`}}catch{return{ok:false,message:"expo-image not installed"}}},getCaptureStatus:()=>d()}};
|
package/lib/module/types.js
CHANGED