| | |
| | | //edit.js |
| | | import { __ } from '@wordpress/i18n'; |
| | | import { |
| | | useBlockProps, |
| | | InspectorControls, |
| | | MediaUpload, |
| | | MediaUploadCheck, |
| | | InnerBlocks |
| | | InnerBlocks, |
| | | useInnerBlocksProps |
| | | } from '@wordpress/block-editor'; |
| | | import { |
| | | PanelBody, |
| | | Button, |
| | | ToggleControl, |
| | | BaseControl |
| | | BaseControl, |
| | | RangeControl, |
| | | SelectControl |
| | | } from '@wordpress/components'; |
| | | import './editor.scss'; |
| | | |
| | | const ALLOWED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg', 'video/ogv']; |
| | | const ALLOWED_VIDEO_TYPES = ['video']; |
| | | |
| | | const INNER_BLOCKS_TEMPLATE = [ |
| | | ['core/heading', { |
| | | level: 1, |
| | | placeholder: 'Add heading...', |
| | | textAlign: 'center' |
| | | }], |
| | | ['core/paragraph', { |
| | | placeholder: 'Add description...', |
| | | align: 'center' |
| | | }], |
| | | ['core/buttons', { |
| | | layout: { type: 'flex', justifyContent: 'center' } |
| | | }] |
| | | ]; |
| | | |
| | | |
| | | |
| | | export default function Edit({ attributes, setAttributes }) { |
| | | const { |
| | | posterId, |
| | | posterUrl, |
| | | videoSources, |
| | | mobileSources, |
| | | fadeEffect |
| | | fadeEffect, |
| | | overlayOpacity, |
| | | contentAlignment, |
| | | minHeight |
| | | } = attributes; |
| | | |
| | | const blockProps = useBlockProps({ |
| | | className: 'video-cover-editor' |
| | | className: 'video-cover-editor', |
| | | style: { |
| | | minHeight: minHeight ? `${minHeight}px` : undefined |
| | | } |
| | | }); |
| | | |
| | | const innerBlocksProps = useInnerBlocksProps( |
| | | { className: 'video-cover-content' }, |
| | | { |
| | | template: INNER_BLOCKS_TEMPLATE, |
| | | templateLock: false |
| | | } |
| | | ); |
| | | |
| | | const onSelectPoster = (media) => { |
| | | setAttributes({ |
| | | posterId: media.id, |
| | |
| | | }); |
| | | }; |
| | | |
| | | const onSelectVideo = (media, isMobile = false) => { |
| | | const newSource = { |
| | | id: media.id, |
| | | url: media.url, |
| | | mime: media.mime |
| | | }; |
| | | const onSelectVideos = (mediaItems) => { |
| | | // multiple=true returns an array |
| | | const items = Array.isArray(mediaItems) ? mediaItems : [mediaItems]; |
| | | const newSources = items |
| | | .filter(media => !videoSources.some(s => s.id === media.id)) |
| | | .map(media => ({ |
| | | id: media.id, |
| | | url: media.url, |
| | | mime: media.mime |
| | | })); |
| | | |
| | | if (isMobile) { |
| | | // Check if this format already exists in mobile sources |
| | | const exists = mobileSources.some(s => s.mime === media.mime); |
| | | if (!exists) { |
| | | setAttributes({ |
| | | mobileSources: [...mobileSources, newSource] |
| | | }); |
| | | } |
| | | } else { |
| | | // Check if this format already exists in desktop sources |
| | | const exists = videoSources.some(s => s.mime === media.mime); |
| | | if (!exists) { |
| | | setAttributes({ |
| | | videoSources: [...videoSources, newSource] |
| | | }); |
| | | } |
| | | if (newSources.length) { |
| | | setAttributes({ |
| | | videoSources: [...videoSources, ...newSources] |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const removeVideoSource = (index, isMobile = false) => { |
| | | if (isMobile) { |
| | | const updated = [...mobileSources]; |
| | | updated.splice(index, 1); |
| | | setAttributes({ mobileSources: updated }); |
| | | } else { |
| | | const updated = [...videoSources]; |
| | | updated.splice(index, 1); |
| | | setAttributes({ videoSources: updated }); |
| | | } |
| | | const removeVideoSource = (index) => { |
| | | const updated = [...videoSources]; |
| | | updated.splice(index, 1); |
| | | setAttributes({ videoSources: updated }); |
| | | }; |
| | | |
| | | const renderVideoSourceList = (sources, isMobile = false) => { |
| | |
| | | </BaseControl> |
| | | |
| | | <BaseControl |
| | | label={__('Desktop Video Sources', 'jvb')} |
| | | help={__('Add multiple formats for better browser support', 'jvb')} |
| | | label={__('Video Sources', 'jvb')} |
| | | help={__('Add multiple formats for better browser support (mp4, webm, etc.)', 'jvb')} |
| | | > |
| | | {renderVideoSourceList(videoSources, false)} |
| | | {videoSources.length > 0 && ( |
| | | <ul className="video-source-list"> |
| | | {videoSources.map((source, index) => ( |
| | | <li key={index} className="video-source-item"> |
| | | <span className="video-source-mime">{source.mime}</span> |
| | | <Button |
| | | isDestructive |
| | | isSmall |
| | | onClick={() => removeVideoSource(index)} |
| | | > |
| | | {__('Remove', 'jvb')} |
| | | </Button> |
| | | </li> |
| | | ))} |
| | | </ul> |
| | | )} |
| | | <MediaUploadCheck> |
| | | <MediaUpload |
| | | onSelect={(media) => onSelectVideo(media, false)} |
| | | multiple={true} |
| | | onSelect={onSelectVideos} |
| | | allowedTypes={ALLOWED_VIDEO_TYPES} |
| | | render={({ open }) => ( |
| | | <Button |
| | | onClick={open} |
| | | variant="secondary" |
| | | > |
| | | {__('Add Desktop Video', 'jvb')} |
| | | </Button> |
| | | )} |
| | | /> |
| | | </MediaUploadCheck> |
| | | </BaseControl> |
| | | |
| | | <BaseControl |
| | | label={__('Mobile Video Sources (Optional)', 'jvb')} |
| | | help={__('Smaller videos for mobile devices', 'jvb')} |
| | | > |
| | | {renderVideoSourceList(mobileSources, true)} |
| | | <MediaUploadCheck> |
| | | <MediaUpload |
| | | onSelect={(media) => onSelectVideo(media, true)} |
| | | allowedTypes={ALLOWED_VIDEO_TYPES} |
| | | render={({ open }) => ( |
| | | <Button |
| | | onClick={open} |
| | | variant="secondary" |
| | | > |
| | | {__('Add Mobile Video', 'jvb')} |
| | | <Button onClick={open} variant="secondary"> |
| | | {__('Add Video', 'jvb')} |
| | | </Button> |
| | | )} |
| | | /> |
| | |
| | | onChange={(value) => setAttributes({ fadeEffect: value })} |
| | | /> |
| | | </PanelBody> |
| | | |
| | | <PanelBody title={__('Overlay Settings', 'jvb')} initialOpen={true}> |
| | | <RangeControl |
| | | label={__('Overlay Opacity', 'jvb')} |
| | | help={__('Darken video for better text readability', 'jvb')} |
| | | value={overlayOpacity} |
| | | onChange={(value) => setAttributes({ overlayOpacity: value })} |
| | | min={0} |
| | | max={100} |
| | | step={5} |
| | | /> |
| | | |
| | | <SelectControl |
| | | label={__('Content Alignment', 'jvb')} |
| | | value={contentAlignment} |
| | | options={[ |
| | | { label: __('Top Left', 'jvb'), value: 'top-left' }, |
| | | { label: __('Top Center', 'jvb'), value: 'top-center' }, |
| | | { label: __('Top Right', 'jvb'), value: 'top-right' }, |
| | | { label: __('Center Left', 'jvb'), value: 'center-left' }, |
| | | { label: __('Center', 'jvb'), value: 'center' }, |
| | | { label: __('Center Right', 'jvb'), value: 'center-right' }, |
| | | { label: __('Bottom Left', 'jvb'), value: 'bottom-left' }, |
| | | { label: __('Bottom Center', 'jvb'), value: 'bottom-center' }, |
| | | { label: __('Bottom Right', 'jvb'), value: 'bottom-right' } |
| | | ]} |
| | | onChange={(value) => setAttributes({ contentAlignment: value })} |
| | | /> |
| | | |
| | | <RangeControl |
| | | label={__('Minimum Height', 'jvb')} |
| | | help={__('Minimum height in pixels (leave 0 for auto)', 'jvb')} |
| | | value={minHeight} |
| | | onChange={(value) => setAttributes({ minHeight: value })} |
| | | min={0} |
| | | max={1000} |
| | | step={50} |
| | | /> |
| | | </PanelBody> |
| | | </InspectorControls> |
| | | |
| | | <div {...blockProps}> |
| | | {posterUrl ? ( |
| | | {posterUrl || videoSources.length > 0 ? ( |
| | | <div className="video-cover-preview"> |
| | | <img src={posterUrl} alt={__('Video poster', 'jvb')} /> |
| | | <div className="video-overlay"> |
| | | {posterUrl && ( |
| | | <> |
| | | <img src={posterUrl} alt={__('Video poster', 'jvb')} /> |
| | | {overlayOpacity > 0 && ( |
| | | <div |
| | | className="video-overlay-preview" |
| | | style={{ opacity: overlayOpacity / 100 }} |
| | | /> |
| | | )} |
| | | </> |
| | | )} |
| | | <div className={`video-cover-content-preview align-${contentAlignment}`}> |
| | | <div {...innerBlocksProps} /> |
| | | </div> |
| | | <div className="video-info"> |
| | | <p> |
| | | {videoSources.length} {__('desktop source(s)', 'jvb')} |
| | | {mobileSources.length > 0 && |
| | | `, ${mobileSources.length} ${__('mobile source(s)', 'jvb')}` |
| | | } |
| | | </p> |
| | | </div> |
| | | </div> |
| | |
| | | </div> |
| | | )} |
| | | </div> |
| | | <div className="inner-blocks"> |
| | | <InnerBlocks /> |
| | | </div> |
| | | </> |
| | | ); |
| | | } |