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);
}}
/>
)}
/>
);
}