All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.js.miller_columns.MillerColumnsItem.js Maven / Gradle / Ivy

/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

import ClayButton, {ClayButtonWithIcon} from '@clayui/button';
import ClayDropDown, {ClayDropDownWithItems} from '@clayui/drop-down';
import {ClayCheckbox} from '@clayui/form';
import ClayIcon from '@clayui/icon';
import ClayLabel from '@clayui/label';
import ClayLayout from '@clayui/layout';
import ClayLink from '@clayui/link';
import ClayLoadingIndicator from '@clayui/loading-indicator';
import {PageTemplateModal} from '@liferay/layout-js-components-web';
import classNames from 'classnames';
import {useId} from 'frontend-js-components-web';
import {fetch, sub} from 'frontend-js-web';
import React, {useContext, useEffect, useMemo, useRef, useState} from 'react';
import {useDrag, useDrop} from 'react-dnd';
import {getEmptyImage} from 'react-dnd-html5-backend';

import ACTIONS from '../actions';
import {ACCEPTING_TYPES} from './constants/acceptingTypes';
import {DROP_POSITIONS} from './constants/dropPositions';
import {LayoutColumnsContext} from './contexts/LayoutColumnsContext';
import {useKeyboardMovement} from './hooks/useKeyboardMovement';
import {useKeyboardNavigation} from './hooks/useKeyboardNavigation';
import {isValidMovement} from './utils/isValidMovement';

const ITEM_HOVER_BORDER_LIMIT = 20;

const ITEM_HOVER_TIMEOUT = 500;

const ITEM_STATES_COLORS = {
	'conversion-draft': 'info',
	'draft': 'secondary',
	'pending': 'info',
};

const getDropPosition = (ref, monitor) => {
	if (!ref.current) {
		return;
	}

	const clientOffset = monitor.getClientOffset();
	const dropItemBoundingRect = ref.current.getBoundingClientRect();
	const hoverTopLimit = ITEM_HOVER_BORDER_LIMIT;
	const hoverBottomLimit =
		dropItemBoundingRect.height - ITEM_HOVER_BORDER_LIMIT;
	const hoverClientY = clientOffset.y - dropItemBoundingRect.top;

	let dropPosition = DROP_POSITIONS.middle;

	if (hoverClientY < hoverTopLimit) {
		dropPosition = DROP_POSITIONS.top;
	}
	else if (hoverClientY > hoverBottomLimit) {
		dropPosition = DROP_POSITIONS.bottom;
	}

	return dropPosition;
};

function addSeparators(items) {
	if (items.length < 2) {
		return items;
	}

	const separatedItems = [items[0]];

	for (let i = 1; i < items.length; i++) {
		const item = items[i];

		if (item.type === 'group' && item.separator) {
			separatedItems.push({type: 'divider'});
		}

		separatedItems.push(item);
	}

	return separatedItems.map((item) => {
		if (item.type === 'group') {
			return {
				...item,
				items: addSeparators(item.items),
			};
		}

		return item;
	});
}

function filterEmptyGroups(items) {
	return items
		.filter(
			(item) =>
				item.type !== 'group' ||
				(Array.isArray(item.items) && item.items.length)
		)
		.map((item) =>
			item.type === 'group'
				? {...item, items: filterEmptyGroups(item.items)}
				: item
		);
}

const noop = () => {};

