Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions components/image/child-components/figure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import { StyledComponentContext } from '../../styled-components-context';
import { InlineControlsStyleWrapper } from '../styles';

export const Figure = (props) => {
const { style, children, ...rest } = props;

return (
<StyledComponentContext cacheKey="tenup-component-image">
<InlineControlsStyleWrapper style={{ ...style }} {...rest}>
{children}
</InlineControlsStyleWrapper>
</StyledComponentContext>
);
};

Figure.defaultProps = {
style: {},
children: undefined,
};

Figure.propTypes = {
style: PropTypes.object,
children: PropTypes.node,
};
5 changes: 5 additions & 0 deletions components/image/child-components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Media, ImageContext } from './media';
import { Figure } from './figure';
import { InlineControls } from './inline-controls';

export { Media, ImageContext, Figure, InlineControls };
39 changes: 39 additions & 0 deletions components/image/child-components/inline-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { __ } from '@wordpress/i18n';
import { MediaReplaceFlow } from '@wordpress/block-editor';
import { ToolbarButton } from '@wordpress/components';
import PropTypes from 'prop-types';

/**
* Internal Dependencies
*/

export const InlineControls = (props) => {
const { imageUrl, onSelect, isOptional, onRemove } = props;

return (
<div className="inline-controls-sticky-wrapper">
<div className="inline-controls">
<MediaReplaceFlow mediaUrl={imageUrl} onSelect={onSelect} name={__('Replace')} />
{!!isOptional && (
<ToolbarButton onClick={onRemove} className="remove-button">
{__('Remove')}
</ToolbarButton>
)}
</div>
</div>
);
};

InlineControls.defaultProps = {
imageUrl: '',
onSelect: undefined,
isOptional: false,
onRemove: undefined,
};

