UNPKG

@wordpress/block-library

Version:
802 lines (801 loc) 25.4 kB
// packages/block-library/src/gallery/edit.js import clsx from "clsx"; import { SelectControl, ToggleControl, RangeControl, MenuGroup, MenuItem, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, ToolbarDropdownMenu, PanelBody } from "@wordpress/components"; import { store as blockEditorStore, MediaPlaceholder, InspectorControls, useBlockProps, useInnerBlocksProps, BlockControls, MediaReplaceFlow, useSettings } from "@wordpress/block-editor"; import { Platform, useEffect, useMemo } from "@wordpress/element"; import { __, _x, sprintf } from "@wordpress/i18n"; import { useSelect, useDispatch } from "@wordpress/data"; import { View } from "@wordpress/primitives"; import { createBlock } from "@wordpress/blocks"; import { createBlobURL } from "@wordpress/blob"; import { store as noticesStore } from "@wordpress/notices"; import { link as linkIcon, customLink, image as imageIcon, linkOff, fullscreen } from "@wordpress/icons"; import { sharedIcon } from "./shared-icon"; import { defaultColumnsNumber, pickRelevantMediaFiles } from "./shared"; import { getHrefAndDestination } from "./utils"; import { useToolsPanelDropdownMenuProps } from "../utils/hooks"; import { getUpdatedLinkTargetSettings, getImageSizeAttributes } from "../image/utils"; import Gallery from "./gallery"; import { LINK_DESTINATION_ATTACHMENT, LINK_DESTINATION_MEDIA, LINK_DESTINATION_NONE, LINK_DESTINATION_LIGHTBOX, DEFAULT_MEDIA_SIZE_SLUG } from "./constants"; import useImageSizes from "./use-image-sizes"; import useGetNewImages from "./use-get-new-images"; import useGetMedia from "./use-get-media"; import GapStyles from "./gap-styles"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var MAX_COLUMNS = 8; var LINK_OPTIONS = [ { icon: customLink, label: __("Link images to attachment pages"), value: LINK_DESTINATION_ATTACHMENT, noticeText: __("Attachment Pages") }, { icon: imageIcon, label: __("Link images to media files"), value: LINK_DESTINATION_MEDIA, noticeText: __("Media Files") }, { icon: fullscreen, label: __("Enlarge on click"), value: LINK_DESTINATION_LIGHTBOX, noticeText: __("Lightbox effect"), infoText: __("Scale images with a lightbox effect") }, { icon: linkOff, label: _x("None", "Media item link option"), value: LINK_DESTINATION_NONE, noticeText: __("None") } ]; var ALLOWED_MEDIA_TYPES = ["image"]; var PLACEHOLDER_TEXT = Platform.isNative ? __("Add media") : __("Drag and drop images, upload, or choose from your library."); var MOBILE_CONTROL_PROPS_RANGE_CONTROL = Platform.isNative ? { type: "stepper" } : {}; var DEFAULT_BLOCK = { name: "core/image" }; var EMPTY_ARRAY = []; function GalleryEdit(props) { const { setAttributes, attributes, className, clientId, isSelected, insertBlocksAfter, isContentLocked, onFocus } = props; const [lightboxSetting, defaultRatios, themeRatios, showDefaultRatios] = useSettings( "blocks.core/image.lightbox", "dimensions.aspectRatios.default", "dimensions.aspectRatios.theme", "dimensions.defaultAspectRatios" ); const linkOptions = !lightboxSetting?.allowEditing ? LINK_OPTIONS.filter( (option) => option.value !== LINK_DESTINATION_LIGHTBOX ) : LINK_OPTIONS; const { columns, imageCrop, randomOrder, linkTarget, linkTo, sizeSlug, aspectRatio } = attributes; const { __unstableMarkNextChangeAsNotPersistent, replaceInnerBlocks, updateBlockAttributes, selectBlock } = useDispatch(blockEditorStore); const { createSuccessNotice, createErrorNotice } = useDispatch(noticesStore); const { getBlock, getSettings, innerBlockImages, blockWasJustInserted, multiGallerySelection } = useSelect( (select) => { const { getBlockName, getMultiSelectedBlockClientIds, getSettings: _getSettings, getBlock: _getBlock, wasBlockJustInserted } = select(blockEditorStore); const multiSelectedClientIds = getMultiSelectedBlockClientIds(); return { getBlock: _getBlock, getSettings: _getSettings, innerBlockImages: _getBlock(clientId)?.innerBlocks ?? EMPTY_ARRAY, blockWasJustInserted: wasBlockJustInserted( clientId, "inserter_menu" ), multiGallerySelection: multiSelectedClientIds.length && multiSelectedClientIds.every( (_clientId) => getBlockName(_clientId) === "core/gallery" ) }; }, [clientId] ); const images = useMemo( () => innerBlockImages?.map((block) => ({ clientId: block.clientId, id: block.attributes.id, url: block.attributes.url, attributes: block.attributes, fromSavedContent: Boolean(block.originalContent) })), [innerBlockImages] ); const imageData = useGetMedia(innerBlockImages); const newImages = useGetNewImages(images, imageData); const themeOptions = themeRatios?.map(({ name, ratio }) => ({ label: name, value: ratio })); const defaultOptions = defaultRatios?.map(({ name, ratio }) => ({ label: name, value: ratio })); const aspectRatioOptions = [ { label: _x( "Original", "Aspect ratio option for dimensions control" ), value: "auto" }, ...showDefaultRatios ? defaultOptions || [] : [], ...themeOptions || [] ]; useEffect(() => { newImages?.forEach((newImage) => { __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes(newImage.clientId, { ...buildImageAttributes(newImage.attributes), id: newImage.id, align: void 0 }); }); }, [newImages]); const imageSizeOptions = useImageSizes( imageData, isSelected, getSettings ); function buildImageAttributes(imageAttributes) { const image = imageAttributes.id ? imageData.find(({ id }) => id === imageAttributes.id) : null; let newClassName; if (imageAttributes.className && imageAttributes.className !== "") { newClassName = imageAttributes.className; } let newLinkTarget; if (imageAttributes.linkTarget || imageAttributes.rel) { newLinkTarget = { linkTarget: imageAttributes.linkTarget, rel: imageAttributes.rel }; } else { newLinkTarget = getUpdatedLinkTargetSettings( linkTarget, attributes ); } return { ...pickRelevantMediaFiles(image, sizeSlug), ...getHrefAndDestination( image, linkTo, imageAttributes?.linkDestination ), ...newLinkTarget, className: newClassName, sizeSlug, caption: imageAttributes.caption.length > 0 ? imageAttributes.caption : image.caption?.raw, alt: imageAttributes.alt || image.alt_text, aspectRatio: aspectRatio === "auto" ? void 0 : aspectRatio }; } function isValidFileType(file) { const nativeFileData = Platform.isNative && file.id ? imageData.find(({ id }) => id === file.id) : null; const mediaTypeSelector = nativeFileData ? nativeFileData?.media_type : file.type; return ALLOWED_MEDIA_TYPES.some( (mediaType) => mediaTypeSelector?.indexOf(mediaType) === 0 ) || file.blob; } function updateImages(selectedImages) { const newFileUploads = Object.prototype.toString.call(selectedImages) === "[object FileList]"; const imageArray = newFileUploads ? Array.from(selectedImages).map((file) => { if (!file.url) { return { blob: createBlobURL(file) }; } return file; }) : selectedImages; if (!imageArray.every(isValidFileType)) { createErrorNotice( __( "If uploading to a gallery all files need to be image formats" ), { id: "gallery-upload-invalid-file", type: "snackbar" } ); } const processedImages = imageArray.filter((file) => file.url || isValidFileType(file)).map((file) => { if (!file.url) { return { blob: file.blob || createBlobURL(file) }; } return file; }); const newOrderMap = processedImages.reduce( (result, image, index) => (result[image.id] = index, result), {} ); const existingImageBlocks = !newFileUploads ? innerBlockImages.filter( (block) => processedImages.find( (img) => img.id === block.attributes.id ) ) : innerBlockImages; const newImageList = processedImages.filter( (img) => !existingImageBlocks.find( (existingImg) => img.id === existingImg.attributes.id ) ); const newBlocks = newImageList.map((image) => { return createBlock("core/image", { id: image.id, blob: image.blob, url: image.url, caption: image.caption, alt: image.alt }); }); replaceInnerBlocks( clientId, existingImageBlocks.concat(newBlocks).sort( (a, b) => newOrderMap[a.attributes.id] - newOrderMap[b.attributes.id] ) ); if (newBlocks?.length > 0) { selectBlock(newBlocks[0].clientId); } } function onUploadError(message) { createErrorNotice(message, { type: "snackbar" }); } function setLinkTo(value) { setAttributes({ linkTo: value }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach((block) => { blocks.push(block.clientId); const image = block.attributes.id ? imageData.find(({ id }) => id === block.attributes.id) : null; changedAttributes[block.clientId] = getHrefAndDestination( image, value, false, block.attributes, lightboxSetting ); }); updateBlockAttributes(blocks, changedAttributes, { uniqueByBlock: true }); const linkToText = [...linkOptions].find( (linkType) => linkType.value === value ); createSuccessNotice( sprintf( /* translators: %s: image size settings */ __("All gallery image links updated to: %s"), linkToText.noticeText ), { id: "gallery-attributes-linkTo", type: "snackbar" } ); } function setColumnsNumber(value) { setAttributes({ columns: value }); } function toggleImageCrop() { setAttributes({ imageCrop: !imageCrop }); } function toggleRandomOrder() { setAttributes({ randomOrder: !randomOrder }); } function toggleOpenInNewTab(openInNewTab) { const newLinkTarget = openInNewTab ? "_blank" : void 0; setAttributes({ linkTarget: newLinkTarget }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach((block) => { blocks.push(block.clientId); changedAttributes[block.clientId] = getUpdatedLinkTargetSettings( newLinkTarget, block.attributes ); }); updateBlockAttributes(blocks, changedAttributes, { uniqueByBlock: true }); const noticeText = openInNewTab ? __("All gallery images updated to open in new tab") : __("All gallery images updated to not open in new tab"); createSuccessNotice(noticeText, { id: "gallery-attributes-openInNewTab", type: "snackbar" }); } function updateImagesSize(newSizeSlug) { setAttributes({ sizeSlug: newSizeSlug }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach((block) => { blocks.push(block.clientId); const image = block.attributes.id ? imageData.find(({ id }) => id === block.attributes.id) : null; changedAttributes[block.clientId] = getImageSizeAttributes( image, newSizeSlug ); }); updateBlockAttributes(blocks, changedAttributes, { uniqueByBlock: true }); const imageSize = imageSizeOptions.find( (size) => size.value === newSizeSlug ); createSuccessNotice( sprintf( /* translators: %s: image size settings */ __("All gallery image sizes updated to: %s"), imageSize?.label ?? newSizeSlug ), { id: "gallery-attributes-sizeSlug", type: "snackbar" } ); } function setAspectRatio(value) { setAttributes({ aspectRatio: value }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach((block) => { blocks.push(block.clientId); changedAttributes[block.clientId] = { aspectRatio: value === "auto" ? void 0 : value }; }); updateBlockAttributes(blocks, changedAttributes, true); const aspectRatioText = aspectRatioOptions.find( (option) => option.value === value ); createSuccessNotice( sprintf( /* translators: %s: aspect ratio setting */ __("All gallery images updated to aspect ratio: %s"), aspectRatioText?.label || value ), { id: "gallery-attributes-aspectRatio", type: "snackbar" } ); } useEffect(() => { if (!linkTo) { __unstableMarkNextChangeAsNotPersistent(); setAttributes({ linkTo: window?.wp?.media?.view?.settings?.defaultProps?.link || LINK_DESTINATION_NONE }); } }, [linkTo]); const hasImages = !!images.length; const hasImageIds = hasImages && images.some((image) => !!image.id); const imagesUploading = images.some( (img) => !Platform.isNative ? !img.id && img.url?.indexOf("blob:") === 0 : img.url?.indexOf("file:") === 0 ); const mediaPlaceholderProps = Platform.select({ web: { addToGallery: false, disableMediaButtons: imagesUploading, value: {} }, native: { addToGallery: hasImageIds, isAppender: hasImages, disableMediaButtons: hasImages && !isSelected || imagesUploading, value: hasImageIds ? images : {}, autoOpenMediaUpload: !hasImages && isSelected && blockWasJustInserted, onFocus } }); const mediaPlaceholder = /* @__PURE__ */ jsx( MediaPlaceholder, { handleUpload: false, icon: sharedIcon, labels: { title: __("Gallery"), instructions: PLACEHOLDER_TEXT }, onSelect: updateImages, allowedTypes: ALLOWED_MEDIA_TYPES, multiple: true, onError: onUploadError, ...mediaPlaceholderProps } ); const blockProps = useBlockProps({ className: clsx(className, "has-nested-images") }); const nativeInnerBlockProps = Platform.isNative && { marginHorizontal: 0, marginVertical: 0 }; const innerBlocksProps = useInnerBlocksProps(blockProps, { defaultBlock: DEFAULT_BLOCK, directInsert: true, orientation: "horizontal", renderAppender: false, ...nativeInnerBlockProps }); const dropdownMenuProps = useToolsPanelDropdownMenuProps(); if (!hasImages) { return /* @__PURE__ */ jsxs(View, { ...innerBlocksProps, children: [ innerBlocksProps.children, mediaPlaceholder ] }); } const hasLinkTo = linkTo && linkTo !== "none"; return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs(InspectorControls, { children: [ Platform.isWeb && /* @__PURE__ */ jsxs( ToolsPanel, { label: __("Settings"), resetAll: () => { setAttributes({ columns: void 0, imageCrop: true, randomOrder: false }); setAspectRatio("auto"); if (sizeSlug !== DEFAULT_MEDIA_SIZE_SLUG) { updateImagesSize(DEFAULT_MEDIA_SIZE_SLUG); } if (linkTarget) { toggleOpenInNewTab(false); } }, dropdownMenuProps, children: [ images.length > 1 && /* @__PURE__ */ jsx( ToolsPanelItem, { isShownByDefault: true, label: __("Columns"), hasValue: () => !!columns && columns !== images.length, onDeselect: () => setColumnsNumber(void 0), children: /* @__PURE__ */ jsx( RangeControl, { __nextHasNoMarginBottom: true, label: __("Columns"), value: columns ? columns : defaultColumnsNumber( images.length ), onChange: setColumnsNumber, min: 1, max: Math.min( MAX_COLUMNS, images.length ), required: true, __next40pxDefaultSize: true } ) } ), imageSizeOptions?.length > 0 && /* @__PURE__ */ jsx( ToolsPanelItem, { isShownByDefault: true, label: __("Resolution"), hasValue: () => sizeSlug !== DEFAULT_MEDIA_SIZE_SLUG, onDeselect: () => updateImagesSize(DEFAULT_MEDIA_SIZE_SLUG), children: /* @__PURE__ */ jsx( SelectControl, { __nextHasNoMarginBottom: true, label: __("Resolution"), help: __( "Select the size of the source images." ), value: sizeSlug, options: imageSizeOptions, onChange: updateImagesSize, hideCancelButton: true, size: "__unstable-large" } ) } ), /* @__PURE__ */ jsx( ToolsPanelItem, { isShownByDefault: true, label: __("Crop images to fit"), hasValue: () => !imageCrop, onDeselect: () => setAttributes({ imageCrop: true }), children: /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Crop images to fit"), checked: !!imageCrop, onChange: toggleImageCrop } ) } ), /* @__PURE__ */ jsx( ToolsPanelItem, { isShownByDefault: true, label: __("Randomize order"), hasValue: () => !!randomOrder, onDeselect: () => setAttributes({ randomOrder: false }), children: /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Randomize order"), checked: !!randomOrder, onChange: toggleRandomOrder } ) } ), hasLinkTo && /* @__PURE__ */ jsx( ToolsPanelItem, { isShownByDefault: true, label: __("Open images in new tab"), hasValue: () => !!linkTarget, onDeselect: () => toggleOpenInNewTab(false), children: /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Open images in new tab"), checked: linkTarget === "_blank", onChange: toggleOpenInNewTab } ) } ), aspectRatioOptions.length > 1 && /* @__PURE__ */ jsx( ToolsPanelItem, { hasValue: () => !!aspectRatio && aspectRatio !== "auto", label: __("Aspect ratio"), onDeselect: () => setAspectRatio("auto"), isShownByDefault: true, children: /* @__PURE__ */ jsx( SelectControl, { __next40pxDefaultSize: true, __nextHasNoMarginBottom: true, label: __("Aspect ratio"), help: __( "Set a consistent aspect ratio for all images in the gallery." ), value: aspectRatio, options: aspectRatioOptions, onChange: setAspectRatio } ) } ) ] } ), Platform.isNative && /* @__PURE__ */ jsxs(PanelBody, { title: __("Settings"), children: [ images.length > 1 && /* @__PURE__ */ jsx( RangeControl, { __nextHasNoMarginBottom: true, label: __("Columns"), value: columns ? columns : defaultColumnsNumber(images.length), onChange: setColumnsNumber, min: 1, max: Math.min(MAX_COLUMNS, images.length), ...MOBILE_CONTROL_PROPS_RANGE_CONTROL, required: true, __next40pxDefaultSize: true } ), imageSizeOptions?.length > 0 && /* @__PURE__ */ jsx( SelectControl, { __nextHasNoMarginBottom: true, label: __("Resolution"), help: __( "Select the size of the source images." ), value: sizeSlug, options: imageSizeOptions, onChange: updateImagesSize, hideCancelButton: true, size: "__unstable-large" } ), /* @__PURE__ */ jsx( SelectControl, { __nextHasNoMarginBottom: true, label: __("Link"), value: linkTo, onChange: setLinkTo, options: linkOptions, hideCancelButton: true, size: "__unstable-large" } ), /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Crop images to fit"), checked: !!imageCrop, onChange: toggleImageCrop } ), /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Randomize order"), checked: !!randomOrder, onChange: toggleRandomOrder } ), hasLinkTo && /* @__PURE__ */ jsx( ToggleControl, { __nextHasNoMarginBottom: true, label: __("Open images in new tab"), checked: linkTarget === "_blank", onChange: toggleOpenInNewTab } ), aspectRatioOptions.length > 1 && /* @__PURE__ */ jsx( SelectControl, { __nextHasNoMarginBottom: true, label: __("Aspect Ratio"), help: __( "Set a consistent aspect ratio for all images in the gallery." ), value: aspectRatio, options: aspectRatioOptions, onChange: setAspectRatio, hideCancelButton: true, size: "__unstable-large" } ) ] }) ] }), Platform.isWeb ? /* @__PURE__ */ jsx(BlockControls, { group: "block", children: /* @__PURE__ */ jsx( ToolbarDropdownMenu, { icon: linkIcon, label: __("Link"), children: ({ onClose }) => /* @__PURE__ */ jsx(MenuGroup, { children: linkOptions.map((linkItem) => { const isOptionSelected = linkTo === linkItem.value; return /* @__PURE__ */ jsx( MenuItem, { isSelected: isOptionSelected, className: clsx( "components-dropdown-menu__menu-item", { "is-active": isOptionSelected } ), iconPosition: "left", icon: linkItem.icon, onClick: () => { setLinkTo(linkItem.value); onClose(); }, role: "menuitemradio", info: linkItem.infoText, children: linkItem.label }, linkItem.value ); }) }) } ) }) : null, Platform.isWeb && /* @__PURE__ */ jsxs(Fragment, { children: [ !multiGallerySelection && /* @__PURE__ */ jsx(BlockControls, { group: "other", children: /* @__PURE__ */ jsx( MediaReplaceFlow, { allowedTypes: ALLOWED_MEDIA_TYPES, handleUpload: false, onSelect: updateImages, name: __("Add"), multiple: true, mediaIds: images.filter((image) => image.id).map((image) => image.id), addToGallery: hasImageIds } ) }), /* @__PURE__ */ jsx( GapStyles, { blockGap: attributes.style?.spacing?.blockGap, clientId } ) ] }), /* @__PURE__ */ jsx( Gallery, { ...props, isContentLocked, images, mediaPlaceholder: !hasImages || Platform.isNative ? mediaPlaceholder : void 0, blockProps: innerBlocksProps, insertBlocksAfter, multiGallerySelection } ) ] }); } export { GalleryEdit as default }; //# sourceMappingURL=edit.js.map