@maas/payload-plugin-media-cloud 0.0.38 → 0.0.39
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/dist/adapter/handleUpload.mjs +9 -9
- package/dist/adapter/handleUpload.mjs.map +1 -1
- package/dist/adapter/staticHandler.mjs +1 -1
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/adapter/storageAdapter.mjs +1 -1
- package/dist/collectionHooks/afterChange.mjs +14 -3
- package/dist/collectionHooks/afterChange.mjs.map +1 -1
- package/dist/collections/mediaCollection.mjs +2 -3
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/uploadHandler/uploadHandler.mjs +117 -99
- package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -1
- package/dist/components/uploadManager/uploadManager.mjs +2 -2
- package/dist/components/uploadManager/uploadManager.mjs.map +1 -1
- package/dist/endpoints/fileExistsHandler.mjs +3 -3
- package/dist/endpoints/fileExistsHandler.mjs.map +1 -1
- package/dist/endpoints/muxAssetHandler.mjs +2 -2
- package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
- package/dist/endpoints/muxCreateUploadHandler.mjs +2 -2
- package/dist/endpoints/muxCreateUploadHandler.mjs.map +1 -1
- package/dist/fields/mux.mjs +0 -3
- package/dist/fields/mux.mjs.map +1 -1
- package/dist/types/errors.d.mts +8 -0
- package/dist/types/errors.mjs +8 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/types/index.d.mts +5 -1
- package/dist/utils/defaultOptions.mjs +5 -1
- package/dist/utils/defaultOptions.mjs.map +1 -1
- package/dist/utils/file.mjs +1 -1
- package/dist/utils/file.mjs.map +1 -1
- package/dist/utils/mux.d.mts +3 -3
- package/dist/utils/tus.d.mts +3 -3
- package/package.json +6 -5
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
import { MediaCloudErrors } from "../types/errors.mjs";
|
|
2
|
+
import { useErrorHandler } from "../hooks/useErrorHandler.mjs";
|
|
3
|
+
|
|
1
4
|
//#region src/adapter/handleUpload.ts
|
|
2
5
|
/**
|
|
3
6
|
* Creates a handle upload function for processing file uploads to both Mux and S3 storage
|
|
4
7
|
* @returns A HandleUpload function that processes client upload context and updates document data
|
|
5
8
|
*/
|
|
6
9
|
function getHandleUpload() {
|
|
10
|
+
const { throwError } = useErrorHandler();
|
|
7
11
|
return async function({ clientUploadContext, data }) {
|
|
8
|
-
if (!clientUploadContext)
|
|
12
|
+
if (!clientUploadContext) throwError(MediaCloudErrors.UPLOAD_HANDLER_ERROR);
|
|
9
13
|
const ctx = clientUploadContext;
|
|
10
14
|
data.mimeType = ctx.mimeType;
|
|
15
|
+
data.storage = ctx.storage;
|
|
11
16
|
switch (ctx.storage) {
|
|
12
17
|
case "mux":
|
|
13
|
-
data.
|
|
14
|
-
data.mux =
|
|
15
|
-
|
|
16
|
-
status: "preparing"
|
|
17
|
-
};
|
|
18
|
-
break;
|
|
19
|
-
default:
|
|
20
|
-
data.storage = "s3";
|
|
18
|
+
data.mux = data.mux || {};
|
|
19
|
+
data.mux.uploadId = ctx.uploadId;
|
|
20
|
+
data.mux.status = "preparing";
|
|
21
21
|
break;
|
|
22
22
|
}
|
|
23
23
|
return data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleUpload.mjs","names":[],"sources":["../../src/adapter/handleUpload.ts"],"sourcesContent":["import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'\n\ninterface ClientUploadContext {\n storage: 'mux' | 's3'\n uploadId?: string\n mimeType?: string\n}\n\n/**\n * Creates a handle upload function for processing file uploads to both Mux and S3 storage\n * @returns A HandleUpload function that processes client upload context and updates document data\n */\nexport function getHandleUpload(): HandleUpload {\n return async function ({ clientUploadContext, data }) {\n if (!clientUploadContext) {\n
|
|
1
|
+
{"version":3,"file":"handleUpload.mjs","names":[],"sources":["../../src/adapter/handleUpload.ts"],"sourcesContent":["import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\ninterface ClientUploadContext {\n storage: 'mux' | 's3'\n uploadId?: string\n mimeType?: string\n}\n\n/**\n * Creates a handle upload function for processing file uploads to both Mux and S3 storage\n * @returns A HandleUpload function that processes client upload context and updates document data\n */\nexport function getHandleUpload(): HandleUpload {\n const { throwError } = useErrorHandler()\n\n return async function ({ clientUploadContext, data }) {\n if (!clientUploadContext) {\n throwError(MediaCloudErrors.UPLOAD_HANDLER_ERROR)\n }\n\n const ctx = clientUploadContext as ClientUploadContext\n\n data.mimeType = ctx.mimeType\n data.storage = ctx.storage\n\n switch (ctx.storage) {\n case 'mux':\n data.mux = data.mux || {}\n data.mux.uploadId = ctx.uploadId\n data.mux.status = 'preparing'\n break\n }\n\n return data\n }\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,kBAAgC;CAC9C,MAAM,EAAE,eAAe,iBAAiB;AAExC,QAAO,eAAgB,EAAE,qBAAqB,QAAQ;AACpD,MAAI,CAAC,oBACH,YAAW,iBAAiB,qBAAqB;EAGnD,MAAM,MAAM;AAEZ,OAAK,WAAW,IAAI;AACpB,OAAK,UAAU,IAAI;AAEnB,UAAQ,IAAI,SAAZ;GACE,KAAK;AACH,SAAK,MAAM,KAAK,OAAO,EAAE;AACzB,SAAK,IAAI,WAAW,IAAI;AACxB,SAAK,IAAI,SAAS;AAClB;;AAGJ,SAAO"}
|
|
@@ -51,7 +51,7 @@ function createEmptyResponse(args) {
|
|
|
51
51
|
*/
|
|
52
52
|
async function serveS3File({ getS3Store, doc }) {
|
|
53
53
|
try {
|
|
54
|
-
const stream$1 = await getS3Store().read(doc.filename);
|
|
54
|
+
const stream$1 = await getS3Store().read(doc.path ?? doc.filename);
|
|
55
55
|
if (stream$1) {
|
|
56
56
|
const webStream = Readable.toWeb(stream$1);
|
|
57
57
|
return new Response(webStream);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staticHandler.mjs","names":["stream"],"sources":["../../src/adapter/staticHandler.ts"],"sourcesContent":["import { Readable } from 'node:stream'\nimport type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'\nimport type { Document } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface StaticHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\ninterface ServeS3FileArgs {\n getS3Store: () => S3Store\n doc: Document\n}\n\ninterface CreateEmptyResponseArgs {\n mimeType?: string\n}\n\n/**\n * Creates a static handler that serves files from S3 or returns empty responses for Mux assets\n * @param args - The arguments for creating the static handler\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.collection - The name of the media collection in Payload\n * @returns A StaticHandler function that serves files or empty responses based on storage type\n */\nexport function getStaticHandler(args: StaticHandlerArgs): StaticHandler {\n const { getS3Store, collection } = args\n return async (req, { params }) => {\n const { payload } = req\n const filename = params.filename\n\n const { docs } = await payload.find({\n collection,\n where: { filename: { equals: filename } },\n })\n\n const doc = docs[0] as Document\n\n if (!doc) {\n return createEmptyResponse()\n }\n\n if (doc.storage === 'mux') {\n return createEmptyResponse({ mimeType: doc.mimeType })\n }\n\n return await serveS3File({ getS3Store, doc })\n }\n}\n\n/**\n * Creates an empty response with appropriate headers\n * @param args - Optional arguments for the empty response\n * @param args.mimeType - The MIME type to set in the Content-Type header\n * @returns A Response object with empty content and appropriate headers\n */\nfunction createEmptyResponse(args?: CreateEmptyResponseArgs): Response {\n const { mimeType = 'application/octet-stream' } = args ?? {}\n return new Response(new Uint8Array(0), {\n status: 200,\n headers: {\n 'Content-Type': mimeType,\n 'Content-Length': '0',\n },\n })\n}\n\n/**\n * Serves a file from S3 storage\n * @param args - The arguments for serving the S3 file\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.doc - The document containing file metadata\n * @returns A Promise that resolves to a Response object containing the file stream or empty response on error\n */\nasync function serveS3File({\n getS3Store,\n doc,\n}: ServeS3FileArgs): Promise<Response> {\n try {\n const s3Store = getS3Store()\n const stream = await s3Store.read(doc.filename)\n\n if (stream) {\n const webStream = Readable.toWeb(stream) as ReadableStream<Uint8Array>\n return new Response(webStream)\n }\n } catch (_error) {\n // File not found or other error, fall back to empty response\n }\n\n return createEmptyResponse({})\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,iBAAiB,MAAwC;CACvE,MAAM,EAAE,YAAY,eAAe;AACnC,QAAO,OAAO,KAAK,EAAE,aAAa;EAChC,MAAM,EAAE,YAAY;EACpB,MAAM,WAAW,OAAO;EAExB,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;GAClC;GACA,OAAO,EAAE,UAAU,EAAE,QAAQ,UAAU,EAAE;GAC1C,CAAC;EAEF,MAAM,MAAM,KAAK;AAEjB,MAAI,CAAC,IACH,QAAO,qBAAqB;AAG9B,MAAI,IAAI,YAAY,MAClB,QAAO,oBAAoB,EAAE,UAAU,IAAI,UAAU,CAAC;AAGxD,SAAO,MAAM,YAAY;GAAE;GAAY;GAAK,CAAC;;;;;;;;;AAUjD,SAAS,oBAAoB,MAA0C;CACrE,MAAM,EAAE,WAAW,+BAA+B,QAAQ,EAAE;AAC5D,QAAO,IAAI,SAAS,IAAI,WAAW,EAAE,EAAE;EACrC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,kBAAkB;GACnB;EACF,CAAC;;;;;;;;;AAUJ,eAAe,YAAY,EACzB,YACA,OACqC;AACrC,KAAI;EAEF,MAAMA,WAAS,MADC,YAAY,CACC,KAAK,IAAI,SAAS;
|
|
1
|
+
{"version":3,"file":"staticHandler.mjs","names":["stream"],"sources":["../../src/adapter/staticHandler.ts"],"sourcesContent":["import { Readable } from 'node:stream'\nimport type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'\nimport type { Document } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface StaticHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\ninterface ServeS3FileArgs {\n getS3Store: () => S3Store\n doc: Document\n}\n\ninterface CreateEmptyResponseArgs {\n mimeType?: string\n}\n\n/**\n * Creates a static handler that serves files from S3 or returns empty responses for Mux assets\n * @param args - The arguments for creating the static handler\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.collection - The name of the media collection in Payload\n * @returns A StaticHandler function that serves files or empty responses based on storage type\n */\nexport function getStaticHandler(args: StaticHandlerArgs): StaticHandler {\n const { getS3Store, collection } = args\n return async (req, { params }) => {\n const { payload } = req\n const filename = params.filename\n\n const { docs } = await payload.find({\n collection,\n where: { filename: { equals: filename } },\n })\n\n const doc = docs[0] as Document\n\n if (!doc) {\n return createEmptyResponse()\n }\n\n if (doc.storage === 'mux') {\n return createEmptyResponse({ mimeType: doc.mimeType })\n }\n\n return await serveS3File({ getS3Store, doc })\n }\n}\n\n/**\n * Creates an empty response with appropriate headers\n * @param args - Optional arguments for the empty response\n * @param args.mimeType - The MIME type to set in the Content-Type header\n * @returns A Response object with empty content and appropriate headers\n */\nfunction createEmptyResponse(args?: CreateEmptyResponseArgs): Response {\n const { mimeType = 'application/octet-stream' } = args ?? {}\n return new Response(new Uint8Array(0), {\n status: 200,\n headers: {\n 'Content-Type': mimeType,\n 'Content-Length': '0',\n },\n })\n}\n\n/**\n * Serves a file from S3 storage\n * @param args - The arguments for serving the S3 file\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.doc - The document containing file metadata\n * @returns A Promise that resolves to a Response object containing the file stream or empty response on error\n */\nasync function serveS3File({\n getS3Store,\n doc,\n}: ServeS3FileArgs): Promise<Response> {\n try {\n const s3Store = getS3Store()\n const stream = await s3Store.read(doc.path ?? doc.filename)\n\n if (stream) {\n const webStream = Readable.toWeb(stream) as ReadableStream<Uint8Array>\n return new Response(webStream)\n }\n } catch (_error) {\n // File not found or other error, fall back to empty response\n }\n\n return createEmptyResponse({})\n}\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,iBAAiB,MAAwC;CACvE,MAAM,EAAE,YAAY,eAAe;AACnC,QAAO,OAAO,KAAK,EAAE,aAAa;EAChC,MAAM,EAAE,YAAY;EACpB,MAAM,WAAW,OAAO;EAExB,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;GAClC;GACA,OAAO,EAAE,UAAU,EAAE,QAAQ,UAAU,EAAE;GAC1C,CAAC;EAEF,MAAM,MAAM,KAAK;AAEjB,MAAI,CAAC,IACH,QAAO,qBAAqB;AAG9B,MAAI,IAAI,YAAY,MAClB,QAAO,oBAAoB,EAAE,UAAU,IAAI,UAAU,CAAC;AAGxD,SAAO,MAAM,YAAY;GAAE;GAAY;GAAK,CAAC;;;;;;;;;AAUjD,SAAS,oBAAoB,MAA0C;CACrE,MAAM,EAAE,WAAW,+BAA+B,QAAQ,EAAE;AAC5D,QAAO,IAAI,SAAS,IAAI,WAAW,EAAE,EAAE;EACrC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,kBAAkB;GACnB;EACF,CAAC;;;;;;;;;AAUJ,eAAe,YAAY,EACzB,YACA,OACqC;AACrC,KAAI;EAEF,MAAMA,WAAS,MADC,YAAY,CACC,KAAK,IAAI,QAAQ,IAAI,SAAS;AAE3D,MAAIA,UAAQ;GACV,MAAM,YAAY,SAAS,MAAMA,SAAO;AACxC,UAAO,IAAI,SAAS,UAAU;;UAEzB,QAAQ;AAIjB,QAAO,oBAAoB,EAAE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MediaCloudErrors } from "../types/errors.mjs";
|
|
2
|
-
import { getHandleUpload } from "./handleUpload.mjs";
|
|
3
2
|
import { useMagicError } from "../error-handler/dist/index.mjs";
|
|
3
|
+
import { getHandleUpload } from "./handleUpload.mjs";
|
|
4
4
|
import { getHandleDelete } from "./handleDelete.mjs";
|
|
5
5
|
import { getStaticHandler } from "./staticHandler.mjs";
|
|
6
6
|
|
|
@@ -6,6 +6,7 @@ import { s3Store } from "../plugin.mjs";
|
|
|
6
6
|
//#region src/collectionHooks/afterChange.ts
|
|
7
7
|
const afterChangeHook = async ({ collection, doc, previousDoc, req }) => {
|
|
8
8
|
const { throwError } = useErrorHandler();
|
|
9
|
+
if (req.context?._mediaCloudPluginInternal) return doc;
|
|
9
10
|
if (doc.path !== previousDoc?.path) {
|
|
10
11
|
if (doc.storage === "s3" && s3Store) try {
|
|
11
12
|
const oldKey = previousDoc?.path ?? previousDoc?.filename;
|
|
@@ -18,17 +19,27 @@ const afterChangeHook = async ({ collection, doc, previousDoc, req }) => {
|
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
if (!doc.thumbnail || previousDoc?.path !== doc.path) {
|
|
22
|
+
if (!doc.thumbnail || previousDoc?.path !== doc.path) try {
|
|
22
23
|
const thumbnail = buildThumbnailURL({
|
|
23
24
|
storage: doc.storage,
|
|
24
25
|
playbackId: doc.storage === "mux" ? doc.mux.playbackId : void 0,
|
|
25
26
|
s3Key: doc.storage === "s3" ? doc.path ?? doc.filename : void 0,
|
|
26
27
|
s3Store
|
|
27
28
|
});
|
|
28
|
-
req.
|
|
29
|
+
req.context = { ...req.context };
|
|
30
|
+
req.context._mediaCloudPluginInternal = true;
|
|
31
|
+
const update = await req.payload.update({
|
|
29
32
|
collection: collection.slug,
|
|
30
33
|
id: doc.id,
|
|
31
|
-
data: { thumbnail }
|
|
34
|
+
data: { thumbnail },
|
|
35
|
+
req
|
|
36
|
+
});
|
|
37
|
+
delete req.context._mediaCloudPluginInternal;
|
|
38
|
+
return update;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throwError({
|
|
41
|
+
...MediaCloudErrors.THUMBNAIL_GENERATION_ERROR,
|
|
42
|
+
cause: error
|
|
32
43
|
});
|
|
33
44
|
}
|
|
34
45
|
return doc;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"afterChange.mjs","names":["afterChangeHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/afterChange.ts"],"sourcesContent":["import { buildThumbnailURL } from '../utils/buildThumbnailURL'\nimport { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const afterChangeHook: CollectionAfterChangeHook = async ({\n collection,\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Move asset in S3 if path has changed\n if (doc.path !== previousDoc?.path) {\n if (doc.storage === 's3' && s3Store) {\n try {\n const oldKey = previousDoc?.path ?? previousDoc?.filename\n const newKey = doc.path\n\n await s3Store.copy(oldKey, newKey)\n } catch (error) {\n throwError({ ...MediaCloudErrors.S3_MOVE_ERROR, cause: error })\n }\n }\n }\n\n // Handle thumbnail\n if (!doc.thumbnail || previousDoc?.path !== doc.path) {\n const thumbnail = buildThumbnailURL({\n
|
|
1
|
+
{"version":3,"file":"afterChange.mjs","names":["afterChangeHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/afterChange.ts"],"sourcesContent":["import { buildThumbnailURL } from '../utils/buildThumbnailURL'\nimport { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const afterChangeHook: CollectionAfterChangeHook = async ({\n collection,\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?._mediaCloudPluginInternal) {\n return doc\n }\n\n // Move asset in S3 if path has changed\n if (doc.path !== previousDoc?.path) {\n if (doc.storage === 's3' && s3Store) {\n try {\n const oldKey = previousDoc?.path ?? previousDoc?.filename\n const newKey = doc.path\n\n await s3Store.copy(oldKey, newKey)\n } catch (error) {\n throwError({ ...MediaCloudErrors.S3_MOVE_ERROR, cause: error })\n }\n }\n }\n\n // Handle thumbnail\n if (!doc.thumbnail || previousDoc?.path !== doc.path) {\n try {\n const thumbnail = buildThumbnailURL({\n storage: doc.storage,\n playbackId: doc.storage === 'mux' ? doc.mux.playbackId : undefined,\n s3Key: doc.storage === 's3' ? (doc.path ?? doc.filename) : undefined,\n s3Store,\n })\n\n req.context = { ...req.context }\n req.context._mediaCloudPluginInternal = true\n\n const update = await req.payload.update({\n collection: collection.slug,\n id: doc.id,\n data: { thumbnail },\n req,\n })\n\n delete req.context._mediaCloudPluginInternal\n\n return update\n } catch (error) {\n throwError({\n ...MediaCloudErrors.THUMBNAIL_GENERATION_ERROR,\n cause: error,\n })\n }\n }\n\n return doc\n}\n"],"mappings":";;;;;;AAOA,MAAaA,kBAA6C,OAAO,EAC/D,YACA,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,0BACf,QAAO;AAIT,KAAI,IAAI,SAAS,aAAa,MAC5B;MAAI,IAAI,YAAY,QAAQ,QAC1B,KAAI;GACF,MAAM,SAAS,aAAa,QAAQ,aAAa;GACjD,MAAM,SAAS,IAAI;AAEnB,SAAM,QAAQ,KAAK,QAAQ,OAAO;WAC3B,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAe,OAAO;IAAO,CAAC;;;AAMrE,KAAI,CAAC,IAAI,aAAa,aAAa,SAAS,IAAI,KAC9C,KAAI;EACF,MAAM,YAAY,kBAAkB;GAClC,SAAS,IAAI;GACb,YAAY,IAAI,YAAY,QAAQ,IAAI,IAAI,aAAa;GACzD,OAAO,IAAI,YAAY,OAAQ,IAAI,QAAQ,IAAI,WAAY;GAC3D;GACD,CAAC;AAEF,MAAI,UAAU,EAAE,GAAG,IAAI,SAAS;AAChC,MAAI,QAAQ,4BAA4B;EAExC,MAAM,SAAS,MAAM,IAAI,QAAQ,OAAO;GACtC,YAAY,WAAW;GACvB,IAAI,IAAI;GACR,MAAM,EAAE,WAAW;GACnB;GACD,CAAC;AAEF,SAAO,IAAI,QAAQ;AAEnB,SAAO;UACA,OAAO;AACd,aAAW;GACT,GAAG,iBAAiB;GACpB,OAAO;GACR,CAAC;;AAIN,QAAO"}
|
|
@@ -19,7 +19,6 @@ function getMediaCollection(args) {
|
|
|
19
19
|
const { baseCollection, view, folders } = args;
|
|
20
20
|
const config = {
|
|
21
21
|
slug: "media",
|
|
22
|
-
custom: { storage: "test" },
|
|
23
22
|
access: {
|
|
24
23
|
read: () => true,
|
|
25
24
|
delete: () => true
|
|
@@ -49,8 +48,8 @@ function getMediaCollection(args) {
|
|
|
49
48
|
muxField
|
|
50
49
|
],
|
|
51
50
|
hooks: {
|
|
52
|
-
beforeChange:
|
|
53
|
-
afterChange:
|
|
51
|
+
beforeChange: [beforeChangeHook],
|
|
52
|
+
afterChange: [afterChangeHook]
|
|
54
53
|
}
|
|
55
54
|
};
|
|
56
55
|
if (!baseCollection) return config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import { thumbnailField } from '../fields/thumbnail'\nimport { pathField } from '../fields/path'\nimport { filenameField } from '../fields/filename'\nimport { altField } from '../fields/alt'\nimport { widthField } from '../fields/width'\nimport { heightField } from '../fields/height'\nimport { storageField } from '../fields/storage'\nimport { muxField } from '../fields/mux'\n\nimport { beforeChangeHook } from '../collectionHooks/beforeChange'\nimport { afterChangeHook } from '../collectionHooks/afterChange'\n\nimport type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types/index'\n\ninterface GetMediaCollectionArgs {\n view: MediaCloudPluginOptions['view']\n folders: MediaCloudPluginOptions['folders']\n baseCollection?: CollectionConfig\n}\n\n/**\n * Creates a media collection configuration for Payload CMS\n * @param args - Arguments including the S3Store instance\n * @returns A configured Payload collection for media files\n */\nexport function getMediaCollection(\n args: GetMediaCollectionArgs\n): CollectionConfig {\n const { baseCollection, view, folders } = args\n\n // Override list view to use grid view if specified\n const components =\n view === 'grid'\n ? {\n views: {\n list: {\n Component: '@maas/payload-plugin-media-cloud/components#GridView',\n },\n },\n }\n : undefined\n\n
|
|
1
|
+
{"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import { thumbnailField } from '../fields/thumbnail'\nimport { pathField } from '../fields/path'\nimport { filenameField } from '../fields/filename'\nimport { altField } from '../fields/alt'\nimport { widthField } from '../fields/width'\nimport { heightField } from '../fields/height'\nimport { storageField } from '../fields/storage'\nimport { muxField } from '../fields/mux'\n\nimport { beforeChangeHook } from '../collectionHooks/beforeChange'\nimport { afterChangeHook } from '../collectionHooks/afterChange'\n\nimport type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types/index'\n\ninterface GetMediaCollectionArgs {\n view: MediaCloudPluginOptions['view']\n folders: MediaCloudPluginOptions['folders']\n baseCollection?: CollectionConfig\n}\n\n/**\n * Creates a media collection configuration for Payload CMS\n * @param args - Arguments including the S3Store instance\n * @returns A configured Payload collection for media files\n */\nexport function getMediaCollection(\n args: GetMediaCollectionArgs\n): CollectionConfig {\n const { baseCollection, view, folders } = args\n\n // Override list view to use grid view if specified\n const components =\n view === 'grid'\n ? {\n views: {\n list: {\n Component: '@maas/payload-plugin-media-cloud/components#GridView',\n },\n },\n }\n : undefined\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\n components,\n group: 'Media Cloud',\n pagination: {\n defaultLimit: 50,\n },\n },\n upload: {\n crop: false,\n displayPreview: true,\n hideRemoveFile: true,\n adminThumbnail({ doc }: { doc: Document }) {\n return doc.thumbnail ?? null\n },\n },\n fields: [\n thumbnailField,\n pathField,\n altField,\n {\n type: 'row',\n fields: [widthField, heightField],\n },\n storageField,\n muxField,\n ],\n hooks: {\n beforeChange: [beforeChangeHook],\n afterChange: [afterChangeHook],\n },\n }\n\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n slug: baseCollection.slug ?? config.slug,\n admin: {\n ...(config.admin ?? {}),\n ...(baseCollection.admin ?? {}),\n },\n access: {\n ...(config.access ?? {}),\n ...(baseCollection.access ?? {}),\n },\n upload: config.upload,\n fields: [...(baseCollection.fields ?? []), ...config.fields, filenameField],\n hooks: {\n ...baseCollection.hooks,\n beforeChange: [\n ...(baseCollection.hooks?.beforeChange ?? []),\n ...(config.hooks?.beforeChange ?? []),\n ],\n afterChange: [\n ...(baseCollection.hooks?.afterChange ?? []),\n ...(config.hooks?.afterChange ?? []),\n ],\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,gBAAgB,MAAM,YAAY;CAc1C,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,YAjBF,SAAS,SACL,EACE,OAAO,EACL,MAAM,EACJ,WAAW,wDACZ,EACF,EACF,GACD;GAUF,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,WAAO,IAAI,aAAa;;GAE3B;EACD,QAAQ;GACN;GACA;GACA;GACA;IACE,MAAM;IACN,QAAQ,CAAC,YAAY,YAAY;IAClC;GACD;GACA;GACD;EACD,OAAO;GACL,cAAc,CAAC,iBAAiB;GAChC,aAAa,CAAC,gBAAgB;GAC/B;EACF;AAED,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,MAAM,eAAe,QAAQ,OAAO;EACpC,OAAO;GACL,GAAI,OAAO,SAAS,EAAE;GACtB,GAAI,eAAe,SAAS,EAAE;GAC/B;EACD,QAAQ;GACN,GAAI,OAAO,UAAU,EAAE;GACvB,GAAI,eAAe,UAAU,EAAE;GAChC;EACD,QAAQ,OAAO;EACf,QAAQ;GAAC,GAAI,eAAe,UAAU,EAAE;GAAG,GAAG,OAAO;GAAQ;GAAc;EAC3E,OAAO;GACL,GAAG,eAAe;GAClB,cAAc,CACZ,GAAI,eAAe,OAAO,gBAAgB,EAAE,EAC5C,GAAI,OAAO,OAAO,gBAAgB,EAAE,CACrC;GACD,aAAa,CACX,GAAI,eAAe,OAAO,eAAe,EAAE,EAC3C,GAAI,OAAO,OAAO,eAAe,EAAE,CACpC;GACF;EACF"}
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import { MediaCloudErrors, MediaCloudLogs } from "../../types/errors.mjs";
|
|
4
4
|
import { useMagicError } from "../../error-handler/dist/index.mjs";
|
|
5
|
-
import { useErrorHandler } from "../../hooks/useErrorHandler.mjs";
|
|
6
5
|
import { generateUniqueFilename, getMimeType, isVideo, sanitizeFilename } from "../../utils/file.mjs";
|
|
7
6
|
import { useMediaCloudEmitter } from "../../hooks/useMediaCloudEmitter.mjs";
|
|
7
|
+
import PQueue from "p-queue";
|
|
8
8
|
import * as upchunk from "@mux/upchunk";
|
|
9
9
|
import * as tus from "tus-js-client";
|
|
10
10
|
import { createClientUploadHandler } from "@payloadcms/plugin-cloud-storage/client";
|
|
11
11
|
|
|
12
12
|
//#region src/components/uploadHandler/uploadHandler.tsx
|
|
13
|
+
let queue;
|
|
13
14
|
const MUX_CHUNK_SIZE = 30720;
|
|
14
15
|
const TUS_CHUNK_SIZE = 1024 * 1024;
|
|
15
16
|
const TUS_RETRY_DELAYS = [
|
|
@@ -19,7 +20,7 @@ const TUS_RETRY_DELAYS = [
|
|
|
19
20
|
5e3
|
|
20
21
|
];
|
|
21
22
|
const magicError = useMagicError({ prefix: "PLUGIN-MEDIA-CLOUD" });
|
|
22
|
-
const { throwError } = magicError;
|
|
23
|
+
const { throwError, log } = magicError;
|
|
23
24
|
const emitter = useMediaCloudEmitter();
|
|
24
25
|
/**
|
|
25
26
|
* Handles Mux video upload with progress tracking
|
|
@@ -27,7 +28,7 @@ const emitter = useMediaCloudEmitter();
|
|
|
27
28
|
* @returns Promise that resolves to upload result or null if upload fails
|
|
28
29
|
*/
|
|
29
30
|
async function muxUpload(args) {
|
|
30
|
-
const { file, serverURL, apiRoute, mimeType } = args;
|
|
31
|
+
const { file, serverURL, apiRoute, mimeType, onResolve } = args;
|
|
31
32
|
const endpoint = `${serverURL}${apiRoute}`;
|
|
32
33
|
const filename = file.name;
|
|
33
34
|
try {
|
|
@@ -51,32 +52,38 @@ async function muxUpload(args) {
|
|
|
51
52
|
polling: false,
|
|
52
53
|
pollingUrl: `${endpoint}/mux/asset`
|
|
53
54
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
await onResolve({
|
|
56
|
+
storage: "mux",
|
|
57
|
+
filename,
|
|
58
|
+
uploadId,
|
|
59
|
+
mimeType
|
|
57
60
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
uploader.on("error", function(error) {
|
|
63
|
+
emitter.emit("removeUpload", { uploadId });
|
|
64
|
+
throwError({
|
|
65
|
+
...MediaCloudErrors.MUX_UPLOAD_ERROR,
|
|
66
|
+
cause: error
|
|
67
|
+
});
|
|
68
|
+
reject(error);
|
|
69
|
+
});
|
|
70
|
+
uploader.on("progress", function(progress) {
|
|
71
|
+
emitter.emit("updateUpload", {
|
|
72
|
+
filename,
|
|
73
|
+
progress: progress.detail
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
uploader.on("success", function() {
|
|
77
|
+
emitter.emit("uploadComplete", { filename });
|
|
78
|
+
resolve();
|
|
62
79
|
});
|
|
63
80
|
});
|
|
64
|
-
uploader.on("success", function() {
|
|
65
|
-
emitter.emit("uploadComplete", { filename });
|
|
66
|
-
});
|
|
67
|
-
return {
|
|
68
|
-
filename,
|
|
69
|
-
uploadId,
|
|
70
|
-
mimeType,
|
|
71
|
-
storage: "mux"
|
|
72
|
-
};
|
|
73
81
|
} catch {
|
|
74
82
|
throwError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR);
|
|
75
83
|
emitter.emit("uploadError", {
|
|
76
84
|
filename,
|
|
77
85
|
error: "Video upload failed"
|
|
78
86
|
});
|
|
79
|
-
return null;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
89
|
/**
|
|
@@ -85,23 +92,13 @@ async function muxUpload(args) {
|
|
|
85
92
|
* @returns Promise that resolves to upload result or null if upload fails
|
|
86
93
|
*/
|
|
87
94
|
async function tusUpload(args) {
|
|
88
|
-
const { apiRoute, serverURL, file, prefix = "", mimeType } = args;
|
|
95
|
+
const { apiRoute, serverURL, file, prefix = "", mimeType, onResolve } = args;
|
|
89
96
|
const filename = file.name;
|
|
90
97
|
const filetype = file.type;
|
|
91
98
|
const filesize = file.size.toString();
|
|
92
99
|
const endpoint = `${serverURL}${apiRoute}/uploads`;
|
|
93
|
-
Object.keys(localStorage).filter((key) => key.startsWith("tus::")).forEach((key) => localStorage.removeItem(key));
|
|
94
100
|
let uploadUrlAvailable = false;
|
|
95
|
-
|
|
96
|
-
function onBeforeUnload(e) {
|
|
97
|
-
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
98
|
-
e.preventDefault();
|
|
99
|
-
}
|
|
100
|
-
function onPageUnload() {
|
|
101
|
-
window.removeEventListener("unload", onPageUnload);
|
|
102
|
-
navigator.sendBeacon(`${endpoint}/cleanup`, JSON.stringify({ filename }));
|
|
103
|
-
}
|
|
104
|
-
return new Promise((resolve) => {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
105
102
|
new tus.Upload(file, {
|
|
106
103
|
endpoint,
|
|
107
104
|
retryDelays: TUS_RETRY_DELAYS,
|
|
@@ -127,14 +124,7 @@ async function tusUpload(args) {
|
|
|
127
124
|
...MediaCloudErrors.TUS_UPLOAD_ERROR,
|
|
128
125
|
cause: error
|
|
129
126
|
});
|
|
130
|
-
|
|
131
|
-
},
|
|
132
|
-
onBeforeRequest() {
|
|
133
|
-
if (!eventListenerAdded) {
|
|
134
|
-
window.addEventListener("beforeunload", onBeforeUnload);
|
|
135
|
-
window.addEventListener("unload", onPageUnload);
|
|
136
|
-
eventListenerAdded = true;
|
|
137
|
-
}
|
|
127
|
+
reject(error);
|
|
138
128
|
},
|
|
139
129
|
onProgress: function(bytesUploaded, bytesTotal) {
|
|
140
130
|
const percentage = Math.round(bytesUploaded / bytesTotal * 100);
|
|
@@ -144,77 +134,105 @@ async function tusUpload(args) {
|
|
|
144
134
|
});
|
|
145
135
|
},
|
|
146
136
|
async onSuccess() {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
try {
|
|
138
|
+
emitter.emit("uploadComplete", { filename });
|
|
139
|
+
await fetch(`${endpoint}/${filename}/process`);
|
|
140
|
+
await fetch(`${endpoint}/${filename}/folder`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(error);
|
|
143
|
+
} finally {
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
152
146
|
},
|
|
153
147
|
onUploadUrlAvailable: async function() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
148
|
+
try {
|
|
149
|
+
if (uploadUrlAvailable) return;
|
|
150
|
+
uploadUrlAvailable = true;
|
|
151
|
+
emitter.emit("addUpload", { filename });
|
|
152
|
+
onResolve({
|
|
153
|
+
filename,
|
|
154
|
+
mimeType,
|
|
155
|
+
storage: "s3"
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(error);
|
|
159
|
+
}
|
|
162
160
|
}
|
|
163
161
|
}).start();
|
|
164
162
|
});
|
|
165
163
|
}
|
|
166
|
-
const { log } = useErrorHandler();
|
|
167
164
|
function UploadHandler(props) {
|
|
168
165
|
return createClientUploadHandler({ handler: async function(args) {
|
|
169
166
|
const { serverURL, apiRoute, file, prefix, updateFilename, extra } = args;
|
|
170
167
|
const { pluginOptions } = extra;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
mappedFile =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
168
|
+
if (!queue) queue = new PQueue({ concurrency: pluginOptions.limits.concurrency });
|
|
169
|
+
if (queue && queue.concurrency !== pluginOptions.limits.concurrency) queue.concurrency = pluginOptions.limits.concurrency;
|
|
170
|
+
async function upload(onResolve) {
|
|
171
|
+
const mimeType = await getMimeType(file);
|
|
172
|
+
const allowedMimeTypes = pluginOptions.limits?.mimeTypes ?? [];
|
|
173
|
+
magicError.assert(mimeType, MediaCloudErrors.FILE_TYPE_UNKNOWN);
|
|
174
|
+
if (!allowedMimeTypes.some((allowed) => allowed.endsWith("/*") ? mimeType?.startsWith(allowed.replace("/*", "/")) : mimeType === allowed)) {
|
|
175
|
+
throwError(MediaCloudErrors.FILE_TYPE_NOT_ALLOWED);
|
|
176
|
+
onResolve(null);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (file.size > pluginOptions.limits.fileSize) {
|
|
180
|
+
throwError(MediaCloudErrors.FILE_SIZE_EXCEEDED);
|
|
181
|
+
onResolve(null);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let mappedFile = file;
|
|
185
|
+
try {
|
|
186
|
+
const sanitizedFilename = sanitizeFilename(file.name);
|
|
187
|
+
const endpoint = `${serverURL}${apiRoute}/uploads`;
|
|
188
|
+
const response = await fetch(`${endpoint}/${sanitizedFilename}/exists`);
|
|
189
|
+
switch (response?.status) {
|
|
190
|
+
case 200:
|
|
191
|
+
log(MediaCloudLogs.S3_STORE_FILE_FOUND);
|
|
192
|
+
break;
|
|
193
|
+
case 204:
|
|
194
|
+
log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const newFilename = response?.status === 200 ? generateUniqueFilename(sanitizedFilename) : sanitizedFilename;
|
|
198
|
+
mappedFile = new File([file], newFilename, { type: file.type });
|
|
199
|
+
await updateFilename(newFilename);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throwError({
|
|
202
|
+
...MediaCloudErrors.NAMING_FUNCTION_ERROR,
|
|
203
|
+
cause: error
|
|
204
|
+
});
|
|
205
|
+
onResolve(null);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const uploadArgs = {
|
|
210
|
+
file: mappedFile,
|
|
211
|
+
serverURL,
|
|
212
|
+
apiRoute,
|
|
213
|
+
mimeType,
|
|
214
|
+
prefix,
|
|
215
|
+
onResolve
|
|
216
|
+
};
|
|
217
|
+
const storageForMimeType = pluginOptions.storage && Object.prototype.hasOwnProperty.call(pluginOptions.storage, mimeType) ? pluginOptions.storage[mimeType] : void 0;
|
|
218
|
+
const isVideoFile = await isVideo(file);
|
|
219
|
+
const storeOnMux = pluginOptions.storage?.["video/*"] === "mux" || storageForMimeType === "mux";
|
|
220
|
+
switch (true) {
|
|
221
|
+
case storeOnMux && isVideoFile: return await muxUpload(uploadArgs);
|
|
222
|
+
default: return await tusUpload(uploadArgs);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throwError({
|
|
226
|
+
...MediaCloudErrors.UPLOAD_HANDLER_ERROR,
|
|
227
|
+
cause: error
|
|
228
|
+
});
|
|
229
|
+
onResolve(null);
|
|
210
230
|
}
|
|
211
|
-
} catch (error) {
|
|
212
|
-
throwError({
|
|
213
|
-
...MediaCloudErrors.UPLOAD_HANDLER_ERROR,
|
|
214
|
-
cause: error
|
|
215
|
-
});
|
|
216
|
-
return null;
|
|
217
231
|
}
|
|
232
|
+
return new Promise((resolve) => {
|
|
233
|
+
magicError.assert(queue, MediaCloudErrors.UPLOAD_QUEUE_NOT_INITIALIZED);
|
|
234
|
+
queue.add(() => upload(resolve));
|
|
235
|
+
});
|
|
218
236
|
} })(props);
|
|
219
237
|
}
|
|
220
238
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadHandler.mjs","names":["upchunk","tus","createClientUploadHandler","MediaCloudErrors","MediaCloudLogs","useMediaCloudEmitter","useErrorHandler","isVideo","getMimeType","generateUniqueFilename","sanitizeFilename","useMagicError","MUX_CHUNK_SIZE","TUS_CHUNK_SIZE","TUS_RETRY_DELAYS","magicError","prefix","throwError","emitter","muxUpload","args","file","serverURL","apiRoute","mimeType","endpoint","filename","name","response","fetch","body","JSON","stringify","credentials","method","headers","url","uploadId","json","uploader","createUpload","chunkSize","emit","polling","pollingUrl","on","MUX_UPLOAD_ERROR","progress","detail","storage","MUX_DIRECT_UPLOAD_ERROR","error","tusUpload","filetype","type","filesize","size","toString","Object","keys","localStorage","filter","key","startsWith","forEach","removeItem","uploadUrlAvailable","eventListenerAdded","onBeforeUnload","e","window","removeEventListener","preventDefault","onPageUnload","navigator","sendBeacon","Promise","resolve","upload","Upload","retryDelays","storeFingerprintForResuming","metadata","contentType","contentLength","contentDisposition","onError","message","TUS_UPLOAD_ERROR","cause","onBeforeRequest","addEventListener","onProgress","bytesUploaded","bytesTotal","percentage","Math","round","onSuccess","onUploadUrlAvailable","start","log","UploadHandler","props","handler","updateFilename","extra","pluginOptions","assert","FILE_TYPE_UNKNOWN","allowedMimeTypes","limits","mimeTypes","mimeTypeMatches","some","allowed","endsWith","replace","FILE_TYPE_NOT_ALLOWED","fileSize","Infinity","FILE_SIZE_EXCEEDED","mappedFile","status","S3_STORE_FILE_FOUND","newFilename","File","NAMING_FUNCTION_ERROR","uploadArgs","storageForMimeType","prototype","hasOwnProperty","call","undefined","isVideoFile","storeOnMux","UPLOAD_HANDLER_ERROR"],"sources":["../../../src/components/uploadHandler/uploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport {\n isVideo,\n getMimeType,\n generateUniqueFilename,\n sanitizeFilename,\n} from '../../utils/file'\n\nimport type {\n MediaCloudPluginOptions,\n Storage,\n MimeType,\n} from '../../types/index'\nimport type { ReactNode } from 'react'\nimport type { UploadCollectionSlug } from 'payload'\nimport { useMagicError, UseMagicErrorReturn } from '@maas/error-handler'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: MimeType\n prefix?: string\n}\n\ninterface UploadResult {\n storage: Storage\n mimeType: MimeType\n filename: string\n uploadId?: string\n}\n\ninterface MuxCreateUploadResponse {\n url: string\n uploadId: string\n filename: string\n}\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\nconst { throwError } = magicError\nconst emitter = useMediaCloudEmitter()\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { file, serverURL, apiRoute, mimeType } = args\n\n const endpoint = `${serverURL}${apiRoute}`\n const filename = file.name\n\n try {\n // Request upload URL from Mux\n const response = await fetch(`${endpoint}/mux/upload`, {\n body: JSON.stringify({ filename, mimeType }),\n credentials: 'include',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n // Create upchunk uploader\n const uploader = await upchunk.createUpload({\n endpoint: url,\n file,\n chunkSize: MUX_CHUNK_SIZE,\n })\n\n // Add upload to tracker\n emitter.emit('addUpload', {\n filename,\n uploadId,\n polling: false,\n pollingUrl: `${endpoint}/mux/asset`,\n })\n\n // Set up event handlers\n uploader.on('error', function () {\n emitter.emit('removeUpload', { uploadId })\n throwError(MediaCloudErrors.MUX_UPLOAD_ERROR)\n })\n\n uploader.on('progress', function (progress) {\n emitter.emit('updateUpload', {\n filename,\n progress: progress.detail,\n })\n })\n\n uploader.on('success', function () {\n emitter.emit('uploadComplete', { filename })\n })\n\n // Update collection entry\n // with filename, uploadId, mimeType, and storage\n return {\n filename,\n uploadId,\n mimeType,\n storage: 'mux',\n }\n } catch {\n throwError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR)\n emitter.emit('uploadError', { filename, error: 'Video upload failed' })\n return null\n }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { apiRoute, serverURL, file, prefix = '', mimeType } = args\n\n const filename = file.name\n const filetype = file.type\n const filesize = file.size.toString()\n\n const endpoint = `${serverURL}${apiRoute}/uploads`\n\n // Clear any stale TUS uploads from localStorage\n Object.keys(localStorage)\n .filter((key) => key.startsWith('tus::'))\n .forEach((key) => localStorage.removeItem(key))\n\n // Flag to prevent multiple onUploadUrlAvailable calls\n let uploadUrlAvailable = false\n let eventListenerAdded = false\n\n function onBeforeUnload(e: BeforeUnloadEvent) {\n window.removeEventListener('beforeunload', onBeforeUnload)\n e.preventDefault()\n }\n\n function onPageUnload() {\n window.removeEventListener('unload', onPageUnload)\n navigator.sendBeacon(`${endpoint}/cleanup`, JSON.stringify({ filename }))\n }\n\n return new Promise((resolve) => {\n const upload = new tus.Upload(file, {\n endpoint,\n retryDelays: TUS_RETRY_DELAYS,\n chunkSize: TUS_CHUNK_SIZE,\n storeFingerprintForResuming: false,\n metadata: {\n filetype,\n filesize,\n filename,\n prefix,\n contentType: filetype,\n contentLength: filesize,\n contentDisposition: 'inline',\n },\n onError(error) {\n // Clean up\n navigator.sendBeacon(\n `${endpoint}/cleanup`,\n JSON.stringify({ filename })\n )\n\n // Inform user\n emitter.emit('uploadError', { filename, error: error.message })\n emitter.emit('removeUpload', { filename })\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n resolve(null)\n },\n onBeforeRequest() {\n if (!eventListenerAdded) {\n window.addEventListener('beforeunload', onBeforeUnload)\n window.addEventListener('unload', onPageUnload)\n eventListenerAdded = true\n }\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n async onSuccess() {\n // Clean up beforeunload listener\n window.removeEventListener('beforeunload', onBeforeUnload)\n window.removeEventListener('unload', onPageUnload)\n\n // Mark upload as complete in UI\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n await fetch(`${endpoint}/${filename}/process`)\n\n // Move file to correct folder\n await fetch(`${endpoint}/${filename}/folder`)\n },\n onUploadUrlAvailable: async function () {\n // Prevent multiple callbacks\n if (uploadUrlAvailable) {\n return\n }\n\n // Update flag\n uploadUrlAvailable = true\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n // with filename, mimeType, and storage\n resolve({\n filename,\n mimeType,\n storage: 's3',\n })\n },\n })\n\n upload.start()\n })\n}\n\nconst { log } = useErrorHandler()\n\ninterface Extra {\n pluginOptions: MediaCloudPluginOptions\n}\n\ntype ClientUploadHandlerProps<T extends Record<string, unknown>> = {\n children: ReactNode\n collectionSlug: UploadCollectionSlug\n enabled?: boolean\n extra: T\n prefix?: string\n serverHandlerPath: `/${string}`\n}\n\nexport function UploadHandler(\n props: ClientUploadHandlerProps<{ pluginOptions: MediaCloudPluginOptions }>\n) {\n return createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, prefix, updateFilename, extra } = args\n const { pluginOptions } = extra as unknown as Extra\n\n // Check mime type\n const mimeType = await getMimeType(file)\n\n magicError.assert(mimeType, MediaCloudErrors.FILE_TYPE_UNKNOWN)\n\n const allowedMimeTypes = pluginOptions.limits?.mimeTypes ?? []\n\n const mimeTypeMatches = allowedMimeTypes.some((allowed) =>\n allowed.endsWith('/*')\n ? mimeType?.startsWith(allowed.replace('/*', '/'))\n : mimeType === allowed\n )\n\n if (!mimeTypeMatches) {\n throwError(MediaCloudErrors.FILE_TYPE_NOT_ALLOWED)\n return null\n }\n\n // Check file size limit\n if (file.size > (pluginOptions.limits?.fileSize ?? Infinity)) {\n throwError(MediaCloudErrors.FILE_SIZE_EXCEEDED)\n return null\n }\n\n // Clone file\n let mappedFile = file\n\n try {\n // Check if file with same name exists\n const endpoint = `${serverURL}${apiRoute}/uploads`\n const response = await fetch(`${endpoint}/${file.name}/exists`)\n\n if (response?.status === 200) {\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n }\n\n // If file exists, generate a cloned file with a unique filename\n // If not, generate a cloned file with a sanitized filename\n const newFilename =\n response?.status === 200\n ? generateUniqueFilename(file.name)\n : sanitizeFilename(file.name)\n\n mappedFile = new File([file], newFilename, {\n type: file.type,\n })\n\n await updateFilename(newFilename)\n } catch (error) {\n throwError({\n ...MediaCloudErrors.NAMING_FUNCTION_ERROR,\n cause: error,\n })\n return null\n }\n\n try {\n const uploadArgs: UploadArgs = {\n file: mappedFile,\n serverURL,\n apiRoute,\n mimeType,\n prefix,\n }\n\n // Check storage mapping for mime type\n type StorageMap = NonNullable<typeof pluginOptions.storage>\n\n const storageForMimeType =\n pluginOptions.storage &&\n Object.prototype.hasOwnProperty.call(pluginOptions.storage, mimeType)\n ? pluginOptions.storage[mimeType as keyof StorageMap]\n : undefined\n\n const isVideoFile = await isVideo(file)\n const storeOnMux =\n pluginOptions.storage?.['video/*'] === 'mux' ||\n storageForMimeType === 'mux'\n\n switch (true) {\n case storeOnMux && isVideoFile:\n return await muxUpload(uploadArgs)\n default:\n return await tusUpload(uploadArgs)\n }\n } catch (error) {\n throwError({ ...MediaCloudErrors.UPLOAD_HANDLER_ERROR, cause: error })\n return null\n }\n },\n })(props)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"uploadHandler.mjs","names":["PQueue","upchunk","tus","createClientUploadHandler","MediaCloudErrors","MediaCloudLogs","useMediaCloudEmitter","isVideo","getMimeType","generateUniqueFilename","sanitizeFilename","useMagicError","queue","MUX_CHUNK_SIZE","TUS_CHUNK_SIZE","TUS_RETRY_DELAYS","magicError","prefix","throwError","log","emitter","muxUpload","args","file","serverURL","apiRoute","mimeType","onResolve","endpoint","filename","name","response","fetch","body","JSON","stringify","credentials","method","headers","url","uploadId","json","uploader","createUpload","chunkSize","emit","polling","pollingUrl","storage","Promise","resolve","reject","on","error","MUX_UPLOAD_ERROR","cause","progress","detail","MUX_DIRECT_UPLOAD_ERROR","tusUpload","filetype","type","filesize","size","toString","uploadUrlAvailable","upload","Upload","retryDelays","storeFingerprintForResuming","metadata","contentType","contentLength","contentDisposition","onError","navigator","sendBeacon","message","TUS_UPLOAD_ERROR","onProgress","bytesUploaded","bytesTotal","percentage","Math","round","onSuccess","console","onUploadUrlAvailable","start","UploadHandler","props","handler","updateFilename","extra","pluginOptions","concurrency","limits","allowedMimeTypes","mimeTypes","assert","FILE_TYPE_UNKNOWN","mimeTypeMatches","some","allowed","endsWith","startsWith","replace","FILE_TYPE_NOT_ALLOWED","fileSize","FILE_SIZE_EXCEEDED","mappedFile","sanitizedFilename","status","S3_STORE_FILE_FOUND","S3_STORE_FILE_NOT_FOUND","newFilename","File","NAMING_FUNCTION_ERROR","uploadArgs","storageForMimeType","Object","prototype","hasOwnProperty","call","undefined","isVideoFile","storeOnMux","UPLOAD_HANDLER_ERROR","UPLOAD_QUEUE_NOT_INITIALIZED","add"],"sources":["../../../src/components/uploadHandler/uploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport PQueue from 'p-queue'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport {\n isVideo,\n getMimeType,\n generateUniqueFilename,\n sanitizeFilename,\n} from '../../utils/file'\n\nimport type {\n MediaCloudPluginOptions,\n Storage,\n MimeType,\n RequireAllNested,\n} from '../../types/index'\nimport type { ReactNode } from 'react'\nimport type { UploadCollectionSlug } from 'payload'\nimport { useMagicError, UseMagicErrorReturn } from '@maas/error-handler'\n\ninterface UploadResult {\n storage: Storage\n mimeType: MimeType\n filename: string\n uploadId?: string\n}\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: MimeType\n prefix?: string\n onResolve: (result: UploadResult | null) => void\n}\n\ninterface MuxCreateUploadResponse {\n url: string\n uploadId: string\n filename: string\n}\n\nlet queue: PQueue | undefined\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\nconst { throwError, log } = magicError\nconst emitter = useMediaCloudEmitter()\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<void> {\n const { file, serverURL, apiRoute, mimeType, onResolve } = args\n\n const endpoint = `${serverURL}${apiRoute}`\n const filename = file.name\n\n try {\n // Request upload URL from Mux\n const response = await fetch(`${endpoint}/mux/upload`, {\n body: JSON.stringify({ filename, mimeType }),\n credentials: 'include',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n // Create upchunk uploader\n const uploader = await upchunk.createUpload({\n endpoint: url,\n file,\n chunkSize: MUX_CHUNK_SIZE,\n })\n\n // Add upload to tracker\n emitter.emit('addUpload', {\n filename,\n uploadId,\n polling: false,\n pollingUrl: `${endpoint}/mux/asset`,\n })\n\n // Update collection entry\n await onResolve({\n storage: 'mux',\n filename,\n uploadId,\n mimeType,\n })\n\n return new Promise((resolve, reject) => {\n // Set up event handlers\n uploader.on('error', function (error) {\n emitter.emit('removeUpload', { uploadId })\n throwError({ ...MediaCloudErrors.MUX_UPLOAD_ERROR, cause: error })\n reject(error)\n })\n\n uploader.on('progress', function (progress) {\n emitter.emit('updateUpload', {\n filename,\n progress: progress.detail,\n })\n })\n\n uploader.on('success', function () {\n emitter.emit('uploadComplete', { filename })\n resolve()\n })\n })\n } catch {\n throwError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR)\n emitter.emit('uploadError', { filename, error: 'Video upload failed' })\n }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<void> {\n const { apiRoute, serverURL, file, prefix = '', mimeType, onResolve } = args\n\n const filename = file.name\n const filetype = file.type\n const filesize = file.size.toString()\n\n const endpoint = `${serverURL}${apiRoute}/uploads`\n\n // Flag to prevent multiple onUploadUrlAvailable calls\n let uploadUrlAvailable = false\n\n return new Promise((resolve, reject) => {\n const upload = new tus.Upload(file, {\n endpoint,\n retryDelays: TUS_RETRY_DELAYS,\n chunkSize: TUS_CHUNK_SIZE,\n storeFingerprintForResuming: false,\n metadata: {\n filetype,\n filesize,\n filename,\n prefix,\n contentType: filetype,\n contentLength: filesize,\n contentDisposition: 'inline',\n },\n onError(error) {\n // Clean up\n navigator.sendBeacon(\n `${endpoint}/cleanup`,\n JSON.stringify({ filename })\n )\n\n // Inform user\n emitter.emit('uploadError', { filename, error: error.message })\n emitter.emit('removeUpload', { filename })\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n reject(error)\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n async onSuccess() {\n try {\n // Mark upload as complete in UI\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n await fetch(`${endpoint}/${filename}/process`)\n\n // Move file to correct folder\n await fetch(`${endpoint}/${filename}/folder`)\n } catch (error) {\n console.error(error)\n } finally {\n resolve()\n }\n },\n onUploadUrlAvailable: async function () {\n try {\n // Prevent multiple callbacks\n if (uploadUrlAvailable) {\n return\n }\n\n // Update flag\n uploadUrlAvailable = true\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n onResolve({\n filename,\n mimeType,\n storage: 's3',\n })\n } catch (error) {\n // Handled elsewhere\n console.error(error)\n }\n },\n })\n\n upload.start()\n })\n}\n\ninterface Extra {\n pluginOptions: RequireAllNested<MediaCloudPluginOptions>\n}\n\ntype ClientUploadHandlerProps<T extends Record<string, unknown>> = {\n children: ReactNode\n collectionSlug: UploadCollectionSlug\n enabled?: boolean\n extra: T\n prefix?: string\n serverHandlerPath: `/${string}`\n}\n\nexport function UploadHandler(\n props: ClientUploadHandlerProps<{ pluginOptions: MediaCloudPluginOptions }>\n) {\n return createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, prefix, updateFilename, extra } = args\n const { pluginOptions } = extra as unknown as Extra\n\n // Initialize upload queue\n if (!queue) {\n queue = new PQueue({\n concurrency: pluginOptions.limits.concurrency,\n })\n }\n\n // Update concurrency\n if (queue && queue.concurrency !== pluginOptions.limits.concurrency) {\n queue.concurrency = pluginOptions.limits.concurrency\n }\n\n async function upload(onResolve: UploadArgs['onResolve']) {\n // Check mime type\n const mimeType = await getMimeType(file)\n const allowedMimeTypes = pluginOptions.limits?.mimeTypes ?? []\n\n magicError.assert(mimeType, MediaCloudErrors.FILE_TYPE_UNKNOWN)\n\n const mimeTypeMatches = allowedMimeTypes.some((allowed) =>\n allowed.endsWith('/*')\n ? mimeType?.startsWith(allowed.replace('/*', '/'))\n : mimeType === allowed\n )\n\n if (!mimeTypeMatches) {\n throwError(MediaCloudErrors.FILE_TYPE_NOT_ALLOWED)\n onResolve(null)\n return\n }\n\n // Check file size limit\n if (file.size > pluginOptions.limits.fileSize) {\n throwError(MediaCloudErrors.FILE_SIZE_EXCEEDED)\n onResolve(null)\n return\n }\n\n // Clone file\n let mappedFile = file\n\n try {\n // Check if file with same name exists\n const sanitizedFilename = sanitizeFilename(file.name)\n const endpoint = `${serverURL}${apiRoute}/uploads`\n const response = await fetch(\n `${endpoint}/${sanitizedFilename}/exists`\n )\n\n switch (response?.status) {\n case 200:\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n break\n case 204:\n log(MediaCloudLogs.S3_STORE_FILE_NOT_FOUND)\n break\n }\n\n // If file exists, generate a cloned file with a unique filename\n // If not, generate a cloned file with a sanitized filename\n const newFilename =\n response?.status === 200\n ? generateUniqueFilename(sanitizedFilename)\n : sanitizedFilename\n\n mappedFile = new File([file], newFilename, {\n type: file.type,\n })\n\n await updateFilename(newFilename)\n } catch (error) {\n throwError({\n ...MediaCloudErrors.NAMING_FUNCTION_ERROR,\n cause: error,\n })\n onResolve(null)\n return\n }\n\n try {\n const uploadArgs: UploadArgs = {\n file: mappedFile,\n serverURL,\n apiRoute,\n mimeType,\n prefix,\n onResolve,\n }\n\n // Check storage mapping for mime type\n type StorageMap = NonNullable<typeof pluginOptions.storage>\n\n const storageForMimeType =\n pluginOptions.storage &&\n Object.prototype.hasOwnProperty.call(\n pluginOptions.storage,\n mimeType\n )\n ? pluginOptions.storage[mimeType as keyof StorageMap]\n : undefined\n\n const isVideoFile = await isVideo(file)\n const storeOnMux =\n pluginOptions.storage?.['video/*'] === 'mux' ||\n storageForMimeType === 'mux'\n\n switch (true) {\n case storeOnMux && isVideoFile:\n return await muxUpload(uploadArgs)\n default:\n return await tusUpload(uploadArgs)\n }\n } catch (error) {\n throwError({\n ...MediaCloudErrors.UPLOAD_HANDLER_ERROR,\n cause: error,\n })\n\n onResolve(null)\n }\n }\n\n return new Promise((resolve) => {\n magicError.assert(queue, MediaCloudErrors.UPLOAD_QUEUE_NOT_INITIALIZED)\n queue.add(() => upload(resolve))\n })\n },\n })(props)\n}\n"],"mappings":"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"}
|
|
@@ -409,7 +409,7 @@ function _temp0() {
|
|
|
409
409
|
function _temp9(upload_18) {
|
|
410
410
|
return upload_18.polling;
|
|
411
411
|
}
|
|
412
|
-
function _temp8(upload_17) {
|
|
412
|
+
function _temp8(upload_17, index) {
|
|
413
413
|
return /* @__PURE__ */ jsxs("li", {
|
|
414
414
|
"data-status": upload_17.status,
|
|
415
415
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -429,7 +429,7 @@ function _temp8(upload_17) {
|
|
|
429
429
|
className: "upload-progress"
|
|
430
430
|
})
|
|
431
431
|
})]
|
|
432
|
-
}, upload_17.filename);
|
|
432
|
+
}, `${upload_17.filename}-${index}`);
|
|
433
433
|
}
|
|
434
434
|
function _temp7(upload_16) {
|
|
435
435
|
return upload_16.status === "completed";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadManager.mjs","names":["c","_c","createContext","use","useState","useCallback","useEffect","useRef","Button","useMediaCloudEmitter","useErrorHandler","MediaCloudErrors","jsx","_jsx","jsxs","_jsxs","UploadManagerContext","showUploadManager","activeUploads","addUpload","updateUpload","UploadManagerProvider","args","$","children","setShowUploadManager","t0","Symbol","for","setActiveUploads","activeTab","setActiveTab","emitter","activeTabRef","activeUploadsRef","logError","t1","t2","current","t3","t4","t5","uploads","hasActiveUploads","some","_temp","hasProcessingUploads","_temp2","hasCompletedUploads","_temp3","checkAutoSwitchToCompleted","t6","t7","pollingUploads","filter","_temp4","length","pollAssets","pollingUpload","response","fetch","pollingUrl","uploadId","method","credentials","ok","data","json","ready","prev","updatedUploads","map","upload_3","upload","polling","progress","status","setTimeout","t8","UPLOAD_POLLING_ERROR","message","intervalId","setInterval","clearInterval","event","filename","error","TUS_UPLOAD_ERROR","prev_0","upload_4","onUploadError","t9","event_0","upload_5","prev_1","onAddUpload","t10","event_1","filename_0","prev_2","upload_6","undefined","onUpdateUpload","t11","event_2","prev_3","upload_7","onRemoveUpload","t12","event_3","upload_9","find","upload_8","prev_4","upload_10","prev_5","updatedUploads_0","upload_11","onUploadComplete","t13","t14","on","off","t15","args_0","filename_1","t16","polling_0","upload_12","prev_6","args_1","filename_2","progress_0","polling_1","prev_7","upload_13","T0","t17","t18","uploadingFiles","_temp5","processingFiles","_temp6","completedFiles","_temp7","t19","renderUploadList","args_2","uploads_0","_temp8","t20","closeUploadManager","hasPollingUploads","_temp9","t21","value","Provider","className","buttonStyle","icon","margin","onClick","size","_temp0","window","location","reload","upload_18","upload_17","Math","ceil","style","upload_16","upload_15","upload_14","upload_2","upload_1","upload_0","useUploadManagerContext"],"sources":["../../../src/components/uploadManager/uploadManager.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n use,\n useState,\n useCallback,\n useEffect,\n useRef,\n} from 'react'\nimport { Button } from '@payloadcms/ui'\n\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../types/errors'\n\nimport type { MediaCloudEmitterEvents } from '../../types'\nimport type React from 'react'\n\nimport './uploadManager.css'\n\ninterface Upload {\n filename: string\n progress: number\n uploadId?: string\n error?: string\n polling?: boolean\n pollingUrl?: string\n status?: 'uploading' | 'processing' | 'completed'\n}\n\ninterface UploadManagerContextType {\n showUploadManager?: boolean\n activeUploads: Upload[]\n addUpload: (args: MediaCloudEmitterEvents['addUpload']) => void\n updateUpload: (args: MediaCloudEmitterEvents['updateUpload']) => void\n}\n\ninterface UploadManagerProviderArgs {\n children: React.ReactNode\n}\n\ninterface RenderUploadListArgs {\n uploads: Upload[]\n}\n\nconst UploadManagerContext = createContext<UploadManagerContextType>({\n showUploadManager: false,\n activeUploads: [],\n addUpload: () => {},\n updateUpload: () => {},\n})\n\n/**\n * Provider component for upload management context\n * @param args - Arguments including children to wrap\n * @returns JSX element providing upload management context\n */\nexport function UploadManagerProvider(args: UploadManagerProviderArgs) {\n const { children } = args\n const [showUploadManager, setShowUploadManager] = useState(false)\n const [activeUploads, setActiveUploads] = useState<Upload[]>([])\n const [activeTab, setActiveTab] = useState<\n 'uploading' | 'processing' | 'completed'\n >('uploading')\n\n const emitter = useMediaCloudEmitter()\n const activeTabRef = useRef(activeTab)\n const activeUploadsRef = useRef(activeUploads)\n\n const { logError } = useErrorHandler()\n\n // Keep refs in sync with state\n useEffect(() => {\n activeTabRef.current = activeTab\n }, [activeTab])\n\n useEffect(() => {\n activeUploadsRef.current = activeUploads\n }, [activeUploads])\n\n // Helper function to check if we should auto-switch to completed tab\n const checkAutoSwitchToCompleted = useCallback((uploads: Upload[]) => {\n const hasActiveUploads = uploads.some(\n (upload) => upload.status === 'uploading'\n )\n const hasProcessingUploads = uploads.some(\n (upload) => upload.status === 'processing'\n )\n const hasCompletedUploads = uploads.some(\n (upload) => upload.status === 'completed'\n )\n\n // Auto-switch to completed tab if no uploading/processing uploads remain\n if (\n !hasActiveUploads &&\n !hasProcessingUploads &&\n hasCompletedUploads &&\n activeTabRef.current !== 'completed'\n ) {\n setActiveTab('completed')\n }\n }, [])\n\n // Polling logic\n useEffect(() => {\n const pollingUploads = activeUploads.filter(\n (upload) => upload.polling && upload.pollingUrl\n )\n\n if (pollingUploads.length === 0) {\n return\n }\n\n const pollAssets = async () => {\n for (const pollingUpload of pollingUploads) {\n try {\n const response = await fetch(\n `${pollingUpload.pollingUrl}?upload_id=${pollingUpload.uploadId}`,\n {\n method: 'GET',\n credentials: 'include',\n }\n )\n\n if (response.ok) {\n const data = await response.json()\n\n if (data.ready) {\n // Asset is ready, stop polling for this upload\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.uploadId === pollingUpload.uploadId\n ? {\n ...upload,\n polling: false,\n progress: 100,\n status: 'completed' as const,\n }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n }\n } catch (_error) {\n logError(MediaCloudErrors.UPLOAD_POLLING_ERROR.message)\n }\n }\n }\n\n const intervalId = setInterval(pollAssets, 2000) // Poll every 2 seconds\n\n return () => clearInterval(intervalId)\n }, [activeUploads, checkAutoSwitchToCompleted, logError])\n\n /**\n * Handles the 'uploadError' event\n * @param event - The upload error event\n */\n const onUploadError = useCallback(\n (event: MediaCloudEmitterEvents['uploadError']) => {\n const { filename, error } = event\n\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? { ...upload, error: error, status: 'completed' }\n : upload\n )\n )\n },\n [logError]\n )\n\n /**\n * Handles the 'addUpload' event\n * @param event - The add upload event\n */\n const onAddUpload = useCallback(\n (event: MediaCloudEmitterEvents['addUpload']) => {\n const upload: Upload = {\n filename: event.filename,\n uploadId: event.uploadId,\n progress: 0,\n polling: event.polling,\n pollingUrl: event.pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n\n // Show the upload manager when new upload starts\n setShowUploadManager(true)\n\n // Auto-switch to uploading tab when new upload starts\n if (activeTabRef.current !== 'uploading') {\n setActiveTab('uploading')\n }\n },\n []\n )\n\n /**\n * Handles the 'updateUpload' event\n * @param event - The update upload event\n */\n const onUpdateUpload = useCallback(\n (event: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = event\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n /**\n * Handles the 'removeUpload' event\n * @param event - The remove upload event\n */\n const onRemoveUpload = useCallback(\n (event: MediaCloudEmitterEvents['removeUpload']) => {\n setActiveUploads((prev) =>\n prev.filter((upload) => upload.filename !== event.filename)\n )\n },\n []\n )\n\n /**\n * Handles the 'uploadComplete' event\n * @param event - The upload completed event\n */\n const onUploadComplete = useCallback(\n (event: MediaCloudEmitterEvents['uploadComplete']) => {\n // Check if this upload has a polling URL (Mux upload)\n const upload = activeUploadsRef.current.find(\n (upload) => upload.filename === event.filename\n )\n if (upload?.pollingUrl) {\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === event.filename\n ? {\n ...upload,\n polling: true,\n progress: 100,\n status: 'processing' as const,\n }\n : upload\n )\n )\n } else {\n // Regular upload completion\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.filename === event.filename\n ? { ...upload, progress: 100, status: 'completed' as const }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n },\n [checkAutoSwitchToCompleted]\n )\n\n useEffect(() => {\n emitter.on('addUpload', onAddUpload)\n emitter.on('updateUpload', onUpdateUpload)\n emitter.on('removeUpload', onRemoveUpload)\n emitter.on('uploadError', onUploadError)\n emitter.on('uploadComplete', onUploadComplete)\n\n return () => {\n emitter.off('addUpload', onAddUpload)\n emitter.off('updateUpload', onUpdateUpload)\n emitter.off('removeUpload', onRemoveUpload)\n emitter.off('uploadError', onUploadError)\n emitter.off('uploadComplete', onUploadComplete)\n }\n }, [\n emitter,\n onAddUpload,\n onUpdateUpload,\n onRemoveUpload,\n onUploadError,\n onUploadComplete,\n ])\n\n /**\n * Adds a new upload to the manager\n * @param args - The upload arguments\n */\n const addUpload = useCallback(\n (args: MediaCloudEmitterEvents['addUpload']) => {\n const { filename, polling = false, pollingUrl } = args\n const upload: Upload = {\n filename,\n progress: 0,\n polling,\n pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n },\n []\n )\n\n /**\n * Updates an existing upload in the manager\n * @param args - The update arguments\n */\n const updateUpload = useCallback(\n (args: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = args\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n // Filter uploads by status\n const uploadingFiles = activeUploads.filter(\n (upload) => upload.status === 'uploading'\n )\n const processingFiles = activeUploads.filter(\n (upload) => upload.status === 'processing'\n )\n const completedFiles = activeUploads.filter(\n (upload) => upload.status === 'completed'\n )\n\n /**\n * Renders the upload list\n * @param args - The render arguments\n * @returns JSX element representing the upload list\n */\n function renderUploadList(args: RenderUploadListArgs) {\n const { uploads } = args\n return (\n <ul>\n {uploads.map((upload) => (\n <li key={upload.filename} data-status={upload.status}>\n <div className=\"upload-info\">\n <span className=\"upload-filename\">{upload.filename}</span>\n <span className=\"upload-meta\">\n {upload.status === 'processing'\n ? 'Processing...'\n : upload.progress < 100\n ? `${Math.ceil(upload.progress)}%`\n : 'Completed'}\n </span>\n </div>\n <div\n className=\"upload-progress-bar\"\n style={\n {\n ['--progress']:\n upload.status === 'processing'\n ? '1'\n : `${upload.progress / 100}`,\n } as React.CSSProperties\n }\n >\n <div\n data-active={upload.status === 'processing'}\n className=\"upload-progress\"\n />\n </div>\n </li>\n ))}\n </ul>\n )\n }\n\n /**\n * Closes the upload manager\n */\n function closeUploadManager() {\n // Only allow closing if no uploads are actively polling\n const hasPollingUploads = activeUploads.some((upload) => upload.polling)\n if (!hasPollingUploads) {\n setActiveUploads([])\n setShowUploadManager(false)\n }\n }\n\n const value: UploadManagerContextType = {\n activeUploads,\n addUpload,\n updateUpload,\n }\n\n return (\n <UploadManagerContext.Provider value={value}>\n {showUploadManager && (\n <div className=\"upload-manager\">\n <div className=\"upload-manager__header\">\n <h4>Uploads</h4>\n <Button\n buttonStyle=\"icon-label\"\n icon=\"x\"\n margin={false}\n onClick={closeUploadManager}\n />\n </div>\n\n <div className=\"upload-manager__tabs\">\n <button\n data-active={activeTab === 'uploading'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('uploading')}\n >\n Uploading ({uploadingFiles.length})\n </button>\n <button\n data-active={activeTab === 'processing'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('processing')}\n >\n Processing ({processingFiles.length})\n </button>\n <button\n data-active={activeTab === 'completed'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('completed')}\n >\n Completed ({completedFiles.length})\n </button>\n </div>\n\n <div className=\"upload-manager__content\">\n {activeTab === 'uploading' && uploadingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: uploadingFiles })}</div>\n )}\n {activeTab === 'processing' && processingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: processingFiles })}</div>\n )}\n {activeTab === 'completed' && completedFiles.length > 0 && (\n <div>\n {renderUploadList({ uploads: completedFiles })}\n <div className=\"upload-manager__footer\">\n <Button\n buttonStyle=\"subtle\"\n size=\"small\"\n margin={false}\n onClick={() => window?.location?.reload()}\n >\n Refresh\n </Button>\n </div>\n </div>\n )}\n {((activeTab === 'uploading' && uploadingFiles.length === 0) ||\n (activeTab === 'processing' && processingFiles.length === 0) ||\n (activeTab === 'completed' && completedFiles.length === 0)) && (\n <p className=\"upload-empty-state\">No {activeTab} files</p>\n )}\n </div>\n </div>\n )}\n {children}\n </UploadManagerContext.Provider>\n )\n}\n\nexport const useUploadManagerContext = () => use(UploadManagerContext)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"uploadManager.mjs","names":["c","_c","createContext","use","useState","useCallback","useEffect","useRef","Button","useMediaCloudEmitter","useErrorHandler","MediaCloudErrors","jsx","_jsx","jsxs","_jsxs","UploadManagerContext","showUploadManager","activeUploads","addUpload","updateUpload","UploadManagerProvider","args","$","children","setShowUploadManager","t0","Symbol","for","setActiveUploads","activeTab","setActiveTab","emitter","activeTabRef","activeUploadsRef","logError","t1","t2","current","t3","t4","t5","uploads","hasActiveUploads","some","_temp","hasProcessingUploads","_temp2","hasCompletedUploads","_temp3","checkAutoSwitchToCompleted","t6","t7","pollingUploads","filter","_temp4","length","pollAssets","pollingUpload","response","fetch","pollingUrl","uploadId","method","credentials","ok","data","json","ready","prev","updatedUploads","map","upload_3","upload","polling","progress","status","setTimeout","t8","UPLOAD_POLLING_ERROR","message","intervalId","setInterval","clearInterval","event","filename","error","TUS_UPLOAD_ERROR","prev_0","upload_4","onUploadError","t9","event_0","upload_5","prev_1","onAddUpload","t10","event_1","filename_0","prev_2","upload_6","undefined","onUpdateUpload","t11","event_2","prev_3","upload_7","onRemoveUpload","t12","event_3","upload_9","find","upload_8","prev_4","upload_10","prev_5","updatedUploads_0","upload_11","onUploadComplete","t13","t14","on","off","t15","args_0","filename_1","t16","polling_0","upload_12","prev_6","args_1","filename_2","progress_0","polling_1","prev_7","upload_13","T0","t17","t18","uploadingFiles","_temp5","processingFiles","_temp6","completedFiles","_temp7","t19","renderUploadList","args_2","uploads_0","_temp8","t20","closeUploadManager","hasPollingUploads","_temp9","t21","value","Provider","className","buttonStyle","icon","margin","onClick","size","_temp0","window","location","reload","upload_18","upload_17","index","Math","ceil","style","upload_16","upload_15","upload_14","upload_2","upload_1","upload_0","useUploadManagerContext"],"sources":["../../../src/components/uploadManager/uploadManager.tsx"],"sourcesContent":["'use client'\n\nimport {\n createContext,\n use,\n useState,\n useCallback,\n useEffect,\n useRef,\n} from 'react'\nimport { Button } from '@payloadcms/ui'\n\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../types/errors'\n\nimport type { MediaCloudEmitterEvents } from '../../types'\nimport type React from 'react'\n\nimport './uploadManager.css'\n\ninterface Upload {\n filename: string\n progress: number\n uploadId?: string\n error?: string\n polling?: boolean\n pollingUrl?: string\n status?: 'uploading' | 'processing' | 'completed'\n}\n\ninterface UploadManagerContextType {\n showUploadManager?: boolean\n activeUploads: Upload[]\n addUpload: (args: MediaCloudEmitterEvents['addUpload']) => void\n updateUpload: (args: MediaCloudEmitterEvents['updateUpload']) => void\n}\n\ninterface UploadManagerProviderArgs {\n children: React.ReactNode\n}\n\ninterface RenderUploadListArgs {\n uploads: Upload[]\n}\n\nconst UploadManagerContext = createContext<UploadManagerContextType>({\n showUploadManager: false,\n activeUploads: [],\n addUpload: () => {},\n updateUpload: () => {},\n})\n\n/**\n * Provider component for upload management context\n * @param args - Arguments including children to wrap\n * @returns JSX element providing upload management context\n */\nexport function UploadManagerProvider(args: UploadManagerProviderArgs) {\n const { children } = args\n const [showUploadManager, setShowUploadManager] = useState(false)\n const [activeUploads, setActiveUploads] = useState<Upload[]>([])\n const [activeTab, setActiveTab] = useState<\n 'uploading' | 'processing' | 'completed'\n >('uploading')\n\n const emitter = useMediaCloudEmitter()\n const activeTabRef = useRef(activeTab)\n const activeUploadsRef = useRef(activeUploads)\n\n const { logError } = useErrorHandler()\n\n // Keep refs in sync with state\n useEffect(() => {\n activeTabRef.current = activeTab\n }, [activeTab])\n\n useEffect(() => {\n activeUploadsRef.current = activeUploads\n }, [activeUploads])\n\n // Helper function to check if we should auto-switch to completed tab\n const checkAutoSwitchToCompleted = useCallback((uploads: Upload[]) => {\n const hasActiveUploads = uploads.some(\n (upload) => upload.status === 'uploading'\n )\n const hasProcessingUploads = uploads.some(\n (upload) => upload.status === 'processing'\n )\n const hasCompletedUploads = uploads.some(\n (upload) => upload.status === 'completed'\n )\n\n // Auto-switch to completed tab if no uploading/processing uploads remain\n if (\n !hasActiveUploads &&\n !hasProcessingUploads &&\n hasCompletedUploads &&\n activeTabRef.current !== 'completed'\n ) {\n setActiveTab('completed')\n }\n }, [])\n\n // Polling logic\n useEffect(() => {\n const pollingUploads = activeUploads.filter(\n (upload) => upload.polling && upload.pollingUrl\n )\n\n if (pollingUploads.length === 0) {\n return\n }\n\n const pollAssets = async () => {\n for (const pollingUpload of pollingUploads) {\n try {\n const response = await fetch(\n `${pollingUpload.pollingUrl}?upload_id=${pollingUpload.uploadId}`,\n {\n method: 'GET',\n credentials: 'include',\n }\n )\n\n if (response.ok) {\n const data = await response.json()\n\n if (data.ready) {\n // Asset is ready, stop polling for this upload\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.uploadId === pollingUpload.uploadId\n ? {\n ...upload,\n polling: false,\n progress: 100,\n status: 'completed' as const,\n }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n }\n } catch (_error) {\n logError(MediaCloudErrors.UPLOAD_POLLING_ERROR.message)\n }\n }\n }\n\n const intervalId = setInterval(pollAssets, 2000) // Poll every 2 seconds\n\n return () => clearInterval(intervalId)\n }, [activeUploads, checkAutoSwitchToCompleted, logError])\n\n /**\n * Handles the 'uploadError' event\n * @param event - The upload error event\n */\n const onUploadError = useCallback(\n (event: MediaCloudEmitterEvents['uploadError']) => {\n const { filename, error } = event\n\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? { ...upload, error: error, status: 'completed' }\n : upload\n )\n )\n },\n [logError]\n )\n\n /**\n * Handles the 'addUpload' event\n * @param event - The add upload event\n */\n const onAddUpload = useCallback(\n (event: MediaCloudEmitterEvents['addUpload']) => {\n const upload: Upload = {\n filename: event.filename,\n uploadId: event.uploadId,\n progress: 0,\n polling: event.polling,\n pollingUrl: event.pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n\n // Show the upload manager when new upload starts\n setShowUploadManager(true)\n\n // Auto-switch to uploading tab when new upload starts\n if (activeTabRef.current !== 'uploading') {\n setActiveTab('uploading')\n }\n },\n []\n )\n\n /**\n * Handles the 'updateUpload' event\n * @param event - The update upload event\n */\n const onUpdateUpload = useCallback(\n (event: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = event\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n /**\n * Handles the 'removeUpload' event\n * @param event - The remove upload event\n */\n const onRemoveUpload = useCallback(\n (event: MediaCloudEmitterEvents['removeUpload']) => {\n setActiveUploads((prev) =>\n prev.filter((upload) => upload.filename !== event.filename)\n )\n },\n []\n )\n\n /**\n * Handles the 'uploadComplete' event\n * @param event - The upload completed event\n */\n const onUploadComplete = useCallback(\n (event: MediaCloudEmitterEvents['uploadComplete']) => {\n // Check if this upload has a polling URL (Mux upload)\n const upload = activeUploadsRef.current.find(\n (upload) => upload.filename === event.filename\n )\n if (upload?.pollingUrl) {\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === event.filename\n ? {\n ...upload,\n polling: true,\n progress: 100,\n status: 'processing' as const,\n }\n : upload\n )\n )\n } else {\n // Regular upload completion\n setActiveUploads((prev) => {\n const updatedUploads = prev.map((upload) =>\n upload.filename === event.filename\n ? { ...upload, progress: 100, status: 'completed' as const }\n : upload\n )\n\n // Check if we should auto-switch to completed tab\n setTimeout(() => checkAutoSwitchToCompleted(updatedUploads), 0)\n\n return updatedUploads\n })\n }\n },\n [checkAutoSwitchToCompleted]\n )\n\n useEffect(() => {\n emitter.on('addUpload', onAddUpload)\n emitter.on('updateUpload', onUpdateUpload)\n emitter.on('removeUpload', onRemoveUpload)\n emitter.on('uploadError', onUploadError)\n emitter.on('uploadComplete', onUploadComplete)\n\n return () => {\n emitter.off('addUpload', onAddUpload)\n emitter.off('updateUpload', onUpdateUpload)\n emitter.off('removeUpload', onRemoveUpload)\n emitter.off('uploadError', onUploadError)\n emitter.off('uploadComplete', onUploadComplete)\n }\n }, [\n emitter,\n onAddUpload,\n onUpdateUpload,\n onRemoveUpload,\n onUploadError,\n onUploadComplete,\n ])\n\n /**\n * Adds a new upload to the manager\n * @param args - The upload arguments\n */\n const addUpload = useCallback(\n (args: MediaCloudEmitterEvents['addUpload']) => {\n const { filename, polling = false, pollingUrl } = args\n const upload: Upload = {\n filename,\n progress: 0,\n polling,\n pollingUrl,\n status: 'uploading',\n }\n\n setActiveUploads((prev) => [...prev, upload])\n },\n []\n )\n\n /**\n * Updates an existing upload in the manager\n * @param args - The update arguments\n */\n const updateUpload = useCallback(\n (args: MediaCloudEmitterEvents['updateUpload']) => {\n const { filename, progress, polling } = args\n setActiveUploads((prev) =>\n prev.map((upload) =>\n upload.filename === filename\n ? {\n ...upload,\n progress,\n ...(polling !== undefined && { polling }),\n status: polling\n ? 'processing'\n : progress >= 100\n ? 'completed'\n : 'uploading',\n }\n : upload\n )\n )\n },\n []\n )\n\n // Filter uploads by status\n const uploadingFiles = activeUploads.filter(\n (upload) => upload.status === 'uploading'\n )\n const processingFiles = activeUploads.filter(\n (upload) => upload.status === 'processing'\n )\n const completedFiles = activeUploads.filter(\n (upload) => upload.status === 'completed'\n )\n\n /**\n * Renders the upload list\n * @param args - The render arguments\n * @returns JSX element representing the upload list\n */\n function renderUploadList(args: RenderUploadListArgs) {\n const { uploads } = args\n return (\n <ul>\n {uploads.map((upload, index) => (\n <li key={`${upload.filename}-${index}`} data-status={upload.status}>\n <div className=\"upload-info\">\n <span className=\"upload-filename\">{upload.filename}</span>\n <span className=\"upload-meta\">\n {upload.status === 'processing'\n ? 'Processing...'\n : upload.progress < 100\n ? `${Math.ceil(upload.progress)}%`\n : 'Completed'}\n </span>\n </div>\n <div\n className=\"upload-progress-bar\"\n style={\n {\n ['--progress']:\n upload.status === 'processing'\n ? '1'\n : `${upload.progress / 100}`,\n } as React.CSSProperties\n }\n >\n <div\n data-active={upload.status === 'processing'}\n className=\"upload-progress\"\n />\n </div>\n </li>\n ))}\n </ul>\n )\n }\n\n /**\n * Closes the upload manager\n */\n function closeUploadManager() {\n // Only allow closing if no uploads are actively polling\n const hasPollingUploads = activeUploads.some((upload) => upload.polling)\n if (!hasPollingUploads) {\n setActiveUploads([])\n setShowUploadManager(false)\n }\n }\n\n const value: UploadManagerContextType = {\n activeUploads,\n addUpload,\n updateUpload,\n }\n\n return (\n <UploadManagerContext.Provider value={value}>\n {showUploadManager && (\n <div className=\"upload-manager\">\n <div className=\"upload-manager__header\">\n <h4>Uploads</h4>\n <Button\n buttonStyle=\"icon-label\"\n icon=\"x\"\n margin={false}\n onClick={closeUploadManager}\n />\n </div>\n\n <div className=\"upload-manager__tabs\">\n <button\n data-active={activeTab === 'uploading'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('uploading')}\n >\n Uploading ({uploadingFiles.length})\n </button>\n <button\n data-active={activeTab === 'processing'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('processing')}\n >\n Processing ({processingFiles.length})\n </button>\n <button\n data-active={activeTab === 'completed'}\n className=\"upload-tab\"\n onClick={() => setActiveTab('completed')}\n >\n Completed ({completedFiles.length})\n </button>\n </div>\n\n <div className=\"upload-manager__content\">\n {activeTab === 'uploading' && uploadingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: uploadingFiles })}</div>\n )}\n {activeTab === 'processing' && processingFiles.length > 0 && (\n <div>{renderUploadList({ uploads: processingFiles })}</div>\n )}\n {activeTab === 'completed' && completedFiles.length > 0 && (\n <div>\n {renderUploadList({ uploads: completedFiles })}\n <div className=\"upload-manager__footer\">\n <Button\n buttonStyle=\"subtle\"\n size=\"small\"\n margin={false}\n onClick={() => window?.location?.reload()}\n >\n Refresh\n </Button>\n </div>\n </div>\n )}\n {((activeTab === 'uploading' && uploadingFiles.length === 0) ||\n (activeTab === 'processing' && processingFiles.length === 0) ||\n (activeTab === 'completed' && completedFiles.length === 0)) && (\n <p className=\"upload-empty-state\">No {activeTab} files</p>\n )}\n </div>\n </div>\n )}\n {children}\n </UploadManagerContext.Provider>\n )\n}\n\nexport const useUploadManagerContext = () => use(UploadManagerContext)\n"],"mappings":"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"}
|
|
@@ -16,11 +16,11 @@ function getFileExistsHandler(args) {
|
|
|
16
16
|
limit: 1,
|
|
17
17
|
pagination: false
|
|
18
18
|
});
|
|
19
|
-
if (docs.length > 0) return Response.json({ message: "File found [
|
|
19
|
+
if (docs.length > 0) return Response.json({ message: "File found [Payload]" }, { status: 200 });
|
|
20
20
|
const url = getS3Store().getUrl(filename);
|
|
21
21
|
if ((await fetch(url, { method: "HEAD" })).status === 200) return Response.json({ message: "File found [S3]" }, { status: 200 });
|
|
22
|
-
return Response
|
|
23
|
-
} catch (
|
|
22
|
+
return new Response(null, { status: 204 });
|
|
23
|
+
} catch (error) {
|
|
24
24
|
logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message);
|
|
25
25
|
return Response.json({ message: "Failed to process asset" }, { status: 500 });
|
|
26
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n // Check if file exists in Payload database\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n return Response.json(\n { message: 'File found [
|
|
1
|
+
{"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n // Check if file exists in Payload database\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n return Response.json(\n { message: 'File found [Payload]' },\n { status: 200 }\n )\n }\n\n // Check if completed file exists in S3\n const url = getS3Store().getUrl(filename)\n const s3Response = await fetch(url, { method: 'HEAD' })\n\n if (s3Response.status === 200) {\n return Response.json({ message: 'File found [S3]' }, { status: 200 })\n }\n\n return new Response(null, {\n status: 204,\n })\n } catch (error) {\n logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message)\n return Response.json(\n { message: 'Failed to process asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;AAWA,SAAgB,qBACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAIhD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,UACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;AAEF,OAAI,KAAK,SAAS,EAChB,QAAO,SAAS,KACd,EAAE,SAAS,wBAAwB,EACnC,EAAE,QAAQ,KAAK,CAChB;GAIH,MAAM,MAAM,YAAY,CAAC,OAAO,SAAS;AAGzC,QAFmB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,EAExC,WAAW,IACxB,QAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGvE,UAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,KACT,CAAC;WACK,OAAO;AACd,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -3,11 +3,11 @@ function getMuxAssetHandler(args) {
|
|
|
3
3
|
const { getMuxClient, collection } = args;
|
|
4
4
|
return async (req) => {
|
|
5
5
|
try {
|
|
6
|
-
const
|
|
6
|
+
const muxClient = getMuxClient();
|
|
7
7
|
const { query } = req;
|
|
8
8
|
const uploadId = query.upload_id;
|
|
9
9
|
if (!uploadId) return Response.json({ message: "Upload ID is required" }, { status: 400 });
|
|
10
|
-
const asset = (await
|
|
10
|
+
const asset = (await muxClient.video.assets.list({
|
|
11
11
|
limit: 1,
|
|
12
12
|
upload_id: uploadId
|
|
13
13
|
}))?.data[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muxAssetHandler.mjs","names":[],"sources":["../../src/endpoints/muxAssetHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport type { PayloadHandler } from 'payload'\nimport type { StaticRenditions } from '../types'\n\ninterface GetMuxAssetHandlerArgs {\n getMuxClient: () => Mux\n collection: string\n}\n\nexport function getMuxAssetHandler(\n args: GetMuxAssetHandlerArgs\n): PayloadHandler {\n const { getMuxClient, collection } = args\n\n return async (req) => {\n try {\n const
|
|
1
|
+
{"version":3,"file":"muxAssetHandler.mjs","names":[],"sources":["../../src/endpoints/muxAssetHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport type { PayloadHandler } from 'payload'\nimport type { StaticRenditions } from '../types'\n\ninterface GetMuxAssetHandlerArgs {\n getMuxClient: () => Mux\n collection: string\n}\n\nexport function getMuxAssetHandler(\n args: GetMuxAssetHandlerArgs\n): PayloadHandler {\n const { getMuxClient, collection } = args\n\n return async (req) => {\n try {\n const muxClient = getMuxClient()\n const { query } = req\n\n const uploadId = query.upload_id as string\n\n if (!uploadId) {\n return Response.json(\n { message: 'Upload ID is required' },\n { status: 400 }\n )\n }\n\n const assets = await muxClient.video.assets.list({\n limit: 1,\n upload_id: uploadId,\n })\n\n const asset = assets?.data[0]\n\n if (!asset) {\n return Response.json(\n { message: 'No asset found for the given upload ID' },\n { status: 404 }\n )\n }\n\n if (asset.status === 'ready') {\n const { payload } = req\n\n const { docs } = await payload.find({\n collection,\n where: {\n 'mux.uploadId': {\n equals: uploadId,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n const { id } = docs[0]\n\n const width = Array.from(asset.tracks ?? []).reduce(\n (max, track) =>\n track.type === 'video' && track.max_width\n ? Math.max(max, track.max_width)\n : max,\n 0\n )\n\n const height = Array.from(asset.tracks ?? []).reduce(\n (max, track) =>\n track.type === 'video' && track.max_height\n ? Math.max(max, track.max_height)\n : max,\n 0\n )\n\n await payload.update({\n collection,\n id,\n data: {\n mux: {\n status: asset.status,\n assetId: asset.id,\n playbackId: asset.playback_ids?.[0]?.id,\n aspectRatio: asset.aspect_ratio,\n duration: asset.duration,\n tracks: asset.tracks,\n maxResolutionTier: asset.max_resolution_tier,\n videoQuality: asset.video_quality,\n staticRenditions: asset.static_renditions as StaticRenditions,\n },\n width,\n height,\n },\n })\n }\n\n return Response.json(\n {\n ready: asset.status === 'ready',\n asset,\n },\n { status: 200 }\n )\n } else {\n return Response.json(\n {\n ready: false,\n asset,\n },\n { status: 200 }\n )\n }\n } catch (_error) {\n return Response.json(\n { message: 'Failed to fetch Mux asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";AASA,SAAgB,mBACd,MACgB;CAChB,MAAM,EAAE,cAAc,eAAe;AAErC,QAAO,OAAO,QAAQ;AACpB,MAAI;GACF,MAAM,YAAY,cAAc;GAChC,MAAM,EAAE,UAAU;GAElB,MAAM,WAAW,MAAM;AAEvB,OAAI,CAAC,SACH,QAAO,SAAS,KACd,EAAE,SAAS,yBAAyB,EACpC,EAAE,QAAQ,KAAK,CAChB;GAQH,MAAM,SALS,MAAM,UAAU,MAAM,OAAO,KAAK;IAC/C,OAAO;IACP,WAAW;IACZ,CAAC,GAEoB,KAAK;AAE3B,OAAI,CAAC,MACH,QAAO,SAAS,KACd,EAAE,SAAS,0CAA0C,EACrD,EAAE,QAAQ,KAAK,CAChB;AAGH,OAAI,MAAM,WAAW,SAAS;IAC5B,MAAM,EAAE,YAAY;IAEpB,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;KAClC;KACA,OAAO,EACL,gBAAgB,EACd,QAAQ,UACT,EACF;KACD,OAAO;KACP,YAAY;KACb,CAAC;AAEF,QAAI,KAAK,SAAS,GAAG;KACnB,MAAM,EAAE,OAAO,KAAK;KAEpB,MAAM,QAAQ,MAAM,KAAK,MAAM,UAAU,EAAE,CAAC,CAAC,QAC1C,KAAK,UACJ,MAAM,SAAS,WAAW,MAAM,YAC5B,KAAK,IAAI,KAAK,MAAM,UAAU,GAC9B,KACN,EACD;KAED,MAAM,SAAS,MAAM,KAAK,MAAM,UAAU,EAAE,CAAC,CAAC,QAC3C,KAAK,UACJ,MAAM,SAAS,WAAW,MAAM,aAC5B,KAAK,IAAI,KAAK,MAAM,WAAW,GAC/B,KACN,EACD;AAED,WAAM,QAAQ,OAAO;MACnB;MACA;MACA,MAAM;OACJ,KAAK;QACH,QAAQ,MAAM;QACd,SAAS,MAAM;QACf,YAAY,MAAM,eAAe,IAAI;QACrC,aAAa,MAAM;QACnB,UAAU,MAAM;QAChB,QAAQ,MAAM;QACd,mBAAmB,MAAM;QACzB,cAAc,MAAM;QACpB,kBAAkB,MAAM;QACzB;OACD;OACA;OACD;MACF,CAAC;;AAGJ,WAAO,SAAS,KACd;KACE,OAAO,MAAM,WAAW;KACxB;KACD,EACD,EAAE,QAAQ,KAAK,CAChB;SAED,QAAO,SAAS,KACd;IACE,OAAO;IACP;IACD,EACD,EAAE,QAAQ,KAAK,CAChB;WAEI,QAAQ;AACf,UAAO,SAAS,KACd,EAAE,SAAS,6BAA6B,EACxC,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -12,9 +12,9 @@ function getMuxCreateUploadHandler(args) {
|
|
|
12
12
|
throw new Error();
|
|
13
13
|
}
|
|
14
14
|
const { filename } = req.json ? await req.json() : "";
|
|
15
|
-
const
|
|
15
|
+
const muxClient = getMuxClient();
|
|
16
16
|
const assetOptions = pluginOptions.mux?.assetOptions || {};
|
|
17
|
-
const upload = await
|
|
17
|
+
const upload = await muxClient.video.uploads.create({
|
|
18
18
|
cors_origin: "*",
|
|
19
19
|
new_asset_settings: {
|
|
20
20
|
playback_policies: ["public"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"muxCreateUploadHandler.mjs","names":[],"sources":["../../src/endpoints/muxCreateUploadHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\
|
|
1
|
+
{"version":3,"file":"muxCreateUploadHandler.mjs","names":[],"sources":["../../src/endpoints/muxCreateUploadHandler.ts"],"sourcesContent":["import type { Mux } from '@mux/mux-node'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { PayloadHandler } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\n\ninterface GetMuxCreateUploadHandlerArgs {\n getMuxClient: () => Mux\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function getMuxCreateUploadHandler(\n args: GetMuxCreateUploadHandlerArgs\n): PayloadHandler {\n const { getMuxClient, pluginOptions } = args\n\n return async (req) => {\n const { throwError, logError } = useErrorHandler()\n\n try {\n if (!req.json) {\n throwError(MediaCloudErrors.MUX_REQUEST_NO_JSON)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n const body = req.json ? await req.json() : ''\n const { filename } = body\n const muxClient = getMuxClient()\n const assetOptions = pluginOptions.mux?.assetOptions || {}\n const upload = await muxClient.video.uploads.create({\n cors_origin: '*',\n new_asset_settings: {\n playback_policies: ['public'],\n meta: {\n title: filename,\n },\n ...assetOptions,\n },\n test: pluginOptions.mux?.testMode ?? false,\n })\n return Response.json({ url: upload.url, uploadId: upload.id })\n } catch (_error) {\n logError(MediaCloudErrors.MUX_CREATE_UPLOAD_ERROR.message)\n return Response.json(\n { message: 'Failed to create upload' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;AAYA,SAAgB,0BACd,MACgB;CAChB,MAAM,EAAE,cAAc,kBAAkB;AAExC,QAAO,OAAO,QAAQ;EACpB,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,MAAI;AACF,OAAI,CAAC,IAAI,MAAM;AACb,eAAW,iBAAiB,oBAAoB;AAChD,UAAM,IAAI,OAAO;;GAGnB,MAAM,EAAE,aADK,IAAI,OAAO,MAAM,IAAI,MAAM,GAAG;GAE3C,MAAM,YAAY,cAAc;GAChC,MAAM,eAAe,cAAc,KAAK,gBAAgB,EAAE;GAC1D,MAAM,SAAS,MAAM,UAAU,MAAM,QAAQ,OAAO;IAClD,aAAa;IACb,oBAAoB;KAClB,mBAAmB,CAAC,SAAS;KAC7B,MAAM,EACJ,OAAO,UACR;KACD,GAAG;KACJ;IACD,MAAM,cAAc,KAAK,YAAY;IACtC,CAAC;AACF,UAAO,SAAS,KAAK;IAAE,KAAK,OAAO;IAAK,UAAU,OAAO;IAAI,CAAC;WACvD,QAAQ;AACf,YAAS,iBAAiB,wBAAwB,QAAQ;AAC1D,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
|
package/dist/fields/mux.mjs
CHANGED
package/dist/fields/mux.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mux.mjs","names":["muxField: GroupField"],"sources":["../../src/fields/mux.ts"],"sourcesContent":["import type { GroupField } from 'payload'\n\nexport const muxField: GroupField = {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n
|
|
1
|
+
{"version":3,"file":"mux.mjs","names":["muxField: GroupField"],"sources":["../../src/fields/mux.ts"],"sourcesContent":["import type { GroupField } from 'payload'\n\nexport const muxField: GroupField = {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n disableListColumn: true,\n components: {\n Field: '@maas/payload-plugin-media-cloud/components#MuxPreview',\n },\n },\n },\n {\n name: 'status',\n label: 'Status',\n type: 'text',\n },\n {\n name: 'uploadId',\n label: 'Upload ID',\n type: 'text',\n },\n {\n name: 'assetId',\n label: 'Asset ID',\n type: 'text',\n },\n {\n name: 'playbackId',\n label: 'Playback ID',\n type: 'text',\n },\n {\n name: 'aspectRatio',\n label: 'Aspect Ratio',\n type: 'text',\n },\n {\n name: 'duration',\n label: 'Duration',\n type: 'number',\n },\n {\n name: 'tracks',\n label: 'Tracks',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'array',\n items: [\n {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['audio', 'video'],\n },\n id: {\n type: 'string',\n },\n duration: {\n type: 'number',\n },\n primary: {\n type: 'boolean',\n },\n max_channels: {\n type: 'number',\n },\n max_channel_layout: {\n type: 'string',\n },\n max_width: {\n type: 'number',\n },\n max_height: {\n type: 'number',\n },\n max_frame_rate: {\n type: 'number',\n },\n },\n required: [\n 'type',\n 'id',\n 'duration',\n 'primary',\n 'max_channels',\n 'max_channel_layout',\n 'max_width',\n 'max_height',\n 'max_frame_rate',\n ],\n },\n ],\n }),\n ],\n },\n {\n name: 'maxResolutionTier',\n label: 'Max Resolution Tier',\n type: 'text',\n },\n {\n name: 'videoQuality',\n label: 'Video Quality',\n type: 'text',\n },\n {\n name: 'staticRenditions',\n label: 'Static Renditions',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'object',\n properties: {\n files: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n },\n status: {\n type: 'string',\n },\n resolution: {\n type: 'string',\n },\n name: {\n type: 'string',\n },\n id: {\n type: 'string',\n },\n ext: {\n type: 'string',\n },\n width: {\n type: 'number',\n },\n height: {\n type: 'number',\n },\n filesize: {\n type: 'string',\n },\n bitrate: {\n type: 'number',\n },\n },\n },\n required: [\n 'type',\n 'status',\n 'resolution',\n 'name',\n 'id',\n 'ext',\n 'width',\n 'height',\n 'filesize',\n 'bitrate',\n ],\n },\n },\n required: ['files'],\n }),\n ],\n },\n ],\n}\n"],"mappings":";AAEA,MAAaA,WAAuB;CAClC,MAAM;CACN,OAAO;CACP,MAAM;CACN,OAAO;EACL,mBAAmB;EACnB,iBAAiB;EACjB,mBAAmB;EACnB,UAAU;EACV,YAAY,SAAS;AACnB,UAAO,KAAK,YAAY;;EAE3B;CACD,QAAQ;EACN;GACE,MAAM;GACN,MAAM;GACN,OAAO;IACL,mBAAmB;IACnB,YAAY,EACV,OAAO,0DACR;IACF;GACF;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,kBAAkB,EACf,EAAE,kBAAkB;IACnB,GAAG;IACH,MAAM;IACN,OAAO,CACL;KACE,MAAM;KACN,YAAY;MACV,MAAM;OACJ,MAAM;OACN,MAAM,CAAC,SAAS,QAAQ;OACzB;MACD,IAAI,EACF,MAAM,UACP;MACD,UAAU,EACR,MAAM,UACP;MACD,SAAS,EACP,MAAM,WACP;MACD,cAAc,EACZ,MAAM,UACP;MACD,oBAAoB,EAClB,MAAM,UACP;MACD,WAAW,EACT,MAAM,UACP;MACD,YAAY,EACV,MAAM,UACP;MACD,gBAAgB,EACd,MAAM,UACP;MACF;KACD,UAAU;MACR;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF,CACF;IACF,EACF;GACF;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACP;EACD;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,kBAAkB,EACf,EAAE,kBAAkB;IACnB,GAAG;IACH,MAAM;IACN,YAAY,EACV,OAAO;KACL,MAAM;KACN,OAAO;MACL,MAAM;MACN,YAAY;OACV,MAAM,EACJ,MAAM,UACP;OACD,QAAQ,EACN,MAAM,UACP;OACD,YAAY,EACV,MAAM,UACP;OACD,MAAM,EACJ,MAAM,UACP;OACD,IAAI,EACF,MAAM,UACP;OACD,KAAK,EACH,MAAM,UACP;OACD,OAAO,EACL,MAAM,UACP;OACD,QAAQ,EACN,MAAM,UACP;OACD,UAAU,EACR,MAAM,UACP;OACD,SAAS,EACP,MAAM,UACP;OACF;MACF;KACD,UAAU;MACR;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACF,EACF;IACD,UAAU,CAAC,QAAQ;IACpB,EACF;GACF;EACF;CACF"}
|
package/dist/types/errors.d.mts
CHANGED
|
@@ -80,6 +80,10 @@ declare const MediaCloudErrors: {
|
|
|
80
80
|
readonly message: "A collection must be specified in the plugin options";
|
|
81
81
|
readonly errorCode: 400;
|
|
82
82
|
};
|
|
83
|
+
readonly THUMBNAIL_GENERATION_ERROR: {
|
|
84
|
+
readonly message: "Error generating thumbnail URL";
|
|
85
|
+
readonly errorCode: 500;
|
|
86
|
+
};
|
|
83
87
|
readonly FILE_TYPE_UNKNOWN: {
|
|
84
88
|
readonly message: "Unable to determine file type";
|
|
85
89
|
readonly errorCode: 400;
|
|
@@ -128,6 +132,10 @@ declare const MediaCloudErrors: {
|
|
|
128
132
|
readonly message: "Polling error for upload";
|
|
129
133
|
readonly errorCode: 500;
|
|
130
134
|
};
|
|
135
|
+
readonly UPLOAD_QUEUE_NOT_INITIALIZED: {
|
|
136
|
+
readonly message: "Upload queue not initialized";
|
|
137
|
+
readonly errorCode: 500;
|
|
138
|
+
};
|
|
131
139
|
readonly PLUGIN_NOT_CONFIGURED: {
|
|
132
140
|
readonly message: "Payload Media Cloud plugin is not configured";
|
|
133
141
|
readonly errorCode: 500;
|
package/dist/types/errors.mjs
CHANGED
|
@@ -80,6 +80,10 @@ const MediaCloudErrors = {
|
|
|
80
80
|
message: "A collection must be specified in the plugin options",
|
|
81
81
|
errorCode: 400
|
|
82
82
|
},
|
|
83
|
+
THUMBNAIL_GENERATION_ERROR: {
|
|
84
|
+
message: "Error generating thumbnail URL",
|
|
85
|
+
errorCode: 500
|
|
86
|
+
},
|
|
83
87
|
FILE_TYPE_UNKNOWN: {
|
|
84
88
|
message: "Unable to determine file type",
|
|
85
89
|
errorCode: 400
|
|
@@ -128,6 +132,10 @@ const MediaCloudErrors = {
|
|
|
128
132
|
message: "Polling error for upload",
|
|
129
133
|
errorCode: 500
|
|
130
134
|
},
|
|
135
|
+
UPLOAD_QUEUE_NOT_INITIALIZED: {
|
|
136
|
+
message: "Upload queue not initialized",
|
|
137
|
+
errorCode: 500
|
|
138
|
+
},
|
|
131
139
|
PLUGIN_NOT_CONFIGURED: {
|
|
132
140
|
message: "Payload Media Cloud plugin is not configured",
|
|
133
141
|
errorCode: 500
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\n },\n THUMBNAIL_GENERATION_ERROR: {\n message: 'Error generating thumbnail URL',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n UPLOAD_QUEUE_NOT_INITIALIZED: {\n message: 'Upload queue not initialized',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,4BAA4B;EAC1B,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,8BAA8B;EAC5B,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
package/dist/types/index.d.mts
CHANGED
|
@@ -33,6 +33,7 @@ interface MediaCloudPluginOptions {
|
|
|
33
33
|
limits?: {
|
|
34
34
|
mimeTypes?: (MimeType | 'image/*' | 'video/*')[];
|
|
35
35
|
fileSize?: number;
|
|
36
|
+
concurrency?: number;
|
|
36
37
|
};
|
|
37
38
|
folders?: boolean;
|
|
38
39
|
mux?: {
|
|
@@ -63,6 +64,8 @@ interface MediaCloudDefaultPluginOptions {
|
|
|
63
64
|
};
|
|
64
65
|
limits: {
|
|
65
66
|
mimeTypes: (MimeType | 'image/*' | 'video/*')[];
|
|
67
|
+
fileSize: number;
|
|
68
|
+
concurrency: number;
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
type Storage = 'mux' | 's3';
|
|
@@ -137,6 +140,7 @@ interface NodeFSError extends Error {
|
|
|
137
140
|
code?: string;
|
|
138
141
|
}
|
|
139
142
|
type StaticRenditions = Record<string, unknown> | null | undefined;
|
|
143
|
+
type RequireAllNested<T> = { [P in keyof T]-?: RequireAllNested<T[P]> };
|
|
140
144
|
//#endregion
|
|
141
|
-
export { AWSError, ImageMimeType, IncompletePartInfo, MediaCloudDefaultPluginOptions, MediaCloudEmitterEvents, MediaCloudPluginOptions, MimeType, NodeFSError, S3StoreConfig, StaticRenditions, Storage, TusUploadMetadata, VideoMimeType };
|
|
145
|
+
export { AWSError, ImageMimeType, IncompletePartInfo, MediaCloudDefaultPluginOptions, MediaCloudEmitterEvents, MediaCloudPluginOptions, MimeType, NodeFSError, RequireAllNested, S3StoreConfig, StaticRenditions, Storage, TusUploadMetadata, VideoMimeType };
|
|
142
146
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -5,7 +5,11 @@ const defaultOptions = {
|
|
|
5
5
|
view: "grid",
|
|
6
6
|
folders: true,
|
|
7
7
|
storage: { "video/*": "mux" },
|
|
8
|
-
limits: {
|
|
8
|
+
limits: {
|
|
9
|
+
mimeTypes: ["image/*", "video/*"],
|
|
10
|
+
fileSize: Infinity,
|
|
11
|
+
concurrency: 10
|
|
12
|
+
}
|
|
9
13
|
};
|
|
10
14
|
|
|
11
15
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultOptions.mjs","names":["defaultOptions: MediaCloudDefaultPluginOptions"],"sources":["../../src/utils/defaultOptions.ts"],"sourcesContent":["import { MediaCloudDefaultPluginOptions } from '../types'\n\nexport const defaultOptions: MediaCloudDefaultPluginOptions = {\n enabled: true,\n collection: 'media',\n view: 'grid',\n folders: true,\n storage: {\n 'video/*': 'mux',\n },\n limits: {\n mimeTypes: ['image/*', 'video/*'],\n },\n}\n"],"mappings":";AAEA,MAAaA,iBAAiD;CAC5D,SAAS;CACT,YAAY;CACZ,MAAM;CACN,SAAS;CACT,SAAS,EACP,WAAW,OACZ;CACD,QAAQ
|
|
1
|
+
{"version":3,"file":"defaultOptions.mjs","names":["defaultOptions: MediaCloudDefaultPluginOptions"],"sources":["../../src/utils/defaultOptions.ts"],"sourcesContent":["import { MediaCloudDefaultPluginOptions } from '../types'\n\nexport const defaultOptions: MediaCloudDefaultPluginOptions = {\n enabled: true,\n collection: 'media',\n view: 'grid',\n folders: true,\n storage: {\n 'video/*': 'mux',\n },\n limits: {\n mimeTypes: ['image/*', 'video/*'],\n fileSize: Infinity,\n concurrency: 10,\n },\n}\n"],"mappings":";AAEA,MAAaA,iBAAiD;CAC5D,SAAS;CACT,YAAY;CACZ,MAAM;CACN,SAAS;CACT,SAAS,EACP,WAAW,OACZ;CACD,QAAQ;EACN,WAAW,CAAC,WAAW,UAAU;EACjC,UAAU;EACV,aAAa;EACd;CACF"}
|
package/dist/utils/file.mjs
CHANGED
|
@@ -95,7 +95,7 @@ async function getMimeType(file) {
|
|
|
95
95
|
function generateUniqueFilename(originalFilename) {
|
|
96
96
|
const timestamp = Date.now().toString(36);
|
|
97
97
|
const { name, ext } = parse(originalFilename);
|
|
98
|
-
return `${
|
|
98
|
+
return `${name}-${timestamp}${ext}`;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Sanitizes a filename by removing/replacing invalid characters and handling edge cases
|
package/dist/utils/file.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.mjs","names":["magicError: UseMagicErrorReturn"],"sources":["../../src/utils/file.ts"],"sourcesContent":["import { fileTypeFromBuffer } from 'file-type'\nimport { parse } from 'pathe'\n\nimport { getFileExistsHandler } from '../endpoints/fileExistsHandler'\n\nimport { useMagicError, type UseMagicErrorReturn } from '@maas/error-handler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { S3Store } from '../tus/stores/s3/s3Store'\nimport type { MediaCloudPluginOptions, MimeType } from '../types'\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\nconst { logError, throwError } = magicError\n\nconst MUX_SUPPORTED_VIDEO_FORMATS = new Set([\n 'application/mxf',\n 'video/3gpp',\n 'video/3gpp2',\n 'video/mp2t',\n 'video/mp4',\n 'video/mpeg',\n 'video/quicktime',\n 'video/webm',\n 'video/x-f4v',\n 'video/x-flv',\n 'video/x-matroska',\n 'video/x-ms-asf',\n 'video/x-ms-wmv',\n 'video/x-msvideo',\n 'video/x-mxf',\n])\n\n/**\n * Checks if a file is a video file supported by Mux\n * @param file - The file to check\n * @returns Promise that resolves to true if the file is a supported video format, false otherwise\n */\nexport async function isVideo(file: File): Promise<boolean> {\n try {\n const buffer = new Uint8Array(await file.arrayBuffer())\n const fileType = await fileTypeFromBuffer(buffer)\n\n return fileType?.mime\n ? MUX_SUPPORTED_VIDEO_FORMATS.has(fileType.mime)\n : false\n } catch {\n return false\n }\n}\n\nconst allowedMimeTypes = new Set<MimeType>([\n 'application/mxf',\n 'video/3gpp',\n 'video/3gpp2',\n 'video/mp2t',\n 'video/mp4',\n 'video/mpeg',\n 'video/quicktime',\n 'video/webm',\n 'video/x-f4v',\n 'video/x-flv',\n 'video/x-matroska',\n 'video/x-ms-asf',\n 'video/x-ms-wmv',\n 'video/x-msvideo',\n 'video/x-mxf',\n 'image/apng',\n 'image/avif',\n 'image/gif',\n 'image/jpeg',\n 'image/png',\n 'image/svg+xml',\n 'image/webp',\n])\n\n/**\n * Check the mime type against allowed mime types\n * @param mimeType - The file to check\n */\nfunction validateMimeType(mimeType: string) {\n if (!allowedMimeTypes.has(mimeType as MimeType)) {\n throwError(MediaCloudErrors.FILE_TYPE_UNSUPPORTED_ERROR)\n }\n}\n\n/**\n * Gets the MIME type of a file using file-type library\n * @param file - The file to check\n * @returns Promise that resolves to the MIME type of the file or undefined if it cannot be determined\n */\nexport async function getMimeType(file: File): Promise<MimeType | undefined> {\n try {\n const buffer = new Uint8Array(await file.arrayBuffer())\n const fileType = await fileTypeFromBuffer(buffer)\n\n if (!fileType?.mime) {\n logError(MediaCloudErrors.FILE_TYPE_ERROR.message)\n return undefined\n }\n\n validateMimeType(fileType.mime)\n return fileType.mime as MimeType\n } catch (_error) {\n logError(MediaCloudErrors.FILE_TYPE_ERROR.message)\n return undefined\n }\n}\n\n/**\n * Generates a unique filename by appending a random suffix if needed\n * @param originalFilename - The original filename\n * @returns A unique filename with a random suffix appended before the extension\n */\nexport function generateUniqueFilename(originalFilename: string): string {\n const timestamp = Date.now().toString(36)\n // Parse name and extension\n const { name, ext } = parse(originalFilename)\n\n
|
|
1
|
+
{"version":3,"file":"file.mjs","names":["magicError: UseMagicErrorReturn"],"sources":["../../src/utils/file.ts"],"sourcesContent":["import { fileTypeFromBuffer } from 'file-type'\nimport { parse } from 'pathe'\n\nimport { getFileExistsHandler } from '../endpoints/fileExistsHandler'\n\nimport { useMagicError, type UseMagicErrorReturn } from '@maas/error-handler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { S3Store } from '../tus/stores/s3/s3Store'\nimport type { MediaCloudPluginOptions, MimeType } from '../types'\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\nconst { logError, throwError } = magicError\n\nconst MUX_SUPPORTED_VIDEO_FORMATS = new Set([\n 'application/mxf',\n 'video/3gpp',\n 'video/3gpp2',\n 'video/mp2t',\n 'video/mp4',\n 'video/mpeg',\n 'video/quicktime',\n 'video/webm',\n 'video/x-f4v',\n 'video/x-flv',\n 'video/x-matroska',\n 'video/x-ms-asf',\n 'video/x-ms-wmv',\n 'video/x-msvideo',\n 'video/x-mxf',\n])\n\n/**\n * Checks if a file is a video file supported by Mux\n * @param file - The file to check\n * @returns Promise that resolves to true if the file is a supported video format, false otherwise\n */\nexport async function isVideo(file: File): Promise<boolean> {\n try {\n const buffer = new Uint8Array(await file.arrayBuffer())\n const fileType = await fileTypeFromBuffer(buffer)\n\n return fileType?.mime\n ? MUX_SUPPORTED_VIDEO_FORMATS.has(fileType.mime)\n : false\n } catch {\n return false\n }\n}\n\nconst allowedMimeTypes = new Set<MimeType>([\n 'application/mxf',\n 'video/3gpp',\n 'video/3gpp2',\n 'video/mp2t',\n 'video/mp4',\n 'video/mpeg',\n 'video/quicktime',\n 'video/webm',\n 'video/x-f4v',\n 'video/x-flv',\n 'video/x-matroska',\n 'video/x-ms-asf',\n 'video/x-ms-wmv',\n 'video/x-msvideo',\n 'video/x-mxf',\n 'image/apng',\n 'image/avif',\n 'image/gif',\n 'image/jpeg',\n 'image/png',\n 'image/svg+xml',\n 'image/webp',\n])\n\n/**\n * Check the mime type against allowed mime types\n * @param mimeType - The file to check\n */\nfunction validateMimeType(mimeType: string) {\n if (!allowedMimeTypes.has(mimeType as MimeType)) {\n throwError(MediaCloudErrors.FILE_TYPE_UNSUPPORTED_ERROR)\n }\n}\n\n/**\n * Gets the MIME type of a file using file-type library\n * @param file - The file to check\n * @returns Promise that resolves to the MIME type of the file or undefined if it cannot be determined\n */\nexport async function getMimeType(file: File): Promise<MimeType | undefined> {\n try {\n const buffer = new Uint8Array(await file.arrayBuffer())\n const fileType = await fileTypeFromBuffer(buffer)\n\n if (!fileType?.mime) {\n logError(MediaCloudErrors.FILE_TYPE_ERROR.message)\n return undefined\n }\n\n validateMimeType(fileType.mime)\n return fileType.mime as MimeType\n } catch (_error) {\n logError(MediaCloudErrors.FILE_TYPE_ERROR.message)\n return undefined\n }\n}\n\n/**\n * Generates a unique filename by appending a random suffix if needed\n * @param originalFilename - The original filename\n * @returns A unique filename with a random suffix appended before the extension\n */\nexport function generateUniqueFilename(originalFilename: string): string {\n const timestamp = Date.now().toString(36)\n // Parse name and extension\n const { name, ext } = parse(originalFilename)\n\n return `${name}-${timestamp}${ext}`\n}\n\n/**\n * Sanitizes a filename by removing/replacing invalid characters and handling edge cases\n * Converts all non-alphanumeric characters (except dots for extensions) to underscores\n * @param filename - The filename to sanitize\n * @returns A sanitized filename safe for all operating systems\n * @throws {TypeError} When filename is not a string\n */\nexport function sanitizeFilename(filename: string): string {\n try {\n const parsed = parse(filename)\n const sanitized = parsed.name\n .replace(/[^a-zA-Z0-9_.-]/g, '_')\n .replace(/_{2,}/g, '_')\n .replace(/^_|_$/g, '')\n return `${sanitized}${parsed.ext}`\n } catch (_error) {\n logError(MediaCloudErrors.FILENAME_SANITIZE_ERROR.message)\n return filename\n }\n}\n\ninterface CreateFileEndpointsArgs {\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createFileEndpoints(args: CreateFileEndpointsArgs) {\n const { getS3Store, pluginOptions } = args\n\n const collection = pluginOptions.collection\n magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED)\n\n return [\n {\n handler: getFileExistsHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/exists',\n },\n ]\n}\n"],"mappings":";;;;;;;AAWA,MAAMA,aAAkC,cAAc,EACpD,QAAQ,sBACT,CAAC;AAEF,MAAM,EAAE,UAAU,eAAe;AAEjC,MAAM,8BAA8B,IAAI,IAAI;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,eAAsB,QAAQ,MAA8B;AAC1D,KAAI;EAEF,MAAM,WAAW,MAAM,mBADR,IAAI,WAAW,MAAM,KAAK,aAAa,CAAC,CACN;AAEjD,SAAO,UAAU,OACb,4BAA4B,IAAI,SAAS,KAAK,GAC9C;SACE;AACN,SAAO;;;AAIX,MAAM,mBAAmB,IAAI,IAAc;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;AAMF,SAAS,iBAAiB,UAAkB;AAC1C,KAAI,CAAC,iBAAiB,IAAI,SAAqB,CAC7C,YAAW,iBAAiB,4BAA4B;;;;;;;AAS5D,eAAsB,YAAY,MAA2C;AAC3E,KAAI;EAEF,MAAM,WAAW,MAAM,mBADR,IAAI,WAAW,MAAM,KAAK,aAAa,CAAC,CACN;AAEjD,MAAI,CAAC,UAAU,MAAM;AACnB,YAAS,iBAAiB,gBAAgB,QAAQ;AAClD;;AAGF,mBAAiB,SAAS,KAAK;AAC/B,SAAO,SAAS;UACT,QAAQ;AACf,WAAS,iBAAiB,gBAAgB,QAAQ;AAClD;;;;;;;;AASJ,SAAgB,uBAAuB,kBAAkC;CACvE,MAAM,YAAY,KAAK,KAAK,CAAC,SAAS,GAAG;CAEzC,MAAM,EAAE,MAAM,QAAQ,MAAM,iBAAiB;AAE7C,QAAO,GAAG,KAAK,GAAG,YAAY;;;;;;;;;AAUhC,SAAgB,iBAAiB,UAA0B;AACzD,KAAI;EACF,MAAM,SAAS,MAAM,SAAS;AAK9B,SAAO,GAJW,OAAO,KACtB,QAAQ,oBAAoB,IAAI,CAChC,QAAQ,UAAU,IAAI,CACtB,QAAQ,UAAU,GAAG,GACF,OAAO;UACtB,QAAQ;AACf,WAAS,iBAAiB,wBAAwB,QAAQ;AAC1D,SAAO;;;AASX,SAAgB,oBAAoB,MAA+B;CACjE,MAAM,EAAE,YAAY,kBAAkB;CAEtC,MAAM,aAAa,cAAc;AACjC,YAAW,OAAO,YAAY,iBAAiB,oBAAoB;AAEnE,QAAO,CACL;EACE,SAAS,qBAAqB;GAAE;GAAY;GAAY,CAAC;EACzD,QAAQ;EACR,MAAM;EACP,CACF"}
|
package/dist/utils/mux.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import Mux from "@mux/mux-node";
|
|
3
|
-
import * as
|
|
3
|
+
import * as payload0 from "payload";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/mux.d.ts
|
|
6
6
|
|
|
@@ -14,11 +14,11 @@ interface CreateMuxEndpointsArgs {
|
|
|
14
14
|
pluginOptions: MediaCloudPluginOptions;
|
|
15
15
|
}
|
|
16
16
|
declare function createMuxEndpoints(args: CreateMuxEndpointsArgs): ({
|
|
17
|
-
handler:
|
|
17
|
+
handler: payload0.PayloadHandler;
|
|
18
18
|
method: "post";
|
|
19
19
|
path: string;
|
|
20
20
|
} | {
|
|
21
|
-
handler:
|
|
21
|
+
handler: payload0.PayloadHandler;
|
|
22
22
|
method: "get";
|
|
23
23
|
path: string;
|
|
24
24
|
})[];
|
package/dist/utils/tus.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import { S3Store } from "../tus/stores/s3/s3Store.mjs";
|
|
3
3
|
import { Server } from "@tus/server";
|
|
4
|
-
import * as
|
|
4
|
+
import * as payload2 from "payload";
|
|
5
5
|
import { PayloadRequest } from "payload";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.d.ts
|
|
@@ -45,11 +45,11 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
45
45
|
method: "delete";
|
|
46
46
|
path: string;
|
|
47
47
|
} | {
|
|
48
|
-
handler:
|
|
48
|
+
handler: payload2.PayloadHandler;
|
|
49
49
|
method: "get";
|
|
50
50
|
path: string;
|
|
51
51
|
} | {
|
|
52
|
-
handler:
|
|
52
|
+
handler: payload2.PayloadHandler;
|
|
53
53
|
method: "post";
|
|
54
54
|
path: string;
|
|
55
55
|
})[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maas/payload-plugin-media-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"contributors": [
|
|
6
6
|
{
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"image-size": "^2.0.2",
|
|
47
47
|
"mitt": "^3.0.1",
|
|
48
48
|
"multistream": "^4.1.0",
|
|
49
|
+
"p-queue": "^9.1.0",
|
|
49
50
|
"pathe": "^2.0.3",
|
|
50
51
|
"tus-js-client": "^4.3.1"
|
|
51
52
|
},
|
|
@@ -71,9 +72,9 @@
|
|
|
71
72
|
"@babel/preset-react": "^7.28.5",
|
|
72
73
|
"@babel/preset-typescript": "^7.28.5",
|
|
73
74
|
"@mux/mux-player-react": "^3.9.2",
|
|
74
|
-
"@payloadcms/plugin-cloud-storage": "3.
|
|
75
|
-
"@payloadcms/translations": "3.
|
|
76
|
-
"@payloadcms/ui": "3.
|
|
75
|
+
"@payloadcms/plugin-cloud-storage": "3.77.0",
|
|
76
|
+
"@payloadcms/translations": "3.77.0",
|
|
77
|
+
"@payloadcms/ui": "3.77.0",
|
|
77
78
|
"@rollup/plugin-babel": "^6.1.0",
|
|
78
79
|
"@types/multistream": "^4.1.3",
|
|
79
80
|
"@types/node": "^22.19.1",
|
|
@@ -82,7 +83,7 @@
|
|
|
82
83
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
83
84
|
"glob": "^13.0.0",
|
|
84
85
|
"next": "15.4.8",
|
|
85
|
-
"payload": "3.
|
|
86
|
+
"payload": "3.77.0",
|
|
86
87
|
"react": "19.2.1",
|
|
87
88
|
"react-dom": "19.2.1",
|
|
88
89
|
"tsdown": "^0.16.8",
|