Source: layout-content-page-editor-web/src/main/resources/META-INF/resources/js/utils/FragmentsEditorGetUtils.es.js

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

import {
	FRAGMENTS_EDITOR_ITEM_BORDERS,
	FRAGMENTS_EDITOR_ITEM_TYPES,
	MAPPING_SOURCE_TYPE_IDS
} from '../utils/constants';

const ARROW_DOWN_KEYCODE = 40;

const ARROW_UP_KEYCODE = 38;

const MOVE_ITEM_DIRECTIONS = {
	DOWN: 1,
	UP: -1
};

/**
 * @param {object} objectToClone
 * @returns {object}
 */
function deepClone(objectToClone) {
	let cloned = objectToClone;

	if (typeof cloned == 'object' && cloned !== null) {
		if (Array.isArray(cloned)) {
			cloned = objectToClone.map(arrayItem => deepClone(arrayItem));
		} else {
			cloned = {...cloned};

			Object.keys(cloned).forEach(clonedKey => {
				cloned[clonedKey] = deepClone(cloned[clonedKey]);
			});
		}
	}

	return cloned;
}

/**
 * Checks if the given editable is mapped
 * @param {object} editableValues
 * @private
 * @return {boolean}
 * @review
 */
function editableIsMapped(editableValues) {
	return Boolean(
		editableValues.mappedField ||
			editableIsMappedToAssetEntry(editableValues)
	);
}

/**
 * Checks if the given editable is mapped to an asset entry
 * @param {object} editableValues
 * @private
 * @return {boolean}
 * @review
 */
function editableIsMappedToAssetEntry(editableValues) {
	return Boolean(
		editableValues.classNameId &&
			editableValues.classPK &&
			editableValues.fieldId
	);
}

/**
 * Checks if the given editable should be highlighted
 * @param {string} activeItemId
 * @param {string} activeItemType
 * @param {string} fragmentEntryLinkId
 * @param {object} structure
 * @private
 * @return {boolean}
 * @review
 */
function editableShouldBeHighlighted(
	activeItemId,
	activeItemType,
	fragmentEntryLinkId,
	structure
) {
	const parentFragmentIsInActiveItemPath = itemIsInPath(
		getItemPath(activeItemId, activeItemType, structure),
		fragmentEntryLinkId,
		FRAGMENTS_EDITOR_ITEM_TYPES.fragment
	);

	return parentFragmentIsInActiveItemPath;
}

/**
 * Returns the column with the given columnId
 * @param {Object} structure
 * @param {string} columnId
 * @return {Object}
 */
function getColumn(structure, columnId) {
	return structure
		.map(row => row.columns.find(_column => _column.columnId === columnId))
		.filter(column => column)
		.find(column => column);
}

/**
 * Returns the position in the structure of the given row
 * @param {object} structure
 * @param {number} targetRowId
 * @param {string} targetBorder
 * @return {number}
 */
function getDropRowPosition(structure, targetRowId, targetBorder) {
	let position = structure.length;

	const targetPosition = structure.findIndex(
		row => row.rowId === targetRowId
	);

	if (targetPosition > -1 && targetBorder) {
		if (targetBorder === FRAGMENTS_EDITOR_ITEM_BORDERS.top) {
			position = targetPosition;
		} else {
			position = targetPosition + 1;
		}
	}

	return position;
}

/**
 * Get HTML elements from itemId and itemType in page editor
 * @param {string} itemId
 * @param {string} itemType
 */
function getElements(itemId, itemType) {
	return document.querySelectorAll(
		`[data-fragments-editor-item-id="${itemId}"][data-fragments-editor-item-type="${itemType}"]`
	);
}

/**
 * Returns the column that contains the fragment
 * with the given fragmentEntryLinkId.
 *
 * @param {Array} structure
 * @param {string} fragmentEntryLinkId
 * @return {Object}
 */
function getFragmentColumn(structure, fragmentEntryLinkId) {
	return structure
		.map(row =>
			row.columns.find(_column =>
				_column.fragmentEntryLinkIds.find(
					fragmentId => fragmentId === fragmentEntryLinkId
				)
			)
		)
		.filter(column => column)
		.find(column => column);
}

/**
 * Returns the row index of a given fragmentEntryLinkId.
 * -1 if it is not present.
 *
 * @param {Array} structure
 * @param {string} fragmentEntryLinkId
 * @return {number}
 */
function getFragmentRowIndex(structure, fragmentEntryLinkId) {
	return structure.findIndex(row =>
		row.columns.find(column =>
			column.fragmentEntryLinkIds.find(
				_fragmentEntryLinkId =>
					_fragmentEntryLinkId === fragmentEntryLinkId
			)
		)
	);
}

/**
 * @param {number} keycode
 * @return {number|null}
 * @review
 */
function getItemMoveDirection(keycode) {
	let direction = null;

	if (keycode === ARROW_UP_KEYCODE) {
		direction = MOVE_ITEM_DIRECTIONS.UP;
	} else if (keycode === ARROW_DOWN_KEYCODE) {
		direction = MOVE_ITEM_DIRECTIONS.DOWN;
	}

	return direction;
}

/**
 * For a given itemId and itemType, returns an array
 * with the whole path of active elements.
 * @param {string|null} itemId
 * @param {string|null} itemType
 * @param {object[]} structure
 * @return {Array<{itemId: string, itemType}>}
 */
