Source: data-engine-js-components-web/src/main/resources/META-INF/resources/js/core/components/PageRenderer/Page.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 ClayIcon from '@clayui/icon';
import {isObject} from 'frontend-js-web';
import React from 'react';

import * as MultiPages from '../../../custom/form/renderer/MultiPagesVariant.es';
import * as Paginated from '../../../custom/form/renderer/PaginatedVariant.es';
import * as SuccessPage from '../../../custom/form/renderer/SuccessVariant.es';
import * as Wizard from '../../../custom/form/renderer/WizardVariant.es';
import {PagesVisitor} from '../../../utils/visitors.es';
import {PageProvider} from '../../hooks/usePage.es';
import {mergeVariants} from '../../utils/merge-variants.es';
import * as DefaultVariant from './DefaultVariant.es';
import {Layout} from './Layout.es';
import * as Tabbed from './TabbedVariant.es';
import {VariantsProvider} from './VariantsContext.es';

const LAYOUT_TYPES = {
	MULTI_PAGES: 'multi-pages',
	PAGINATED: 'paginated',
	SINGLE_PAGE: 'single-page',
	SUCCESS: 'success',
	TABBED: 'tabbed',
	WIZARD: 'wizard',
};

const LAYOUT_COMPONENTS_TYPES = {
	[LAYOUT_TYPES.MULTI_PAGES]: MultiPages,
	[LAYOUT_TYPES.PAGINATED]: Paginated,
	[LAYOUT_TYPES.SINGLE_PAGE]: {
		Container: DefaultVariant.Container,
	},
	[LAYOUT_TYPES.SUCCESS]: SuccessPage,
	[LAYOUT_TYPES.TABBED]: Tabbed,
	[LAYOUT_TYPES.WIZARD]: Wizard,
};

const DDM_FORM_PORTLET_NAMESPACE =
	'_com_liferay_dynamic_data_mapping_form_web_portlet_DDMFormPortlet_';

const isEmptyPage = ({rows}) => {
	let empty = false;

	if (!rows || !rows.length) {
		empty = true;
	}
	else {
		empty = !rows.some(({columns}) => {
			let hasFields = true;

			if (!columns) {
				hasFields = false;
			}
			else {
				hasFields = columns.some((column) => column.fields.length);
			}

			return hasFields;
		});
	}

	return empty;
};

const normalizePage = (page, editingLanguageId) => {
	if (isObject(page.description)) {
		page = {
			...page,
			description: page.description[editingLanguageId],
		};
	}
	if (isObject(page.title)) {
		page = {
			...page,
			title: page.title[editingLanguageId],
		};
	}

	return page;
};

const getVariant = ({page, pages, paginationMode}) => {
	if (
		page.contentRenderer &&
		page.contentRenderer !== null &&
		page.contentRenderer !== ''
	) {
		return page.contentRenderer;
	}
	else if (paginationMode === null || paginationMode === '') {
		if (pages.length > 1) {
			return LAYOUT_TYPES.WIZARD;
		}
		else {
			return LAYOUT_TYPES.SINGLE_PAGE;
		}
	}
	else {
		return paginationMode;
	}
};

/**
 * Renderer is the main component for dealing with Forms `pages` structure,
 * you can use the renderer to render fields from different layouts.
 *
 * Renderer supports some rendering modes by default
 * - multi-pages
 * - paginated
 * - single-page
 * - success
 * - tabbed
 * - wizard
 *
 * You can replace all major components or just a few to change the layout
 * structure, consider the following components
 * - Container
 * - Page
 * - Rows
 * - Row
 * - Column
 *
 * Use the `overrides` API to pass the components you want to replace,
 * an example of Page.
 * <Page
 * 	overrides={{
 * 	  Page: ({children}) => children
 * 	}}
 * />
 */
const Page = ({
	activePage = 0,
	editable = false,
	editingLanguageId,
	forceAriaUpdate,
	invalidFormMessage,
	overrides = {},
	page: defaultPage,
	pageIndex = 0,
	pages,
	paginationMode,
	portletNamespace,
	readOnly,
	strings,
	viewMode,
}) => {
	const empty = isEmptyPage(defaultPage);
	const page = normalizePage(defaultPage, editingLanguageId);

	const variantName = getVariant({page, pages, paginationMode});
	const variantComponents = LAYOUT_COMPONENTS_TYPES[variantName] || {};

	const variants = {
		defaults: DefaultVariant,
		overrides,
		variant: variantComponents,
	};

	const Components = mergeVariants(editable, variants);

	let hasFieldRequired = false;

	if (pages?.[activePage]) {
		const visitor = new PagesVisitor([pages[activePage]]);

		visitor.mapFields((field) => {
			if (field.required) {
				hasFieldRequired = true;
			}
		});
	}

	const isDDMFormPortletNamespace = portletNamespace.includes(
		DDM_FORM_PORTLET_NAMESPACE
	);

	return (
		<VariantsProvider components={variants}>
			<Components.Container
				activePage={activePage}
				editable={editable}
				empty={empty}
				page={page}
				pageIndex={pageIndex}
				pages={pages}
				readOnly={readOnly}
				strings={strings}
			>
				<Components.Page
					activePage={activePage}
					editable={editable}
					empty={empty}
					forceAriaUpdate={forceAriaUpdate}
					header={
						variantName === LAYOUT_TYPES.SINGLE_PAGE ? null : (
							<Components.PageHeader {...page} />
						)
					}
					invalidFormMessage={invalidFormMessage}
					page={page}
					pageIndex={pageIndex}
				>
					{hasFieldRequired && isDDMFormPortletNamespace && (
						<p aria-hidden="true" className="text-secondary">
							<span className="c-mr-1 reference-mark">
								<ClayIcon symbol="asterisk" />
							</span>

							{Liferay.Language.get('indicates-required-fields')}
						</p>
					)}

					<Layout
						components={Components}
						editable={editable}
						rows={page.rows}
						viewMode={viewMode}
					/>
				</Components.Page>
			</Components.Container>
		</VariantsProvider>
	);
};

const PageWithProvider = (props) => (
	<PageProvider value={props}>
		<Page {...props} />
	</PageProvider>
);

export default PageWithProvider;