components/SlidePage/SlidePage.jsxjavascript
import React from "react";
import { useParams, useNavigate, useLocation, Navigate } from "react-router";
import { useContent } from "../../services/content";
import { navigateWithTransition } from "../../utils/viewTransition";
import { ComponentRenderer } from "../ComponentRenderer";
import { SlideNavigation } from "../SlideNavigation/SlideNavigation";
import { getBlockRegion } from "../elements/registry";
/**
* Shared skeleton of the slide-based presentation pages (ValueAdvisoryServices,
* RadEnablement): viewId routing, slide navigation, element-bucket layout and
* the content error/redirect states. The pages themselves only supply their
* constants, their CSS (via `classPrefix`) and an optional header.
*
* @param {string} basePath - route prefix, e.g. "/rad-enablement"
* @param {string} startViewId - view shown at the bare route, e.g. "rad-intro"
* @param {string} storyHubViewId - hub view (see getAdjacentRootViews), also the menu's Home target
* @param {string} classPrefix - CSS class prefix, e.g. "rad" -> "rad-page", "rad-slide-content"
* @param {function(string): *} [renderHeader] - optional per-page header, returning a React node
*/
export const SlidePage = ({ basePath, startViewId, storyHubViewId, classPrefix, renderHeader }) => {
const { viewId = startViewId } = useParams();
const navigate = useNavigate();
const { search } = useLocation();
const { views, error, getView, getAdjacentRootViews } = useContent();
// the story hub's selectWidget picks the target view (its options' ids are
// view ids, e.g. "cancer-1") rather than following a fixed nextView; the
// choice only arms the "next" button, it doesn't navigate by itself, so
// it's reset whenever the current slide changes
const [selectedTargetViewId, setSelectedTargetViewId] = React.useState(null);
React.useEffect(() => {
setSelectedTargetViewId(null);
}, [viewId]);
// keeps the current URL params (kiosk/device config, see config-schema.js -
// useConfig re-reads them from the URL on every render) alive across slide
// navigation, and cross-fades the content
const navigateToView = React.useCallback(
(targetViewId) => {
navigateWithTransition(navigate, `${basePath}/${targetViewId}${search}`);
},
[navigate, search, basePath]
);
// visible failure state instead of a blank kiosk screen; ContentProvider
// keeps retrying in the background (see services/content.jsx)
if (error) {
return (
<div className={`${classPrefix}-page page content-error`}>
<p>Inhalte konnten nicht geladen werden.</p>
<p>Es wird automatisch ein neuer Versuch gestartet …</p>
</div>
);
}
const view = getView(viewId);
// a typo'd/stale view id would otherwise render an empty page without any
// navigation (SlideNavigation hides itself when adjacency is empty)
if (views.size > 0 && !view) {
return <Navigate to={`${basePath}/${startViewId}${search}`} replace />;
}
const blocks = view?.elements || [];
const { prevId, nextId } = getAdjacentRootViews(viewId, { storyHubViewId });
const effectiveNextId = selectedTargetViewId || nextId;
// each layout region gets its own wrapper below; a block's region comes
// from its element's registry default, overridable per block in the JSON
const blocksIn = (region) => blocks.filter((block) => getBlockRegion(block) === region);
const slideStoryTitleBlocks = blocksIn("storyTitle");
const slideHeadlineBlocks = blocksIn("headline");
const contentBlocks = blocksIn("content");
const renderElement = (block, index) => (
<ComponentRenderer
key={`${block.type}-${index}`}
block={block}
selectedTargetViewId={selectedTargetViewId}
onSelectTarget={setSelectedTargetViewId}
onNavigate={navigateToView}
/>
);
return (
<div className={`${classPrefix}-page page`}>
{renderHeader?.(viewId)}
{slideStoryTitleBlocks.length > 0 && (
<div className={`${classPrefix}-slide-story-title`}>
{slideStoryTitleBlocks.map(renderElement)}
</div>
)}
<div className={`${classPrefix}-slide-content`}>
{slideHeadlineBlocks.length > 0 && (
<div className={`${classPrefix}-slide-headline-container`}>
{slideHeadlineBlocks.map(renderElement)}
</div>
)}
{contentBlocks.map(renderElement)}
</div>
<SlideNavigation
prevViewId={prevId}
nextViewId={effectiveNextId}
homeViewId={storyHubViewId}
currentViewId={viewId}
onNavigate={navigateToView}
/>
</div>
);
};