UNPKG

@wordpress/editor

Version:
8 lines (7 loc) 11.3 kB
{ "version": 3, "sources": ["../../../src/components/sync-connection-error-modal/index.tsx"], "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useSelect, select } from '@wordpress/data';\nimport { useCopyToClipboard } from '@wordpress/compose';\n// @ts-ignore No exported types.\nimport { serialize } from '@wordpress/blocks';\nimport {\n\tstore as coreDataStore,\n\tprivateApis as coreDataPrivateApis,\n} from '@wordpress/core-data';\n// @ts-expect-error - No type declarations available for @wordpress/block-editor\n// prettier-ignore\nimport { privateApis, store as blockEditorStore } from '@wordpress/block-editor';\nimport {\n\tButton,\n\tModal,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { applyFilters } from '@wordpress/hooks';\nimport { useState, useEffect } from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { getSyncErrorMessages } from '../../utils/sync-error-messages';\nimport { store as editorStore } from '../../store';\nimport { unlock } from '../../lock-unlock';\nimport { useRetryCountdown } from './use-retry-countdown';\n\nconst { BlockCanvasCover } = unlock( privateApis );\nconst { retrySyncConnection } = unlock( coreDataPrivateApis );\n\n// Debounce time for initial disconnected status to allow connection to establish.\nconst INITIAL_DISCONNECTED_DEBOUNCE_MS = 20000;\n\n/**\n * Sync connection modal that displays when any entity reports a disconnection.\n * Uses BlockCanvasCover.Fill to render in the block canvas.\n *\n * @return The modal component or null if not disconnected.\n */\nexport function SyncConnectionErrorModal() {\n\tconst [ hasInitialized, setHasInitialized ] = useState( false );\n\tconst [ showModal, setShowModal ] = useState( false );\n\tconst [ isManualRetryAvailable, setIsManualRetryAvailable ] =\n\t\tuseState( false );\n\n\tconst { connectionStatus, isCollaborationEnabled, postType } = useSelect(\n\t\t( selectFn ) => {\n\t\t\tconst currentPostType =\n\t\t\t\tselectFn( editorStore ).getCurrentPostType();\n\t\t\treturn {\n\t\t\t\tconnectionStatus:\n\t\t\t\t\tselectFn( coreDataStore ).getSyncConnectionStatus() || null,\n\t\t\t\tisCollaborationEnabled:\n\t\t\t\t\tselectFn(\n\t\t\t\t\t\teditorStore\n\t\t\t\t\t).isCollaborationEnabledForCurrentPost(),\n\t\t\t\tpostType: currentPostType\n\t\t\t\t\t? selectFn( coreDataStore ).getPostType( currentPostType )\n\t\t\t\t\t: null,\n\t\t\t};\n\t\t},\n\t\t[]\n\t);\n\n\tconst { onManualRetry, secondsRemaining } =\n\t\tuseRetryCountdown( connectionStatus );\n\n\tconst copyButtonRef = useCopyToClipboard( () => {\n\t\tconst blocks = select( blockEditorStore ).getBlocks();\n\t\treturn serialize( blocks );\n\t} );\n\n\t// Set hasInitialized after a debounce to give extra time on initial load.\n\tuseEffect( () => {\n\t\tconst timeout = setTimeout( () => {\n\t\t\tsetHasInitialized( true );\n\t\t}, INITIAL_DISCONNECTED_DEBOUNCE_MS );\n\n\t\treturn () => clearTimeout( timeout );\n\t}, [] );\n\n\t// Track retry availability separately from the raw connection status.\n\t// The polling manager briefly emits `{ status: 'connecting' }` without\n\t// `canManuallyRetry` when a retry is kicked off, which would otherwise\n\t// unmount the Retry button briefly.\n\tuseEffect( () => {\n\t\tif ( 'connecting' === connectionStatus?.status ) {\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsManualRetryAvailable(\n\t\t\tconnectionStatus !== null &&\n\t\t\t\t'canManuallyRetry' in connectionStatus &&\n\t\t\t\tconnectionStatus.canManuallyRetry === true\n\t\t);\n\t}, [ connectionStatus ] );\n\n\t// Show the modal when disconnected and either retries are exhausted or\n\t// no retry is available (unrecoverable error). Hide on reconnect.\n\t// The 'connecting' state is ignored so the modal preserves its current\n\t// visibility during active retry attempts.\n\tconst canRetry =\n\t\tconnectionStatus &&\n\t\t'disconnected' === connectionStatus.status &&\n\t\t( connectionStatus.canManuallyRetry ||\n\t\t\tconnectionStatus.willAutoRetryInMs );\n\n\tuseEffect( () => {\n\t\tif ( 'connected' === connectionStatus?.status ) {\n\t\t\tsetShowModal( false );\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tconnectionStatus?.status &&\n\t\t\t'connecting' !== connectionStatus.status &&\n\t\t\t( ! canRetry || connectionStatus.backgroundRetriesFailed )\n\t\t) {\n\t\t\tsetShowModal( true );\n\t\t}\n\t}, [ connectionStatus, canRetry ] );\n\n\tif ( ! isCollaborationEnabled || ! hasInitialized || ! showModal ) {\n\t\treturn null;\n\t}\n\n\tconst error =\n\t\tconnectionStatus && 'error' in connectionStatus\n\t\t\t? connectionStatus?.error\n\t\t\t: undefined;\n\n\t// For unrecoverable errors (no retry available), allow plugins to handle\n\t// the error themselves. If a plugin returns a value other than false, it\n\t// signals that it has taken over error display and the default modal is\n\t// suppressed.\n\t//\n\t// @example\n\t// ```js\n\t// wp.hooks.addFilter(\n\t// 'editor.isSyncConnectionErrorHandled',\n\t// 'my-plugin/handle-sync-error',\n\t// ( isHandled, errorCode ) => {\n\t// if ( errorCode === 'connection-limit-exceeded' ) {\n\t// return true; // Plugin handles this error via its own UI.\n\t// }\n\t// return isHandled;\n\t// }\n\t// );\n\t// ```\n\tif (\n\t\t! canRetry &&\n\t\tapplyFilters(\n\t\t\t'editor.isSyncConnectionErrorHandled',\n\t\t\tfalse,\n\t\t\terror?.code\n\t\t) !== false\n\t) {\n\t\treturn null;\n\t}\n\n\tconst manualRetry = isManualRetryAvailable\n\t\t? () => {\n\t\t\t\tonManualRetry();\n\t\t\t\tretrySyncConnection();\n\t\t }\n\t\t: undefined;\n\n\tconst messages = getSyncErrorMessages( error );\n\n\tlet retryCountdownText: string = '';\n\tlet isRetrying = false;\n\tif ( secondsRemaining && secondsRemaining > 0 ) {\n\t\tretryCountdownText = sprintf(\n\t\t\t/* translators: %d: number of seconds until retry */\n\t\t\t_n(\n\t\t\t\t'Retrying connection in %d second\\u2026',\n\t\t\t\t'Retrying connection in %d seconds\\u2026',\n\t\t\t\tsecondsRemaining\n\t\t\t),\n\t\t\tsecondsRemaining\n\t\t);\n\t} else if ( 0 === secondsRemaining ) {\n\t\tisRetrying = true;\n\t\tretryCountdownText = __( 'Retrying\\u2026' );\n\t}\n\n\tlet editPostHref = 'edit.php';\n\tif ( postType?.slug ) {\n\t\teditPostHref = `edit.php?post_type=${ postType.slug }`;\n\t}\n\n\treturn (\n\t\t<BlockCanvasCover.Fill>\n\t\t\t<Modal\n\t\t\t\toverlayClassName=\"editor-sync-connection-error-modal\"\n\t\t\t\tisDismissible={ false }\n\t\t\t\tonRequestClose={ () => {} }\n\t\t\t\tshouldCloseOnClickOutside={ false }\n\t\t\t\tshouldCloseOnEsc={ false }\n\t\t\t\tsize=\"medium\"\n\t\t\t\ttitle={ messages.title }\n\t\t\t>\n\t\t\t\t<VStack spacing={ 6 }>\n\t\t\t\t\t<p>{ messages.description }</p>\n\t\t\t\t\t{ retryCountdownText && (\n\t\t\t\t\t\t<p className=\"editor-sync-connection-error-modal__retry-countdown\">\n\t\t\t\t\t\t\t{ retryCountdownText }\n\t\t\t\t\t\t</p>\n\t\t\t\t\t) }\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\thref={ editPostHref }\n\t\t\t\t\t\t\tisDestructive\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ sprintf(\n\t\t\t\t\t\t\t\t/* translators: %s: Post type name (e.g., \"Posts\", \"Pages\"). */\n\t\t\t\t\t\t\t\t__( 'Back to %s' ),\n\t\t\t\t\t\t\t\tpostType?.labels?.name ?? __( 'Posts' )\n\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\tref={ copyButtonRef }\n\t\t\t\t\t\t\tvariant={ manualRetry ? 'secondary' : 'primary' }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Copy Post Content' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t{ manualRetry && (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\t\taccessibleWhenDisabled\n\t\t\t\t\t\t\t\taria-disabled={ isRetrying }\n\t\t\t\t\t\t\t\tdisabled={ isRetrying }\n\t\t\t\t\t\t\t\tisBusy={ isRetrying }\n\t\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\t\tonClick={ manualRetry }\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ __( 'Retry' ) }\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t) }\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</Modal>\n\t\t</BlockCanvasCover.Fill>\n\t);\n}\n"], "mappings": "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", "names": [] }