@maas/payload-plugin-media-cloud 0.0.30 → 0.0.32
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/handleDelete.d.mts +3 -3
- package/dist/adapter/handleDelete.mjs +6 -6
- package/dist/adapter/handleDelete.mjs.map +1 -1
- package/dist/adapter/staticHandler.d.mts +5 -3
- package/dist/adapter/staticHandler.mjs +8 -7
- package/dist/adapter/staticHandler.mjs.map +1 -1
- package/dist/adapter/storageAdapter.d.mts +3 -3
- package/dist/adapter/storageAdapter.mjs +4 -4
- package/dist/adapter/storageAdapter.mjs.map +1 -1
- package/dist/collectionHooks/afterChange.d.mts +7 -0
- package/dist/collectionHooks/afterChange.mjs +39 -0
- package/dist/collectionHooks/afterChange.mjs.map +1 -0
- package/dist/collectionHooks/beforeChange.d.mts +7 -0
- package/dist/collectionHooks/beforeChange.mjs +33 -0
- package/dist/collectionHooks/beforeChange.mjs.map +1 -0
- package/dist/collections/mediaCollection.d.mts +3 -2
- package/dist/collections/mediaCollection.mjs +46 -198
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/folderFileCard/folderFileCard.d.mts +13 -0
- package/dist/components/folderFileCard/folderFileCard.mjs +30 -0
- package/dist/components/folderFileCard/folderFileCard.mjs.map +1 -0
- package/dist/components/gridContext/gridContext.d.mts +51 -0
- package/dist/components/gridContext/gridContext.mjs +227 -0
- package/dist/components/gridContext/gridContext.mjs.map +1 -0
- package/dist/components/gridView/gridView.css +50 -0
- package/dist/components/gridView/gridView.d.mts +16 -0
- package/dist/components/gridView/gridView.mjs +124 -0
- package/dist/components/gridView/gridView.mjs.map +1 -0
- package/dist/components/gridView/index.d.mts +2 -0
- package/dist/components/gridView/index.mjs +3 -0
- package/dist/components/index.d.mts +9 -7
- package/dist/components/index.mjs +5 -4
- package/dist/components/itemCardGrid/itemCardGrid.css +12 -0
- package/dist/components/itemCardGrid/itemCardGrid.d.mts +18 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs +22 -0
- package/dist/components/itemCardGrid/itemCardGrid.mjs.map +1 -0
- package/dist/components/muxPreview/index.d.mts +2 -0
- package/dist/components/muxPreview/index.mjs +3 -0
- package/dist/components/{mux-preview/mux-preview.d.mts → muxPreview/muxPreview.d.mts} +2 -2
- package/dist/components/{mux-preview/mux-preview.mjs → muxPreview/muxPreview.mjs} +2 -2
- package/dist/components/muxPreview/muxPreview.mjs.map +1 -0
- package/dist/components/uploadHandler/index.d.mts +2 -0
- package/dist/components/uploadHandler/index.mjs +3 -0
- package/dist/components/uploadHandler/uploadHandler.d.mts +20 -0
- package/dist/components/{upload-handler/upload-handler.mjs → uploadHandler/uploadHandler.mjs} +82 -52
- package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -0
- package/dist/components/uploadManager/index.d.mts +2 -0
- package/dist/components/uploadManager/index.mjs +3 -0
- package/dist/components/{upload-manager/upload-manager.d.mts → uploadManager/uploadManager.d.mts} +3 -3
- package/dist/components/{upload-manager/upload-manager.mjs → uploadManager/uploadManager.mjs} +3 -3
- package/dist/components/uploadManager/uploadManager.mjs.map +1 -0
- package/dist/endpoints/fileExistsHandler.d.mts +12 -0
- package/dist/endpoints/{tusFileExistsHandler.mjs → fileExistsHandler.mjs} +7 -7
- package/dist/endpoints/fileExistsHandler.mjs.map +1 -0
- package/dist/endpoints/muxAssetHandler.d.mts +1 -0
- package/dist/endpoints/muxAssetHandler.mjs +3 -3
- package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
- package/dist/endpoints/muxWebhookHandler.d.mts +1 -0
- package/dist/endpoints/muxWebhookHandler.mjs +6 -5
- package/dist/endpoints/muxWebhookHandler.mjs.map +1 -1
- package/dist/endpoints/tusCleanupHandler.d.mts +3 -2
- package/dist/endpoints/tusCleanupHandler.mjs +4 -4
- package/dist/endpoints/tusCleanupHandler.mjs.map +1 -1
- package/dist/endpoints/tusFolderHandler.d.mts +12 -0
- package/dist/endpoints/tusFolderHandler.mjs +39 -0
- package/dist/endpoints/tusFolderHandler.mjs.map +1 -0
- package/dist/endpoints/tusPostProcessorHandler.d.mts +3 -2
- package/dist/endpoints/tusPostProcessorHandler.mjs +6 -5
- package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -1
- package/dist/fields/alt.d.mts +7 -0
- package/dist/fields/alt.mjs +10 -0
- package/dist/fields/alt.mjs.map +1 -0
- package/dist/fields/filename.d.mts +7 -0
- package/dist/fields/filename.mjs +14 -0
- package/dist/fields/filename.mjs.map +1 -0
- package/dist/fields/height.d.mts +7 -0
- package/dist/fields/height.mjs +15 -0
- package/dist/fields/height.mjs.map +1 -0
- package/dist/fields/mux.d.mts +7 -0
- package/dist/fields/mux.mjs +149 -0
- package/dist/fields/mux.mjs.map +1 -0
- package/dist/fields/path.d.mts +7 -0
- package/dist/fields/path.mjs +14 -0
- package/dist/fields/path.mjs.map +1 -0
- package/dist/fields/storage.d.mts +7 -0
- package/dist/fields/storage.mjs +18 -0
- package/dist/fields/storage.mjs.map +1 -0
- package/dist/fields/thumbnail.d.mts +7 -0
- package/dist/fields/thumbnail.mjs +17 -0
- package/dist/fields/thumbnail.mjs.map +1 -0
- package/dist/fields/width.d.mts +7 -0
- package/dist/fields/width.mjs +15 -0
- package/dist/fields/width.mjs.map +1 -0
- package/dist/hooks/useErrorHandler.d.mts +1 -1
- package/dist/hooks/useErrorHandler.mjs +4 -2
- package/dist/hooks/useErrorHandler.mjs.map +1 -1
- package/dist/plugin.d.mts +5 -4
- package/dist/plugin.mjs +53 -29
- package/dist/plugin.mjs.map +1 -1
- package/dist/tus/stores/s3/{expiration-manager.d.mts → expirationManager.d.mts} +4 -3
- package/dist/tus/stores/s3/{expiration-manager.mjs → expirationManager.mjs} +6 -3
- package/dist/tus/stores/s3/expirationManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{file-operations.d.mts → fileOperations.d.mts} +2 -2
- package/dist/tus/stores/s3/{file-operations.mjs → fileOperations.mjs} +2 -2
- package/dist/tus/stores/s3/fileOperations.mjs.map +1 -0
- package/dist/tus/stores/s3/index.d.mts +1 -1
- package/dist/tus/stores/s3/index.mjs +20 -9
- package/dist/tus/stores/s3/index.mjs.map +1 -1
- package/dist/tus/stores/s3/{metadata-manager.d.mts → metadataManager.d.mts} +4 -2
- package/dist/tus/stores/s3/{metadata-manager.mjs → metadataManager.mjs} +6 -5
- package/dist/tus/stores/s3/metadataManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{parts-manager.d.mts → partsManager.d.mts} +4 -4
- package/dist/tus/stores/s3/{parts-manager.mjs → partsManager.mjs} +67 -29
- package/dist/tus/stores/s3/partsManager.mjs.map +1 -0
- package/dist/tus/stores/s3/{s3-store.d.mts → s3Store.d.mts} +38 -32
- package/dist/tus/stores/s3/{s3-store.mjs → s3Store.mjs} +102 -57
- package/dist/tus/stores/s3/s3Store.mjs.map +1 -0
- package/dist/types/errors.d.mts +32 -0
- package/dist/types/errors.mjs +32 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/types/index.d.mts +42 -4
- package/dist/utils/buildS3Path.d.mts +10 -0
- package/dist/utils/buildS3Path.mjs +16 -0
- package/dist/utils/buildS3Path.mjs.map +1 -0
- package/dist/utils/buildThumbnailURL.d.mts +14 -0
- package/dist/utils/buildThumbnailURL.mjs +10 -0
- package/dist/utils/buildThumbnailURL.mjs.map +1 -0
- package/dist/utils/defaultOptions.d.mts +7 -0
- package/dist/utils/defaultOptions.mjs +12 -0
- package/dist/utils/defaultOptions.mjs.map +1 -0
- package/dist/utils/file.d.mts +16 -2
- package/dist/utils/file.mjs +58 -6
- package/dist/utils/file.mjs.map +1 -1
- package/dist/utils/mux.mjs +19 -8
- package/dist/utils/mux.mjs.map +1 -1
- package/dist/utils/tus.d.mts +9 -6
- package/dist/utils/tus.mjs +31 -11
- package/dist/utils/tus.mjs.map +1 -1
- package/package.json +9 -4
- package/dist/components/mux-preview/index.d.mts +0 -2
- package/dist/components/mux-preview/index.mjs +0 -3
- package/dist/components/mux-preview/mux-preview.mjs.map +0 -1
- package/dist/components/upload-handler/index.d.mts +0 -2
- package/dist/components/upload-handler/index.mjs +0 -3
- package/dist/components/upload-handler/upload-handler.d.mts +0 -22
- package/dist/components/upload-handler/upload-handler.mjs.map +0 -1
- package/dist/components/upload-manager/index.d.mts +0 -2
- package/dist/components/upload-manager/index.mjs +0 -3
- package/dist/components/upload-manager/upload-manager.mjs.map +0 -1
- package/dist/endpoints/tusFileExistsHandler.d.mts +0 -11
- package/dist/endpoints/tusFileExistsHandler.mjs.map +0 -1
- package/dist/tus/stores/s3/expiration-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/file-operations.mjs.map +0 -1
- package/dist/tus/stores/s3/metadata-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/parts-manager.mjs.map +0 -1
- package/dist/tus/stores/s3/s3-store.mjs.map +0 -1
- /package/dist/components/{upload-manager/upload-manager.css → uploadManager/uploadManager.css} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { S3Store } from './../tus/stores/s3/s3-store'\n\ninterface GetMediaCollectionArgs {\n s3Store: S3Store\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 { s3Store, baseCollection } = args\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\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 if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=200&height=200&fit_mode=pad`\n } else if (doc?.storage === 's3') {\n // @TODO: Make configurable\n const url = s3Store.getUrl(doc.filename)\n return `https://wsrv.nl/?url=${url}?width=200&height=200&default=1`\n }\n return null\n },\n },\n fields: [\n {\n name: 'alt',\n label: 'Alternative Text',\n type: 'text',\n },\n {\n name: 'caption',\n label: 'Caption',\n type: 'text',\n },\n {\n name: 'copyright',\n label: 'Copyright',\n type: 'text',\n },\n {\n type: 'row',\n fields: [\n {\n name: 'width',\n label: 'Width',\n type: 'text',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n {\n name: 'height',\n label: 'Height',\n type: 'number',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n ],\n },\n {\n name: 'storage',\n label: 'Storage',\n type: 'select',\n options: [\n {\n label: 'Mux',\n value: 'mux',\n },\n {\n label: 'S3',\n value: 's3',\n },\n ],\n admin: {\n readOnly: true,\n },\n },\n {\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 condition: (data) => {\n return data.storage === 'mux'\n },\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 ],\n }\n\n // Simple merge: if baseCollection exists, spread it first, then our config overrides\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n ...config,\n fields: [...(baseCollection.fields || []), ...config.fields],\n }\n}\n"],"mappings":";;;;;;AAcA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,SAAS,mBAAmB;CAEpC,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,QAAI,KAAK,YAAY,SAAS,KAAK,KAAK,WACtC,QAAO,yBAAyB,IAAI,IAAI,WAAW;aAC1C,KAAK,YAAY,KAG1B,QAAO,wBADK,QAAQ,OAAO,IAAI,SAAS,CACL;AAErC,WAAO;;GAEV;EACD,QAAQ;GACN;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,QAAQ,CACN;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,EACD;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,CACF;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,SAAS,CACP;KACE,OAAO;KACP,OAAO;KACR,EACD;KACE,OAAO;KACP,OAAO;KACR,CACF;IACD,OAAO,EACL,UAAU,MACX;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;KACL,mBAAmB;KACnB,iBAAiB;KACjB,mBAAmB;KACnB,UAAU;KACV,YAAY,SAAS;AACnB,aAAO,KAAK,YAAY;;KAE3B;IACD,QAAQ;KACN;MACE,MAAM;MACN,MAAM;MACN,OAAO;OACL,YAAY,SAAS;AACnB,eAAO,KAAK,YAAY;;OAE1B,mBAAmB;OACnB,YAAY,EACV,OAAO,0DACR;OACF;MACF;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACN,kBAAkB,EACf,EAAE,kBAAkB;OACnB,GAAG;OACH,MAAM;OACN,OAAO,CACL;QACE,MAAM;QACN,YAAY;SACV,MAAM;UACJ,MAAM;UACN,MAAM,CAAC,SAAS,QAAQ;UACzB;SACD,IAAI,EACF,MAAM,UACP;SACD,UAAU,EACR,MAAM,UACP;SACD,SAAS,EACP,MAAM,WACP;SACD,cAAc,EACZ,MAAM,UACP;SACD,oBAAoB,EAClB,MAAM,UACP;SACD,WAAW,EACT,MAAM,UACP;SACD,YAAY,EACV,MAAM,UACP;SACD,gBAAgB,EACd,MAAM,UACP;SACF;QACD,UAAU;SACR;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACD;QACF,CACF;OACF,EACF;MACF;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACN,kBAAkB,EACf,EAAE,kBAAkB;OACnB,GAAG;OACH,MAAM;OACN,YAAY,EACV,OAAO;QACL,MAAM;QACN,OAAO;SACL,MAAM;SACN,YAAY;UACV,MAAM,EACJ,MAAM,UACP;UACD,QAAQ,EACN,MAAM,UACP;UACD,YAAY,EACV,MAAM,UACP;UACD,MAAM,EACJ,MAAM,UACP;UACD,IAAI,EACF,MAAM,UACP;UACD,KAAK,EACH,MAAM,UACP;UACD,OAAO,EACL,MAAM,UACP;UACD,QAAQ,EACN,MAAM,UACP;UACD,UAAU,EACR,MAAM,UACP;UACD,SAAS,EACP,MAAM,UACP;UACF;SACF;QACD,UAAU;SACR;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACD;QACF,EACF;OACD,UAAU,CAAC,QAAQ;OACpB,EACF;MACF;KACF;IACF;GACF;EACF;AAGD,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,GAAG;EACH,QAAQ,CAAC,GAAI,eAAe,UAAU,EAAE,EAAG,GAAG,OAAO,OAAO;EAC7D"}
|
|
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 // Add hook, if folders are enabled\n // to handle thumbnails and S3 prefixing\n const afterChange = folders ? [afterChangeHook] : []\n const beforeChange = folders ? [beforeChangeHook] : []\n\n const config: CollectionConfig = {\n slug: 'media',\n custom: {\n storage: 'test',\n },\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,\n afterChange,\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;CAmB1C,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ,EACN,SAAS,QACV;EACD,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,YAzBF,SAAS,SACL,EACE,OAAO,EACL,MAAM,EACJ,WAAW,wDACZ,EACF,EACF,GACD;GAkBF,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,cAtCiB,UAAU,CAAC,iBAAiB,GAAG,EAAE;GAuClD,aAxCgB,UAAU,CAAC,gBAAgB,GAAG,EAAE;GAyCjD;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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MappedDocument } from "../gridView/gridView.mjs";
|
|
2
|
+
import * as react0 from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/components/folderFileCard/folderFileCard.d.ts
|
|
5
|
+
type ContextCardProps = {
|
|
6
|
+
readonly className?: string;
|
|
7
|
+
readonly index: number;
|
|
8
|
+
readonly item: MappedDocument;
|
|
9
|
+
};
|
|
10
|
+
declare function ContextFolderFileCard(props: ContextCardProps): react0.JSX.Element;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ContextFolderFileCard };
|
|
13
|
+
//# sourceMappingURL=folderFileCard.d.mts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useGrid } from "../gridContext/gridContext.mjs";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useSelection } from "@payloadcms/ui";
|
|
4
|
+
import { FolderFileCard } from "@payloadcms/ui/elements/FolderView/FolderFileCard";
|
|
5
|
+
|
|
6
|
+
//#region src/components/folderFileCard/folderFileCard.tsx
|
|
7
|
+
function ContextFolderFileCard(props) {
|
|
8
|
+
const { className, index, item } = props;
|
|
9
|
+
const { focusedItemIndex, onItemClick, onItemKeyPress } = useGrid();
|
|
10
|
+
const { selectedIDs } = useSelection();
|
|
11
|
+
const isSelected = useMemo(() => selectedIDs.includes(item.id), [selectedIDs, item.id]);
|
|
12
|
+
const isFocused = useMemo(() => focusedItemIndex === index, [focusedItemIndex, index]);
|
|
13
|
+
return <FolderFileCard className={className} id={item.id} isFocused={isFocused} isSelected={isSelected} itemKey={item.id.toString()} previewUrl={item.thumbnailURL} title={item.title} type={"file"} onClick={(event) => {
|
|
14
|
+
onItemClick({
|
|
15
|
+
event,
|
|
16
|
+
index,
|
|
17
|
+
item
|
|
18
|
+
});
|
|
19
|
+
}} onKeyDown={(event) => {
|
|
20
|
+
onItemKeyPress({
|
|
21
|
+
event,
|
|
22
|
+
index,
|
|
23
|
+
item
|
|
24
|
+
});
|
|
25
|
+
}} />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ContextFolderFileCard };
|
|
30
|
+
//# sourceMappingURL=folderFileCard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folderFileCard.mjs","names":[],"sources":["../../../src/components/folderFileCard/folderFileCard.tsx"],"sourcesContent":["import { useMemo } from 'react'\nimport { FolderFileCard } from '@payloadcms/ui/elements/FolderView/FolderFileCard'\nimport { useSelection } from '@payloadcms/ui'\nimport { useGrid } from '../gridContext/gridContext'\n\nimport type { MappedDocument } from '../gridView/gridView'\n\ntype ContextCardProps = {\n readonly className?: string\n readonly index: number\n readonly item: MappedDocument\n}\n\nexport function ContextFolderFileCard(props: ContextCardProps) {\n const { className, index, item } = props\n\n const { focusedItemIndex, onItemClick, onItemKeyPress } = useGrid()\n const { selectedIDs } = useSelection()\n\n const isSelected = useMemo(\n () => selectedIDs.includes(item.id),\n [selectedIDs, item.id]\n )\n\n const isFocused = useMemo(\n () => focusedItemIndex === index,\n [focusedItemIndex, index]\n )\n\n return (\n <FolderFileCard\n className={className}\n id={item.id}\n isFocused={isFocused}\n isSelected={isSelected}\n itemKey={item.id.toString()}\n previewUrl={item.thumbnailURL}\n title={item.title}\n type={'file'}\n onClick={(event) => {\n void onItemClick({ event, index, item })\n }}\n onKeyDown={(event) => {\n void onItemKeyPress({ event, index, item })\n }}\n />\n )\n}\n"],"mappings":";;;;;;AAaA,SAAgB,sBAAsB,OAAyB;CAC7D,MAAM,EAAE,WAAW,OAAO,SAAS;CAEnC,MAAM,EAAE,kBAAkB,aAAa,mBAAmB,SAAS;CACnE,MAAM,EAAE,gBAAgB,cAAc;CAEtC,MAAM,aAAa,cACX,YAAY,SAAS,KAAK,GAAG,EACnC,CAAC,aAAa,KAAK,GAAG,CACvB;CAED,MAAM,YAAY,cACV,qBAAqB,OAC3B,CAAC,kBAAkB,MAAM,CAC1B;AAED,QACE,CAAC,eACC,WAAW,WACX,IAAI,KAAK,IACT,WAAW,WACX,YAAY,YACZ,SAAS,KAAK,GAAG,UAAU,EAC3B,YAAY,KAAK,cACjB,OAAO,KAAK,OACZ,MAAM,QACN,UAAU,UAAU;AAClB,EAAK,YAAY;GAAE;GAAO;GAAO;GAAM,CAAC;IAE1C,YAAY,UAAU;AACpB,EAAK,eAAe;GAAE;GAAO;GAAO;GAAM,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { MappedDocument } from "../gridView/gridView.mjs";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { FolderSortKeys } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/components/gridContext/gridContext.d.ts
|
|
6
|
+
interface OnItemClickArgs {
|
|
7
|
+
event: React.MouseEvent;
|
|
8
|
+
index: number;
|
|
9
|
+
item: MappedDocument;
|
|
10
|
+
}
|
|
11
|
+
interface OnItemKeyPressArgs {
|
|
12
|
+
event: React.KeyboardEvent;
|
|
13
|
+
index: number;
|
|
14
|
+
item: MappedDocument;
|
|
15
|
+
}
|
|
16
|
+
type GridContextValue = {
|
|
17
|
+
docs?: MappedDocument[];
|
|
18
|
+
clearSelections: () => void;
|
|
19
|
+
onItemClick: (args: OnItemClickArgs) => void;
|
|
20
|
+
onItemKeyPress: (args: OnItemKeyPressArgs) => void;
|
|
21
|
+
setFocusedItemIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
22
|
+
focusedItemIndex: number;
|
|
23
|
+
};
|
|
24
|
+
type GridProviderProps = {
|
|
25
|
+
readonly allowMultiSelection?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Children to render inside the provider
|
|
28
|
+
*/
|
|
29
|
+
readonly children: React.ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* All documents in the current folder
|
|
32
|
+
*/
|
|
33
|
+
readonly docs: MappedDocument[];
|
|
34
|
+
/**
|
|
35
|
+
* The intial search query
|
|
36
|
+
*/
|
|
37
|
+
readonly search?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The sort order of the documents
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* `name` for descending
|
|
43
|
+
* `-name` for ascending
|
|
44
|
+
*/
|
|
45
|
+
readonly sort?: FolderSortKeys;
|
|
46
|
+
};
|
|
47
|
+
declare function GridProvider(props: GridProviderProps): React.JSX.Element;
|
|
48
|
+
declare function useGrid(): GridContextValue;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { GridContextValue, GridProvider, GridProviderProps, useGrid };
|
|
51
|
+
//# sourceMappingURL=gridContext.d.mts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { useConfig, useRouteTransition, useSelection } from "@payloadcms/ui";
|
|
6
|
+
import { formatAdminURL } from "payload/shared";
|
|
7
|
+
import { useDrawerDepth } from "@payloadcms/ui/elements/Drawer";
|
|
8
|
+
|
|
9
|
+
//#region src/components/gridContext/gridContext.tsx
|
|
10
|
+
const Context = React.createContext({
|
|
11
|
+
docs: [],
|
|
12
|
+
clearSelections: () => {},
|
|
13
|
+
onItemClick: () => void 0,
|
|
14
|
+
onItemKeyPress: () => void 0,
|
|
15
|
+
setFocusedItemIndex: () => -1,
|
|
16
|
+
focusedItemIndex: -1
|
|
17
|
+
});
|
|
18
|
+
function GridProvider(props) {
|
|
19
|
+
const { allowMultiSelection = true, children, docs } = props;
|
|
20
|
+
const { config } = useConfig();
|
|
21
|
+
const drawerDepth = useDrawerDepth();
|
|
22
|
+
const router = useRouter();
|
|
23
|
+
const { startRouteTransition } = useRouteTransition();
|
|
24
|
+
const { setSelection, selectedIDs } = useSelection();
|
|
25
|
+
const currentlySelectedIndexes = React.useRef(/* @__PURE__ */ new Set());
|
|
26
|
+
const [focusedItemIndex, setFocusedItemIndex] = React.useState(-1);
|
|
27
|
+
const lastClickTime = React.useRef(null);
|
|
28
|
+
const totalCount = docs.length;
|
|
29
|
+
const clearSelections = React.useCallback(() => {
|
|
30
|
+
setFocusedItemIndex(-1);
|
|
31
|
+
currentlySelectedIndexes.current = /* @__PURE__ */ new Set();
|
|
32
|
+
}, []);
|
|
33
|
+
const getItem = React.useCallback((id) => {
|
|
34
|
+
return docs.find((doc) => doc.id === id);
|
|
35
|
+
}, [docs]);
|
|
36
|
+
const navigateAfterSelection = React.useCallback(({ collectionSlug, docID }) => {
|
|
37
|
+
if (drawerDepth === 1) {
|
|
38
|
+
if (collectionSlug) startRouteTransition(() => {
|
|
39
|
+
router.push(formatAdminURL({
|
|
40
|
+
adminRoute: config.routes.admin,
|
|
41
|
+
path: `/collections/${collectionSlug}/${docID}`
|
|
42
|
+
}));
|
|
43
|
+
clearSelections();
|
|
44
|
+
});
|
|
45
|
+
} else clearSelections();
|
|
46
|
+
}, [
|
|
47
|
+
clearSelections,
|
|
48
|
+
config.routes.admin,
|
|
49
|
+
drawerDepth,
|
|
50
|
+
getItem,
|
|
51
|
+
router,
|
|
52
|
+
startRouteTransition
|
|
53
|
+
]);
|
|
54
|
+
const handleShiftSelection = React.useCallback((targetIndex) => {
|
|
55
|
+
const existingIndexes = docs.reduce((acc, item, idx) => {
|
|
56
|
+
if (selectedIDs.includes(item.id)) acc.push(idx);
|
|
57
|
+
return acc;
|
|
58
|
+
}, []);
|
|
59
|
+
if (existingIndexes.length === 0) return [targetIndex];
|
|
60
|
+
const firstSelectedIndex = Math.min(...existingIndexes);
|
|
61
|
+
const lastSelectedIndex = Math.max(...existingIndexes);
|
|
62
|
+
const isWithinBounds = targetIndex >= firstSelectedIndex && targetIndex <= lastSelectedIndex;
|
|
63
|
+
let anchorIndex = targetIndex;
|
|
64
|
+
if (isWithinBounds) if (targetIndex === firstSelectedIndex || targetIndex === lastSelectedIndex) anchorIndex = targetIndex;
|
|
65
|
+
else anchorIndex = Math.abs(targetIndex - firstSelectedIndex) >= Math.abs(targetIndex - lastSelectedIndex) ? firstSelectedIndex : lastSelectedIndex;
|
|
66
|
+
else anchorIndex = Math.abs(targetIndex - firstSelectedIndex) <= Math.abs(targetIndex - lastSelectedIndex) ? firstSelectedIndex : lastSelectedIndex;
|
|
67
|
+
const startIndex = Math.min(anchorIndex, targetIndex);
|
|
68
|
+
const endIndex = Math.max(anchorIndex, targetIndex);
|
|
69
|
+
const newRangeIndexes = Array.from({ length: endIndex - startIndex + 1 }, (_, i) => startIndex + i);
|
|
70
|
+
if (isWithinBounds) return newRangeIndexes;
|
|
71
|
+
else {
|
|
72
|
+
const mappedSet = new Set([...existingIndexes, ...newRangeIndexes]);
|
|
73
|
+
return Array.from(mappedSet);
|
|
74
|
+
}
|
|
75
|
+
}, [docs, selectedIDs]);
|
|
76
|
+
const updateSelections = React.useCallback(({ indexes }) => {
|
|
77
|
+
const { newSelectedIDs } = docs.reduce((acc, item, index) => {
|
|
78
|
+
if (indexes.includes(index)) acc.newSelectedIDs.add(item.id);
|
|
79
|
+
return acc;
|
|
80
|
+
}, { newSelectedIDs: /* @__PURE__ */ new Set() });
|
|
81
|
+
selectedIDs.forEach((id) => {
|
|
82
|
+
setSelection(id);
|
|
83
|
+
});
|
|
84
|
+
newSelectedIDs.forEach((id) => {
|
|
85
|
+
const item = getItem(id);
|
|
86
|
+
if (item) setSelection(item.id);
|
|
87
|
+
});
|
|
88
|
+
}, [
|
|
89
|
+
docs,
|
|
90
|
+
getItem,
|
|
91
|
+
setSelection
|
|
92
|
+
]);
|
|
93
|
+
const onItemKeyPress = React.useCallback(({ event, item: currentItem }) => {
|
|
94
|
+
const { code, ctrlKey, metaKey, shiftKey } = event;
|
|
95
|
+
const isShiftPressed = shiftKey;
|
|
96
|
+
const isCtrlPressed = ctrlKey || metaKey;
|
|
97
|
+
const isCurrentlySelected = selectedIDs.includes(currentItem.id);
|
|
98
|
+
const currentItemIndex = docs.findIndex((item) => item.id === currentItem.id);
|
|
99
|
+
switch (code) {
|
|
100
|
+
case "ArrowDown":
|
|
101
|
+
case "ArrowLeft":
|
|
102
|
+
case "ArrowRight":
|
|
103
|
+
case "ArrowUp": {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
if (currentItemIndex === -1) break;
|
|
106
|
+
const newItemIndex = code === "ArrowLeft" || code === "ArrowUp" ? currentItemIndex - 1 : currentItemIndex + 1;
|
|
107
|
+
if (newItemIndex < 0 || newItemIndex > totalCount - 1) return;
|
|
108
|
+
setFocusedItemIndex(newItemIndex);
|
|
109
|
+
if (isCtrlPressed) break;
|
|
110
|
+
if (isShiftPressed && allowMultiSelection) {
|
|
111
|
+
updateSelections({ indexes: handleShiftSelection(newItemIndex) });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!isShiftPressed) updateSelections({ indexes: [newItemIndex] });
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "Enter":
|
|
118
|
+
if (selectedIDs.length === 1) {
|
|
119
|
+
navigateAfterSelection({
|
|
120
|
+
collectionSlug: currentItem.relationTo,
|
|
121
|
+
docID: currentItem.id
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case "Escape":
|
|
127
|
+
clearSelections();
|
|
128
|
+
break;
|
|
129
|
+
case "KeyA":
|
|
130
|
+
if (allowMultiSelection && isCtrlPressed) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
setFocusedItemIndex(totalCount - 1);
|
|
133
|
+
updateSelections({ indexes: Array.from({ length: totalCount }, (_, i) => i) });
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "Space":
|
|
137
|
+
if (allowMultiSelection && isShiftPressed) {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
updateSelections({ indexes: docs.reduce((acc, item, idx) => {
|
|
140
|
+
if (item.id === currentItem.id) if (isCurrentlySelected) return acc;
|
|
141
|
+
else acc.push(idx);
|
|
142
|
+
else if (selectedIDs.includes(item.id)) acc.push(idx);
|
|
143
|
+
return acc;
|
|
144
|
+
}, []) });
|
|
145
|
+
} else {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
updateSelections({ indexes: isCurrentlySelected ? [] : [currentItemIndex] });
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "Tab":
|
|
151
|
+
if (allowMultiSelection && isShiftPressed) {
|
|
152
|
+
const prevIndex = currentItemIndex - 1;
|
|
153
|
+
if (prevIndex < 0 && selectedIDs.length > 0) setFocusedItemIndex(prevIndex);
|
|
154
|
+
} else if (currentItemIndex + 1 === totalCount && selectedIDs.length > 0) setFocusedItemIndex(totalCount - 1);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}, [
|
|
158
|
+
selectedIDs,
|
|
159
|
+
docs,
|
|
160
|
+
allowMultiSelection,
|
|
161
|
+
handleShiftSelection,
|
|
162
|
+
updateSelections,
|
|
163
|
+
navigateAfterSelection,
|
|
164
|
+
clearSelections,
|
|
165
|
+
totalCount
|
|
166
|
+
]);
|
|
167
|
+
const [lastClickedItem, setLastClickedItem] = React.useState();
|
|
168
|
+
const onItemClick = React.useCallback(({ event, item: clickedItem }) => {
|
|
169
|
+
let doubleClicked = false;
|
|
170
|
+
const isCtrlPressed = event.ctrlKey || event.metaKey;
|
|
171
|
+
const isShiftPressed = event.shiftKey;
|
|
172
|
+
const isCurrentlySelected = selectedIDs.includes(clickedItem.id);
|
|
173
|
+
const currentItemIndex = docs.findIndex((item) => item.id === clickedItem.id);
|
|
174
|
+
switch (true) {
|
|
175
|
+
case allowMultiSelection && isCtrlPressed:
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
updateSelections({ indexes: docs.reduce((acc, item, idx) => {
|
|
178
|
+
if (item.id === clickedItem.id) {
|
|
179
|
+
if (!isCurrentlySelected) acc.push(idx);
|
|
180
|
+
} else if (selectedIDs.includes(item.id)) acc.push(idx);
|
|
181
|
+
return acc;
|
|
182
|
+
}, []) });
|
|
183
|
+
break;
|
|
184
|
+
case allowMultiSelection && isShiftPressed:
|
|
185
|
+
if (currentItemIndex !== -1) updateSelections({ indexes: handleShiftSelection(currentItemIndex) });
|
|
186
|
+
break;
|
|
187
|
+
default: {
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
doubleClicked = now - (lastClickTime.current ?? 0) < 400 && lastClickedItem?.id === clickedItem.id;
|
|
190
|
+
lastClickTime.current = now;
|
|
191
|
+
if (!doubleClicked) updateSelections({ indexes: isCurrentlySelected && selectedIDs.length === 1 ? [] : [currentItemIndex] });
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
setLastClickedItem(clickedItem);
|
|
196
|
+
if (doubleClicked) navigateAfterSelection({
|
|
197
|
+
collectionSlug: clickedItem.relationTo,
|
|
198
|
+
docID: clickedItem.id
|
|
199
|
+
});
|
|
200
|
+
}, [
|
|
201
|
+
docs,
|
|
202
|
+
allowMultiSelection,
|
|
203
|
+
getItem,
|
|
204
|
+
updateSelections,
|
|
205
|
+
navigateAfterSelection,
|
|
206
|
+
handleShiftSelection
|
|
207
|
+
]);
|
|
208
|
+
return <Context value={{
|
|
209
|
+
clearSelections,
|
|
210
|
+
docs,
|
|
211
|
+
focusedItemIndex,
|
|
212
|
+
onItemClick,
|
|
213
|
+
onItemKeyPress,
|
|
214
|
+
setFocusedItemIndex
|
|
215
|
+
}}>
|
|
216
|
+
{children}
|
|
217
|
+
</Context>;
|
|
218
|
+
}
|
|
219
|
+
function useGrid() {
|
|
220
|
+
const context = React.use(Context);
|
|
221
|
+
if (context === void 0) throw new Error("useGrid must be used within a GridProvider");
|
|
222
|
+
return context;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//#endregion
|
|
226
|
+
export { GridProvider, useGrid };
|
|
227
|
+
//# sourceMappingURL=gridContext.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gridContext.mjs","names":["onItemKeyPress: GridContextValue['onItemKeyPress']","onItemClick: GridContextValue['onItemClick']","doubleClicked: boolean"],"sources":["../../../src/components/gridContext/gridContext.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { formatAdminURL } from 'payload/shared'\n\nimport { useDrawerDepth } from '@payloadcms/ui/elements/Drawer'\nimport { useConfig, useRouteTransition, useSelection } from '@payloadcms/ui'\n\nimport type { FolderSortKeys } from 'payload'\nimport type { MappedDocument } from '../gridView/gridView'\ninterface OnItemClickArgs {\n event: React.MouseEvent\n index: number\n item: MappedDocument\n}\n\ninterface OnItemKeyPressArgs {\n event: React.KeyboardEvent\n index: number\n item: MappedDocument\n}\n\nexport type GridContextValue = {\n docs?: MappedDocument[]\n clearSelections: () => void\n onItemClick: (args: OnItemClickArgs) => void\n onItemKeyPress: (args: OnItemKeyPressArgs) => void\n setFocusedItemIndex: React.Dispatch<React.SetStateAction<number>>\n focusedItemIndex: number\n}\n\nconst Context = React.createContext<GridContextValue>({\n docs: [],\n clearSelections: () => {},\n onItemClick: () => undefined,\n onItemKeyPress: () => undefined,\n setFocusedItemIndex: () => -1,\n focusedItemIndex: -1,\n})\n\nexport type GridProviderProps = {\n readonly allowMultiSelection?: boolean\n /**\n * Children to render inside the provider\n */\n readonly children: React.ReactNode\n /**\n * All documents in the current folder\n */\n readonly docs: MappedDocument[]\n /**\n * The intial search query\n */\n readonly search?: string\n /**\n * The sort order of the documents\n *\n * @example\n * `name` for descending\n * `-name` for ascending\n */\n readonly sort?: FolderSortKeys\n}\nexport function GridProvider(props: GridProviderProps) {\n const { allowMultiSelection = true, children, docs } = props\n\n const { config } = useConfig()\n const drawerDepth = useDrawerDepth()\n const router = useRouter()\n const { startRouteTransition } = useRouteTransition()\n\n const { setSelection, selectedIDs } = useSelection()\n const currentlySelectedIndexes = React.useRef(new Set<number>())\n\n const [focusedItemIndex, setFocusedItemIndex] = React.useState(-1)\n const lastClickTime = React.useRef<null | number>(null)\n const totalCount = docs.length\n\n const clearSelections = React.useCallback(() => {\n setFocusedItemIndex(-1)\n\n currentlySelectedIndexes.current = new Set()\n }, [])\n\n const getItem = React.useCallback(\n (id: string | number) => {\n return docs.find((doc) => doc.id === id)\n },\n [docs]\n )\n\n const navigateAfterSelection = React.useCallback(\n ({\n collectionSlug,\n docID,\n }: {\n collectionSlug: string\n docID?: number | string\n }) => {\n if (drawerDepth === 1) {\n // not in a drawer (default is 1)\n if (collectionSlug) {\n // clicked on document, take the user to the documet view\n startRouteTransition(() => {\n router.push(\n formatAdminURL({\n adminRoute: config.routes.admin,\n path: `/collections/${collectionSlug}/${docID}`,\n })\n )\n clearSelections()\n })\n }\n } else {\n clearSelections()\n }\n },\n [\n clearSelections,\n config.routes.admin,\n drawerDepth,\n getItem,\n router,\n startRouteTransition,\n ]\n )\n\n const handleShiftSelection = React.useCallback(\n (targetIndex: number) => {\n // Find existing selection boundaries\n const existingIndexes = docs.reduce((acc, item, idx) => {\n if (selectedIDs.includes(item.id)) {\n acc.push(idx)\n }\n return acc\n }, [] as number[])\n\n if (existingIndexes.length === 0) {\n // No existing selection, just select target\n return [targetIndex]\n }\n\n const firstSelectedIndex = Math.min(...existingIndexes)\n const lastSelectedIndex = Math.max(...existingIndexes)\n const isWithinBounds =\n targetIndex >= firstSelectedIndex && targetIndex <= lastSelectedIndex\n\n // Choose anchor based on whether we're contracting or extending\n let anchorIndex = targetIndex\n if (isWithinBounds) {\n // Contracting: if target is at a boundary, use target as anchor\n // Otherwise, use furthest boundary to maintain opposite edge\n if (\n targetIndex === firstSelectedIndex ||\n targetIndex === lastSelectedIndex\n ) {\n anchorIndex = targetIndex\n } else {\n const distanceToFirst = Math.abs(targetIndex - firstSelectedIndex)\n const distanceToLast = Math.abs(targetIndex - lastSelectedIndex)\n anchorIndex =\n distanceToFirst >= distanceToLast\n ? firstSelectedIndex\n : lastSelectedIndex\n }\n } else {\n // Extending: use closest boundary\n const distanceToFirst = Math.abs(targetIndex - firstSelectedIndex)\n const distanceToLast = Math.abs(targetIndex - lastSelectedIndex)\n anchorIndex =\n distanceToFirst <= distanceToLast\n ? firstSelectedIndex\n : lastSelectedIndex\n }\n\n // Create range from anchor to target\n const startIndex = Math.min(anchorIndex, targetIndex)\n const endIndex = Math.max(anchorIndex, targetIndex)\n const newRangeIndexes = Array.from(\n { length: endIndex - startIndex + 1 },\n (_, i) => startIndex + i\n )\n\n if (isWithinBounds) {\n // Contracting: replace with new range\n return newRangeIndexes\n } else {\n // Extending: union with existing\n const mappedSet = new Set([...existingIndexes, ...newRangeIndexes])\n return Array.from(mappedSet)\n }\n },\n [docs, selectedIDs]\n )\n\n const updateSelections = React.useCallback(\n ({ indexes }: { indexes: number[] }) => {\n const { newSelectedIDs } = docs.reduce(\n (acc, item, index) => {\n if (indexes.includes(index)) {\n acc.newSelectedIDs.add(item.id)\n }\n return acc\n },\n {\n newSelectedIDs: new Set<string | number>(),\n }\n )\n\n // Clear previous selection in global selection context\n selectedIDs.forEach((id) => {\n setSelection(id)\n })\n\n // Update selection in global selection context\n newSelectedIDs.forEach((id) => {\n const item = getItem(id)\n if (item) {\n setSelection(item.id)\n }\n })\n },\n [docs, getItem, setSelection]\n )\n\n const onItemKeyPress: GridContextValue['onItemKeyPress'] = React.useCallback(\n ({ event, item: currentItem }) => {\n const { code, ctrlKey, metaKey, shiftKey } = event\n\n const isShiftPressed = shiftKey\n const isCtrlPressed = ctrlKey || metaKey\n const isCurrentlySelected = selectedIDs.includes(currentItem.id)\n const currentItemIndex = docs.findIndex(\n (item) => item.id === currentItem.id\n )\n\n switch (code) {\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight':\n case 'ArrowUp': {\n event.preventDefault()\n\n if (currentItemIndex === -1) {\n break\n }\n\n const isBackward = code === 'ArrowLeft' || code === 'ArrowUp'\n const newItemIndex = isBackward\n ? currentItemIndex - 1\n : currentItemIndex + 1\n\n if (newItemIndex < 0 || newItemIndex > totalCount - 1) {\n // out of bounds, keep current selection\n return\n }\n\n setFocusedItemIndex(newItemIndex)\n\n if (isCtrlPressed) {\n break\n }\n\n if (isShiftPressed && allowMultiSelection) {\n const selectedIndexes = handleShiftSelection(newItemIndex)\n updateSelections({ indexes: selectedIndexes })\n return\n }\n\n // Single selection without shift\n if (!isShiftPressed) {\n // const newItem = allItems[newItemIndex]\n updateSelections({ indexes: [newItemIndex] })\n }\n\n break\n }\n case 'Enter': {\n if (selectedIDs.length === 1) {\n // setFocusedItemIndex(undefined)\n navigateAfterSelection({\n collectionSlug: currentItem.relationTo,\n docID: currentItem.id,\n })\n return\n }\n break\n }\n case 'Escape': {\n clearSelections()\n break\n }\n case 'KeyA': {\n if (allowMultiSelection && isCtrlPressed) {\n event.preventDefault()\n setFocusedItemIndex(totalCount - 1)\n updateSelections({\n indexes: Array.from({ length: totalCount }, (_, i) => i),\n })\n }\n break\n }\n case 'Space': {\n if (allowMultiSelection && isShiftPressed) {\n event.preventDefault()\n updateSelections({\n indexes: docs.reduce((acc, item, idx) => {\n if (item.id === currentItem.id) {\n if (isCurrentlySelected) {\n return acc\n } else {\n acc.push(idx)\n }\n } else if (selectedIDs.includes(item.id)) {\n acc.push(idx)\n }\n return acc\n }, [] as number[]),\n })\n } else {\n event.preventDefault()\n updateSelections({\n indexes: isCurrentlySelected ? [] : [currentItemIndex],\n })\n }\n break\n }\n case 'Tab': {\n if (allowMultiSelection && isShiftPressed) {\n const prevIndex = currentItemIndex - 1\n if (prevIndex < 0 && selectedIDs.length > 0) {\n setFocusedItemIndex(prevIndex)\n }\n } else {\n const nextIndex = currentItemIndex + 1\n if (nextIndex === totalCount && selectedIDs.length > 0) {\n setFocusedItemIndex(totalCount - 1)\n }\n }\n break\n }\n }\n },\n [\n selectedIDs,\n docs,\n allowMultiSelection,\n handleShiftSelection,\n updateSelections,\n navigateAfterSelection,\n clearSelections,\n totalCount,\n ]\n )\n\n // Track last clicked item for double-click detection\n const [lastClickedItem, setLastClickedItem] = React.useState<\n MappedDocument | undefined\n >()\n\n const onItemClick: GridContextValue['onItemClick'] = React.useCallback(\n ({ event, item: clickedItem }) => {\n let doubleClicked: boolean = false\n const isCtrlPressed = event.ctrlKey || event.metaKey\n const isShiftPressed = event.shiftKey\n const isCurrentlySelected = selectedIDs.includes(clickedItem.id)\n const currentItemIndex = docs.findIndex(\n (item) => item.id === clickedItem.id\n )\n\n switch (true) {\n case allowMultiSelection && isCtrlPressed: {\n event.preventDefault()\n const indexes = docs.reduce((acc, item, idx) => {\n if (item.id === clickedItem.id) {\n if (!isCurrentlySelected) {\n acc.push(idx)\n }\n } else if (selectedIDs.includes(item.id)) {\n acc.push(idx)\n }\n return acc\n }, [] as number[])\n\n updateSelections({ indexes })\n break\n }\n case allowMultiSelection && isShiftPressed: {\n if (currentItemIndex !== -1) {\n const selectedIndexes = handleShiftSelection(currentItemIndex)\n updateSelections({ indexes: selectedIndexes })\n }\n break\n }\n default: {\n const now = Date.now()\n doubleClicked =\n now - (lastClickTime.current ?? 0) < 400 &&\n lastClickedItem?.id === clickedItem.id\n lastClickTime.current = now\n\n if (!doubleClicked) {\n updateSelections({\n indexes:\n isCurrentlySelected && selectedIDs.length === 1\n ? []\n : [currentItemIndex],\n })\n }\n break\n }\n }\n\n // Update last clicked item to determine double clicks\n setLastClickedItem(clickedItem)\n\n if (doubleClicked) {\n navigateAfterSelection({\n collectionSlug: clickedItem.relationTo,\n docID: clickedItem.id,\n })\n }\n },\n [\n docs,\n allowMultiSelection,\n getItem,\n updateSelections,\n navigateAfterSelection,\n handleShiftSelection,\n ]\n )\n\n return (\n <Context\n value={{\n clearSelections,\n docs,\n focusedItemIndex,\n onItemClick,\n onItemKeyPress,\n setFocusedItemIndex,\n }}\n >\n {children}\n </Context>\n )\n}\n\nexport function useGrid(): GridContextValue {\n const context = React.use(Context)\n\n if (context === undefined) {\n throw new Error('useGrid must be used within a GridProvider')\n }\n\n return context\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.grid-view {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.grid-view__wrapper {
|
|
6
|
+
width: 100%;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.grid-view__no-results {
|
|
10
|
+
padding: calc(var(--base) * 2) var(--base);
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
border: 1px dashed var(--theme-border-color);
|
|
16
|
+
border-radius: var(--style-radius-m);
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.grid-view__no-results__actions {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: calc(var(--base) / 2);
|
|
23
|
+
margin-top: var(--base);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.grid-view__no-results__actions .btn {
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Grid layout for Payload's FolderFileCard components */
|
|
31
|
+
.grid-view__grid {
|
|
32
|
+
display: grid;
|
|
33
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
34
|
+
gap: 1rem;
|
|
35
|
+
padding: var(--gutter-v, 2rem) 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (max-width: 768px) {
|
|
39
|
+
.grid-view__grid {
|
|
40
|
+
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
41
|
+
gap: 0.75rem;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.grid-view__list-selection {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 1rem;
|
|
49
|
+
margin-top: 1rem;
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import "./gridView.css";
|
|
3
|
+
import { ListViewClientProps } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/components/gridView/gridView.d.ts
|
|
6
|
+
interface MappedDocument {
|
|
7
|
+
id: string | number;
|
|
8
|
+
storage: 'mux' | 's3';
|
|
9
|
+
title: string;
|
|
10
|
+
thumbnailURL?: string;
|
|
11
|
+
relationTo: string;
|
|
12
|
+
}
|
|
13
|
+
declare function GridView(props: ListViewClientProps): React.JSX.Element;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { GridView, GridView as default, MappedDocument };
|
|
16
|
+
//# sourceMappingURL=gridView.d.mts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { GridProvider } from "../gridContext/gridContext.mjs";
|
|
4
|
+
import { ItemCardGrid } from "../itemCardGrid/itemCardGrid.mjs";
|
|
5
|
+
import React, { Fragment, useEffect, useMemo } from "react";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
7
|
+
import { getTranslation } from "@payloadcms/translations";
|
|
8
|
+
import { Gutter, ListControls, ListHeader, ListSelection, PageControls, SelectMany, SelectionProvider, useBulkUpload, useConfig, useListDrawerContext, useListQuery, useModal, useStepNav, useTranslation, useWindowInfo } from "@payloadcms/ui";
|
|
9
|
+
import { DefaultListViewTabs } from "@payloadcms/ui/elements/DefaultListViewTabs";
|
|
10
|
+
import { formatAdminURL } from "payload/shared";
|
|
11
|
+
import "./gridView.css";
|
|
12
|
+
|
|
13
|
+
//#region src/components/gridView/gridView.tsx
|
|
14
|
+
function GridView(props) {
|
|
15
|
+
const baseClass = "grid-view";
|
|
16
|
+
const { collectionSlug, AfterList, AfterListTable, BeforeList, BeforeListTable, Description, newDocumentURL, hasCreatePermission, hasDeletePermission, disableBulkDelete, disableBulkEdit, beforeActions, viewType } = props;
|
|
17
|
+
const { config, getEntityConfig } = useConfig();
|
|
18
|
+
const { routes: { admin: adminRoute } } = config;
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const { data } = useListQuery() ?? {};
|
|
21
|
+
const { docs = [] } = data ?? {};
|
|
22
|
+
const { i18n } = useTranslation();
|
|
23
|
+
const { setStepNav } = useStepNav();
|
|
24
|
+
const { openModal } = useModal();
|
|
25
|
+
const { drawerSlug: bulkUploadDrawerSlug, setCollectionSlug: setBulkUploadCollectionSlug, setOnSuccess } = useBulkUpload();
|
|
26
|
+
const { breakpoints: { s: smallBreak } } = useWindowInfo();
|
|
27
|
+
const collectionConfig = getEntityConfig({ collectionSlug });
|
|
28
|
+
const { labels, upload } = collectionConfig;
|
|
29
|
+
const isBulkUploadEnabled = (Boolean(upload) && collectionConfig.upload?.bulkUpload) ?? true;
|
|
30
|
+
const isTrashEnabled = Boolean(collectionConfig.trash);
|
|
31
|
+
const { onBulkSelect } = useListDrawerContext();
|
|
32
|
+
const getThumbnailURL = (doc) => {
|
|
33
|
+
if (doc?.storage === "mux" && doc?.mux?.playbackId) return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=400&height=400&fit_mode=pad`;
|
|
34
|
+
if (doc?.thumbnailURL) return doc.thumbnailURL;
|
|
35
|
+
};
|
|
36
|
+
const mappedDocs = useMemo(() => {
|
|
37
|
+
return docs.map((doc) => {
|
|
38
|
+
const title = doc.filename ?? doc.alt ?? String(doc.id);
|
|
39
|
+
return {
|
|
40
|
+
id: doc.id,
|
|
41
|
+
storage: doc.storage,
|
|
42
|
+
title,
|
|
43
|
+
thumbnailURL: getThumbnailURL(doc),
|
|
44
|
+
relationTo: collectionSlug
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}, [docs, collectionSlug]);
|
|
48
|
+
const openBulkUpload = React.useCallback(() => {
|
|
49
|
+
setBulkUploadCollectionSlug(collectionSlug);
|
|
50
|
+
openModal(bulkUploadDrawerSlug);
|
|
51
|
+
setOnSuccess(() => router.refresh());
|
|
52
|
+
}, [
|
|
53
|
+
router,
|
|
54
|
+
collectionSlug,
|
|
55
|
+
bulkUploadDrawerSlug,
|
|
56
|
+
openModal,
|
|
57
|
+
setBulkUploadCollectionSlug,
|
|
58
|
+
setOnSuccess
|
|
59
|
+
]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const baseLabel = {
|
|
62
|
+
label: getTranslation(labels?.plural, i18n),
|
|
63
|
+
url: isTrashEnabled && viewType === "trash" ? formatAdminURL({
|
|
64
|
+
adminRoute,
|
|
65
|
+
path: `/collections/${collectionSlug}`
|
|
66
|
+
}) : void 0
|
|
67
|
+
};
|
|
68
|
+
const trashLabel = { label: i18n.t("general:trash") };
|
|
69
|
+
setStepNav(isTrashEnabled && viewType === "trash" ? [baseLabel, trashLabel] : [baseLabel]);
|
|
70
|
+
}, [
|
|
71
|
+
adminRoute,
|
|
72
|
+
setStepNav,
|
|
73
|
+
labels,
|
|
74
|
+
isTrashEnabled,
|
|
75
|
+
viewType,
|
|
76
|
+
i18n,
|
|
77
|
+
collectionSlug
|
|
78
|
+
]);
|
|
79
|
+
return <SelectionProvider docs={docs} totalDocs={data?.totalDocs ?? 0}>
|
|
80
|
+
<GridProvider allowMultiSelection={true} docs={mappedDocs}>
|
|
81
|
+
<div className={`grid-view grid-view--${collectionSlug}`}>
|
|
82
|
+
<Fragment>
|
|
83
|
+
{BeforeList}
|
|
84
|
+
<Gutter className={`${baseClass}__wrapper collection-list__wrap`}>
|
|
85
|
+
<ListHeader Actions={[
|
|
86
|
+
beforeActions,
|
|
87
|
+
!smallBreak && <ListSelection disableBulkDelete={disableBulkDelete} disableBulkEdit={collectionConfig.disableBulkEdit ?? disableBulkEdit} key="list-selection" label={getTranslation(labels?.plural, i18n)} />,
|
|
88
|
+
<DefaultListViewTabs collectionConfig={collectionConfig} config={config} key="default-list-actions" viewType="list" />
|
|
89
|
+
].filter(Boolean)} collectionConfig={collectionConfig} Description={Description} disableBulkDelete={disableBulkDelete} disableBulkEdit={disableBulkEdit} hasCreatePermission={hasCreatePermission} hasDeletePermission={hasDeletePermission} i18n={i18n} isBulkUploadEnabled={isBulkUploadEnabled} isTrashEnabled={isTrashEnabled} newDocumentURL={newDocumentURL} onBulkUploadSuccess={() => router.refresh()} openBulkUpload={openBulkUpload} smallBreak={smallBreak} viewType={viewType} />
|
|
90
|
+
<ListControls collectionConfig={collectionConfig} collectionSlug={collectionSlug} enableColumns={false} />
|
|
91
|
+
|
|
92
|
+
{BeforeListTable}
|
|
93
|
+
|
|
94
|
+
{docs.length === 0 ? <div className={`${baseClass}__no-results`}>
|
|
95
|
+
<p>
|
|
96
|
+
{i18n.t(viewType === "trash" ? "general:noTrashResults" : "general:noResults", { label: getTranslation(labels?.plural, i18n) })}
|
|
97
|
+
</p>
|
|
98
|
+
{hasCreatePermission && newDocumentURL && <div className={`${baseClass}__no-results__actions`}>
|
|
99
|
+
<button type="button" onClick={() => router.push(newDocumentURL)} className={`${baseClass}__create-link btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup`}>
|
|
100
|
+
{i18n.t("general:createNewLabel", { label: getTranslation(labels?.singular, i18n) })}
|
|
101
|
+
</button>
|
|
102
|
+
</div>}
|
|
103
|
+
</div> : <ItemCardGrid items={mappedDocs} adminRoute={adminRoute} collectionSlug={collectionSlug} />}
|
|
104
|
+
|
|
105
|
+
{docs?.length > 0 && <PageControls AfterPageControls={smallBreak ? <div className={`${baseClass}__list-selection`}>
|
|
106
|
+
<ListSelection collectionConfig={collectionConfig} disableBulkDelete={disableBulkDelete} disableBulkEdit={disableBulkEdit} label={getTranslation(collectionConfig.labels.plural, i18n)} />
|
|
107
|
+
<div className={`${baseClass}__list-selection-actions`}>
|
|
108
|
+
{typeof onBulkSelect === "function" ? beforeActions ? [...beforeActions, <SelectMany key="select-many" onClick={onBulkSelect} />] : [<SelectMany key="select-many" onClick={onBulkSelect} />] : beforeActions}
|
|
109
|
+
</div>
|
|
110
|
+
</div> : null} collectionConfig={collectionConfig} />}
|
|
111
|
+
|
|
112
|
+
{AfterListTable}
|
|
113
|
+
</Gutter>
|
|
114
|
+
{AfterList}
|
|
115
|
+
</Fragment>
|
|
116
|
+
</div>
|
|
117
|
+
</GridProvider>
|
|
118
|
+
</SelectionProvider>;
|
|
119
|
+
}
|
|
120
|
+
var gridView_default = GridView;
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { GridView, gridView_default as default };
|
|
124
|
+
//# sourceMappingURL=gridView.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gridView.mjs","names":["mappedDocs: MappedDocument[]"],"sources":["../../../src/components/gridView/gridView.tsx"],"sourcesContent":["'use client'\n\nimport React, { Fragment, useMemo, useEffect } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n Gutter,\n useListQuery,\n useConfig,\n SelectionProvider,\n useTranslation,\n useWindowInfo,\n useBulkUpload,\n useModal,\n useStepNav,\n ListHeader,\n ListSelection,\n ListControls,\n PageControls,\n SelectMany,\n useListDrawerContext,\n} from '@payloadcms/ui'\nimport { DefaultListViewTabs } from '@payloadcms/ui/elements/DefaultListViewTabs'\nimport { formatAdminURL } from 'payload/shared'\nimport { ItemCardGrid } from '../itemCardGrid/itemCardGrid'\nimport { GridProvider } from '../gridContext/gridContext'\n\nimport type { ListViewClientProps } from 'payload'\n\nimport './gridView.css'\n\n// Type for media document with common properties\ninterface MediaDocument {\n id: string | number\n storage: 'mux' | 's3'\n filename?: string\n alt?: string\n mimeType?: string\n url?: string\n thumbnailURL?: string\n mux?: {\n playbackId?: string\n }\n}\n\nexport interface MappedDocument {\n id: string | number\n storage: 'mux' | 's3'\n title: string\n thumbnailURL?: string\n relationTo: string\n}\n\nexport function GridView(props: ListViewClientProps): React.JSX.Element {\n const baseClass = 'grid-view'\n\n const {\n collectionSlug,\n AfterList,\n AfterListTable,\n BeforeList,\n BeforeListTable,\n Description,\n newDocumentURL,\n hasCreatePermission,\n hasDeletePermission,\n disableBulkDelete,\n disableBulkEdit,\n beforeActions,\n viewType,\n } = props\n\n const { config, getEntityConfig } = useConfig()\n\n const {\n routes: { admin: adminRoute },\n } = config\n\n const router = useRouter()\n\n const { data } = useListQuery() ?? {}\n const { docs = [] } = data ?? {}\n\n const { i18n } = useTranslation()\n const { setStepNav } = useStepNav()\n const { openModal } = useModal()\n\n const {\n drawerSlug: bulkUploadDrawerSlug,\n setCollectionSlug: setBulkUploadCollectionSlug,\n setOnSuccess,\n } = useBulkUpload()\n\n const {\n breakpoints: { s: smallBreak },\n } = useWindowInfo()\n\n const collectionConfig = getEntityConfig({ collectionSlug })\n const { labels, upload } = collectionConfig\n\n const isUploadCollection = Boolean(upload)\n\n const isBulkUploadEnabled =\n (isUploadCollection && collectionConfig.upload?.bulkUpload) ?? true\n\n const isTrashEnabled = Boolean(collectionConfig.trash)\n\n const { onBulkSelect } = useListDrawerContext()\n\n const getThumbnailURL = (doc: MediaDocument): string | undefined => {\n if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=400&height=400&fit_mode=pad`\n }\n\n if (doc?.thumbnailURL) {\n return doc.thumbnailURL\n }\n\n return undefined\n }\n\n const mappedDocs: MappedDocument[] = useMemo(() => {\n return docs.map((doc: MediaDocument) => {\n const title = doc.filename ?? doc.alt ?? String(doc.id)\n return {\n id: doc.id,\n storage: doc.storage,\n title: title,\n thumbnailURL: getThumbnailURL(doc),\n relationTo: collectionSlug,\n }\n })\n }, [docs, collectionSlug])\n\n const openBulkUpload = React.useCallback(() => {\n setBulkUploadCollectionSlug(collectionSlug)\n openModal(bulkUploadDrawerSlug)\n setOnSuccess(() => router.refresh())\n }, [\n router,\n collectionSlug,\n bulkUploadDrawerSlug,\n openModal,\n setBulkUploadCollectionSlug,\n setOnSuccess,\n ])\n\n // Set breadcrumb navigation\n useEffect(() => {\n const baseLabel = {\n label: getTranslation(labels?.plural, i18n),\n url:\n isTrashEnabled && viewType === 'trash'\n ? formatAdminURL({\n adminRoute,\n path: `/collections/${collectionSlug}`,\n })\n : undefined,\n }\n const trashLabel = {\n label: i18n.t('general:trash'),\n }\n const navItems =\n isTrashEnabled && viewType === 'trash'\n ? [baseLabel, trashLabel]\n : [baseLabel]\n setStepNav(navItems)\n }, [\n adminRoute,\n setStepNav,\n labels,\n isTrashEnabled,\n viewType,\n i18n,\n collectionSlug,\n ])\n\n return (\n <SelectionProvider docs={docs} totalDocs={data?.totalDocs ?? 0}>\n <GridProvider allowMultiSelection={true} docs={mappedDocs}>\n <div className={`grid-view grid-view--${collectionSlug}`}>\n <Fragment>\n {BeforeList}\n <Gutter className={`${baseClass}__wrapper collection-list__wrap`}>\n <ListHeader\n Actions={[\n beforeActions,\n !smallBreak && (\n <ListSelection\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={\n collectionConfig.disableBulkEdit ?? disableBulkEdit\n }\n key=\"list-selection\"\n label={getTranslation(labels?.plural, i18n)}\n />\n ),\n <DefaultListViewTabs\n collectionConfig={collectionConfig}\n config={config}\n key=\"default-list-actions\"\n viewType=\"list\"\n />,\n ].filter(Boolean)}\n collectionConfig={collectionConfig}\n Description={Description}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n hasCreatePermission={hasCreatePermission}\n hasDeletePermission={hasDeletePermission}\n i18n={i18n}\n isBulkUploadEnabled={isBulkUploadEnabled}\n isTrashEnabled={isTrashEnabled}\n newDocumentURL={newDocumentURL}\n onBulkUploadSuccess={() => router.refresh()}\n openBulkUpload={openBulkUpload}\n smallBreak={smallBreak}\n viewType={viewType}\n />\n <ListControls\n collectionConfig={collectionConfig}\n collectionSlug={collectionSlug}\n enableColumns={false}\n />\n\n {BeforeListTable}\n\n {docs.length === 0 ? (\n <div className={`${baseClass}__no-results`}>\n <p>\n {i18n.t(\n viewType === 'trash'\n ? 'general:noTrashResults'\n : 'general:noResults',\n {\n label: getTranslation(labels?.plural, i18n),\n }\n )}\n </p>\n {hasCreatePermission && newDocumentURL && (\n <div className={`${baseClass}__no-results__actions`}>\n <button\n type=\"button\"\n onClick={() => router.push(newDocumentURL)}\n className={`${baseClass}__create-link btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup`}\n >\n {i18n.t('general:createNewLabel', {\n label: getTranslation(labels?.singular, i18n),\n })}\n </button>\n </div>\n )}\n </div>\n ) : (\n <ItemCardGrid\n items={mappedDocs}\n adminRoute={adminRoute}\n collectionSlug={collectionSlug}\n />\n )}\n\n {docs?.length > 0 && (\n <PageControls\n AfterPageControls={\n smallBreak ? (\n <div className={`${baseClass}__list-selection`}>\n <ListSelection\n collectionConfig={collectionConfig}\n disableBulkDelete={disableBulkDelete}\n disableBulkEdit={disableBulkEdit}\n label={getTranslation(\n collectionConfig.labels.plural,\n i18n\n )}\n />\n <div className={`${baseClass}__list-selection-actions`}>\n {typeof onBulkSelect === 'function'\n ? beforeActions\n ? [\n ...beforeActions,\n <SelectMany\n key=\"select-many\"\n onClick={onBulkSelect}\n />,\n ]\n : [\n <SelectMany\n key=\"select-many\"\n onClick={onBulkSelect}\n />,\n ]\n : beforeActions}\n </div>\n </div>\n ) : null\n }\n collectionConfig={collectionConfig}\n />\n )}\n\n {AfterListTable}\n </Gutter>\n {AfterList}\n </Fragment>\n </div>\n </GridProvider>\n </SelectionProvider>\n )\n}\n\nexport default GridView\n"],"mappings":"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"}
|