Skip to content
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
85 changes: 85 additions & 0 deletions assets/before-after/frontblocks-before-after-frontend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
( function () {
'use strict';

function initBeforeAfterBlock( block ) {
var initialPosition = parseFloat( block.dataset.initialPosition ) || 50;
var beforeEl = block.querySelector( '.frbl-before-after__before' );
var handle = block.querySelector( '.frbl-before-after__handle' );

if ( ! beforeEl || ! handle ) {
return;
}

var isDragging = false;

function clamp( value, min, max ) {
return Math.max( min, Math.min( max, value ) );
}

function setPosition( pos ) {
pos = clamp( pos, 0, 100 );
beforeEl.style.clipPath = 'inset(0 ' + ( 100 - pos ) + '% 0 0)';
handle.style.left = pos + '%';
handle.setAttribute( 'aria-valuenow', Math.round( pos ) );
}

function getPositionFromEvent( e ) {
var rect = block.getBoundingClientRect();
var clientX = e.touches && e.touches.length ? e.touches[ 0 ].clientX : e.clientX;
return ( ( clientX - rect.left ) / rect.width ) * 100;
}

setPosition( initialPosition );

handle.addEventListener( 'mousedown', function ( e ) {
isDragging = true;
e.preventDefault();
} );

handle.addEventListener( 'touchstart', function () {
isDragging = true;
}, { passive: true } );

document.addEventListener( 'mousemove', function ( e ) {
if ( ! isDragging ) { return; }
setPosition( getPositionFromEvent( e ) );
} );

document.addEventListener( 'touchmove', function ( e ) {
if ( ! isDragging ) { return; }
setPosition( getPositionFromEvent( e ) );
}, { passive: true } );

document.addEventListener( 'mouseup', function () {
isDragging = false;
} );

document.addEventListener( 'touchend', function () {
isDragging = false;
} );

handle.setAttribute( 'tabindex', '0' );

handle.addEventListener( 'keydown', function ( e ) {
var current = parseFloat( handle.style.left ) || initialPosition;
if ( e.key === 'ArrowLeft' ) {
setPosition( current - 1 );
e.preventDefault();
} else if ( e.key === 'ArrowRight' ) {
setPosition( current + 1 );
e.preventDefault();
}
} );
}

function init() {
var blocks = document.querySelectorAll( '.frbl-before-after:not(.frbl-before-after--editor)' );
blocks.forEach( initBeforeAfterBlock );
}

if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', init );
} else {
init();
}
} )();
275 changes: 275 additions & 0 deletions assets/before-after/frontblocks-before-after-option.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
const { registerBlockType } = wp.blocks;
const { Fragment } = wp.element;
const { InspectorControls, MediaUpload, MediaUploadCheck, useBlockProps } = wp.blockEditor;
const { PanelBody, RangeControl, Button, Placeholder, TextControl, ToggleControl } = wp.components;
const { __ } = wp.i18n;