function getItemPath(itemId, itemType, structure) {
	let itemPath = [];

	if (itemId && itemType && structure.length) {
		itemPath = [
			{
				itemId,
				itemType
			}
		];

		if (
			itemType === FRAGMENTS_EDITOR_ITEM_TYPES.editable ||
			itemType === FRAGMENTS_EDITOR_ITEM_TYPES.backgroundImageEditable
		) {
			const [fragmentEntryLinkId] = itemId.split('-');

			if (fragmentEntryLinkId) {
				itemPath = [
					...itemPath,
					...getItemPath(
						fragmentEntryLinkId,
						FRAGMENTS_EDITOR_ITEM_TYPES.fragment,
						structure
					)
				];
			}
		} else if (itemType === FRAGMENTS_EDITOR_ITEM_TYPES.fragment) {
			const column = []
				.concat(...structure.map(row => row.columns))
				.find(
					_column =>
						_column.fragmentEntryLinkIds.indexOf(itemId) !== -1
				);

			if (column) {
				itemPath = [
					...itemPath,
					...getItemPath(
						column.columnId,
						FRAGMENTS_EDITOR_ITEM_TYPES.column,
						structure
					)
				];
			}
		} else if (itemType === FRAGMENTS_EDITOR_ITEM_TYPES.column) {
			const row = structure.find(row =>
				row.columns.find(column => column.columnId === itemId)
			);

			if (row) {
				itemPath = [
					...itemPath,
					...getItemPath(
						row.rowId,
						FRAGMENTS_EDITOR_ITEM_TYPES.row,
						structure
					)
				];
			}
		}
	}

	return itemPath;
}

/**
 * @param {string} subtypeLabel
 * @return {Array<{id: string, label: string}>} Source types
 * @private
 * @review
 */
function getMappingSourceTypes(subtypeLabel) {
	return [
		{
			id: MAPPING_SOURCE_TYPE_IDS.structure,
			label: Liferay.Util.sub(
				Liferay.Language.get('x-default'),
				subtypeLabel
			)
		},
		{
			id: MAPPING_SOURCE_TYPE_IDS.content,
			label: Liferay.Language.get('specific-content')
		}
	];
}

/**
 * Get the fragmentEntryLinkIds of the fragments inside the given row
 * @param {{columns: Array<{fragmentEntryLinkIds: Array<string>}>}} row
 * @return {string[]}
 * @review
 */
function getRowFragmentEntryLinkIds(row) {
	let fragmentEntryLinkIds = [];

	row.columns.forEach(column => {
		fragmentEntryLinkIds = fragmentEntryLinkIds.concat(
			column.fragmentEntryLinkIds
		);
	});

	return fragmentEntryLinkIds;
}

/**
 * Returns the index of the row with the given rowId
 * @param {Array} structure
 * @param {string} rowId
 * @return {number}
 */
function getRowIndex(structure, rowId) {
	return structure.findIndex(row => row.rowId === rowId);
}

/**
 * Get target item border from the direction the item is moving in
 * @param {number} direction
 * @return {string}
 * @review
 */
function getTargetBorder(direction) {
	let targetBorder = null;

	if (direction === MOVE_ITEM_DIRECTIONS.UP) {
		targetBorder = FRAGMENTS_EDITOR_ITEM_BORDERS.top;
	} else if (direction === MOVE_ITEM_DIRECTIONS.DOWN) {
		targetBorder = FRAGMENTS_EDITOR_ITEM_BORDERS.bottom;
	}

	return targetBorder;
}

/**
 * Get widget from the widgets tree by portletId
 * @param {!Array} widgets
 * @param {!string} portletId
 * @return {object}
 * @review
 */
function getWidget(widgets, portletId) {
	let widget = null;

	const widgetsLength = widgets.length;

	for (let i = 0; i < widgetsLength; i++) {
		const {categories = [], portlets = []} = widgets[i];
		const categoryPortlet = portlets.find(
			_portlet => _portlet.portletId === portletId
		);
		const subCategoryPortlet = getWidget(categories, portletId);

		if (categoryPortlet) {
			widget = categoryPortlet;
		}

		if (subCategoryPortlet) {
			widget = subCategoryPortlet;
		}
	}

	return widget;
}

/**
 * Get widget path from the widgets tree by portletId
 * @param {Array<{categories: Array, portlets: Array}>} widgets
 * @param {string} portletId
 * @param {string[]} [_path=['widgets']]
 * @return {object}
 * @review
 */
function getWidgetPath(widgets, portletId, _path = ['widgets']) {
	let widgetPath = null;

	for (
		let categoryIndex = 0;
		categoryIndex < widgets.length;
		categoryIndex += 1
	) {
		const {categories = [], portlets = []} = widgets[categoryIndex];

		const categoryPortletIndex = portlets.findIndex(
			_portlet => _portlet.portletId === portletId
		);

		const subCategoryPortletPath = getWidgetPath(categories, portletId, [
			..._path,
			categoryIndex.toString(),
			'categories'
		]);

		if (categoryPortletIndex !== -1) {
			widgetPath = [
				..._path,
				categoryIndex,
				'portlets',
				categoryPortletIndex
			];
		}

		if (subCategoryPortletPath) {
			widgetPath = subCategoryPortletPath;
		}
	}

	return widgetPath;
}

/**
 * @param {object} path
 * @param {string} itemId
 * @param {string} itemType
 * @return {boolean} Item is in path
 * @review
 */
function itemIsInPath(path, itemId, itemType) {
	return path.some(
		pathItem => pathItem.itemId === itemId && pathItem.itemType === itemType
	);
}

export {
	deepClone,
	editableIsMapped,
	editableIsMappedToAssetEntry,
	editableShouldBeHighlighted,
	getColumn,
	getDropRowPosition,
	getElements,
	getItemPath,
	getFragmentColumn,
	getFragmentRowIndex,
	getItemMoveDirection,
	getMappingSourceTypes,
	getRowFragmentEntryLinkIds,
	getRowIndex,
	getTargetBorder,
	getWidget,
	getWidgetPath,
	itemIsInPath
};