UNPKG

@wordpress/block-library

Version:
510 lines (461 loc) 15.5 kB
import { createElement, Fragment } from "@wordpress/element"; /** * External dependencies */ import classnames from 'classnames'; import { includes, pick } from 'lodash'; /** * WordPress dependencies */ import { isBlobURL } from '@wordpress/blob'; import { createInterpolateElement, useEffect, useState, useRef } from '@wordpress/element'; import { __, isRTL } from '@wordpress/i18n'; import { MenuItem, PanelBody, RangeControl, ResizableBox, Spinner, ToggleControl, ToolbarButton, Placeholder, Button } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { BlockControls, InspectorControls, MediaPlaceholder, MediaReplaceFlow, useBlockProps, store as blockEditorStore, __experimentalImageEditor as ImageEditor, __experimentalImageEditingProvider as ImageEditingProvider } from '@wordpress/block-editor'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { crop, upload } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import useClientWidth from '../image/use-client-width'; /** * Module constants */ import { MIN_SIZE } from '../image/constants'; const ALLOWED_MEDIA_TYPES = ['image']; const ACCEPT_MEDIA_STRING = 'image/*'; const SiteLogo = _ref => { let { alt, attributes: { align, width, height, isLink, linkTarget, shouldSyncIcon }, containerRef, isSelected, setAttributes, setLogo, logoUrl, siteUrl, logoId, iconId, setIcon, canUserEdit } = _ref; const clientWidth = useClientWidth(containerRef, [align]); const isLargeViewport = useViewportMatch('medium'); const isWideAligned = includes(['wide', 'full'], align); const isResizable = !isWideAligned && isLargeViewport; const [{ naturalWidth, naturalHeight }, setNaturalSize] = useState({}); const [isEditingImage, setIsEditingImage] = useState(false); const { toggleSelection } = useDispatch(blockEditorStore); const classes = classnames('custom-logo-link', { 'is-transient': isBlobURL(logoUrl) }); const { imageEditing, maxWidth, title } = useSelect(select => { const { getSettings } = select(blockEditorStore); const siteEntities = select(coreStore).getEditedEntityRecord('root', 'site'); return { title: siteEntities.title, ...pick(getSettings(), ['imageEditing', 'maxWidth']) }; }, []); useEffect(() => { // Turn the `Use as site icon` toggle off if it is on but the logo and icon have // fallen out of sync. This can happen if the toggle is saved in the `on` position, // but changes are later made to the site icon in the Customizer. if (shouldSyncIcon && logoId !== iconId) { setAttributes({ shouldSyncIcon: false }); } }, []); useEffect(() => { if (!isSelected) { setIsEditingImage(false); } }, [isSelected]); function onResizeStart() { toggleSelection(false); } function onResizeStop() { toggleSelection(true); } const img = createElement("img", { className: "custom-logo", src: logoUrl, alt: alt, onLoad: event => { setNaturalSize(pick(event.target, ['naturalWidth', 'naturalHeight'])); } }); let imgWrapper = img; // Disable reason: Image itself is not meant to be interactive, but // should direct focus to block. if (isLink) { imgWrapper = /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */ createElement("a", { href: siteUrl, className: classes, rel: "home", title: title, onClick: event => event.preventDefault() }, img) /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */ ; } let imageWidthWithinContainer; if (clientWidth && naturalWidth && naturalHeight) { const exceedMaxWidth = naturalWidth > clientWidth; imageWidthWithinContainer = exceedMaxWidth ? clientWidth : naturalWidth; } if (!isResizable || !imageWidthWithinContainer) { return createElement("div", { style: { width, height } }, imgWrapper); } // Set the default width to a responsible size. // Note that this width is also set in the attached frontend CSS file. const defaultWidth = 120; const currentWidth = width || defaultWidth; const ratio = naturalWidth / naturalHeight; const currentHeight = currentWidth / ratio; const minWidth = naturalWidth < naturalHeight ? MIN_SIZE : Math.ceil(MIN_SIZE * ratio); const minHeight = naturalHeight < naturalWidth ? MIN_SIZE : Math.ceil(MIN_SIZE / ratio); // With the current implementation of ResizableBox, an image needs an // explicit pixel value for the max-width. In absence of being able to // set the content-width, this max-width is currently dictated by the // vanilla editor style. The following variable adds a buffer to this // vanilla style, so 3rd party themes have some wiggleroom. This does, // in most cases, allow you to scale the image beyond the width of the // main column, though not infinitely. // @todo It would be good to revisit this once a content-width variable // becomes available. const maxWidthBuffer = maxWidth * 2.5; let showRightHandle = false; let showLeftHandle = false; /* eslint-disable no-lonely-if */ // See https://github.com/WordPress/gutenberg/issues/7584. if (align === 'center') { // When the image is centered, show both handles. showRightHandle = true; showLeftHandle = true; } else if (isRTL()) { // In RTL mode the image is on the right by default. // Show the right handle and hide the left handle only when it is // aligned left. Otherwise always show the left handle. if (align === 'left') { showRightHandle = true; } else { showLeftHandle = true; } } else { // Show the left handle and hide the right handle only when the // image is aligned right. Otherwise always show the right handle. if (align === 'right') { showLeftHandle = true; } else { showRightHandle = true; } } /* eslint-enable no-lonely-if */ const canEditImage = logoId && naturalWidth && naturalHeight && imageEditing; const imgEdit = canEditImage && isEditingImage ? createElement(ImageEditingProvider, { id: logoId, url: logoUrl, naturalWidth: naturalWidth, naturalHeight: naturalHeight, clientWidth: clientWidth, onSaveImage: imageAttributes => { setLogo(imageAttributes.id); }, isEditing: isEditingImage, onFinishEditing: () => setIsEditingImage(false) }, createElement(ImageEditor, { url: logoUrl, width: currentWidth, height: currentHeight, clientWidth: clientWidth, naturalHeight: naturalHeight, naturalWidth: naturalWidth })) : createElement(ResizableBox, { size: { width: currentWidth, height: currentHeight }, showHandle: isSelected, minWidth: minWidth, maxWidth: maxWidthBuffer, minHeight: minHeight, maxHeight: maxWidthBuffer / ratio, lockAspectRatio: true, enable: { top: false, right: showRightHandle, bottom: true, left: showLeftHandle }, onResizeStart: onResizeStart, onResizeStop: (event, direction, elt, delta) => { onResizeStop(); setAttributes({ width: parseInt(currentWidth + delta.width, 10), height: parseInt(currentHeight + delta.height, 10) }); } }, imgWrapper); const syncSiteIconHelpText = createInterpolateElement(__('Site Icons are what you see in browser tabs, bookmark bars, and within the WordPress mobile apps. To use a custom icon that is different from your site logo, use the <a>Site Icon settings</a>.'), { a: // eslint-disable-next-line jsx-a11y/anchor-has-content createElement("a", { href: siteUrl + '/wp-admin/customize.php?autofocus[section]=title_tagline', target: "_blank", rel: "noopener noreferrer" }) }); return createElement(Fragment, null, createElement(InspectorControls, null, createElement(PanelBody, { title: __('Settings') }, createElement(RangeControl, { label: __('Image width'), onChange: newWidth => setAttributes({ width: newWidth }), min: minWidth, max: maxWidthBuffer, initialPosition: Math.min(defaultWidth, maxWidthBuffer), value: width || '', disabled: !isResizable }), createElement(ToggleControl, { label: __('Link image to home'), onChange: () => setAttributes({ isLink: !isLink }), checked: isLink }), isLink && createElement(Fragment, null, createElement(ToggleControl, { label: __('Open in new tab'), onChange: value => setAttributes({ linkTarget: value ? '_blank' : '_self' }), checked: linkTarget === '_blank' })), canUserEdit && createElement(Fragment, null, createElement(ToggleControl, { label: __('Use as site icon'), onChange: value => { setAttributes({ shouldSyncIcon: value }); setIcon(value ? logoId : undefined); }, checked: !!shouldSyncIcon, help: syncSiteIconHelpText })))), createElement(BlockControls, { group: "block" }, canEditImage && !isEditingImage && createElement(ToolbarButton, { onClick: () => setIsEditingImage(true), icon: crop, label: __('Crop') })), imgEdit); }; export default function LogoEdit(_ref2) { let { attributes, className, setAttributes, isSelected } = _ref2; const { width, shouldSyncIcon } = attributes; const ref = useRef(); const { siteLogoId, canUserEdit, url, siteIconId, mediaItemData, isRequestingMediaItem } = useSelect(select => { const { canUser, getEntityRecord, getEditedEntityRecord } = select(coreStore); const siteSettings = getEditedEntityRecord('root', 'site'); const siteData = getEntityRecord('root', '__unstableBase'); const _siteLogo = siteSettings === null || siteSettings === void 0 ? void 0 : siteSettings.site_logo; const _readOnlyLogo = siteData === null || siteData === void 0 ? void 0 : siteData.site_logo; const _canUserEdit = canUser('update', 'settings'); const _siteLogoId = _canUserEdit ? _siteLogo : _readOnlyLogo; const _siteIconId = siteSettings === null || siteSettings === void 0 ? void 0 : siteSettings.site_icon; const mediaItem = _siteLogoId && select(coreStore).getMedia(_siteLogoId, { context: 'view' }); const _isRequestingMediaItem = _siteLogoId && !select(coreStore).hasFinishedResolution('getMedia', [_siteLogoId, { context: 'view' }]); return { siteLogoId: _siteLogoId, canUserEdit: _canUserEdit, url: siteData === null || siteData === void 0 ? void 0 : siteData.url, mediaItemData: mediaItem, isRequestingMediaItem: _isRequestingMediaItem, siteIconId: _siteIconId }; }, []); const { editEntityRecord } = useDispatch(coreStore); const setLogo = function (newValue) { let shouldForceSync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; // `shouldForceSync` is used to force syncing when the attribute // may not have updated yet. if (shouldSyncIcon || shouldForceSync) { setIcon(newValue); } editEntityRecord('root', 'site', undefined, { site_logo: newValue }); }; const setIcon = newValue => // The new value needs to be `null` to reset the Site Icon. editEntityRecord('root', 'site', undefined, { site_icon: newValue !== null && newValue !== void 0 ? newValue : null }); const { alt_text: alt, source_url: logoUrl } = mediaItemData !== null && mediaItemData !== void 0 ? mediaItemData : {}; const onInitialSelectLogo = media => { // Initialize the syncSiteIcon toggle. If we currently have no Site logo and no // site icon, automatically sync the logo to the icon. if (shouldSyncIcon === undefined) { const shouldForceSync = !siteIconId; setAttributes({ shouldSyncIcon: shouldForceSync }); // Because we cannot rely on the `shouldSyncIcon` attribute to have updated by // the time `setLogo` is called, pass an argument to force the syncing. onSelectLogo(media, shouldForceSync); return; } onSelectLogo(media); }; const onSelectLogo = function (media) { let shouldForceSync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (!media) { return; } if (!media.id && media.url) { // This is a temporary blob image. setLogo(undefined); return; } setLogo(media.id, shouldForceSync); }; const onRemoveLogo = () => { setLogo(null); setAttributes({ width: undefined }); }; const { createErrorNotice } = useDispatch(noticesStore); const onUploadError = message => { createErrorNotice(message, { type: 'snackbar' }); }; const controls = canUserEdit && logoUrl && createElement(BlockControls, { group: "other" }, createElement(MediaReplaceFlow, { mediaURL: logoUrl, allowedTypes: ALLOWED_MEDIA_TYPES, accept: ACCEPT_MEDIA_STRING, onSelect: onSelectLogo, onError: onUploadError }, createElement(MenuItem, { onClick: onRemoveLogo }, __('Reset')))); let logoImage; const isLoading = siteLogoId === undefined || isRequestingMediaItem; if (isLoading) { logoImage = createElement(Spinner, null); } if (!!logoUrl) { logoImage = createElement(SiteLogo, { alt: alt, attributes: attributes, className: className, containerRef: ref, isSelected: isSelected, setAttributes: setAttributes, logoUrl: logoUrl, setLogo: setLogo, logoId: (mediaItemData === null || mediaItemData === void 0 ? void 0 : mediaItemData.id) || siteLogoId, siteUrl: url, setIcon: setIcon, iconId: siteIconId, canUserEdit: canUserEdit }); } const placeholder = content => { const placeholderClassName = classnames('block-editor-media-placeholder', className); return createElement(Placeholder, { className: placeholderClassName, preview: logoImage, withIllustration: true }, content); }; const classes = classnames(className, { 'is-default-size': !width }); const blockProps = useBlockProps({ ref, className: classes }); const label = __('Add a site logo'); return createElement("div", blockProps, controls, !!logoUrl && logoImage, !logoUrl && !canUserEdit && createElement(Placeholder, { className: "site-logo_placeholder" }, !!isLoading && createElement("span", { className: "components-placeholder__preview" }, createElement(Spinner, null))), !logoUrl && canUserEdit && createElement(MediaPlaceholder, { onSelect: onInitialSelectLogo, accept: ACCEPT_MEDIA_STRING, allowedTypes: ALLOWED_MEDIA_TYPES, onError: onUploadError, placeholder: placeholder, mediaLibraryButton: _ref3 => { let { open } = _ref3; return createElement(Button, { icon: upload, variant: "primary", label: label, showTooltip: true, tooltipPosition: "top center", onClick: () => { open(); } }); } })); } //# sourceMappingURL=edit.js.map