Skip to content

Commit

Permalink
Refactor Blocks/Video/Edit component (#4960)
Browse files Browse the repository at this point in the history
Co-authored-by: Nilesh <[email protected]>
  • Loading branch information
Tishasoumya-02 and nileshgulia1 authored Aug 13, 2024
1 parent 8615542 commit 1710f8b
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 172 deletions.
1 change: 1 addition & 0 deletions packages/volto/news/4960.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor Blocks/Video/Edit component from class to functional component. @Tishasoumya-02
277 changes: 105 additions & 172 deletions packages/volto/src/components/manage/Blocks/Video/Edit.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
/**
* Edit video block.
* @module components/manage/Blocks/Title/Edit
*/

import React, { Component } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { Button, Input, Message } from 'semantic-ui-react';
import cx from 'classnames';
import { isEqual } from 'lodash';

import { Icon, SidebarPortal, VideoSidebar } from '@plone/volto/components';
import clearSVG from '@plone/volto/icons/clear.svg';
import aheadSVG from '@plone/volto/icons/ahead.svg';
import videoBlockSVG from '@plone/volto/components/manage/Blocks/Video/block-video.svg';
import Body from '@plone/volto/components/manage/Blocks/Video/Body';
import { withBlockExtensions } from '@plone/volto/helpers';
import { compose } from 'redux';

const messages = defineMessages({
VideoFormDescription: {
Expand All @@ -29,183 +22,123 @@ const messages = defineMessages({
},
});

/**
* Edit video block class.
* @class Edit
* @extends Component
*/
class Edit extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
selected: PropTypes.bool.isRequired,
block: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
data: PropTypes.objectOf(PropTypes.any).isRequired,
onChangeBlock: PropTypes.func.isRequired,
onSelectBlock: PropTypes.func.isRequired,
onDeleteBlock: PropTypes.func.isRequired,
onFocusPreviousBlock: PropTypes.func.isRequired,
onFocusNextBlock: PropTypes.func.isRequired,
handleKeyDown: PropTypes.func.isRequired,
};

/**
* Constructor
* @method constructor
* @param {Object} props Component properties
* @constructs WysiwygEditor
*/
constructor(props) {
super(props);

this.onChangeUrl = this.onChangeUrl.bind(this);
this.onSubmitUrl = this.onSubmitUrl.bind(this);
this.onKeyDownVariantMenuForm = this.onKeyDownVariantMenuForm.bind(this);
this.state = {
url: '',
};
}

/**
* Change url handler
* @method onChangeUrl
* @param {Object} target Target object
* @returns {undefined}
*/
onChangeUrl({ target }) {
this.setState({
url: target.value,
});
}
const Edit = (props) => {
const { data, block, onChangeBlock, selected } = props;
const intl = useIntl();
const [url, setUrl] = useState('');

/**
* @param {*} nextProps
* @returns {boolean}
* @memberof Edit
*/
shouldComponentUpdate(nextProps) {
return (
this.props.selected ||
nextProps.selected ||
!isEqual(this.props.data, nextProps.data)
);
}
const onChangeUrl = ({ target }) => {
setUrl(target.value);
};

/**
* Submit url handler
* @method onSubmitUrl
* @returns {undefined}
*/
onSubmitUrl() {
this.props.onChangeBlock(this.props.block, {
...this.props.data,
url: this.state.url,
const onSubmitUrl = useCallback(() => {
onChangeBlock(block, {
...data,
url: url,
});
}
}, [onChangeBlock, block, data, url]);

resetSubmitUrl = () => {
this.setState({
url: '',
});
const resetSubmitUrl = () => {
setUrl('');
};

/**
* Keydown handler on Variant Menu Form
* This is required since the ENTER key is already mapped to a onKeyDown
* event and needs to be overridden with a child onKeyDown.
* @method onKeyDownVariantMenuForm
* @param {Object} e Event object
* @returns {undefined}
*/
onKeyDownVariantMenuForm(e) {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
this.onSubmitUrl();
} else if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
// TODO: Do something on ESC key
}
}
const onKeyDownVariantMenuForm = useCallback(
(e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
onSubmitUrl();
} else if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
// TODO: Do something on ESC key
}
},
[onSubmitUrl],
);

/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
const { data } = this.props;
const placeholder =
this.props.data.placeholder ||
this.props.intl.formatMessage(messages.VideoBlockInputPlaceholder);
return (
<div
className={cx(
'block video align',
{
selected: this.props.selected,
center: !Boolean(this.props.data.align),
},
this.props.data.align,
)}
>
{data.url ? (
<Body data={this.props.data} isEditMode={true} />
) : (
<Message>
<center>
<img src={videoBlockSVG} alt="" />
<div className="toolbar-inner">
<Input
onKeyDown={this.onKeyDownVariantMenuForm}
onChange={this.onChangeUrl}
placeholder={placeholder}
value={this.state.url}
// Prevents propagation to the Dropzone and the opening
// of the upload browser dialog
onClick={(e) => e.stopPropagation()}
/>
{this.state.url && (
<Button.Group>
<Button
basic
className="cancel"
onClick={(e) => {
e.stopPropagation();
this.setState({ url: '' });
}}
>
<Icon name={clearSVG} size="30px" />
</Button>
</Button.Group>
)}
const placeholder = useMemo(
() =>
data.placeholder ||
intl.formatMessage(messages.VideoBlockInputPlaceholder),
[intl, data],
);

return (
<div
className={cx(
'block video align',
{
selected: selected,
center: !Boolean(data.align),
},
data.align,
)}
>
{data.url ? (
<Body data={data} isEditMode={true} />
) : (
<Message>
<center>
<img src={videoBlockSVG} alt="" />
<div className="toolbar-inner">
<Input
onKeyDown={onKeyDownVariantMenuForm}
onChange={onChangeUrl}
placeholder={placeholder}
value={url}
// Prevents propagation to the Dropzone and the opening
// of the upload browser dialog
onClick={(e) => e.stopPropagation()}
/>
{url && (
<Button.Group>
<Button
basic
primary
className="cancel"
onClick={(e) => {
e.stopPropagation();
this.onSubmitUrl();
setUrl('');
}}
>
<Icon name={aheadSVG} size="30px" />
<Icon name={clearSVG} size="30px" />
</Button>
</Button.Group>
</div>
</center>
</Message>
)}
<SidebarPortal selected={this.props.selected}>
<VideoSidebar {...this.props} resetSubmitUrl={this.resetSubmitUrl} />
</SidebarPortal>
</div>
);
}
}
)}
<Button.Group>
<Button
basic
primary
onClick={(e) => {
e.stopPropagation();
onSubmitUrl();
}}
>
<Icon name={aheadSVG} size="30px" />
</Button>
</Button.Group>
</div>
</center>
</Message>
)}
<SidebarPortal selected={selected}>
<VideoSidebar {...props} resetSubmitUrl={resetSubmitUrl} />
</SidebarPortal>
</div>
);
};

