sanity-plugin-s3-media 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # sanity-plugin-s3-media
2
2
 
3
3
  Sanity Studio plugin that adds an S3-backed media browser, asset sources, and
4
- schema types for files and images stored in AWS S3.
4
+ schema types for files, images, and videos stored in AWS S3.
5
5
 
6
6
  ## Features
7
7
 
8
8
  - Media tool in Studio for browsing, searching, and managing S3 assets.
9
- - Asset sources for `s3Image` and `s3File` inputs.
9
+ - Asset sources for `s3Image`, `s3File`, and `s3Video` inputs.
10
10
  - Direct uploads to S3 via a signed URL endpoint.
11
11
  - Optional CloudFront domain for delivery URLs.
12
12
  - Utilities to build S3 asset URLs from document IDs.
@@ -42,8 +42,8 @@ Plugin options:
42
42
 
43
43
  ### 2) Add schema types
44
44
 
45
- This plugin provides `s3Image` and `s3File` object types that reference
46
- `s3ImageAsset` and `s3FileAsset` documents. Use them like any other field:
45
+ This plugin provides `s3Image`, `s3File`, and `s3Video` object types that reference
46
+ `s3ImageAsset`, `s3FileAsset`, and `s3VideoAsset` documents. Use them like any other field:
47
47
 
48
48
  ```ts
49
49
  import {defineField, defineType} from 'sanity'
@@ -66,6 +66,10 @@ export const product = defineType({
66
66
  accept: 'application/pdf',
67
67
  },
68
68
  }),
69
+ defineField({
70
+ name: 'promoVideo',
71
+ type: 's3Video',
72
+ }),
69
73
  ],
70
74
  })
71
75
  ```
@@ -140,10 +144,11 @@ Response: any 2xx status is treated as success.
140
144
  The package also exports helpers for constructing asset URLs:
141
145
 
142
146
  ```ts
143
- import {buildS3FileUrl, buildS3ImageUrl} from 'sanity-plugin-s3-media'
147
+ import {buildS3FileUrl, buildS3ImageUrl, buildS3VideoUrl} from 'sanity-plugin-s3-media'
144
148
 
145
149
  const fileUrl = buildS3FileUrl('s3File-<assetId>-<ext>', {baseUrl})
146
150
  const imageUrl = buildS3ImageUrl('s3Image-<assetId>-<width>x<height>-<ext>', {baseUrl})
151
+ const videoUrl = buildS3VideoUrl('s3Video-<assetId>-<width>x<height>-<ext>', {baseUrl})
147
152
  ```
148
153
 
149
154
  ## Troubleshooting
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  var sanity = require("sanity");
3
3
  let S3AssetType = /* @__PURE__ */ (function(S3AssetType2) {
4
- return S3AssetType2.FILE = "s3File", S3AssetType2.IMAGE = "s3Image", S3AssetType2;
4
+ return S3AssetType2.FILE = "s3File", S3AssetType2.IMAGE = "s3Image", S3AssetType2.VIDEO = "s3Video", S3AssetType2;
5
5
  })({});
