@wordpress/block-library
Version:
Block library for the WordPress editor.
468 lines (430 loc) • 17.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import { createElement, Fragment } from "@wordpress/element";
/**
* External dependencies
*/
import { View, TouchableWithoutFeedback, InteractionManager, AccessibilityInfo, Platform } from 'react-native';
import Video from 'react-native-video';
import classnames from 'classnames/dedupe';
/**
* WordPress dependencies
*/
import { requestImageFailedRetryDialog, requestImageUploadCancelDialog, requestImageFullscreenPreview, mediaUploadSync } from '@wordpress/react-native-bridge';
import { __ } from '@wordpress/i18n';
import { Icon, Image, ImageEditingButton, IMAGE_DEFAULT_FOCAL_POINT, ToolbarButton, Gradient, ColorPalette, ColorPicker, BottomSheetConsumer, useConvertUnitToMobile, useMobileGlobalStylesColors } from '@wordpress/components';
import { BlockControls, InnerBlocks, InspectorControls, MEDIA_TYPE_IMAGE, MediaPlaceholder, MediaUpload, MediaUploadProgress, getColorObjectByColorValue, getColorObjectByAttributeValues, getGradientValueBySlug, store as blockEditorStore } from '@wordpress/block-editor';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { useDispatch, withSelect, withDispatch } from '@wordpress/data';
import { useEffect, useState, useRef, useCallback, useMemo } from '@wordpress/element';
import { cover as icon, replace, image, warning } from '@wordpress/icons';
import { getProtocol } from '@wordpress/url';
import { store as editPostStore } from '@wordpress/edit-post';
/**
* Internal dependencies
*/
import styles from './style.scss';
import { attributesFromMedia, ALLOWED_MEDIA_TYPES, IMAGE_BACKGROUND_TYPE, VIDEO_BACKGROUND_TYPE, COVER_DEFAULT_HEIGHT } from './shared';
import Controls from './controls';
import useCoverIsDark from './use-cover-is-dark';
/**
* Constants
*/
const INNER_BLOCKS_TEMPLATE = [['core/paragraph', {
align: 'center',
placeholder: __('Write title…')
}]];
const Cover = _ref => {
var _style$color, _styles$overlay;
let {
attributes,
getStylesFromColorScheme,
isParentSelected,
onFocus,
setAttributes,
openGeneralSidebar,
closeSettingsBottomSheet,
isSelected,
selectBlock,
blockWidth,
hasInnerBlocks
} = _ref;
const {
backgroundType,
dimRatio,
focalPoint,
minHeight,
url,
id,
style,
customOverlayColor,
minHeightUnit = 'px',
allowedBlocks,
templateLock,
customGradient,
gradient,
overlayColor,
isDark
} = attributes;
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false);
useEffect(() => {
let isCurrent = true; // Sync with local media store.
mediaUploadSync();
const a11yInfoChangeSubscription = AccessibilityInfo.addEventListener('screenReaderChanged', setIsScreenReaderEnabled);
AccessibilityInfo.isScreenReaderEnabled().then(() => {
if (isCurrent) {
setIsScreenReaderEnabled();
}
});
return () => {
isCurrent = false;
a11yInfoChangeSubscription.remove();
};
}, []);
const convertedMinHeight = useConvertUnitToMobile(minHeight || COVER_DEFAULT_HEIGHT, minHeightUnit);
const isImage = backgroundType === MEDIA_TYPE_IMAGE;
const THEME_COLORS_COUNT = 4;
const colorsDefault = useMobileGlobalStylesColors();
const coverDefaultPalette = useMemo(() => {
return {
colors: colorsDefault.slice(0, THEME_COLORS_COUNT)
};
}, [colorsDefault]);
const gradients = useMobileGlobalStylesColors('gradients');
const gradientValue = customGradient || getGradientValueBySlug(gradients, gradient);
const overlayColorValue = getColorObjectByAttributeValues(colorsDefault, overlayColor);
const hasBackground = !!(url || style && style.color && style.color.background || attributes.overlayColor || overlayColorValue.color || customOverlayColor || gradientValue);
const hasOnlyColorBackground = !url && (hasBackground || hasInnerBlocks);
const [isCustomColorPickerShowing, setCustomColorPickerShowing] = useState(false);
const openMediaOptionsRef = useRef(); // Initialize uploading flag to false, awaiting sync.
const [isUploadInProgress, setIsUploadInProgress] = useState(false); // Initialize upload failure flag to true if url is local.
const [didUploadFail, setDidUploadFail] = useState(id && getProtocol(url) === 'file:'); // Don't show failure if upload is in progress.
const shouldShowFailure = didUploadFail && !isUploadInProgress;
const onSelectMedia = media => {
setDidUploadFail(false);
const onSelect = attributesFromMedia(setAttributes, dimRatio);
onSelect(media);
};
const onMediaPressed = () => {
if (isUploadInProgress) {
requestImageUploadCancelDialog(id);
} else if (shouldShowFailure) {
requestImageFailedRetryDialog(id);
} else if (isImage && url) {
requestImageFullscreenPreview(url);
}
};
const [isVideoLoading, setIsVideoLoading] = useState(true);
const onVideoLoadStart = () => {
setIsVideoLoading(true);
};
const onVideoLoad = () => {
setIsVideoLoading(false);
};
const onClearMedia = useCallback(() => {
setAttributes({
focalPoint: undefined,
hasParallax: undefined,
id: undefined,
url: undefined
});
closeSettingsBottomSheet();
}, [closeSettingsBottomSheet]);
function setColor(color) {
var _colorValue$slug, _ref2;
const colorValue = getColorObjectByColorValue(colorsDefault, color);
setAttributes({
// Clear all related attributes (only one should be set).
overlayColor: (_colorValue$slug = colorValue === null || colorValue === void 0 ? void 0 : colorValue.slug) !== null && _colorValue$slug !== void 0 ? _colorValue$slug : undefined,
customOverlayColor: (_ref2 = !(colorValue !== null && colorValue !== void 0 && colorValue.slug) && color) !== null && _ref2 !== void 0 ? _ref2 : undefined,
gradient: undefined,
customGradient: undefined
});
}
function openColorPicker() {
selectBlock();
setCustomColorPickerShowing(true);
openGeneralSidebar();
}
const {
__unstableMarkNextChangeAsNotPersistent
} = useDispatch(blockEditorStore);
const isCoverDark = useCoverIsDark(isDark, url, dimRatio, overlayColorValue === null || overlayColorValue === void 0 ? void 0 : overlayColorValue.color);
useEffect(() => {
var _attributes$className;
// This side-effect should not create an undo level.
__unstableMarkNextChangeAsNotPersistent(); // Used to set a default color for its InnerBlocks
// since there's no system to inherit styles yet
// the RichText component will check if there are
// parent styles for the current block. If there are,
// it will use that color instead.
setAttributes({
isDark: isCoverDark,
childrenStyles: isCoverDark ? styles.defaultColor : styles.defaultColorLightMode
}); // Ensure that "is-light" is removed from "className" attribute if cover background is dark.
if (isCoverDark && (_attributes$className = attributes.className) !== null && _attributes$className !== void 0 && _attributes$className.includes('is-light')) {
const className = classnames(attributes.className, {
'is-light': false
});
setAttributes({
className: className !== '' ? className : undefined
});
}
}, [isCoverDark]);
const backgroundColor = getStylesFromColorScheme(styles.backgroundSolid, styles.backgroundSolidDark);
const overlayStyles = [styles.overlay, url && {
opacity: dimRatio / 100
}, !gradientValue && {
backgroundColor: customOverlayColor || (overlayColorValue === null || overlayColorValue === void 0 ? void 0 : overlayColorValue.color) || (style === null || style === void 0 ? void 0 : (_style$color = style.color) === null || _style$color === void 0 ? void 0 : _style$color.background) || ((_styles$overlay = styles.overlay) === null || _styles$overlay === void 0 ? void 0 : _styles$overlay.color)
}, // While we don't support theme colors we add a default bg color.
!overlayColorValue.color && !url ? backgroundColor : {}, isImage && isParentSelected && !isUploadInProgress && !didUploadFail && styles.overlaySelected];
const placeholderIconStyle = getStylesFromColorScheme(styles.icon, styles.iconDark);
const placeholderIcon = createElement(Icon, _extends({
icon: icon
}, placeholderIconStyle));
const toolbarControls = open => createElement(BlockControls, {
group: "other"
}, createElement(ToolbarButton, {
title: __('Edit cover media'),
icon: replace,
onClick: open
}));
const accessibilityHint = Platform.OS === 'ios' ? __('Double tap to open Action Sheet to add image or video') : __('Double tap to open Bottom Sheet to add image or video');
const addMediaButton = () => createElement(TouchableWithoutFeedback, {
accessibilityHint: accessibilityHint,
accessibilityLabel: __('Add image or video'),
accessibilityRole: "button",
onPress: openMediaOptionsRef.current
}, createElement(View, {
style: styles.selectImageContainer
}, createElement(View, {
style: styles.selectImage
}, createElement(Icon, _extends({
size: 16,
icon: image
}, styles.selectImageIcon)))));
const onBottomSheetClosed = useCallback(() => {
InteractionManager.runAfterInteractions(() => {
setCustomColorPickerShowing(false);
});
}, []);
const colorPickerControls = createElement(InspectorControls, null, createElement(BottomSheetConsumer, null, _ref3 => {
let {
shouldEnableBottomSheetScroll,
shouldEnableBottomSheetMaxHeight,
onHandleClosingBottomSheet,
onHandleHardwareButtonPress,
isBottomSheetContentScrolling
} = _ref3;
return createElement(ColorPicker, {
shouldEnableBottomSheetScroll: shouldEnableBottomSheetScroll,
shouldEnableBottomSheetMaxHeight: shouldEnableBottomSheetMaxHeight,
setColor: setColor,
onNavigationBack: closeSettingsBottomSheet,
onHandleClosingBottomSheet: onHandleClosingBottomSheet,
onHandleHardwareButtonPress: onHandleHardwareButtonPress,
onBottomSheetClosed: onBottomSheetClosed,
isBottomSheetContentScrolling: isBottomSheetContentScrolling,
bottomLabelText: __('Select a color')
});
}));
const renderContent = getMediaOptions => createElement(Fragment, null, renderBackground(getMediaOptions), isParentSelected && hasOnlyColorBackground && addMediaButton());
const renderBackground = getMediaOptions => {
var _styles$image;
return createElement(TouchableWithoutFeedback, {
accessible: !isParentSelected,
onPress: onMediaPressed,
disabled: !isParentSelected
}, createElement(View, {
style: [styles.background, backgroundColor]
}, getMediaOptions(), isParentSelected && backgroundType === VIDEO_BACKGROUND_TYPE && toolbarControls(openMediaOptionsRef.current), createElement(MediaUploadProgress, {
mediaId: id,
onUpdateMediaProgress: () => {
setIsUploadInProgress(true);
},
onFinishMediaUploadWithSuccess: _ref4 => {
let {
mediaServerId,
mediaUrl
} = _ref4;
setIsUploadInProgress(false);
setDidUploadFail(false);
setAttributes({
id: mediaServerId,
url: mediaUrl,
backgroundType
});
},
onFinishMediaUploadWithFailure: () => {
setIsUploadInProgress(false);
setDidUploadFail(true);
},
onMediaUploadStateReset: () => {
setIsUploadInProgress(false);
setDidUploadFail(false);
setAttributes({
id: undefined,
url: undefined
});
}
}), IMAGE_BACKGROUND_TYPE === backgroundType && createElement(View, {
style: styles.imageContainer
}, createElement(Image, {
editButton: false,
focalPoint: focalPoint || IMAGE_DEFAULT_FOCAL_POINT,
isSelected: isParentSelected,
isUploadFailed: didUploadFail,
isUploadInProgress: isUploadInProgress,
onSelectMediaUploadOption: onSelectMedia,
openMediaOptions: openMediaOptionsRef.current,
url: url,
width: (_styles$image = styles.image) === null || _styles$image === void 0 ? void 0 : _styles$image.width
})), VIDEO_BACKGROUND_TYPE === backgroundType && createElement(Video, {
muted: true,
disableFocus: true,
repeat: true,
resizeMode: 'cover',
source: {
uri: url
},
onLoad: onVideoLoad,
onLoadStart: onVideoLoadStart,
style: [styles.background, // Hide Video component since it has black background while loading the source.
{
opacity: isVideoLoading ? 0 : 1
}]
})));
};
if (!hasBackground && !hasInnerBlocks || isCustomColorPickerShowing) {
var _styles$mediaPlacehol;
return createElement(View, null, isCustomColorPickerShowing && colorPickerControls, createElement(MediaPlaceholder, {
height: (_styles$mediaPlacehol = styles.mediaPlaceholderEmptyStateContainer) === null || _styles$mediaPlacehol === void 0 ? void 0 : _styles$mediaPlacehol.height,
backgroundColor: customOverlayColor,
hideContent: customOverlayColor !== '' && customOverlayColor !== undefined,
icon: placeholderIcon,
labels: {
title: __('Cover')
},
onSelect: onSelectMedia,
allowedTypes: ALLOWED_MEDIA_TYPES,
onFocus: onFocus
}, createElement(View, {
style: styles.colorPaletteWrapper,
pointerEvents: isScreenReaderEnabled ? 'none' : 'auto'
}, createElement(BottomSheetConsumer, null, _ref5 => {
let {
shouldEnableBottomSheetScroll
} = _ref5;
return createElement(ColorPalette, {
customColorIndicatorStyles: styles.paletteColorIndicator,
customIndicatorWrapperStyles: styles.paletteCustomIndicatorWrapper,
setColor: setColor,
onCustomPress: openColorPicker,
defaultSettings: coverDefaultPalette,
shouldShowCustomLabel: false,
shouldShowCustomVerticalSeparator: false,
shouldEnableBottomSheetScroll: shouldEnableBottomSheetScroll
});
}))));
}
return createElement(View, {
style: styles.backgroundContainer
}, isSelected && createElement(InspectorControls, null, createElement(Controls, {
attributes: attributes,
didUploadFail: didUploadFail,
hasOnlyColorBackground: hasOnlyColorBackground,
isUploadInProgress: isUploadInProgress,
onClearMedia: onClearMedia,
onSelectMedia: onSelectMedia,
setAttributes: setAttributes
})), createElement(View, {
pointerEvents: "box-none",
style: [styles.content, {
minHeight: convertedMinHeight
}]
}, createElement(InnerBlocks, {
allowedBlocks: allowedBlocks,
template: INNER_BLOCKS_TEMPLATE,
templateLock: templateLock,
templateInsertUpdatesSelection: true,
blockWidth: blockWidth
})), createElement(View, {
pointerEvents: "none",
style: styles.overlayContainer
}, createElement(View, {
style: overlayStyles
}, gradientValue && createElement(Gradient, {
gradientValue: gradientValue,
style: styles.background
}))), createElement(MediaUpload, {
allowedTypes: ALLOWED_MEDIA_TYPES,
isReplacingMedia: !hasOnlyColorBackground,
onSelect: onSelectMedia,
render: _ref6 => {
let {
open,
getMediaOptions
} = _ref6;
openMediaOptionsRef.current = open;
return renderContent(getMediaOptions);
}
}), isImage && url && openMediaOptionsRef.current && isParentSelected && !isUploadInProgress && !didUploadFail && createElement(View, {
style: styles.imageEditButton
}, createElement(ImageEditingButton, {
onSelectMediaUploadOption: onSelectMedia,
openMediaOptions: openMediaOptionsRef.current,
pickerOptions: [{
destructiveButton: true,
id: 'clearMedia',
label: __('Clear Media'),
onPress: onClearMedia,
separated: true,
value: 'clearMedia'
}],
url: url
})), shouldShowFailure && createElement(View, {
pointerEvents: "none",
style: styles.uploadFailedContainer
}, createElement(View, {
style: styles.uploadFailed
}, createElement(Icon, _extends({
icon: warning
}, styles.uploadFailedIcon)))));
};
export default compose([withSelect((select, _ref7) => {
var _getBlock;
let {
clientId
} = _ref7;
const {
getSelectedBlockClientId,
getBlock
} = select(blockEditorStore);
const selectedBlockClientId = getSelectedBlockClientId();
const {
getSettings
} = select(blockEditorStore);
const hasInnerBlocks = ((_getBlock = getBlock(clientId)) === null || _getBlock === void 0 ? void 0 : _getBlock.innerBlocks.length) > 0;
return {
settings: getSettings(),
isParentSelected: selectedBlockClientId === clientId,
hasInnerBlocks
};
}), withDispatch((dispatch, _ref8) => {
let {
clientId
} = _ref8;
const {
openGeneralSidebar
} = dispatch(editPostStore);
const {
selectBlock
} = dispatch(blockEditorStore);
return {
openGeneralSidebar: () => openGeneralSidebar('edit-post/block'),
closeSettingsBottomSheet() {
dispatch(editPostStore).closeGeneralSidebar();
},
selectBlock: () => selectBlock(clientId)
};
}), withPreferredColorScheme])(Cover);
//# sourceMappingURL=edit.native.js.map