InlineControls.propTypes = {
imageUrl: PropTypes.string,
onSelect: PropTypes.func,
isOptional: PropTypes.bool,
onRemove: PropTypes.func,
};
82 changes: 82 additions & 0 deletions components/image/child-components/media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from 'prop-types';
import { useContext, createContext } from '@wordpress/element';
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { Spinner, FocalPointPicker, PanelBody, Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

export const ImageContext = createContext();

export const Media = (props) => {
const { style, ...rest } = props;
const {
imageUrl,
altText,
labels,
onSelect,
isResolvingMedia,
shouldDisplayFocalPointPicker,
focalPoint,
onChangeFocalPoint,
canEditImage,
hasImage,
} = useContext(ImageContext);

let focalPointStyle = {};

if (shouldDisplayFocalPointPicker) {
focalPointStyle = {
objectFit: 'cover',
objectPosition: `${focalPoint.x * 100}% ${focalPoint.y * 100}%`,
};
}

if (isResolvingMedia) {
return <Spinner />;
}

if (!hasImage && !canEditImage) {
return <Placeholder className="block-editor-media-placeholder" withIllustration />;
}

return (
<>
{shouldDisplayFocalPointPicker && (
<InspectorControls>
<PanelBody title={__('Image Settings')}>
<FocalPointPicker
label={__('Focal Point Picker')}
url={imageUrl}
value={focalPoint}
onChange={onChangeFocalPoint}
/>
</PanelBody>
</InspectorControls>
)}
{hasImage && (
<img
src={imageUrl}
alt={altText}
style={{ ...style, ...focalPointStyle }}
{...rest}
/>
)}
{canEditImage && (
<MediaPlaceholder
labels={labels}
onSelect={onSelect}
accept="image"
multiple={false}
disableMediaButtons={imageUrl}
/>
)}
</>
);
};

Media.defaultProps = {
style: {},
};

Media.propTypes = {
style: PropTypes.object,
};
150 changes: 107 additions & 43 deletions components/image/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { Spinner, FocalPointPicker, PanelBody, Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { MediaPlaceholder } from '@wordpress/block-editor';
import { useMemo, Children } from '@wordpress/element';
import PropTypes from 'prop-types';

/**
* Internal Dependencies
*/
import { Media, ImageContext, Figure, InlineControls } from './child-components';
import { useMedia } from '../../hooks/use-media';

const Image = (props) => {
const ImageWrapper = (props) => {
const {
id,
size = 'full',
Expand All @@ -14,72 +17,128 @@ const Image = (props) => {
onChangeFocalPoint,
labels = {},
canEditImage = true,
children,
hasInlineControls = false,
isOptional = true,
onRemove,
style,
...rest
} = props;
const hasImage = !!id;
const { media, isResolvingMedia } = useMedia(id);

const shouldDisplayFocalPointPicker = typeof onChangeFocalPoint === 'function';

if (!hasImage && !canEditImage) {
return <Placeholder className="block-editor-media-placeholder" withIllustration />;
}
const hasRenderCallback = typeof children === 'function';
const hasChildComponents = !hasRenderCallback && Children.count(children);

if (!hasImage && canEditImage) {
return (
<MediaPlaceholder labels={labels} onSelect={onSelect} accept="image" multiple={false} />
);
}

if (isResolvingMedia) {
return <Spinner />;
}
const shouldDisplayFocalPointPicker = typeof onChangeFocalPoint === 'function';

const imageUrl = media?.media_details?.sizes[size]?.source_url ?? media?.source_url;
const altText = media?.alt_text;

if (shouldDisplayFocalPointPicker) {
const focalPointStyle = {
objectFit: 'cover',
objectPosition: `${focalPoint.x * 100}% ${focalPoint.y * 100}%`,
const imageContext = useMemo(() => {
return {
id,
size,
focalPoint,
onChangeFocalPoint,
imageUrl,
altText,
labels,
canEditImage,
onSelect,
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
};
}, [
id,
size,
focalPoint,
onChangeFocalPoint,
imageUrl,
altText,
labels,
canEditImage,
onSelect,
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
]);

rest.style = {
...rest.style,
...focalPointStyle,
};
if (hasRenderCallback) {
return children({
hasImage,
imageUrl,
altText,
focalPoint,
labels,
canEditImage,
onSelect,
hasInlineControls,
isOptional,
onRemove,
});
}

if (hasChildComponents) {
return <ImageContext.Provider value={imageContext}>{children}</ImageContext.Provider>;
}

if (!hasImage && canEditImage) {
return (
<MediaPlaceholder
labels={labels}
onSelect={onSelect}
accept="image"
multiple={false}
disableMediaButtons={imageUrl}
/>
);
}

return (
<>
{shouldDisplayFocalPointPicker && (
<InspectorControls>
<PanelBody title={__('Image Settings')}>
<FocalPointPicker
label={__('Focal Point Picker')}
url={imageUrl}
value={focalPoint}
onChange={onChangeFocalPoint}
/>
</PanelBody>
</InspectorControls>
<ImageContext.Provider value={imageContext}>
{hasImage && !!hasInlineControls ? (
<Figure style={{ ...style }} {...rest}>
<Media />
<InlineControls
imageUrl={imageUrl}
onSelect={onSelect}
isOptional={isOptional}
onRemove={onRemove}
/>
</Figure>
) : (
<Media style={{ display: 'block', ...style }} {...rest} />
)}
<img src={imageUrl} alt={altText} {...rest} />
</>
</ImageContext.Provider>
);
};

export { Image };
ImageWrapper.Figure = Figure;

export { ImageWrapper as Image };

Image.defaultProps = {
ImageWrapper.defaultProps = {
size: 'large',
focalPoint: { x: 0.5, y: 0.5 },
onChangeFocalPoint: undefined,
labels: {},
canEditImage: true,
hasInlineControls: false,
isOptional: true,
onRemove: undefined,
children: undefined,
style: {},
};

Image.propTypes = {
ImageWrapper.propTypes = {
id: PropTypes.number.isRequired,
size: PropTypes.string,
onSelect: PropTypes.func.isRequired,
Expand All @@ -93,4 +152,9 @@ Image.propTypes = {
instructions: PropTypes.string,
}),
canEditImage: PropTypes.bool,
hasInlineControls: PropTypes.bool,
isOptional: PropTypes.bool,
onRemove: PropTypes.func,
children: PropTypes.node,
style: PropTypes.object,
};
8 changes: 5 additions & 3 deletions components/image/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ function BlockEdit(props) {

| Name | Type | Default | Description |
| ---------- | ----------------- | -------- | -------------------------------------------------------------- |
| `id` | `number` | `null` | Image ID |
| `id` | `number` | `null` | Image ID |
| `onSelect` | `Function` | `null` | Callback that gets called with the new image when one is selected |
| `size` | `string` | `large` | Name of the image size to be displayed |
| `focalPoint` | `object` | `{x:0.5,y:0.5}` | Optional focal point object.
| `onChangeFocalPoint` | `function` | `undefined` | Callback that gets called with the new focal point when it changes. (Is required for the FocalPointPicker to appear) |
| `labels` | `object` | `{}` | Pass in an object of labels to be used by the `MediaPlaceholder` component under the hook. Allows the sub properties `title` and `instructions` |
| `canEditImage` | `boolean` | `true` | whether or not the image can be edited by in the context its getting viewed. Controls whether a placeholder or upload controls should be shown when no image is present |
| `...rest` | `*` | `null` | any additional attributes you want to pass to the underlying `img` tag |
| `canEditImage` | `boolean` | `true` | Whether or not the image can be edited by in the context its getting viewed. Controls whether a placeholder or upload controls should be shown when no image is present |
| `hasInlineControls` | `boolean` | `false` | When `true`, it will display inline media flow controls |
| `isOptional` | `boolean` | `false` | Wether or not the inline controls' Remove Image button should be shown. ***NOTE:*** it has no effect if `hasInlineControls` is `false` |
| `...rest` | `*` | `null` | Any additional attributes you want to pass to the underlying `img` tag |
Loading