/**
* Edit component for Before After block.
*/
function BeforeAfterEdit( props ) {
const { attributes, setAttributes } = props;
const {
beforeImageId,
beforeImageUrl,
afterImageId,
afterImageUrl,
beforeLabel,
afterLabel,
initialPosition,
blockHeight,
fixedHeight,
} = attributes;

const blockProps = useBlockProps();

const hasImages = beforeImageUrl && afterImageUrl;

return (
<Fragment>
<InspectorControls>
<PanelBody title={ __( 'Before Image', 'frontblocks' ) } initialOpen={ true }>
<MediaUploadCheck>
<MediaUpload
onSelect={ ( media ) =>
setAttributes( {
beforeImageId: media.id,
beforeImageUrl: media.url,
} )
}
allowedTypes={ [ 'image' ] }
value={ beforeImageId }
render={ ( { open } ) => (
<div>
{ beforeImageUrl && (
<img
src={ beforeImageUrl }
alt=""
style={ { width: '100%', marginBottom: '8px', borderRadius: '2px' } }
/>
) }
<Button
onClick={ open }
variant={ beforeImageUrl ? 'secondary' : 'primary' }
style={ { width: '100%' } }
>
{ beforeImageUrl
? __( 'Replace Before Image', 'frontblocks' )
: __( 'Select Before Image', 'frontblocks' ) }
</Button>
{ beforeImageUrl && (
<Button
onClick={ () =>
setAttributes( {
beforeImageId: undefined,
beforeImageUrl: '',
} )
}
variant="link"
isDestructive
style={ { marginTop: '4px', display: 'block' } }
>
{ __( 'Remove', 'frontblocks' ) }
</Button>
) }
</div>
) }
/>
</MediaUploadCheck>
<TextControl
label={ __( 'Before Label', 'frontblocks' ) }
value={ beforeLabel }
onChange={ ( value ) => setAttributes( { beforeLabel: value } ) }
style={ { marginTop: '12px' } }
/>
</PanelBody>

<PanelBody title={ __( 'After Image', 'frontblocks' ) } initialOpen={ true }>
<MediaUploadCheck>
<MediaUpload
onSelect={ ( media ) =>
setAttributes( {
afterImageId: media.id,
afterImageUrl: media.url,
} )
}
allowedTypes={ [ 'image' ] }
value={ afterImageId }
render={ ( { open } ) => (
<div>
{ afterImageUrl && (
<img
src={ afterImageUrl }
alt=""
style={ { width: '100%', marginBottom: '8px', borderRadius: '2px' } }
/>
) }
<Button
onClick={ open }
variant={ afterImageUrl ? 'secondary' : 'primary' }
style={ { width: '100%' } }
>
{ afterImageUrl
? __( 'Replace After Image', 'frontblocks' )
: __( 'Select After Image', 'frontblocks' ) }
</Button>
{ afterImageUrl && (
<Button
onClick={ () =>
setAttributes( {
afterImageId: undefined,
afterImageUrl: '',
} )
}
variant="link"
isDestructive
style={ { marginTop: '4px', display: 'block' } }
>
{ __( 'Remove', 'frontblocks' ) }
</Button>
) }
</div>
) }
/>
</MediaUploadCheck>
<TextControl
label={ __( 'After Label', 'frontblocks' ) }
value={ afterLabel }
onChange={ ( value ) => setAttributes( { afterLabel: value } ) }
style={ { marginTop: '12px' } }
/>
</PanelBody>

<PanelBody title={ __( 'Slider Settings', 'frontblocks' ) } initialOpen={ false }>
<RangeControl
label={ __( 'Initial Handle Position (%)', 'frontblocks' ) }
value={ initialPosition }
onChange={ ( value ) => setAttributes( { initialPosition: value } ) }
min={ 0 }
max={ 100 }
/>
<ToggleControl
label={ __( 'Fixed Height', 'frontblocks' ) }
checked={ fixedHeight }
onChange={ ( value ) => setAttributes( { fixedHeight: value } ) }
/>
{ fixedHeight && (
<RangeControl
label={ __( 'Height (px)', 'frontblocks' ) }
value={ blockHeight }
onChange={ ( value ) => setAttributes( { blockHeight: value } ) }
min={ 100 }
max={ 1000 }
step={ 10 }
/>
) }
</PanelBody>
</InspectorControls>

<div { ...blockProps }>
{ ! hasImages ? (
<Placeholder
icon="image-flip-horizontal"
label={ __( 'Before / After', 'frontblocks' ) }
instructions={ __(
'Select a "before" image and an "after" image from the sidebar.',
'frontblocks'
) }
/>
) : (
<div
className={ 'frbl-before-after frbl-before-after--editor' + ( fixedHeight ? ' frbl-before-after--fixed-height' : '' ) }
data-initial-position={ initialPosition }
style={ fixedHeight && blockHeight ? { height: blockHeight + 'px' } : {} }
>
<div className="frbl-before-after__after">
<img src={ afterImageUrl } alt="" />
{ afterLabel && (
<span className="frbl-before-after__label frbl-before-after__label--after">
{ afterLabel }
</span>
) }
</div>
<div
className="frbl-before-after__before"
style={ { clipPath: `inset(0 ${ 100 - initialPosition }% 0 0)` } }
>
<img src={ beforeImageUrl } alt="" />
{ beforeLabel && (
<span className="frbl-before-after__label frbl-before-after__label--before">
{ beforeLabel }
</span>
) }
</div>
<div
className="frbl-before-after__handle"
style={ { left: `${ initialPosition }%` } }
>
<span className="frbl-before-after__handle-line"></span>
<span className="frbl-before-after__handle-thumb">
<svg viewBox="0 0 20 20" width="18" height="18" fill="currentColor" aria-hidden="true">
<path d="M7 4l-4 6 4 6M13 4l4 6-4 6" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span className="frbl-before-after__handle-line"></span>
</div>
</div>
) }
</div>
</Fragment>
);
}

// Register the block.
registerBlockType( 'frontblocks/before-after', {
title: __( 'Before After', 'frontblocks' ),
description: __( 'Compare two images with a draggable before/after slider.', 'frontblocks' ),
category: 'media',
icon: 'image-flip-horizontal',
keywords: [
__( 'before', 'frontblocks' ),
__( 'after', 'frontblocks' ),
__( 'comparison', 'frontblocks' ),
__( 'slider', 'frontblocks' ),
],
attributes: {
beforeImageId: {
type: 'integer',
},
beforeImageUrl: {
type: 'string',
default: '',
},
afterImageId: {
type: 'integer',
},
afterImageUrl: {
type: 'string',
default: '',
},
beforeLabel: {
type: 'string',
default: 'Before',
},
afterLabel: {
type: 'string',
default: 'After',
},
initialPosition: {
type: 'number',
default: 50,
},
fixedHeight: {
type: 'boolean',
default: false,
},
blockHeight: {
type: 'number',
default: 400,
},
},
edit: BeforeAfterEdit,
save: function () {
return null; // Dynamic block, rendered server-side.
},
} );
Loading
Loading