Source: layout-content-page-editor-web/src/main/resources/META-INF/resources/js/components/sidebar/page_structure/SidebarPageStructurePanel.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 Component from 'metal-component';
import Soy from 'metal-soy';
import {Config} from 'metal-state';

import '../fragments/FragmentsEditorSidebarCard.es';
import {removeFragmentEntryLinkAction} from '../../../actions/removeFragmentEntryLinks.es';
import {removeRowAction} from '../../../actions/removeRow.es';
import {getConnectedComponent} from '../../../store/ConnectedComponent.es';
import {
	getItemPath,
	getRowFragmentEntryLinkIds
} from '../../../utils/FragmentsEditorGetUtils.es';
import {removeItem, setIn} from '../../../utils/FragmentsEditorUpdateUtils.es';
import {
	EDITABLE_FRAGMENT_ENTRY_PROCESSOR,
	FRAGMENTS_EDITOR_ITEM_TYPES,
	FRAGMENTS_EDITOR_ROW_TYPES,
	BACKGROUND_IMAGE_FRAGMENT_ENTRY_PROCESSOR
} from '../../../utils/constants';
import templates from './SidebarPageStructurePanel.soy';

/**
 * SidebarPageStructurePanel
 */
class SidebarPageStructurePanel extends Component {
	/**
	 * @param {Object} state
	 * @private
	 * @return {Object}
	 * @review
	 * @static
	 */
	static _addStructureToState(state) {
		return setIn(
			state,
			['structure'],
			SidebarPageStructurePanel._getTreeNode(state, {
				children: state.layoutData.structure.map(row =>
					SidebarPageStructurePanel._getRowTree(state, row)
				),
				expanded: true,
				key: 'root',
				label: Liferay.Language.get('page')
			})
		);
	}

	/**
	 * @param {object} state
	 * @param {object} column
	 * @return {Object}
	 * @review
	 * @static
	 */
	static _getColumnTree(state, column) {
		return SidebarPageStructurePanel._getTreeNode(state, {
			children: column.fragmentEntryLinkIds
				.map(
					fragmentEntryLinkId =>
						state.fragmentEntryLinks[fragmentEntryLinkId]
				)
				.filter(fragmentEntryLink => fragmentEntryLink)
				.map(fragmentEntryLink =>
					SidebarPageStructurePanel._getFragmentEntryLinkTree(
						state,
						fragmentEntryLink
					)
				),
			key: `${FRAGMENTS_EDITOR_ITEM_TYPES.column}-${column.columnId}`,
			label: Liferay.Language.get('column')
		});
	}

	/**
	 * @param {object} state
	 * @param {object} fragmentEntryLink
	 * @return {Object}
	 * @review
	 * @static
	 */
	static _getFragmentEntryLinkTree(state, fragmentEntryLink) {
		return SidebarPageStructurePanel._getTreeNode(state, {
			children: [
				...Object.keys(
					fragmentEntryLink.editableValues[
						EDITABLE_FRAGMENT_ENTRY_PROCESSOR
					] || {}
				).map(editableValueKey =>
					SidebarPageStructurePanel._getTreeNode(state, {
						elementId: `${fragmentEntryLink.fragmentEntryLinkId}-${editableValueKey}`,
						elementType: FRAGMENTS_EDITOR_ITEM_TYPES.editable,
						key: `${FRAGMENTS_EDITOR_ITEM_TYPES.editable}-${fragmentEntryLink.fragmentEntryLinkId}-${editableValueKey}`,
						label: editableValueKey,
						parentId: fragmentEntryLink.fragmentEntryLinkId
					})
				),
				...Object.keys(
					fragmentEntryLink.editableValues[
						BACKGROUND_IMAGE_FRAGMENT_ENTRY_PROCESSOR
					] || {}
				).map(editableValueKey =>
					SidebarPageStructurePanel._getTreeNode(state, {
						elementId: `${fragmentEntryLink.fragmentEntryLinkId}-${editableValueKey}`,
						elementType:
							FRAGMENTS_EDITOR_ITEM_TYPES.backgroundImageEditable,
						key: `${FRAGMENTS_EDITOR_ITEM_TYPES.backgroundImageEditable}-${fragmentEntryLink.fragmentEntryLinkId}-${editableValueKey}`,
						label: editableValueKey,
						parentId: fragmentEntryLink.fragmentEntryLinkId
					})
				)
			],
			elementId: fragmentEntryLink.fragmentEntryLinkId,
			elementType: FRAGMENTS_EDITOR_ITEM_TYPES.fragment,
			key: `${FRAGMENTS_EDITOR_ITEM_TYPES.fragment}-${fragmentEntryLink.fragmentEntryLinkId}`,
			label: fragmentEntryLink.name,
			removable: true
		});
	}

