import './style.scss'; import './extensions/dynamic-categories'; import './extensions/image-title-and-desription'; import { closestCenter, DndContext, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core'; import { arrayMove, rectSortingStrategy, SortableContext, useSortable, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import classnames from 'classnames/dedupe'; import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; import { Button, FocalPointPicker, Modal, TextControl, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { useState } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { __, _n, sprintf } from '@wordpress/i18n'; import ControlsRender from '../controls-render'; const { navigator, VPGutenbergVariables } = window; const ALLOWED_MEDIA_TYPES = ['image']; function getHumanFileSize(size) { const i = Math.floor(Math.log(size) / Math.log(1024)); return `${(size / 1024 ** i).toFixed(2) * 1} ${ ['B', 'KB', 'MB', 'GB', 'TB'][i] }`; } function prepareImage(img) { const imgData = { id: img.id, imgUrl: img.url, imgThumbnailUrl: img.url, }; // Prepare thumbnail for all images except GIF, since GIFs animated only in full size. if (!img.mime || img.mime !== 'image/gif') { if (img.sizes && img.sizes.large && img.sizes.large.url) { imgData.imgThumbnailUrl = img.sizes.large.url; } else if (img.sizes && img.sizes.medium && img.sizes.medium.url) { imgData.imgThumbnailUrl = img.sizes.medium.url; } else if ( img.sizes && img.sizes.thumbnail && img.sizes.thumbnail.url ) { imgData.imgThumbnailUrl = img.sizes.thumbnail.url; } } if (img.title) { imgData.title = img.title; } if (img.description) { imgData.description = img.description; } return imgData; } /** * Prepare selected images array using our format. * Use the current images set with already user data on images. * * @param {Array} images - new images set. * @param {Array} currentImages - current images set. * @return {Array} */ function prepareImages(images, currentImages) { const result = []; const currentImagesIds = currentImages && Object.keys(currentImages).length ? currentImages.map((img) => img.id) : []; if (images && images.length) { images.forEach((img) => { // We have to check for image URL, because when the image is removed from the // system, it should be removed from our block as well after re-save. if (img.url) { let currentImgData = false; if (currentImagesIds.length) { const currentId = currentImagesIds.indexOf(img.id); if (currentId > -1 && currentImages[currentId]) { currentImgData = currentImages[currentId]; } } const imgData = currentImgData || prepareImage(img); result.push(imgData); } }); } return result; } const SelectedImageData = function (props) { const { showFocalPoint, focalPoint, imgId, imgUrl, onChangeFocalPoint, onChangeImage, } = props; const [showMoreInfo, setShowMoreInfo] = useState(false); const [linkCopied, setLinkCopied] = useState(false); const { imageData } = useSelect( (select) => { if (!imgId) { return {}; } const { getMedia } = select('core'); const imgData = getMedia(imgId); if (!imgData) { return {}; } return { imageData: imgData, }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [showMoreInfo, imgId] ); return (
{showFocalPoint ? ( { onChangeFocalPoint(val); }} /> ) : null} { const imgData = prepareImage(image); onChangeImage(imgData); }} allowedTypes={ALLOWED_MEDIA_TYPES} render={({ open }) => ( )} />
{showMoreInfo ? ( <>
{__('File name:', 'visual-portfolio')} {' '} {imageData?.source_url.split('/').pop() || '-'}
{' '} {__('File type:', 'visual-portfolio')} {' '} {imageData?.mime_type || '-'}
{' '} {__('File size:', '@text_domain')} {' '} {imageData?.media_details?.filesize ? getHumanFileSize( imageData.media_details.filesize ) : '-'} {imageData?.media_details?.width ? ( <>
{' '} {__('Dimensions:', '@text_domain')} {' '} {imageData.media_details.width} by{' '} {imageData.media_details.height} pixels ) : null}
{linkCopied ? ( {__('Copied!', 'visual-portfolio')} ) : null}
{imageData?.link ? ( ) : null} ) : null}
); }; const SortableItem = function (props) { const { img, items, index, onChange, imageControls, controlName, focalPoint, clientId, isSetupWizard, } = props; const idx = index - 1; const { attributes, listeners, setNodeRef, transform, transition, isDragging, isSorting, } = useSortable({ id: props.id, }); const style = { transform: CSS.Translate.toString(transform), transition: isSorting ? transition : '', }; const [isOpen, setOpen] = useState(false); const openModal = () => setOpen(true); const closeModal = () => setOpen(false); return ( <>
{isOpen ? ( { if ( e?.relatedTarget?.classList?.contains('media-modal') ) { // Don't close modal if opened media modal. } else { closeModal(e); } }} >
{focalPoint && img.id ? ( { const newImages = [...items]; if (newImages[idx]) { newImages[idx] = { ...newImages[idx], focalPoint: val, }; onChange(newImages); } }} onChangeImage={(imgData) => { const newImages = [...items]; if (!newImages[idx]) { return; } if (imgData === false) { newImages.splice(idx, 1); onChange(newImages); closeModal(); } else { newImages[idx] = { ...newImages[idx], ...imgData, }; onChange(newImages); } }} /> ) : ( '' )}
{Object.keys(imageControls).map((name) => { const newCondition = []; // prepare name. const imgControlName = `${controlName}[${idx}].${name}`; // prepare conditions for the current item. if (imageControls[name].condition.length) { imageControls[name].condition.forEach( (data) => { const newData = { ...data }; if ( newData.control && /SELF/g.test(newData.control) ) { newData.control = newData.control.replace( /SELF/g, `${controlName}[${idx}]` ); } newCondition.push(newData); } ); } return applyFilters( 'vpf.editor.gallery-controls-render', { const newImages = [...items]; if (newImages[idx]) { newImages[idx] = { ...newImages[idx], [name]: val, }; onChange(newImages); } }} {...imageControls[name]} name={imgControlName} value={img[name]} condition={newCondition} clientId={clientId} isSetupWizard={isSetupWizard} />, imageControls[name], props, { name, fullName: imgControlName, index: idx, condition: newCondition, } ); })}
) : null} ); }; const SortableList = function (props) { const { items, onChange, onSortEnd, imageControls, controlName, attributes, focalPoint, isSetupWizard, } = props; const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }) ); // Automatically open images selector when first time select Images in Setup Wizard. const [isOpenedInSetupWizard, setOpenInSetupWizard] = useState( !isSetupWizard ); const [showingItems, setShowingItems] = useState(18); const sortableItems = []; if (items && items.length) { items.forEach((data, i) => { if (i < showingItems) { sortableItems.push({ id: i + 1, data, }); } }); } const editGalleryButton = ( { onChange(prepareImages(images, items)); }} allowedTypes={ALLOWED_MEDIA_TYPES} value={items && items.length ? items.map((img) => img.id) : false} render={({ open }) => { if (!isOpenedInSetupWizard && (!items || !items.length)) { setOpenInSetupWizard(true); open(); } return ( ); }} /> ); return (
{items && items.length && items.length > 9 ? editGalleryButton : null} { const { active, over } = event; if (active.id !== over.id) { onSortEnd(active.id - 1, over.id - 1); } }} > {sortableItems.map(({ data, id }) => ( ))} {items && items.length ? ( {sprintf( _n( 'Showing %1$s of %2$s Image', 'Showing %1$s of %2$s Images', items.length, 'visual-portfolio' ), showingItems > items.length ? items.length : showingItems, items.length )} {items.length > showingItems ? (
) : null}
) : null} {editGalleryButton}
); }; /** * Component Class * * @param props */ export default function GalleryControl(props) { const { imageControls, attributes, name: controlName, value, onChange, focalPoint, isSetupWizard, } = props; const filteredValue = value.filter((img) => img.id); return (
{ onChange(prepareImages(images)); }} allowedTypes={ALLOWED_MEDIA_TYPES} multiple="add" value={ filteredValue && Object.keys(filteredValue).length ? filteredValue.map((img) => img.id) : [] } render={() => ( { const newImages = arrayMove( filteredValue, oldIndex, newIndex ); onChange(newImages); }} /> )} />
); }