@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.
- package/LICENSE +18 -0
- package/README.md +95 -2
- package/dist/analyzers/frame-guard.d.ts +6 -0
- package/dist/analyzers/frame-guard.d.ts.map +1 -0
- package/dist/analyzers/frame-guard.js +10 -0
- package/dist/analyzers/frame-guard.js.map +1 -0
- package/dist/analyzers/motion-math.d.ts +66 -0
- package/dist/analyzers/motion-math.d.ts.map +1 -0
- package/dist/analyzers/motion-math.js +356 -0
- package/dist/analyzers/motion-math.js.map +1 -0
- package/dist/analyzers/reference-motion-analyzer.impl.d.ts +23 -0
- package/dist/analyzers/reference-motion-analyzer.impl.d.ts.map +1 -0
- package/dist/analyzers/reference-motion-analyzer.impl.js +69 -0
- package/dist/analyzers/reference-motion-analyzer.impl.js.map +1 -0
- package/dist/analyzers/reference-motion.descriptor.d.ts +11 -0
- package/dist/analyzers/reference-motion.descriptor.d.ts.map +1 -0
- package/dist/analyzers/reference-motion.descriptor.js +21 -0
- package/dist/analyzers/reference-motion.descriptor.js.map +1 -0
- package/dist/capture-client.d.ts +34 -0
- package/dist/capture-client.d.ts.map +1 -0
- package/dist/capture-client.js +163 -0
- package/dist/capture-client.js.map +1 -0
- package/dist/config/media.uploads.config.descriptor.d.ts +27 -0
- package/dist/config/media.uploads.config.descriptor.d.ts.map +1 -0
- package/dist/config/media.uploads.config.descriptor.js +46 -0
- package/dist/config/media.uploads.config.descriptor.js.map +1 -0
- package/dist/config/uploads.d.ts +11 -0
- package/dist/config/uploads.d.ts.map +1 -0
- package/dist/config/uploads.js +25 -0
- package/dist/config/uploads.js.map +1 -0
- package/dist/docs/artifact-references.docs.descriptor.d.ts +4 -0
- package/dist/docs/artifact-references.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/artifact-references.docs.descriptor.js +69 -0
- package/dist/docs/artifact-references.docs.descriptor.js.map +1 -0
- package/dist/docs/image-transform.docs.descriptor.d.ts +4 -0
- package/dist/docs/image-transform.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/image-transform.docs.descriptor.js +52 -0
- package/dist/docs/image-transform.docs.descriptor.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/capture-provider.d.ts +33 -0
- package/dist/interfaces/capture-provider.d.ts.map +1 -0
- package/dist/interfaces/capture-provider.js +10 -0
- package/dist/interfaces/capture-provider.js.map +1 -0
- package/dist/interfaces/image-transformer.d.ts +7 -0
- package/dist/interfaces/image-transformer.d.ts.map +1 -0
- package/dist/interfaces/image-transformer.js +2 -0
- package/dist/interfaces/image-transformer.js.map +1 -0
- package/dist/interfaces/index.d.ts +7 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +5 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/motion-analyzer.d.ts +55 -0
- package/dist/interfaces/motion-analyzer.d.ts.map +1 -0
- package/dist/interfaces/motion-analyzer.js +14 -0
- package/dist/interfaces/motion-analyzer.js.map +1 -0
- package/dist/kinds/capture.d.ts +58 -0
- package/dist/kinds/capture.d.ts.map +1 -0
- package/dist/kinds/capture.js +34 -0
- package/dist/kinds/capture.js.map +1 -0
- package/dist/kinds/image-transform.d.ts +91 -0
- package/dist/kinds/image-transform.d.ts.map +1 -0
- package/dist/kinds/image-transform.js +12 -0
- package/dist/kinds/image-transform.js.map +1 -0
- package/dist/kinds/index.d.ts +10 -0
- package/dist/kinds/index.d.ts.map +1 -0
- package/dist/kinds/index.js +7 -0
- package/dist/kinds/index.js.map +1 -0
- package/dist/kinds/motion.d.ts +57 -0
- package/dist/kinds/motion.d.ts.map +1 -0
- package/dist/kinds/motion.js +34 -0
- package/dist/kinds/motion.js.map +1 -0
- package/dist/media.plugin.d.ts +23 -0
- package/dist/media.plugin.d.ts.map +1 -0
- package/dist/media.plugin.js +48 -0
- package/dist/media.plugin.js.map +1 -0
- package/dist/schemas/artifact-reference.d.ts +59 -0
- package/dist/schemas/artifact-reference.d.ts.map +1 -0
- package/dist/schemas/artifact-reference.js +77 -0
- package/dist/schemas/artifact-reference.js.map +1 -0
- package/dist/schemas/capture-descriptor.d.ts +71 -0
- package/dist/schemas/capture-descriptor.d.ts.map +1 -0
- package/dist/schemas/capture-descriptor.js +74 -0
- package/dist/schemas/capture-descriptor.js.map +1 -0
- package/dist/schemas/capture-options.d.ts +51 -0
- package/dist/schemas/capture-options.d.ts.map +1 -0
- package/dist/schemas/capture-options.js +68 -0
- package/dist/schemas/capture-options.js.map +1 -0
- package/dist/schemas/capture-parse.d.ts +10 -0
- package/dist/schemas/capture-parse.d.ts.map +1 -0
- package/dist/schemas/capture-parse.js +30 -0
- package/dist/schemas/capture-parse.js.map +1 -0
- package/dist/schemas/capture-state.d.ts +77 -0
- package/dist/schemas/capture-state.d.ts.map +1 -0
- package/dist/schemas/capture-state.js +25 -0
- package/dist/schemas/capture-state.js.map +1 -0
- package/dist/schemas/image-transform.d.ts +269 -0
- package/dist/schemas/image-transform.d.ts.map +1 -0
- package/dist/schemas/image-transform.js +161 -0
- package/dist/schemas/image-transform.js.map +1 -0
- package/dist/schemas/index.d.ts +20 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +17 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/motion-analysis.d.ts +187 -0
- package/dist/schemas/motion-analysis.d.ts.map +1 -0
- package/dist/schemas/motion-analysis.js +213 -0
- package/dist/schemas/motion-analysis.js.map +1 -0
- package/dist/schemas/motion-descriptor.d.ts +53 -0
- package/dist/schemas/motion-descriptor.d.ts.map +1 -0
- package/dist/schemas/motion-descriptor.js +47 -0
- package/dist/schemas/motion-descriptor.js.map +1 -0
- package/dist/schemas/motion-frame.d.ts +63 -0
- package/dist/schemas/motion-frame.d.ts.map +1 -0
- package/dist/schemas/motion-frame.js +86 -0
- package/dist/schemas/motion-frame.js.map +1 -0
- package/dist/schemas/opaque-id.d.ts +10 -0
- package/dist/schemas/opaque-id.d.ts.map +1 -0
- package/dist/schemas/opaque-id.js +24 -0
- package/dist/schemas/opaque-id.js.map +1 -0
- package/dist/schemas/sharing-state.d.ts +43 -0
- package/dist/schemas/sharing-state.d.ts.map +1 -0
- package/dist/schemas/sharing-state.js +11 -0
- package/dist/schemas/sharing-state.js.map +1 -0
- package/dist/svelte/CaptureLoader.svelte +29 -0
- package/dist/svelte/CaptureLoader.svelte.d.ts +11 -0
- package/dist/svelte/CaptureLoader.svelte.d.ts.map +1 -0
- package/dist/svelte/CapturePanel.svelte +189 -0
- package/dist/svelte/CapturePanel.svelte.d.ts +12 -0
- package/dist/svelte/CapturePanel.svelte.d.ts.map +1 -0
- package/dist/svelte/MediaUploader.svelte +109 -0
- package/dist/svelte/MediaUploader.svelte.d.ts +5 -0
- package/dist/svelte/MediaUploader.svelte.d.ts.map +1 -0
- package/dist/svelte/capture-parse.d.ts +2 -0
- package/dist/svelte/capture-parse.d.ts.map +1 -0
- package/dist/svelte/capture-parse.js +1 -0
- package/dist/svelte/failure-message.d.ts +2 -0
- package/dist/svelte/failure-message.d.ts.map +1 -0
- package/dist/svelte/failure-message.js +1 -0
- package/dist/svelte/index.d.ts +8 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +6 -0
- package/dist/svelte/runtime-log.d.ts +14 -0
- package/dist/svelte/runtime-log.d.ts.map +1 -0
- package/dist/svelte/runtime-log.js +19 -0
- package/dist/svelte/runtime.d.ts +9 -0
- package/dist/svelte/runtime.d.ts.map +1 -0
- package/dist/svelte/runtime.js +27 -0
- package/dist/svelte/sharing/README.md +38 -0
- package/dist/svelte/sharing/components/ShareButton.svelte +57 -0
- package/dist/svelte/sharing/components/ShareButton.svelte.d.ts +11 -0
- package/dist/svelte/sharing/components/ShareButton.svelte.d.ts.map +1 -0
- package/dist/svelte/sharing/components/ShareDialog.svelte +222 -0
- package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts +8 -0
- package/dist/svelte/sharing/components/ShareDialog.svelte.d.ts.map +1 -0
- package/dist/svelte/sharing/components/ShareLoader.svelte +19 -0
- package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts +7 -0
- package/dist/svelte/sharing/components/ShareLoader.svelte.d.ts.map +1 -0
- package/dist/svelte/sharing/index.d.ts +20 -0
- package/dist/svelte/sharing/index.d.ts.map +1 -0
- package/dist/svelte/sharing/index.js +19 -0
- package/dist/svelte/sharing/runtime.d.ts +44 -0
- package/dist/svelte/sharing/runtime.d.ts.map +1 -0
- package/dist/svelte/sharing/runtime.js +81 -0
- package/dist/svelte/sharing/state.svelte.d.ts +28 -0
- package/dist/svelte/sharing/state.svelte.d.ts.map +1 -0
- package/dist/svelte/sharing/state.svelte.js +101 -0
- package/dist/svelte/sharing/styles.css +8 -0
- package/dist/svelte/state.svelte.d.ts +38 -0
- package/dist/svelte/state.svelte.d.ts.map +1 -0
- package/dist/svelte/state.svelte.js +102 -0
- package/dist/svelte/styles.css +2 -0
- package/dist/svelte/uploader.d.ts +17 -0
- package/dist/svelte/uploader.d.ts.map +1 -0
- package/dist/svelte/uploader.js +9 -0
- package/dist/uploads/adapters/local/index.d.ts +9 -0
- package/dist/uploads/adapters/local/index.d.ts.map +1 -0
- package/dist/uploads/adapters/local/index.js +12 -0
- package/dist/uploads/adapters/local/index.js.map +1 -0
- package/dist/uploads/adapters/local/local-storage.descriptor.d.ts +24 -0
- package/dist/uploads/adapters/local/local-storage.descriptor.d.ts.map +1 -0
- package/dist/uploads/adapters/local/local-storage.descriptor.js +20 -0
- package/dist/uploads/adapters/local/local-storage.descriptor.js.map +1 -0
- package/dist/uploads/adapters/local/local-storage.impl.d.ts +123 -0
- package/dist/uploads/adapters/local/local-storage.impl.d.ts.map +1 -0
- package/dist/uploads/adapters/local/local-storage.impl.js +231 -0
- package/dist/uploads/adapters/local/local-storage.impl.js.map +1 -0
- package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts +9 -0
- package/dist/uploads/adapters/local/uploads-adapter-local.plugin.d.ts.map +1 -0
- package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js +41 -0
- package/dist/uploads/adapters/local/uploads-adapter-local.plugin.js.map +1 -0
- package/dist/uploads/client/helper.d.ts +26 -0
- package/dist/uploads/client/helper.d.ts.map +1 -0
- package/dist/uploads/client/helper.js +27 -0
- package/dist/uploads/client/helper.js.map +1 -0
- package/dist/uploads/client/index.d.ts +11 -0
- package/dist/uploads/client/index.d.ts.map +1 -0
- package/dist/uploads/client/index.js +9 -0
- package/dist/uploads/client/index.js.map +1 -0
- package/dist/uploads/client/sharing-client.d.ts +45 -0
- package/dist/uploads/client/sharing-client.d.ts.map +1 -0
- package/dist/uploads/client/sharing-client.js +153 -0
- package/dist/uploads/client/sharing-client.js.map +1 -0
- package/dist/uploads/client/uploads-client.d.ts +200 -0
- package/dist/uploads/client/uploads-client.d.ts.map +1 -0
- package/dist/uploads/client/uploads-client.js +487 -0
- package/dist/uploads/client/uploads-client.js.map +1 -0
- package/dist/uploads/docs/uploads.docs.descriptor.d.ts +4 -0
- package/dist/uploads/docs/uploads.docs.descriptor.d.ts.map +1 -0
- package/dist/uploads/docs/uploads.docs.descriptor.js +65 -0
- package/dist/uploads/docs/uploads.docs.descriptor.js.map +1 -0
- package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts +22 -0
- package/dist/uploads/docs/uploads.share-links.docs.descriptor.d.ts.map +1 -0
- package/dist/uploads/docs/uploads.share-links.docs.descriptor.js +86 -0
- package/dist/uploads/docs/uploads.share-links.docs.descriptor.js.map +1 -0
- package/dist/uploads/index.d.ts +12 -0
- package/dist/uploads/index.d.ts.map +1 -0
- package/dist/uploads/index.js +13 -0
- package/dist/uploads/index.js.map +1 -0
- package/dist/uploads/schemas/common.d.ts +135 -0
- package/dist/uploads/schemas/common.d.ts.map +1 -0
- package/dist/uploads/schemas/common.js +152 -0
- package/dist/uploads/schemas/common.js.map +1 -0
- package/dist/uploads/schemas/forms.d.ts +59 -0
- package/dist/uploads/schemas/forms.d.ts.map +1 -0
- package/dist/uploads/schemas/forms.js +41 -0
- package/dist/uploads/schemas/forms.js.map +1 -0
- package/dist/uploads/schemas/index.d.ts +18 -0
- package/dist/uploads/schemas/index.d.ts.map +1 -0
- package/dist/uploads/schemas/index.js +13 -0
- package/dist/uploads/schemas/index.js.map +1 -0
- package/dist/uploads/schemas/records.d.ts +81 -0
- package/dist/uploads/schemas/records.d.ts.map +1 -0
- package/dist/uploads/schemas/records.js +82 -0
- package/dist/uploads/schemas/records.js.map +1 -0
- package/dist/uploads/schemas/requests.d.ts +32 -0
- package/dist/uploads/schemas/requests.d.ts.map +1 -0
- package/dist/uploads/schemas/requests.js +30 -0
- package/dist/uploads/schemas/requests.js.map +1 -0
- package/dist/uploads/schemas/shares.d.ts +67 -0
- package/dist/uploads/schemas/shares.d.ts.map +1 -0
- package/dist/uploads/schemas/shares.js +39 -0
- package/dist/uploads/schemas/shares.js.map +1 -0
- package/dist/uploads/server/backend.d.ts +8 -0
- package/dist/uploads/server/backend.d.ts.map +1 -0
- package/dist/uploads/server/backend.js +11 -0
- package/dist/uploads/server/backend.js.map +1 -0
- package/dist/uploads/server/config.d.ts +5 -0
- package/dist/uploads/server/config.d.ts.map +1 -0
- package/dist/uploads/server/config.js +38 -0
- package/dist/uploads/server/config.js.map +1 -0
- package/dist/uploads/server/service.d.ts +77 -0
- package/dist/uploads/server/service.d.ts.map +1 -0
- package/dist/uploads/server/service.js +152 -0
- package/dist/uploads/server/service.js.map +1 -0
- package/dist/uploads/server/storage-client.d.ts +11 -0
- package/dist/uploads/server/storage-client.d.ts.map +1 -0
- package/dist/uploads/server/storage-client.js +23 -0
- package/dist/uploads/server/storage-client.js.map +1 -0
- package/dist/uploads/server/storage.d.ts +40 -0
- package/dist/uploads/server/storage.d.ts.map +1 -0
- package/dist/uploads/server/storage.js +59 -0
- package/dist/uploads/server/storage.js.map +1 -0
- package/dist/uploads/uploads.plugin.d.ts +11 -0
- package/dist/uploads/uploads.plugin.d.ts.map +1 -0
- package/dist/uploads/uploads.plugin.js +89 -0
- package/dist/uploads/uploads.plugin.js.map +1 -0
- package/dist/utils/attempt.d.ts +27 -0
- package/dist/utils/attempt.d.ts.map +1 -0
- package/dist/utils/attempt.js +39 -0
- package/dist/utils/attempt.js.map +1 -0
- package/dist/utils/failure-message.d.ts +8 -0
- package/dist/utils/failure-message.d.ts.map +1 -0
- package/dist/utils/failure-message.js +28 -0
- package/dist/utils/failure-message.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|