	/**
	 * @param {object} state
	 * @param {object} row
	 * @return {Object}
	 * @review
	 * @static
	 */
	static _getRowTree(state, row) {
		let treeNode;

		if (row.type === FRAGMENTS_EDITOR_ROW_TYPES.sectionRow) {
			const [fragmentEntryLinkId] = getRowFragmentEntryLinkIds(row);

			const fragmentEntryLink =
				state.fragmentEntryLinks[fragmentEntryLinkId];

			if (fragmentEntryLink) {
				treeNode = SidebarPageStructurePanel._getFragmentEntryLinkTree(
					state,
					fragmentEntryLink
				);
			}
		}

		if (!treeNode) {
			treeNode = SidebarPageStructurePanel._getTreeNode(state, {
				children: row.columns.map(column =>
					SidebarPageStructurePanel._getColumnTree(state, column)
				),
				elementId: row.rowId,
				elementType: FRAGMENTS_EDITOR_ITEM_TYPES.row,
				key: `${FRAGMENTS_EDITOR_ITEM_TYPES.row}-${row.rowId}`,
				label: Liferay.Language.get('section'),
				removable: true
			});
		}

		return treeNode;
	}

	/**
	 * @param {object} state
	 * @param {object} data
	 * @param {string} data.key
	 * @param {string} data.label
	 * @param {object[]} [data.children=[]]
	 * @param {string} [data.elementId='']
	 * @param {string} [data.elementType='']
	 * @param {boolean} [data.expanded=false]
	 * @param {boolean} [data.removable=false]
	 * @private
	 * @return {object}
	 * @review
	 * @static
	 */
	static _getTreeNode(state, data) {
		return {
			active:
				state.activeItemId === data.elementId &&
				state.activeItemType === data.elementType &&
				state.selectedItems.some(
					selectedItem =>
						selectedItem.itemId === data.elementId &&
						selectedItem.itemType === data.elementType
				),
			children: data.children || [],
			elementId: data.elementId || '',
			elementType: data.elementType || '',
			expanded:
				data.expanded || state._expandedNodes.indexOf(data.key) !== -1,
			hovered:
				state.hoveredItemId === data.elementId &&
				state.hoveredItemType === data.elementType,
			key: data.key,
			label: data.label,
			parentId: data.parentId,
			removable: data.removable || false,
			selected: state.selectedItems.some(
				selectedItem =>
					selectedItem.itemId === data.elementId &&
					selectedItem.itemType === data.elementType
			)
		};
	}

	/**
	 * @inheritDoc
	 * @private
	 * @review
	 */
	prepareStateForRender(state) {
		return SidebarPageStructurePanel._addStructureToState(state);
	}

	/**
	 * @private
	 * @review
	 */
	syncActiveItemId() {
		if (this.activeItemId && this.activeItemType) {
			getItemPath(
				this.activeItemId,
				this.activeItemType,
				this.layoutData.structure
			)
				.filter(
					activeItem =>
						this.activeItemId !== activeItem.itemId &&
						this.activeItemType !== activeItem.itemType
				)
				.forEach(activeItem => {
					const key = `${activeItem.itemType}-${activeItem.itemId}`;

					if (this._expandedNodes.indexOf(key) === -1) {
						this._expandedNodes = [...this._expandedNodes, key];
					}
				});
		}
	}

	/**
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleElementClick(event) {
		const {elementId, elementType, nodeKey} = event.delegateTarget.dataset;

		if (nodeKey) {
			const nodeKeyIndex = this._expandedNodes.indexOf(nodeKey);

			if (nodeKeyIndex === -1) {
				this._expandedNodes.push(nodeKey);
			} else {
				this._expandedNodes.splice(nodeKeyIndex, 1);
			}

			this._expandedNodes = this._expandedNodes;
		}

		if (elementId && elementType) {
			const element = document.querySelector(
				`.fragment-entry-link-list [data-fragments-editor-item-id="${elementId}"][data-fragments-editor-item-type="${elementType}"]`
			);

			if (element) {
				element.scrollIntoView({behavior: 'smooth', block: 'center'});
			}
		}
	}

	/**
	 * Callback executed when the element remove button is clicked.
	 * @param {object} event
	 * @private
	 * @review
	 */
	_handleElementRemoveButtonClick(event) {
		const itemId = event.delegateTarget.dataset.elementId;
		const itemType = event.delegateTarget.dataset.elementType;

		let removeItemAction = null;

		if (itemType === FRAGMENTS_EDITOR_ITEM_TYPES.row) {
			removeItemAction = removeRowAction(itemId);
		} else if (itemType === FRAGMENTS_EDITOR_ITEM_TYPES.fragment) {
			removeItemAction = removeFragmentEntryLinkAction(itemId);
		}

		if (removeItemAction) {
			removeItem(this.store, removeItemAction);
		}
	}
}

/**
 * State definition.
 * @review
 * @static
 * @type {!Object}
 */
SidebarPageStructurePanel.STATE = {
	/**
	 * List of expanded nodes.
	 * @default ['root']
	 * @instance
	 * @memberOf SidebarPageStructurePanel
	 * @review
	 * @type {string[]}
	 */
	_expandedNodes: Config.arrayOf(Config.string())
		.internal()
		.value(['root'])
};

const ConnectedSidebarPageStructurePanel = getConnectedComponent(
	SidebarPageStructurePanel,
	[
		'activeItemId',
		'activeItemType',
		'fragmentEntryLinks',
		'hoveredItemId',
		'hoveredItemType',
		'layoutData',
		'selectedItems',
		'spritemap'
	]
);

Soy.register(ConnectedSidebarPageStructurePanel, templates);

export {ConnectedSidebarPageStructurePanel, SidebarPageStructurePanel};
export default ConnectedSidebarPageStructurePanel;