@wordpress/media-utils 5.41.1-next.v.202603161435.0 → 5.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/build/components/media-upload-modal/index.cjs +133 -87
- package/build/components/media-upload-modal/index.cjs.map +3 -3
- package/build/components/media-upload-modal/upload-status-popover.cjs +156 -0
- package/build/components/media-upload-modal/upload-status-popover.cjs.map +7 -0
- package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs +45 -0
- package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs.map +7 -0
- package/build/components/media-upload-modal/use-upload-status.cjs +127 -0
- package/build/components/media-upload-modal/use-upload-status.cjs.map +7 -0
- package/build-module/components/media-upload-modal/index.mjs +126 -88
- package/build-module/components/media-upload-modal/index.mjs.map +2 -2
- package/build-module/components/media-upload-modal/upload-status-popover.mjs +131 -0
- package/build-module/components/media-upload-modal/upload-status-popover.mjs.map +7 -0
- package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs +20 -0
- package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs.map +7 -0
- package/build-module/components/media-upload-modal/use-upload-status.mjs +102 -0
- package/build-module/components/media-upload-modal/use-upload-status.mjs.map +7 -0
- package/build-style/style-rtl.css +73 -3
- package/build-style/style.css +73 -3
- package/build-types/components/media-upload-modal/index.d.ts.map +1 -1
- package/build-types/components/media-upload-modal/upload-status-popover.d.ts +15 -0
- package/build-types/components/media-upload-modal/upload-status-popover.d.ts.map +1 -0
- package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts +22 -0
- package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts.map +1 -0
- package/build-types/components/media-upload-modal/use-upload-status.d.ts +41 -0
- package/build-types/components/media-upload-modal/use-upload-status.d.ts.map +1 -0
- package/package.json +17 -15
- package/src/components/media-upload-modal/index.tsx +129 -107
- package/src/components/media-upload-modal/style.scss +88 -3
- package/src/components/media-upload-modal/test/use-upload-status.test.ts +501 -0
- package/src/components/media-upload-modal/upload-status-popover.tsx +155 -0
- package/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts +50 -0
- package/src/components/media-upload-modal/use-upload-status.ts +203 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/media-upload-modal/index.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type { View, Field, ActionButton } from '@wordpress/dataviews';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\nimport { isBlobURL } from '@wordpress/blob';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, createErrorNotice, createInfoNotice } =\n\t\tuseDispatch( noticesStore );\n\tconst { invalidateResolution } = useDispatch( coreStore );\n\n\t// DataViews configuration - allow view updates\n\tconst [ view, setView ] = useState< View >( () => ( {\n\t\ttype: LAYOUT_PICKER_GRID,\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\ttitleField: 'title',\n\t\tmediaField: 'media_thumbnail',\n\t\tsearch: '',\n\t\tpage: 1,\n\t\tperPage: 50,\n\t\tfilters: [],\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t} ) );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: multiple ? __( 'Select' ) : __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tonClose?.();\n\t}, [ onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Shared upload success handler\n\tconst handleUploadComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\t// Check if all uploads are complete (no blob URLs)\n\t\t\tconst allComplete = attachments.every(\n\t\t\t\t( attachment ) =>\n\t\t\t\t\tattachment.id &&\n\t\t\t\t\tattachment.url &&\n\t\t\t\t\t! isBlobURL( attachment.url )\n\t\t\t);\n\n\t\t\tif ( allComplete && attachments.length > 0 ) {\n\t\t\t\t// Show success notice (replaces progress notice via ID)\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tattachments.length\n\t\t\t\t\t\t),\n\t\t\t\t\t\tattachments.length.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t\t// Auto-select the newly uploaded items\n\t\t\t\tconst uploadedIds = attachments\n\t\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\tif ( multiple ) {\n\t\t\t\t\t// In multiple mode, add to existing selection\n\t\t\t\t\tsetSelection( ( prev ) => [ ...prev, ...uploadedIds ] );\n\t\t\t\t} else {\n\t\t\t\t\t// In single mode, replace selection with the first uploaded item\n\t\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t\t}\n\n\t\t\t\t// Invalidate the entity records resolution to refresh the view\n\t\t\t\tinvalidateResolution( 'getEntityRecords', [\n\t\t\t\t\t'postType',\n\t\t\t\t\t'attachment',\n\t\t\t\t\tqueryArgs,\n\t\t\t\t] );\n\t\t\t}\n\t\t},\n\t\t[ createSuccessNotice, invalidateResolution, queryArgs, multiple ]\n\t);\n\n\t// Shared upload error handler\n\tconst handleUploadError = useCallback(\n\t\t( error: Error ) => {\n\t\t\t// Show error notice (replaces progress notice via ID)\n\t\t\tcreateErrorNotice( error.message, {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t} );\n\t\t},\n\t\t[ createErrorNotice ]\n\t);\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\n\t\t\t\t// Show upload start notice\n\t\t\t\tcreateInfoNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploading %s file',\n\t\t\t\t\t\t\t'Uploading %s files',\n\t\t\t\t\t\t\tfilesArray.length\n\t\t\t\t\t\t),\n\t\t\t\t\t\tfilesArray.length.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t\texplicitDismiss: true,\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange: handleUploadComplete,\n\t\t\t\t\tonError: handleUploadError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tallowedTypes,\n\t\t\thandleUpload,\n\t\t\tcreateInfoNotice,\n\t\t\thandleUploadComplete,\n\t\t\thandleUploadError,\n\t\t]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\tconst defaultLayouts = useMemo(\n\t\t() => ( {\n\t\t\t[ LAYOUT_PICKER_GRID ]: {\n\t\t\t\tfields: [],\n\t\t\t\tshowTitle: false,\n\t\t\t},\n\t\t\t[ LAYOUT_PICKER_TABLE ]: {\n\t\t\t\tfields: [\n\t\t\t\t\t'filename',\n\t\t\t\t\t'filesize',\n\t\t\t\t\t'media_dimensions',\n\t\t\t\t\t'author',\n\t\t\t\t\t'date',\n\t\t\t\t],\n\t\t\t\tshowTitle: true,\n\t\t\t},\n\t\t} ),\n\t\t[]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\t// Show upload start notice\n\t\t\t\t\t\tcreateInfoNotice(\n\t\t\t\t\t\t\tsprintf(\n\t\t\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t\t\t'Uploading %s file',\n\t\t\t\t\t\t\t\t\t'Uploading %s files',\n\t\t\t\t\t\t\t\t\tfilteredFiles.length\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tfilteredFiles.length.toLocaleString()\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t\t\t\texplicitDismiss: true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange: handleUploadComplete,\n\t\t\t\t\t\t\tonError: handleUploadError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ setView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\tsearch={ search }\n\t\t\t\tsearchLabel={ searchLabel }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t/>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
-
"mappings": ";AAGA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,IAAI,SAAS,UAAU;AAChC;AAAA,EACC,eAAe;AAAA,EACf,SAAS;AAAA,OACH;AACP,SAAS,eAAe,mBAAmB;AAC3C,SAAS,OAAO,UAAU,gBAAgB,cAAc;AACxD,SAAS,UAAU,kBAAkB;AACrC,SAAS,uBAAuB;AAEhC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,SAAS,cAAc,uBAAuB;
|
|
4
|
+
"sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\n\n/**\n * WordPress dependencies\n */\nimport {\n\tcreatePortal,\n\tuseState,\n\tuseCallback,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n} from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport {\n\tprivateApis as coreDataPrivateApis,\n\tstore as coreStore,\n} from '@wordpress/core-data';\nimport { resolveSelect, useDispatch } from '@wordpress/data';\nimport { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';\nimport { upload as uploadIcon } from '@wordpress/icons';\nimport { DataViewsPicker } from '@wordpress/dataviews';\nimport type { View, Field, ActionButton } from '@wordpress/dataviews';\nimport { Stack } from '@wordpress/ui';\nimport {\n\taltTextField,\n\tattachedToField,\n\tauthorField,\n\tcaptionField,\n\tdateAddedField,\n\tdateModifiedField,\n\tdescriptionField,\n\tfilenameField,\n\tfilesizeField,\n\tmediaDimensionsField,\n\tmediaThumbnailField,\n\tmimeTypeField,\n} from '@wordpress/media-fields';\nimport { store as noticesStore, SnackbarNotices } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment, RestAttachment } from '../../utils/types';\nimport { transformAttachment } from '../../utils/transform-attachment';\nimport { uploadMedia } from '../../utils/upload-media';\nimport { unlock } from '../../lock-unlock';\nimport { UploadStatusPopover } from './upload-status-popover';\nimport { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';\nimport { useUploadStatus } from './use-upload-status';\n\nconst { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );\n\n// Layout constants - matching the picker layout types\nconst LAYOUT_PICKER_GRID = 'pickerGrid';\nconst LAYOUT_PICKER_TABLE = 'pickerTable';\n\n// Custom notices context for the media modal\nconst NOTICES_CONTEXT = 'media-modal';\n\n// Notice ID - reused for all upload-related notices to prevent flooding\nconst NOTICE_ID_UPLOAD_PROGRESS = 'media-modal-upload-progress';\n\ninterface MediaUploadModalProps {\n\t/**\n\t * Array of allowed media types.\n\t */\n\tallowedTypes?: string[];\n\n\t/**\n\t * Whether multiple files can be selected.\n\t * @default false\n\t */\n\tmultiple?: boolean;\n\n\t/**\n\t * The currently selected media item(s).\n\t * Can be a single ID number or array of IDs for multiple selection.\n\t */\n\tvalue?: number | number[];\n\n\t/**\n\t * Function called when media is selected.\n\t * Receives single attachment object or array of attachments.\n\t */\n\tonSelect: ( media: Attachment | Attachment[] ) => void;\n\n\t/**\n\t * Function called when the modal is closed without selection.\n\t */\n\tonClose?: () => void;\n\n\t/**\n\t * Function to handle media uploads.\n\t * If not provided, drag and drop will be disabled.\n\t */\n\tonUpload?: ( args: {\n\t\tallowedTypes?: string[];\n\t\tfilesList: File[];\n\t\tonFileChange?: ( attachments: Partial< Attachment >[] ) => void;\n\t\tonError?: ( error: Error ) => void;\n\t\tmultiple?: boolean;\n\t} ) => void;\n\n\t/**\n\t * Title for the modal.\n\t * @default 'Select Media'\n\t */\n\ttitle?: string;\n\n\t/**\n\t * Whether the modal is open.\n\t */\n\tisOpen: boolean;\n\n\t/**\n\t * Whether the modal can be closed by clicking outside or pressing escape.\n\t * @default true\n\t */\n\tisDismissible?: boolean;\n\n\t/**\n\t * Additional CSS class for the modal.\n\t */\n\tmodalClass?: string;\n\n\t/**\n\t * Whether to show a search input.\n\t * @default true\n\t */\n\tsearch?: boolean;\n\n\t/**\n\t * Label for the search input.\n\t */\n\tsearchLabel?: string;\n}\n\n/**\n * MediaUploadModal component that uses Modal and DataViewsPicker for media selection.\n *\n * This is a modern functional component alternative to the legacy MediaUpload class component.\n * It provides a cleaner API and better integration with the WordPress block editor.\n *\n * @param props Component props\n * @param props.allowedTypes Array of allowed media types\n * @param props.multiple Whether multiple files can be selected\n * @param props.value Currently selected media item(s)\n * @param props.onSelect Function called when media is selected\n * @param props.onClose Function called when modal is closed\n * @param props.onUpload Function to handle media uploads\n * @param props.title Title for the modal\n * @param props.isOpen Whether the modal is open\n * @param props.isDismissible Whether modal can be dismissed\n * @param props.modalClass Additional CSS class for modal\n * @param props.search Whether to show search input\n * @param props.searchLabel Label for search input\n * @return JSX element or null\n */\nexport function MediaUploadModal( {\n\tallowedTypes,\n\tmultiple = false,\n\tvalue,\n\tonSelect,\n\tonClose,\n\tonUpload,\n\ttitle = __( 'Select Media' ),\n\tisOpen,\n\tisDismissible = true,\n\tmodalClass,\n\tsearch = true,\n\tsearchLabel = __( 'Search media' ),\n}: MediaUploadModalProps ) {\n\tconst [ selection, setSelection ] = useState< string[] >( () => {\n\t\tif ( ! value ) {\n\t\t\treturn [];\n\t\t}\n\t\treturn Array.isArray( value )\n\t\t\t? value.map( String )\n\t\t\t: [ String( value ) ];\n\t} );\n\n\tconst { createSuccessNotice, removeAllNotices } =\n\t\tuseDispatch( noticesStore );\n\tconst invalidateAttachmentResolutions =\n\t\tuseInvalidateAttachmentResolutions();\n\n\t// DataViews configuration - allow view updates\n\tconst [ view, setView ] = useState< View >( () => ( {\n\t\ttype: LAYOUT_PICKER_GRID,\n\t\tfields: [],\n\t\tshowTitle: false,\n\t\ttitleField: 'title',\n\t\tmediaField: 'media_thumbnail',\n\t\tsearch: '',\n\t\tpage: 1,\n\t\tperPage: 50,\n\t\tfilters: [],\n\t\tlayout: {\n\t\t\tpreviewSize: 170,\n\t\t\tdensity: 'compact',\n\t\t},\n\t} ) );\n\n\t// Build query args based on view properties, similar to PostList\n\tconst queryArgs = useMemo( () => {\n\t\tconst filters: Record< string, any > = {};\n\n\t\tview.filters?.forEach( ( filter ) => {\n\t\t\t// Handle media type filters\n\t\t\tif ( filter.field === 'media_type' ) {\n\t\t\t\tfilters.media_type = filter.value;\n\t\t\t}\n\t\t\t// Handle author filters\n\t\t\tif ( filter.field === 'author' ) {\n\t\t\t\tif ( filter.operator === 'isAny' ) {\n\t\t\t\t\tfilters.author = filter.value;\n\t\t\t\t} else if ( filter.operator === 'isNone' ) {\n\t\t\t\t\tfilters.author_exclude = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle date filters\n\t\t\tif ( filter.field === 'date' || filter.field === 'modified' ) {\n\t\t\t\tif ( filter.operator === 'before' ) {\n\t\t\t\t\tfilters.before = filter.value;\n\t\t\t\t} else if ( filter.operator === 'after' ) {\n\t\t\t\t\tfilters.after = filter.value;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle mime type filters\n\t\t\tif ( filter.field === 'mime_type' ) {\n\t\t\t\tfilters.mime_type = filter.value;\n\t\t\t}\n\t\t} );\n\n\t\t// Base media type on allowedTypes if no filter is set\n\t\tif ( ! filters.media_type ) {\n\t\t\tfilters.media_type = allowedTypes?.includes( '*' )\n\t\t\t\t? undefined\n\t\t\t\t: allowedTypes;\n\t\t}\n\n\t\treturn {\n\t\t\tper_page: view.perPage || 20,\n\t\t\tpage: view.page || 1,\n\t\t\tstatus: 'inherit',\n\t\t\torder: view.sort?.direction,\n\t\t\torderby: view.sort?.field,\n\t\t\tsearch: view.search,\n\t\t\t_embed: 'author,wp:attached-to',\n\t\t\t...filters,\n\t\t};\n\t}, [ view, allowedTypes ] );\n\n\t// Per-batch completion handler: auto-select uploaded items and refresh the grid.\n\tconst handleBatchComplete = useCallback(\n\t\t( attachments: Partial< Attachment >[] ) => {\n\t\t\tconst uploadedIds = attachments\n\t\t\t\t.map( ( attachment ) => String( attachment.id ) )\n\t\t\t\t.filter( Boolean );\n\n\t\t\tif ( multiple ) {\n\t\t\t\tsetSelection( ( prev ) => {\n\t\t\t\t\tconst existing = new Set( prev );\n\t\t\t\t\tconst newIds = uploadedIds.filter(\n\t\t\t\t\t\t( id ) => ! existing.has( id )\n\t\t\t\t\t);\n\t\t\t\t\treturn [ ...prev, ...newIds ];\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tsetSelection( uploadedIds.slice( 0, 1 ) );\n\t\t\t}\n\n\t\t\t// Invalidate all cached attachment queries so every page of\n\t\t\t// results refreshes \u2014 not just the page the user is viewing.\n\t\t\tinvalidateAttachmentResolutions();\n\t\t},\n\t\t[ multiple, invalidateAttachmentResolutions ]\n\t);\n\n\tconst {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t} = useUploadStatus( { onBatchComplete: handleBatchComplete } );\n\n\tconst isPopoverOpenRef = useRef( false );\n\tconst handlePopoverOpenChange = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tisPopoverOpenRef.current = open;\n\t\t\tif ( ! open ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t},\n\t\t[ clearCompleted ]\n\t);\n\n\t// Fetch all media attachments using WordPress core data with permissions\n\tconst {\n\t\trecords: mediaRecords,\n\t\tisResolving: isLoading,\n\t\ttotalItems,\n\t\ttotalPages,\n\t} = useEntityRecordsWithPermissions( 'postType', 'attachment', queryArgs );\n\n\tconst fields: Field< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t// Media field definitions from @wordpress/media-fields\n\t\t\t// Cast is safe because RestAttachment has the same properties as Attachment\n\t\t\t{\n\t\t\t\t...( mediaThumbnailField as Field< RestAttachment > ),\n\t\t\t\tenableHiding: false, // Within the modal, the thumbnail should always be shown.\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'title',\n\t\t\t\ttype: 'text' as const,\n\t\t\t\tlabel: __( 'Title' ),\n\t\t\t\tgetValue: ( { item }: { item: RestAttachment } ) => {\n\t\t\t\t\tconst titleValue = item.title.raw || item.title.rendered;\n\t\t\t\t\treturn titleValue || __( '(no title)' );\n\t\t\t\t},\n\t\t\t},\n\t\t\taltTextField as Field< RestAttachment >,\n\t\t\tcaptionField as Field< RestAttachment >,\n\t\t\tdescriptionField as Field< RestAttachment >,\n\t\t\tdateAddedField as Field< RestAttachment >,\n\t\t\tdateModifiedField as Field< RestAttachment >,\n\t\t\tauthorField as Field< RestAttachment >,\n\t\t\tfilenameField as Field< RestAttachment >,\n\t\t\tfilesizeField as Field< RestAttachment >,\n\t\t\tmediaDimensionsField as Field< RestAttachment >,\n\t\t\tmimeTypeField as Field< RestAttachment >,\n\t\t\tattachedToField as Field< RestAttachment >,\n\t\t],\n\t\t[]\n\t);\n\n\tconst actions: ActionButton< RestAttachment >[] = useMemo(\n\t\t() => [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: __( 'Select' ),\n\t\t\t\tisPrimary: true,\n\t\t\t\tsupportsBulk: multiple,\n\t\t\t\tasync callback() {\n\t\t\t\t\tif ( selection.length === 0 ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst selectedPostsQuery = {\n\t\t\t\t\t\tinclude: selection,\n\t\t\t\t\t\tper_page: -1,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst selectedPosts = await resolveSelect(\n\t\t\t\t\t\tcoreStore\n\t\t\t\t\t).getEntityRecords< RestAttachment >(\n\t\t\t\t\t\t'postType',\n\t\t\t\t\t\t'attachment',\n\t\t\t\t\t\tselectedPostsQuery\n\t\t\t\t\t);\n\n\t\t\t\t\t// Transform the selected posts to the expected Attachment format\n\t\t\t\t\tconst transformedPosts = ( selectedPosts ?? [] )\n\t\t\t\t\t\t.map( transformAttachment )\n\t\t\t\t\t\t.filter( Boolean );\n\n\t\t\t\t\tconst selectedItems = multiple\n\t\t\t\t\t\t? transformedPosts\n\t\t\t\t\t\t: transformedPosts?.[ 0 ];\n\n\t\t\t\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\t\t\t\tonSelect( selectedItems );\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t[ multiple, onSelect, selection, removeAllNotices ]\n\t);\n\n\tconst handleModalClose = useCallback( () => {\n\t\tremoveAllNotices( 'snackbar', NOTICES_CONTEXT );\n\t\tonClose?.();\n\t}, [ removeAllNotices, onClose ] );\n\n\t// Use onUpload if provided, otherwise fall back to uploadMedia\n\tconst handleUpload = onUpload || uploadMedia;\n\n\t// Show success notice and auto-clear completed entries when all batches finish.\n\tconst prevAllCompleteRef = useRef( false );\n\tuseEffect( () => {\n\t\tif ( allComplete && ! prevAllCompleteRef.current ) {\n\t\t\tconst completeCount = uploadingFiles.filter(\n\t\t\t\t( file ) => file.status === 'uploaded'\n\t\t\t).length;\n\t\t\tif ( completeCount > 0 ) {\n\t\t\t\tcreateSuccessNotice(\n\t\t\t\t\tsprintf(\n\t\t\t\t\t\t// translators: %s: number of files\n\t\t\t\t\t\t_n(\n\t\t\t\t\t\t\t'Uploaded %s file',\n\t\t\t\t\t\t\t'Uploaded %s files',\n\t\t\t\t\t\t\tcompleteCount\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcompleteCount.toLocaleString()\n\t\t\t\t\t),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\t\tcontext: NOTICES_CONTEXT,\n\t\t\t\t\t\tid: NOTICE_ID_UPLOAD_PROGRESS,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Auto-clear completed entries, unless the popover is\n\t\t\t// open \u2014 in that case, they'll be cleared on close.\n\t\t\tif ( ! isPopoverOpenRef.current ) {\n\t\t\t\tclearCompleted();\n\t\t\t}\n\t\t}\n\t\tprevAllCompleteRef.current = allComplete;\n\t}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );\n\n\tconst handleFileSelect = useCallback(\n\t\t( event: React.ChangeEvent< HTMLInputElement > ) => {\n\t\t\tconst files = event.target.files;\n\t\t\tif ( files && files.length > 0 ) {\n\t\t\t\tconst filesArray = Array.from( files );\n\t\t\t\tconst { onFileChange, onError } = registerBatch( filesArray );\n\n\t\t\t\thandleUpload( {\n\t\t\t\t\tallowedTypes,\n\t\t\t\t\tfilesList: filesArray,\n\t\t\t\t\tonFileChange,\n\t\t\t\t\tonError,\n\t\t\t\t} );\n\t\t\t}\n\t\t},\n\t\t[ allowedTypes, handleUpload, registerBatch ]\n\t);\n\n\tconst paginationInfo = useMemo(\n\t\t() => ( {\n\t\t\ttotalItems,\n\t\t\ttotalPages,\n\t\t} ),\n\t\t[ totalItems, totalPages ]\n\t);\n\n\tconst defaultLayouts = useMemo(\n\t\t() => ( {\n\t\t\t[ LAYOUT_PICKER_GRID ]: {\n\t\t\t\tfields: [],\n\t\t\t\tshowTitle: false,\n\t\t\t},\n\t\t\t[ LAYOUT_PICKER_TABLE ]: {\n\t\t\t\tfields: [\n\t\t\t\t\t'filename',\n\t\t\t\t\t'filesize',\n\t\t\t\t\t'media_dimensions',\n\t\t\t\t\t'author',\n\t\t\t\t\t'date',\n\t\t\t\t],\n\t\t\t\tshowTitle: true,\n\t\t\t},\n\t\t} ),\n\t\t[]\n\t);\n\n\t// Build accept attribute from allowedTypes\n\tconst acceptTypes = useMemo( () => {\n\t\tif ( allowedTypes?.includes( '*' ) ) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn allowedTypes?.join( ',' );\n\t}, [ allowedTypes ] );\n\n\tif ( ! isOpen ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ title }\n\t\t\tonRequestClose={ handleModalClose }\n\t\t\tisDismissible={ isDismissible }\n\t\t\tclassName={ modalClass }\n\t\t\toverlayClassName=\"media-upload-modal\"\n\t\t\tsize=\"fill\"\n\t\t\theaderActions={\n\t\t\t\t<FormFileUpload\n\t\t\t\t\taccept={ acceptTypes }\n\t\t\t\t\tmultiple\n\t\t\t\t\tonChange={ handleFileSelect }\n\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\trender={ ( { openFileDialog } ) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tonClick={ openFileDialog }\n\t\t\t\t\t\t\ticon={ uploadIcon }\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Upload media' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t}\n\t\t>\n\t\t\t<DropZone\n\t\t\t\tonFilesDrop={ ( files ) => {\n\t\t\t\t\tlet filteredFiles = files;\n\t\t\t\t\t// Filter files by allowed types if specified\n\t\t\t\t\tif ( allowedTypes && ! allowedTypes.includes( '*' ) ) {\n\t\t\t\t\t\tfilteredFiles = files.filter( ( file ) =>\n\t\t\t\t\t\t\tallowedTypes.some( ( allowedType ) => {\n\t\t\t\t\t\t\t\t// Check if the file type matches the allowed MIME type\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\tfile.type === allowedType ||\n\t\t\t\t\t\t\t\t\tfile.type.startsWith(\n\t\t\t\t\t\t\t\t\t\tallowedType.replace( '*', '' )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif ( filteredFiles.length > 0 ) {\n\t\t\t\t\t\tconst { onFileChange, onError } =\n\t\t\t\t\t\t\tregisterBatch( filteredFiles );\n\n\t\t\t\t\t\thandleUpload( {\n\t\t\t\t\t\t\tallowedTypes,\n\t\t\t\t\t\t\tfilesList: filteredFiles,\n\t\t\t\t\t\t\tonFileChange,\n\t\t\t\t\t\t\tonError,\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} }\n\t\t\t\tlabel={ __( 'Drop files to upload' ) }\n\t\t\t/>\n\t\t\t<DataViewsPicker\n\t\t\t\tdata={ mediaRecords || [] }\n\t\t\t\tfields={ fields }\n\t\t\t\tview={ view }\n\t\t\t\tonChangeView={ setView }\n\t\t\t\tactions={ actions }\n\t\t\t\tselection={ selection }\n\t\t\t\tonChangeSelection={ setSelection }\n\t\t\t\tisLoading={ isLoading }\n\t\t\t\tpaginationInfo={ paginationInfo }\n\t\t\t\tdefaultLayouts={ defaultLayouts }\n\t\t\t\tgetItemId={ ( item: RestAttachment ) => String( item.id ) }\n\t\t\t\titemListLabel={ __( 'Media items' ) }\n\t\t\t>\n\t\t\t\t<Stack\n\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\talign=\"top\"\n\t\t\t\t\tjustify=\"space-between\"\n\t\t\t\t\tclassName=\"dataviews__view-actions\"\n\t\t\t\t\tgap=\"xs\"\n\t\t\t\t>\n\t\t\t\t\t<Stack\n\t\t\t\t\t\tdirection=\"row\"\n\t\t\t\t\t\tgap=\"sm\"\n\t\t\t\t\t\tjustify=\"start\"\n\t\t\t\t\t\tclassName=\"dataviews__search\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{ search && (\n\t\t\t\t\t\t\t<DataViewsPicker.Search label={ searchLabel } />\n\t\t\t\t\t\t) }\n\t\t\t\t\t\t<DataViewsPicker.FiltersToggle />\n\t\t\t\t\t</Stack>\n\t\t\t\t\t<Stack direction=\"row\" gap=\"xs\" style={ { flexShrink: 0 } }>\n\t\t\t\t\t\t<DataViewsPicker.LayoutSwitcher />\n\t\t\t\t\t\t<DataViewsPicker.ViewConfig />\n\t\t\t\t\t</Stack>\n\t\t\t\t</Stack>\n\t\t\t\t<DataViewsPicker.FiltersToggled className=\"dataviews-filters__container\" />\n\t\t\t\t<DataViewsPicker.Layout />\n\t\t\t\t<div\n\t\t\t\t\tclassName={ clsx( 'media-upload-modal__footer', {\n\t\t\t\t\t\t'is-uploading': uploadingFiles.length > 0,\n\t\t\t\t\t} ) }\n\t\t\t\t>\n\t\t\t\t\t<UploadStatusPopover\n\t\t\t\t\t\tuploadingFiles={ uploadingFiles }\n\t\t\t\t\t\tonDismissError={ dismissError }\n\t\t\t\t\t\tonOpenChange={ handlePopoverOpenChange }\n\t\t\t\t\t/>\n\t\t\t\t\t<DataViewsPicker.BulkActionToolbar />\n\t\t\t\t</div>\n\t\t\t</DataViewsPicker>\n\t\t\t{ createPortal(\n\t\t\t\t<SnackbarNotices\n\t\t\t\t\tclassName=\"media-upload-modal__snackbar\"\n\t\t\t\t\tcontext={ NOTICES_CONTEXT }\n\t\t\t\t/>,\n\t\t\t\tdocument.body\n\t\t\t) }\n\t\t</Modal>\n\t);\n}\n\nexport default MediaUploadModal;\n"],
|
|
5
|
+
"mappings": ";AAGA,OAAO,UAAU;AAKjB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,IAAI,SAAS,UAAU;AAChC;AAAA,EACC,eAAe;AAAA,EACf,SAAS;AAAA,OACH;AACP,SAAS,eAAe,mBAAmB;AAC3C,SAAS,OAAO,UAAU,gBAAgB,cAAc;AACxD,SAAS,UAAU,kBAAkB;AACrC,SAAS,uBAAuB;AAEhC,SAAS,aAAa;AACtB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,SAAS,cAAc,uBAAuB;AAMvD,SAAS,2BAA2B;AACpC,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,0CAA0C;AACnD,SAAS,uBAAuB;AAgc1B,cA+DD,YA/DC;AA9bN,IAAM,EAAE,gCAAgC,IAAI,OAAQ,mBAAoB;AAGxE,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAG5B,IAAM,kBAAkB;AAGxB,IAAM,4BAA4B;AAkG3B,SAAS,iBAAkB;AAAA,EACjC;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ,GAAI,cAAe;AAAA,EAC3B;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,SAAS;AAAA,EACT,cAAc,GAAI,cAAe;AAClC,GAA2B;AAC1B,QAAM,CAAE,WAAW,YAAa,IAAI,SAAsB,MAAM;AAC/D,QAAK,CAAE,OAAQ;AACd,aAAO,CAAC;AAAA,IACT;AACA,WAAO,MAAM,QAAS,KAAM,IACzB,MAAM,IAAK,MAAO,IAClB,CAAE,OAAQ,KAAM,CAAE;AAAA,EACtB,CAAE;AAEF,QAAM,EAAE,qBAAqB,iBAAiB,IAC7C,YAAa,YAAa;AAC3B,QAAM,kCACL,mCAAmC;AAGpC,QAAM,CAAE,MAAM,OAAQ,IAAI,SAAkB,OAAQ;AAAA,IACnD,MAAM;AAAA,IACN,QAAQ,CAAC;AAAA,IACT,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,IACV,QAAQ;AAAA,MACP,aAAa;AAAA,MACb,SAAS;AAAA,IACV;AAAA,EACD,EAAI;AAGJ,QAAM,YAAY,QAAS,MAAM;AAChC,UAAM,UAAiC,CAAC;AAExC,SAAK,SAAS,QAAS,CAAE,WAAY;AAEpC,UAAK,OAAO,UAAU,cAAe;AACpC,gBAAQ,aAAa,OAAO;AAAA,MAC7B;AAEA,UAAK,OAAO,UAAU,UAAW;AAChC,YAAK,OAAO,aAAa,SAAU;AAClC,kBAAQ,SAAS,OAAO;AAAA,QACzB,WAAY,OAAO,aAAa,UAAW;AAC1C,kBAAQ,iBAAiB,OAAO;AAAA,QACjC;AAAA,MACD;AAEA,UAAK,OAAO,UAAU,UAAU,OAAO,UAAU,YAAa;AAC7D,YAAK,OAAO,aAAa,UAAW;AACnC,kBAAQ,SAAS,OAAO;AAAA,QACzB,WAAY,OAAO,aAAa,SAAU;AACzC,kBAAQ,QAAQ,OAAO;AAAA,QACxB;AAAA,MACD;AAEA,UAAK,OAAO,UAAU,aAAc;AACnC,gBAAQ,YAAY,OAAO;AAAA,MAC5B;AAAA,IACD,CAAE;AAGF,QAAK,CAAE,QAAQ,YAAa;AAC3B,cAAQ,aAAa,cAAc,SAAU,GAAI,IAC9C,SACA;AAAA,IACJ;AAEA,WAAO;AAAA,MACN,UAAU,KAAK,WAAW;AAAA,MAC1B,MAAM,KAAK,QAAQ;AAAA,MACnB,QAAQ;AAAA,MACR,OAAO,KAAK,MAAM;AAAA,MAClB,SAAS,KAAK,MAAM;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,QAAQ;AAAA,MACR,GAAG;AAAA,IACJ;AAAA,EACD,GAAG,CAAE,MAAM,YAAa,CAAE;AAG1B,QAAM,sBAAsB;AAAA,IAC3B,CAAE,gBAA0C;AAC3C,YAAM,cAAc,YAClB,IAAK,CAAE,eAAgB,OAAQ,WAAW,EAAG,CAAE,EAC/C,OAAQ,OAAQ;AAElB,UAAK,UAAW;AACf,qBAAc,CAAE,SAAU;AACzB,gBAAM,WAAW,IAAI,IAAK,IAAK;AAC/B,gBAAM,SAAS,YAAY;AAAA,YAC1B,CAAE,OAAQ,CAAE,SAAS,IAAK,EAAG;AAAA,UAC9B;AACA,iBAAO,CAAE,GAAG,MAAM,GAAG,MAAO;AAAA,QAC7B,CAAE;AAAA,MACH,OAAO;AACN,qBAAc,YAAY,MAAO,GAAG,CAAE,CAAE;AAAA,MACzC;AAIA,sCAAgC;AAAA,IACjC;AAAA,IACA,CAAE,UAAU,+BAAgC;AAAA,EAC7C;AAEA,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI,gBAAiB,EAAE,iBAAiB,oBAAoB,CAAE;AAE9D,QAAM,mBAAmB,OAAQ,KAAM;AACvC,QAAM,0BAA0B;AAAA,IAC/B,CAAE,SAAmB;AACpB,uBAAiB,UAAU;AAC3B,UAAK,CAAE,MAAO;AACb,uBAAe;AAAA,MAChB;AAAA,IACD;AAAA,IACA,CAAE,cAAe;AAAA,EAClB;AAGA,QAAM;AAAA,IACL,SAAS;AAAA,IACT,aAAa;AAAA,IACb;AAAA,IACA;AAAA,EACD,IAAI,gCAAiC,YAAY,cAAc,SAAU;AAEzE,QAAM,SAAoC;AAAA,IACzC,MAAM;AAAA;AAAA;AAAA,MAGL;AAAA,QACC,GAAK;AAAA,QACL,cAAc;AAAA;AAAA,MACf;AAAA,MACA;AAAA,QACC,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,GAAI,OAAQ;AAAA,QACnB,UAAU,CAAE,EAAE,KAAK,MAAiC;AACnD,gBAAM,aAAa,KAAK,MAAM,OAAO,KAAK,MAAM;AAChD,iBAAO,cAAc,GAAI,YAAa;AAAA,QACvC;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,IACA,CAAC;AAAA,EACF;AAEA,QAAM,UAA4C;AAAA,IACjD,MAAM;AAAA,MACL;AAAA,QACC,IAAI;AAAA,QACJ,OAAO,GAAI,QAAS;AAAA,QACpB,WAAW;AAAA,QACX,cAAc;AAAA,QACd,MAAM,WAAW;AAChB,cAAK,UAAU,WAAW,GAAI;AAC7B;AAAA,UACD;AAEA,gBAAM,qBAAqB;AAAA,YAC1B,SAAS;AAAA,YACT,UAAU;AAAA,UACX;AAEA,gBAAM,gBAAgB,MAAM;AAAA,YAC3B;AAAA,UACD,EAAE;AAAA,YACD;AAAA,YACA;AAAA,YACA;AAAA,UACD;AAGA,gBAAM,oBAAqB,iBAAiB,CAAC,GAC3C,IAAK,mBAAoB,EACzB,OAAQ,OAAQ;AAElB,gBAAM,gBAAgB,WACnB,mBACA,mBAAoB,CAAE;AAEzB,2BAAkB,YAAY,eAAgB;AAC9C,mBAAU,aAAc;AAAA,QACzB;AAAA,MACD;AAAA,IACD;AAAA,IACA,CAAE,UAAU,UAAU,WAAW,gBAAiB;AAAA,EACnD;AAEA,QAAM,mBAAmB,YAAa,MAAM;AAC3C,qBAAkB,YAAY,eAAgB;AAC9C,cAAU;AAAA,EACX,GAAG,CAAE,kBAAkB,OAAQ,CAAE;AAGjC,QAAM,eAAe,YAAY;AAGjC,QAAM,qBAAqB,OAAQ,KAAM;AACzC,YAAW,MAAM;AAChB,QAAK,eAAe,CAAE,mBAAmB,SAAU;AAClD,YAAM,gBAAgB,eAAe;AAAA,QACpC,CAAE,SAAU,KAAK,WAAW;AAAA,MAC7B,EAAE;AACF,UAAK,gBAAgB,GAAI;AACxB;AAAA,UACC;AAAA;AAAA,YAEC;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,YACD;AAAA,YACA,cAAc,eAAe;AAAA,UAC9B;AAAA,UACA;AAAA,YACC,MAAM;AAAA,YACN,SAAS;AAAA,YACT,IAAI;AAAA,UACL;AAAA,QACD;AAAA,MACD;AAIA,UAAK,CAAE,iBAAiB,SAAU;AACjC,uBAAe;AAAA,MAChB;AAAA,IACD;AACA,uBAAmB,UAAU;AAAA,EAC9B,GAAG,CAAE,aAAa,gBAAgB,qBAAqB,cAAe,CAAE;AAExE,QAAM,mBAAmB;AAAA,IACxB,CAAE,UAAkD;AACnD,YAAM,QAAQ,MAAM,OAAO;AAC3B,UAAK,SAAS,MAAM,SAAS,GAAI;AAChC,cAAM,aAAa,MAAM,KAAM,KAAM;AACrC,cAAM,EAAE,cAAc,QAAQ,IAAI,cAAe,UAAW;AAE5D,qBAAc;AAAA,UACb;AAAA,UACA,WAAW;AAAA,UACX;AAAA,UACA;AAAA,QACD,CAAE;AAAA,MACH;AAAA,IACD;AAAA,IACA,CAAE,cAAc,cAAc,aAAc;AAAA,EAC7C;AAEA,QAAM,iBAAiB;AAAA,IACtB,OAAQ;AAAA,MACP;AAAA,MACA;AAAA,IACD;AAAA,IACA,CAAE,YAAY,UAAW;AAAA,EAC1B;AAEA,QAAM,iBAAiB;AAAA,IACtB,OAAQ;AAAA,MACP,CAAE,kBAAmB,GAAG;AAAA,QACvB,QAAQ,CAAC;AAAA,QACT,WAAW;AAAA,MACZ;AAAA,MACA,CAAE,mBAAoB,GAAG;AAAA,QACxB,QAAQ;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACD;AAAA,QACA,WAAW;AAAA,MACZ;AAAA,IACD;AAAA,IACA,CAAC;AAAA,EACF;AAGA,QAAM,cAAc,QAAS,MAAM;AAClC,QAAK,cAAc,SAAU,GAAI,GAAI;AACpC,aAAO;AAAA,IACR;AACA,WAAO,cAAc,KAAM,GAAI;AAAA,EAChC,GAAG,CAAE,YAAa,CAAE;AAEpB,MAAK,CAAE,QAAS;AACf,WAAO;AAAA,EACR;AAEA,SACC;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,gBAAiB;AAAA,MACjB;AAAA,MACA,WAAY;AAAA,MACZ,kBAAiB;AAAA,MACjB,MAAK;AAAA,MACL,eACC;AAAA,QAAC;AAAA;AAAA,UACA,QAAS;AAAA,UACT,UAAQ;AAAA,UACR,UAAW;AAAA,UACX,uBAAqB;AAAA,UACrB,QAAS,CAAE,EAAE,eAAe,MAC3B;AAAA,YAAC;AAAA;AAAA,cACA,SAAU;AAAA,cACV,MAAO;AAAA,cACP,uBAAqB;AAAA,cAEnB,aAAI,cAAe;AAAA;AAAA,UACtB;AAAA;AAAA,MAEF;AAAA,MAGD;AAAA;AAAA,UAAC;AAAA;AAAA,YACA,aAAc,CAAE,UAAW;AAC1B,kBAAI,gBAAgB;AAEpB,kBAAK,gBAAgB,CAAE,aAAa,SAAU,GAAI,GAAI;AACrD,gCAAgB,MAAM;AAAA,kBAAQ,CAAE,SAC/B,aAAa,KAAM,CAAE,gBAAiB;AAErC,2BACC,KAAK,SAAS,eACd,KAAK,KAAK;AAAA,sBACT,YAAY,QAAS,KAAK,EAAG;AAAA,oBAC9B;AAAA,kBAEF,CAAE;AAAA,gBACH;AAAA,cACD;AACA,kBAAK,cAAc,SAAS,GAAI;AAC/B,sBAAM,EAAE,cAAc,QAAQ,IAC7B,cAAe,aAAc;AAE9B,6BAAc;AAAA,kBACb;AAAA,kBACA,WAAW;AAAA,kBACX;AAAA,kBACA;AAAA,gBACD,CAAE;AAAA,cACH;AAAA,YACD;AAAA,YACA,OAAQ,GAAI,sBAAuB;AAAA;AAAA,QACpC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACA,MAAO,gBAAgB,CAAC;AAAA,YACxB;AAAA,YACA;AAAA,YACA,cAAe;AAAA,YACf;AAAA,YACA;AAAA,YACA,mBAAoB;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA,WAAY,CAAE,SAA0B,OAAQ,KAAK,EAAG;AAAA,YACxD,eAAgB,GAAI,aAAc;AAAA,YAElC;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACA,WAAU;AAAA,kBACV,OAAM;AAAA,kBACN,SAAQ;AAAA,kBACR,WAAU;AAAA,kBACV,KAAI;AAAA,kBAEJ;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACA,WAAU;AAAA,wBACV,KAAI;AAAA,wBACJ,SAAQ;AAAA,wBACR,WAAU;AAAA,wBAER;AAAA,oCACD,oBAAC,gBAAgB,QAAhB,EAAuB,OAAQ,aAAc;AAAA,0BAE/C,oBAAC,gBAAgB,eAAhB,EAA8B;AAAA;AAAA;AAAA,oBAChC;AAAA,oBACA,qBAAC,SAAM,WAAU,OAAM,KAAI,MAAK,OAAQ,EAAE,YAAY,EAAE,GACvD;AAAA,0CAAC,gBAAgB,gBAAhB,EAA+B;AAAA,sBAChC,oBAAC,gBAAgB,YAAhB,EAA2B;AAAA,uBAC7B;AAAA;AAAA;AAAA,cACD;AAAA,cACA,oBAAC,gBAAgB,gBAAhB,EAA+B,WAAU,gCAA+B;AAAA,cACzE,oBAAC,gBAAgB,QAAhB,EAAuB;AAAA,cACxB;AAAA,gBAAC;AAAA;AAAA,kBACA,WAAY,KAAM,8BAA8B;AAAA,oBAC/C,gBAAgB,eAAe,SAAS;AAAA,kBACzC,CAAE;AAAA,kBAEF;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACA;AAAA,wBACA,gBAAiB;AAAA,wBACjB,cAAe;AAAA;AAAA,oBAChB;AAAA,oBACA,oBAAC,gBAAgB,mBAAhB,EAAkC;AAAA;AAAA;AAAA,cACpC;AAAA;AAAA;AAAA,QACD;AAAA,QACE;AAAA,UACD;AAAA,YAAC;AAAA;AAAA,cACA,WAAU;AAAA,cACV,SAAU;AAAA;AAAA,UACX;AAAA,UACA,SAAS;AAAA,QACV;AAAA;AAAA;AAAA,EACD;AAEF;AAEA,IAAO,6BAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// packages/media-utils/src/components/media-upload-modal/upload-status-popover.tsx
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from "@wordpress/element";
|
|
3
|
+
import { __, sprintf, _n } from "@wordpress/i18n";
|
|
4
|
+
import { Button, Icon, Notice, Popover, Spinner } from "@wordpress/components";
|
|
5
|
+
import { check, chevronDown } from "@wordpress/icons";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function UploadStatusPopover({
|
|
8
|
+
uploadingFiles,
|
|
9
|
+
onDismissError,
|
|
10
|
+
onOpenChange
|
|
11
|
+
}) {
|
|
12
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
13
|
+
const [prevHadErrors, setPrevHadErrors] = useState(false);
|
|
14
|
+
const triggerRef = useRef(null);
|
|
15
|
+
const updateIsOpen = useCallback(
|
|
16
|
+
(open) => {
|
|
17
|
+
setIsOpen(open);
|
|
18
|
+
onOpenChange?.(open);
|
|
19
|
+
},
|
|
20
|
+
[onOpenChange]
|
|
21
|
+
);
|
|
22
|
+
const activeFiles = uploadingFiles.filter(
|
|
23
|
+
(file) => file.status === "uploading"
|
|
24
|
+
);
|
|
25
|
+
const errorFiles = uploadingFiles.filter(
|
|
26
|
+
(file) => file.status === "error"
|
|
27
|
+
);
|
|
28
|
+
const hasErrors = errorFiles.length > 0;
|
|
29
|
+
const isUploading = activeFiles.length > 0;
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (hasErrors && !prevHadErrors) {
|
|
32
|
+
updateIsOpen(true);
|
|
33
|
+
}
|
|
34
|
+
setPrevHadErrors(hasErrors);
|
|
35
|
+
}, [hasErrors, prevHadErrors, updateIsOpen]);
|
|
36
|
+
if (uploadingFiles.length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
let buttonLabel, popoverHeading;
|
|
40
|
+
if (isUploading) {
|
|
41
|
+
buttonLabel = sprintf(
|
|
42
|
+
// translators: %s: number of files being uploaded
|
|
43
|
+
_n("Uploading %s file", "Uploading %s files", activeFiles.length),
|
|
44
|
+
activeFiles.length.toLocaleString()
|
|
45
|
+
);
|
|
46
|
+
popoverHeading = __("Uploading");
|
|
47
|
+
} else if (hasErrors) {
|
|
48
|
+
buttonLabel = sprintf(
|
|
49
|
+
// translators: %s: number of upload errors
|
|
50
|
+
_n("%s upload error", "%s upload errors", errorFiles.length),
|
|
51
|
+
errorFiles.length.toLocaleString()
|
|
52
|
+
);
|
|
53
|
+
popoverHeading = __("Upload errors");
|
|
54
|
+
} else {
|
|
55
|
+
buttonLabel = __("Upload complete");
|
|
56
|
+
popoverHeading = __("Upload complete");
|
|
57
|
+
}
|
|
58
|
+
return /* @__PURE__ */ jsxs("div", { className: "media-upload-modal__upload-status", children: [
|
|
59
|
+
isUploading && /* @__PURE__ */ jsx(Spinner, {}),
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
Button,
|
|
62
|
+
{
|
|
63
|
+
className: "media-upload-modal__upload-status__trigger",
|
|
64
|
+
size: "compact",
|
|
65
|
+
icon: chevronDown,
|
|
66
|
+
iconPosition: "right",
|
|
67
|
+
onClick: () => updateIsOpen(!isOpen),
|
|
68
|
+
"aria-expanded": isOpen,
|
|
69
|
+
ref: triggerRef,
|
|
70
|
+
children: buttonLabel
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
isOpen && /* @__PURE__ */ jsxs(
|
|
74
|
+
Popover,
|
|
75
|
+
{
|
|
76
|
+
className: "media-upload-modal__upload-status__popover",
|
|
77
|
+
placement: "top-start",
|
|
78
|
+
offset: 8,
|
|
79
|
+
anchor: triggerRef.current,
|
|
80
|
+
focusOnMount: true,
|
|
81
|
+
onClose: () => {
|
|
82
|
+
if (triggerRef.current?.contains(
|
|
83
|
+
triggerRef.current.ownerDocument.activeElement
|
|
84
|
+
)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
updateIsOpen(false);
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "media-upload-modal__upload-status__header", children: /* @__PURE__ */ jsx("h3", { children: popoverHeading }) }),
|
|
91
|
+
/* @__PURE__ */ jsx("ul", { className: "media-upload-modal__upload-status__list", children: uploadingFiles.map((file) => /* @__PURE__ */ jsxs(
|
|
92
|
+
"li",
|
|
93
|
+
{
|
|
94
|
+
className: "media-upload-modal__upload-status__item",
|
|
95
|
+
children: [
|
|
96
|
+
file.status === "uploading" && /* @__PURE__ */ jsx(Spinner, {}),
|
|
97
|
+
file.status === "uploaded" && /* @__PURE__ */ jsx(Icon, { icon: check, size: 16 }),
|
|
98
|
+
(file.status === "uploading" || file.status === "uploaded") && /* @__PURE__ */ jsx(
|
|
99
|
+
"span",
|
|
100
|
+
{
|
|
101
|
+
className: "media-upload-modal__upload-status__filename",
|
|
102
|
+
title: file.name,
|
|
103
|
+
children: file.name
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
file.status === "error" && /* @__PURE__ */ jsxs(
|
|
107
|
+
Notice,
|
|
108
|
+
{
|
|
109
|
+
status: "error",
|
|
110
|
+
isDismissible: !!onDismissError,
|
|
111
|
+
onRemove: () => onDismissError?.(file.id),
|
|
112
|
+
children: [
|
|
113
|
+
file.name,
|
|
114
|
+
": ",
|
|
115
|
+
file.error
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
file.id
|
|
122
|
+
)) })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
] });
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
UploadStatusPopover
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=upload-status-popover.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/upload-status-popover.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useState, useEffect, useCallback, useRef } from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport { Button, Icon, Notice, Popover, Spinner } from '@wordpress/components';\nimport { check, chevronDown } from '@wordpress/icons';\n\nexport interface UploadingFile {\n\tid: string;\n\tbatchId: string;\n\tname: string;\n\tstatus: 'uploading' | 'uploaded' | 'error';\n\terror?: string;\n}\n\ninterface UploadStatusPopoverProps {\n\tuploadingFiles: UploadingFile[];\n\tonDismissError?: ( fileId: string ) => void;\n\tonOpenChange?: ( open: boolean ) => void;\n}\n\nexport function UploadStatusPopover( {\n\tuploadingFiles,\n\tonDismissError,\n\tonOpenChange,\n}: UploadStatusPopoverProps ) {\n\tconst [ isOpen, setIsOpen ] = useState( false );\n\tconst [ prevHadErrors, setPrevHadErrors ] = useState( false );\n\tconst triggerRef = useRef< HTMLButtonElement >( null );\n\n\tconst updateIsOpen = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tsetIsOpen( open );\n\t\t\tonOpenChange?.( open );\n\t\t},\n\t\t[ onOpenChange ]\n\t);\n\n\tconst activeFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'uploading'\n\t);\n\tconst errorFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'error'\n\t);\n\tconst hasErrors = errorFiles.length > 0;\n\tconst isUploading = activeFiles.length > 0;\n\n\t// Auto-expand when an error occurs.\n\tuseEffect( () => {\n\t\tif ( hasErrors && ! prevHadErrors ) {\n\t\t\tupdateIsOpen( true );\n\t\t}\n\t\tsetPrevHadErrors( hasErrors );\n\t}, [ hasErrors, prevHadErrors, updateIsOpen ] );\n\n\tif ( uploadingFiles.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tlet buttonLabel, popoverHeading: string;\n\tif ( isUploading ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of files being uploaded\n\t\t\t_n( 'Uploading %s file', 'Uploading %s files', activeFiles.length ),\n\t\t\tactiveFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Uploading' );\n\t} else if ( hasErrors ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of upload errors\n\t\t\t_n( '%s upload error', '%s upload errors', errorFiles.length ),\n\t\t\terrorFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Upload errors' );\n\t} else {\n\t\tbuttonLabel = __( 'Upload complete' );\n\t\tpopoverHeading = __( 'Upload complete' );\n\t}\n\n\treturn (\n\t\t<div className=\"media-upload-modal__upload-status\">\n\t\t\t{ isUploading && <Spinner /> }\n\t\t\t<Button\n\t\t\t\tclassName=\"media-upload-modal__upload-status__trigger\"\n\t\t\t\tsize=\"compact\"\n\t\t\t\ticon={ chevronDown }\n\t\t\t\ticonPosition=\"right\"\n\t\t\t\tonClick={ () => updateIsOpen( ! isOpen ) }\n\t\t\t\taria-expanded={ isOpen }\n\t\t\t\tref={ triggerRef }\n\t\t\t>\n\t\t\t\t{ buttonLabel }\n\t\t\t</Button>\n\t\t\t{ isOpen && (\n\t\t\t\t<Popover\n\t\t\t\t\tclassName=\"media-upload-modal__upload-status__popover\"\n\t\t\t\t\tplacement=\"top-start\"\n\t\t\t\t\toffset={ 8 }\n\t\t\t\t\tanchor={ triggerRef.current }\n\t\t\t\t\tfocusOnMount\n\t\t\t\t\tonClose={ () => {\n\t\t\t\t\t\t// Let the button's onClick handle toggling when\n\t\t\t\t\t\t// the close was triggered by clicking the trigger.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttriggerRef.current?.contains(\n\t\t\t\t\t\t\t\ttriggerRef.current.ownerDocument.activeElement\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdateIsOpen( false );\n\t\t\t\t\t} }\n\t\t\t\t>\n\t\t\t\t\t<div className=\"media-upload-modal__upload-status__header\">\n\t\t\t\t\t\t<h3>{ popoverHeading }</h3>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul className=\"media-upload-modal__upload-status__list\">\n\t\t\t\t\t\t{ uploadingFiles.map( ( file ) => (\n\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\tkey={ file.id }\n\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__item\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ file.status === 'uploading' && <Spinner /> }\n\t\t\t\t\t\t\t\t{ file.status === 'uploaded' && (\n\t\t\t\t\t\t\t\t\t<Icon icon={ check } size={ 16 } />\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ ( file.status === 'uploading' ||\n\t\t\t\t\t\t\t\t\tfile.status === 'uploaded' ) && (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__filename\"\n\t\t\t\t\t\t\t\t\t\ttitle={ file.name }\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ file.status === 'error' && (\n\t\t\t\t\t\t\t\t\t<Notice\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\tisDismissible={ !! onDismissError }\n\t\t\t\t\t\t\t\t\t\tonRemove={ () =>\n\t\t\t\t\t\t\t\t\t\t\tonDismissError?.( file.id )\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }: { file.error }\n\t\t\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Popover>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// packages/media-utils/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts
|
|
2
|
+
import { useCallback } from "@wordpress/element";
|
|
3
|
+
import { store as coreStore } from "@wordpress/core-data";
|
|
4
|
+
import { useRegistry } from "@wordpress/data";
|
|
5
|
+
function useInvalidateAttachmentResolutions() {
|
|
6
|
+
const registry = useRegistry();
|
|
7
|
+
return useCallback(() => {
|
|
8
|
+
const resolvers = registry.select(coreStore).getCachedResolvers();
|
|
9
|
+
const entityRecordResolutions = resolvers.getEntityRecords;
|
|
10
|
+
entityRecordResolutions?.forEach((_value, args) => {
|
|
11
|
+
if (args[0] === "postType" && args[1] === "attachment") {
|
|
12
|
+
registry.dispatch(coreStore).invalidateResolution("getEntityRecords", args);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
}, [registry]);
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
useInvalidateAttachmentResolutions
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=use-invalidate-attachment-resolutions.mjs.map
|
package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Hook for invalidating all cached `getEntityRecords` resolutions for the\n * attachment post type.\n *\n * After a file upload completes the media grid needs to refresh, but\n * `invalidateResolution` only clears the exact query that is passed to it.\n * If the user is on page 2, page 1 (where the new upload would appear) stays\n * stale. Using `invalidateResolutionForStoreSelector` would work but is too\n * broad \u2014 it clears every `getEntityRecords` resolution, potentially\n * triggering unnecessary refetches for unrelated entity types.\n *\n * This hook provides a middle ground: it iterates over every cached\n * resolution for `getEntityRecords` and invalidates only the entries where\n * the first two arguments match `['postType', 'attachment']`.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useCallback } from '@wordpress/element';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useRegistry } from '@wordpress/data';\n\n/**\n * Returns a stable callback that invalidates all cached `getEntityRecords`\n * resolutions for `postType / attachment`, leaving every other entity type\n * untouched.\n */\nexport function useInvalidateAttachmentResolutions() {\n\tconst registry = useRegistry();\n\n\treturn useCallback( () => {\n\t\tconst resolvers = registry.select( coreStore ).getCachedResolvers();\n\n\t\t// getCachedResolvers() is typed as Record<string, unknown> but the\n\t\t// values are EquivalentKeyMap instances (Map-like). Cast the same\n\t\t// way the resolvers-cache-middleware does internally.\n\t\tconst entityRecordResolutions = resolvers.getEntityRecords as\n\t\t\t| Map< string[], { status: string } >\n\t\t\t| undefined;\n\n\t\tentityRecordResolutions?.forEach( ( _value, args ) => {\n\t\t\tif ( args[ 0 ] === 'postType' && args[ 1 ] === 'attachment' ) {\n\t\t\t\tregistry\n\t\t\t\t\t.dispatch( coreStore )\n\t\t\t\t\t.invalidateResolution( 'getEntityRecords', args );\n\t\t\t}\n\t\t} );\n\t}, [ registry ] );\n}\n"],
|
|
5
|
+
"mappings": ";AAmBA,SAAS,mBAAmB;AAC5B,SAAS,SAAS,iBAAiB;AACnC,SAAS,mBAAmB;AAOrB,SAAS,qCAAqC;AACpD,QAAM,WAAW,YAAY;AAE7B,SAAO,YAAa,MAAM;AACzB,UAAM,YAAY,SAAS,OAAQ,SAAU,EAAE,mBAAmB;AAKlE,UAAM,0BAA0B,UAAU;AAI1C,6BAAyB,QAAS,CAAE,QAAQ,SAAU;AACrD,UAAK,KAAM,CAAE,MAAM,cAAc,KAAM,CAAE,MAAM,cAAe;AAC7D,iBACE,SAAU,SAAU,EACpB,qBAAsB,oBAAoB,IAAK;AAAA,MAClD;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAE,QAAS,CAAE;AACjB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// packages/media-utils/src/components/media-upload-modal/use-upload-status.ts
|
|
2
|
+
import { useState, useCallback } from "@wordpress/element";
|
|
3
|
+
import { isBlobURL } from "@wordpress/blob";
|
|
4
|
+
import { UploadError } from "../../utils/upload-error.mjs";
|
|
5
|
+
var idCounter = 0;
|
|
6
|
+
var batchIdCounter = 0;
|
|
7
|
+
function useUploadStatus({
|
|
8
|
+
onBatchComplete
|
|
9
|
+
} = {}) {
|
|
10
|
+
const [uploadingFiles, setUploadingFiles] = useState(
|
|
11
|
+
[]
|
|
12
|
+
);
|
|
13
|
+
const clearCompleted = useCallback(() => {
|
|
14
|
+
setUploadingFiles(
|
|
15
|
+
(prev) => prev.filter((item) => item.status !== "uploaded")
|
|
16
|
+
);
|
|
17
|
+
}, []);
|
|
18
|
+
const dismissError = useCallback((fileId) => {
|
|
19
|
+
setUploadingFiles(
|
|
20
|
+
(prev) => prev.filter((item) => item.id !== fileId)
|
|
21
|
+
);
|
|
22
|
+
}, []);
|
|
23
|
+
const registerBatch = useCallback(
|
|
24
|
+
(files) => {
|
|
25
|
+
const batchId = String(++batchIdCounter);
|
|
26
|
+
const batchSize = files.length;
|
|
27
|
+
const newEntries = files.map((file) => ({
|
|
28
|
+
id: String(++idCounter),
|
|
29
|
+
batchId,
|
|
30
|
+
name: file.name,
|
|
31
|
+
status: "uploading"
|
|
32
|
+
}));
|
|
33
|
+
setUploadingFiles((prev) => [...prev, ...newEntries]);
|
|
34
|
+
let successCount = 0;
|
|
35
|
+
let errorCount = 0;
|
|
36
|
+
let batchDone = false;
|
|
37
|
+
let successAttachments = [];
|
|
38
|
+
const completeBatchIfDone = () => {
|
|
39
|
+
if (batchDone || successCount + errorCount < batchSize) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
batchDone = true;
|
|
43
|
+
setUploadingFiles(
|
|
44
|
+
(prev) => prev.map(
|
|
45
|
+
(item) => item.batchId === batchId && item.status === "uploading" ? {
|
|
46
|
+
...item,
|
|
47
|
+
status: "uploaded"
|
|
48
|
+
} : item
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
onBatchComplete?.(successAttachments);
|
|
52
|
+
};
|
|
53
|
+
const onFileChange = (attachments) => {
|
|
54
|
+
if (batchDone) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const allReal = attachments.every(
|
|
58
|
+
(attachment) => attachment.id && attachment.url && !isBlobURL(attachment.url)
|
|
59
|
+
);
|
|
60
|
+
if (!allReal) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
successCount = attachments.length;
|
|
64
|
+
successAttachments = attachments;
|
|
65
|
+
completeBatchIfDone();
|
|
66
|
+
};
|
|
67
|
+
const onError = (error) => {
|
|
68
|
+
const fileName = error instanceof UploadError ? error.file.name : void 0;
|
|
69
|
+
setUploadingFiles((prev) => {
|
|
70
|
+
let matched = false;
|
|
71
|
+
return prev.map((item) => {
|
|
72
|
+
if (!matched && item.batchId === batchId && item.status === "uploading" && (!fileName || item.name === fileName)) {
|
|
73
|
+
matched = true;
|
|
74
|
+
return {
|
|
75
|
+
...item,
|
|
76
|
+
status: "error",
|
|
77
|
+
error: error.message
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return item;
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
errorCount++;
|
|
84
|
+
completeBatchIfDone();
|
|
85
|
+
};
|
|
86
|
+
return { onFileChange, onError };
|
|
87
|
+
},
|
|
88
|
+
[onBatchComplete]
|
|
89
|
+
);
|
|
90
|
+
const allComplete = uploadingFiles.length > 0 && uploadingFiles.every((item) => item.status !== "uploading");
|
|
91
|
+
return {
|
|
92
|
+
uploadingFiles,
|
|
93
|
+
registerBatch,
|
|
94
|
+
dismissError,
|
|
95
|
+
clearCompleted,
|
|
96
|
+
allComplete
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
useUploadStatus
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=use-upload-status.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/use-upload-status.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Hook for tracking media upload status with batch-scoped callbacks.\n *\n * This is a transitional layer that manually tracks upload progress using\n * local state. The @wordpress/upload-media package provides a Redux-based\n * store with richer capabilities (per-file progress, pause/resume, retry,\n * concurrency control, client-side processing). When the media upload modal\n * adopts @wordpress/upload-media, this hook can be replaced by selectors\n * from that store (getItems, isBatchUploaded, getItemProgress, etc.) while\n * keeping the same return interface.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useState, useCallback } from '@wordpress/element';\nimport { isBlobURL } from '@wordpress/blob';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment } from '../../utils/types';\nimport { UploadError } from '../../utils/upload-error';\nimport type { UploadingFile } from './upload-status-popover';\n\nlet idCounter = 0;\nlet batchIdCounter = 0;\n\ninterface UseUploadStatusOptions {\n\tonBatchComplete?: ( attachments: Partial< Attachment >[] ) => void;\n}\n\ninterface RegisterBatchResult {\n\tonFileChange: ( attachments: Partial< Attachment >[] ) => void;\n\tonError: ( error: Error ) => void;\n}\n\ninterface UseUploadStatusReturn {\n\t/** Current list of all tracked files. */\n\tuploadingFiles: UploadingFile[];\n\t/**\n\t * Register a new batch of files for tracking.\n\t * Returns batch-scoped onFileChange and onError callbacks.\n\t */\n\tregisterBatch: ( files: File[] ) => RegisterBatchResult;\n\t/** Remove a single error entry by file id. */\n\tdismissError: ( fileId: string ) => void;\n\t/** Remove all uploaded (completed) entries from the list. */\n\tclearCompleted: () => void;\n\t/** True when tracked entries exist but none are still uploading. */\n\tallComplete: boolean;\n}\n\nexport function useUploadStatus( {\n\tonBatchComplete,\n}: UseUploadStatusOptions = {} ): UseUploadStatusReturn {\n\tconst [ uploadingFiles, setUploadingFiles ] = useState< UploadingFile[] >(\n\t\t[]\n\t);\n\n\tconst clearCompleted = useCallback( () => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.status !== 'uploaded' )\n\t\t);\n\t}, [] );\n\n\tconst dismissError = useCallback( ( fileId: string ) => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.id !== fileId )\n\t\t);\n\t}, [] );\n\n\tconst registerBatch = useCallback(\n\t\t( files: File[] ): RegisterBatchResult => {\n\t\t\tconst batchId = String( ++batchIdCounter );\n\t\t\tconst batchSize = files.length;\n\n\t\t\tconst newEntries: UploadingFile[] = files.map( ( file ) => ( {\n\t\t\t\tid: String( ++idCounter ),\n\t\t\t\tbatchId,\n\t\t\t\tname: file.name,\n\t\t\t\tstatus: 'uploading' as const,\n\t\t\t} ) );\n\n\t\t\tsetUploadingFiles( ( prev ) => [ ...prev, ...newEntries ] );\n\n\t\t\t// Track successes and errors separately. onFileChange receives\n\t\t\t// the full current array each time (not incremental), so we\n\t\t\t// store the latest count rather than accumulating. The batch\n\t\t\t// is complete when successCount + errorCount >= batchSize.\n\t\t\tlet successCount = 0;\n\t\t\tlet errorCount = 0;\n\t\t\tlet batchDone = false;\n\t\t\tlet successAttachments: Partial< Attachment >[] = [];\n\n\t\t\tconst completeBatchIfDone = () => {\n\t\t\t\tif ( batchDone || successCount + errorCount < batchSize ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbatchDone = true;\n\n\t\t\t\t// Mark any remaining 'uploading' entries in this batch\n\t\t\t\t// as 'uploaded' (these are the successful ones).\n\t\t\t\tsetUploadingFiles( ( prev ) =>\n\t\t\t\t\tprev.map( ( item ) =>\n\t\t\t\t\t\titem.batchId === batchId && item.status === 'uploading'\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\t\tstatus: 'uploaded' as const,\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t\t: item\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tonBatchComplete?.( successAttachments );\n\t\t\t};\n\n\t\t\tconst onFileChange = ( attachments: Partial< Attachment >[] ) => {\n\t\t\t\tif ( batchDone ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Ignore intermediate calls where some files still have\n\t\t\t\t// blob URLs (not yet uploaded to the server).\n\t\t\t\tconst allReal = attachments.every(\n\t\t\t\t\t( attachment ) =>\n\t\t\t\t\t\tattachment.id &&\n\t\t\t\t\t\tattachment.url &&\n\t\t\t\t\t\t! isBlobURL( attachment.url )\n\t\t\t\t);\n\n\t\t\t\tif ( ! allReal ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Store the latest success count and attachments.\n\t\t\t\t// onFileChange receives the full array each time, so\n\t\t\t\t// we replace rather than accumulate.\n\t\t\t\tsuccessCount = attachments.length;\n\t\t\t\tsuccessAttachments = attachments;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\tconst onError = ( error: Error ) => {\n\t\t\t\t// uploadMedia always wraps errors in UploadError, which\n\t\t\t\t// carries the originating File so we can match it back to\n\t\t\t\t// the correct item. A custom onUpload prop could pass a\n\t\t\t\t// plain Error instead \u2014 in that case fileName is undefined\n\t\t\t\t// and no entry will be visually marked as errored, but the\n\t\t\t\t// batch will still complete correctly via errorCount.\n\t\t\t\tconst fileName =\n\t\t\t\t\terror instanceof UploadError ? error.file.name : undefined;\n\n\t\t\t\t// Find the first still-uploading entry in this batch whose\n\t\t\t\t// name matches the failed file and mark it as errored.\n\t\t\t\t// Falls back to the first uploading entry in the batch\n\t\t\t\t// when no filename is available. The `matched` flag\n\t\t\t\t// ensures only one entry is updated even when duplicate\n\t\t\t\t// filenames exist in the same batch.\n\t\t\t\tsetUploadingFiles( ( prev ) => {\n\t\t\t\t\tlet matched = false;\n\t\t\t\t\treturn prev.map( ( item ) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t! matched &&\n\t\t\t\t\t\t\titem.batchId === batchId &&\n\t\t\t\t\t\t\titem.status === 'uploading' &&\n\t\t\t\t\t\t\t( ! fileName || item.name === fileName )\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tmatched = true;\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\tstatus: 'error' as const,\n\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn item;\n\t\t\t\t\t} );\n\t\t\t\t} );\n\n\t\t\t\terrorCount++;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\treturn { onFileChange, onError };\n\t\t},\n\t\t[ onBatchComplete ]\n\t);\n\n\tconst allComplete =\n\t\tuploadingFiles.length > 0 &&\n\t\tuploadingFiles.every( ( item ) => item.status !== 'uploading' );\n\n\treturn {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t};\n}\n"],
|
|
5
|
+
"mappings": ";AAeA,SAAS,UAAU,mBAAmB;AACtC,SAAS,iBAAiB;AAM1B,SAAS,mBAAmB;AAG5B,IAAI,YAAY;AAChB,IAAI,iBAAiB;AA2Bd,SAAS,gBAAiB;AAAA,EAChC;AACD,IAA4B,CAAC,GAA2B;AACvD,QAAM,CAAE,gBAAgB,iBAAkB,IAAI;AAAA,IAC7C,CAAC;AAAA,EACF;AAEA,QAAM,iBAAiB,YAAa,MAAM;AACzC;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,WAAW,UAAW;AAAA,IACrD;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,eAAe,YAAa,CAAE,WAAoB;AACvD;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,OAAO,MAAO;AAAA,IAC7C;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,gBAAgB;AAAA,IACrB,CAAE,UAAwC;AACzC,YAAM,UAAU,OAAQ,EAAE,cAAe;AACzC,YAAM,YAAY,MAAM;AAExB,YAAM,aAA8B,MAAM,IAAK,CAAE,UAAY;AAAA,QAC5D,IAAI,OAAQ,EAAE,SAAU;AAAA,QACxB;AAAA,QACA,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,MACT,EAAI;AAEJ,wBAAmB,CAAE,SAAU,CAAE,GAAG,MAAM,GAAG,UAAW,CAAE;AAM1D,UAAI,eAAe;AACnB,UAAI,aAAa;AACjB,UAAI,YAAY;AAChB,UAAI,qBAA8C,CAAC;AAEnD,YAAM,sBAAsB,MAAM;AACjC,YAAK,aAAa,eAAe,aAAa,WAAY;AACzD;AAAA,QACD;AAEA,oBAAY;AAIZ;AAAA,UAAmB,CAAE,SACpB,KAAK;AAAA,YAAK,CAAE,SACX,KAAK,YAAY,WAAW,KAAK,WAAW,cACzC;AAAA,cACA,GAAG;AAAA,cACH,QAAQ;AAAA,YACR,IACA;AAAA,UACJ;AAAA,QACD;AAEA,0BAAmB,kBAAmB;AAAA,MACvC;AAEA,YAAM,eAAe,CAAE,gBAA0C;AAChE,YAAK,WAAY;AAChB;AAAA,QACD;AAIA,cAAM,UAAU,YAAY;AAAA,UAC3B,CAAE,eACD,WAAW,MACX,WAAW,OACX,CAAE,UAAW,WAAW,GAAI;AAAA,QAC9B;AAEA,YAAK,CAAE,SAAU;AAChB;AAAA,QACD;AAKA,uBAAe,YAAY;AAC3B,6BAAqB;AAErB,4BAAoB;AAAA,MACrB;AAEA,YAAM,UAAU,CAAE,UAAkB;AAOnC,cAAM,WACL,iBAAiB,cAAc,MAAM,KAAK,OAAO;AAQlD,0BAAmB,CAAE,SAAU;AAC9B,cAAI,UAAU;AACd,iBAAO,KAAK,IAAK,CAAE,SAAU;AAC5B,gBACC,CAAE,WACF,KAAK,YAAY,WACjB,KAAK,WAAW,gBACd,CAAE,YAAY,KAAK,SAAS,WAC7B;AACD,wBAAU;AACV,qBAAO;AAAA,gBACN,GAAG;AAAA,gBACH,QAAQ;AAAA,gBACR,OAAO,MAAM;AAAA,cACd;AAAA,YACD;AACA,mBAAO;AAAA,UACR,CAAE;AAAA,QACH,CAAE;AAEF;AAEA,4BAAoB;AAAA,MACrB;AAEA,aAAO,EAAE,cAAc,QAAQ;AAAA,IAChC;AAAA,IACA,CAAE,eAAgB;AAAA,EACnB;AAEA,QAAM,cACL,eAAe,SAAS,KACxB,eAAe,MAAO,CAAE,SAAU,KAAK,WAAW,WAAY;AAE/D,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -288,7 +288,7 @@
|
|
|
288
288
|
padding-bottom: 8px;
|
|
289
289
|
}
|
|
290
290
|
.media-upload-modal .components-modal__frame.is-full-screen .components-modal__content {
|
|
291
|
-
margin-bottom:
|
|
291
|
+
margin-bottom: 0;
|
|
292
292
|
}
|
|
293
293
|
.media-upload-modal .components-modal__content {
|
|
294
294
|
padding: 0;
|
|
@@ -296,8 +296,34 @@
|
|
|
296
296
|
.media-upload-modal .dataviews-picker-wrapper > .dataviews__view-actions {
|
|
297
297
|
padding-top: 8px;
|
|
298
298
|
}
|
|
299
|
-
.media-upload-modal .
|
|
300
|
-
|
|
299
|
+
.media-upload-modal .media-upload-modal__footer {
|
|
300
|
+
position: sticky;
|
|
301
|
+
bottom: 0;
|
|
302
|
+
background-color: inherit;
|
|
303
|
+
z-index: 2;
|
|
304
|
+
}
|
|
305
|
+
.media-upload-modal .media-upload-modal__footer.is-uploading .dataviews-picker-footer__bulk-selection {
|
|
306
|
+
visibility: hidden;
|
|
307
|
+
}
|
|
308
|
+
.media-upload-modal .media-upload-modal__footer .dataviews-footer {
|
|
309
|
+
position: static;
|
|
310
|
+
z-index: auto;
|
|
311
|
+
}
|
|
312
|
+
.media-upload-modal .media-upload-modal__upload-status {
|
|
313
|
+
position: absolute;
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
background-color: var(--wp-dataviews-color-background, #fff);
|
|
318
|
+
right: 24px;
|
|
319
|
+
top: 1px;
|
|
320
|
+
bottom: 1px;
|
|
321
|
+
z-index: 1;
|
|
322
|
+
}
|
|
323
|
+
.media-upload-modal .media-upload-modal__upload-status .components-spinner {
|
|
324
|
+
width: 16px;
|
|
325
|
+
height: 16px;
|
|
326
|
+
margin: 0;
|
|
301
327
|
}
|
|
302
328
|
|
|
303
329
|
.media-upload-modal__snackbar {
|
|
@@ -320,4 +346,48 @@
|
|
|
320
346
|
.media-upload-modal__snackbar > div {
|
|
321
347
|
transform: none !important;
|
|
322
348
|
transform-origin: 100% !important;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.media-upload-modal__upload-status__popover .components-popover__content {
|
|
352
|
+
width: 320px;
|
|
353
|
+
}
|
|
354
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__header {
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
justify-content: space-between;
|
|
358
|
+
padding: 12px 16px;
|
|
359
|
+
}
|
|
360
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__header h3 {
|
|
361
|
+
margin: 0;
|
|
362
|
+
font-size: 13px;
|
|
363
|
+
font-weight: 499;
|
|
364
|
+
}
|
|
365
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__list {
|
|
366
|
+
max-height: 200px;
|
|
367
|
+
overflow-y: auto;
|
|
368
|
+
margin: 0;
|
|
369
|
+
padding: 0 0 4px;
|
|
370
|
+
list-style: none;
|
|
371
|
+
}
|
|
372
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__item {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: flex-start;
|
|
375
|
+
margin-bottom: 0;
|
|
376
|
+
justify-content: space-between;
|
|
377
|
+
gap: 8px;
|
|
378
|
+
padding: 8px 16px;
|
|
379
|
+
}
|
|
380
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__item .components-spinner {
|
|
381
|
+
flex-shrink: 0;
|
|
382
|
+
width: 16px;
|
|
383
|
+
height: 16px;
|
|
384
|
+
margin: 0;
|
|
385
|
+
}
|
|
386
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__filename {
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
text-overflow: ellipsis;
|
|
389
|
+
white-space: nowrap;
|
|
390
|
+
min-width: 0;
|
|
391
|
+
flex: 1;
|
|
392
|
+
font-size: 12px;
|
|
323
393
|
}
|
package/build-style/style.css
CHANGED
|
@@ -288,7 +288,7 @@
|
|
|
288
288
|
padding-bottom: 8px;
|
|
289
289
|
}
|
|
290
290
|
.media-upload-modal .components-modal__frame.is-full-screen .components-modal__content {
|
|
291
|
-
margin-bottom:
|
|
291
|
+
margin-bottom: 0;
|
|
292
292
|
}
|
|
293
293
|
.media-upload-modal .components-modal__content {
|
|
294
294
|
padding: 0;
|
|
@@ -296,8 +296,34 @@
|
|
|
296
296
|
.media-upload-modal .dataviews-picker-wrapper > .dataviews__view-actions {
|
|
297
297
|
padding-top: 8px;
|
|
298
298
|
}
|
|
299
|
-
.media-upload-modal .
|
|
300
|
-
|
|
299
|
+
.media-upload-modal .media-upload-modal__footer {
|
|
300
|
+
position: sticky;
|
|
301
|
+
bottom: 0;
|
|
302
|
+
background-color: inherit;
|
|
303
|
+
z-index: 2;
|
|
304
|
+
}
|
|
305
|
+
.media-upload-modal .media-upload-modal__footer.is-uploading .dataviews-picker-footer__bulk-selection {
|
|
306
|
+
visibility: hidden;
|
|
307
|
+
}
|
|
308
|
+
.media-upload-modal .media-upload-modal__footer .dataviews-footer {
|
|
309
|
+
position: static;
|
|
310
|
+
z-index: auto;
|
|
311
|
+
}
|
|
312
|
+
.media-upload-modal .media-upload-modal__upload-status {
|
|
313
|
+
position: absolute;
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
background-color: var(--wp-dataviews-color-background, #fff);
|
|
318
|
+
left: 24px;
|
|
319
|
+
top: 1px;
|
|
320
|
+
bottom: 1px;
|
|
321
|
+
z-index: 1;
|
|
322
|
+
}
|
|
323
|
+
.media-upload-modal .media-upload-modal__upload-status .components-spinner {
|
|
324
|
+
width: 16px;
|
|
325
|
+
height: 16px;
|
|
326
|
+
margin: 0;
|
|
301
327
|
}
|
|
302
328
|
|
|
303
329
|
.media-upload-modal__snackbar {
|
|
@@ -320,4 +346,48 @@
|
|
|
320
346
|
.media-upload-modal__snackbar > div {
|
|
321
347
|
transform: none !important;
|
|
322
348
|
transform-origin: 0 !important;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.media-upload-modal__upload-status__popover .components-popover__content {
|
|
352
|
+
width: 320px;
|
|
353
|
+
}
|
|
354
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__header {
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
justify-content: space-between;
|
|
358
|
+
padding: 12px 16px;
|
|
359
|
+
}
|
|
360
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__header h3 {
|
|
361
|
+
margin: 0;
|
|
362
|
+
font-size: 13px;
|
|
363
|
+
font-weight: 499;
|
|
364
|
+
}
|
|
365
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__list {
|
|
366
|
+
max-height: 200px;
|
|
367
|
+
overflow-y: auto;
|
|
368
|
+
margin: 0;
|
|
369
|
+
padding: 0 0 4px;
|
|
370
|
+
list-style: none;
|
|
371
|
+
}
|
|
372
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__item {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: flex-start;
|
|
375
|
+
margin-bottom: 0;
|
|
376
|
+
justify-content: space-between;
|
|
377
|
+
gap: 8px;
|
|
378
|
+
padding: 8px 16px;
|
|
379
|
+
}
|
|
380
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__item .components-spinner {
|
|
381
|
+
flex-shrink: 0;
|
|
382
|
+
width: 16px;
|
|
383
|
+
height: 16px;
|
|
384
|
+
margin: 0;
|
|
385
|
+
}
|
|
386
|
+
.media-upload-modal__upload-status__popover .media-upload-modal__upload-status__filename {
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
text-overflow: ellipsis;
|
|
389
|
+
white-space: nowrap;
|
|
390
|
+
min-width: 0;
|
|
391
|
+
flex: 1;
|
|
392
|
+
font-size: 12px;
|
|
323
393
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/index.tsx"],"names":[],"mappings":"AA2CA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAoBpE,UAAU,qBAAqB;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE1B;;;OAGG;IACH,QAAQ,EAAE,CAAE,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,KAAM,IAAI,CAAC;IAEvD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAE,IAAI,EAAE;QAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,SAAS,EAAE,IAAI,EAAE,CAAC;QAClB,YAAY,CAAC,EAAE,CAAE,WAAW,EAAE,OAAO,CAAE,UAAU,CAAE,EAAE,KAAM,IAAI,CAAC;QAChE,OAAO,CAAC,EAAE,CAAE,KAAK,EAAE,KAAK,KAAM,IAAI,CAAC;QACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;KACnB,KAAM,IAAI,CAAC;IAEZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAE,EACjC,YAAY,EACZ,QAAgB,EAChB,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,KAA4B,EAC5B,MAAM,EACN,aAAoB,EACpB,UAAU,EACV,MAAa,EACb,WAAkC,GAClC,EAAE,qBAAqB,sCA4avB;AAED,eAAe,gBAAgB,CAAC"}
|