36 KiB
Frontend Components Module
Overview
The Components module contains 194 TypeScript files that provide the React component library for the Tour Builder Platform. Components are organized by domain, function, and factory patterns to maximize reuse.
Location: frontend/src/components/
Architecture Diagram
frontend/src/components/
│
├── Entity Components (13 directories, 52+ files)
│ ├── Users/ # TableUsers, configureUsersCols, CardUsers, ListUsers
│ ├── Projects/ # TableProjects, configureProjectsCols, ...
│ ├── Assets/ # TableAssets, configureAssetsCols, useAssetUploader
│ ├── Roles/ # TableRoles, configureRolesCols, ...
│ └── ... (9 more entity directories)
│
├── Uploaders/
│ └── UploadService.js # Chunked upload with MIME validation
│
├── Factory Components
│ ├── Factory/createTableComponent.tsx # Table component generator
│ └── DataGrid/configBuilderFactory.tsx # Column config generator
│
├── Generic Components
│ ├── Generic/GenericTable.tsx # Base table with DataGrid
│ └── Generic/GenericFormField.tsx # Form field wrapper
│
├── Constructor Components (15 files)
│ ├── CanvasElement.tsx # Canvas element rendering
│ ├── ElementEditorPanel.tsx # Element settings sidebar
│ ├── ConstructorControlsPanel.tsx # Editor controls
│ └── ...
│
├── ElementSettings (23 files)
│ ├── CommonSettingsSection.tsx # Position, timing
│ ├── NavigationSettingsSection.tsx # Navigation links
│ ├── StyleSettingsSection.tsx # CSS properties
│ └── ... (Full + Compact variants, types, hooks)
│
├── UiElements (16 files)
│ ├── UiElementRenderer.tsx # Unified element renderer
│ ├── shared/useElementWrapperStyle.ts # Shared styling hook
│ ├── GalleryCarouselOverlay.tsx # Gallery/Carousel overlay component
│ └── elements/ (10 per-type components) # NavigationElement, GalleryElement, etc.
│
├── Offline/PWA (5 files)
│ ├── OfflineStatusIndicator.tsx
│ ├── DownloadProgressPanel.tsx
│ └── ...
│
├── Runtime/ (1 file)
│ └── RuntimeControls.tsx # Configurable offline/fullscreen/sound controls
│
├── Layout Components
│ ├── NavBar.tsx # Top navigation
│ ├── AsideMenu.tsx # Sidebar navigation
│ ├── FooterBar.tsx # Footer
│ └── SectionMain.tsx # Main content wrapper
│
└── Standalone Components (~50 files)
├── BaseButton.tsx # Button component
├── CardBox.tsx # Card container
├── FormField.tsx # Form field wrapper
├── RuntimePresentation.tsx # Tour playback
├── RuntimeElement.tsx # Runtime element wrapper
├── TourFlowManager.tsx # Page management
└── ...
Component Categories
1. Entity Components (13 Directories)
Each entity has a dedicated directory with consistent structure.
Entities:
Users/,Roles/,Permissions/Projects/,Project_memberships/Assets/,Asset_variants/Tour_pages/,Project_audio_tracks/Publish_events/,Pwa_caches/Access_logs/,Presigned_url_requests/
Standard Files per Entity:
| File | Purpose | Generator |
|---|---|---|
Table[Entity].tsx |
Data grid table | createTableComponent |
configure[Entity]Cols.tsx |
Column definitions | createColumnLoader |
Card[Entity].tsx |
Card display | Manual |
List[Entity].tsx |
List display | Manual |
Example: TableUsers.tsx (23 LOC)
import { createTableComponent } from '../Factory/createTableComponent';
import {
fetch, update, deleteItem, setRefetch, deleteItemsByIds,
} from '../../stores/users/usersSlice';
import { loadColumns } from './configureUsersCols';
import type { User } from '../../types/entities';
const TableUsers = createTableComponent<User>({
entityName: 'users',
sliceSelector: (state) => state.users,
fetchAction: fetch,
updateAction: update,
deleteAction: deleteItem,
deleteByIdsAction: deleteItemsByIds,
setRefetchAction: setRefetch,
loadColumnsFunction: loadColumns,
});
export default TableUsers;
Example: configureUsersCols.tsx
import { createColumnLoader, ColumnMetadata } from '../DataGrid/configBuilderFactory';
const USERS_COLUMNS: ColumnMetadata[] = [
{ field: 'firstName', headerName: 'First Name', type: 'text', editable: true },
{ field: 'lastName', headerName: 'Last Name', type: 'text', editable: true },
{ field: 'email', headerName: 'E-Mail', type: 'text', editable: true },
{ field: 'disabled', headerName: 'Disabled', type: 'boolean', editable: true },
{
field: 'avatar',
headerName: 'Avatar',
type: 'image',
renderCell: (params) => <ImageField image={params?.row?.avatar} />,
},
{
field: 'app_role',
headerName: 'App Role',
type: 'singleSelectRelation',
entityRef: 'roles',
editable: true,
},
{ field: 'actions', headerName: '', type: 'actions' },
];
export const loadColumns = createColumnLoader({
entityName: 'users',
columns: USERS_COLUMNS,
});
2. Factory Components
createTableComponent (Factory/createTableComponent.tsx)
Purpose: Generates entity table components from configuration.
Input Configuration:
interface TableComponentConfig<T extends BaseEntity> {
entityName: string;
sliceSelector: (state: RootState) => EntitySliceState<T>;
fetchAction: AsyncThunk<...>;
updateAction: AsyncThunk<...>;
deleteAction: AsyncThunk<...>;
deleteByIdsAction: AsyncThunk<...>;
setRefetchAction: (refetch: boolean) => Action;
loadColumnsFunction: (onDelete, entityName, user) => Promise<GridColDef[]>;
}
Output: React component wrapping GenericTable.
Usage Pattern:
const TableAssets = createTableComponent<Asset>({
entityName: 'assets',
sliceSelector: (state) => state.assets,
fetchAction: fetch,
updateAction: update,
deleteAction: deleteItem,
deleteByIdsAction: deleteItemsByIds,
setRefetchAction: setRefetch,
loadColumnsFunction: loadColumns,
});
configBuilderFactory (DataGrid/configBuilderFactory.tsx)
Purpose: Generates MUI X DataGrid column configurations from metadata.
Column Types Supported:
| Type | Description | MUI Type |
|---|---|---|
text |
Plain text | string |
boolean |
Checkbox | boolean |
date |
Date only | dateTime |
datetime |
Date and time | dateTime |
number |
Numeric | number |
relation |
Single relation | singleSelect |
relationMany |
Multiple relations | singleSelect + DataGridMultiSelect |
singleSelectRelation |
Dropdown relation | singleSelect |
image |
Image thumbnail | custom renderCell |
actions |
Row actions | actions |
Features:
- Auto-fetches relation options via
/[entity]/autocomplete?limit=50to match the backend autocomplete limit - Formats
singleSelectRelationvalues from either loaded{ id, label }options or nested relation objects, so list tables display names instead of UUIDs - AsyncPaginate relation fields (
SelectField,SelectFieldMany,RoleSelect) use page size50for the same backend limit - Permission-aware editable fields
- Custom formatters via
dataFormatter - Action column with view/edit/delete popover
3. Generic Components
GenericTable (Generic/GenericTable.tsx)
Purpose: Reusable data grid with full CRUD functionality.
Features:
- MUI X DataGrid v7 integration
- Server-side pagination, sorting, filtering
- Row editing (inline)
- Bulk selection and delete
- Toast notifications
- Filter panel with field selection
Props:
interface GenericTableProps<T extends BaseEntity> {
entityName: string;
sliceSelector: (state: RootState) => EntitySliceState<T>;
fetchAction: AsyncThunk<...>;
updateAction: AsyncThunk<...>;
deleteAction: AsyncThunk<...>;
deleteByIdsAction?: AsyncThunk<...>;
setRefetchAction: (refetch: boolean) => Action;
loadColumnsFunction: (...) => Promise<GridColDef[]>;
filters: Filter[];
filterItems: FilterItem[];
setFilterItems: (items: FilterItem[]) => void;
extraQuery?: string;
}
Data Flow:
1. Component mounts → loadColumnsFunction loads columns
2. fetchAction dispatched with pagination/sort/filter query
3. Data stored in Redux slice under entityName key
4. DataGrid renders rows with server-side pagination
5. Edit saves via updateAction
6. Delete via deleteAction or deleteByIdsAction (bulk)
4. Constructor Components (15 files)
Components for the visual tour builder interface.
| Component | Purpose | LOC |
|---|---|---|
CanvasElement.tsx |
Renders element on canvas with positioning | 62 |
ElementEditorPanel.tsx |
Settings sidebar with tabs (General/CSS/Effects) | 592 |
ConstructorToolbar.tsx |
Floating main constructor toolbar with mode, page, element, save, stage, exit, and collapse actions | ~490 |
ConstructorControlsPanel.tsx |
Legacy/secondary controls panel for constructor actions | ~200 |
ConstructorMenu.tsx |
Left menu with element types | ~150 |
BackgroundSettingsEditor.tsx |
Image/video/audio background selection | ~100 |
CreateTransitionForm.tsx |
Transition video creation form | ~80 |
ElementEditorHeader.tsx |
Draggable header for editor panel | ~50 |
PageSelector.tsx |
Tour page dropdown selector with ordinal labels and optional toolbar sizing class | ~60 |
InteractionModeToggle.tsx |
Edit vs Interact mode toggle with compact toolbar layout | ~40 |
MenuActionButton.tsx |
Reusable menu button | ~30 |
AssetSelectCompact.tsx |
Asset dropdown selector | ~50 |
CanvasBackground.tsx |
Background rendering (image/video) | ~80 |
TransitionPreviewOverlay.tsx |
Video transition preview | ~100 |
index.ts |
Barrel exports | ~20 |
types.ts |
Constructor type definitions | ~50 |
ConstructorToolbar.tsx action layout:
- The left block contains the Edit/Interact mode toggle.
Page actionsgroups page selector, page reorder, new page, duplicate page, delete page, and background controls. All page-level controls use consistent 40px control height and explicit vertical dividers.Elements actionsgroups the Add Elements dropdown and element Copy/Paste buttons. Copy is enabled only when an element is selected; Paste is enabled only when the constructor-local element clipboard has content.- Save, Stage, Exit, and Collapse are kept in the final action group. Save and Stage use fixed compact widths and reserve the timestamp subtitle row even when no timestamp is available, so the action group remains vertically aligned as save status changes.
- Section labels are centered above their controls to distinguish page-level copy/duplicate from element copy/paste.
Page management controls:
PageSelector.tsxdisplays page labels with order prefixes so users can see the current sequence directly in the dropdown.- Page move buttons call the constructor page reorder handler with
upordown; the handler persists the full ordered ID list viaPOST /api/tour_pages/reorder. - Duplicate page saves the current dev page first, then calls
POST /api/tour_pages/:id/duplicate; the backend appends the new independent page as the last page and regenerates page element IDs. - Delete page opens the constructor confirmation modal and then calls the same
DELETE /api/tour_pages/:idendpoint used by Pages & Transitions. - Buttons are disabled while their matching async action is saving, when the user lacks the required permission, and at first/last page boundaries for reorder.
Element clipboard controls:
- Element Copy/Paste lives in the main toolbar, not in the element editor panel, so users can copy an element, switch pages, and paste it into another page.
- The clipboard is session-local to the active constructor page instance and is
managed by
useConstructorElements. - Paste appends a deep clone to the active page's inline
ui_schema_json.elements[]data. It preserves all settings, including position, dimensions, links, navigation targets, transition fields, CSS styles, and effects, while regenerating element and nested item IDs.
CanvasElement.tsx Architecture:
interface CanvasElementProps {
element: CanvasElementType;
isSelected: boolean;
isEditMode: boolean;
isDisabled?: boolean;
onClick: () => void;
onMouseDown?: (event: React.MouseEvent) => void;
resolveUrl?: (url: string | undefined) => string;
}
const CanvasElement: React.FC<CanvasElementProps> = ({
element, isSelected, isEditMode, isDisabled, onClick, onMouseDown, resolveUrl,
}) => {
return (
<button
type='button'
data-constructor-element-id={element.id}
className='absolute'
style={{
left: `${element.xPercent}%`,
top: `${element.yPercent}%`,
transform: 'translate(-50%, -50%)',
background: 'transparent',
border: 'none',
padding: 0,
margin: 0,
}}
onMouseDown={isEditMode ? onMouseDown : undefined}
onClick={onClick}
>
<UiElementRenderer
element={element}
resolveUrl={resolveUrl}
isSelected={isSelected}
isEditMode={isEditMode}
isDisabled={isDisabled}
/>
</button>
);
};
ElementEditorPanel Tabs:
| Tab | Section | Properties |
|---|---|---|
| General | CommonSettingsSection | label, position, timing |
| General | NavigationSettingsSection | type, target, transition |
| General | TooltipSettingsSection | title, text, icon |
| General | DescriptionSettingsSection | title, text, fonts, colors |
| General | MediaSettingsSection | url, autoplay, loop, muted |
| General | GallerySettingsSection | cards (image, title, link) |
| General | CarouselSettingsSection | slides, navigation icons |
| CSS | StyleSettingsSection | width, height, margin, padding, etc. |
| Effects | EffectsSettingsSection | appear animation, hover (incl. hover reveal), focus, active |
5. ElementSettings Components (23 files)
Settings panels for element configuration in the constructor.
Full-Size Variants:
CommonSettingsSection.tsxNavigationSettingsSection.tsxTooltipSettingsSection.tsxDescriptionSettingsSection.tsxMediaSettingsSection.tsxGallerySettingsSection.tsxCarouselSettingsSection.tsxInfoPanelSettingsSection.tsxStyleSettingsSection.tsxEffectsSettingsSection.tsxElementSettingsTabs.tsx
Compact Variants (for ElementEditorPanel):
CommonSettingsSectionCompact.tsxNavigationSettingsSectionCompact.tsxTooltipSettingsSectionCompact.tsxDescriptionSettingsSectionCompact.tsxMediaSettingsSectionCompact.tsxGallerySettingsSectionCompact.tsxGalleryCarouselSettingsSectionCompact.tsx(shared gallery/carousel settings)CarouselSettingsSectionCompact.tsxInfoPanelSettingsSectionCompact.tsxStyleSettingsSectionCompact.tsxEffectsSettingsSectionCompact.tsx
Info Panel Settings:
- The constructor General tab renders
InfoPanelSettingsSectionCompact. Open by defaultwritesinfoPanelOpenByDefaultto the element JSON.- Global and project element-default detail pages render the full
InfoPanelSettingsSection, so Info Panel default state can be configured at platform, project, and instance scope. - Disabled Info Panels (
infoPanelDisabled) do not open by click or by default state.
Supporting Files:
index.ts- Barrel exportstypes.ts- Type definitions and unit normalization helpersuseElementSettingsForm.ts- Form state management hook
Unit Normalization Helpers (types.ts):
The types.ts file provides helper functions for CSS value handling:
// Extract numeric value from CSS value with unit
extractNumericValue('24vw') // '24'
extractNumericValue('100px') // '100'
// Convert value to CSS value with unit (robust normalization)
toUnitValue('24', 'vw') // '24vw'
toUnitValue('24vw', 'vw') // '24vw' (preserved)
toUnitValue('0', 'px') // '0' (no unit for zero)
toUnitValue('calc(100%)', 'px') // 'calc(100%)' (CSS functions preserved)
toUnitValue('10px 20px', 'px') // '10px 20px' (complex values preserved)
// Normalize number string (removes invalid chars)
normalizeNumberString('24.5') // '24.5'
normalizeNumberString('abc') // ''
// Convert to optional trimmed value
toOptionalTrimmed('') // undefined
toOptionalTrimmed(' value ') // 'value'
Edge Cases Handled by toUnitValue:
| Input | Output | Reason |
|---|---|---|
"24" |
"24px" |
Plain number → add unit |
"24px" |
"24px" |
Has unit → preserve |
"0" |
"0" |
Zero → no unit needed |
"-10" |
"-10px" |
Negative → add unit |
".5" |
"0.5rem" |
Decimal → add unit |
"10px 20px" |
"10px 20px" |
Complex → preserve |
"calc(100% - 20px)" |
preserved | CSS function → preserve |
"var(--spacing)" |
preserved | CSS variable → preserve |
Index Re-exports:
// ElementSettings/index.ts
export { ElementSettingsTabsCompact } from './ElementSettingsTabs';
export { StyleSettingsSectionCompact } from './StyleSettingsSectionCompact';
export { EffectsSettingsSectionCompact } from './EffectsSettingsSectionCompact';
export { extractNumericValue, toUnitValue, toOptionalTrimmed } from './types';
// ... etc
6. UiElements Components (16 files)
Unified element rendering for WYSIWYG consistency between Constructor and Runtime.
Directory Structure:
UiElements/
├── UiElementRenderer.tsx # Main entry point
├── GalleryCarouselOverlay.tsx # Fullscreen overlay for gallery/carousel and Info Panel media
├── shared/
│ └── useElementWrapperStyle.ts # Shared styling hook
├── elements/
│ ├── NavigationElement.tsx # Forward/back buttons
│ ├── GalleryElement.tsx # Image grid
│ ├── TooltipElement.tsx # Tooltip popup
│ ├── DescriptionElement.tsx # Styled text block
│ ├── CarouselElement.tsx # Image slideshow
│ ├── LogoElement.tsx # Logo display
│ ├── SpotElement.tsx # Hotspot indicator
│ ├── VideoPlayerElement.tsx # Video with controls
│ ├── AudioPlayerElement.tsx # Audio with controls
│ └── PopupElement.tsx # Popup trigger
├── ElementPreview.tsx # Palette preview (separate system)
├── defaults.ts # Default values
└── types.ts # Type definitions
UiElementRenderer.tsx
Purpose: Unified entry point for rendering UI elements with consistent styling. Used by both CanvasElement (constructor) and RuntimeElement (presentation).
Architecture:
┌─────────────────────────────────────────────────────────────┐
│ UiElementRenderer - unified styling + content │
│ ├── useElementWrapperStyle (shared hook) │
│ └── Per-type components (10 files) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ CONSTRUCTOR: CanvasElement (position, selection, drag) │
│ └── UiElementRenderer │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ RUNTIME: RuntimeElement (position, effects) │
│ └── UiElementRenderer │
└─────────────────────────────────────────────────────────────┘
Props:
interface UiElementRendererProps {
element: CanvasElement;
resolveUrl?: (url: string | undefined) => string;
// Constructor-specific props (optional)
isSelected?: boolean;
isEditMode?: boolean;
isDisabled?: boolean;
}
useElementWrapperStyle Hook
Purpose: Single source of truth for element wrapper styling. Ensures constructor and runtime render elements identically.
interface UseElementWrapperStyleOptions {
element: CanvasElement;
isSelected?: boolean; // Constructor only
isEditMode?: boolean; // Constructor only
isDisabled?: boolean; // Constructor only
}
interface ElementWrapperStyle {
className: string; // Tailwind classes
style: CSSProperties; // Inline styles from buildElementStyle
}
Per-Type Element Components
Each element type has a dedicated component that handles both wrapper styling and content:
| Component | Element Type | Rendering |
|---|---|---|
NavigationElement |
navigation_next/prev |
Icon image or text label |
TooltipElement |
tooltip |
Icon or title/text popup |
DescriptionElement |
description |
Icon or styled title/text block |
VideoPlayerElement |
video_player |
<video> with controls |
AudioPlayerElement |
audio_player |
<audio> with controls |
GalleryElement |
gallery |
3-column image grid |
CarouselElement |
carousel |
Slides with dots and nav icons |
LogoElement |
logo |
Icon image or text |
SpotElement |
spot |
Icon or animated pulse circle |
PopupElement |
popup |
Text label |
Per-Type Component Interface:
interface ElementComponentProps {
element: CanvasElement;
resolveUrl?: (url: string | undefined) => string;
className: string; // From useElementWrapperStyle
style: CSSProperties; // From useElementWrapperStyle
}
7. Offline/PWA Components (4 files)
| Component | Purpose |
|---|---|
OfflineStatusIndicator.tsx |
Shows online/offline status |
DownloadProgressPanel.tsx |
Shows download progress for PWA caching |
OfflineToggle.tsx |
Enable/disable offline mode |
StorageUsageDisplay.tsx |
Shows storage quota usage |
8. Uploaders (1 file)
UploadService.js
Purpose: File upload service with chunked uploads and MIME type validation.
Location: frontend/src/components/Uploaders/UploadService.js
Key Features:
- Single-file upload via
upload() - Chunked upload via
uploadChunked()for large files - MIME type validation with extension fallback
- Progress callbacks and abort support
MIME Type Validation:
const VALID_MIME_TYPES = {
image: {
prefixes: ['image/'],
extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'],
},
video: {
prefixes: ['video/'],
extensions: ['mp4', 'webm', 'mov', 'avi', 'mkv', 'm4v', 'ogv'],
},
audio: {
prefixes: ['audio/'],
extensions: ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'weba'],
},
};
Validation Schema:
| Property | Type | Description |
|---|---|---|
assetType |
string | Asset type: 'image', 'video', or 'audio' |
image |
boolean | Legacy: Must be an image |
video |
boolean | Legacy: Must be a video |
audio |
boolean | Legacy: Must be audio |
size |
number | Max file size in bytes |
formats |
string[] | Allowed file extensions |
Usage:
// Chunked upload with validation
const result = await FileUploader.uploadChunked(
'assets/project-id',
file,
{ assetType: 'image' }, // Validation schema
{
chunkSize: 5 * 1024 * 1024,
maxRetries: 3,
signal: abortController.signal,
onProgress: (percent) => console.log(`${percent}%`),
onStatus: (status) => console.log(status),
}
);
See Asset Upload & Variants for complete upload flow documentation.
9. Layout Components
NavBar.tsx
Purpose: Top navigation bar with responsive menu.
Features:
- Fixed position with scroll shadow
- Mobile menu toggle
- Integrates with
NavBarMenuListfor menu items - Theme-aware background
type Props = {
menu: MenuNavBarItem[];
className: string;
children: ReactNode;
};
export default function NavBar({ menu, className, children }: Props) {
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
// ...
}
AsideMenu.tsx
Purpose: Sidebar navigation with mobile responsiveness.
type Props = {
menu: MenuAsideItem[];
isAsideMobileExpanded: boolean;
isAsideLgActive: boolean;
onAsideLgClose: () => void;
};
SectionMain.tsx
Purpose: Main content wrapper with consistent padding.
FooterBar.tsx
Purpose: Footer with copyright.
10. Standalone Components (~50 files)
Key standalone components used throughout the application.
BaseButton.tsx
Purpose: Universal button component with variants.
type Props = {
label?: string;
/** Optional subtitle displayed below the label in smaller text */
subtitle?: string;
icon?: string;
iconSize?: string | number;
href?: string;
target?: string;
type?: string;
color?: ColorButtonKey;
className?: string;
small?: boolean;
outline?: boolean;
active?: boolean;
disabled?: boolean;
roundedFull?: boolean;
onClick?: (e: React.MouseEvent) => void;
};
Features:
- Link or button rendering
- Icon support with BaseIcon
- Color variants via
getButtonColor - Theme-aware corners from Redux
- Subtitle support for secondary text (e.g., timestamps)
Subtitle Usage:
// Show publish timestamp below button label
<BaseButton
label="Publish to Production"
subtitle={lastPublishedAt ? `Last: ${relativeTimestamp(lastPublishedAt)}` : undefined}
color="success"
onClick={handlePublish}
/>
// Renders:
// ┌──────────────────────────┐
// │ Publish to Production │
// │ Last: 5 min ago │ ← smaller, 80% opacity
// └──────────────────────────┘
CardBox.tsx
Purpose: Card container with consistent styling.
type Props = {
rounded?: string;
flex?: string;
className?: string;
hasComponentLayout?: boolean;
hasTable?: boolean;
isHoverable?: boolean;
isModal?: boolean;
children?: ReactNode;
footer?: ReactNode;
};
FormField.tsx
Purpose: Form field wrapper with label and help text.
Features:
- Multi-column grid support (2-3 children)
- Icon prefix support
- Theme-aware styling
RuntimePresentation.tsx (18.5KB)
Purpose: Full-screen tour playback component.
Hooks Used:
usePageDataLoader- Load page datausePreloadOrchestrator- Asset preloadingusePageSwitch- Page navigationuseTransitionPlayback- Video transitionsuseBackgroundTransition- Background changes
Features:
- Fullscreen mode
- Keyboard navigation (ArrowLeft/Right, Escape)
- Touch gestures
- Transition videos
- Element effects (appear animation, hover)
RuntimeElement.tsx
Purpose: Element wrapper for runtime with interactive effects. Delegates styling and content to UiElementRenderer.
interface RuntimeElementProps {
element: any;
onClick: () => void;
resolveUrl?: (url: string | undefined) => string;
}
const RuntimeElement: React.FC<RuntimeElementProps> = ({
element,
onClick,
resolveUrl,
}) => {
// Extract effect properties for hover/focus/active states
const { effectStyle, eventHandlers } = useElementEffects(effectProperties);
// Build position style with effects merged
let positionStyle: React.CSSProperties = {
left: `${xPercent}%`,
top: `${yPercent}%`,
transform: `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''}`,
};
// Merge effect styles and animations...
return (
<div
className='absolute cursor-pointer'
style={positionStyle}
onClick={onClick}
tabIndex={0}
{...eventHandlers}
>
<UiElementRenderer element={element} resolveUrl={resolveUrl} />
</div>
);
};
TourFlowManager.tsx (17KB)
Purpose: Page and transition management interface.
Features:
- Page list with thumbnails
- Drag-and-drop reordering
- Transition configuration
- Page CRUD operations
- Sort order management
Other Notable Components
| Component | Purpose |
|---|---|
BaseIcon.tsx |
MDI icon wrapper |
ImageField.tsx |
Image display with fallback |
Search.tsx |
Global search input |
SectionTitleLineWithButton.tsx |
Section header with action |
DataGridMultiSelect.tsx |
Multi-select for DataGrid |
ListActionsPopover.tsx |
Row actions dropdown |
Divider.tsx |
Horizontal divider |
NotificationBar.tsx |
Toast notification wrapper |
OverlayLayer.tsx |
Modal overlay background |
PillTag.tsx |
Tag/badge component |
UserAvatar.tsx |
User avatar display |
Component Patterns
1. Factory Pattern
Tables: 13 entity tables generated from configuration. Columns: 13 column configs generated from metadata.
Benefit: ~1500 LOC reduced to ~300 LOC configuration.
2. Theme-Aware Styling
Components read theme values from Redux:
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
3. Composition Pattern
Complex components compose smaller ones:
RuntimePresentation
├── RuntimeElement (×N)
│ └── UiElementRenderer
│ └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── TransitionVideoOverlay
Constructor Page
├── CanvasElement (×N)
│ └── UiElementRenderer
│ └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── ElementEditorPanel
4. Compact Variants
Settings sections have two variants:
- Full: Standalone use with labels
- Compact: Embedded use with compact layout
5. URL Resolution Pattern
Components accept optional URL resolver for blob URLs:
interface Props {
resolveUrl?: (url: string | undefined) => string;
}
// Usage
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
const src = resolve(element.iconUrl);
File Count Summary
| Category | Files | LOC (est.) |
|---|---|---|
| Entity Components | 52 | ~1000 |
| Factory Components | 2 | ~420 |
| Generic Components | 2 | ~500 |
| Constructor | 15 | ~1600 |
| ElementSettings | 23 | ~2200 |
| UiElements | 16 | ~750 |
| Offline | 5 | ~350 |
| Layout | 10 | ~500 |
| Standalone | ~50 | ~4000 |
| Total | ~175 | ~11,300 |
Component Dependencies
┌─────────────────────────────────────────────────────┐
│ Pages │
│ (dashboard.tsx, assets-list.tsx, constructor.tsx) │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Entity Components │
│ (TableUsers, TableAssets, TableProjects, ...) │
│ │
│ Uses: createTableComponent + configBuilderFactory │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ GenericTable │
│ MUI X DataGrid + filters + pagination + editing │
│ │
│ Uses: CardBox, BaseButton, Formik │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Base Components │
│ BaseButton, CardBox, FormField, BaseIcon │
│ │
│ Uses: Redux style selectors, Tailwind CSS │
└─────────────────────────────────────────────────────┘
Styling System
Tailwind CSS Classes
Components use Tailwind utilities with theme values:
const componentClass = [
`flex dark:border-dark-700 dark:bg-dark-900`,
corners !== 'rounded-full' ? corners : 'rounded-3xl',
cardsStyle,
];
Dark Mode
All components support dark mode via:
dark:prefix classesdarkModestate from Redux- Applied at layout level
Dynamic Styling
Constructor elements use inline styles from buildElementStyle:
import { buildElementStyle } from '../../lib/elementStyles';
const style = buildElementStyle(element);
// Returns: { width, height, backgroundColor, color, fontSize, ... }
Best Practices
1. Use Factories for Entity Components
// Good - 23 LOC
const TableUsers = createTableComponent<User>({ ... });
// Avoid - 400+ LOC of boilerplate
2. Theme-Aware Styling
// Good - reads from Redux
const corners = useAppSelector((state) => state.style.corners);
// Avoid - hardcoded
className="rounded-lg"
3. URL Resolution for Assets
// Good - supports preloaded blob URLs
<img src={resolve(element.iconUrl)} />
// Avoid - only works online
<img src={element.iconUrl} />
4. Compact Variants for Embedded Use
// Good - use compact in panels
<CommonSettingsSectionCompact {...props} />
// Avoid - full variant in compact space
<CommonSettingsSection {...props} />
Related Documentation
- Frontend Architecture - Overall frontend structure
- Pages Module - Page components
- Hooks Reference - Custom hooks
- Constructor Page Editor - Visual builder
- Runtime Presentation - Tour playback