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

import './components/fragment_entry_link/FragmentEntryLinkList.es';

import './components/sidebar/FragmentsEditorSidebar.es';

import './components/toolbar/FragmentsEditorToolbar.es';
import templates from './FragmentsEditor.soy';
import {
	CLEAR_ACTIVE_ITEM,
	CLEAR_HOVERED_ITEM,
	UPDATE_HOVERED_ITEM
} from './actions/actions.es';
import {updateActiveItemAction} from './actions/updateActiveItem.es';
import {INITIAL_STATE} from './store/state.es';
import {Store} from './store/store.es';
import {
	startListeningWidgetConfigurationChange,
	stopListeningWidgetConfigurationChange
} from './utils/FragmentsEditorDialogUtils';
import {getElements} from './utils/FragmentsEditorGetUtils.es';
import {FRAGMENTS_EDITOR_ITEM_TYPES} from './utils/constants';

/**
 * @type {string}
 */
const ITEM_CLASS = 'fragments-editor__item';

/**
 * @type {string}
 */
const HOVERED_ITEM_CLASS = `${ITEM_CLASS}--hovered`;

/**
 * DOM selector where the sidebar is rendered
 */
const SIDEBAR_SELECTOR = '.fragments-editor-sidebar';

/**
 * DOM selector where the fragmentEntryLinks are rendered
 */
const WRAPPER_SELECTOR = '.fragment-entry-link-list-wrapper';

/**
 * FragmentsEditor
 * @review
 */
class FragmentsEditor extends Component {
	/**
	 * @param {KeyboardEvent|MouseEvent} event
	 * @return {{targetItem: HTMLElement, targetItemId: string|null, targetItemType: string|null}}
	 * @private
	 * @review
	 */
	static _getTargetItemData(event) {
		let targetItem = event.target;
		let {targetItemId = null, targetItemType = null} =
			targetItem.dataset || {};

		if (!targetItemId || !targetItemType) {
			targetItem = dom.closest(
				event.target,
				'[data-fragments-editor-item-id]'
			);

			if (targetItem) {
				targetItemId = targetItem.dataset.fragmentsEditorItemId;
				targetItemType = targetItem.dataset.fragmentsEditorItemType;
			}
		}

		return {
			targetItem,
			targetItemId,
			targetItemType
		};
	}

	/**
	 * @inheritdoc
	 * @review
	 */
	created() {
		this._handleDocumentClick = this._handleDocumentClick.bind(this);
		this._handleDocumentKeyDown = this._handleDocumentKeyDown.bind(this);
		this._handleDocumentKeyUp = this._handleDocumentKeyUp.bind(this);
		this._handleDocumentMouseOver = this._handleDocumentMouseOver.bind(
			this
		);

		document.addEventListener('click', this._handleDocumentClick, true);
		document.addEventListener('keydown', this._handleDocumentKeyDown);
		document.addEventListener('keyup', this._handleDocumentKeyUp);
		document.addEventListener('mouseover', this._handleDocumentMouseOver);
	}

	/**
	 * @inheritdoc
	 * @review
	 */
	disposed() {
		document.removeEventListener('click', this._handleDocumentClick, true);
		document.removeEventListener('keydown', this._handleDocumentKeyDown);
		document.removeEventListener('keyup', this._handleDocumentKeyUp);
		document.removeEventListener(
			'mouseover',
			this._handleDocumentMouseOver
		);

		stopListeningWidgetConfigurationChange();
	}

	/**
	 * @inheritdoc
	 * @review
	 */
	syncStore() {
		if (this.store) {
			startListeningWidgetConfigurationChange(this.store);
		}
	}

	/**
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleDocumentClick(event) {
		this._updateActiveItem(event);
	}

	/**
	 * @param {KeyboardEvent} event
	 * @private
	 * @review
	 */
	_handleDocumentKeyDown(event) {
		this._shiftPressed = event.shiftKey;
	}

	/**
	 * @param {KeyboardEvent} event
	 * @private
	 * @review
	 */
	_handleDocumentKeyUp(event) {
		this._shiftPressed = event.shiftKey;

		if (event.key !== 'Shift') {
			this._updateActiveItem(event);
		}
	}

	/**
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_handleDocumentMouseOver(event) {
		if (this.store) {
			this._updateHoveredItem(event);
		}
	}

	/**
	 * @param {KeyboardEvent|MouseEvent} event
	 * @private
	 * @review
	 */
	_updateActiveItem(event) {
		const {
			targetItemId,
			targetItemType
		} = FragmentsEditor._getTargetItemData(event);

		if (targetItemId && targetItemType) {
			this.store.dispatch(
				updateActiveItemAction(targetItemId, targetItemType, {
					appendItem: this._shiftPressed
				})
			);
		} else if (
			(dom.closest(event.target, WRAPPER_SELECTOR) ||
				event.target === document.querySelector(WRAPPER_SELECTOR)) &&
			!dom.closest(event.target, SIDEBAR_SELECTOR)
		) {
			this.store.dispatch({
				type: CLEAR_ACTIVE_ITEM
			});
		}
	}

	/**
	 * Update hovered item
	 * @param {MouseEvent} event
	 * @private
	 * @review
	 */
	_updateHoveredItem(event) {
		const {
			targetItemId,
			targetItemType
		} = FragmentsEditor._getTargetItemData(event);

		let hoveredItemId = targetItemId;
		let hoveredItemType = targetItemType;

		document
			.querySelectorAll(`.${HOVERED_ITEM_CLASS}`)
			.forEach(hoveredItem => {
				hoveredItem.classList.remove(HOVERED_ITEM_CLASS);
			});

		const targetItems = getElements(targetItemId, targetItemType);

		if (targetItems.length === 0) {
			const targetItemIsEditable =
				targetItemType === FRAGMENTS_EDITOR_ITEM_TYPES.editable ||
				targetItemType ===
					FRAGMENTS_EDITOR_ITEM_TYPES.backgroundImageEditable;

			if (
				targetItemIsEditable &&
				!targetItems[0].classList.contains(
					'fragments-editor__editable--highlighted'
				)
			) {
				const fragment = getElements(
					targetItems[0].dataset.fragmentEntryLinkId,
					FRAGMENTS_EDITOR_ITEM_TYPES.fragment
				)[0];

				hoveredItemId = fragment.dataset.fragmentsEditorItemId;
				hoveredItemType = FRAGMENTS_EDITOR_ITEM_TYPES.fragment;
			}
		} else if (targetItems.length > 1) {
			targetItems.forEach(targetItem => {
				targetItem.classList.add(ITEM_CLASS);
				targetItem.classList.add(HOVERED_ITEM_CLASS);
			});
		}

		if (hoveredItemId && hoveredItemType) {
			this.store.dispatch({
				hoveredItemId,
				hoveredItemType,
				type: UPDATE_HOVERED_ITEM
			});
		} else {
			this.store.dispatch({
				type: CLEAR_HOVERED_ITEM
			});
		}
	}
}

/**
 * State definition.
 * @review
 * @static
 * @type {object}
 */
FragmentsEditor.STATE = {
	/**
	 * @default false
	 * @instance
	 * @memberOf FragmentsEditor
	 * @private
	 * @review
	 * @type {boolean}
	 */
	_shiftPressed: Config.bool()
		.internal()
		.value(false),

	/**
	 * Store instance
	 * @default undefined
	 * @instance
	 * @memberOf FragmentsEditor
	 * @review
	 * @type {Store}
	 */
	store: Config.instanceOf(Store),
	...INITIAL_STATE
};

Soy.register(FragmentsEditor, templates);

export {FragmentsEditor};
export default FragmentsEditor;