const MillerColumnsItem = ({
	createPageTemplateURL,
	getPageTemplateCollectionsURL,
	getItemActionsURL,
	isLayoutSetPrototype,
	item,
	items,
	namespace,
	onItemDrop = noop,
	getItemChildren = noop,
	rtl,
}) => {
	const {
		active,
		bulkActions = [],
		checked,
		description,
		draggable,
		hasChild,
		hasDuplicatedFriendlyURL = false,
		hasGuestViewPermission,
		id: itemId,
		quickActions = [],
		selectable,
		states = [],
		target,
		title,
		url,
		viewUrl,
	} = item;

	const ref = useRef();
	const timeoutRef = useRef();

	const onClose = () => {
		setOpenModal(false);
	};

	const [openModal, setOpenModal] = useState(false);

	const [dropPosition, setDropPosition] = useState();

	const [layoutActionsActive, setLayoutActionsActive] = useState(false);

	const [dropdownActions, setDropdownActions] = useState([]);

	const loadPromiseRef = useRef();

	const [loadMessage, setLoadMessage] = useState('');

	const {layoutColumns, setLayoutColumns} = useContext(LayoutColumnsContext);

	function loadDropdownActions() {
		if (!loadPromiseRef.current) {
			let loadingMessageShown = false;
			let optionsLoaded = false;
			const url = new URL(getItemActionsURL);
			url.searchParams.append(`${namespace}plid`, itemId);

			setTimeout(() => {
				if (!optionsLoaded) {
					setLoadMessage(
						sub(Liferay.Language.get('loading-x-options'), title)
					);
					loadingMessageShown = true;
				}
			}, 500);

			loadPromiseRef.current = fetch(url, {
				method: 'GET',
			})
				.then((response) => response.json())
				.then(({actions}) => {
					optionsLoaded = true;
					if (loadingMessageShown) {
						setLoadMessage(
							sub(Liferay.Language.get('x-options-loaded'), title)
						);
					}

					const updateItem = (item) => {
						const newItem = {
							...item,
							onClick(event) {
								const action = item.data?.action;

								if (action === 'convertToPageTemplate') {
									setOpenModal(true);
								}
								else if (action) {
									event.preventDefault();

									ACTIONS[action]?.(item.data);
								}
							},
							symbolLeft: item.icon,
						};

						if (Array.isArray(item.items)) {
							newItem.items = item.items.map(updateItem);
						}

						return newItem;
					};

					const dropdownActions = actions.map((action) => {
						return {
							...action,
							items: action.items?.map(updateItem),
						};
					});

					setDropdownActions(
						addSeparators(filterEmptyGroups(dropdownActions))
					);
				});
		}
	}

	const layoutActions = useMemo(() => {
		return quickActions.filter(
			(action) => action.layoutAction && action.url
		);
	}, [quickActions]);

	const normalizedQuickActions = useMemo(() => {
		return quickActions.filter(
			(action) => action.quickAction && action.url
		);
	}, [quickActions]);

	const [{isDragging: isDragSource}, drag, previewRef] = useDrag({
		canDrag: () => !isKeyboardMovementEnabled,
		collect: (monitor) => ({
			isDragging: !!monitor.isDragging(),
		}),
		end: ({initialColumns}, monitor) => {
			if (!monitor.didDrop() && Liferay.FeatureFlags['LPD-35220']) {
				setLayoutColumns(initialColumns);
			}
		},
		isDragging: (monitor) => {
			const movedItems = monitor.getItem().items;

			return (
				(movedItems.some((item) => item.checked) && checked) ||
				movedItems.some((item) => item.id === itemId)
			);
		},
		item: {
			initialColumns: layoutColumns,
			items: checked
				? Array.from(items.values()).filter((item) => item.checked)
				: [items.get(itemId)],
			type: ACCEPTING_TYPES.ITEM,
		},
	});

	const [{isOver}, drop] = useDrop({
		accept: ACCEPTING_TYPES.ITEM,
		canDrop(source, monitor) {
			const dropPosition = getDropPosition(ref, monitor);

			return isValidMovement({
				dropPosition,
				sources: source.items,
				target: item,
			});
		},
		collect: (monitor) => ({
			isOver: !!monitor.isOver(),
		}),
		drop(source, monitor) {
			if (monitor.canDrop()) {
				onItemDrop(source.items, item, dropPosition);
			}
		},
		hover(source, monitor) {
			let dropPosition;

			if (isOver && monitor.canDrop()) {
				dropPosition = getDropPosition(ref, monitor);
			}

			setDropPosition(dropPosition);
		},
	});

	const {
		enableMovement,
		isEnabled: isKeyboardMovementEnabled,
		isSource: isKeyboardMovementSource,
		isTarget: isKeyboardMovementTarget,
		position: keyboardMovementPosition,
	} = useKeyboardMovement({
		element: ref.current,
		item,
		items,
	});

	const {isTarget: isNavigationTarget, onKeyDown} = useKeyboardNavigation({
		element: ref.current,
		getItemChildren,
		item,
		rtl,
	});

	const tabIndex =
		isNavigationTarget || !Liferay.FeatureFlags['LPD-35220'] ? 0 : -1;

	const targetPosition = dropPosition || keyboardMovementPosition;
	const isSource = isDragSource || isKeyboardMovementSource;
	const isTarget = isOver || isKeyboardMovementTarget;

	useEffect(() => {
		drag(drop(ref));
	}, [drag, drop]);

	useEffect(() => {
		previewRef(getEmptyImage(), {captureDraggingState: true});
	}, [previewRef]);

	useEffect(() => {
		if (
			!active &&
			isTarget &&
			targetPosition === DROP_POSITIONS.middle &&
			!timeoutRef.current
		) {
			timeoutRef.current = setTimeout(() => {
				if (isTarget) {
					getItemChildren(itemId);
				}
			}, ITEM_HOVER_TIMEOUT);
		}
		else if (
			!isTarget ||
			(targetPosition !== DROP_POSITIONS.middle && timeoutRef.current)
		) {
			clearTimeout(timeoutRef.current);
			timeoutRef.current = null;
		}
	}, [active, isTarget, itemId, getItemChildren, targetPosition]);

	const warningMessage = isLayoutSetPrototype
		? Liferay.Language.get(
				'there-is-a-page-with-the-same-friendly-url-in-a-site-using-this-site-template'
			)
		: Liferay.Language.get(
				'there-is-a-page-with-the-same-friendly-url-in-the-site-template'
			);

	const groupId = useId();

	const ariaProps = hasChild
		? {
				'aria-controls': `miller-columns-list-${itemId}`,
				'aria-expanded': active,
			}
		: {};

	return (
		
			
				{title}
			

			
				{draggable && (
					
						 {
								if (
									Liferay.FeatureFlags['LPD-35220'] &&
									event.detail === 0
								) {
									const sources = checked
										? Array.from(items.values()).filter(
												(item) => item.checked
											)
										: [item];

									enableMovement(sources);
								}
							}}
							onKeyDown={(event) => {
								if (
									![
										'ArrowDown',
										'ArrowLeft',
										'ArrowRight',
										'ArrowUp',
									].includes(event.key)
								) {
									event.stopPropagation();
								}
							}}
							tabIndex={tabIndex}
							title={sub(Liferay.Language.get('move-x'), [title])}
						>
							
						
					
				)}

				{selectable && (
					
						
					
				)}

				
					
{viewUrl ? ( { if (!hasGuestViewPermission) { return `${title}. ${Liferay.Language.get( 'restricted-page' )}`; } if ( Liferay.FeatureFlags['LPS-174417'] && hasDuplicatedFriendlyURL ) { return `${title}. ${warningMessage}`; } return title; })()} className={classNames({ 'text-truncate': !Liferay.FeatureFlags['LPD-35220'], })} href={viewUrl} tabIndex={tabIndex} target={target} > {title} ) : ( {title} )} {!hasGuestViewPermission && ( )} {Liferay.FeatureFlags['LPS-174417'] && hasDuplicatedFriendlyURL ? ( ) : null}
{description && (
{description} {states.map((state) => ( {state.label} ))}
)}
{!!layoutActions.length && ( event.stopPropagation()} renderMenuOnClick trigger={ } > {layoutActions.map((action) => ( {action.label} ))} )} {normalizedQuickActions.map((action) => ( ))} {!!getItemActionsURL && itemId !== '0' ? ( ) : ( '' ) } items={dropdownActions} onKeyDown={(event) => event.stopPropagation()} trigger={ } /> {loadMessage} {openModal && ( )} ) : null} {hasChild && ( )}
); }; export default MillerColumnsItem;




© 2015 - 2025 Weber Informatics LLC | Privacy Policy