@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.
Files changed (157) hide show
  1. package/dist/adapter/handleDelete.d.mts +3 -3
  2. package/dist/adapter/handleDelete.mjs +6 -6
  3. package/dist/adapter/handleDelete.mjs.map +1 -1
  4. package/dist/adapter/staticHandler.d.mts +5 -3
  5. package/dist/adapter/staticHandler.mjs +8 -7
  6. package/dist/adapter/staticHandler.mjs.map +1 -1
  7. package/dist/adapter/storageAdapter.d.mts +3 -3
  8. package/dist/adapter/storageAdapter.mjs +4 -4
  9. package/dist/adapter/storageAdapter.mjs.map +1 -1
  10. package/dist/collectionHooks/afterChange.d.mts +7 -0
  11. package/dist/collectionHooks/afterChange.mjs +39 -0
  12. package/dist/collectionHooks/afterChange.mjs.map +1 -0
  13. package/dist/collectionHooks/beforeChange.d.mts +7 -0
  14. package/dist/collectionHooks/beforeChange.mjs +33 -0
  15. package/dist/collectionHooks/beforeChange.mjs.map +1 -0
  16. package/dist/collections/mediaCollection.d.mts +3 -2
  17. package/dist/collections/mediaCollection.mjs +46 -198
  18. package/dist/collections/mediaCollection.mjs.map +1 -1
  19. package/dist/components/folderFileCard/folderFileCard.d.mts +13 -0
  20. package/dist/components/folderFileCard/folderFileCard.mjs +30 -0
  21. package/dist/components/folderFileCard/folderFileCard.mjs.map +1 -0
  22. package/dist/components/gridContext/gridContext.d.mts +51 -0
  23. package/dist/components/gridContext/gridContext.mjs +227 -0
  24. package/dist/components/gridContext/gridContext.mjs.map +1 -0
  25. package/dist/components/gridView/gridView.css +50 -0
  26. package/dist/components/gridView/gridView.d.mts +16 -0
  27. package/dist/components/gridView/gridView.mjs +124 -0
  28. package/dist/components/gridView/gridView.mjs.map +1 -0
  29. package/dist/components/gridView/index.d.mts +2 -0
  30. package/dist/components/gridView/index.mjs +3 -0
  31. package/dist/components/index.d.mts +9 -7
  32. package/dist/components/index.mjs +5 -4
  33. package/dist/components/itemCardGrid/itemCardGrid.css +12 -0
  34. package/dist/components/itemCardGrid/itemCardGrid.d.mts +18 -0
  35. package/dist/components/itemCardGrid/itemCardGrid.mjs +22 -0
  36. package/dist/components/itemCardGrid/itemCardGrid.mjs.map +1 -0
  37. package/dist/components/muxPreview/index.d.mts +2 -0
  38. package/dist/components/muxPreview/index.mjs +3 -0
  39. package/dist/components/{mux-preview/mux-preview.d.mts → muxPreview/muxPreview.d.mts} +2 -2
  40. package/dist/components/{mux-preview/mux-preview.mjs → muxPreview/muxPreview.mjs} +2 -2
  41. package/dist/components/muxPreview/muxPreview.mjs.map +1 -0
  42. package/dist/components/uploadHandler/index.d.mts +2 -0
  43. package/dist/components/uploadHandler/index.mjs +3 -0
  44. package/dist/components/uploadHandler/uploadHandler.d.mts +20 -0
  45. package/dist/components/{upload-handler/upload-handler.mjs → uploadHandler/uploadHandler.mjs} +82 -52
  46. package/dist/components/uploadHandler/uploadHandler.mjs.map +1 -0
  47. package/dist/components/uploadManager/index.d.mts +2 -0
  48. package/dist/components/uploadManager/index.mjs +3 -0
  49. package/dist/components/{upload-manager/upload-manager.d.mts → uploadManager/uploadManager.d.mts} +3 -3
  50. package/dist/components/{upload-manager/upload-manager.mjs → uploadManager/uploadManager.mjs} +3 -3
  51. package/dist/components/uploadManager/uploadManager.mjs.map +1 -0
  52. package/dist/endpoints/fileExistsHandler.d.mts +12 -0
  53. package/dist/endpoints/{tusFileExistsHandler.mjs → fileExistsHandler.mjs} +7 -7
  54. package/dist/endpoints/fileExistsHandler.mjs.map +1 -0
  55. package/dist/endpoints/muxAssetHandler.d.mts +1 -0
  56. package/dist/endpoints/muxAssetHandler.mjs +3 -3
  57. package/dist/endpoints/muxAssetHandler.mjs.map +1 -1
  58. package/dist/endpoints/muxWebhookHandler.d.mts +1 -0
  59. package/dist/endpoints/muxWebhookHandler.mjs +6 -5
  60. package/dist/endpoints/muxWebhookHandler.mjs.map +1 -1
  61. package/dist/endpoints/tusCleanupHandler.d.mts +3 -2
  62. package/dist/endpoints/tusCleanupHandler.mjs +4 -4
  63. package/dist/endpoints/tusCleanupHandler.mjs.map +1 -1
  64. package/dist/endpoints/tusFolderHandler.d.mts +12 -0
  65. package/dist/endpoints/tusFolderHandler.mjs +39 -0
  66. package/dist/endpoints/tusFolderHandler.mjs.map +1 -0
  67. package/dist/endpoints/tusPostProcessorHandler.d.mts +3 -2
  68. package/dist/endpoints/tusPostProcessorHandler.mjs +6 -5
  69. package/dist/endpoints/tusPostProcessorHandler.mjs.map +1 -1
  70. package/dist/fields/alt.d.mts +7 -0
  71. package/dist/fields/alt.mjs +10 -0
  72. package/dist/fields/alt.mjs.map +1 -0
  73. package/dist/fields/filename.d.mts +7 -0
  74. package/dist/fields/filename.mjs +14 -0
  75. package/dist/fields/filename.mjs.map +1 -0
  76. package/dist/fields/height.d.mts +7 -0
  77. package/dist/fields/height.mjs +15 -0
  78. package/dist/fields/height.mjs.map +1 -0
  79. package/dist/fields/mux.d.mts +7 -0
  80. package/dist/fields/mux.mjs +149 -0
  81. package/dist/fields/mux.mjs.map +1 -0
  82. package/dist/fields/path.d.mts +7 -0
  83. package/dist/fields/path.mjs +14 -0
  84. package/dist/fields/path.mjs.map +1 -0
  85. package/dist/fields/storage.d.mts +7 -0
  86. package/dist/fields/storage.mjs +18 -0
  87. package/dist/fields/storage.mjs.map +1 -0
  88. package/dist/fields/thumbnail.d.mts +7 -0
  89. package/dist/fields/thumbnail.mjs +17 -0
  90. package/dist/fields/thumbnail.mjs.map +1 -0
  91. package/dist/fields/width.d.mts +7 -0
  92. package/dist/fields/width.mjs +15 -0
  93. package/dist/fields/width.mjs.map +1 -0
  94. package/dist/hooks/useErrorHandler.d.mts +1 -1
  95. package/dist/hooks/useErrorHandler.mjs +4 -2
  96. package/dist/hooks/useErrorHandler.mjs.map +1 -1
  97. package/dist/plugin.d.mts +5 -4
  98. package/dist/plugin.mjs +53 -29
  99. package/dist/plugin.mjs.map +1 -1
  100. package/dist/tus/stores/s3/{expiration-manager.d.mts → expirationManager.d.mts} +4 -3
  101. package/dist/tus/stores/s3/{expiration-manager.mjs → expirationManager.mjs} +6 -3
  102. package/dist/tus/stores/s3/expirationManager.mjs.map +1 -0
  103. package/dist/tus/stores/s3/{file-operations.d.mts → fileOperations.d.mts} +2 -2
  104. package/dist/tus/stores/s3/{file-operations.mjs → fileOperations.mjs} +2 -2
  105. package/dist/tus/stores/s3/fileOperations.mjs.map +1 -0
  106. package/dist/tus/stores/s3/index.d.mts +1 -1
  107. package/dist/tus/stores/s3/index.mjs +20 -9
  108. package/dist/tus/stores/s3/index.mjs.map +1 -1
  109. package/dist/tus/stores/s3/{metadata-manager.d.mts → metadataManager.d.mts} +4 -2
  110. package/dist/tus/stores/s3/{metadata-manager.mjs → metadataManager.mjs} +6 -5
  111. package/dist/tus/stores/s3/metadataManager.mjs.map +1 -0
  112. package/dist/tus/stores/s3/{parts-manager.d.mts → partsManager.d.mts} +4 -4
  113. package/dist/tus/stores/s3/{parts-manager.mjs → partsManager.mjs} +67 -29
  114. package/dist/tus/stores/s3/partsManager.mjs.map +1 -0
  115. package/dist/tus/stores/s3/{s3-store.d.mts → s3Store.d.mts} +38 -32
  116. package/dist/tus/stores/s3/{s3-store.mjs → s3Store.mjs} +102 -57
  117. package/dist/tus/stores/s3/s3Store.mjs.map +1 -0
  118. package/dist/types/errors.d.mts +32 -0
  119. package/dist/types/errors.mjs +32 -0
  120. package/dist/types/errors.mjs.map +1 -1
  121. package/dist/types/index.d.mts +42 -4
  122. package/dist/utils/buildS3Path.d.mts +10 -0
  123. package/dist/utils/buildS3Path.mjs +16 -0
  124. package/dist/utils/buildS3Path.mjs.map +1 -0
  125. package/dist/utils/buildThumbnailURL.d.mts +14 -0
  126. package/dist/utils/buildThumbnailURL.mjs +10 -0
  127. package/dist/utils/buildThumbnailURL.mjs.map +1 -0
  128. package/dist/utils/defaultOptions.d.mts +7 -0
  129. package/dist/utils/defaultOptions.mjs +12 -0
  130. package/dist/utils/defaultOptions.mjs.map +1 -0
  131. package/dist/utils/file.d.mts +16 -2
  132. package/dist/utils/file.mjs +58 -6
  133. package/dist/utils/file.mjs.map +1 -1
  134. package/dist/utils/mux.mjs +19 -8
  135. package/dist/utils/mux.mjs.map +1 -1
  136. package/dist/utils/tus.d.mts +9 -6
  137. package/dist/utils/tus.mjs +31 -11
  138. package/dist/utils/tus.mjs.map +1 -1
  139. package/package.json +9 -4
  140. package/dist/components/mux-preview/index.d.mts +0 -2
  141. package/dist/components/mux-preview/index.mjs +0 -3
  142. package/dist/components/mux-preview/mux-preview.mjs.map +0 -1
  143. package/dist/components/upload-handler/index.d.mts +0 -2
  144. package/dist/components/upload-handler/index.mjs +0 -3
  145. package/dist/components/upload-handler/upload-handler.d.mts +0 -22
  146. package/dist/components/upload-handler/upload-handler.mjs.map +0 -1
  147. package/dist/components/upload-manager/index.d.mts +0 -2
  148. package/dist/components/upload-manager/index.mjs +0 -3
  149. package/dist/components/upload-manager/upload-manager.mjs.map +0 -1
  150. package/dist/endpoints/tusFileExistsHandler.d.mts +0 -11
  151. package/dist/endpoints/tusFileExistsHandler.mjs.map +0 -1
  152. package/dist/tus/stores/s3/expiration-manager.mjs.map +0 -1
  153. package/dist/tus/stores/s3/file-operations.mjs.map +0 -1
  154. package/dist/tus/stores/s3/metadata-manager.mjs.map +0 -1
  155. package/dist/tus/stores/s3/parts-manager.mjs.map +0 -1
  156. package/dist/tus/stores/s3/s3-store.mjs.map +0 -1
  157. /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":";;;;;;;;;;;;;AAqDA,SAAgB,SAAS,OAA+C;CACtE,MAAM,YAAY;CAElB,MAAM,EACJ,gBACA,WACA,gBACA,YACA,iBACA,aACA,gBACA,qBACA,qBACA,mBACA,iBACA,eACA,aACE;CAEJ,MAAM,EAAE,QAAQ,oBAAoB,WAAW;CAE/C,MAAM,EACJ,QAAQ,EAAE,OAAO,iBACf;CAEJ,MAAM,SAAS,WAAW;CAE1B,MAAM,EAAE,SAAS,cAAc,IAAI,EAAE;CACrC,MAAM,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE;CAEhC,MAAM,EAAE,SAAS,gBAAgB;CACjC,MAAM,EAAE,eAAe,YAAY;CACnC,MAAM,EAAE,cAAc,UAAU;CAEhC,MAAM,EACJ,YAAY,sBACZ,mBAAmB,6BACnB,iBACE,eAAe;CAEnB,MAAM,EACJ,aAAa,EAAE,GAAG,iBAChB,eAAe;CAEnB,MAAM,mBAAmB,gBAAgB,EAAE,gBAAgB,CAAC;CAC5D,MAAM,EAAE,QAAQ,WAAW;CAI3B,MAAM,uBAFqB,QAAQ,OAAO,IAGjB,iBAAiB,QAAQ,eAAe;CAEjE,MAAM,iBAAiB,QAAQ,iBAAiB,MAAM;CAEtD,MAAM,EAAE,iBAAiB,sBAAsB;CAE/C,MAAM,mBAAmB,QAA2C;AAClE,MAAI,KAAK,YAAY,SAAS,KAAK,KAAK,WACtC,QAAO,yBAAyB,IAAI,IAAI,WAAW;AAGrD,MAAI,KAAK,aACP,QAAO,IAAI;;CAMf,MAAMA,aAA+B,cAAc;AACjD,SAAO,KAAK,KAAK,QAAuB;GACtC,MAAM,QAAQ,IAAI,YAAY,IAAI,OAAO,OAAO,IAAI,GAAG;AACvD,UAAO;IACL,IAAI,IAAI;IACR,SAAS,IAAI;IACN;IACP,cAAc,gBAAgB,IAAI;IAClC,YAAY;IACb;IACD;IACD,CAAC,MAAM,eAAe,CAAC;CAE1B,MAAM,iBAAiB,MAAM,kBAAkB;AAC7C,8BAA4B,eAAe;AAC3C,YAAU,qBAAqB;AAC/B,qBAAmB,OAAO,SAAS,CAAC;IACnC;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAGF,iBAAgB;EACd,MAAM,YAAY;GAChB,OAAO,eAAe,QAAQ,QAAQ,KAAK;GAC3C,KACE,kBAAkB,aAAa,UAC3B,eAAe;IACb;IACA,MAAM,gBAAgB;IACvB,CAAC,GACF;GACP;EACD,MAAM,aAAa,EACjB,OAAO,KAAK,EAAE,gBAAgB,EAC/B;AAKD,aAHE,kBAAkB,aAAa,UAC3B,CAAC,WAAW,WAAW,GACvB,CAAC,UAAU,CACG;IACnB;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QACE,CAAC,kBAAkB,MAAM,MAAM,WAAW,MAAM,aAAa,GAAG;MAC9D,CAAC,aAAa,qBAAqB,MAAM,MAAM,YAAY;QACzD,CAAC,IAAI,WAAW,wBAAwB,kBAAkB;UACxD,CAAC,SAAS;aACP,WAAW;YACZ,CAAC,OAAO,WAAW,GAAG,UAAU,kCAAkC;cAChE,CAAC,WACC,SAAS;EACP;EACA,CAAC,cACC,CAAC,cACC,mBAAmB,mBACnB,iBACE,iBAAiB,mBAAmB,iBAEtC,qBACA,OAAO,eAAe,QAAQ,QAAQ,KAAK;EAG/C,CAAC,oBACC,kBAAkB,kBAClB,QAAQ,QACR,2BACA;EAEH,CAAC,OAAO,QAAQ,EACjB,kBAAkB,kBAClB,aAAa,aACb,mBAAmB,mBACnB,iBAAiB,iBACjB,qBAAqB,qBACrB,qBAAqB,qBACrB,MAAM,MACN,qBAAqB,qBACrB,gBAAgB,gBAChB,gBAAgB,gBAChB,2BAA2B,OAAO,SAAS,EAC3C,gBAAgB,gBAChB,YAAY,YACZ,UAAU,YACV;cACF,CAAC,aACC,kBAAkB,kBAClB,gBAAgB,gBAChB,eAAe,SACf;;eAED,gBAAgB;;eAEhB,KAAK,WAAW,IACf,CAAC,IAAI,WAAW,GAAG,UAAU,eAAe;kBAC1C,CAAC,EAAE;qBACA,KAAK,EACJ,aAAa,UACT,2BACA,qBACJ,EACE,OAAO,eAAe,QAAQ,QAAQ,KAAK,EAC5C,CACF,CAAC;kBACJ,EAAE,EAAE;mBACH,uBAAuB,kBACtB,CAAC,IAAI,WAAW,GAAG,UAAU,wBAAwB;sBACnD,CAAC,OACC,cACA,eAAe,OAAO,KAAK,eAAe,EAC1C,WAAW,GAAG,UAAU,uHACzB;yBACE,KAAK,EAAE,0BAA0B,EAChC,OAAO,eAAe,QAAQ,UAAU,KAAK,EAC9C,CAAC,CAAC;sBACL,EAAE,OAAO;oBACX,EAAE,KACF;gBACJ,EAAE,OAEF,CAAC,aACC,OAAO,YACP,YAAY,YACZ,gBAAgB,mBAElB;;eAED,MAAM,SAAS,KACd,CAAC,aACC,mBACE,aACE,CAAC,IAAI,WAAW,GAAG,UAAU,mBAAmB;wBAC9C,CAAC,cACC,kBAAkB,kBAClB,mBAAmB,mBACnB,iBAAiB,iBACjB,OAAO,eACL,iBAAiB,OAAO,QACxB,KACD,IACD;wBACF,CAAC,IAAI,WAAW,GAAG,UAAU,2BAA2B;2BACrD,OAAO,iBAAiB,aACrB,gBACE,CACE,GAAG,eACH,CAAC,WACC,kBACA,SAAS,iBAEZ,GACD,CACE,CAAC,WACC,kBACA,SAAS,iBAEZ,GACH,cAAc;wBACpB,EAAE,IAAI;sBACR,EAAE,OACA,MAEN,kBAAkB,qBAEpB;;eAED,eAAe;YAClB,EAAE,OAAO;aACR,UAAU;UACb,EAAE,SAAS;QACb,EAAE,IAAI;MACR,EAAE,aAAa;IACjB,EAAE;;AAIN,uBAAe"}
@@ -0,0 +1,2 @@
1
+ import { GridView } from "./gridView.mjs";
2
+ export { GridView };
@@ -0,0 +1,3 @@
1
+ import { GridView } from "./gridView.mjs";
2
+
3
+ export { GridView };