6
6
  function parseFileAssetId(documentId) {
7
7
  const [, assetId, extension] = documentId.split("-");
8
8
  if (!assetId || !extension)
9
9
  throw new Error(`Malformed file asset ID '${documentId}'.`);
10
10
  return {
11
- type: "s3File",
11
+ type: S3AssetType.FILE,
12
12
  assetId,
13
13
  extension
14
14
  };
@@ -18,7 +18,19 @@ function parseImageAssetId(documentId) {
18
18
  if (!assetId || !dimensionString || !extension || !(width > 0) || !(height > 0))
19
19
  throw new Error(`Malformed asset ID '${documentId}'.`);
20
20
  return {
21
- type: "s3Image",
21
+ type: S3AssetType.IMAGE,
22
+ assetId,
23
+ width,
24
+ height,
25
+ extension
26
+ };
27
+ }
28
+ function parseVideoAssetId(documentId) {
29
+ const [, assetId, dimensionString, extension] = documentId.split("-"), [width, height] = (dimensionString || "").split("x").map(Number);
30
+ if (!assetId || !dimensionString || !extension || !(width > 0) || !(height > 0))
31
+ throw new Error(`Malformed asset ID '${documentId}'.`);
32
+ return {
33
+ type: S3AssetType.VIDEO,
22
34
  assetId,
23
35
  width,
24
36
  height,
@@ -47,10 +59,22 @@ function buildS3ImagePath(documentId) {
47
59
  function buildS3ImageUrl(assetId, options) {
48
60
  return `${options?.baseUrl}/${buildS3ImagePath(assetId)}`;
49
61
  }
62
+ function buildS3VideoPath(documentId) {
63
+ const {
64
+ assetId,
65
+ width,
66
+ height,
67
+ extension
68
+ } = parseVideoAssetId(documentId);
69
+ return `${assetId}-${width}x${height}.${extension}`;
70
+ }
71
+ function buildS3VideoUrl(assetId, options) {
72
+ return `${options?.baseUrl}/${buildS3VideoPath(assetId)}`;
73
+ }
50
74
  function isObject(obj) {
51
75
  return obj !== null && !Array.isArray(obj) && typeof obj == "object";
52
76
  }
53
- const idPattern = new RegExp(`^(?:${S3AssetType.IMAGE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\d+x\\d+-[a-z0-9]+|${S3AssetType.FILE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-[a-z0-9]+)$`), inProgressAssetId = "upload-in-progress-placeholder", inProgressAssetExtension = "tmp";
77
+ const idPattern = new RegExp(`^(?:${S3AssetType.IMAGE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\d+x\\d+-[a-z0-9]+|${S3AssetType.VIDEO}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\d+x\\d+-[a-z0-9]+|${S3AssetType.FILE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-[a-z0-9]+)$`), inProgressAssetId = "upload-in-progress-placeholder", inProgressAssetExtension = "tmp";
54
78
  class UnresolvableError extends Error {
55
79
  unresolvable = !0;
56
80
  // The input may not be a valid source, so let's not type it as one
@@ -110,12 +134,31 @@ function getS3ImageDimensions(src) {
110
134
  _type: "s3ImageDimensions"
111
135
  };
112
136
  }
113
- const tryGetS3ImageDimensions = getForgivingResolver(getS3ImageDimensions);
137
+ function getS3VideoDimensions(src) {
138
+ if (isInProgressUpload(src))
139
+ return {
140
+ width: 0,
141
+ height: 0,
142
+ aspectRatio: 0,
143
+ _type: "s3VideoDimensions"
144
+ };
145
+ const videoId = getS3AssetDocumentId(src), {
146
+ width,
147
+ height
148
+ } = parseVideoAssetId(videoId), aspectRatio = width / height;
149
+ return {
150
+ width,
151
+ height,
152
+ aspectRatio,
153
+ _type: "s3VideoDimensions"
154
+ };
155
+ }
156
+ const tryGetS3ImageDimensions = getForgivingResolver(getS3ImageDimensions), tryGetS3VideoDimensions = getForgivingResolver(getS3VideoDimensions);
114
157
  function getS3AssetExtension(src) {
115
158
  if (isInProgressUpload(src))
116
159
  return inProgressAssetExtension;
117
160
  const assetId = getS3AssetDocumentId(src);
118
- return isS3FileSource(src) ? parseFileAssetId(assetId).extension : parseImageAssetId(assetId).extension;
161
+ return isS3FileSource(src) ? parseFileAssetId(assetId).extension : isS3VideoSource(src) ? parseVideoAssetId(assetId).extension : parseImageAssetId(assetId).extension;
119
162
  }
120
163
  const tryGetS3AssetExtension = getForgivingResolver(getS3AssetExtension);
121
164
  function isS3FileSource(src) {
@@ -126,20 +169,30 @@ function isS3ImageSource(src) {
126
169
  const assetId = tryGetS3AssetDocumentId(src);
127
170
  return assetId ? assetId.startsWith(`${S3AssetType.IMAGE}-`) : !1;
128
171
  }
129
- const isS3FileAsset = (asset) => asset._type === "s3FileAsset", isS3ImageAsset = (asset) => asset._type === "s3ImageAsset";
172
+ function isS3VideoSource(src) {
173
+ const assetId = tryGetS3AssetDocumentId(src);
174
+ return assetId ? assetId.startsWith(`${S3AssetType.VIDEO}-`) : !1;
175
+ }
176
+ const isS3FileAsset = (asset) => asset._type === "s3FileAsset", isS3ImageAsset = (asset) => asset._type === "s3ImageAsset", isS3VideoAsset = (asset) => asset._type === "s3VideoAsset";
130
177
  exports.S3AssetType = S3AssetType;
131
178
  exports.buildS3FilePath = buildS3FilePath;
132
179
  exports.buildS3FileUrl = buildS3FileUrl;
133
180
  exports.buildS3ImagePath = buildS3ImagePath;
134
181
  exports.buildS3ImageUrl = buildS3ImageUrl;
182
+ exports.buildS3VideoPath = buildS3VideoPath;
183
+ exports.buildS3VideoUrl = buildS3VideoUrl;
135
184
  exports.getS3AssetExtension = getS3AssetExtension;
136
185
  exports.getS3ImageDimensions = getS3ImageDimensions;
186
+ exports.getS3VideoDimensions = getS3VideoDimensions;
137
187
  exports.isInProgressUpload = isInProgressUpload;
138
188
  exports.isObject = isObject;
139
189
  exports.isS3FileAsset = isS3FileAsset;
140
190
  exports.isS3FileSource = isS3FileSource;
141
191
  exports.isS3ImageAsset = isS3ImageAsset;
142
192
  exports.isS3ImageSource = isS3ImageSource;
193
+ exports.isS3VideoAsset = isS3VideoAsset;
194
+ exports.isS3VideoSource = isS3VideoSource;
143
195
  exports.tryGetS3AssetExtension = tryGetS3AssetExtension;
144
196
  exports.tryGetS3ImageDimensions = tryGetS3ImageDimensions;
197
+ exports.tryGetS3VideoDimensions = tryGetS3VideoDimensions;
145
198
  //# sourceMappingURL=asset-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"asset-utils.js","sources":["../../src/types/asset.ts","../../src/utils/asset/parse.ts","../../src/utils/asset/paths.ts","../../src/utils/isObject.ts","../../src/utils/resolve.ts"],"sourcesContent":["import type {ComponentType, ReactNode} from 'react'\nimport type {\n AssetFromSource,\n AssetSourceComponentAction,\n AssetSourceUploaderClass,\n SanityDocument,\n} from 'sanity'\n\nimport type {S3FileSchemaType, S3ImageMetaData, S3ImageSchemaType} from './schema'\n\n/** @public */\nexport interface S3AssetDocument extends SanityDocument {\n _id: string\n _type: 's3FileAsset' | 's3ImageAsset'\n assetId: string\n extension: string\n mimeType: string\n sha1hash: string\n size: number\n originalFilename?: string\n}\n\n/** @public */\nexport type S3FileAsset = S3AssetDocument & {_type: 's3FileAsset'}\n\n/** @public */\nexport type S3ImageAsset = S3AssetDocument & {_type: 's3ImageAsset'; metadata: S3ImageMetaData}\n\n/** @public */\nexport type S3Asset = S3FileAsset | S3ImageAsset\n\n/** @public */\nexport enum S3AssetType {\n FILE = 's3File',\n IMAGE = 's3Image',\n}\n\nexport interface S3AssetSourceComponentProps {\n action?: AssetSourceComponentAction\n assetSource: S3AssetSource\n assetType?: S3AssetType\n accept: string\n selectionType: 'single'\n dialogHeaderTitle?: ReactNode\n selectedAssets: S3AssetDocument[]\n onClose: () => void\n onSelect: (assetFromSource: AssetFromSource[]) => void\n onChangeAction?: (action: AssetSourceComponentAction) => void\n schemaType?: S3ImageSchemaType | S3FileSchemaType\n assetToOpen?: S3AssetDocument\n}\n\nexport interface S3AssetSource {\n name: string\n title?: string\n component: ComponentType<S3AssetSourceComponentProps>\n icon?: ComponentType\n Uploader?: AssetSourceUploaderClass\n}\n\nexport interface S3FileAssetIdParts {\n type: 's3File'\n assetId: string\n extension: string\n}\n\nexport interface S3ImageAssetIdParts {\n type: 's3Image'\n assetId: string\n extension: string\n width: number\n height: number\n}\n","import type {S3FileAssetIdParts, S3ImageAssetIdParts} from '../../types'\n\n/**\n * Parses a S3 file asset document ID into individual parts (type, id, extension)\n *\n * @param documentId - File asset document ID to parse into named parts\n * @returns Object of named properties\n * @public\n * @throws If document ID invalid\n */\nexport function parseFileAssetId(documentId: string): S3FileAssetIdParts {\n const [, assetId, extension] = documentId.split('-')\n\n if (!assetId || !extension) {\n throw new Error(`Malformed file asset ID '${documentId}'.`)\n }\n\n return {type: 's3File', assetId, extension}\n}\n\n/**\n * Parses a S3 image asset document ID into individual parts (type, id, extension, width, height)\n *\n * @param documentId - Image asset document ID to parse into named parts\n * @returns Object of named properties\n * @public\n * @throws If document ID invalid\n */\nexport function parseImageAssetId(documentId: string): S3ImageAssetIdParts {\n const [, assetId, dimensionString, extension] = documentId.split('-')\n const [width, height] = (dimensionString || '').split('x').map(Number)\n\n if (!assetId || !dimensionString || !extension || !(width > 0) || !(height > 0)) {\n throw new Error(`Malformed asset ID '${documentId}'.`)\n }\n\n return {type: 's3Image', assetId, width, height, extension}\n}\n","import {parseFileAssetId, parseImageAssetId} from './parse'\n\ninterface UrlBuilderOptions {\n baseUrl: string\n}\n\n/**\n * Builds the base file path from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the file belongs to, along with other options\n * @returns The path to the file\n * @public\n */\nexport function buildS3FilePath(documentId: string): string {\n const {assetId, extension} = parseFileAssetId(documentId)\n\n return `${assetId}.${extension}`\n}\n\n/**\n * Builds the base file URL from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID and extension\n * @param options - Project ID and dataset the file belongs to, along with other options\n * @returns The URL to the file, as a string\n * @public\n */\nexport function buildS3FileUrl(assetId: string, options: UrlBuilderOptions): string {\n const baseUrl = options?.baseUrl\n\n return `${baseUrl}/${buildS3FilePath(assetId)}`\n}\n\n/**\n * Builds the base image path from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the image belongs to, along with other options\n * @returns The path to the image\n * @public\n */\nexport function buildS3ImagePath(documentId: string): string {\n const {assetId, width, height, extension} = parseImageAssetId(documentId)\n\n return `${assetId}-${width}x${height}.${extension}`\n}\n\n/**\n * Builds the base image URL from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the image belongs to\n * @returns The URL to the image, as a string\n * @public\n */\nexport function buildS3ImageUrl(assetId: string, options: UrlBuilderOptions): string {\n const baseUrl = options?.baseUrl\n return `${baseUrl}/${buildS3ImagePath(assetId)}`\n}\n","/**\n * Checks whether or not the passed object is an object (and not `null`)\n *\n * @param obj Item to check whether or not is an object\n * @returns Whether or not `obj` is an object\n * @internal\n */\nexport function isObject(obj: unknown): obj is object {\n return obj !== null && !Array.isArray(obj) && typeof obj === 'object'\n}\n","import {isReference} from 'sanity'\n\nimport {\n type S3Asset,\n type S3AssetObjectStub,\n S3AssetType,\n type S3FileAsset,\n type S3FileSource,\n type S3FileUploadStub,\n type S3ImageAsset,\n type S3ImageDimensions,\n type S3ImageSource,\n type S3ImageUploadStub,\n} from '../types'\nimport {parseFileAssetId, parseImageAssetId} from './asset/parse'\nimport {isObject} from './isObject'\n\n/**\n * A \"safe function\" is a wrapped function that would normally throw an UnresolvableError,\n * but will instead return `undefined`. Other errors are still thrown.\n *\n * @public\n */\ntype SafeFunction<Args extends unknown[], Return> = (...args: Args) => Return | undefined\n\nconst idPattern = new RegExp(\n `^(?:` +\n `${S3AssetType.IMAGE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\\\d+x\\\\d+-[a-z0-9]+` +\n `|` +\n `${S3AssetType.FILE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-[a-z0-9]+` +\n `)$`,\n)\n\n/**\n * Placeholder asset _ids for in-progress uploads\n * @internal\n */\nconst inProgressAssetId = 'upload-in-progress-placeholder'\n\n/**\n * Placeholder extension for in-progress uploads\n * @internal\n */\nconst inProgressAssetExtension = 'tmp'\n\n/**\n * Error type thrown when the library fails to resolve a value, such as an asset ID,\n * filename or project ID/dataset information.\n *\n * The `input` property holds the value passed as the input, which failed to be\n * resolved to something meaningful.\n *\n * @public\n */\nclass UnresolvableError extends Error {\n unresolvable = true\n\n // The input may not be a valid source, so let's not type it as one\n input?: unknown\n\n constructor(inputSource: unknown, message = 'Failed to resolve asset ID from source') {\n super(message)\n this.input = inputSource\n }\n}\n\n/**\n * Checks whether or not an error instance is of type UnresolvableError\n *\n * @param err - Error to check for unresolvable error type\n * @returns True if the passed error instance appears to be an unresolvable error\n * @public\n */\nfunction isUnresolvableError(err: unknown): err is UnresolvableError {\n const error = err as UnresolvableError\n return Boolean(error.unresolvable && 'input' in error)\n}\n\n/**\n * Returns a getter which returns `undefined` instead of throwing,\n * if encountering an `UnresolvableError`\n *\n * @param method - Function to use as resolver\n * @returns Function that returns `undefined` if passed resolver throws UnresolvableError\n * @internal\n */\nfunction getForgivingResolver<Args extends unknown[], Return>(\n method: (...args: Args) => Return,\n): SafeFunction<Args, Return> {\n return (...args: Args): Return | undefined => {\n try {\n return method(...args)\n } catch (err) {\n if (isUnresolvableError(err)) {\n return undefined\n }\n\n throw err\n }\n }\n}\n\n/**\n * Checks whether or not the given source is an in-progress upload\n * (has upload property but no asset property)\n *\n * @param stub - Possible in-progress upload\n * @returns Whether or not the passed object is an in-progress upload\n * @public\n */\nexport function isInProgressUpload(stub: unknown): stub is S3ImageUploadStub | S3FileUploadStub {\n const item = stub as S3ImageUploadStub | S3FileUploadStub\n return isObject(item) && Boolean(item._upload) && !('asset' in item)\n}\n\n/**\n * Checks whether or not the given source is an asset object stub\n *\n * @param stub - Possible asset object stub\n * @returns Whether or not the passed object is an object stub\n * @public\n */\nexport function isS3AssetObjectStub(stub: unknown): stub is S3AssetObjectStub {\n const item = stub as S3AssetObjectStub\n return isObject(item) && Boolean(item.asset) && typeof item.asset === 'object'\n}\n\n/**\n * Tries to resolve the asset document ID from any inferrable structure\n *\n * @param src - Input source (image/file object, asset, reference, id, url, path)\n * @returns The asset document ID\n *\n * @throws {@link UnresolvableError}\n * Throws if passed asset source could not be resolved to an asset document ID\n * @public\n */\nexport function getS3AssetDocumentId(src: S3FileSource | S3ImageSource): string {\n // Check if this is an in-progress upload (has upload but no asset)\n if (isInProgressUpload(src)) {\n // Return a placeholder ID that indicates in-progress state\n // This allows the render cycle to continue until asset is available\n return inProgressAssetId\n }\n\n const source = isS3AssetObjectStub(src) ? src.asset : src\n\n let id = ''\n\n if (isReference(source)) {\n id = source._ref\n } else {\n id = source._id\n }\n\n const hasId = id && idPattern.test(id)\n\n if (!hasId) {\n throw new UnresolvableError(src)\n }\n\n return id\n}\n\n/**\n * {@inheritDoc getS3AssetDocumentId}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nconst tryGetS3AssetDocumentId = getForgivingResolver(getS3AssetDocumentId)\n\n/**\n * Returns the width, height and aspect ratio of a passed image asset, from any\n * inferrable structure (id, url, path, asset document, image object etc)\n *\n * @param src - Input source (image object, asset, reference, id, url, path)\n * @returns Object with width, height and aspect ratio properties\n *\n * @throws {@link UnresolvableError}\n * Throws if passed image source could not be resolved to an asset ID\n * @public\n */\nexport function getS3ImageDimensions(src: S3ImageSource): S3ImageDimensions {\n // Check if this is an in-progress upload\n if (isInProgressUpload(src)) {\n // Return placeholder dimensions for in-progress uploads\n return {width: 0, height: 0, aspectRatio: 0, _type: 's3ImageDimensions'}\n }\n\n const imageId = getS3AssetDocumentId(src)\n const {width, height} = parseImageAssetId(imageId)\n const aspectRatio = width / height\n\n return {width, height, aspectRatio, _type: 's3ImageDimensions'}\n}\n\n/**\n * {@inheritDoc getS3ImageDimensions}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nexport const tryGetS3ImageDimensions = getForgivingResolver(getS3ImageDimensions)\n\n/**\n * Returns the file extension for a given asset\n *\n * @param src - Input source (file/image object, asset, reference, id, url, path)\n * @returns The file extension, if resolvable (no `.` included)\n *\n * @throws {@link UnresolvableError}\n * Throws if passed asset source could not be resolved to an asset ID\n * @public\n */\nexport function getS3AssetExtension(src: S3FileSource | S3ImageSource): string {\n // Check if this is an in-progress upload\n if (isInProgressUpload(src)) {\n // Return placeholder extension for in-progress uploads\n return inProgressAssetExtension\n }\n\n const assetId = getS3AssetDocumentId(src)\n\n return isS3FileSource(src)\n ? parseFileAssetId(assetId).extension\n : parseImageAssetId(assetId).extension\n}\n\n/**\n * {@inheritDoc getS3AssetExtension}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nexport const tryGetS3AssetExtension = getForgivingResolver(getS3AssetExtension)\n\n/**\n * Return whether or not the passed source is an s3 file source\n *\n * @param src - Source to check\n * @returns Whether or not the given source is an s3 file source\n * @public\n */\nexport function isS3FileSource(src: unknown): src is S3FileSource {\n const assetId = tryGetS3AssetDocumentId(src as S3FileSource)\n return assetId ? assetId.startsWith(`${S3AssetType.FILE}-`) : false\n}\n\n/**\n * Return whether or not the passed source is an s3 image source\n *\n * @param src - Source to check\n * @returns Whether or not the given source is an s3 image source\n * @public\n */\nexport function isS3ImageSource(src: unknown): src is S3ImageSource {\n const assetId = tryGetS3AssetDocumentId(src as S3ImageSource)\n\n return assetId ? assetId.startsWith(`${S3AssetType.IMAGE}-`) : false\n}\n\nexport const isS3FileAsset = (asset: S3Asset): asset is S3FileAsset => {\n return (asset as S3FileAsset)._type === 's3FileAsset'\n}\n\nexport const isS3ImageAsset = (asset: S3Asset): asset is S3ImageAsset => {\n return (asset as S3ImageAsset)._type === 's3ImageAsset'\n}\n"],"names":["S3AssetType","parseFileAssetId","documentId","assetId","extension","split","Error","type","parseImageAssetId","dimensionString","width","height","map","Number","buildS3FilePath","buildS3FileUrl","options","baseUrl","buildS3ImagePath","buildS3ImageUrl","isObject","obj","Array","isArray","idPattern","RegExp","IMAGE","FILE","inProgressAssetId","inProgressAssetExtension","UnresolvableError","unresolvable","constructor","inputSource","message","input","isUnresolvableError","err","error","Boolean","getForgivingResolver","method","args","isInProgressUpload","stub","item","_upload","isS3AssetObjectStub","asset","getS3AssetDocumentId","src","source","id","isReference","_ref","_id","test","tryGetS3AssetDocumentId","getS3ImageDimensions","aspectRatio","_type","imageId","tryGetS3ImageDimensions","getS3AssetExtension","isS3FileSource","tryGetS3AssetExtension","startsWith","isS3ImageSource","isS3FileAsset","isS3ImageAsset"],"mappings":"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"}
1
+ {"version":3,"file":"asset-utils.js","sources":["../../src/types/asset.ts","../../src/utils/asset/parse.ts","../../src/utils/asset/paths.ts","../../src/utils/isObject.ts","../../src/utils/resolve.ts"],"sourcesContent":["import type {ComponentType, ReactNode} from 'react'\nimport type {\n AssetFromSource,\n AssetSourceComponentAction,\n AssetSourceUploaderClass,\n SanityDocument,\n} from 'sanity'\n\nimport type {\n S3FileSchemaType,\n S3ImageMetaData,\n S3ImageSchemaType,\n S3VideoMetaData,\n S3VideoSchemaType,\n} from './schema'\n\n/** @public */\nexport interface S3AssetDocument extends SanityDocument {\n _id: string\n _type: 's3FileAsset' | 's3ImageAsset' | 's3VideoAsset'\n assetId: string\n extension: string\n mimeType: string\n sha1hash: string\n size: number\n originalFilename?: string\n}\n\n/** @public */\nexport type S3FileAsset = S3AssetDocument & {_type: 's3FileAsset'}\n\n/** @public */\nexport type S3ImageAsset = S3AssetDocument & {_type: 's3ImageAsset'; metadata: S3ImageMetaData}\n\n/** @public */\nexport type S3VideoAsset = S3AssetDocument & {_type: 's3VideoAsset'; metadata: S3VideoMetaData}\n\n/** @public */\nexport type S3Asset = S3FileAsset | S3ImageAsset | S3VideoAsset\n\n/** @public */\nexport enum S3AssetType {\n FILE = 's3File',\n IMAGE = 's3Image',\n VIDEO = 's3Video',\n}\n\nexport interface S3AssetSourceComponentProps {\n action?: AssetSourceComponentAction\n assetSource: S3AssetSource\n assetType?: S3AssetType\n accept: string\n selectionType: 'single'\n dialogHeaderTitle?: ReactNode\n selectedAssets: S3AssetDocument[]\n onClose: () => void\n onSelect: (assetFromSource: AssetFromSource[]) => void\n onChangeAction?: (action: AssetSourceComponentAction) => void\n schemaType?: S3ImageSchemaType | S3FileSchemaType | S3VideoSchemaType\n assetToOpen?: S3AssetDocument\n}\n\nexport interface S3AssetSource {\n name: string\n title?: string\n component: ComponentType<S3AssetSourceComponentProps>\n icon?: ComponentType\n Uploader?: AssetSourceUploaderClass\n}\n\nexport interface S3FileAssetIdParts {\n type: 's3File'\n assetId: string\n extension: string\n}\n\nexport interface S3ImageAssetIdParts {\n type: 's3Image'\n assetId: string\n extension: string\n width: number\n height: number\n}\n\nexport interface S3VideoAssetIdParts {\n type: 's3Video'\n assetId: string\n extension: string\n width: number\n height: number\n}\n","import {\n S3AssetType,\n type S3FileAssetIdParts,\n type S3ImageAssetIdParts,\n type S3VideoAssetIdParts,\n} from '../../types'\n\n/**\n * Parses a S3 file asset document ID into individual parts (type, id, extension)\n *\n * @param documentId - File asset document ID to parse into named parts\n * @returns Object of named properties\n * @public\n * @throws If document ID invalid\n */\nexport function parseFileAssetId(documentId: string): S3FileAssetIdParts {\n const [, assetId, extension] = documentId.split('-')\n\n if (!assetId || !extension) {\n throw new Error(`Malformed file asset ID '${documentId}'.`)\n }\n\n return {type: S3AssetType.FILE, assetId, extension}\n}\n\n/**\n * Parses a S3 image asset document ID into individual parts (type, id, extension, width, height)\n *\n * @param documentId - Image asset document ID to parse into named parts\n * @returns Object of named properties\n * @public\n * @throws If document ID invalid\n */\nexport function parseImageAssetId(documentId: string): S3ImageAssetIdParts {\n const [, assetId, dimensionString, extension] = documentId.split('-')\n const [width, height] = (dimensionString || '').split('x').map(Number)\n\n if (!assetId || !dimensionString || !extension || !(width > 0) || !(height > 0)) {\n throw new Error(`Malformed asset ID '${documentId}'.`)\n }\n\n return {type: S3AssetType.IMAGE, assetId, width, height, extension}\n}\n\n/**\n * Parses a S3 video asset document ID into individual parts (type, id, extension, width, height)\n *\n * @param documentId - Video asset document ID to parse into named parts\n * @returns Object of named properties\n * @public\n * @throws If document ID invalid\n */\nexport function parseVideoAssetId(documentId: string): S3VideoAssetIdParts {\n const [, assetId, dimensionString, extension] = documentId.split('-')\n const [width, height] = (dimensionString || '').split('x').map(Number)\n\n if (!assetId || !dimensionString || !extension || !(width > 0) || !(height > 0)) {\n throw new Error(`Malformed asset ID '${documentId}'.`)\n }\n\n return {type: S3AssetType.VIDEO, assetId, width, height, extension}\n}\n","import {parseFileAssetId, parseImageAssetId, parseVideoAssetId} from './parse'\n\ninterface UrlBuilderOptions {\n baseUrl: string\n}\n\n/**\n * Builds the base file path from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the file belongs to, along with other options\n * @returns The path to the file\n * @public\n */\nexport function buildS3FilePath(documentId: string): string {\n const {assetId, extension} = parseFileAssetId(documentId)\n\n return `${assetId}.${extension}`\n}\n\n/**\n * Builds the base file URL from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID and extension\n * @param options - Project ID and dataset the file belongs to, along with other options\n * @returns The URL to the file, as a string\n * @public\n */\nexport function buildS3FileUrl(assetId: string, options: UrlBuilderOptions): string {\n const baseUrl = options?.baseUrl\n\n return `${baseUrl}/${buildS3FilePath(assetId)}`\n}\n\n/**\n * Builds the base image path from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the image belongs to, along with other options\n * @returns The path to the image\n * @public\n */\nexport function buildS3ImagePath(documentId: string): string {\n const {assetId, width, height, extension} = parseImageAssetId(documentId)\n\n return `${assetId}-${width}x${height}.${extension}`\n}\n\n/**\n * Builds the base image URL from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Project ID and dataset the image belongs to\n * @returns The URL to the image, as a string\n * @public\n */\nexport function buildS3ImageUrl(assetId: string, options: UrlBuilderOptions): string {\n const baseUrl = options?.baseUrl\n return `${baseUrl}/${buildS3ImagePath(assetId)}`\n}\n\n/**\n * Builds the base video path from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @returns The path to the video\n * @public\n */\nexport function buildS3VideoPath(documentId: string): string {\n const {assetId, width, height, extension} = parseVideoAssetId(documentId)\n\n return `${assetId}-${width}x${height}.${extension}`\n}\n\n/**\n * Builds the base video URL from the minimal set of parts required to assemble it\n *\n * @param asset - An asset-like shape defining ID, dimensions and extension\n * @param options - Base URL configuration\n * @returns The URL to the video, as a string\n * @public\n */\nexport function buildS3VideoUrl(assetId: string, options: UrlBuilderOptions): string {\n const baseUrl = options?.baseUrl\n return `${baseUrl}/${buildS3VideoPath(assetId)}`\n}\n","/**\n * Checks whether or not the passed object is an object (and not `null`)\n *\n * @param obj Item to check whether or not is an object\n * @returns Whether or not `obj` is an object\n * @internal\n */\nexport function isObject(obj: unknown): obj is object {\n return obj !== null && !Array.isArray(obj) && typeof obj === 'object'\n}\n","import {isReference} from 'sanity'\n\nimport {\n type S3Asset,\n type S3AssetObjectStub,\n S3AssetType,\n type S3FileAsset,\n type S3FileSource,\n type S3FileUploadStub,\n type S3ImageAsset,\n type S3ImageDimensions,\n type S3ImageSource,\n type S3ImageUploadStub,\n type S3VideoAsset,\n type S3VideoDimensions,\n type S3VideoSource,\n type S3VideoUploadStub,\n} from '../types'\nimport {parseFileAssetId, parseImageAssetId, parseVideoAssetId} from './asset/parse'\nimport {isObject} from './isObject'\n\n/**\n * A \"safe function\" is a wrapped function that would normally throw an UnresolvableError,\n * but will instead return `undefined`. Other errors are still thrown.\n *\n * @public\n */\ntype SafeFunction<Args extends unknown[], Return> = (...args: Args) => Return | undefined\n\nconst idPattern = new RegExp(\n `^(?:` +\n `${S3AssetType.IMAGE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\\\d+x\\\\d+-[a-z0-9]+` +\n `|` +\n `${S3AssetType.VIDEO}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-\\\\d+x\\\\d+-[a-z0-9]+` +\n `|` +\n `${S3AssetType.FILE}-(?:[a-zA-Z0-9_]{24,40}|[a-f0-9]{40})+-[a-z0-9]+` +\n `)$`,\n)\n\n/**\n * Placeholder asset _ids for in-progress uploads\n * @internal\n */\nconst inProgressAssetId = 'upload-in-progress-placeholder'\n\n/**\n * Placeholder extension for in-progress uploads\n * @internal\n */\nconst inProgressAssetExtension = 'tmp'\n\n/**\n * Error type thrown when the library fails to resolve a value, such as an asset ID,\n * filename or project ID/dataset information.\n *\n * The `input` property holds the value passed as the input, which failed to be\n * resolved to something meaningful.\n *\n * @public\n */\nclass UnresolvableError extends Error {\n unresolvable = true\n\n // The input may not be a valid source, so let's not type it as one\n input?: unknown\n\n constructor(inputSource: unknown, message = 'Failed to resolve asset ID from source') {\n super(message)\n this.input = inputSource\n }\n}\n\n/**\n * Checks whether or not an error instance is of type UnresolvableError\n *\n * @param err - Error to check for unresolvable error type\n * @returns True if the passed error instance appears to be an unresolvable error\n * @public\n */\nfunction isUnresolvableError(err: unknown): err is UnresolvableError {\n const error = err as UnresolvableError\n return Boolean(error.unresolvable && 'input' in error)\n}\n\n/**\n * Returns a getter which returns `undefined` instead of throwing,\n * if encountering an `UnresolvableError`\n *\n * @param method - Function to use as resolver\n * @returns Function that returns `undefined` if passed resolver throws UnresolvableError\n * @internal\n */\nfunction getForgivingResolver<Args extends unknown[], Return>(\n method: (...args: Args) => Return,\n): SafeFunction<Args, Return> {\n return (...args: Args): Return | undefined => {\n try {\n return method(...args)\n } catch (err) {\n if (isUnresolvableError(err)) {\n return undefined\n }\n\n throw err\n }\n }\n}\n\n/**\n * Checks whether or not the given source is an in-progress upload\n * (has upload property but no asset property)\n *\n * @param stub - Possible in-progress upload\n * @returns Whether or not the passed object is an in-progress upload\n * @public\n */\nexport function isInProgressUpload(\n stub: unknown,\n): stub is S3ImageUploadStub | S3FileUploadStub | S3VideoUploadStub {\n const item = stub as S3ImageUploadStub | S3FileUploadStub | S3VideoUploadStub\n return isObject(item) && Boolean(item._upload) && !('asset' in item)\n}\n\n/**\n * Checks whether or not the given source is an asset object stub\n *\n * @param stub - Possible asset object stub\n * @returns Whether or not the passed object is an object stub\n * @public\n */\nexport function isS3AssetObjectStub(stub: unknown): stub is S3AssetObjectStub {\n const item = stub as S3AssetObjectStub\n return isObject(item) && Boolean(item.asset) && typeof item.asset === 'object'\n}\n\n/**\n * Tries to resolve the asset document ID from any inferrable structure\n *\n * @param src - Input source (image/file object, asset, reference, id, url, path)\n * @returns The asset document ID\n *\n * @throws {@link UnresolvableError}\n * Throws if passed asset source could not be resolved to an asset document ID\n * @public\n */\nexport function getS3AssetDocumentId(src: S3FileSource | S3ImageSource | S3VideoSource): string {\n // Check if this is an in-progress upload (has upload but no asset)\n if (isInProgressUpload(src)) {\n // Return a placeholder ID that indicates in-progress state\n // This allows the render cycle to continue until asset is available\n return inProgressAssetId\n }\n\n const source = isS3AssetObjectStub(src) ? src.asset : src\n\n let id = ''\n\n if (isReference(source)) {\n id = source._ref\n } else {\n id = source._id\n }\n\n const hasId = id && idPattern.test(id)\n\n if (!hasId) {\n throw new UnresolvableError(src)\n }\n\n return id\n}\n\n/**\n * {@inheritDoc getS3AssetDocumentId}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nconst tryGetS3AssetDocumentId = getForgivingResolver(getS3AssetDocumentId)\n\n/**\n * Returns the width, height and aspect ratio of a passed image asset, from any\n * inferrable structure (id, url, path, asset document, image object etc)\n *\n * @param src - Input source (image object, asset, reference, id, url, path)\n * @returns Object with width, height and aspect ratio properties\n *\n * @throws {@link UnresolvableError}\n * Throws if passed image source could not be resolved to an asset ID\n * @public\n */\nexport function getS3ImageDimensions(src: S3ImageSource): S3ImageDimensions {\n // Check if this is an in-progress upload\n if (isInProgressUpload(src)) {\n // Return placeholder dimensions for in-progress uploads\n return {width: 0, height: 0, aspectRatio: 0, _type: 's3ImageDimensions'}\n }\n\n const imageId = getS3AssetDocumentId(src)\n const {width, height} = parseImageAssetId(imageId)\n const aspectRatio = width / height\n\n return {width, height, aspectRatio, _type: 's3ImageDimensions'}\n}\n\n/**\n * Returns the width, height and aspect ratio of a passed video asset, from any\n * inferrable structure (id, url, path, asset document, video object etc)\n *\n * @param src - Input source (video object, asset, reference, id, url, path)\n * @returns Object with width, height and aspect ratio properties\n *\n * @throws {@link UnresolvableError}\n * Throws if passed video source could not be resolved to an asset ID\n * @public\n */\nexport function getS3VideoDimensions(src: S3VideoSource): S3VideoDimensions {\n // Check if this is an in-progress upload\n if (isInProgressUpload(src)) {\n // Return placeholder dimensions for in-progress uploads\n return {width: 0, height: 0, aspectRatio: 0, _type: 's3VideoDimensions'}\n }\n\n const videoId = getS3AssetDocumentId(src)\n const {width, height} = parseVideoAssetId(videoId)\n const aspectRatio = width / height\n\n return {width, height, aspectRatio, _type: 's3VideoDimensions'}\n}\n\n/**\n * {@inheritDoc getS3ImageDimensions}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nexport const tryGetS3ImageDimensions = getForgivingResolver(getS3ImageDimensions)\n\n/**\n * {@inheritDoc getS3VideoDimensions}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nexport const tryGetS3VideoDimensions = getForgivingResolver(getS3VideoDimensions)\n\n/**\n * Returns the file extension for a given asset\n *\n * @param src - Input source (file/image object, asset, reference, id, url, path)\n * @returns The file extension, if resolvable (no `.` included)\n *\n * @throws {@link UnresolvableError}\n * Throws if passed asset source could not be resolved to an asset ID\n * @public\n */\nexport function getS3AssetExtension(src: S3FileSource | S3ImageSource | S3VideoSource): string {\n // Check if this is an in-progress upload\n if (isInProgressUpload(src)) {\n // Return placeholder extension for in-progress uploads\n return inProgressAssetExtension\n }\n\n const assetId = getS3AssetDocumentId(src)\n\n if (isS3FileSource(src)) {\n return parseFileAssetId(assetId).extension\n }\n\n if (isS3VideoSource(src)) {\n return parseVideoAssetId(assetId).extension\n }\n\n return parseImageAssetId(assetId).extension\n}\n\n/**\n * {@inheritDoc getS3AssetExtension}\n * @returns Returns `undefined` instead of throwing if a value cannot be resolved\n * @public\n */\nexport const tryGetS3AssetExtension = getForgivingResolver(getS3AssetExtension)\n\n/**\n * Return whether or not the passed source is an s3 file source\n *\n * @param src - Source to check\n * @returns Whether or not the given source is an s3 file source\n * @public\n */\nexport function isS3FileSource(src: unknown): src is S3FileSource {\n const assetId = tryGetS3AssetDocumentId(src as S3FileSource)\n return assetId ? assetId.startsWith(`${S3AssetType.FILE}-`) : false\n}\n\n/**\n * Return whether or not the passed source is an s3 image source\n *\n * @param src - Source to check\n * @returns Whether or not the given source is an s3 image source\n * @public\n */\nexport function isS3ImageSource(src: unknown): src is S3ImageSource {\n const assetId = tryGetS3AssetDocumentId(src as S3ImageSource)\n\n return assetId ? assetId.startsWith(`${S3AssetType.IMAGE}-`) : false\n}\n\n/**\n * Return whether or not the passed source is an s3 video source\n *\n * @param src - Source to check\n * @returns Whether or not the given source is an s3 video source\n * @public\n */\nexport function isS3VideoSource(src: unknown): src is S3VideoSource {\n const assetId = tryGetS3AssetDocumentId(src as S3VideoSource)\n\n return assetId ? assetId.startsWith(`${S3AssetType.VIDEO}-`) : false\n}\n\nexport const isS3FileAsset = (asset: S3Asset): asset is S3FileAsset => {\n return (asset as S3FileAsset)._type === 's3FileAsset'\n}\n\nexport const isS3ImageAsset = (asset: S3Asset): asset is S3ImageAsset => {\n return (asset as S3ImageAsset)._type === 's3ImageAsset'\n}\n\nexport const isS3VideoAsset = (asset: S3Asset): asset is S3VideoAsset => {\n return (asset as S3VideoAsset)._type === 's3VideoAsset'\n}\n"],"names":["S3AssetType","parseFileAssetId","documentId","assetId","extension","split","Error","type","FILE","parseImageAssetId","dimensionString","width","height","map","Number","IMAGE","parseVideoAssetId","VIDEO","buildS3FilePath","buildS3FileUrl","options","baseUrl","buildS3ImagePath","buildS3ImageUrl","buildS3VideoPath","buildS3VideoUrl","isObject","obj","Array","isArray","idPattern","RegExp","inProgressAssetId","inProgressAssetExtension","UnresolvableError","unresolvable","constructor","inputSource","message","input","isUnresolvableError","err","error","Boolean","getForgivingResolver","method","args","isInProgressUpload","stub","item","_upload","isS3AssetObjectStub","asset","getS3AssetDocumentId","src","source","id","isReference","_ref","_id","test","tryGetS3AssetDocumentId","getS3ImageDimensions","aspectRatio","_type","imageId","getS3VideoDimensions","videoId","tryGetS3ImageDimensions","tryGetS3VideoDimensions","getS3AssetExtension","isS3FileSource","isS3VideoSource","tryGetS3AssetExtension","startsWith","isS3ImageSource","isS3FileAsset","isS3ImageAsset","isS3VideoAsset"],"mappings":"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"}
@@ -327,7 +327,7 @@ const constructFilter = ({
327
327
  try {
328
328
  canvas.getContext("2d")?.drawImage(img, 0, 0, PREVIEW_WIDTH, PREVIEW_WIDTH / imageAspect), canvas.toBlob(resolve, "image/jpeg");
329
329
  } catch (err) {
330
- console.warn("Unable to generate preview image:", err);
330
+ console.warn("Unable to generate preview image:", err), resolve(null);
331
331
  }
332
332
  }), createImageEl = (file) => new Promise((resolve) => {
333
333
  const blobUrlLarge = window.URL.createObjectURL(file), img = new Image();
@@ -380,7 +380,7 @@ function getUniqueDocuments(documents) {
380
380
  const draftIds = documents.reduce((acc, doc) => doc._id.startsWith("drafts.") ? acc.concat(doc._id.slice(7)) : acc, []);
381
381
  return documents.filter((doc) => !draftIds.includes(doc._id));
382
382
  }
383
- const UPLOAD_STATUS_KEY = "_upload", STALE_UPLOAD_MS = 1e3 * 60 * 2, SUPPORTED_ASSET_TYPES = [assetUtils.S3AssetType.FILE, assetUtils.S3AssetType.IMAGE], ORDER_OPTIONS = [{
383
+ const UPLOAD_STATUS_KEY = "_upload", STALE_UPLOAD_MS = 1e3 * 60 * 2, SUPPORTED_ASSET_TYPES = [assetUtils.S3AssetType.FILE, assetUtils.S3AssetType.IMAGE, assetUtils.S3AssetType.VIDEO], ORDER_OPTIONS = [{
384
384
  direction: "desc",
385
385
  field: "_createdAt"
386
386
  }, {
@@ -466,10 +466,25 @@ const createThrottler = (concurrency = DEFAULT_CONCURRENCY) => {
466
466
  }, img.onerror = () => {
467
467
  subscriber.error(new Error("Failed to load image")), cleanup();
468
468
  }, img.src = url;
469
- }).pipe(operators.catchError(() => rxjs.of(null))) : rxjs.of(null), getFileExtension = (filename) => filename.split(".").pop() ?? "", buildFileName$1 = (fileId, extension, dimensions) => dimensions ? `${fileId}-${dimensions.width}x${dimensions.height}.${extension}` : `${fileId}.${extension}`, buildDocumentId = (assetType, fileId, extension, dimensions) => assetType === assetUtils.S3AssetType.IMAGE && dimensions ? `${assetUtils.S3AssetType.IMAGE}-${fileId}-${dimensions.width}x${dimensions.height}-${extension}` : `${assetUtils.S3AssetType.FILE}-${fileId}-${extension}`, buildAssetDocument = (assetType, fileId, file, documentId, hash, dimensions, storeOriginalFilename) => {
469
+ }).pipe(operators.catchError(() => rxjs.of(null))) : rxjs.of(null), getVideoDimensions = (file) => file.type.startsWith("video/") ? new rxjs.Observable((subscriber) => {
470
+ const video = document.createElement("video"), url = URL.createObjectURL(file), cleanup = () => {
471
+ URL.revokeObjectURL(url), video.removeAttribute("src"), video.load();
472
+ };
473
+ video.onloadedmetadata = () => {
474
+ subscriber.next({
475
+ width: video.videoWidth,
476
+ height: video.videoHeight
477
+ }), subscriber.complete(), cleanup();
478
+ }, video.onerror = () => {
479
+ subscriber.error(new Error("Failed to load video metadata")), cleanup();
480
+ }, video.preload = "metadata", video.src = url;
481
+ }).pipe(operators.catchError(() => rxjs.of(null))) : rxjs.of(null), getFileExtension = (filename) => {
482
+ const segments = filename.split(".");
483
+ return segments[segments.length - 1];
484
+ }, buildFileName$1 = (fileId, extension, dimensions) => dimensions ? `${fileId}-${dimensions.width}x${dimensions.height}.${extension}` : `${fileId}.${extension}`, buildDocumentId = (assetType, fileId, extension, dimensions) => assetType === assetUtils.S3AssetType.IMAGE && dimensions ? `${assetUtils.S3AssetType.IMAGE}-${fileId}-${dimensions.width}x${dimensions.height}-${extension}` : assetType === assetUtils.S3AssetType.VIDEO && dimensions ? `${assetUtils.S3AssetType.VIDEO}-${fileId}-${dimensions.width}x${dimensions.height}-${extension}` : `${assetUtils.S3AssetType.FILE}-${fileId}-${extension}`, buildAssetDocument = (assetType, fileId, file, documentId, hash, dimensions, storeOriginalFilename) => {
470
485
  const baseDocument = {
471
486
  _id: documentId,
472
- _type: assetType === assetUtils.S3AssetType.IMAGE ? "s3ImageAsset" : "s3FileAsset",
487
+ _type: assetType === assetUtils.S3AssetType.IMAGE ? "s3ImageAsset" : assetType === assetUtils.S3AssetType.VIDEO ? "s3VideoAsset" : "s3FileAsset",
473
488
  assetId: fileId,
474
489
  originalFilename: storeOriginalFilename ? file.name : void 0,
475
490
  sha1hash: hash,
@@ -489,6 +504,18 @@ const createThrottler = (concurrency = DEFAULT_CONCURRENCY) => {
489
504
  aspectRatio: dimensions.width / dimensions.height
490
505
  }
491
506
  }
507
+ } : assetType === assetUtils.S3AssetType.VIDEO && dimensions ? {
508
+ ...baseDocument,
509
+ _type: "s3VideoAsset",
510
+ metadata: {
511
+ _type: "s3VideoMetadata",
512
+ dimensions: {
513
+ _type: "s3VideoDimensions",
514
+ height: dimensions.height,
515
+ width: dimensions.width,
516
+ aspectRatio: dimensions.width / dimensions.height
517
+ }
518
+ }
492
519
  } : baseDocument;
493
520
  }, createCompleteEvent = ({
494
521
  asset,
@@ -508,13 +535,15 @@ const createThrottler = (concurrency = DEFAULT_CONCURRENCY) => {
508
535
  const {
509
536
  storeOriginalFilename = !0
510
537
  } = options, extension = getFileExtension(file.name);
511
- return hashFile(file).pipe(operators.mergeMap((hash) => (assetType === assetUtils.S3AssetType.IMAGE ? getImageDimensions(file) : rxjs.of(null)).pipe(operators.map((dimensions) => ({
538
+ return hashFile(file).pipe(operators.mergeMap((hash) => (assetType === assetUtils.S3AssetType.IMAGE ? getImageDimensions(file) : assetType === assetUtils.S3AssetType.VIDEO ? getVideoDimensions(file) : rxjs.of(null)).pipe(operators.map((dimensions) => ({
512
539
  hash,
513
540
  dimensions
514
541
  })))), operators.mergeMap(({
515
542
  hash,
516
543
  dimensions
517
544
  }) => {
545
+ if (assetType === assetUtils.S3AssetType.VIDEO && !dimensions)
546
+ throw new Error("Unable to determine video dimensions");
518
547
  if (hash) {
519
548
  const documentId = buildDocumentId(assetType, hash, extension, dimensions);
520
549
  return fetchExistingAsset(sanityClient, assetType, documentId).pipe(operators.map((existing) => ({
@@ -697,15 +726,14 @@ const validateAssetType = (type) => {
697
726
  return new rxjs.Observable((subscriber) => {
698
727
  (async () => {
699
728
  try {
700
- if (subscriber.next({
729
+ subscriber.next({
701
730
  type: "progress",
702
731
  stage: "upload",
703
732
  percent: 5,
704
733
  loaded: 0,
705
734
  total: fileSize,
706
735
  lengthComputable: !0
707
- }), !getSignedUrlEndpoint)
708
- throw new Error("getSignedUrlEndpoint is not defined");
736
+ });
709
737
  const signedUrlResponse = await fetch(getSignedUrlEndpoint, {
710
738
  method: "POST",
711
739
  body: JSON.stringify({
@@ -1002,7 +1030,7 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1002
1030
  if (!assetId || !assetType)
1003
1031
  throw new Error("");
1004
1032
  if (!s3AssetBaseUrl)
1005
- return "";
1033
+ throw new Error("S3 Base URL is not defined");
1006
1034
  if (assetType === assetUtils.S3AssetType.IMAGE)
1007
1035
  return assetUtils.buildS3ImageUrl(assetId, {
1008
1036
  baseUrl: s3AssetBaseUrl
@@ -1011,7 +1039,11 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1011
1039
  return assetUtils.buildS3FileUrl(assetId, {
1012
1040
  baseUrl: s3AssetBaseUrl
1013
1041
  });
1014
- throw new Error("");
1042
+ if (assetType === assetUtils.S3AssetType.VIDEO)
1043
+ return assetUtils.buildS3VideoUrl(assetId, {
1044
+ baseUrl: s3AssetBaseUrl
1045
+ });
1046
+ throw new Error("Can't build asset id");
1015
1047
  }, $[6] = s3AssetBaseUrl, $[7] = t1) : t1 = $[7];
1016
1048
  const buildAssetUrl = t1;
1017
1049
  let t2;
@@ -1106,6 +1138,8 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1106
1138
  }, buildFileName = (asset) => {
1107
1139
  if (assetUtils.isS3ImageAsset(asset))
1108
1140
  return assetUtils.buildS3ImagePath(asset._id);
1141
+ if (assetUtils.isS3VideoAsset(asset))
1142
+ return assetUtils.buildS3VideoPath(asset._id);
1109
1143
  if (assetUtils.isS3FileAsset(asset))
1110
1144
  return assetUtils.buildS3FilePath(asset._id);
1111
1145
  throw new Error("Unsupported asset type");
@@ -1319,8 +1353,8 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1319
1353
  },
1320
1354
  sort(state) {
1321
1355
  state.allIds.sort((a, b) => {
1322
- const tagA = state.byIds[a].asset[state.order.field], tagB = state.byIds[b].asset[state.order.field];
1323
- return tagA < tagB ? state.order.direction === "asc" ? -1 : 1 : tagA > tagB ? state.order.direction === "asc" ? 1 : -1 : 0;
1356
+ const tagA = state.byIds[a].asset[state.order.field], tagB = state.byIds[b].asset[state.order.field], direction = state.order.direction === "asc" ? 1 : -1;
1357
+ return tagA < tagB ? -1 * direction : tagA > tagB ? 1 * direction : 0;
1324
1358
  });
1325
1359
  },
1326
1360
  updateComplete(state, action) {
@@ -1513,12 +1547,9 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1513
1547
  });
1514
1548
  }
1515
1549
  }
1516
- }), dialogClearOnAssetUpdateEpic = (action$) => action$.pipe(reduxObservable.ofType(assetsActions.deleteComplete.type, assetsActions.updateComplete.type), operators.filter((action) => !!action?.payload?.closeDialogId), operators.mergeMap((action) => {
1517
- const dialogId = action?.payload?.closeDialogId;
1518
- return dialogId ? rxjs.of(dialogSlice.actions.remove({
1519
- id: dialogId
1520
- })) : rxjs.EMPTY;
1521
- })), dialogActions = {
1550
+ }), dialogClearOnAssetUpdateEpic = (action$) => action$.pipe(reduxObservable.ofType(assetsActions.deleteComplete.type, assetsActions.updateComplete.type), operators.filter((action) => !!action?.payload?.closeDialogId), operators.mergeMap((action) => rxjs.of(dialogSlice.actions.remove({
1551
+ id: action.payload.closeDialogId
1552
+ })))), dialogActions = {
1522
1553
  ...dialogSlice.actions
1523
1554
  }, dialogReducer = dialogSlice.reducer, initialState$2 = {
1524
1555
  allIds: [],
@@ -1641,7 +1672,7 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1641
1672
  operators.filter((hash) => !state.uploads.byIds[hash]),
1642
1673
  // Dispatch start action and begin upload process
1643
1674
  operators.mergeMap((hash) => {
1644
- const assetType = forceAsAssetType || (file.type.indexOf("image") >= 0 ? assetUtils.S3AssetType.IMAGE : assetUtils.S3AssetType.FILE), uploadItem = {
1675
+ const assetType = forceAsAssetType || (file.type.indexOf("image") >= 0 ? assetUtils.S3AssetType.IMAGE : file.type.indexOf("video") >= 0 ? assetUtils.S3AssetType.VIDEO : assetUtils.S3AssetType.FILE), uploadItem = {
1645
1676
  hash,
1646
1677
  _type: "upload",
1647
1678
  assetType,
@@ -1787,7 +1818,7 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1787
1818
  } = useS3MediaContext();
1788
1819
  let t0;
1789
1820
  $[0] !== asset || $[1] !== buildAssetUrl ? (t0 = asset ? buildAssetUrl({
1790
- assetType: assetUtils.S3AssetType.FILE,
1821
+ assetType: asset._type === "s3VideoAsset" ? assetUtils.S3AssetType.VIDEO : assetUtils.S3AssetType.FILE,
1791
1822
  assetId: asset._id
1792
1823
  }) : null, $[0] = asset, $[1] = buildAssetUrl, $[2] = t0) : t0 = $[2];
1793
1824
  const assetUrl = t0;
@@ -1921,7 +1952,7 @@ const exp = defineCreateClientExports(S3Client), createS3Client = exp.createClie
1921
1952
  opacity: opacityPreview
1922
1953
  }, $[18] = opacityPreview, $[19] = t5) : t5 = $[19];
1923
1954
  let t6;
1924
- $[20] !== asset ? (t6 = assetUtils.isS3FileAsset(asset) && /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { asset, extension: asset.extension, width: "80px" }), $[20] = asset, $[21] = t6) : t6 = $[21];
1955
+ $[20] !== asset ? (t6 = (assetUtils.isS3FileAsset(asset) || assetUtils.isS3VideoAsset(asset)) && /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { asset, extension: asset.extension, width: "80px" }), $[20] = asset, $[21] = t6) : t6 = $[21];
1925
1956
  let t7;
1926
1957
  $[22] !== asset || $[23] !== buildAssetUrl || $[24] !== scheme ? (t7 = assetUtils.isS3ImageAsset(asset) && /* @__PURE__ */ jsxRuntime.jsx(Image$1, { draggable: !1, $scheme: scheme, $showCheckerboard: !0, src: buildAssetUrl({
1927
1958
  assetId: asset._id,
@@ -2054,7 +2085,7 @@ const CardWrapper = styledComponents.styled(ui.Flex)`
2054
2085
  opacity: 0.4
2055
2086
  } }), $[14] = item.assetType, $[15] = item.objectUrl, $[16] = scheme, $[17] = t8) : t8 = $[17];
2056
2087
  let t9;
2057
- $[18] !== item.assetType ? (t9 = item.assetType === assetUtils.S3AssetType.FILE && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2088
+ $[18] !== item.assetType ? (t9 = (item.assetType === assetUtils.S3AssetType.FILE || item.assetType === assetUtils.S3AssetType.VIDEO) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2058
2089
  height: "100%",
2059
2090
  opacity: 0.1
2060
2091
  }, children: /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { width: "80px" }) }), $[18] = item.assetType, $[19] = t9) : t9 = $[19];
@@ -2399,7 +2430,7 @@ const REFERENCE_COUNT_VISIBILITY_DELAY = 750, ContainerGrid = styledComponents.s
2399
2430
  position: "relative"
2400
2431
  }, $[34] = opacityPreview, $[35] = t16) : t16 = $[35];
2401
2432
  let t17;
2402
- $[36] !== asset ? (t17 = assetUtils.isS3FileAsset(asset) && /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { asset, extension: asset.extension, width: "40px" }), $[36] = asset, $[37] = t17) : t17 = $[37];
2433
+ $[36] !== asset ? (t17 = (assetUtils.isS3FileAsset(asset) || assetUtils.isS3VideoAsset(asset)) && /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { asset, extension: asset.extension, width: "40px" }), $[36] = asset, $[37] = t17) : t17 = $[37];
2403
2434
  let t18;
2404
2435
  $[38] !== asset || $[39] !== buildAssetUrl || $[40] !== scheme ? (t18 = assetUtils.isS3ImageAsset(asset) && /* @__PURE__ */ jsxRuntime.jsx(Image$1, { draggable: !1, $scheme: scheme, $showCheckerboard: !0, src: buildAssetUrl({
2405
2436
  assetId: asset._id,
@@ -2459,7 +2490,7 @@ const REFERENCE_COUNT_VISIBILITY_DELAY = 750, ContainerGrid = styledComponents.s
2459
2490
  lineHeight: "2em"
2460
2491
  }, $[69] = t33) : t33 = $[69];
2461
2492
  let t34;
2462
- $[70] !== asset ? (t34 = assetUtils.isS3ImageAsset(asset) && getAssetResolution(asset), $[70] = asset, $[71] = t34) : t34 = $[71];
2493
+ $[70] !== asset ? (t34 = (assetUtils.isS3ImageAsset(asset) || assetUtils.isS3VideoAsset(asset)) && getAssetResolution(asset), $[70] = asset, $[71] = t34) : t34 = $[71];
2463
2494
  let t35;
2464
2495
  $[72] !== t34 ? (t35 = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, size: 1, style: t33, textOverflow: "ellipsis", children: t34 }), $[72] = t34, $[73] = t35) : t35 = $[73];
2465
2496
  let t36;
@@ -2641,7 +2672,7 @@ const UploadDropzone = (props) => {
2641
2672
  directUploads
2642
2673
  } = useS3MediaOptionsContext(), {
2643
2674
  onSelect
2644
- } = useAssetSourceActions(), dispatch = reactRedux.useDispatch(), assetTypes = useTypedSelector(_temp$3), isImageAssetType = assetTypes.length === 1 && assetTypes[0] === assetUtils.S3AssetType.IMAGE;
2675
+ } = useAssetSourceActions(), dispatch = reactRedux.useDispatch(), assetTypes = useTypedSelector(_temp$3), isImageAssetType = assetTypes.length === 1 && assetTypes[0] === assetUtils.S3AssetType.IMAGE, isVideoAssetType = assetTypes.length === 1 && assetTypes[0] === assetUtils.S3AssetType.VIDEO;
2645
2676
  let t0;
2646
2677
  $[0] !== assetTypes[0] || $[1] !== assetTypes.length || $[2] !== dispatch ? (t0 = async (acceptedFiles) => {
2647
2678
  acceptedFiles.forEach((file) => dispatch(uploadsActions.uploadRequest({
@@ -2673,7 +2704,7 @@ const UploadDropzone = (props) => {
2673
2704
  title: "Unable to upload some items (folders and packages aren't supported)"
2674
2705
  })), files;
2675
2706
  }, $[6] = dispatch, $[7] = t2) : t2 = $[7];
2676
- const handleFileGetter = t2, t3 = isImageAssetType ? "image/*" : "", t4 = !!onSelect, t5 = !directUploads;
2707
+ const handleFileGetter = t2, t3 = isImageAssetType ? "image/*" : isVideoAssetType ? "video/*" : "", t4 = !!onSelect, t5 = !directUploads;
2677
2708
  let t6;
2678
2709
  $[8] !== handleDrop || $[9] !== handleDropRejected || $[10] !== handleFileGetter || $[11] !== t3 || $[12] !== t4 || $[13] !== t5 ? (t6 = {
2679
2710
  accept: t3,
@@ -2834,7 +2865,7 @@ const UPLOAD_DROP_DOWN_MENU_POPOVER = {
2834
2865
  readOnly,
2835
2866
  schemaType,
2836
2867
  type
2837
- } = props, [rootElement, setRootElement] = react.useState(null), rect = ui.useElementSize(rootElement), collapsed = rect?.border && rect.border.width < 440, isFileType = type === assetUtils.S3AssetType.FILE, {
2868
+ } = props, [rootElement, setRootElement] = react.useState(null), rect = ui.useElementSize(rootElement), collapsed = rect?.border && rect.border.width < 440, isFileType = type === assetUtils.S3AssetType.FILE, isVideoType = type === assetUtils.S3AssetType.VIDEO, {
2838
2869
  t
2839
2870
  } = sanity.useTranslation();
2840
2871
  let t0;
@@ -2844,7 +2875,7 @@ const UPLOAD_DROP_DOWN_MENU_POPOVER = {
2844
2875
  $[2] !== onUpload ? (t1 = (assetSource, files) => {
2845
2876
  onUpload && onUpload(assetSource, files);
2846
2877
  }, $[2] = onUpload, $[3] = t1) : t1 = $[3];
2847
- const handleSelectFiles = t1, t2 = isFileType ? "" : "image/*";
2878
+ const handleSelectFiles = t1, t2 = isFileType ? "" : isVideoType ? "video/*" : "image/*";
2848
2879
  let t3;
2849
2880
  $[4] !== schemaType || $[5] !== t2 ? (t3 = get__default.default(schemaType, "options.accept", t2), $[4] = schemaType, $[5] = t2, $[6] = t3) : t3 = $[6];
2850
2881
  const accept = t3;
@@ -2884,8 +2915,13 @@ const UPLOAD_DROP_DOWN_MENU_POPOVER = {
2884
2915
  $[24] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t63 = /* @__PURE__ */ jsxRuntime.jsx(icons.AccessDeniedIcon, {}), $[24] = t63) : t63 = $[24], t5 = t63;
2885
2916
  break bb1;
2886
2917
  }
2918
+ if (isVideoType || isFileType) {
2919
+ let t63;
2920
+ $[25] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t63 = /* @__PURE__ */ jsxRuntime.jsx(icons.BinaryDocumentIcon, {}), $[25] = t63) : t63 = $[25], t5 = t63;
2921
+ break bb1;
2922
+ }
2887
2923
  let t62;
2888
- $[25] !== isFileType ? (t62 = isFileType ? /* @__PURE__ */ jsxRuntime.jsx(icons.BinaryDocumentIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.ImageIcon, {}), $[25] = isFileType, $[26] = t62) : t62 = $[26], t5 = t62;
2924
+ $[26] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t62 = /* @__PURE__ */ jsxRuntime.jsx(icons.ImageIcon, {}), $[26] = t62) : t62 = $[26], t5 = t62;
2889
2925
  }
2890
2926
  const messageIcon = t5;
2891
2927
  let t6;
@@ -2898,7 +2934,7 @@ const UPLOAD_DROP_DOWN_MENU_POPOVER = {
2898
2934
  t6 = "Read only";
2899
2935
  break bb2;
2900
2936
  }
2901
- t6 = isFileType ? "Drag or paste file here" : "Drag or paste image here";
2937
+ t6 = isVideoType ? "Drag or paste video here" : isFileType ? "Drag or paste file here" : "Drag or paste image here";
2902
2938
  }
2903
2939
  const messageText = t6;
2904
2940
  let t7;