@vibesdotdev/media 0.0.0-stage → 1.0.0-rc.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.
Files changed (278) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +95 -2
  3. package/dist/analyzers/frame-guard.d.ts +6 -0
  4. package/dist/analyzers/frame-guard.d.ts.map +1 -0
  5. package/dist/analyzers/frame-guard.js +10 -0
  6. package/dist/analyzers/frame-guard.js.map +1 -0
  7. package/dist/analyzers/motion-math.d.ts +66 -0
  8. package/dist/analyzers/motion-math.d.ts.map +1 -0
  9. package/dist/analyzers/motion-math.js +356 -0
  10. package/dist/analyzers/motion-math.js.map +1 -0
  11. package/dist/analyzers/reference-motion-analyzer.impl.d.ts +23 -0
  12. package/dist/analyzers/reference-motion-analyzer.impl.d.ts.map +1 -0
  13. package/dist/analyzers/reference-motion-analyzer.impl.js +69 -0
  14. package/dist/analyzers/reference-motion-analyzer.impl.js.map +1 -0
  15. package/dist/analyzers/reference-motion.descriptor.d.ts +11 -0
  16. package/dist/analyzers/reference-motion.descriptor.d.ts.map +1 -0
  17. package/dist/analyzers/reference-motion.descriptor.js +21 -0
  18. package/dist/analyzers/reference-motion.descriptor.js.map +1 -0
  19. package/dist/capture-client.d.ts +34 -0
  20. package/dist/capture-client.d.ts.map +1 -0
  21. package/dist/capture-client.js +163 -0
  22. package/dist/capture-client.js.map +1 -0
  23. package/dist/config/media.uploads.config.descriptor.d.ts +27 -0
  24. package/dist/config/media.uploads.config.descriptor.d.ts.map +1 -0
  25. package/dist/config/media.uploads.config.descriptor.js +46 -0
  26. package/dist/config/media.uploads.config.descriptor.js.map +1 -0
  27. package/dist/config/uploads.d.ts +11 -0
  28. package/dist/config/uploads.d.ts.map +1 -0
  29. package/dist/config/uploads.js +25 -0
  30. package/dist/config/uploads.js.map +1 -0
  31. package/dist/docs/artifact-references.docs.descriptor.d.ts +4 -0
  32. package/dist/docs/artifact-references.docs.descriptor.d.ts.map +1 -0
  33. package/dist/docs/artifact-references.docs.descriptor.js +69 -0
  34. package/dist/docs/artifact-references.docs.descriptor.js.map +1 -0
  35. package/dist/docs/image-transform.docs.descriptor.d.ts +4 -0
  36. package/dist/docs/image-transform.docs.descriptor.d.ts.map +1 -0
  37. package/dist/docs/image-transform.docs.descriptor.js +52 -0
  38. package/dist/docs/image-transform.docs.descriptor.js.map +1 -0
  39. package/dist/index.d.ts +48 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +49 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/interfaces/capture-provider.d.ts +33 -0
  44. package/dist/interfaces/capture-provider.d.ts.map +1 -0
  45. package/dist/interfaces/capture-provider.js +10 -0
  46. package/dist/interfaces/capture-provider.js.map +1 -0
  47. package/dist/interfaces/image-transformer.d.ts +7 -0
  48. package/dist/interfaces/image-transformer.d.ts.map +1 -0
  49. package/dist/interfaces/image-transformer.js +2 -0
  50. package/dist/interfaces/image-transformer.js.map +1 -0
  51. package/dist/interfaces/index.d.ts +7 -0
  52. package/dist/interfaces/index.d.ts.map +1 -0
  53. package/dist/interfaces/index.js +5 -0
  54. package/dist/interfaces/index.js.map +1 -0
  55. package/dist/interfaces/motion-analyzer.d.ts +55 -0
  56. package/dist/interfaces/motion-analyzer.d.ts.map +1 -0
  57. package/dist/interfaces/motion-analyzer.js +14 -0
  58. package/dist/interfaces/motion-analyzer.js.map +1 -0
  59. package/dist/kinds/capture.d.ts +58 -0
  60. package/dist/kinds/capture.d.ts.map +1 -0
  61. package/dist/kinds/capture.js +34 -0
  62. package/dist/kinds/capture.js.map +1 -0
  63. package/dist/kinds/image-transform.d.ts +91 -0
  64. package/dist/kinds/image-transform.d.ts.map +1 -0
  65. package/dist/kinds/image-transform.js +12 -0
  66. package/dist/kinds/image-transform.js.map +1 -0
  67. package/dist/kinds/index.d.ts +10 -0
  68. package/dist/kinds/index.d.ts.map +1 -0
  69. package/dist/kinds/index.js +7 -0
  70. package/dist/kinds/index.js.map +1 -0
  71. package/dist/kinds/motion.d.ts +57 -0
  72. package/dist/kinds/motion.d.ts.map +1 -0
  73. package/dist/kinds/motion.js +34 -0
  74. package/dist/kinds/motion.js.map +1 -0
  75. package/dist/media.plugin.d.ts +23 -0
  76. package/dist/media.plugin.d.ts.map +1 -0
  77. package/dist/media.plugin.js +48 -0
  78. package/dist/media.plugin.js.map +1 -0
  79. package/dist/schemas/artifact-reference.d.ts +59 -0
  80. package/dist/schemas/artifact-reference.d.ts.map +1 -0
  81. package/dist/schemas/artifact-reference.js +77 -0
  82. package/dist/schemas/artifact-reference.js.map +1 -0
  83. package/dist/schemas/capture-descriptor.d.ts +71 -0
  84. package/dist/schemas/capture-descriptor.d.ts.map +1 -0
  85. package/dist/schemas/capture-descriptor.js +74 -0
  86. package/dist/schemas/capture-descriptor.js.map +1 -0
  87. package/dist/schemas/capture-options.d.ts +51 -0
  88. package/dist/schemas/capture-options.d.ts.map +1 -0
  89. package/dist/schemas/capture-options.js +68 -0
  90. package/dist/schemas/capture-options.js.map +1 -0
  91. package/dist/schemas/capture-parse.d.ts +10 -0
  92. package/dist/schemas/capture-parse.d.ts.map +1 -0
  93. package/dist/schemas/capture-parse.js +30 -0
  94. package/dist/schemas/capture-parse.js.map +1 -0
  95. package/dist/schemas/capture-state.d.ts +77 -0
  96. package/dist/schemas/capture-state.d.ts.map +1 -0
  97. package/dist/schemas/capture-state.js +25 -0
  98. package/dist/schemas/capture-state.js.map +1 -0
  99. package/dist/schemas/image-transform.d.ts +269 -0
  100. package/dist/schemas/image-transform.d.ts.map +1 -0
  101. package/dist/schemas/image-transform.js +161 -0
  102. package/dist/schemas/image-transform.js.map +1 -0
  103. package/dist/schemas/index.d.ts +20 -0
  104. package/dist/schemas/index.d.ts.map +1 -0
  105. package/dist/schemas/index.js +17 -0
  106. package/dist/schemas/index.js.map +1 -0
  107. package/dist/schemas/motion-analysis.d.ts +187 -0
  108. package/dist/schemas/motion-analysis.d.ts.map +1 -0
  109. package/dist/schemas/motion-analysis.js +213 -0
  110. package/dist/schemas/motion-analysis.js.map +1 -0
  111. package/dist/schemas/motion-descriptor.d.ts +53 -0
  112. package/dist/schemas/motion-descriptor.d.ts.map +1 -0
  113. package/dist/schemas/motion-descriptor.js +47 -0
  114. package/dist/schemas/motion-descriptor.js.map +1 -0
  115. package/dist/schemas/motion-frame.d.ts +63 -0
  116. package/dist/schemas/motion-frame.d.ts.map +1 -0
  117. package/dist/schemas/motion-frame.js +86 -0
  118. package/dist/schemas/motion-frame.js.map +1 -0
  119. package/dist/schemas/opaque-id.d.ts +10 -0
  120. package/dist/schemas/opaque-id.d.ts.map +1 -0
  121. package/dist/schemas/opaque-id.js +24 -0
  122. package/dist/schemas/opaque-id.js.map +1 -0
  123. package/dist/schemas/sharing-state.d.ts +43 -0
  124. package/dist/schemas/sharing-state.d.ts.map +1 -0
  125. package/dist/schemas/sharing-state.js +11 -0
  126. package/dist/schemas/sharing-state.js.map +1 -0
  127. package/dist/svelte/CaptureLoader.svelte +29 -0
  128. package/dist/svelte/CaptureLoader.svelte.d.ts +11 -0
  129. package/dist/svelte/CaptureLoader.svelte.d.ts.map +1 -0
  130. package/dist/svelte/CapturePanel.svelte +189 -0
  131. package/dist/svelte/CapturePanel.svelte.d.ts +12 -0
  132. package/dist/svelte/CapturePanel.svelte.d.ts.map +1 -0
  133. package/dist/svelte/MediaUploader.svelte +109 -0
  134. package/dist/svelte/MediaUploader.svelte.d.ts +5 -0
  135. package/dist/svelte/MediaUploader.svelte.d.ts.map +1 -0
  136. package/dist/svelte/capture-parse.d.ts +2 -0
  137. package/dist/svelte/capture-parse.d.ts.map +1 -0
  138. package/dist/svelte/capture-parse.js +1 -0
  139. package/dist/svelte/failure-message.d.ts +2 -0
  140. package/dist/svelte/failure-message.d.ts.map +1 -0
  141. package/dist/svelte/failure-message.js +1 -0
  142. package/dist/svelte/index.d.ts +8 -0
  143. package/dist/svelte/index.d.ts.map +1 -0
  144. package/dist/svelte/index.js +6 -0
  145. package/dist/svelte/runtime-log.d.ts +14 -0
  146. package/dist/svelte/runtime-log.d.ts.map +1 -0
  147. package/dist/svelte/runtime-log.js +19 -0
  148. package/dist/svelte/runtime.d.ts +9 -0
  149. package/dist/svelte/runtime.d.ts.map +1 -0
  150. package/dist/svelte/runtime.js +27 -0
  151. package/dist/svelte/sharing/README.md +38 -0
  152. package/dist/svelte/sharing/components/ShareButton.svelte +57 -0
  153. package/dist/svelte/sharing/components/ShareButton.svelte.d.ts +11 -0
  154. package/dist/svelte/sharing/components/ShareButton.svelte.d.ts.map +1 -0
  155. package/dist/svelte/sharing/components/ShareDialog.svelte +222 -0
  156. package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts +8 -0
  157. package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts.map +1 -0
  158. package/dist/svelte/sharing/components/ShareLoader.svelte +19 -0
  159. package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts +7 -0
  160. package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts.map +1 -0
  161. package/dist/svelte/sharing/index.d.ts +20 -0
  162. package/dist/svelte/sharing/index.d.ts.map +1 -0
  163. package/dist/svelte/sharing/index.js +19 -0
  164. package/dist/svelte/sharing/runtime.d.ts +44 -0
  165. package/dist/svelte/sharing/runtime.d.ts.map +1 -0
  166. package/dist/svelte/sharing/runtime.js +81 -0
  167. package/dist/svelte/sharing/state.svelte.d.ts +28 -0
  168. package/dist/svelte/sharing/state.svelte.d.ts.map +1 -0
  169. package/dist/svelte/sharing/state.svelte.js +101 -0
  170. package/dist/svelte/sharing/styles.css +8 -0
  171. package/dist/svelte/state.svelte.d.ts +38 -0
  172. package/dist/svelte/state.svelte.d.ts.map +1 -0
  173. package/dist/svelte/state.svelte.js +102 -0
  174. package/dist/svelte/styles.css +2 -0
  175. package/dist/svelte/uploader.d.ts +17 -0
  176. package/dist/svelte/uploader.d.ts.map +1 -0
  177. package/dist/svelte/uploader.js +9 -0
  178. package/dist/uploads/adapters/local/index.d.ts +9 -0
  179. package/dist/uploads/adapters/local/index.d.ts.map +1 -0
  180. package/dist/uploads/adapters/local/index.js +12 -0
  181. package/dist/uploads/adapters/local/index.js.map +1 -0
  182. package/dist/uploads/adapters/local/local-storage.descriptor.d.ts +24 -0
  183. package/dist/uploads/adapters/local/local-storage.descriptor.d.ts.map +1 -0
  184. package/dist/uploads/adapters/local/local-storage.descriptor.js +20 -0
  185. package/dist/uploads/adapters/local/local-storage.descriptor.js.map +1 -0
  186. package/dist/uploads/adapters/local/local-storage.impl.d.ts +123 -0
  187. package/dist/uploads/adapters/local/local-storage.impl.d.ts.map +1 -0
  188. package/dist/uploads/adapters/local/local-storage.impl.js +231 -0
  189. package/dist/uploads/adapters/local/local-storage.impl.js.map +1 -0
  190. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts +9 -0
  191. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts.map +1 -0
  192. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js +41 -0
  193. package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js.map +1 -0
  194. package/dist/uploads/client/helper.d.ts +26 -0
  195. package/dist/uploads/client/helper.d.ts.map +1 -0
  196. package/dist/uploads/client/helper.js +27 -0
  197. package/dist/uploads/client/helper.js.map +1 -0
  198. package/dist/uploads/client/index.d.ts +11 -0
  199. package/dist/uploads/client/index.d.ts.map +1 -0
  200. package/dist/uploads/client/index.js +9 -0
  201. package/dist/uploads/client/index.js.map +1 -0
  202. package/dist/uploads/client/sharing-client.d.ts +45 -0
  203. package/dist/uploads/client/sharing-client.d.ts.map +1 -0
  204. package/dist/uploads/client/sharing-client.js +153 -0
  205. package/dist/uploads/client/sharing-client.js.map +1 -0
  206. package/dist/uploads/client/uploads-client.d.ts +200 -0
  207. package/dist/uploads/client/uploads-client.d.ts.map +1 -0
  208. package/dist/uploads/client/uploads-client.js +487 -0
  209. package/dist/uploads/client/uploads-client.js.map +1 -0
  210. package/dist/uploads/docs/uploads.docs.descriptor.d.ts +4 -0
  211. package/dist/uploads/docs/uploads.docs.descriptor.d.ts.map +1 -0
  212. package/dist/uploads/docs/uploads.docs.descriptor.js +65 -0
  213. package/dist/uploads/docs/uploads.docs.descriptor.js.map +1 -0
  214. package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts +22 -0
  215. package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts.map +1 -0
  216. package/dist/uploads/docs/uploads.share-links.docs.descriptor.js +86 -0
  217. package/dist/uploads/docs/uploads.share-links.docs.descriptor.js.map +1 -0
  218. package/dist/uploads/index.d.ts +12 -0
  219. package/dist/uploads/index.d.ts.map +1 -0
  220. package/dist/uploads/index.js +13 -0
  221. package/dist/uploads/index.js.map +1 -0
  222. package/dist/uploads/schemas/common.d.ts +135 -0
  223. package/dist/uploads/schemas/common.d.ts.map +1 -0
  224. package/dist/uploads/schemas/common.js +152 -0
  225. package/dist/uploads/schemas/common.js.map +1 -0
  226. package/dist/uploads/schemas/forms.d.ts +59 -0
  227. package/dist/uploads/schemas/forms.d.ts.map +1 -0
  228. package/dist/uploads/schemas/forms.js +41 -0
  229. package/dist/uploads/schemas/forms.js.map +1 -0
  230. package/dist/uploads/schemas/index.d.ts +18 -0
  231. package/dist/uploads/schemas/index.d.ts.map +1 -0
  232. package/dist/uploads/schemas/index.js +13 -0
  233. package/dist/uploads/schemas/index.js.map +1 -0
  234. package/dist/uploads/schemas/records.d.ts +81 -0
  235. package/dist/uploads/schemas/records.d.ts.map +1 -0
  236. package/dist/uploads/schemas/records.js +82 -0
  237. package/dist/uploads/schemas/records.js.map +1 -0
  238. package/dist/uploads/schemas/requests.d.ts +32 -0
  239. package/dist/uploads/schemas/requests.d.ts.map +1 -0
  240. package/dist/uploads/schemas/requests.js +30 -0
  241. package/dist/uploads/schemas/requests.js.map +1 -0
  242. package/dist/uploads/schemas/shares.d.ts +67 -0
  243. package/dist/uploads/schemas/shares.d.ts.map +1 -0
  244. package/dist/uploads/schemas/shares.js +39 -0
  245. package/dist/uploads/schemas/shares.js.map +1 -0
  246. package/dist/uploads/server/backend.d.ts +8 -0
  247. package/dist/uploads/server/backend.d.ts.map +1 -0
  248. package/dist/uploads/server/backend.js +11 -0
  249. package/dist/uploads/server/backend.js.map +1 -0
  250. package/dist/uploads/server/config.d.ts +5 -0
  251. package/dist/uploads/server/config.d.ts.map +1 -0
  252. package/dist/uploads/server/config.js +38 -0
  253. package/dist/uploads/server/config.js.map +1 -0
  254. package/dist/uploads/server/service.d.ts +77 -0
  255. package/dist/uploads/server/service.d.ts.map +1 -0
  256. package/dist/uploads/server/service.js +152 -0
  257. package/dist/uploads/server/service.js.map +1 -0
  258. package/dist/uploads/server/storage-client.d.ts +11 -0
  259. package/dist/uploads/server/storage-client.d.ts.map +1 -0
  260. package/dist/uploads/server/storage-client.js +23 -0
  261. package/dist/uploads/server/storage-client.js.map +1 -0
  262. package/dist/uploads/server/storage.d.ts +40 -0
  263. package/dist/uploads/server/storage.d.ts.map +1 -0
  264. package/dist/uploads/server/storage.js +59 -0
  265. package/dist/uploads/server/storage.js.map +1 -0
  266. package/dist/uploads/uploads.plugin.d.ts +11 -0
  267. package/dist/uploads/uploads.plugin.d.ts.map +1 -0
  268. package/dist/uploads/uploads.plugin.js +89 -0
  269. package/dist/uploads/uploads.plugin.js.map +1 -0
  270. package/dist/utils/attempt.d.ts +27 -0
  271. package/dist/utils/attempt.d.ts.map +1 -0
  272. package/dist/utils/attempt.js +39 -0
  273. package/dist/utils/attempt.js.map +1 -0
  274. package/dist/utils/failure-message.d.ts +8 -0
  275. package/dist/utils/failure-message.d.ts.map +1 -0
  276. package/dist/utils/failure-message.js +28 -0
  277. package/dist/utils/failure-message.js.map +1 -0
  278. package/package.json +257 -4
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Motion Frame Schema
3
+ *
4
+ * A `Frame` is a single **decoded** raster — the unit of input for
5
+ * `media/motion` analysis. Motion analysis operates on a *sequence* of these
6
+ * (e.g. successive screenshots, or frames pulled from a clip by a decoder),
7
+ * not on encoded bytes: decoding/transcoding is `media/capture`'s concern,
8
+ * perception is this kind's.
9
+ *
10
+ * Frames carry raw pixel bytes plus geometry. Single-channel (luma) frames
11
+ * are analysed directly; 3-/4-channel frames are reduced to luminance
12
+ * (0.299R + 0.587G + 0.114B) before analysis, because human motion-energy
13
+ * detectors (V1/MT) are overwhelmingly luminance-driven and that is the
14
+ * signal the passage's coherent-drift carrier lives in.
15
+ */
16
+ import * as z from 'zod/v4';
17
+ import { CapabilityError } from '@vibesdotdev/kernel';
18
+ import { attemptSync } from "../utils/attempt.js";
19
+ export const MAX_FRAME_PIXELS = 40_000_000;
20
+ /**
21
+ * Accepted pixel-buffer types. Canvas `ImageData` yields `Uint8ClampedArray`;
22
+ * native/decoder paths yield `Uint8Array`. Both are byte-addressable rasters.
23
+ */
24
+ export const PixelBufferSchema = z.union([
25
+ z.instanceof(Uint8Array),
26
+ z.instanceof(Uint8ClampedArray)
27
+ ]);
28
+ /**
29
+ * Channel layout of a frame's pixel buffer.
30
+ *
31
+ * - `1` — luma / grayscale, one byte per pixel.
32
+ * - `3` — packed RGB, three bytes per pixel.
33
+ * - `4` — packed RGBA (alpha ignored for luma), four bytes per pixel.
34
+ */
35
+ export const FrameChannelsSchema = z.union([z.literal(1), z.literal(3), z.literal(4)]);
36
+ /**
37
+ * A single decoded frame.
38
+ *
39
+ * `data.length` must be at least `width * height * channels`. Extra trailing
40
+ * bytes (row padding / stride) are tolerated but not interpreted — callers
41
+ * with strided buffers should repack to tight rows first.
42
+ */
43
+ export const FrameSchema = z.object({
44
+ /** Raw pixel bytes, row-major, top-left origin. */
45
+ data: PixelBufferSchema,
46
+ /** Pixel width. */
47
+ width: z.number().int().positive(),
48
+ /** Pixel height. */
49
+ height: z.number().int().positive(),
50
+ /** Channel layout. Defaults to single-channel luma. */
51
+ channels: FrameChannelsSchema.default(1),
52
+ /** Capture timestamp (epoch ms), when known. Used only for reporting. */
53
+ timestampMs: z.number().nonnegative().optional()
54
+ }).superRefine((frame, ctx) => {
55
+ const pixels = frame.width * frame.height;
56
+ if (!Number.isSafeInteger(pixels) || pixels > MAX_FRAME_PIXELS) {
57
+ ctx.addIssue({ code: 'custom', message: 'Frame pixel limit exceeded.' });
58
+ }
59
+ else if (frame.data.length < pixels * frame.channels) {
60
+ ctx.addIssue({ code: 'custom', message: 'Frame buffer is insufficient.' });
61
+ }
62
+ });
63
+ /**
64
+ * An ordered sequence of frames. Temporal analyses (`temporalStats`,
65
+ * `motionEnergy`) require at least two frames; pairwise analyses
66
+ * (`frameDiff`, `opticalFlow`) take exactly two.
67
+ */
68
+ export const FrameSequenceSchema = z.array(FrameSchema).min(2).superRefine((frames, ctx) => {
69
+ const first = frames[0];
70
+ if (first && frames.some(frame => frame.width !== first.width || frame.height !== first.height)) {
71
+ ctx.addIssue({ code: 'custom', message: 'Sequence geometry differs.' });
72
+ }
73
+ });
74
+ /** Discard schema diagnostics and thrown property access: refusals never carry rejected values. */
75
+ export function parseMotionInput(schema, value, options = false) {
76
+ const parsed = attemptSync(() => schema.safeParse(value));
77
+ if (!parsed.ok)
78
+ throw motionInputRefusal(options);
79
+ if (!parsed.value.success)
80
+ throw motionInputRefusal(options);
81
+ return parsed.value.data;
82
+ }
83
+ function motionInputRefusal(options) {
84
+ return new CapabilityError(options ? 'Motion analysis options are invalid.' : 'Motion frames are invalid.', 'INVALID_INPUT', { reason: options ? 'media-motion-options-invalid' : 'media-motion-frame-invalid', retryable: false });
85
+ }
86
+ //# sourceMappingURL=motion-frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion-frame.js","sourceRoot":"","sources":["../../src/schemas/motion-frame.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import * as z from 'zod/v4';
2
+ /**
3
+ * An artifact, revision, file or share id: an opaque token of 1 to 256
4
+ * characters that can never act as a path or URL. `/`, `\`, `?`, `#`, `%`,
5
+ * whitespace and control characters are refused, and `.` and `..` are never a
6
+ * valid id, so an id interpolates into an owner's route as it is.
7
+ */
8
+ export declare const OpaqueMediaIdSchema: z.ZodString;
9
+ export type OpaqueMediaId = z.infer<typeof OpaqueMediaIdSchema>;
10
+ //# sourceMappingURL=opaque-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-id.d.ts","sourceRoot":"","sources":["../../src/schemas/opaque-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAW5B;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,aAMoD,CAAC;AAErF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import * as z from 'zod/v4';
2
+ /** Whether any character is an ASCII control character (U+0000 to U+001F, U+007F). */
3
+ function hasControlCharacter(text) {
4
+ for (const character of text) {
5
+ const code = character.charCodeAt(0);
6
+ if (code <= 0x1f || code === 0x7f)
7
+ return true;
8
+ }
9
+ return false;
10
+ }
11
+ /**
12
+ * An artifact, revision, file or share id: an opaque token of 1 to 256
13
+ * characters that can never act as a path or URL. `/`, `\`, `?`, `#`, `%`,
14
+ * whitespace and control characters are refused, and `.` and `..` are never a
15
+ * valid id, so an id interpolates into an owner's route as it is.
16
+ */
17
+ export const OpaqueMediaIdSchema = z
18
+ .string()
19
+ .min(1)
20
+ .max(256)
21
+ .regex(/^[^\s/\\?#%]+$/u, 'Ids are opaque tokens: no path, query, escape or whitespace characters')
22
+ .refine((id) => !hasControlCharacter(id), 'Ids carry no control characters')
23
+ .refine((id) => id !== '.' && id !== '..', 'Ids are never a relative path segment');
24
+ //# sourceMappingURL=opaque-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opaque-id.js","sourceRoot":"","sources":["../../src/schemas/opaque-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,sFAAsF;AACtF,SAAS,mBAAmB,CAAC,IAAY;IACxC,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC;KAClC,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,KAAK,CAAC,iBAAiB,EAAE,wEAAwE,CAAC;KAClG,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,EAAE,CAAC,EAAE,iCAAiC,CAAC;KAC3E,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,uCAAuC,CAAC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import * as z from 'zod/v4';
2
+ export declare const sharingEntrySchema: z.ZodObject<{
3
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
4
+ id: z.ZodString;
5
+ token: z.ZodString;
6
+ shareUrl: z.ZodString;
7
+ mediaItemId: z.ZodString;
8
+ label: z.ZodNullable<z.ZodString>;
9
+ expiresAt: z.ZodNullable<z.ZodString>;
10
+ revokedAt: z.ZodNullable<z.ZodString>;
11
+ maxDownloads: z.ZodNullable<z.ZodNumber>;
12
+ downloadCount: z.ZodNumber;
13
+ lastAccessedAt: z.ZodNullable<z.ZodString>;
14
+ createdAt: z.ZodString;
15
+ updatedAt: z.ZodString;
16
+ }, z.core.$strip>>>;
17
+ loading: z.ZodDefault<z.ZodBoolean>;
18
+ error: z.ZodDefault<z.ZodNullable<z.ZodString>>;
19
+ loaded: z.ZodDefault<z.ZodBoolean>;
20
+ }, z.core.$strip>;
21
+ export declare const sharingSnapshotSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
22
+ links: z.ZodDefault<z.ZodArray<z.ZodObject<{
23
+ id: z.ZodString;
24
+ token: z.ZodString;
25
+ shareUrl: z.ZodString;
26
+ mediaItemId: z.ZodString;
27
+ label: z.ZodNullable<z.ZodString>;
28
+ expiresAt: z.ZodNullable<z.ZodString>;
29
+ revokedAt: z.ZodNullable<z.ZodString>;
30
+ maxDownloads: z.ZodNullable<z.ZodNumber>;
31
+ downloadCount: z.ZodNumber;
32
+ lastAccessedAt: z.ZodNullable<z.ZodString>;
33
+ createdAt: z.ZodString;
34
+ updatedAt: z.ZodString;
35
+ }, z.core.$strip>>>;
36
+ loading: z.ZodDefault<z.ZodBoolean>;
37
+ error: z.ZodDefault<z.ZodNullable<z.ZodString>>;
38
+ loaded: z.ZodDefault<z.ZodBoolean>;
39
+ }, z.core.$strip>>;
40
+ export type SharingEntry = z.infer<typeof sharingEntrySchema>;
41
+ export type SharingSnapshot = z.infer<typeof sharingSnapshotSchema>;
42
+ export declare function emptySharingEntry(): SharingEntry;
43
+ //# sourceMappingURL=sharing-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing-state.d.ts","sourceRoot":"","sources":["../../src/schemas/sharing-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;iBAK7B,CAAC;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;kBAA2C,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,wBAAgB,iBAAiB,IAAI,YAAY,CAAyC"}
@@ -0,0 +1,11 @@
1
+ import * as z from 'zod/v4';
2
+ import { shareLinkSchema } from "../uploads/schemas/shares.js";
3
+ export const sharingEntrySchema = z.object({
4
+ links: z.array(shareLinkSchema).default([]),
5
+ loading: z.boolean().default(false),
6
+ error: z.string().nullable().default(null),
7
+ loaded: z.boolean().default(false)
8
+ });
9
+ export const sharingSnapshotSchema = z.record(z.string(), sharingEntrySchema);
10
+ export function emptySharingEntry() { return sharingEntrySchema.parse({}); }
11
+ //# sourceMappingURL=sharing-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing-state.js","sourceRoot":"","sources":["../../src/schemas/sharing-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC1C,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAClC,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAI9E,MAAM,UAAU,iBAAiB,KAAmB,OAAO,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { onDestroy, onMount, type Snippet } from 'svelte';
3
+ import {
4
+ createMediaCaptureState, provideMediaCaptureState,
5
+ type MediaCaptureState, type MediaCaptureStateInitOptions
6
+ } from './state.svelte.js';
7
+
8
+ type Props = MediaCaptureStateInitOptions & {
9
+ state?: MediaCaptureState;
10
+ disposeOnDestroy?: boolean;
11
+ children?: Snippet;
12
+ };
13
+
14
+ let {
15
+ providerId,
16
+ runtime,
17
+ state = createMediaCaptureState(),
18
+ disposeOnDestroy = true,
19
+ children
20
+ }: Props = $props();
21
+
22
+ provideMediaCaptureState(() => state);
23
+ onMount(() => { void state.init({ providerId, runtime }); });
24
+ onDestroy(() => {
25
+ if (disposeOnDestroy) state.dispose();
26
+ });
27
+ </script>
28
+
29
+ {@render children?.()}
@@ -0,0 +1,11 @@
1
+ import { type Snippet } from 'svelte';
2
+ import { type MediaCaptureState, type MediaCaptureStateInitOptions } from './state.svelte.ts';
3
+ type Props = MediaCaptureStateInitOptions & {
4
+ state?: MediaCaptureState;
5
+ disposeOnDestroy?: boolean;
6
+ children?: Snippet;
7
+ };
8
+ declare const CaptureLoader: import("svelte").Component<Props, {}, "">;
9
+ type CaptureLoader = ReturnType<typeof CaptureLoader>;
10
+ export default CaptureLoader;
11
+ //# sourceMappingURL=CaptureLoader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CaptureLoader.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/CaptureLoader.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAEL,KAAK,iBAAiB,EAAE,KAAK,4BAA4B,EACzD,MAAM,mBAAmB,CAAC;AAG3B,KAAK,KAAK,GAAG,4BAA4B,GAAG;IAC3C,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAyBH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,189 @@
1
+ <script lang="ts">
2
+ import { Button } from '@vibesdotdev/kit/components/ui/button';
3
+ import * as Card from '@vibesdotdev/kit/components/ui/card';
4
+ import * as Select from '@vibesdotdev/kit/components/ui/select';
5
+ import { Label } from '@vibesdotdev/kit/components/ui/label';
6
+ import { Icon } from '@vibesdotdev/kit/components';
7
+ import type { CaptureSource } from '../schemas/capture-options.js';
8
+ import { useMediaCaptureState, type MediaCaptureState, type MediaCaptureEntry } from './state.svelte.js';
9
+ import { captureFileName, captureMimeType } from './runtime.js';
10
+ import { onMount } from 'svelte';
11
+
12
+ interface Props {
13
+ title?: string;
14
+ description?: string;
15
+ showHeader?: boolean;
16
+ providerLabel?: string;
17
+ state?: MediaCaptureState;
18
+ }
19
+
20
+ let {
21
+ title = 'Capture',
22
+ description = 'Capture media from a runtime-selected provider',
23
+ showHeader = true,
24
+ providerLabel,
25
+ state: captureState = useMediaCaptureState()
26
+ }: Props = $props();
27
+ const selected = $derived(
28
+ captureState.captures.find(
29
+ (capture: MediaCaptureEntry) => capture.id === captureState.selectedCaptureId
30
+ ) ?? captureState.captures[0] ?? null
31
+ );
32
+ const selectedMimeType = $derived(selected ? captureMimeType(selected.format) : null);
33
+ let modKey = $state('Ctrl');
34
+
35
+ onMount(() => {
36
+ const platform = navigator.platform || '';
37
+ modKey = /Mac|iPhone|iPad|iPod/i.test(platform) ? 'Cmd' : 'Ctrl';
38
+ const onKeyDown = (event: KeyboardEvent) => {
39
+ if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === 's') {
40
+ event.preventDefault();
41
+ void captureState.capture();
42
+ }
43
+ };
44
+ window.addEventListener('keydown', onKeyDown);
45
+ return () => window.removeEventListener('keydown', onKeyDown);
46
+ });
47
+ </script>
48
+
49
+ <div class="space-y-6">
50
+ {#if showHeader}
51
+ <div class="flex items-center justify-between gap-4">
52
+ <div>
53
+ <h1 class="text-2xl font-bold">{title}</h1>
54
+ <p class="text-muted-foreground text-sm">{description}</p>
55
+ </div>
56
+ <Button variant="outline" size="sm" onclick={() => void captureState.loadSources()} disabled={captureState.loading}>
57
+ <Icon icon="tabler:refresh" class={`mr-2 h-4 w-4 ${captureState.loading ? 'animate-spin' : ''}`} />
58
+ Refresh
59
+ </Button>
60
+ </div>
61
+ {/if}
62
+
63
+ {#if captureState.error}
64
+ <div role="alert" class="border-destructive bg-destructive/10 text-destructive rounded-lg border p-4 text-sm">
65
+ {captureState.error}
66
+ </div>
67
+ {/if}
68
+
69
+ <Card.Root>
70
+ <Card.Header>
71
+ <Card.Title>Capture source</Card.Title>
72
+ <Card.Description>
73
+ {providerLabel ?? (captureState.providerId ? 'Connected capture source' : 'No source connected')}
74
+ </Card.Description>
75
+ </Card.Header>
76
+ <Card.Content class="space-y-4">
77
+ <div class="space-y-2">
78
+ <Label>Source</Label>
79
+ <Select.Root
80
+ type="single"
81
+ value={captureState.sourceId == null ? '' : String(captureState.sourceId)}
82
+ onValueChange={(value: string | undefined) => value && captureState.selectSource(value)}
83
+ >
84
+ <Select.Trigger class="w-full">
85
+ {captureState.sources.find((source: CaptureSource) => String(source.id) === String(captureState.sourceId))?.name ?? 'Select source…'}
86
+ </Select.Trigger>
87
+ <Select.Content>
88
+ {#each captureState.sources as source}
89
+ <Select.Item value={String(source.id)}>{source.name}</Select.Item>
90
+ {/each}
91
+ </Select.Content>
92
+ </Select.Root>
93
+ </div>
94
+
95
+ <Button
96
+ onclick={() => void captureState.capture()}
97
+ disabled={captureState.sourceId == null || captureState.capturing}
98
+ class="w-full"
99
+ >
100
+ <Icon
101
+ icon={captureState.capturing ? 'tabler:loader-2' : 'tabler:camera'}
102
+ class={`mr-2 h-4 w-4 ${captureState.capturing ? 'animate-spin' : ''}`}
103
+ />
104
+ Capture
105
+ </Button>
106
+ </Card.Content>
107
+ </Card.Root>
108
+
109
+ <Card.Root>
110
+ <Card.Header>
111
+ <Card.Title class="text-sm">Quick capture</Card.Title>
112
+ <Card.Description>Capture the selected source without leaving the keyboard.</Card.Description>
113
+ </Card.Header>
114
+ <Card.Content>
115
+ <div class="flex items-center justify-between rounded-lg border p-3">
116
+ <div class="flex items-center gap-2 text-sm">
117
+ <Icon icon="tabler:camera" class="text-muted-foreground h-4 w-4" />
118
+ <span>Capture selected source</span>
119
+ </div>
120
+ <kbd class="bg-muted rounded px-2 py-1 text-xs">{modKey}+Shift+S</kbd>
121
+ </div>
122
+ </Card.Content>
123
+ </Card.Root>
124
+
125
+ {#if selected}
126
+ <Card.Root>
127
+ <Card.Header>
128
+ <div class="flex items-center justify-between gap-3">
129
+ <div>
130
+ <Card.Title class="text-sm">Selected capture</Card.Title>
131
+ <Card.Description>{new Date(selected.timestamp).toLocaleString()}</Card.Description>
132
+ </div>
133
+ <a
134
+ href={selected.url}
135
+ download={captureFileName(selected.timestamp, selected.format)}
136
+ class="border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center justify-center rounded-md border px-3 text-sm font-medium"
137
+ >
138
+ <Icon icon="tabler:download" class="mr-2 h-4 w-4" />
139
+ Download
140
+ </a>
141
+ </div>
142
+ </Card.Header>
143
+ <Card.Content>
144
+ {#if selectedMimeType?.startsWith('image/')}
145
+ <img src={selected.url} alt="Selected captured media" class="max-h-[32rem] w-full rounded-md border object-contain" />
146
+ {:else if selectedMimeType?.startsWith('video/')}
147
+ <!-- svelte-ignore a11y_media_has_caption -->
148
+ <video src={selected.url} controls class="max-h-[32rem] w-full rounded-md border"></video>
149
+ {:else if selectedMimeType?.startsWith('audio/')}
150
+ <audio src={selected.url} controls class="w-full"></audio>
151
+ {:else}
152
+ <a href={selected.url} target="_blank" rel="noreferrer" class="text-primary text-sm underline">
153
+ Open captured media
154
+ </a>
155
+ {/if}
156
+ </Card.Content>
157
+ </Card.Root>
158
+ {/if}
159
+
160
+ {#if captureState.captures.length > 0}
161
+ <Card.Root>
162
+ <Card.Header><Card.Title class="text-sm">Recent captures</Card.Title></Card.Header>
163
+ <Card.Content>
164
+ <ul class="space-y-2" aria-label="Capture history">
165
+ {#each captureState.captures as capture (capture.id)}
166
+ <li>
167
+ <button
168
+ type="button"
169
+ onclick={() => captureState.selectCapture(capture.id)}
170
+ class="hover:bg-muted flex w-full items-center justify-between rounded-lg border p-3 text-left"
171
+ class:ring-2={capture.id === selected?.id}
172
+ class:ring-primary={capture.id === selected?.id}
173
+ >
174
+ <span class="flex items-center gap-3">
175
+ <Icon icon="tabler:photo" class="text-muted-foreground h-5 w-5" />
176
+ <span>
177
+ <span class="block text-sm font-medium">{capture.format.toUpperCase()} capture</span>
178
+ <span class="text-muted-foreground block text-xs">{new Date(capture.timestamp).toLocaleString()}</span>
179
+ </span>
180
+ </span>
181
+ <Icon icon="tabler:chevron-right" class="text-muted-foreground h-4 w-4" />
182
+ </button>
183
+ </li>
184
+ {/each}
185
+ </ul>
186
+ </Card.Content>
187
+ </Card.Root>
188
+ {/if}
189
+ </div>
@@ -0,0 +1,12 @@
1
+ import { type MediaCaptureState } from './state.svelte.ts';
2
+ interface Props {
3
+ title?: string;
4
+ description?: string;
5
+ showHeader?: boolean;
6
+ providerLabel?: string;
7
+ state?: MediaCaptureState;
8
+ }
9
+ declare const CapturePanel: import("svelte").Component<Props, {}, "">;
10
+ type CapturePanel = ReturnType<typeof CapturePanel>;
11
+ export default CapturePanel;
12
+ //# sourceMappingURL=CapturePanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CapturePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/CapturePanel.svelte.ts"],"names":[],"mappings":"AASA,OAAO,EAAwB,KAAK,iBAAiB,EAA0B,MAAM,mBAAmB,CAAC;AAKxG,UAAU,KAAK;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAqKF,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,109 @@
1
+ <script lang="ts">
2
+ import { Icon } from '@vibesdotdev/kit/components';
3
+ import { getVibesUploadsClient } from '@vibesdotdev/media/uploads/client';
4
+ import { getRuntimeSvelteContext } from '@vibesdotdev/surface-sveltekit/svelte';
5
+ import { logOn } from './runtime-log.js';
6
+ import { attempt, failureLabel } from '../utils/attempt.js';
7
+ import { failureMessage } from './failure-message.js';
8
+ import { parseMediaUploaderProvenance, type MediaUploaderProps } from './uploader.js';
9
+
10
+
11
+ let {
12
+ value = $bindable(''),
13
+ accept = 'image/*',
14
+ kind = 'media',
15
+ sourceApp,
16
+ sourceId,
17
+ isPublic = false,
18
+ onUploaded
19
+ }: MediaUploaderProps = $props();
20
+
21
+ const runtimeStore = getRuntimeSvelteContext();
22
+ let dragging = $state(false);
23
+ let uploading = $state(false);
24
+ let error = $state<string | null>(null);
25
+ let fileInputEl = $state<HTMLInputElement | null>(null);
26
+
27
+ async function upload(file: File): Promise<void> {
28
+ if (uploading) return;
29
+ uploading = true;
30
+ error = null;
31
+ const uploaded = await attempt(async () => {
32
+ const provenance = parseMediaUploaderProvenance({ sourceApp, sourceId });
33
+ const uploads = await getVibesUploadsClient(await runtimeStore.load());
34
+ return uploads.upload({
35
+ file,
36
+ kind,
37
+ ...provenance,
38
+ originalName: file.name,
39
+ contentType: file.type || undefined,
40
+ isPublic
41
+ });
42
+ });
43
+ uploading = false;
44
+ if (!uploaded.ok) {
45
+ // A fixed message for the user; the failure's class and reason go to the log, never its text.
46
+ error = failureMessage(uploaded.failure, 'Upload failed. Try again.');
47
+ void logOn(runtimeStore, 'warn', 'svelte.uploader', 'media uploader: upload failed', { failure: failureLabel(uploaded.failure) });
48
+ return;
49
+ }
50
+ value = uploaded.value.url;
51
+ onUploaded?.(uploaded.value.url);
52
+ }
53
+
54
+ function onFileChange(files: FileList | null): void {
55
+ const file = files?.[0];
56
+ if (file) void upload(file);
57
+ }
58
+
59
+ function onDrop(event: DragEvent): void {
60
+ event.preventDefault();
61
+ dragging = false;
62
+ const file = event.dataTransfer?.files?.[0];
63
+ if (file) void upload(file);
64
+ }
65
+
66
+ function clear(): void {
67
+ value = '';
68
+ onUploaded?.('');
69
+ if (fileInputEl) fileInputEl.value = '';
70
+ }
71
+ </script>
72
+
73
+ <div class="space-y-3">
74
+ {#if value}
75
+ <div class="relative inline-block">
76
+ <img src={value} alt="Uploaded" class="h-16 w-auto rounded border object-contain" />
77
+ <button type="button" onclick={clear} class="absolute -right-2 -top-2 rounded-full border border-border bg-background p-0.5 shadow-sm hover:bg-red-50" aria-label="Remove image">
78
+ <Icon icon="lucide:x" class="h-3 w-3 text-red-500" />
79
+ </button>
80
+ </div>
81
+ {/if}
82
+
83
+ <div
84
+ role="button"
85
+ tabindex="0"
86
+ class="flex cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-border px-6 py-8 transition-colors hover:border-primary/50 {dragging ? 'border-primary bg-primary/5' : 'bg-muted/30'} {uploading ? 'pointer-events-none opacity-60' : ''}"
87
+ ondragover={(event) => { event.preventDefault(); dragging = true; }}
88
+ ondragleave={() => { dragging = false; }}
89
+ ondrop={onDrop}
90
+ onclick={() => fileInputEl?.click()}
91
+ onkeydown={(event) => event.key === 'Enter' && fileInputEl?.click()}
92
+ >
93
+ {#if uploading}
94
+ <div class="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
95
+ <p class="text-sm text-muted-foreground">Uploading...</p>
96
+ {:else}
97
+ <div class="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
98
+ <Icon icon={dragging ? 'lucide:upload' : 'lucide:image'} class={dragging ? 'h-5 w-5 text-primary' : 'h-5 w-5 text-muted-foreground'} />
99
+ </div>
100
+ <div class="text-center">
101
+ <p class="text-sm font-medium">{dragging ? 'Drop to upload' : 'Click or drag to upload'}</p>
102
+ <p class="text-xs text-muted-foreground">Choose a supported media file</p>
103
+ </div>
104
+ {/if}
105
+ </div>
106
+
107
+ {#if error}<p class="text-sm text-destructive">{error}</p>{/if}
108
+ <input bind:this={fileInputEl} type="file" {accept} class="hidden" onchange={(event) => onFileChange(event.currentTarget.files)} />
109
+ </div>
@@ -0,0 +1,5 @@
1
+ import { type MediaUploaderProps } from './uploader.ts';
2
+ declare const MediaUploader: import("svelte").Component<MediaUploaderProps, {}, "value">;
3
+ type MediaUploader = ReturnType<typeof MediaUploader>;
4
+ export default MediaUploader;
5
+ //# sourceMappingURL=MediaUploader.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaUploader.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/MediaUploader.svelte.ts"],"names":[],"mappings":"AASA,OAAO,EAAgC,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAyGtF,QAAA,MAAM,aAAa,6DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { parseCaptureResult, parseCaptureSources } from '../schemas/capture-parse.ts';
2
+ //# sourceMappingURL=capture-parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-parse.d.ts","sourceRoot":"","sources":["../../src/svelte/capture-parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1 @@
1
+ export { parseCaptureResult, parseCaptureSources } from "../schemas/capture-parse.js";
@@ -0,0 +1,2 @@
1
+ export { failureMessage } from '../utils/failure-message.ts';
2
+ //# sourceMappingURL=failure-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-message.d.ts","sourceRoot":"","sources":["../../src/svelte/failure-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1 @@
1
+ export { failureMessage } from "../utils/failure-message.js";
@@ -0,0 +1,8 @@
1
+ export { default as CaptureLoader } from './CaptureLoader.svelte';
2
+ export { default as CapturePanel } from './CapturePanel.svelte';
3
+ export { default as MediaUploader } from './MediaUploader.svelte';
4
+ export { mediaCaptureState, MediaCaptureState, createMediaCaptureState, provideMediaCaptureState, useMediaCaptureState } from './state.svelte.ts';
5
+ export type { MediaCaptureEntry, MediaCaptureStateInitOptions } from './state.svelte.ts';
6
+ export { captureFileName, captureMimeType } from './runtime.ts';
7
+ export { MediaUploaderProvenanceSchema, parseMediaUploaderProvenance, type MediaUploaderProps, type MediaUploaderProvenance } from './uploader.ts';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAClJ,YAAY,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACN,6BAA6B,EAC7B,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,MAAM,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { default as CaptureLoader } from './CaptureLoader.svelte';
2
+ export { default as CapturePanel } from './CapturePanel.svelte';
3
+ export { default as MediaUploader } from './MediaUploader.svelte';
4
+ export { mediaCaptureState, MediaCaptureState, createMediaCaptureState, provideMediaCaptureState, useMediaCaptureState } from "./state.svelte.js";
5
+ export { captureFileName, captureMimeType } from "./runtime.js";
6
+ export { MediaUploaderProvenanceSchema, parseMediaUploaderProvenance } from "./uploader.js";
@@ -0,0 +1,14 @@
1
+ import type { RuntimeRef } from '@vibesdotdev/kernel';
2
+ /** Where a runtime comes from: the store `getRuntimeSvelteContext()` returns. */
3
+ export interface RuntimeSource {
4
+ load(): Promise<RuntimeRef>;
5
+ }
6
+ export type LogLevel = 'debug' | 'warn';
7
+ export type LogFields = Record<string, string | number | boolean | null | undefined>;
8
+ /**
9
+ * Log one entry on the runtime `source` holds. A source that never loaded has
10
+ * no runtime to bind a logger to, so the entry is dropped; the store records
11
+ * that failed load on its own `error`.
12
+ */
13
+ export declare function logOn(source: RuntimeSource, level: LogLevel, context: string, message: string, fields: LogFields): Promise<void>;
14
+ //# sourceMappingURL=runtime-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-log.d.ts","sourceRoot":"","sources":["../../src/svelte/runtime-log.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC7B,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AACxC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAErF;;;;GAIG;AACH,wBAAsB,KAAK,CAC1B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC,CAIf"}