@buoy-gg/images 7.0.41 → 7.0.42

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.
Files changed (194) hide show
  1. package/README.md +11 -70
  2. package/lib/commonjs/autoCapture.js +1 -4
  3. package/lib/commonjs/autoCapture.web.js +1 -6
  4. package/lib/commonjs/capture/actions.js +1 -243
  5. package/lib/commonjs/capture/install.js +1 -45
  6. package/lib/commonjs/capture/install.web.js +1 -250
  7. package/lib/commonjs/capture/installExpoImage.js +1 -99
  8. package/lib/commonjs/capture/installRnImage.js +1 -132
  9. package/lib/commonjs/capture/nativeImageModules.js +1 -30
  10. package/lib/commonjs/capture/nativeImageModules.web.js +1 -18
  11. package/lib/commonjs/capture/overrides.js +1 -346
  12. package/lib/commonjs/capture/rnDecoratorEarly.js +1 -55
  13. package/lib/commonjs/capture/savings.js +1 -209
  14. package/lib/commonjs/capture/savings.web.js +1 -71
  15. package/lib/commonjs/capture/shared.js +1 -378
  16. package/lib/commonjs/components/ImageDetail.js +1 -455
  17. package/lib/commonjs/components/ImageDetailFooter.js +1 -158
  18. package/lib/commonjs/components/ImageRow.js +1 -156
  19. package/lib/commonjs/components/ImagesHeader.js +1 -63
  20. package/lib/commonjs/components/ImagesIcon.js +1 -19
  21. package/lib/commonjs/components/ImagesListFooter.js +1 -126
  22. package/lib/commonjs/components/ImagesModal.js +1 -240
  23. package/lib/commonjs/components/ImagesRoot.js +1 -24
  24. package/lib/commonjs/components/ImagesRoot.web.js +1 -13
  25. package/lib/commonjs/components/InsightsBar.js +1 -59
  26. package/lib/commonjs/export.js +1 -81
  27. package/lib/commonjs/format.js +1 -117
  28. package/lib/commonjs/hooks/useImageRecords.js +1 -20
  29. package/lib/commonjs/index.js +1 -261
  30. package/lib/commonjs/preset.js +1 -39
  31. package/lib/commonjs/register.js +1 -19
  32. package/lib/commonjs/registerEarly.js +1 -14
  33. package/lib/commonjs/remote.js +1 -42
  34. package/lib/commonjs/state.js +1 -58
  35. package/lib/commonjs/store/imageEventStore.js +1 -238
  36. package/lib/commonjs/store/insights.js +1 -76
  37. package/lib/commonjs/sync/imagesSyncAdapter.js +1 -361
  38. package/lib/commonjs/types.js +0 -1
  39. package/lib/module/autoCapture.js +1 -4
  40. package/lib/module/autoCapture.web.js +1 -5
  41. package/lib/module/capture/actions.js +1 -230
  42. package/lib/module/capture/install.js +1 -40
  43. package/lib/module/capture/install.web.js +1 -243
  44. package/lib/module/capture/installExpoImage.js +1 -94
  45. package/lib/module/capture/installRnImage.js +1 -125
  46. package/lib/module/capture/nativeImageModules.js +1 -24
  47. package/lib/module/capture/nativeImageModules.web.js +1 -12
  48. package/lib/module/capture/overrides.js +1 -329
  49. package/lib/module/capture/rnDecoratorEarly.js +1 -50
  50. package/lib/module/capture/savings.js +1 -205
  51. package/lib/module/capture/savings.web.js +1 -67
  52. package/lib/module/capture/shared.js +1 -369
  53. package/lib/module/components/ImageDetail.js +1 -451
  54. package/lib/module/components/ImageDetailFooter.js +1 -154
  55. package/lib/module/components/ImageRow.js +1 -152
  56. package/lib/module/components/ImagesHeader.js +1 -59
  57. package/lib/module/components/ImagesIcon.js +1 -12
  58. package/lib/module/components/ImagesListFooter.js +1 -122
  59. package/lib/module/components/ImagesModal.js +1 -235
  60. package/lib/module/components/ImagesRoot.js +1 -20
  61. package/lib/module/components/ImagesRoot.web.js +1 -9
  62. package/lib/module/components/InsightsBar.js +1 -55
  63. package/lib/module/export.js +1 -76
  64. package/lib/module/format.js +1 -108
  65. package/lib/module/hooks/useImageRecords.js +1 -16
  66. package/lib/module/index.js +1 -79
  67. package/lib/module/preset.js +1 -35
  68. package/lib/module/register.js +1 -19
  69. package/lib/module/registerEarly.js +1 -14
  70. package/lib/module/remote.js +1 -36
  71. package/lib/module/state.js +1 -54
  72. package/lib/module/store/imageEventStore.js +1 -221
  73. package/lib/module/store/insights.js +1 -71
  74. package/lib/module/sync/imagesSyncAdapter.js +1 -356
  75. package/lib/module/types.js +0 -1
  76. package/lib/web/chunks/chunk-TTS5EH4R.mjs +1 -2
  77. package/lib/web/index.mjs +3 -4
  78. package/lib/web/register.mjs +1 -2
  79. package/package.json +2 -2
  80. package/register.js +1 -15
  81. package/lib/commonjs/autoCapture.js.map +0 -1
  82. package/lib/commonjs/autoCapture.web.js.map +0 -1
  83. package/lib/commonjs/capture/actions.js.map +0 -1
  84. package/lib/commonjs/capture/install.js.map +0 -1
  85. package/lib/commonjs/capture/install.web.js.map +0 -1
  86. package/lib/commonjs/capture/installExpoImage.js.map +0 -1
  87. package/lib/commonjs/capture/installRnImage.js.map +0 -1
  88. package/lib/commonjs/capture/nativeImageModules.js.map +0 -1
  89. package/lib/commonjs/capture/nativeImageModules.web.js.map +0 -1
  90. package/lib/commonjs/capture/overrides.js.map +0 -1
  91. package/lib/commonjs/capture/rnDecoratorEarly.js.map +0 -1
  92. package/lib/commonjs/capture/savings.js.map +0 -1
  93. package/lib/commonjs/capture/savings.web.js.map +0 -1
  94. package/lib/commonjs/capture/shared.js.map +0 -1
  95. package/lib/commonjs/components/ImageDetail.js.map +0 -1
  96. package/lib/commonjs/components/ImageDetailFooter.js.map +0 -1
  97. package/lib/commonjs/components/ImageRow.js.map +0 -1
  98. package/lib/commonjs/components/ImagesHeader.js.map +0 -1
  99. package/lib/commonjs/components/ImagesIcon.js.map +0 -1
  100. package/lib/commonjs/components/ImagesListFooter.js.map +0 -1
  101. package/lib/commonjs/components/ImagesModal.js.map +0 -1
  102. package/lib/commonjs/components/ImagesRoot.js.map +0 -1
  103. package/lib/commonjs/components/ImagesRoot.web.js.map +0 -1
  104. package/lib/commonjs/components/InsightsBar.js.map +0 -1
  105. package/lib/commonjs/export.js.map +0 -1
  106. package/lib/commonjs/format.js.map +0 -1
  107. package/lib/commonjs/hooks/useImageRecords.js.map +0 -1
  108. package/lib/commonjs/index.js.map +0 -1
  109. package/lib/commonjs/preset.js.map +0 -1
  110. package/lib/commonjs/register.js.map +0 -1
  111. package/lib/commonjs/registerEarly.js.map +0 -1
  112. package/lib/commonjs/remote.js.map +0 -1
  113. package/lib/commonjs/state.js.map +0 -1
  114. package/lib/commonjs/store/imageEventStore.js.map +0 -1
  115. package/lib/commonjs/store/insights.js.map +0 -1
  116. package/lib/commonjs/sync/imagesSyncAdapter.js.map +0 -1
  117. package/lib/commonjs/types.js.map +0 -1
  118. package/lib/module/autoCapture.js.map +0 -1
  119. package/lib/module/autoCapture.web.js.map +0 -1
  120. package/lib/module/capture/actions.js.map +0 -1
  121. package/lib/module/capture/install.js.map +0 -1
  122. package/lib/module/capture/install.web.js.map +0 -1
  123. package/lib/module/capture/installExpoImage.js.map +0 -1
  124. package/lib/module/capture/installRnImage.js.map +0 -1
  125. package/lib/module/capture/nativeImageModules.js.map +0 -1
  126. package/lib/module/capture/nativeImageModules.web.js.map +0 -1
  127. package/lib/module/capture/overrides.js.map +0 -1
  128. package/lib/module/capture/rnDecoratorEarly.js.map +0 -1
  129. package/lib/module/capture/savings.js.map +0 -1
  130. package/lib/module/capture/savings.web.js.map +0 -1
  131. package/lib/module/capture/shared.js.map +0 -1
  132. package/lib/module/components/ImageDetail.js.map +0 -1
  133. package/lib/module/components/ImageDetailFooter.js.map +0 -1
  134. package/lib/module/components/ImageRow.js.map +0 -1
  135. package/lib/module/components/ImagesHeader.js.map +0 -1
  136. package/lib/module/components/ImagesIcon.js.map +0 -1
  137. package/lib/module/components/ImagesListFooter.js.map +0 -1
  138. package/lib/module/components/ImagesModal.js.map +0 -1
  139. package/lib/module/components/ImagesRoot.js.map +0 -1
  140. package/lib/module/components/ImagesRoot.web.js.map +0 -1
  141. package/lib/module/components/InsightsBar.js.map +0 -1
  142. package/lib/module/export.js.map +0 -1
  143. package/lib/module/format.js.map +0 -1
  144. package/lib/module/hooks/useImageRecords.js.map +0 -1
  145. package/lib/module/index.js.map +0 -1
  146. package/lib/module/preset.js.map +0 -1
  147. package/lib/module/register.js.map +0 -1
  148. package/lib/module/registerEarly.js.map +0 -1
  149. package/lib/module/remote.js.map +0 -1
  150. package/lib/module/state.js.map +0 -1
  151. package/lib/module/store/imageEventStore.js.map +0 -1
  152. package/lib/module/store/insights.js.map +0 -1
  153. package/lib/module/sync/imagesSyncAdapter.js.map +0 -1
  154. package/lib/module/types.js.map +0 -1
  155. package/lib/typescript/autoCapture.d.ts.map +0 -1
  156. package/lib/typescript/autoCapture.web.d.ts.map +0 -1
  157. package/lib/typescript/capture/actions.d.ts.map +0 -1
  158. package/lib/typescript/capture/install.d.ts.map +0 -1
  159. package/lib/typescript/capture/install.web.d.ts.map +0 -1
  160. package/lib/typescript/capture/installExpoImage.d.ts.map +0 -1
  161. package/lib/typescript/capture/installRnImage.d.ts.map +0 -1
  162. package/lib/typescript/capture/nativeImageModules.d.ts.map +0 -1
  163. package/lib/typescript/capture/nativeImageModules.web.d.ts.map +0 -1
  164. package/lib/typescript/capture/overrides.d.ts.map +0 -1
  165. package/lib/typescript/capture/rnDecoratorEarly.d.ts.map +0 -1
  166. package/lib/typescript/capture/savings.d.ts.map +0 -1
  167. package/lib/typescript/capture/savings.web.d.ts.map +0 -1
  168. package/lib/typescript/capture/shared.d.ts.map +0 -1
  169. package/lib/typescript/components/ImageDetail.d.ts.map +0 -1
  170. package/lib/typescript/components/ImageDetailFooter.d.ts.map +0 -1
  171. package/lib/typescript/components/ImageRow.d.ts.map +0 -1
  172. package/lib/typescript/components/ImagesHeader.d.ts.map +0 -1
  173. package/lib/typescript/components/ImagesIcon.d.ts.map +0 -1
  174. package/lib/typescript/components/ImagesListFooter.d.ts.map +0 -1
  175. package/lib/typescript/components/ImagesModal.d.ts.map +0 -1
  176. package/lib/typescript/components/ImagesRoot.d.ts.map +0 -1
  177. package/lib/typescript/components/ImagesRoot.web.d.ts.map +0 -1
  178. package/lib/typescript/components/InsightsBar.d.ts.map +0 -1
  179. package/lib/typescript/export.d.ts.map +0 -1
  180. package/lib/typescript/format.d.ts.map +0 -1
  181. package/lib/typescript/hooks/useImageRecords.d.ts.map +0 -1
  182. package/lib/typescript/index.d.ts.map +0 -1
  183. package/lib/typescript/preset.d.ts.map +0 -1
  184. package/lib/typescript/register.d.ts.map +0 -1
  185. package/lib/typescript/registerEarly.d.ts.map +0 -1
  186. package/lib/typescript/remote.d.ts.map +0 -1
  187. package/lib/typescript/state.d.ts.map +0 -1
  188. package/lib/typescript/store/imageEventStore.d.ts.map +0 -1
  189. package/lib/typescript/store/insights.d.ts.map +0 -1
  190. package/lib/typescript/sync/imagesSyncAdapter.d.ts.map +0 -1
  191. package/lib/typescript/types.d.ts.map +0 -1
  192. package/lib/web/chunks/chunk-7CWLRQ2L.mjs +0 -2
  193. package/lib/web/chunks/chunk-B5A6CYSS.mjs +0 -2
  194. package/lib/web/chunks/chunk-LKTR6X2V.mjs +0 -2
