Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inline media controls for the Image component #269

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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,
};
126 changes: 44 additions & 82 deletions components/image/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor';
import { useContext, useMemo, Children, createContext } from '@wordpress/element';
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';

export const ImageContext = createContext();

const ImageWrapper = (props) => {
const {
id,
Expand All @@ -18,6 +18,10 @@ const ImageWrapper = (props) => {
labels = {},
canEditImage = true,
children,
hasInlineControls = false,
isOptional = true,
onRemove,
style,
...rest
} = props;
const hasImage = !!id;
Expand Down Expand Up @@ -45,6 +49,9 @@ const ImageWrapper = (props) => {
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
};
}, [
id,
Expand All @@ -59,6 +66,9 @@ const ImageWrapper = (props) => {
isResolvingMedia,
shouldDisplayFocalPointPicker,
hasImage,
hasInlineControls,
isOptional,
onRemove,
]);

if (hasRenderCallback) {
Expand All @@ -70,6 +80,9 @@ const ImageWrapper = (props) => {
labels,
canEditImage,
onSelect,
hasInlineControls,
isOptional,
onRemove,
});
}

Expand All @@ -91,13 +104,25 @@ const ImageWrapper = (props) => {

return (
<ImageContext.Provider value={imageContext}>
<Figure>
<Image {...rest} />
</Figure>
{hasImage && !!hasInlineControls ? (
<Figure style={{ ...style }} {...rest}>
<Media />
<InlineControls
imageUrl={imageUrl}
onSelect={onSelect}
isOptional={isOptional}
onRemove={onRemove}
/>
</Figure>
) : (
<Media style={{ display: 'block', ...style }} {...rest} />
)}
</ImageContext.Provider>
);
};

ImageWrapper.Figure = Figure;

export { ImageWrapper as Image };

ImageWrapper.defaultProps = {
Expand All @@ -106,6 +131,11 @@ ImageWrapper.defaultProps = {
onChangeFocalPoint: undefined,
labels: {},
canEditImage: true,
hasInlineControls: false,
isOptional: true,
onRemove: undefined,
children: undefined,
style: {},
};

ImageWrapper.propTypes = {
Expand All @@ -122,77 +152,9 @@ ImageWrapper.propTypes = {
instructions: PropTypes.string,
}),
canEditImage: PropTypes.bool,
};

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

return (
<figure style={{ position: 'relative', ...style }} {...rest}>
{children}
</figure>
);
};

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

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

props.style = {
...style,
...focalPointStyle,
};
}

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} {...props} />}
{canEditImage && (
<MediaPlaceholder
labels={labels}
onSelect={onSelect}
accept="image"
multiple={false}
disableMediaButtons={imageUrl}
/>
)}
</>
);
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
Loading