UNPKG

@wordpress/editor

Version:
8 lines (7 loc) 10.6 kB
{ "version": 3, "sources": ["../../../src/components/upload-progress-snackbar/index.js"], "sourcesContent": ["import { useSelect, useDispatch } from '@wordpress/data';\nimport { useEffect, useMemo, useRef } from '@wordpress/element';\nimport { __, sprintf } from '@wordpress/i18n';\nimport { speak } from '@wordpress/a11y';\nimport { store as uploadStore } from '@wordpress/upload-media';\nimport { store as noticesStore } from '@wordpress/notices';\nimport { Icon as WCIcon, Spinner } from '@wordpress/components';\nimport { check } from '@wordpress/icons';\nimport { useTracker } from './tracker';\n\nconst NOTICE_ID = 'upload-progress';\n\n// How long the completion checkmark is shown before the snackbar dismisses.\nconst COMPLETION_DISPLAY_MS = 3000;\n\n// Longest filename shown before it is middle-truncated. Long names (e.g. the\n// UUID-style names some sources produce) would otherwise stretch the snackbar.\nconst MAX_FILENAME_LENGTH = 40;\n\n/**\n * Middle-truncates a filename that exceeds `MAX_FILENAME_LENGTH`, keeping the\n * start and the end (so the file extension stays visible).\n *\n * @param {string} filename The filename to truncate.\n * @return {string} The original or middle-truncated filename.\n */\nfunction truncateFilename( filename ) {\n\tif ( filename.length <= MAX_FILENAME_LENGTH ) {\n\t\treturn filename;\n\t}\n\tconst ellipsis = '…';\n\tconst visible = MAX_FILENAME_LENGTH - ellipsis.length;\n\tconst front = Math.ceil( visible / 2 );\n\tconst back = Math.floor( visible / 2 );\n\treturn (\n\t\tfilename.slice( 0, front ) +\n\t\tellipsis +\n\t\tfilename.slice( filename.length - back )\n\t);\n}\n\n// Exported so the Storybook story can render the exact icon markup the notice\n// uses, keeping the visual review faithful to what ships.\nexport const UPLOAD_SPINNER = (\n\t<span\n\t\tclassName=\"editor-upload-progress-snackbar__spinner\"\n\t\taria-hidden=\"true\"\n\t>\n\t\t<Spinner />\n\t</span>\n);\n\nexport const UPLOAD_DONE = (\n\t<span className=\"editor-upload-progress-snackbar__check\" aria-hidden=\"true\">\n\t\t<WCIcon icon={ check } />\n\t</span>\n);\n\n/**\n * Manages a snackbar notice that shows media upload progress while uploads are\n * in progress. It creates/updates a notice via the notices store so that it\n * positions and stacks with every other snackbar in the editor.\n *\n * Reads from two sources to cover both upload paths:\n * - `@wordpress/upload-media` store (client-side media processing path).\n * - An editor-local tracker populated by the traditional `mediaUpload`\n * wrapper (non-CSM path — e.g. Safari, or when a filter disables CSM).\n *\n * Only counts original user-uploaded files (items without a `parentId`),\n * ignoring generated subsizes/thumbnails.\n *\n * @return {null} This component renders nothing — it only manages a notice.\n */\nexport default function UploadProgressSnackbar() {\n\tconst items = useSelect(\n\t\t( select ) => select( uploadStore ).getItems(),\n\t\t[]\n\t);\n\tconst tracker = useTracker();\n\n\t// CSM path: originals in the upload-media queue (subsizes excluded). Memoized\n\t// so its reference is stable across renders where `items` is unchanged, since\n\t// it's a dependency of the effect below.\n\tconst csmOriginals = useMemo(\n\t\t() => items.filter( ( item ) => ! item.parentId ),\n\t\t[ items ]\n\t);\n\tconst csmRemaining = csmOriginals.length;\n\n\t// Non-CSM path: files tracked by the editor's mediaUpload wrapper.\n\tconst trackedRemaining = tracker ? tracker.total - tracker.completed : 0;\n\n\tconst remaining = csmRemaining + trackedRemaining;\n\n\t// Track peak total across sources during a session. The CSM queue removes\n\t// items on completion, and the tracker tops out at its recorded total, so\n\t// `total` has to be tracked as the high-water mark. Held in a ref (updated\n\t// inside the effect below) because it never drives rendering - setting\n\t// state during render here discarded the in-progress render pass, which\n\t// corrupted `useSyncExternalStore`'s snapshot bookkeeping and made it drop\n\t// the tracker's completion notification, leaving the snackbar stuck in its\n\t// uploading state (see gutenberg#80343).\n\tconst sessionTotal = csmRemaining + ( tracker ? tracker.total : 0 );\n\tconst peakRef = useRef( 0 );\n\n\tconst { createNotice, removeNotice } = useDispatch( noticesStore );\n\n\t// Track whether the user has dismissed the notice. If so, don't re-create\n\t// it until the current batch finishes and a new one starts.\n\tconst dismissedRef = useRef( false );\n\tconst wasUploadingRef = useRef( false );\n\n\t// Timeout that removes the completion-state (checkmark) notice after a\n\t// brief display. Held so a new upload can cancel it.\n\tconst completionTimeoutRef = useRef( null );\n\tuseEffect( () => {\n\t\treturn () => {\n\t\t\tif ( completionTimeoutRef.current ) {\n\t\t\t\tclearTimeout( completionTimeoutRef.current );\n\t\t\t}\n\t\t};\n\t}, [] );\n\n\tuseEffect( () => {\n\t\tconst isUploading = remaining > 0;\n\n\t\tif ( isUploading && ! wasUploadingRef.current ) {\n\t\t\tdismissedRef.current = false;\n\t\t\tspeak( __( 'Media upload started' ), 'polite' );\n\t\t\t// A new batch started during the completion display: cancel the\n\t\t\t// pending dismissal so the snackbar transitions straight back\n\t\t\t// into the uploading state, and reset the peak so the new batch\n\t\t\t// counts from `1 of N` rather than resuming the previous total.\n\t\t\tif ( completionTimeoutRef.current ) {\n\t\t\t\tclearTimeout( completionTimeoutRef.current );\n\t\t\t\tcompletionTimeoutRef.current = null;\n\t\t\t\tpeakRef.current = 0;\n\t\t\t}\n\t\t} else if ( ! isUploading && wasUploadingRef.current ) {\n\t\t\tspeak( __( 'Media upload complete' ), 'polite' );\n\n\t\t\tif ( ! dismissedRef.current ) {\n\t\t\t\tcreateNotice( 'info', __( 'Upload complete' ), {\n\t\t\t\t\tid: NOTICE_ID,\n\t\t\t\t\ttype: 'snackbar',\n\t\t\t\t\tisDismissible: false,\n\t\t\t\t\texplicitDismiss: false,\n\t\t\t\t\tspeak: false,\n\t\t\t\t\ticon: UPLOAD_DONE,\n\t\t\t\t\tonDismiss: () => {\n\t\t\t\t\t\tdismissedRef.current = true;\n\t\t\t\t\t},\n\t\t\t\t} );\n\n\t\t\t\tcompletionTimeoutRef.current = setTimeout( () => {\n\t\t\t\t\tremoveNotice( NOTICE_ID );\n\t\t\t\t\tcompletionTimeoutRef.current = null;\n\t\t\t\t\tpeakRef.current = 0;\n\t\t\t\t}, COMPLETION_DISPLAY_MS );\n\t\t\t} else {\n\t\t\t\tpeakRef.current = 0;\n\t\t\t}\n\t\t}\n\n\t\twasUploadingRef.current = isUploading;\n\n\t\tif ( ! isUploading || dismissedRef.current ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( sessionTotal > peakRef.current ) {\n\t\t\tpeakRef.current = sessionTotal;\n\t\t}\n\n\t\tconst total = peakRef.current;\n\t\tconst current = total - remaining + 1;\n\n\t\t// Prefer the CSM queue's first original filename, then fall back to\n\t\t// the tracker's first pending filename.\n\t\tconst filename = truncateFilename(\n\t\t\tcsmOriginals[ 0 ]?.sourceFile?.name ||\n\t\t\t\ttracker?.pending[ 0 ] ||\n\t\t\t\t__( 'Uploading' )\n\t\t);\n\n\t\tconst content =\n\t\t\ttotal === 1\n\t\t\t\t? sprintf(\n\t\t\t\t\t\t/* translators: %s: filename. */\n\t\t\t\t\t\t__( 'Uploading — %s' ),\n\t\t\t\t\t\tfilename\n\t\t\t\t )\n\t\t\t\t: sprintf(\n\t\t\t\t\t\t/* translators: 1: current upload number, 2: total uploads, 3: filename. */\n\t\t\t\t\t\t__( 'Uploading %1$d of %2$d — %3$s' ),\n\t\t\t\t\t\tcurrent,\n\t\t\t\t\t\ttotal,\n\t\t\t\t\t\tfilename\n\t\t\t\t );\n\n\t\tcreateNotice( 'info', content, {\n\t\t\tid: NOTICE_ID,\n\t\t\ttype: 'snackbar',\n\t\t\tisDismissible: false,\n\t\t\texplicitDismiss: true,\n\t\t\tspeak: false,\n\t\t\ticon: UPLOAD_SPINNER,\n\t\t\tonDismiss: () => {\n\t\t\t\tdismissedRef.current = true;\n\t\t\t},\n\t\t} );\n\t}, [\n\t\tremaining,\n\t\tsessionTotal,\n\t\tcsmOriginals,\n\t\ttracker,\n\t\tcreateNotice,\n\t\tremoveNotice,\n\t] );\n\n\treturn null;\n}\n"], "mappings": "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", "names": ["WCIcon", "uploadStore", "noticesStore"] }