@@ -1,451 +1 @@
1
- "use strict";
2
-
3
- import { loadExpoImage } from "../capture/nativeImageModules";
4
- /**
5
- * ImageDetail — full record view: preview, source, load timeline, dimensions
6
- * with the oversize verdict + estimated decoded/wasted memory, cache verdict,
7
- * and error details (including HTTP status/headers when the platform provides
8
- * them). Cache actions live here too (expo-image global clears).
9
- */
10
-
11
- import { useState } from "react";
12
- import { Image, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
13
- import { macOSColors, night, monoFont } from "@buoy-gg/shared-ui";
14
- import { clearOverride, setOverride } from "../capture/overrides";
15
- import { proveSavings } from "../capture/savings";
16
- import { remoteImagesDispatch } from "../remote";
17
- import { effectivePixelRatio, estDecodedBytes, estWastedBytes, neededPixels } from "../store/imageEventStore";
18
- import { cacheBadge, formatBytes, formatMs, sizeVerdict } from "../format";
19
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
20
- function Section({
21
- title,
22
- children
23
- }) {
24
- return /*#__PURE__*/_jsxs(View, {
25
- style: styles.section,
26
- children: [/*#__PURE__*/_jsx(Text, {
27
- style: styles.sectionTitle,
28
- children: title
29
- }), /*#__PURE__*/_jsx(View, {
30
- style: styles.sectionBody,
31
- children: children
32
- })]
33
- });
34
- }
35
- function Row({
36
- label,
37
- value,
38
- color
39
- }) {
40
- return /*#__PURE__*/_jsxs(View, {
41
- style: styles.kvRow,
42
- children: [/*#__PURE__*/_jsx(Text, {
43
- style: styles.kvLabel,
44
- children: label
45
- }), /*#__PURE__*/_jsx(Text, {
46
- style: [styles.kvValue, color ? {
47
- color
48
- } : null],
49
- selectable: true,
50
- children: value
51
- })]
52
- });
53
- }
54
-
55
- /** expo-image global cache clears (no-ops if expo-image is absent). */
56
- function clearExpoCache(kind) {
57
- // Mirror mode: the caches live on the synced device.
58
- const remote = remoteImagesDispatch();
59
- if (remote) {
60
- void remote("clearExpoCaches", {
61
- memory: kind === "memory",
62
- disk: kind === "disk"
63
- });
64
- return;
65
- }
66
- try {
67
- const expo = loadExpoImage();
68
- if (kind === "memory") void expo.Image.clearMemoryCache();else void expo.Image.clearDiskCache();
69
- } catch {
70
- // expo-image not installed
71
- }
72
- }
73
-
74
- /** Free-text source swap: test another URL/format/CDN param on the live view. */
75
- function SwapUrlSection({
76
- record
77
- }) {
78
- const [url, setUrl] = useState(record.uri);
79
- const trimmed = url.trim();
80
- const canApply = record.mounted && trimmed.length > 0 && trimmed !== record.uri;
81
- return /*#__PURE__*/_jsxs(Section, {
82
- title: "SWAP SOURCE URL",
83
- children: [/*#__PURE__*/_jsx(TextInput, {
84
- style: styles.urlInput,
85
- value: url,
86
- onChangeText: setUrl,
87
- placeholder: "https://\u2026",
88
- placeholderTextColor: night.color.placeholder,
89
- autoCapitalize: "none",
90
- autoCorrect: false,
91
- multiline: true
92
- }), /*#__PURE__*/_jsxs(View, {
93
- style: styles.actionsRow,
94
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
95
- style: [styles.actionButton, !canApply && styles.actionDisabled],
96
- disabled: !canApply,
97
- onPress: () => setOverride(record.id, {
98
- source: {
99
- type: "url",
100
- uri: trimmed
101
- },
102
- label: "URL swapped"
103
- }),
104
- children: /*#__PURE__*/_jsx(Text, {
105
- style: styles.actionText,
106
- children: "Apply"
107
- })
108
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
109
- style: styles.actionButton,
110
- onPress: () => setUrl(record.uri),
111
- children: /*#__PURE__*/_jsx(Text, {
112
- style: styles.actionText,
113
- children: "Reset field"
114
- })
115
- })]
116
- })]
117
- });
118
- }
119
-
120
- /**
121
- * Real, on-device savings proof: re-encode at displayed size as WebP via
122
- * expo-image-manipulator, report actual bytes, offer an in-place preview.
123
- */
124
- function SavingsSection({
125
- record
126
- }) {
127
- const [busy, setBusy] = useState(false);
128
- const [result, setResult] = useState(null);
129
- const run = () => {
130
- setBusy(true);
131
- proveSavings(record).then(setResult).catch(error => setResult({
132
- ok: false,
133
- message: String(error?.message ?? error)
134
- })).finally(() => setBusy(false));
135
- };
136
- return /*#__PURE__*/_jsx(Section, {
137
- title: "PROVE SAVINGS (re-encode on device)",
138
- children: result == null ? /*#__PURE__*/_jsx(View, {
139
- style: styles.actionsRow,
140
- children: /*#__PURE__*/_jsx(TouchableOpacity, {
141
- style: styles.actionButton,
142
- onPress: run,
143
- disabled: busy,
144
- children: /*#__PURE__*/_jsx(Text, {
145
- style: styles.actionText,
146
- children: busy ? "Re-encoding…" : "Re-encode at displayed size (WebP)"
147
- })
148
- })
149
- }) : /*#__PURE__*/_jsxs(_Fragment, {
150
- children: [/*#__PURE__*/_jsx(Row, {
151
- label: "Result",
152
- value: result.message || (result.ok ? "done" : "failed"),
153
- color: result.ok ? macOSColors.semantic.success : macOSColors.semantic.error
154
- }), result.originalBytes != null ? /*#__PURE__*/_jsx(Row, {
155
- label: "Original",
156
- value: `${formatBytes(result.originalBytes)} (${result.originalBytesSource})`
157
- }) : null, result.optimizedBytes != null ? /*#__PURE__*/_jsx(Row, {
158
- label: "Optimized",
159
- value: `${formatBytes(result.optimizedBytes)} — ${result.optimizedDims?.width}×${result.optimizedDims?.height} WebP`,
160
- color: macOSColors.semantic.success
161
- }) : null, result.savedBytes != null ? /*#__PURE__*/_jsx(Row, {
162
- label: "Saved",
163
- value: `${formatBytes(result.savedBytes)} (${result.savedPct}%)`,
164
- color: macOSColors.semantic.success
165
- }) : null, /*#__PURE__*/_jsxs(View, {
166
- style: styles.actionsRow,
167
- children: [result.ok && result.optimizedUri && record.mounted ? /*#__PURE__*/_jsx(TouchableOpacity, {
168
- style: styles.actionButton,
169
- onPress: () => setOverride(record.id, {
170
- source: {
171
- type: "url",
172
- uri: result.optimizedUri
173
- },
174
- label: "Optimized preview"
175
- }),
176
- children: /*#__PURE__*/_jsx(Text, {
177
- style: styles.actionText,
178
- children: "Preview in place"
179
- })
180
- }) : null, /*#__PURE__*/_jsx(TouchableOpacity, {
181
- style: styles.actionButton,
182
- onPress: run,
183
- disabled: busy,
184
- children: /*#__PURE__*/_jsx(Text, {
185
- style: styles.actionText,
186
- children: busy ? "Re-encoding…" : "Re-run"
187
- })
188
- })]
189
- })]
190
- })
191
- });
192
- }
193
- export function ImageDetail({
194
- record
195
- }) {
196
- const verdict = sizeVerdict(record);
197
- const cache = cacheBadge(record);
198
- const needed = neededPixels(record);
199
- const decoded = estDecodedBytes(record);
200
- const wasted = estWastedBytes(record);
201
- return /*#__PURE__*/_jsxs(ScrollView, {
202
- style: styles.container,
203
- contentContainerStyle: styles.content,
204
- children: [record.overrideLabel ? /*#__PURE__*/_jsxs(View, {
205
- style: styles.overrideBanner,
206
- children: [/*#__PURE__*/_jsxs(Text, {
207
- style: styles.overrideText,
208
- children: ["Simulation active: ", record.overrideLabel, " \u2014 stats below describe the override, not the original source."]
209
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
210
- onPress: () => clearOverride(record.id),
211
- children: /*#__PURE__*/_jsx(Text, {
212
- style: styles.overrideRestore,
213
- children: "RESTORE"
214
- })
215
- })]
216
- }) : null, record.uri && record.sourceKind !== "data" ? /*#__PURE__*/_jsx(View, {
217
- style: styles.previewBox,
218
- children: /*#__PURE__*/_jsx(Image, {
219
- testID: "buoy-internal-images-preview",
220
- source: {
221
- uri: record.uri
222
- },
223
- style: styles.preview,
224
- resizeMode: "contain"
225
- })
226
- }) : null, /*#__PURE__*/_jsxs(Section, {
227
- title: "SOURCE",
228
- children: [/*#__PURE__*/_jsx(Row, {
229
- label: "URI",
230
- value: record.uri || "(none)"
231
- }), /*#__PURE__*/_jsx(Row, {
232
- label: "Kind",
233
- value: record.sourceKind
234
- }), /*#__PURE__*/_jsx(Row, {
235
- label: "Library",
236
- value: record.lib === "web" ? "Browser <img>" : record.lib === "expo" ? "expo-image" : "RN <Image>"
237
- }), record.hasAltText === false ? /*#__PURE__*/_jsx(Row, {
238
- label: "Alt text",
239
- value: "missing \u2014 add alt/accessibilityLabel",
240
- color: macOSColors.semantic.warning
241
- }) : null, record.mediaType != null ? /*#__PURE__*/_jsx(Row, {
242
- label: "Media type",
243
- value: String(record.mediaType)
244
- }) : null, record.isAnimated != null ? /*#__PURE__*/_jsx(Row, {
245
- label: "Animated",
246
- value: record.isAnimated ? "yes" : "no"
247
- }) : null]
248
- }), /*#__PURE__*/_jsxs(Section, {
249
- title: "LOAD",
250
- children: [/*#__PURE__*/_jsx(Row, {
251
- label: "Status",
252
- value: record.status,
253
- color: record.status === "error" ? macOSColors.semantic.error : record.status === "loaded" ? macOSColors.semantic.success : macOSColors.semantic.warning
254
- }), /*#__PURE__*/_jsx(Row, {
255
- label: "Duration",
256
- value: formatMs(record.durationMs)
257
- }), record.displayedAt && record.loadedAt ? /*#__PURE__*/_jsx(Row, {
258
- label: "Decode \u2192 display",
259
- value: formatMs(Math.max(0, record.displayedAt - record.loadedAt))
260
- }) : null, /*#__PURE__*/_jsx(Row, {
261
- label: "Cache",
262
- value: `${cache.label}`,
263
- color: cache.color
264
- }), record.cacheVerdictSource ? /*#__PURE__*/_jsx(Row, {
265
- label: "Cache source",
266
- value: record.cacheVerdictSource === "event" ? "reported by expo-image onLoad" : "Image.queryCache after load"
267
- }) : null, record.progressSeen ? /*#__PURE__*/_jsx(Row, {
268
- label: "Downloaded",
269
- value: record.bytesTotal ? `${formatBytes(record.bytesLoaded ?? 0)} / ${formatBytes(record.bytesTotal)}` : "network transfer observed"
270
- }) : null, /*#__PURE__*/_jsx(Row, {
271
- label: "Load cycles",
272
- value: String(record.loadCount)
273
- }), /*#__PURE__*/_jsx(Row, {
274
- label: "Still mounted",
275
- value: record.mounted ? "yes" : "no"
276
- })]
277
- }), /*#__PURE__*/_jsxs(Section, {
278
- title: "DIMENSIONS",
279
- children: [record.intrinsic ? /*#__PURE__*/_jsx(Row, {
280
- label: "Decoded",
281
- value: `${record.intrinsic.width}×${record.intrinsic.height} px`
282
- }) : /*#__PURE__*/_jsx(Row, {
283
- label: "Decoded",
284
- value: "unknown"
285
- }), record.layout ? /*#__PURE__*/_jsx(Row, {
286
- label: "Displayed",
287
- value: `${Math.round(record.layout.width)}×${Math.round(record.layout.height)} dp (needs ${needed ? `${needed.width}×${needed.height}` : "?"} px @${Math.round(effectivePixelRatio() * 100) / 100}x)`
288
- }) : /*#__PURE__*/_jsx(Row, {
289
- label: "Displayed",
290
- value: "no layout yet"
291
- }), verdict.label ? /*#__PURE__*/_jsx(Row, {
292
- label: "Verdict",
293
- value: verdict.label,
294
- color: verdict.color
295
- }) : null, decoded > 0 ? /*#__PURE__*/_jsx(Row, {
296
- label: "Est. decoded memory",
297
- value: formatBytes(decoded)
298
- }) : null, wasted > 0 ? /*#__PURE__*/_jsx(Row, {
299
- label: "Est. wasted",
300
- value: `${formatBytes(wasted)} — resize the source to ${needed ? `${needed.width}×${needed.height}` : "the displayed size"}`,
301
- color: macOSColors.semantic.warning
302
- }) : null, (record.layoutShifts ?? 0) > 0 ? /*#__PURE__*/_jsx(Row, {
303
- label: "Layout shifts",
304
- value: `${record.layoutShifts} — box resized after load; reserve explicit width/height`,
305
- color: macOSColors.semantic.warning
306
- }) : null]
307
- }), record.sourceKind === "network" || record.sourceKind === "asset" ? /*#__PURE__*/_jsx(SavingsSection, {
308
- record: record
309
- }, `savings-${record.id}`) : null, record.status === "error" ? /*#__PURE__*/_jsxs(Section, {
310
- title: "ERROR",
311
- children: [/*#__PURE__*/_jsx(Row, {
312
- label: "Message",
313
- value: record.error ?? "unknown",
314
- color: macOSColors.semantic.error
315
- }), record.errorCode != null ? /*#__PURE__*/_jsx(Row, {
316
- label: "HTTP status",
317
- value: String(record.errorCode)
318
- }) : null, record.errorHeaders ? Object.entries(record.errorHeaders).map(([k, v]) => /*#__PURE__*/_jsx(Row, {
319
- label: k,
320
- value: String(v)
321
- }, k)) : null]
322
- }) : null, record.sourceKind === "network" && record.mounted ? /*#__PURE__*/_jsx(SwapUrlSection, {
323
- record: record
324
- }, record.id) : null, record.lib === "expo" ? /*#__PURE__*/_jsx(Section, {
325
- title: "CACHE ACTIONS (expo-image, global)",
326
- children: /*#__PURE__*/_jsxs(View, {
327
- style: styles.actionsRow,
328
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
329
- style: styles.actionButton,
330
- onPress: () => clearExpoCache("memory"),
331
- children: /*#__PURE__*/_jsx(Text, {
332
- style: styles.actionText,
333
- children: "Clear memory cache"
334
- })
335
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
336
- style: styles.actionButton,
337
- onPress: () => clearExpoCache("disk"),
338
- children: /*#__PURE__*/_jsx(Text, {
339
- style: styles.actionText,
340
- children: "Clear disk cache"
341
- })
342
- })]
343
- })
344
- }) : null]
345
- });
346
- }
347
- const styles = StyleSheet.create({
348
- container: {
349
- flex: 1
350
- },
351
- content: {
352
- padding: 12,
353
- gap: 12
354
- },
355
- previewBox: {
356
- height: 140,
357
- borderRadius: 8,
358
- backgroundColor: night.color.bg,
359
- overflow: "hidden"
360
- },
361
- preview: {
362
- width: "100%",
363
- height: "100%"
364
- },
365
- section: {
366
- gap: 6
367
- },
368
- sectionTitle: {
369
- color: night.color.textTertiary,
370
- fontSize: 10,
371
- fontWeight: "700",
372
- letterSpacing: 0.8
373
- },
374
- sectionBody: {
375
- backgroundColor: night.color.surface,
376
- borderRadius: 8,
377
- borderWidth: StyleSheet.hairlineWidth,
378
- borderColor: night.color.border,
379
- paddingHorizontal: 10,
380
- paddingVertical: 4
381
- },
382
- kvRow: {
383
- flexDirection: "row",
384
- paddingVertical: 5,
385
- gap: 12
386
- },
387
- kvLabel: {
388
- color: night.color.textSecondary,
389
- fontSize: 11,
390
- width: 110
391
- },
392
- kvValue: {
393
- color: night.color.text,
394
- fontSize: 11,
395
- flex: 1
396
- },
397
- actionsRow: {
398
- flexDirection: "row",
399
- gap: 8,
400
- paddingVertical: 8
401
- },
402
- actionButton: {
403
- backgroundColor: night.color.surfaceElevated,
404
- borderRadius: 6,
405
- paddingHorizontal: 10,
406
- paddingVertical: 6,
407
- borderWidth: StyleSheet.hairlineWidth,
408
- borderColor: night.color.border
409
- },
410
- actionText: {
411
- color: night.color.text,
412
- fontSize: 11,
413
- fontWeight: "600"
414
- },
415
- actionDisabled: {
416
- opacity: 0.4
417
- },
418
- urlInput: {
419
- color: night.color.text,
420
- fontSize: 11,
421
- fontFamily: monoFont,
422
- backgroundColor: night.color.surfaceElevated,
423
- borderRadius: 6,
424
- borderWidth: StyleSheet.hairlineWidth,
425
- borderColor: night.color.border,
426
- paddingHorizontal: 8,
427
- paddingVertical: 6,
428
- marginTop: 8
429
- },
430
- overrideBanner: {
431
- flexDirection: "row",
432
- alignItems: "center",
433
- gap: 10,
434
- padding: 10,
435
- borderRadius: 8,
436
- backgroundColor: night.color.warningSoft
437
- },
438
- overrideText: {
439
- flex: 1,
440
- color: night.color.warning,
441
- fontSize: 11,
442
- lineHeight: 15
443
- },
444
- overrideRestore: {
445
- color: night.color.warning,
446
- fontSize: 11,
447
- fontWeight: "700",
448
- letterSpacing: 0.5
449
- }
450
- });
451
- //# sourceMappingURL=ImageDetail.js.map
1
+ "use strict";import{loadExpoImage as C}from"../capture/nativeImageModules";import{useState as f}from"react";import{Image as T,ScrollView as B,StyleSheet as v,Text as c,TextInput as z,TouchableOpacity as m,View as u}from"react-native";import{macOSColors as d,night as a,monoFont as $}from"@buoy-gg/shared-ui";import{clearOverride as k,setOverride as x}from"../capture/overrides";import{proveSavings as D}from"../capture/savings";import{remoteImagesDispatch as I}from"../remote";import{effectivePixelRatio as P,estDecodedBytes as A,estWastedBytes as O,neededPixels as E}from"../store/imageEventStore";import{cacheBadge as V,formatBytes as y,formatMs as w,sizeVerdict as L}from"../format";import{jsx as l,jsxs as r,Fragment as W}from"react/jsx-runtime";function p({title:e,children:n}){return r(u,{style:t.section,children:[l(c,{style:t.sectionTitle,children:e}),l(u,{style:t.sectionBody,children:n})]})}function i({label:e,value:n,color:s}){return r(u,{style:t.kvRow,children:[l(c,{style:t.kvLabel,children:e}),l(c,{style:[t.kvValue,s?{color:s}:null],selectable:true,children:n})]})}function S(e){const n=I();if(n){void n("clearExpoCaches",{memory:e==="memory",disk:e==="disk"});return}try{const s=C();if(e==="memory")void s.Image.clearMemoryCache();else void s.Image.clearDiskCache()}catch{}}function M({record:e}){const[n,s]=f(e.uri);const o=n.trim();const h=e.mounted&&o.length>0&&o!==e.uri;return r(p,{title:"SWAP SOURCE URL",children:[l(z,{style:t.urlInput,value:n,onChangeText:s,placeholder:"https://\u2026",placeholderTextColor:a.color.placeholder,autoCapitalize:"none",autoCorrect:false,multiline:true}),r(u,{style:t.actionsRow,children:[l(m,{style:[t.actionButton,!h&&t.actionDisabled],disabled:!h,onPress:()=>x(e.id,{source:{type:"url",uri:o},label:"URL swapped"}),children:l(c,{style:t.actionText,children:"Apply"})}),l(m,{style:t.actionButton,onPress:()=>s(e.uri),children:l(c,{style:t.actionText,children:"Reset field"})})]})]})}function U({record:e}){const[n,s]=f(false);const[o,h]=f(null);const b=()=>{s(true);D(e).then(h).catch(g=>h({ok:false,message:String(g?.message??g)})).finally(()=>s(false))};return l(p,{title:"PROVE SAVINGS (re-encode on device)",children:o==null?l(u,{style:t.actionsRow,children:l(m,{style:t.actionButton,onPress:b,disabled:n,children:l(c,{style:t.actionText,children:n?"Re-encoding\u2026":"Re-encode at displayed size (WebP)"})})}):r(W,{children:[l(i,{label:"Result",value:o.message||(o.ok?"done":"failed"),color:o.ok?d.semantic.success:d.semantic.error}),o.originalBytes!=null?l(i,{label:"Original",value:`${y(o.originalBytes)} (${o.originalBytesSource})`}):null,o.optimizedBytes!=null?l(i,{label:"Optimized",value:`${y(o.optimizedBytes)} \u2014 ${o.optimizedDims?.width}\xD7${o.optimizedDims?.height} WebP`,color:d.semantic.success}):null,o.savedBytes!=null?l(i,{label:"Saved",value:`${y(o.savedBytes)} (${o.savedPct}%)`,color:d.semantic.success}):null,r(u,{style:t.actionsRow,children:[o.ok&&o.optimizedUri&&e.mounted?l(m,{style:t.actionButton,onPress:()=>x(e.id,{source:{type:"url",uri:o.optimizedUri},label:"Optimized preview"}),children:l(c,{style:t.actionText,children:"Preview in place"})}):null,l(m,{style:t.actionButton,onPress:b,disabled:n,children:l(c,{style:t.actionText,children:n?"Re-encoding\u2026":"Re-run"})})]})]})})}export function ImageDetail({record:e}){const n=L(e);const s=V(e);const o=E(e);const h=A(e);const b=O(e);return r(B,{style:t.container,contentContainerStyle:t.content,children:[e.overrideLabel?r(u,{style:t.overrideBanner,children:[r(c,{style:t.overrideText,children:["Simulation active: ",e.overrideLabel," \u2014 stats below describe the override, not the original source."]}),l(m,{onPress:()=>k(e.id),children:l(c,{style:t.overrideRestore,children:"RESTORE"})})]}):null,e.uri&&e.sourceKind!=="data"?l(u,{style:t.previewBox,children:l(T,{testID:"buoy-internal-images-preview",source:{uri:e.uri},style:t.preview,resizeMode:"contain"})}):null,r(p,{title:"SOURCE",children:[l(i,{label:"URI",value:e.uri||"(none)"}),l(i,{label:"Kind",value:e.sourceKind}),l(i,{label:"Library",value:e.lib==="web"?"Browser <img>":e.lib==="expo"?"expo-image":"RN <Image>"}),e.hasAltText===false?l(i,{label:"Alt text",value:"missing \u2014 add alt/accessibilityLabel",color:d.semantic.warning}):null,e.mediaType!=null?l(i,{label:"Media type",value:String(e.mediaType)}):null,e.isAnimated!=null?l(i,{label:"Animated",value:e.isAnimated?"yes":"no"}):null]}),r(p,{title:"LOAD",children:[l(i,{label:"Status",value:e.status,color:e.status==="error"?d.semantic.error:e.status==="loaded"?d.semantic.success:d.semantic.warning}),l(i,{label:"Duration",value:w(e.durationMs)}),e.displayedAt&&e.loadedAt?l(i,{label:"Decode \u2192 display",value:w(Math.max(0,e.displayedAt-e.loadedAt))}):null,l(i,{label:"Cache",value:`${s.label}`,color:s.color}),e.cacheVerdictSource?l(i,{label:"Cache source",value:e.cacheVerdictSource==="event"?"reported by expo-image onLoad":"Image.queryCache after load"}):null,e.progressSeen?l(i,{label:"Downloaded",value:e.bytesTotal?`${y(e.bytesLoaded??0)} / ${y(e.bytesTotal)}`:"network transfer observed"}):null,l(i,{label:"Load cycles",value:String(e.loadCount)}),l(i,{label:"Still mounted",value:e.mounted?"yes":"no"})]}),r(p,{title:"DIMENSIONS",children:[e.intrinsic?l(i,{label:"Decoded",value:`${e.intrinsic.width}\xD7${e.intrinsic.height} px`}):l(i,{label:"Decoded",value:"unknown"}),e.layout?l(i,{label:"Displayed",value:`${Math.round(e.layout.width)}\xD7${Math.round(e.layout.height)} dp (needs ${o?`${o.width}\xD7${o.height}`:"?"} px @${Math.round(P()*100)/100}x)`}):l(i,{label:"Displayed",value:"no layout yet"}),n.label?l(i,{label:"Verdict",value:n.label,color:n.color}):null,h>0?l(i,{label:"Est. decoded memory",value:y(h)}):null,b>0?l(i,{label:"Est. wasted",value:`${y(b)} \u2014 resize the source to ${o?`${o.width}\xD7${o.height}`:"the displayed size"}`,color:d.semantic.warning}):null,(e.layoutShifts??0)>0?l(i,{label:"Layout shifts",value:`${e.layoutShifts} \u2014 box resized after load; reserve explicit width/height`,color:d.semantic.warning}):null]}),e.sourceKind==="network"||e.sourceKind==="asset"?l(U,{record:e},`savings-${e.id}`):null,e.status==="error"?r(p,{title:"ERROR",children:[l(i,{label:"Message",value:e.error??"unknown",color:d.semantic.error}),e.errorCode!=null?l(i,{label:"HTTP status",value:String(e.errorCode)}):null,e.errorHeaders?Object.entries(e.errorHeaders).map(([g,R])=>l(i,{label:g,value:String(R)},g)):null]}):null,e.sourceKind==="network"&&e.mounted?l(M,{record:e},e.id):null,e.lib==="expo"?l(p,{title:"CACHE ACTIONS (expo-image, global)",children:r(u,{style:t.actionsRow,children:[l(m,{style:t.actionButton,onPress:()=>S("memory"),children:l(c,{style:t.actionText,children:"Clear memory cache"})}),l(m,{style:t.actionButton,onPress:()=>S("disk"),children:l(c,{style:t.actionText,children:"Clear disk cache"})})]})}):null]})}const t=v.create({container:{flex:1},content:{padding:12,gap:12},previewBox:{height:140,borderRadius:8,backgroundColor:a.color.bg,overflow:"hidden"},preview:{width:"100%",height:"100%"},section:{gap:6},sectionTitle:{color:a.color.textTertiary,fontSize:10,fontWeight:"700",letterSpacing:.8},sectionBody:{backgroundColor:a.color.surface,borderRadius:8,borderWidth:v.hairlineWidth,borderColor:a.color.border,paddingHorizontal:10,paddingVertical:4},kvRow:{flexDirection:"row",paddingVertical:5,gap:12},kvLabel:{color:a.color.textSecondary,fontSize:11,width:110},kvValue:{color:a.color.text,fontSize:11,flex:1},actionsRow:{flexDirection:"row",gap:8,paddingVertical:8},actionButton:{backgroundColor:a.color.surfaceElevated,borderRadius:6,paddingHorizontal:10,paddingVertical:6,borderWidth:v.hairlineWidth,borderColor:a.color.border},actionText:{color:a.color.text,fontSize:11,fontWeight:"600"},actionDisabled:{opacity:.4},urlInput:{color:a.color.text,fontSize:11,fontFamily:$,backgroundColor:a.color.surfaceElevated,borderRadius:6,borderWidth:v.hairlineWidth,borderColor:a.color.border,paddingHorizontal:8,paddingVertical:6,marginTop:8},overrideBanner:{flexDirection:"row",alignItems:"center",gap:10,padding:10,borderRadius:8,backgroundColor:a.color.warningSoft},overrideText:{flex:1,color:a.color.warning,fontSize:11,lineHeight:15},overrideRestore:{color:a.color.warning,fontSize:11,fontWeight:"700",letterSpacing:.5}});
@@ -1,154 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * ImageDetailFooter — bottom navbar for the detail view: previous/next
5
- * stepping through the captured list at the edges, per-image actions in the
6
- * middle (hard reload / retry), with inline action feedback. Styling matches
7
- * shared-ui's EventStepperFooter so it reads like the rest of Buoy.
8
- *
9
- * Mount with key={record.id} so action feedback resets when stepping.
10
- */
11
-
12
- import { useState } from "react";
13
- import { StyleSheet, TouchableOpacity } from "react-native";
14
- import { ChevronLeft, ChevronRight, FooterButton, FooterMessage, FooterRow, RefreshCw, ToolFooter, night } from "@buoy-gg/shared-ui";
15
- import { hardReloadRecord, retryRecord } from "../capture/actions";
16
- import { clearOverride, flashRecord, setOverride } from "../capture/overrides";
17
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
- export function ImageDetailFooter({
19
- record,
20
- hasPrevious,
21
- hasNext,
22
- onPrevious,
23
- onNext
24
- }) {
25
- const [message, setMessage] = useState(null);
26
- const runAction = action => {
27
- setMessage("…");
28
- action().then(result => setMessage(result.message)).catch(error => setMessage(String(error?.message ?? error)));
29
- };
30
- const actionsDisabled = !record.mounted;
31
- const overrideActive = record.overrideLabel != null;
32
- const simulate = (source, label) => {
33
- setOverride(record.id, {
34
- source,
35
- label
36
- });
37
- setMessage(`${label} — Restore to undo`);
38
- };
39
- return /*#__PURE__*/_jsxs(ToolFooter, {
40
- children: [message ? /*#__PURE__*/_jsx(FooterMessage, {
41
- children: message
42
- }) : actionsDisabled ? /*#__PURE__*/_jsx(FooterMessage, {
43
- tone: "muted",
44
- children: "Instance unmounted \u2014 reload actions need the image on screen."
45
- }) : null, /*#__PURE__*/_jsxs(FooterRow, {
46
- style: {
47
- gap: 8
48
- },
49
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
50
- onPress: onPrevious,
51
- disabled: !hasPrevious,
52
- style: [styles.navButton, !hasPrevious && styles.disabled],
53
- activeOpacity: 0.7,
54
- accessibilityLabel: "Previous image",
55
- children: /*#__PURE__*/_jsx(ChevronLeft, {
56
- size: 20,
57
- color: hasPrevious ? night.color.accent : night.color.textTertiary
58
- })
59
- }), /*#__PURE__*/_jsx(FooterButton, {
60
- label: "Hard reload",
61
- size: "md",
62
- disabled: actionsDisabled,
63
- icon: /*#__PURE__*/_jsx(RefreshCw, {
64
- size: 13,
65
- color: actionsDisabled ? night.color.textTertiary : night.color.info
66
- }),
67
- onPress: () => runAction(() => hardReloadRecord(record))
68
- }), /*#__PURE__*/_jsx(FooterButton, {
69
- label: record.status === "error" ? "Retry" : "Reload",
70
- size: "md",
71
- disabled: actionsDisabled,
72
- onPress: () => runAction(() => retryRecord(record))
73
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
74
- onPress: onNext,
75
- disabled: !hasNext,
76
- style: [styles.navButton, !hasNext && styles.disabled],
77
- activeOpacity: 0.7,
78
- accessibilityLabel: "Next image",
79
- children: /*#__PURE__*/_jsx(ChevronRight, {
80
- size: 20,
81
- color: hasNext ? night.color.accent : night.color.textTertiary
82
- })
83
- })]
84
- }), /*#__PURE__*/_jsxs(FooterRow, {
85
- style: {
86
- gap: 8
87
- },
88
- children: [/*#__PURE__*/_jsx(SimButton, {
89
- label: "Error",
90
- disabled: actionsDisabled,
91
- onPress: () => simulate({
92
- type: "error"
93
- }, "Forced error")
94
- }), /*#__PURE__*/_jsx(SimButton, {
95
- label: "Loading",
96
- disabled: actionsDisabled,
97
- onPress: () => simulate({
98
- type: "hang"
99
- }, "Forced loading")
100
- }), /*#__PURE__*/_jsx(SimButton, {
101
- label: "Blank",
102
- disabled: actionsDisabled,
103
- onPress: () => simulate({
104
- type: "blank"
105
- }, "Blanked")
106
- }), /*#__PURE__*/_jsx(SimButton, {
107
- label: "Find",
108
- disabled: actionsDisabled,
109
- onPress: () => {
110
- flashRecord(record.id);
111
- setMessage("Flashing a red border on the image (2.5s)");
112
- }
113
- }), /*#__PURE__*/_jsx(SimButton, {
114
- label: "Restore",
115
- accent: true,
116
- disabled: !overrideActive,
117
- onPress: () => {
118
- clearOverride(record.id);
119
- setMessage("Override removed — original source restored");
120
- }
121
- })]
122
- })]
123
- });
124
- }
125
- function SimButton({
126
- label,
127
- onPress,
128
- disabled,
129
- accent
130
- }) {
131
- return /*#__PURE__*/_jsx(FooterButton, {
132
- label: label,
133
- onPress: onPress,
134
- disabled: disabled,
135
- accent: accent,
136
- style: {
137
- paddingVertical: 7
138
- }
139
- });
140
- }
141
- const styles = StyleSheet.create({
142
- navButton: {
143
- paddingHorizontal: 10,
144
- paddingVertical: 8,
145
- borderRadius: 6,
146
- backgroundColor: night.color.button,
147
- alignItems: "center",
148
- justifyContent: "center"
149
- },
150
- disabled: {
151
- opacity: 0.4
152
- }
153
- });
154
- //# sourceMappingURL=ImageDetailFooter.js.map
1
+ "use strict";import{useState as P}from"react";import{StyleSheet as x,TouchableOpacity as h}from"react-native";import{ChevronLeft as B,ChevronRight as z,FooterButton as u,FooterMessage as f,FooterRow as v,RefreshCw as O,ToolFooter as j,night as s}from"@buoy-gg/shared-ui";import{hardReloadRecord as C,retryRecord as L}from"../capture/actions";import{clearOverride as S,flashRecord as T,setOverride as k}from"../capture/overrides";import{jsx as e,jsxs as g}from"react/jsx-runtime";export function ImageDetailFooter({record:t,hasPrevious:i,hasNext:n,onPrevious:c,onNext:R}){const[y,a]=P(null);const p=m=>{a("\u2026");m().then(r=>a(r.message)).catch(r=>a(String(r?.message??r)))};const o=!t.mounted;const F=t.overrideLabel!=null;const b=(m,r)=>{k(t.id,{source:m,label:r});a(`${r} \u2014 Restore to undo`)};return g(j,{children:[y?e(f,{children:y}):o?e(f,{tone:"muted",children:"Instance unmounted \u2014 reload actions need the image on screen."}):null,g(v,{style:{gap:8},children:[e(h,{onPress:c,disabled:!i,style:[d.navButton,!i&&d.disabled],activeOpacity:.7,accessibilityLabel:"Previous image",children:e(B,{size:20,color:i?s.color.accent:s.color.textTertiary})}),e(u,{label:"Hard reload",size:"md",disabled:o,icon:e(O,{size:13,color:o?s.color.textTertiary:s.color.info}),onPress:()=>p(()=>C(t))}),e(u,{label:t.status==="error"?"Retry":"Reload",size:"md",disabled:o,onPress:()=>p(()=>L(t))}),e(h,{onPress:R,disabled:!n,style:[d.navButton,!n&&d.disabled],activeOpacity:.7,accessibilityLabel:"Next image",children:e(z,{size:20,color:n?s.color.accent:s.color.textTertiary})})]}),g(v,{style:{gap:8},children:[e(l,{label:"Error",disabled:o,onPress:()=>b({type:"error"},"Forced error")}),e(l,{label:"Loading",disabled:o,onPress:()=>b({type:"hang"},"Forced loading")}),e(l,{label:"Blank",disabled:o,onPress:()=>b({type:"blank"},"Blanked")}),e(l,{label:"Find",disabled:o,onPress:()=>{T(t.id);a("Flashing a red border on the image (2.5s)")}}),e(l,{label:"Restore",accent:true,disabled:!F,onPress:()=>{S(t.id);a("Override removed \u2014 original source restored")}})]})]})}function l({label:t,onPress:i,disabled:n,accent:c}){return e(u,{label:t,onPress:i,disabled:n,accent:c,style:{paddingVertical:7}})}const d=x.create({navButton:{paddingHorizontal:10,paddingVertical:8,borderRadius:6,backgroundColor:s.color.button,alignItems:"center",justifyContent:"center"},disabled:{opacity:.4}});