Edit.propTypes = {
selected: PropTypes.bool.isRequired,
block: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
data: PropTypes.objectOf(PropTypes.any).isRequired,
onChangeBlock: PropTypes.func.isRequired,
onSelectBlock: PropTypes.func.isRequired,
onDeleteBlock: PropTypes.func.isRequired,
onFocusPreviousBlock: PropTypes.func.isRequired,
onFocusNextBlock: PropTypes.func.isRequired,
handleKeyDown: PropTypes.func.isRequired,
};

export default compose(injectIntl, withBlockExtensions)(Edit);
export default withBlockExtensions(Edit);
57 changes: 57 additions & 0 deletions packages/volto/src/components/manage/Blocks/Video/Edit.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { injectIntl } from 'react-intl';
import React from 'react';
import VideoComponent from './Edit';
import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';

const IntlVideoComponent = injectIntl(VideoComponent);

function StoryComponent(args) {
return (
<Wrapper
customStore={{
intl: {
locale: 'en',
messages: {},
},
}}
>
<div id="toolbar" style={{ display: 'none' }} />
<IntlVideoComponent
data={{
...args,
'@type': 'video',
}}
selected={false}
block="1234"
onChangeBlock={() => {}}
onSelectBlock={() => {}}
onDeleteBlock={() => {}}
onFocusPreviousBlock={() => {}}
onFocusNextBlock={() => {}}
handleKeyDown={() => {}}
index={1}
/>
</Wrapper>
);
}

export const Video = StoryComponent.bind({});

Video.args = {
url: 'https://www.youtube.com/watch?v=ayjNbKju-8s',
};

export default {
title: 'Public components/Video/Video',
component: Video,
decorators: [
(Story) => (
<div className="ui segment form attached" style={{ width: '600px' }}>
<Story />
</div>
),
],
argTypes: {
// controlled value prop
},
};

0 comments on commit 1710f8b

Please sign in to comment.